fix(vscode): repair WRN language diagnostics and activation
This commit is contained in:
+126
-31
@@ -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, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
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, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}`);
|
||||
}
|
||||
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)}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user