fix(vscode): repair WRN language diagnostics and activation

This commit is contained in:
2026-07-19 14:02:17 +05:30
parent df4c1d3e7d
commit 646d16f83d
16 changed files with 425 additions and 171 deletions
+126 -31
View File
@@ -1023,8 +1023,12 @@ function hasClientBehavior(nodes) {
return nodes.some((node) => {
if (node.type === "text")
return /\{(?!t:)[^{}]+\}/.test(node.value);
if (node.type === "each" || node.type === "if")
return false;
if (node.type === "each") {
return hasClientBehavior(node.body) || hasClientBehavior(node.empty);
}
if (node.type === "if") {
return node.branches.some((branch) => hasClientBehavior(branch.body));
}
return node.attrs.some((attr) => attr.event || attr.name === "csrGet" || attr.name === "csrText") || hasClientBehavior(node.children);
});
}
@@ -1391,6 +1395,18 @@ function viewHasEvents(nodes) {
return node.attrs.some((attr) => attr.event) || viewHasEvents(node.children);
});
}
function viewHasServerEach(nodes) {
return nodes.some((node) => {
if (node.type === "text")
return false;
if (node.type === "each")
return true;
if (node.type === "if") {
return node.branches.some((branch) => viewHasServerEach(branch.body));
}
return viewHasServerEach(node.children);
});
}
function compileText(raw, ctx) {
let out = "";
let last = 0;
@@ -1455,6 +1471,14 @@ function renderComponentEachNode(node, ctx) {
const empty = node.empty.map((child) => renderComponentNode(child, ctx)).join("");
return "${(() => { const __wl = Array.isArray(" + list + ") ? (" + list + ") : []; return __wl.length ? __wl.map((" + item + ", " + index + ") => `" + body + '`).join("") : `' + empty + "`; })()}";
}
function serverLoopLocalsAttribute(ctx) {
const locals = [...ctx.serverLocals ?? []];
if (locals.length === 0) {
return "";
}
const entries = locals.map((name) => `${JSON.stringify(name)}: ${name}`).join(", ");
return ` data-wrn-loop-locals="\${__wrnexusEncodeLoopLocals({ ${entries} })}"`;
}
function renderComponentNode(node, ctx) {
if (node.type === "text")
return compileText(node.value, ctx);
@@ -1467,6 +1491,11 @@ function renderComponentNode(node, ctx) {
if (isComponentTag(node.tag)) {
return renderNestedComponentInvocation(node, ctx);
}
const loopVariables = loopVarsOf(node);
const elementContext = loopVariables.length > 0 ? {
...ctx,
loopVars: new Set([...ctx.loopVars ?? [], ...loopVariables])
} : ctx;
let bindIndex = 0;
const staticClasses = [];
const conditionalClasses = [];
@@ -1488,37 +1517,48 @@ function renderComponentNode(node, ctx) {
if (a.boolean) {
return ` ${a.name}`;
}
const rendered = ` ${a.name}="${compileAttrValue(a.value, ctx)}"`;
if (!a.value.includes("{") || !exprRefsState(a.value, ctx.stateNames)) {
const rendered = ` ${a.name}="${compileAttrValue(a.value, elementContext)}"`;
const referencesState = exprRefsState(a.value, ctx.stateNames);
const referencesLoopVariable = elementContext.loopVars ? exprRefsState(a.value, elementContext.loopVars) : false;
const referencesServerLocal = ctx.serverLocals ? exprRefsState(a.value, ctx.serverLocals) : false;
if (!a.value.includes("{") || !referencesState && !referencesLoopVariable && !referencesServerLocal) {
return rendered;
}
const marker = attrEscape(JSON.stringify([a.name, a.value]));
return `${rendered} data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
}).join("");
const initialConditionalClasses = conditionalClasses.map(({ className, expression }) => {
const referencesLoopVariable = elementContext.loopVars ? exprRefsState(expression, elementContext.loopVars) : false;
if (referencesLoopVariable) {
return "";
}
return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`;
}).join("");
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 classReferencesState = exprRefsState(staticClassValue, ctx.stateNames);
const classReferencesLoopVariable = elementContext.loopVars ? exprRefsState(staticClassValue, elementContext.loopVars) : false;
const classReferencesServerLocal = ctx.serverLocals ? exprRefsState(staticClassValue, ctx.serverLocals) : false;
const classHasReactiveExpression = staticClassValue.includes("{") && (classReferencesState || classReferencesLoopVariable || classReferencesServerLocal);
const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 ? ` class="${compileAttrValue(staticClassValue, elementContext)}${initialConditionalClasses}"` : "";
const classReactiveBinding = classHasReactiveExpression ? ` data-wrn-bind-class="${escLit(attrEscape(JSON.stringify(["class", staticClassValue])))}"` : "";
const classBindings = conditionalClasses.filter(({ expression }) => {
return !ctx.serverLocals || !exprRefsState(expression, ctx.serverLocals);
}).map(({ className, expression }, index) => {
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}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
const loopLocalsAttribute = serverLoopLocalsAttribute(ctx);
const allAttrs = `${loopLocalsAttribute}` + `${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return `<${node.tag}${allAttrs}>`;
}
const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join("");
const inner = node.children.map((child) => renderComponentNode(child, elementContext)).join("");
return `<${node.tag}${allAttrs}>${inner}</${node.tag}>`;
}
function generateComponent(ast) {
const out = [];
const hasServerEach = viewHasServerEach(ast.view);
if (hasServerEach) {
out.push(`import { Buffer } from "node:buffer";`);
}
const effectiveProps = ast.kind === "layout" && !ast.props.some((prop) => prop.name === "content") ? [
{
name: "content",
@@ -1542,6 +1582,9 @@ function generateComponent(ast) {
return result;
};
const ctx = { stateNames, resolveExpr };
const serverFunctions = ast.functions.map((body) => body.trim()).filter(Boolean).join(`
`);
const viewCode = ast.view.map((node) => renderComponentNode(node, ctx)).join("");
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = styles.length > 0 ? escLit(`<style data-wrnexus-style="${attrEscape(ast.name)}">
@@ -1560,10 +1603,10 @@ ${styles.map(styleEscape).join(`
decls.push(` const ${nameRefs.get(prop.name)} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}));`);
}
for (const state of ast.states) {
decls.push(` const ${nameRefs.get(state.name)} = (${resolveExpr(state.expr)});`);
decls.push(` let ${nameRefs.get(state.name)} = (${resolveExpr(state.expr)});`);
}
const returnExpr = needsScope ? "`" + styleTag + `<div data-scope="\${__scope}"${behaviorAttr}>` + viewCode + "</div>`" : "`" + styleTag + viewCode + "`";
const scopeLine = needsScope && scopeKeys.length > 0 ? ` const __scope = __wrnexusScopeDecl({ ${scopeKeys.map((k) => `${JSON.stringify(k)}: ${nameRefs.get(k)}`).join(", ")} });
const scopeLine = needsScope && scopeKeys.length > 0 ? ` const __scope = __wrnexusScopeDecl({ ${scopeKeys.map((key) => `${JSON.stringify(key)}: ${nameRefs.get(key)}`).join(", ")} });
` : needsScope ? ` const __scope = "";
` : "";
if (ast.kind === "layout") {
@@ -1673,26 +1716,70 @@ function __wireProp(v: any): string {
function __wireRaw(v: any): string {
return String(v == null ? "" : v);
}`);
if (needsScope) {
out.push(`function __wrnexusScopeDecl(obj: Record<string, any>): string {
const lit = (v: any) =>
typeof v === "number" || typeof v === "boolean"
? String(v)
: "'" + String(v).replace(/\\\\/g, "\\\\\\\\").replace(/'/g, "\\\\'").replace(/\\n/g, "\\\\n") + "'";
return Object.keys(obj)
.map((k) => k + ": " + lit(obj[k]))
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
if (hasServerEach) {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
}`);
}
if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string {
if (value === undefined) {
return "undefined";
}
if (value === null) {
return "null";
}
if (typeof value === "number") {
return Number.isFinite(value)
? String(value)
: "null";
}
if (typeof value === "boolean") {
return value ? "true" : "false";
}
if (typeof value === "string") {
return JSON.stringify(value);
}
try {
const serialized = JSON.stringify(value);
return serialized === undefined
? "undefined"
: serialized;
} catch {
return "null";
}
}
function __wrnexusScopeDecl(obj: Record<string, any>): string {
return Object.keys(obj)
.map(
(key) =>
key +
": " +
__wrnexusSerializeScopeValue(
obj[key],
),
)
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}`);
}
const serverFunctionSource = serverFunctions ? `${serverFunctions}
` : "";
out.push(`export function render(props: Record<string, any> = {}): string {
` + ` const __p = props || {};
` + (decls.length > 0 ? decls.join(`
`) + `
` : "") + scopeLine + ` return ${returnExpr};
` : "") + serverFunctionSource + scopeLine + ` return ${returnExpr};
` + `}`);
return out.join(`
@@ -1924,8 +2011,16 @@ ${nativeStyles(ast.styles)}
function compileNativeWireFile(source) {
return generateNative(parse(source));
}
function compileWireFile(source) {
const ast = parse(source);
function compileWireFile(source, filePath = "<inline .wrn>") {
let ast;
try {
ast = parse(source);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to parse ${filePath}: ${message}`, {
cause: error
});
}
return `// compiled from .wrn
${generate(ast)}`;
}