From e27c92e3b6a1f83cb017921b5db244003e802646 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Tue, 14 Jul 2026 15:37:02 +0530 Subject: [PATCH] release: Vs Code New Extension with layout Support --- editors/vscode/language-configuration.json | 2 +- editors/vscode/package.json | 2 +- editors/vscode/snippets/wrn.json | 14 + editors/vscode/src/compiler.cjs | 129 +++- editors/vscode/src/completion.js | 14 + editors/vscode/src/definition.js | 134 ++++ editors/vscode/src/diagnostics.js | 761 ++++++++++++++++++++ editors/vscode/src/extension.js | 4 + editors/vscode/syntaxes/wrn.tmLanguage.json | 2 +- editors/vscode/wrnexus-0.2.5.vsix | Bin 0 -> 40634 bytes packages/cli/src/generate.ts | 2 +- 11 files changed, 1034 insertions(+), 30 deletions(-) create mode 100644 editors/vscode/src/definition.js create mode 100644 editors/vscode/src/diagnostics.js create mode 100644 editors/vscode/wrnexus-0.2.5.vsix diff --git a/editors/vscode/language-configuration.json b/editors/vscode/language-configuration.json index 68fe0402..9302a17d 100644 --- a/editors/vscode/language-configuration.json +++ b/editors/vscode/language-configuration.json @@ -46,7 +46,7 @@ "folding": { "offSide": false, "markers": { - "start": "^\\s*(?:page|component|api|middleware|realtime|view|seo|props|functions)\\b.*\\{\\s*$", + "start": "^\\s*(?:page|component|layout|api|middleware|realtime|view|seo|props|functions)\\b.*\\{\\s*$", "end": "^\\s*\\}\\s*$" } } diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 562e6544..bfda88c2 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -2,7 +2,7 @@ "name": "wrnexus", "displayName": "WRNexus Language Support", "description": "Syntax highlighting, formatting, diagnostics, snippets and completions for WrNexus .wrn files.", - "version": "0.2.4", + "version": "0.2.5", "publisher": "wrnexus", "private": true, "license": "SEE LICENSE IN LICENSE", diff --git a/editors/vscode/snippets/wrn.json b/editors/vscode/snippets/wrn.json index ae144f67..12952f52 100644 --- a/editors/vscode/snippets/wrn.json +++ b/editors/vscode/snippets/wrn.json @@ -56,6 +56,20 @@ ] }, + "WRN layout": { + "prefix": ["wrn-layout", "layout"], + "description": "Create a WRN layout", + "body": [ + "layout ${1:LayoutName} {", + " view {", + "
", + " {content}", + "
", + " }", + "}" + ] + }, + "WRN state": { "prefix": ["wrn-state", "state"], "description": "Create reactive state", diff --git a/editors/vscode/src/compiler.cjs b/editors/vscode/src/compiler.cjs index 8a053f29..efb59e19 100644 --- a/editors/vscode/src/compiler.cjs +++ b/editors/vscode/src/compiler.cjs @@ -274,10 +274,10 @@ function parse(source) { }; try { const opener = lx.next(); - if (opener.type !== "ident" || opener.value !== "page" && opener.value !== "component") { - throw new ParseError(`Expected 'page' or 'component' but got '${opener.value || opener.type}' at offset ${opener.pos}`); + if (opener.type !== "ident" || !["page", "component", "layout"].includes(opener.value)) { + throw new ParseError(`Expected 'page', 'component', or 'layout' but got '${opener.value || opener.type}' at offset ${opener.pos}`); } - const kind = opener.value === "component" ? "component" : "page"; + const kind = opener.value; const name = expect("ident").value; expect("lbrace"); let layout; @@ -448,12 +448,12 @@ function parse(source) { function parseHtmlView(src, pos) { let i = pos; const isNameStart = (c) => /[A-Za-z_]/.test(c); - const isNamePart = (c) => /[A-Za-z0-9_:.[\]%-]/.test(c); + const isTagNamePart = (c) => /[A-Za-z0-9_$:.-]/.test(c); const isAttributeNamePart = (c, next) => { if (c === "/") { return next !== ">"; } - return isNamePart(c); + return /[A-Za-z0-9_$:.[\]%-]/.test(c); }; const isWs2 = (c) => c === " " || c === "\t" || c === ` ` || c === "\r"; @@ -491,9 +491,21 @@ function parseHtmlView(src, pos) { i++; return value; }; - const readName = () => { - if (i >= src.length || !isNameStart(src[i])) - return fail("Expected a tag or attribute name"); + const readTagName = () => { + if (i >= src.length || !isNameStart(src[i])) { + return fail("Expected a tag name"); + } + const start = i++; + while (i < src.length && isTagNamePart(src[i])) { + i++; + } + return src.slice(start, i); + }; + const readAttributeName = () => { + const first = src[i]; + if (i >= src.length || !isNameStart(first) && first !== ":" && first !== "$") { + return fail("Expected an attribute name"); + } const start = i++; while (i < src.length && isAttributeNamePart(src[i], src[i + 1])) { i++; @@ -502,7 +514,7 @@ function parseHtmlView(src, pos) { }; const parseTag = () => { i++; - const tag = readName(); + const tag = readTagName(); const attrs = []; for (;; ) { skipWs(); @@ -519,7 +531,7 @@ function parseHtmlView(src, pos) { } if (c === "@") { i++; - const name2 = readName(); + const name2 = readAttributeName(); skipWs(); if (src[i] !== "=") return fail(`Expected '=' after @${name2}`); @@ -528,7 +540,7 @@ function parseHtmlView(src, pos) { attrs.push({ name: name2, value: readQuoted(), event: true }); continue; } - const name = readName(); + const name = readAttributeName(); skipWs(); if (src[i] === "=") { i++; @@ -546,7 +558,7 @@ function parseHtmlView(src, pos) { return fail(`Expected `); i += 2; skipWs(); - const close = readName(); + const close = readTagName(); if (close !== tag) return fail(`Mismatched , expected `); skipWs(); @@ -674,6 +686,9 @@ function parseHtmlView(src, pos) { } // ../../packages/compiler/src/codegen.ts +function isComponentTag(tag) { + return /^[A-Z][A-Za-z0-9_$]*$/.test(tag); +} function attrEscape(value) { return value.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); } @@ -804,21 +819,30 @@ function bakeLoopAttr(raw) { return out + escLit(attrEscape(raw.slice(last))); } function renderLoopBody(node) { - if (node.type === "text") + if (node.type === "text") { return bakeLoopText(node.value); - if (node.type === "each") + } + if (node.type === "each") { return compileEachExpr(node); - if (node.type === "if") + } + if (node.type === "if") { return compileIfExpr(node); - const attrs = node.attrs.map((a) => { - const name = a.event ? eventAttribute(a.name) : a.name; - if (a.boolean) + } + const componentTag = isComponentTag(node.tag); + const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => { + const name = attr.event ? eventAttribute(attr.name) : attr.name; + if (attr.boolean) { return escLit(` ${name}`); - return escLit(` ${name}="`) + bakeLoopAttr(a.value) + escLit(`"`); + } + return escLit(` ${name}="`) + bakeLoopAttr(attr.value) + escLit(`"`); }).join(""); - if (VOID_ELEMENTS.has(node.tag.toLowerCase())) - return escLit(`<${node.tag}`) + attrs + escLit(">"); const inner = node.children.map(renderLoopBody).join(""); + if (componentTag) { + return escLit(`
") + inner + escLit("
"); + } + if (VOID_ELEMENTS.has(node.tag.toLowerCase())) { + return escLit(`<${node.tag}`) + attrs + escLit(">"); + } return escLit(`<${node.tag}`) + attrs + escLit(">") + inner + escLit(``); } function compileEachExpr(node) { @@ -862,6 +886,9 @@ function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive loops.push(node.type === "each" ? compileEachExpr(node) : compileIfExpr(node)); return `\x00WRNEACH${loops.length - 1}\x00`; } + if (isComponentTag(node.tag)) { + return renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive); + } const apiName = attrValue(node.attrs, "api"); const apiBinding = apiName ? apiBindings.get(apiName) : undefined; if (apiName && !apiBinding) { @@ -888,6 +915,35 @@ function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive }) : node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>${inner}`; } +function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive) { + const attrs = node.attrs.filter((attr) => attr.name !== "data-component"); + const inner = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join(""); + return `
${inner}
`; +} +function renderNestedComponentInvocation(node, ctx) { + let bindIndex = 0; + const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => { + if (attr.event) { + return ` ${eventAttribute(attr.name)}="${compileAttrValue(attr.value, ctx)}"`; + } + if (attr.boolean) { + return ` ${attr.name}`; + } + const rendered = ` ${attr.name}="` + `${compileAttrValue(attr.value, ctx)}"`; + if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) { + return rendered; + } + const marker = attrEscape(JSON.stringify([attr.name, attr.value])); + return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`; + }).join(""); + const loops = loopVarsOf(node); + const childCtx = loops.length > 0 ? { + ...ctx, + loopVars: new Set([...ctx.loopVars ?? [], ...loops]) + } : ctx; + const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join(""); + return `
${inner}
`; +} function ssrMarker(bindings, binding) { const marker = ``; bindings.push({ marker, ...binding }); @@ -1000,8 +1056,9 @@ async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(``); + } else if (expr === "content") { + out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`; } else { out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`; } @@ -1262,6 +1321,9 @@ function renderComponentNode(node, ctx) { if (node.type === "each" || node.type === "if") { throw new Error("Server `{#each}` / `{#if}` blocks are supported in pages, not components. Move them into a page (or use data-for / data-show on the client)."); } + if (isComponentTag(node.tag)) { + return renderNestedComponentInvocation(node, ctx); + } let bindIndex = 0; const staticClasses = []; const conditionalClasses = []; @@ -1309,10 +1371,18 @@ function renderComponentNode(node, ctx) { } function generateComponent(ast) { const out = []; + const effectiveProps = ast.kind === "layout" && !ast.props.some((prop) => prop.name === "content") ? [ + { + name: "content", + default: '""' + }, + ...ast.props + ] : ast.props; const stateNames = new Set(ast.states.map((s) => s.name)); const nameRefs = new Map; - for (const p of ast.props) + for (const p of effectiveProps) { nameRefs.set(p.name, safeRef(p.name)); + } for (const s of ast.states) nameRefs.set(s.name, safeRef(s.name)); const resolveExpr = (expr) => { @@ -1331,9 +1401,12 @@ ${styles.map(styleEscape).join(` `)} `) : ""; const needsScope = ast.states.length > 0 || viewHasEvents(ast.view); - const scopeKeys = [...ast.props.map((p) => p.name), ...ast.states.map((s) => s.name)]; + const scopeKeys = [ + ...effectiveProps.map((prop) => prop.name), + ...ast.states.map((state) => state.name) + ]; const decls = []; - for (const prop of ast.props) { + for (const prop of effectiveProps) { decls.push(` const ${nameRefs.get(prop.name)} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}));`); } for (const state of ast.states) { @@ -1343,7 +1416,11 @@ ${styles.map(styleEscape).join(` const scopeLine = needsScope && scopeKeys.length > 0 ? ` const __scope = __wrnexusScopeDecl({ ${scopeKeys.map((k) => `${JSON.stringify(k)}: ${nameRefs.get(k)}`).join(", ")} }); ` : needsScope ? ` const __scope = ""; ` : ""; - out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`); + if (ast.kind === "layout") { + out.push(`export const __wrnexusLayout = ${JSON.stringify(ast.name)};`); + } else { + out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`); + } out.push(`function __coerce(v: any, def: any): any { if (v === undefined || v === null) return def; if (typeof def === "number") return Number(v); diff --git a/editors/vscode/src/completion.js b/editors/vscode/src/completion.js index 9d20d4b2..8020fb00 100644 --- a/editors/vscode/src/completion.js +++ b/editors/vscode/src/completion.js @@ -33,6 +33,20 @@ const BLOCK_COMPLETIONS = [ "}", ].join("\n"), }, + { + label: "layout", + detail: "WRN layout", + documentation: "Create a reusable WRN layout.", + snippet: [ + "layout ${1:LayoutName} {", + " view {", + "
", + " {content}", + "
", + " }", + "}", + ].join("\n"), + }, { label: "seo", detail: "SEO metadata block", diff --git a/editors/vscode/src/definition.js b/editors/vscode/src/definition.js new file mode 100644 index 00000000..d4be17e7 --- /dev/null +++ b/editors/vscode/src/definition.js @@ -0,0 +1,134 @@ +"use strict"; + +const vscode = require("vscode"); + +const COMPONENT_DECLARATION = + /^\s*(component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/gm; + +function getTagAtPosition(document, position) { + const range = document.getWordRangeAtPosition( + position, + /[A-Za-z_$][\w$]*/, + ); + + if (!range) { + return null; + } + + const name = document.getText(range); + + if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) { + return null; + } + + const line = document.lineAt(position.line).text; + const offset = document.offsetAt(position); + const lineStart = document.offsetAt( + new vscode.Position(position.line, 0), + ); + + const characterOffset = offset - lineStart; + const before = line.slice(0, characterOffset); + const after = line.slice(characterOffset); + + const insideTag = + before.lastIndexOf("<") > before.lastIndexOf(">") && + after.includes(">"); + + if (!insideTag) { + return null; + } + + return { + name, + range, + }; +} + +async function findDeclaration(name) { + const files = await vscode.workspace.findFiles( + "**/*.wrn", + "**/{node_modules,dist,.wrnexus,.git}/**", + ); + + const matches = []; + + for (const uri of files) { + let document; + + try { + document = await vscode.workspace.openTextDocument(uri); + } catch { + continue; + } + + const source = document.getText(); + + COMPONENT_DECLARATION.lastIndex = 0; + + let match; + + while ((match = COMPONENT_DECLARATION.exec(source)) !== null) { + const declarationName = match[2]; + + if (declarationName !== name) { + continue; + } + + const nameOffset = + match.index + + match[0].indexOf(declarationName); + + const start = document.positionAt(nameOffset); + const end = document.positionAt( + nameOffset + declarationName.length, + ); + + matches.push( + new vscode.Location( + uri, + new vscode.Range(start, end), + ), + ); + } + } + + return matches; +} + +async function provideDefinition(document, position) { + const tag = getTagAtPosition(document, position); + + if (!tag) { + return null; + } + + const matches = await findDeclaration(tag.name); + + if (matches.length === 0) { + return null; + } + + return matches.length === 1 ? matches[0] : matches; +} + +function registerDefinitionProvider(context) { + const disposable = vscode.languages.registerDefinitionProvider( + { + language: "wrn", + scheme: "file", + }, + { + provideDefinition, + }, + ); + + context.subscriptions.push(disposable); +} + +module.exports = { + findDeclaration, + getTagAtPosition, + provideDefinition, + registerDefinitionProvider, +}; \ No newline at end of file diff --git a/editors/vscode/src/diagnostics.js b/editors/vscode/src/diagnostics.js new file mode 100644 index 00000000..01b1d245 --- /dev/null +++ b/editors/vscode/src/diagnostics.js @@ -0,0 +1,761 @@ +"use strict"; + +const vscode = require("vscode"); + +const COLLECTION_NAME = "wrnexus"; +const WRN_LANGUAGE_ID = "wrn"; + +const TOP_LEVEL_PATTERN = + /^\s*(page|component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/; + +const VALID_TOP_LEVEL_KINDS = new Set([ + "page", + "component", + "layout", +]); + +const VALID_MEMBERS = { + page: new Set([ + "layout", + "state", + "view", + "seo", + "style", + "functions", + "api", + "ssr", + "client", + "realtime", + ]), + + component: new Set([ + "props", + "state", + "view", + "style", + "functions", + ]), + + layout: new Set([ + "props", + "state", + "view", + "style", + "functions", + ]), +}; + +function createDiagnostic( + document, + startOffset, + endOffset, + message, + severity = vscode.DiagnosticSeverity.Error, + code, +) { + const diagnostic = new vscode.Diagnostic( + new vscode.Range( + document.positionAt(startOffset), + document.positionAt(endOffset), + ), + message, + severity, + ); + + diagnostic.source = "WRNexus"; + + if (code) { + diagnostic.code = code; + } + + return diagnostic; +} + +function lineDiagnostic( + document, + lineNumber, + message, + severity = vscode.DiagnosticSeverity.Error, + code, +) { + const line = document.lineAt(lineNumber); + + const diagnostic = new vscode.Diagnostic( + line.range, + message, + severity, + ); + + diagnostic.source = "WRNexus"; + + if (code) { + diagnostic.code = code; + } + + return diagnostic; +} + +function stripComments(source) { + return source.replace(//g, (comment) => + comment.replace(/[^\n]/g, " "), + ); +} + +function findTopLevelDeclaration(document, source) { + const match = TOP_LEVEL_PATTERN.exec(source); + + if (!match) { + const firstMeaningfulLine = source + .split(/\r?\n/) + .findIndex((line) => line.trim().length > 0); + + return { + diagnostic: lineDiagnostic( + document, + Math.max(0, firstMeaningfulLine), + "A .wrn file must start with `page`, `component`, or `layout`.", + vscode.DiagnosticSeverity.Error, + "wrn-invalid-root", + ), + }; + } + + return { + kind: match[1], + name: match[2], + match, + }; +} + +function validateBalancedCharacters(document, source) { + const diagnostics = []; + const stack = []; + + let quote = null; + let escaped = false; + + const pairs = { + "}": "{", + "]": "[", + ")": "(", + }; + + for (let index = 0; index < source.length; index += 1) { + const character = source[index]; + + if (quote !== null) { + if (escaped) { + escaped = false; + continue; + } + + if (character === "\\") { + escaped = true; + continue; + } + + if (character === quote) { + quote = null; + } + + continue; + } + + if (character === '"' || character === "'") { + quote = character; + continue; + } + + if ( + source.startsWith("", index + 4); + + if (commentEnd === -1) { + diagnostics.push( + createDiagnostic( + document, + index, + Math.min(source.length, index + 4), + "Unclosed HTML comment.", + vscode.DiagnosticSeverity.Error, + "wrn-unclosed-comment", + ), + ); + + break; + } + + index = commentEnd + 2; + continue; + } + + if ( + character === "{" || + character === "[" || + character === "(" + ) { + stack.push({ + character, + offset: index, + }); + + continue; + } + + if ( + character === "}" || + character === "]" || + character === ")" + ) { + const expectedOpening = pairs[character]; + const opening = stack.pop(); + + if (!opening || opening.character !== expectedOpening) { + diagnostics.push( + createDiagnostic( + document, + index, + index + 1, + `Unexpected \`${character}\`.`, + vscode.DiagnosticSeverity.Error, + "wrn-unexpected-closing", + ), + ); + } + } + } + + for (const opening of stack) { + const expectedClosing = + opening.character === "{" + ? "}" + : opening.character === "[" + ? "]" + : ")"; + + diagnostics.push( + createDiagnostic( + document, + opening.offset, + opening.offset + 1, + `Missing closing \`${expectedClosing}\`.`, + vscode.DiagnosticSeverity.Error, + "wrn-missing-closing", + ), + ); + } + + if (quote !== null) { + diagnostics.push( + createDiagnostic( + document, + Math.max(0, source.length - 1), + source.length, + `Unclosed ${quote === '"' ? "double" : "single"} quote.`, + vscode.DiagnosticSeverity.Error, + "wrn-unclosed-string", + ), + ); + } + + return diagnostics; +} + +function validateHtmlTags(document, source) { + const diagnostics = []; + const stack = []; + + const voidElements = new Set([ + "area", + "base", + "br", + "col", + "embed", + "hr", + "img", + "input", + "link", + "meta", + "param", + "source", + "track", + "wbr", + ]); + + const cleaned = stripComments(source); + const tagPattern = + /<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g; + + let match; + + while ((match = tagPattern.exec(cleaned)) !== null) { + const completeTag = match[0]; + const tagName = match[1]; + const lowerTag = tagName.toLowerCase(); + + const isClosing = completeTag.startsWith(""); + const isVoid = voidElements.has(lowerTag); + + if (isClosing) { + const last = stack.pop(); + + if (!last) { + diagnostics.push( + createDiagnostic( + document, + match.index, + match.index + completeTag.length, + `Unexpected closing tag .`, + vscode.DiagnosticSeverity.Error, + "wrn-unexpected-html-close", + ), + ); + + continue; + } + + if (last.tagName !== tagName) { + diagnostics.push( + createDiagnostic( + document, + match.index, + match.index + completeTag.length, + `Mismatched closing tag . Expected .`, + vscode.DiagnosticSeverity.Error, + "wrn-mismatched-html-tag", + ), + ); + } + + continue; + } + + if (!isSelfClosing && !isVoid) { + stack.push({ + tagName, + offset: match.index, + length: completeTag.length, + }); + } + } + + for (const tag of stack) { + diagnostics.push( + createDiagnostic( + document, + tag.offset, + tag.offset + tag.length, + `Missing closing tag .`, + vscode.DiagnosticSeverity.Error, + "wrn-missing-html-close", + ), + ); + } + + return diagnostics; +} + +function getRootBodyRange(source, rootMatch) { + const openingBrace = + rootMatch.index + rootMatch[0].lastIndexOf("{"); + + let depth = 0; + let quote = null; + let escaped = false; + + for ( + let index = openingBrace; + index < source.length; + index += 1 + ) { + const character = source[index]; + + if (quote !== null) { + if (escaped) { + escaped = false; + continue; + } + + if (character === "\\") { + escaped = true; + continue; + } + + if (character === quote) { + quote = null; + } + + continue; + } + + if (character === '"' || character === "'") { + quote = character; + continue; + } + + if (character === "{") { + depth += 1; + continue; + } + + if (character === "}") { + depth -= 1; + + if (depth === 0) { + return { + start: openingBrace + 1, + end: index, + }; + } + } + } + + return { + start: openingBrace + 1, + end: source.length, + }; +} + +function findRootMembers(source, bodyStart, bodyEnd) { + const members = []; + let index = bodyStart; + let depth = 0; + let quote = null; + let escaped = false; + + while (index < bodyEnd) { + const character = source[index]; + + if (quote !== null) { + if (escaped) { + escaped = false; + index += 1; + continue; + } + + if (character === "\\") { + escaped = true; + index += 1; + continue; + } + + if (character === quote) { + quote = null; + } + + index += 1; + continue; + } + + if (character === '"' || character === "'") { + quote = character; + index += 1; + continue; + } + + if (source.startsWith("", index + 4); + index = end === -1 ? bodyEnd : end + 3; + continue; + } + + if (character === "{") { + depth += 1; + index += 1; + continue; + } + + if (character === "}") { + depth = Math.max(0, depth - 1); + index += 1; + continue; + } + + if ( + depth === 0 && + /[A-Za-z_]/.test(character) + ) { + const start = index; + index += 1; + + while ( + index < bodyEnd && + /[A-Za-z0-9_-]/.test(source[index]) + ) { + index += 1; + } + + const name = source.slice(start, index); + + members.push({ + name, + start, + end: index, + }); + + continue; + } + + index += 1; + } + + return members; +} + +function validateRootMembers( + document, + source, + rootKind, + rootMatch, +) { + const diagnostics = []; + const allowed = VALID_MEMBERS[rootKind]; + + if (!allowed) { + return diagnostics; + } + + const bodyRange = getRootBodyRange(source, rootMatch); + const members = findRootMembers( + source, + bodyRange.start, + bodyRange.end, + ); + + for (const member of members) { + if (allowed.has(member.name)) { + continue; + } + + diagnostics.push( + createDiagnostic( + document, + member.start, + member.end, + `Unknown ${rootKind} member \`${member.name}\`.`, + vscode.DiagnosticSeverity.Error, + "wrn-unknown-member", + ), + ); + } + + return diagnostics; +} + +function validateRequiredView( + document, + source, + rootKind, + rootMatch, +) { + const bodyRange = getRootBodyRange(source, rootMatch); + const body = source.slice(bodyRange.start, bodyRange.end); + + if (/\bview\s*\{/.test(body)) { + return []; + } + + return [ + createDiagnostic( + document, + rootMatch.index, + rootMatch.index + rootMatch[0].length, + `The ${rootKind} \`${rootMatch[2]}\` does not contain a \`view { ... }\` block.`, + vscode.DiagnosticSeverity.Warning, + "wrn-missing-view", + ), + ]; +} + +function validateLayoutUsage( + document, + source, + rootKind, +) { + const diagnostics = []; + + if ( + rootKind !== "page" && + /^\s*layout\s*=/m.test(source) + ) { + const match = /^\s*layout\s*=/m.exec(source); + + if (match) { + diagnostics.push( + createDiagnostic( + document, + match.index, + match.index + match[0].length, + "`layout = \"...\"` is only valid inside a page.", + vscode.DiagnosticSeverity.Error, + "wrn-invalid-layout-member", + ), + ); + } + } + + return diagnostics; +} + +function validateDocument(document) { + if (document.languageId !== WRN_LANGUAGE_ID) { + return []; + } + + const source = document.getText(); + + if (!source.trim()) { + return []; + } + + const diagnostics = []; + const declaration = findTopLevelDeclaration( + document, + source, + ); + + if (declaration.diagnostic) { + diagnostics.push(declaration.diagnostic); + diagnostics.push( + ...validateBalancedCharacters(document, source), + ); + + return diagnostics; + } + + if (!VALID_TOP_LEVEL_KINDS.has(declaration.kind)) { + diagnostics.push( + createDiagnostic( + document, + declaration.match.index, + declaration.match.index + + declaration.match[0].length, + `Unsupported WRN declaration \`${declaration.kind}\`.`, + vscode.DiagnosticSeverity.Error, + "wrn-invalid-kind", + ), + ); + + return diagnostics; + } + + diagnostics.push( + ...validateBalancedCharacters(document, source), + ); + + diagnostics.push( + ...validateHtmlTags(document, source), + ); + + diagnostics.push( + ...validateRootMembers( + document, + source, + declaration.kind, + declaration.match, + ), + ); + + diagnostics.push( + ...validateRequiredView( + document, + source, + declaration.kind, + declaration.match, + ), + ); + + diagnostics.push( + ...validateLayoutUsage( + document, + source, + declaration.kind, + ), + ); + + return diagnostics; +} + +function registerDiagnostics(context) { + const collection = + vscode.languages.createDiagnosticCollection( + COLLECTION_NAME, + ); + + const timers = new Map(); + + const update = (document) => { + if (document.languageId !== WRN_LANGUAGE_ID) { + return; + } + + const previousTimer = timers.get( + document.uri.toString(), + ); + + if (previousTimer) { + clearTimeout(previousTimer); + } + + const timer = setTimeout(() => { + timers.delete(document.uri.toString()); + + collection.set( + document.uri, + validateDocument(document), + ); + }, 150); + + timers.set(document.uri.toString(), timer); + }; + + for (const document of vscode.workspace.textDocuments) { + update(document); + } + + context.subscriptions.push( + collection, + + vscode.workspace.onDidOpenTextDocument(update), + + vscode.workspace.onDidChangeTextDocument((event) => { + update(event.document); + }), + + vscode.workspace.onDidSaveTextDocument(update), + + vscode.workspace.onDidCloseTextDocument( + (document) => { + const key = document.uri.toString(); + const timer = timers.get(key); + + if (timer) { + clearTimeout(timer); + timers.delete(key); + } + + collection.delete(document.uri); + }, + ), + + { + dispose() { + for (const timer of timers.values()) { + clearTimeout(timer); + } + + timers.clear(); + }, + }, + ); +} + +module.exports = { + registerDiagnostics, + validateBalancedCharacters, + validateDocument, + validateHtmlTags, + validateRootMembers, +}; \ No newline at end of file diff --git a/editors/vscode/src/extension.js b/editors/vscode/src/extension.js index 331d40e6..435f4d1c 100644 --- a/editors/vscode/src/extension.js +++ b/editors/vscode/src/extension.js @@ -4,6 +4,8 @@ const vscode = require("vscode"); const { formatWrn } = require("./formatter"); const { registerCompletionProvider } = require("./completion"); +const { registerDefinitionProvider } = require("./definition"); +const { registerDiagnostics } = require("./diagnostics"); // The .wrn compiler, bundled to CJS by `bun run build:compiler`. Loaded // defensively so the rest of the extension (highlighting, snippets, completion) @@ -68,7 +70,9 @@ const EVENTS = [ * @param {vscode.ExtensionContext} context */ function activate(context) { + registerDiagnostics(context); registerCompletionProvider(context); + registerDefinitionProvider(context); const diagnostics = vscode.languages.createDiagnosticCollection("wrn"); context.subscriptions.push(diagnostics); diff --git a/editors/vscode/syntaxes/wrn.tmLanguage.json b/editors/vscode/syntaxes/wrn.tmLanguage.json index e7693513..134ed2e4 100644 --- a/editors/vscode/syntaxes/wrn.tmLanguage.json +++ b/editors/vscode/syntaxes/wrn.tmLanguage.json @@ -14,7 +14,7 @@ }, "declaration": { - "match": "\\b(page|component)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)?", + "match": "\\b(page|component|layout)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)?", "captures": { "1": { "name": "storage.type.wrn keyword.control.wrn" }, "2": { "name": "entity.name.type.wrn" } diff --git a/editors/vscode/wrnexus-0.2.5.vsix b/editors/vscode/wrnexus-0.2.5.vsix new file mode 100644 index 0000000000000000000000000000000000000000..17333bc2a7bc90fd058db27df994a1f708be7f00 GIT binary patch literal 40634 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

#>!P4 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%0A_x`%(bV^#v9v&{>bLbvqItd5RA{TFat4Y>g z`s*%$%Gf$h8e!6977g5~Vy2$x{k4DsAGXfD`QdmG^?{h@PVqI5!W?XIqC#+x+1&d% zY#?SGe!1T=QR!hSo`2k&U#nDQ$IF&n#O>FglKAS?u9Um(bDq=u4`l9H5C zT96LuZV;uUq$Oo&5bkh)S6_VZy=(qC=XuV2W}UUq?3rghdw;(`Tl~?vd1o~l%vgWe zpf`mlgOzi#HncFZJQJMfRI+7t@xoB}>S$E**TJWyL5_}-Dq&dLQ%21pIviUPip z3`+Bz*Q+$IcBYso&o>{pQ4jTnJFPMCEE}H7BX%yuj#AyHRLrds&f7lCntp}@~TbBESf{iGkMkJ!cU#W zyXCpb@uB?WI`k1+it?bXvgIa!?+{aJI#h&9((45SJkkcp7IBs;xn)L<8Q8@pGq7bH z;V}{~_42R=S_AC{LCVG>@s2HZ>$iR6pBt;|>U268YQKB_r6VSLy+%OO`n6c_i9AB= zAgEx_%)SXi9SbL26D{F(2tzEfFoTpcmTWxCIZ`|pszF~xWjh?97J`VN28h44O5z?a ztitbvZywN>jy2L?`uBbl*D(ZbQ4|=?-M5#TTd_gXwvO=^7RwyM??9~k1-+p?QcWQc zUw5EU?)Z^!2fkOuyuJnmvFR~3qmgpgg^Q0VH8?0_951+h^RQWmv^89+igmuTKdSb}(JKin~* z8G99O_HsC=%-sE8b&gZ!dVL9x>eNy@VMtQ-Ek$+FEFC>NdR^nQiYK2Ji^4yAB_jug zi4D}|VNR>(4-N2k=Yyw9wyDLIN?hI)ePr^Qg8#+ew zP33$5%g-y!4e7Xe?~UT_7@_5Mt_(l6`LYoT3Jaz(COydgBFq`^Lk4Bdt?g@D{NBC7 z&YaWDzBF{LGBx|L=Er4&L@kg$_X;_RPiXI?1k6L53ybUm%~^vh+3U&iu*?_|!S)Pl zj+B@VdDNJ52||QDKZGih7azA}$=~G4^AW0bb;G*lsgtxH@?Oj5C@7sJs*8guL|3pZ)fVt>{#$W?63@C-K@b37ow>Usi_drWuN_$wf9bIy^hX{4t;`r{>5*lr0}ZphIj0d7S-eg7HHkF@h{Ca><&W zMCi|;lyii#R;k|jnWui`8$Q|Fejm2IZ|ZN(7qa(%o%^O-w)ihDI_dFua(!=&L;=*A zMUUtM07%=4T9g3i^Ao5pATYIVoS7aVY9=L1DyD{<|S;HE=*}`+OAY#b4 z4TQlu6k-$|PyGIKN2m%@2^i>SFSAoP?gG+fVj@-j!q0o($v;m1>41=M1xA%J0GIqk zXVbx{Qh+gb%;J0U>AQ`Bckg3%sES3^;I?mn$Yi4U??IX^iJ4a|^5EL%xE&nql~u3f zd%KO#qK)h)jHuxXmMM$p)Evm@X0vinACoF@Czl zHJlt29`!PZ!m>vF6p(RPQc{2Q_AF*8l`iRj90-`(TTh{GVq*R7b>eM+`(5j`86{O- zN7b11(1Ww&9{q())4>#l2&N36ft#+8M$MRYFTs`+wJJs`HQGH&qO3|YN^2jJWU}E^ zGt-N626iJ8N@J%nsepouzOVyoMRNGtZwc&{DKVHnjmcP1Q4v^?J?P8NV*A888)k~{ zQO@i{t(0&+2?0N;G~$|%>7$CT`nrzFf5^C-ES8)fxXe8|ODOHXwY_(=g|7xN_U1$H zG~uakoAyi&p}abjJ4G^qX?1H@fc^M=mqwVyo>byjKjHVSOdER^P6}NFL3m(BD6;@)P11o!&%f(&bE+ z3~kGs-g)^UWkOCy z2Yz0{9s%nHxYJMBiMKqA?Otc(h`;u8~U!Fp7 zdVd#|Sv-@Z02?}AP86;rex_9tk%JQIca%;;Q>=a8+`XFpu^DwtgW00X3Z*G6Mtv7OH2lLL|^^_|=u%?fRt$V64fM9}Eo=-m2rTAS~-^^UOwP zxmCnbhj*aQZM|$pQ|bm4iWiY zo^?03Oa(L1D)c^sv#F6_Bpad4*&;@MliyDPsbCU32Eyg`c1QKJ&~NVBFOCC`=Ih64 zPpFeQ#4ZT~$)E~35#WU>3og)@**+_-QCO)&u?8wHhk^!OkJC$;K#3lkVLi=FV~{q{ zTa4HGGW0pZla1SDfg&_rE_%rFgA}KvCI+-gM^h*d-eBEK&&e&fN$ZgUY_&lQ;GGa; z_jwk~J{oTTz|#b=Uj-==u(Y6v99VxC(0(FuMUZh#ebP4+^$XG{SXcCBhAUwralFEQ zeZ58#72i47g*b#{GAYG$9(7@H)dxd!zzEzjB5-XmVk)4@o>Ao7T1i;?X-q;}t29wZ zbo1dOtcsMq=fy%vMc78d$9@CrByKxD`!w8ieUQ!Ri`4*YrQT6ekDJ$}HJu*4)bMki zh`vw4ev-IGPAvP@m>i5;pi^vsy_;+@;|>lRG;i0+oCuohs6@NDzNcUr-yFySSBc=( z;~hF(nd89bnNg00)QYUY+ZfIXolF+e zz9$D6#b+ku6#?;f93JMQ;62YU#v?G)PB?cqAgE19@E0HsCl<}8Q$50M&_s@33TZ`rnN(ZvtYw~XgNP#Fee;H;~!1hFNQB8ex% ze{u@^GGO}EG?Ca|jlQ3|VnEnAFg5tObpz7!B~MVb5qBzmL8ZMRuD54;01dyIt|;&9 zq-dtgeT?-n!%}`Y%C$tHHar2ZpykI@uaGt@UETU@$RgWhmsC;u!6cu(NGBhikk&)W zoivK3nR$YP#2oLT>7()*JqWinP1h?XjZQY*P1|=*Sv#>r8dcQ$S@$>Pw9AxCxosupHAUMJ%KTm{ zeX4u+)CE1NxLTK@U0_9jR@!NM-@X&dMaCgPkQKC zij!wp^_pJbah#tonu$CmzoJBl5C)&_oX_xu(P>4?@Cevsjr7R>JL`&Io7$M-LMv&((H^#x)5ziX?$Qhr3 z%Koj2Xd#MkM>!Y%a)`m?iWu9q91YHp61da zu&0tBcpX!JDL1d2BB4Apv*_bno+A{Vm>nS`7bvYF+d;oZ6IEe)3EM+{>>pn4Y<;&UB?3VuL?bl20kC$}pMb#K7%7D7c|r zckd8?AC=pwOEHG4>Y)NQ^XqC)yd2G;C)!qQ?K`}gP*KlCim_=jZz2u%T zAlP)9@!fGE`aR&|g$2FY_GPu_XF>J8GSpSsSc(f{SJQh$i6pOWuo)}k|Q4OCn8fV)013wOIdfK(@eB!X38SU@vmd5P=6IhL!8=> z;2{#V2}PMXFG;Q&;iqAoXWUr%xG!dcaqd>G`<(S;lN|6=ls%m4=>& z8|6cV)M{+zGydsp;cDp5%R(*^ybO|Iy{1nm;N5g<&f3fjo8F+Jr0CkDOFJ^niT>s@ zxV_s!FsDgwi&DlOCC{`+ln?HqsLFvd5zP>?8)yrvQY9jS2) zF&j?c+dORB??2iDebW1q5v-R|XE$m3EzTMFWEo$=ZQxg;#|DW<$t#dZ+g#KH$)n1F zG97Vx2O%}23Id(8A)6@J=i0T8tv4Zf$bzXGB~uNuj1;;7lLiIw)pTH{qsbl)4{HO4 zaU=+8nCg(4^)yGI4uBqsALsRYWkSO+vX z{n_WSAwP3cfM^TlC*mTtL>@(n5}V#!5}rRb2c)+ObB`Otr-U(b=y4C;gc7J_N3KUx z0rfu6M*^H4$*Z1K#8nC7HHK1G_tY)r%kzm`RNgpf@0tb+$=fp~em!<^DAp`gz>W&2 zN)5^aB!v`V!+Iz{d)>00+DCOL4%nngJ(^$m!HP~t?G!jPknkX{x#Kixa9MaVuH`ItPLXW?S^in|B70SgjyF^t(P#*}r{bq+}1*apw z7a}k(r{;&Ea6My$rJvre;>(uM&+6Xpavp&35r?5)t?8X0o&HYE@S;8l-_Zc^Kp!Rt zEHs}&fvfKqxW+&nVV~SKOo0SeI(iGdH)l3^kwzMo?B!-)0h zB+9|Q^0Z^uY#5A@c4@ytcSq`vYv#Y(@?O*6Rsyj$>bi=PTT(=Sj(m2$KOJ9VSAnUH zL*vWv{%~A8-~U)X`wE}_MY~d_-{+)(bG3JqQ6*RDUqtOjF`MVWV+~&kDfWiS8BAFob(#e72N7*LxTY3In#^v18x)~M7D&rgPUlI5qY z4x}bS@;#gzA@BaR{A58J3&*N(dB4ZodVyP9Ft`wJK_!3Wz-WFzkLs4+pF#+So z_$a_AiOMrsdT#{6BZF#e%N=3yErqSAl&qemAvjMw(l90iJG}HfQ}}2;In&IW&(UL{ z)ZvM4l4msUs=b8v(yF!dga^g8sE_G+QaMnttzGkR;G(gf8ncQCeN&o^<-LTWT9%Jy z_&3r9U-DYfO$2%`bx#=@XuPeQ4O5SdFKF8SxD!?53Ba4Hz)ur_rJ+y8NL4#R`vZGS z$-J*}ugJd(NF%kLMcpW3ZdtYM^W*wInTfJIb6ma9+X0GCJ?pdy7v$)y)WSb{4A6;r zUJ(36+_`6q&NKdWP=Kb{QP)`ebcddL$Wg1Y<;c6-1SuRS30^0qyZT5>tMVgG(7yzO zPZbT9dgKLeJ9qB#gUu*h__X37GataM$+b~4rKUEL7>RSdBBrD7s@#YfqAw-#yzcwN z%!~jCL^hx>E~u#ZV#>*-|JtDL=xm8Ot|8s58uSyiiyxpxBnqC-`GDgr+97&Ms3hn6 zjySo6*ZPER6YGJv#n!M*05yG6u#f@C@*+mUVWi?i{jihDg{*Q^kKz4SsDexNK<@nN z+#!G6nf-#jWOGy00Ky8h2cIQr-$2dPm(*W}Y@Zc-N|PE_yfECc-%8?Tr5_$u4ZHtB zifFN2jCUK(-@o$GwMm&X>;sBMSJ5=w&kqGf39hVOZ1G%u zSnjCPQ&zgN)|?jkxVxq!HJ}?VRaZ0EYJIhUT zy@^ZwCg?CuCG~1ArJLeW@9Bt5OcVEx1PLh(jsHaJ%mdqBx_a-4i~)m0uDZVK9~Tk! zqC6K?8`$o&d%sX_C57aT1wZ%_0|3mVw+$>LWJwgn0VXEkR#fO--viPtgqYqNgrp`y zap+G}!)_1%7z}WGy4iX;m^<29S-QFZn-M4_y7&!oH6bzpfcWq~Q4O0wkPV+WIl5aq zx|?cwJ6pOLb9*`1{{~b{0R7&9r~(ZF!TbjR0Mx>w5rz2&VQ=ne?SaUH3(;q^vbFYb zHUA%7!+)~+T^clp{Pj8nk{K};e~)ku;`ATnGaCfc(%#AXU)6brNb9cAJImi`QQl>^ zc{{qBds+Uc^Z74J_q)})wTQp#E`Dnf|2t}7E42SF^ZzxBze~S$D8FmAd+SjCJ8EGa z^ncO+?N@#mbnDG~*RJqZb?bN3!iW$9^iTP29XRef_1@*&nj7AA_4u1Z$@y>2A1dN8 zyUV%t0J*Dwd+P!6J8EIV!vE&{?Fe$0bgP$pS4Z>j66wnRFVbE0+`FJ#+0DC>Wq*V8 z75)wSON8?-=yo!?+iCy1MnLud3-nv>>@MfFFMoH!yY0*Wj#^lM*j>(FU3xV|G{gh~ R01zWyREU`%^3Cnpe*t "); + console.error("Usage: wrnexus generate "); process.exit(1); }