release: WRNexusJS 0.2.36
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"name": "wrnexus",
|
||||
"displayName": "WRNexus Language Support",
|
||||
"description": "Complete language support for WRNexus .wrn files, including highlighting, formatting, diagnostics, snippets, lifecycle hooks, state watchers, component functions, completions, and definition navigation.",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.9",
|
||||
"publisher": "wrnexus",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
@@ -225,7 +225,30 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"semanticTokenScopes": [
|
||||
{
|
||||
"language": "wrn",
|
||||
"scopes": {
|
||||
"variable.declaration": [
|
||||
"variable.other.readwrite.wrn",
|
||||
"variable.other.readwrite.declaration.wrn"
|
||||
],
|
||||
"variable": [
|
||||
"variable.other.readwrite.wrn"
|
||||
],
|
||||
"parameter": [
|
||||
"variable.parameter.wrn"
|
||||
],
|
||||
"function": [
|
||||
"entity.name.function.wrn"
|
||||
],
|
||||
"property": [
|
||||
"variable.other.property.wrn"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build:compiler": "bun build ../../packages/compiler/src/index.ts --target=node --format=cjs --outfile=src/compiler.cjs",
|
||||
|
||||
@@ -546,14 +546,22 @@ function parseHtmlView(src, pos) {
|
||||
return src.slice(start, i);
|
||||
};
|
||||
const readAttributeName = () => {
|
||||
const first = src[i];
|
||||
if (i >= src.length || !isNameStart(first) && first !== ":" && first !== "$") {
|
||||
if (i >= src.length) {
|
||||
return fail("Expected an attribute name");
|
||||
}
|
||||
const start = i++;
|
||||
while (i < src.length && isAttributeNamePart(src[i], src[i + 1])) {
|
||||
const start = i;
|
||||
while (i < src.length) {
|
||||
const char = src[i];
|
||||
const next = src[i + 1];
|
||||
if (char === "=" || char === ">" || char === '"' || char === "'" || char === " " || char === "\t" || char === `
|
||||
` || char === "\r" || char === "/" && next === ">") {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i === start) {
|
||||
return fail("Expected an attribute name");
|
||||
}
|
||||
return src.slice(start, i);
|
||||
};
|
||||
const parseTag = () => {
|
||||
@@ -1340,7 +1348,8 @@ function behaviorAttribute(behavior) {
|
||||
if (!behavior) {
|
||||
return "";
|
||||
}
|
||||
return ` data-wrn-behavior="${attrEscape(JSON.stringify(behavior))}"`;
|
||||
const encoded = Buffer.from(JSON.stringify(behavior), "utf8").toString("base64");
|
||||
return ` data-wrn-behavior="${encoded}"`;
|
||||
}
|
||||
var INTERP_RE = /\{([^{}]+)\}/g;
|
||||
function exprRefsState(expr, stateNames) {
|
||||
@@ -1435,14 +1444,17 @@ function renderComponentNode(node, ctx) {
|
||||
const initialConditionalClasses = conditionalClasses.map(({ className, expression }) => {
|
||||
return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`;
|
||||
}).join("");
|
||||
const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 ? ` class="${compileAttrValue(staticClasses.join(" "), ctx)}${initialConditionalClasses}"` : "";
|
||||
const staticClassValue = staticClasses.join(" ");
|
||||
const classHasReactiveExpression = staticClassValue.includes("{") && exprRefsState(staticClassValue, ctx.stateNames);
|
||||
const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 ? ` class="${compileAttrValue(staticClassValue, ctx)}${initialConditionalClasses}"` : "";
|
||||
const classReactiveBinding = classHasReactiveExpression ? ` data-wrn-bind-class="${escLit(attrEscape(JSON.stringify(["class", staticClassValue])))}"` : "";
|
||||
const classBindings = conditionalClasses.map(({ className, expression }, index) => {
|
||||
const marker = attrEscape(JSON.stringify([className, expression]));
|
||||
return ` data-wrn-class-${index}="${escLit(marker)}"`;
|
||||
}).join("");
|
||||
const loops = loopVarsOf(node);
|
||||
const childCtx = loops.length > 0 ? { ...ctx, loopVars: new Set([...ctx.loopVars ?? [], ...loops]) } : ctx;
|
||||
const allAttrs = `${classAttribute}${classBindings}${attrs}`;
|
||||
const allAttrs = `${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
|
||||
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
|
||||
return `<${node.tag}${allAttrs}>`;
|
||||
}
|
||||
|
||||
@@ -271,10 +271,227 @@ function activate(context) {
|
||||
registerCompletionProvider(context);
|
||||
registerDefinitionProvider(context);
|
||||
registerFormatter(context);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerDocumentSemanticTokensProvider(
|
||||
{
|
||||
language: "wrn",
|
||||
},
|
||||
wrnSemanticTokensProvider,
|
||||
semanticTokenLegend,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function deactivate() {}
|
||||
|
||||
const semanticTokenLegend = new vscode.SemanticTokensLegend(
|
||||
["variable"],
|
||||
["declaration", "modification"],
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @returns {string}
|
||||
*/
|
||||
function escapeRegExp(value) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {Map<string, Set<number>>}
|
||||
*/
|
||||
function collectStateVariables(text) {
|
||||
const states = new Map();
|
||||
const statePattern = /\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm;
|
||||
|
||||
let match;
|
||||
|
||||
while ((match = statePattern.exec(text)) !== null) {
|
||||
const name = match[1];
|
||||
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const offset = match.index + match[0].lastIndexOf(name);
|
||||
const declarations = states.get(name) || new Set();
|
||||
|
||||
declarations.add(offset);
|
||||
states.set(name, declarations);
|
||||
}
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comments are excluded, but quoted attribute expressions are deliberately
|
||||
* included because WRN state references commonly appear in values such as
|
||||
* class:flex="centered" and @click="enabled = !enabled".
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {Array<{start: number, end: number}>}
|
||||
*/
|
||||
function collectCommentRanges(text) {
|
||||
const ranges = [];
|
||||
let index = 0;
|
||||
let quote = null;
|
||||
let escaped = false;
|
||||
|
||||
while (index < text.length) {
|
||||
const current = text[index];
|
||||
const next = text[index + 1];
|
||||
|
||||
if (quote) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === "\\") {
|
||||
escaped = true;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === quote) {
|
||||
quote = null;
|
||||
}
|
||||
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === '"' || current === "'" || current === "`") {
|
||||
quote = current;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === "/" && next === "/") {
|
||||
const start = index;
|
||||
index += 2;
|
||||
|
||||
while (index < text.length && text[index] !== "\n") {
|
||||
index++;
|
||||
}
|
||||
|
||||
ranges.push({ start, end: index });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === "/" && next === "*") {
|
||||
const start = index;
|
||||
index += 2;
|
||||
|
||||
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) {
|
||||
index++;
|
||||
}
|
||||
|
||||
index = Math.min(text.length, index + 2);
|
||||
ranges.push({ start, end: index });
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} offset
|
||||
* @param {Array<{start: number, end: number}>} ranges
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isInsideComment(offset, ranges) {
|
||||
let low = 0;
|
||||
let high = ranges.length - 1;
|
||||
|
||||
while (low <= high) {
|
||||
const middle = Math.floor((low + high) / 2);
|
||||
const range = ranges[middle];
|
||||
|
||||
if (!range) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (offset < range.start) {
|
||||
high = middle - 1;
|
||||
} else if (offset >= range.end) {
|
||||
low = middle + 1;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {number} offset
|
||||
* @param {number} length
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isStateModification(text, offset, length) {
|
||||
const after = text.slice(offset + length).match(/^\s*(=|\+=|-=|\*=|\/=|%=|\+\+|--)/);
|
||||
|
||||
if (after) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const before = text.slice(Math.max(0, offset - 8), offset).match(/(\+\+|--)\s*$/);
|
||||
|
||||
return Boolean(before);
|
||||
}
|
||||
|
||||
const wrnSemanticTokensProvider = {
|
||||
/**
|
||||
* @param {vscode.TextDocument} document
|
||||
* @param {vscode.CancellationToken} token
|
||||
*/
|
||||
provideDocumentSemanticTokens(document, token) {
|
||||
const builder = new vscode.SemanticTokensBuilder(semanticTokenLegend);
|
||||
const text = document.getText();
|
||||
const states = collectStateVariables(text);
|
||||
const commentRanges = collectCommentRanges(text);
|
||||
|
||||
for (const [name, declarationOffsets] of states) {
|
||||
if (token.isCancellationRequested) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegExp(name)}(?![A-Za-z0-9_$])`, "g");
|
||||
|
||||
let match;
|
||||
|
||||
while ((match = pattern.exec(text)) !== null) {
|
||||
const offset = match.index;
|
||||
|
||||
if (isInsideComment(offset, commentRanges)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const position = document.positionAt(offset);
|
||||
let modifiers = [];
|
||||
|
||||
if (declarationOffsets.has(offset)) {
|
||||
modifiers = ["declaration"];
|
||||
} else if (isStateModification(text, offset, name.length)) {
|
||||
modifiers = ["modification"];
|
||||
}
|
||||
|
||||
builder.push(position.line, position.character, name.length, "variable", modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
activate,
|
||||
deactivate,
|
||||
|
||||
@@ -534,6 +534,9 @@
|
||||
{
|
||||
"include": "#conditional-class"
|
||||
},
|
||||
{
|
||||
"include": "#conditional-class-single"
|
||||
},
|
||||
{
|
||||
"include": "text.html.basic"
|
||||
}
|
||||
@@ -560,8 +563,9 @@
|
||||
}
|
||||
},
|
||||
"conditional-class": {
|
||||
"match": "\\b(class)(:)([^\\s=]+)(\\s*=)",
|
||||
"captures": {
|
||||
"name": "meta.attribute.class-directive.wrn",
|
||||
"begin": "(?<=\\s)(class)(:)((?:\\[[^\\]\\r\\n]*\\]|[^\\s=])+)(\\s*=\\s*)(\")",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "entity.other.attribute-name.class.wrn"
|
||||
},
|
||||
@@ -573,8 +577,56 @@
|
||||
},
|
||||
"4": {
|
||||
"name": "keyword.operator.assignment.wrn"
|
||||
},
|
||||
"5": {
|
||||
"name": "punctuation.definition.string.begin.wrn"
|
||||
}
|
||||
}
|
||||
},
|
||||
"conditional-class-single": {
|
||||
"name": "meta.attribute.class-directive.wrn",
|
||||
"begin": "(?<=\\s)(class)(:)((?:\\[[^\\]\\r\\n]*\\]|[^\\s=])+)(\\s*=\\s*)(')",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "entity.other.attribute-name.class.wrn"
|
||||
},
|
||||
"2": {
|
||||
"name": "punctuation.separator.key-value.wrn"
|
||||
},
|
||||
"3": {
|
||||
"name": "string.unquoted.class-name.wrn"
|
||||
},
|
||||
"4": {
|
||||
"name": "keyword.operator.assignment.wrn"
|
||||
},
|
||||
"5": {
|
||||
"name": "punctuation.definition.string.begin.wrn"
|
||||
}
|
||||
},
|
||||
"end": "'",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.string.end.wrn"
|
||||
}
|
||||
},
|
||||
"contentName": "meta.embedded.expression.wrn",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "source.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"end": "\"",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.string.end.wrn"
|
||||
}
|
||||
},
|
||||
"contentName": "meta.embedded.expression.wrn",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "source.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"interpolation": {
|
||||
"patterns": [
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user