first commit

This commit is contained in:
2026-07-12 15:55:18 +05:30
commit ee98026cc5
404 changed files with 44522 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Run WrNexus Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"${workspaceFolder}/../../examples/basic-app"
],
"outFiles": ["${workspaceFolder}/src/**/*.js"],
"preLaunchTask": "build:compiler"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build:compiler",
"detail": "Bundle @wrnexus/compiler to src/compiler.cjs (powers diagnostics).",
"type": "shell",
"command": "bun",
"args": [
"build",
"../../packages/compiler/src/index.ts",
"--target=node",
"--format=cjs",
"--outfile=src/compiler.cjs"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [],
"presentation": {
"reveal": "silent",
"panel": "shared"
}
}
]
}
+10
View File
@@ -0,0 +1,10 @@
.vscode/**
test/**
*.vsix
**/*.ts
**/*.map
**/tsconfig.json
package-lock.json
bun.lock
../../**
node_modules/**
+6
View File
@@ -0,0 +1,6 @@
# Changelog
## 0.1.0
- Initial Marketplace package for WrNexus `.wrn` language support.
- Added syntax highlighting, grammar injections, snippets, completions, and compiler-backed diagnostics.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 WrNexus contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+51
View File
@@ -0,0 +1,51 @@
# WrNexus for VS Code
Language support for [WrNexus](https://github.com/) `.wrn` files — the SSR-first,
Bun-powered full-stack framework.
## Features
- **Syntax highlighting** ("color-full code"). Keywords (`page`, `component`,
`state`, `view`, `api`, `realtime`, …) plus **embedded highlighting** for the
languages inside each block:
- `view { … }` → HTML, with `{expr}` interpolation and `{t:key}` translations
and `@event="…"` bindings picked out.
- `style { … }` → CSS.
- `functions { … }`, `api … { … }`, `ssr`/`client`, `realtime on(…) { … }` → TypeScript.
- **WrNexus attributes in distinct colors.** A grammar injection recolors the
framework's own attributes so they pop even inside plain HTML: `@event`
bindings and `{t:…}` in one accent, runtime directives (`data-component`,
`data-for`, `data-show`, `data-text`, `data-scope`, `data-slot`, `data-on-*`,
`data-wire-*`) in another. Default colors ship with the extension; change them
under `editor.tokenColorCustomizations` (scopes
`entity.other.attribute-name.wrn.directive` and `…wire.event`).
- **Diagnostics.** Parse errors from the real `@wrnexus/compiler` are shown inline
as you type, anchored to the exact offset.
- **Snippets.** `page`, `component`, `view`, `state`, `props`, `seo`, `api`,
`ssr`, `client`, `realtime`, `functions`, `style`, plus view helpers `mount`,
`for`, `show`, `t`.
- **Completions.** Block keywords at file scope, `data-*` runtime attributes and
`@event` bindings inside a `view`, and HTTP methods after `api`.
## Settings
| Setting | Default | Description |
| ---------------------------- | ------- | ----------------------------------- |
| `wrnexus.diagnostics.enable` | `true` | Show parse errors for `.wrn` files. |
## Building the diagnostics engine
Diagnostics use a bundled copy of the compiler. Regenerate it after changing
`@wrnexus/compiler`:
```bash
bun run build:compiler # writes src/compiler.cjs
```
The extension still highlights, snippets and completes without the bundle; only
inline error reporting requires it.
## Development
Open this folder in VS Code and press <kbd>F5</kbd> to launch an Extension
Development Host, then open any `.wrn` file.
+7
View File
@@ -0,0 +1,7 @@
# Support
For bugs, feature requests, and publishing issues, open an issue in the WrNexus repository:
https://github.com/wrnexus/wrnexus/issues
Please include your VS Code version, extension version, operating system, and a small `.wrn` sample when reporting language support problems.
+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<rect width="32" height="32" rx="6" fill="#0b1021"/>
<path d="M6 11l3 10 3-8 3 8 3-10" fill="none" stroke="#5eead4" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="24" cy="12" r="2.4" fill="#f472b6"/>
<path d="M24 14.5v6" stroke="#f472b6" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 418 B

@@ -0,0 +1,46 @@
{
"comments": {
"lineComment": "//"
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"],
["<", ">"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] },
{ "open": "<", "close": ">" }
],
"autoCloseBefore": ";:.,=}])> \n\t",
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"],
["<", ">"]
],
"colorizedBracketPairs": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"folding": {
"markers": {
"start": "^\\s*//\\s*#region\\b",
"end": "^\\s*//\\s*#endregion\\b"
}
},
"onEnterRules": [
{
"beforeText": "^\\s*//.*$",
"action": { "indent": "none", "appendText": "// " }
}
],
"wordPattern": "[A-Za-z_][A-Za-z0-9_-]*"
}
+3811
View File
File diff suppressed because it is too large Load Diff
+154
View File
@@ -0,0 +1,154 @@
{
"name": "wrnexus",
"displayName": "WrNexus (.wrn)",
"description": "Syntax highlighting, diagnostics, snippets and completions for WrNexus .wrn files.",
"version": "0.1.0",
"publisher": "wrnexus",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/wrnexus/wrnexus.git",
"directory": "editors/vscode"
},
"bugs": {
"url": "https://github.com/wrnexus/wrnexus/issues"
},
"homepage": "https://github.com/wrnexus/wrnexus#readme",
"galleryBanner": {
"color": "#0b1021",
"theme": "dark"
},
"pricing": "Free",
"engines": {
"vscode": "^1.75.0"
},
"categories": [
"Programming Languages",
"Snippets",
"Linters"
],
"keywords": [
"wire",
"wrnexus",
"wrn",
"full-stack",
"ssr",
"bun"
],
"main": "./src/extension.js",
"activationEvents": [
"onLanguage:wrn"
],
"contributes": {
"languages": [
{
"id": "wrn",
"aliases": [
"WrNexus",
"wrn"
],
"extensions": [
".wrn"
],
"configuration": "./language-configuration.json",
"icon": {
"light": "./icons/wrn.svg",
"dark": "./icons/wrn.svg"
}
}
],
"grammars": [
{
"language": "wrn",
"scopeName": "source.wrn",
"path": "./syntaxes/wrn.tmLanguage.json",
"embeddedLanguages": {
"meta.embedded.block.html": "html",
"meta.embedded.block.css": "css",
"meta.embedded.block.ts": "typescript",
"meta.embedded.expr.wrn": "typescript"
}
},
{
"scopeName": "wrn.attributes.injection",
"path": "./syntaxes/wrn.injection.tmLanguage.json",
"injectTo": [
"source.wrn"
]
}
],
"snippets": [
{
"language": "wrn",
"path": "./snippets/wrn.json"
}
],
"configuration": {
"title": "WrNexus",
"properties": {
"wrnexus.diagnostics.enable": {
"type": "boolean",
"default": true,
"description": "Show parse errors for .wrn files as you type."
}
}
},
"configurationDefaults": {
"editor.tokenColorCustomizations": {
"textMateRules": [
{
"scope": "entity.other.attribute-name.wrn.directive",
"settings": {
"foreground": "#5eead4",
"fontStyle": "bold"
}
},
{
"scope": "entity.other.attribute-name.wrn.event",
"settings": {
"foreground": "#f472b6",
"fontStyle": "bold"
}
},
{
"scope": "punctuation.definition.keyword.wrn",
"settings": {
"foreground": "#f472b6"
}
},
{
"scope": "keyword.control.i18n.wrn",
"settings": {
"foreground": "#f472b6",
"fontStyle": "bold"
}
},
{
"scope": "string.other.i18n-key.wrn",
"settings": {
"foreground": "#5eead4"
}
},
{
"scope": "constant.language.http-method.wrn",
"settings": {
"foreground": "#f472b6",
"fontStyle": "bold"
}
}
]
}
}
},
"scripts": {
"build:compiler": "bun build ../../packages/compiler/src/index.ts --target=node --format=cjs --outfile=src/compiler.cjs",
"validate": "node test/validate.mjs",
"vscode:prepublish": "bun run build:compiler",
"package": "vsce package --no-dependencies",
"publish": "vsce publish --no-dependencies",
"publish:azure": "vsce publish --no-dependencies --azure-credential"
},
"devDependencies": {
"@vscode/vsce": "^3.9.2"
}
}
+114
View File
@@ -0,0 +1,114 @@
{
"Page": {
"prefix": "page",
"body": [
"page ${1:Home} {",
" layout = \"${2:public}\"",
"",
" seo {",
" title = \"${3:Home}\"",
" description = \"${4:A WrNexus page.}\"",
" }",
"",
" view {",
" <h1>$5</h1>",
" }",
"}",
""
],
"description": "A WrNexus page (a route) with layout, seo and view."
},
"Component": {
"prefix": "component",
"body": [
"component ${1:Counter} {",
" props {",
" ${2:start} = ${3:0}",
" }",
"",
" state ${4:count} = ${2:start}",
"",
" view {",
" <button @click=\"${4:count}++\">{${4:count}}</button>",
" }",
"}",
""
],
"description": "A reusable, prop-driven component."
},
"View block": {
"prefix": "view",
"body": ["view {", " $0", "}"],
"description": "The server-rendered HTML view block."
},
"State declaration": {
"prefix": "state",
"body": ["state ${1:count} = ${2:0}"],
"description": "Reactive state seeded into the scope."
},
"Props block": {
"prefix": "props",
"body": ["props {", " ${1:label} = ${2:\"Label\"}", "}"],
"description": "Component props with typed defaults."
},
"SEO block": {
"prefix": "seo",
"body": ["seo {", " title = \"${1:Title}\"", " description = \"${2:Description}\"", "}"],
"description": "Per-page SEO metadata."
},
"API route": {
"prefix": "api",
"body": [
"api ${1|GET,POST,PUT,PATCH,DELETE|} ${2:/path} {",
" $0",
" return Response.json({ ok: true });",
"}"
],
"description": "A colocated API route handler."
},
"Functions block": {
"prefix": "functions",
"body": ["functions {", " $0", "}"],
"description": "Shared helper functions for this file."
},
"Style block": {
"prefix": "style",
"body": ["style {", " .${1:box} {", " $0", " }", "}"],
"description": "Scoped CSS inlined with the page."
},
"SSR data block": {
"prefix": "ssr",
"body": ["ssr {", " api ${1:load} ${2|GET,POST|} ${3:/api/data} {", " $0", " }", "}"],
"description": "Server-side data fetching block."
},
"Client data block": {
"prefix": "client",
"body": ["client {", " api ${1:load} ${2|GET,POST|} ${3:/api/data} {", " $0", " }", "}"],
"description": "Client-side data fetching block."
},
"Realtime block": {
"prefix": "realtime",
"body": ["realtime ${1:chat} {", " on ${2:message}(${3:data}) {", " $0", " }", "}"],
"description": "A realtime channel with event handlers."
},
"Component mount": {
"prefix": "mount",
"body": ["<div data-component=\"${1:counter}\" ${2:start=\"0\"}></div>"],
"description": "Mount a component in a view."
},
"List rendering (data-for)": {
"prefix": "for",
"body": ["<${1:li} data-for=\"${2:item} in ${3:items}\" data-text=\"${2:item}\"></${1:li}>"],
"description": "Repeat an element for each item."
},
"Conditional (data-show)": {
"prefix": "show",
"body": ["<div data-show=\"${1:condition}\">$0</div>"],
"description": "Toggle visibility reactively."
},
"Translation": {
"prefix": "t",
"body": ["{t:${1:key}}"],
"description": "Localized text via the i18n dictionary."
}
}
File diff suppressed because it is too large Load Diff
+235
View File
@@ -0,0 +1,235 @@
// @ts-check
"use strict";
const vscode = require("vscode");
// The .wrn compiler, bundled to CJS by `bun run build:compiler`. Loaded
// defensively so the rest of the extension (highlighting, snippets, completion)
// still works even if the bundle is missing.
let compiler = null;
try {
compiler = require("./compiler.cjs");
} catch (err) {
console.warn("[wrnexus] compiler bundle not found; diagnostics disabled.", err && err.message);
}
/** Top-level block keywords offered at file scope. */
const BLOCK_KEYWORDS = [
["page", "page ${1:Name} {\n\t$0\n}", "A route page"],
["component", "component ${1:Name} {\n\t$0\n}", "A reusable, prop-driven component"],
["layout", 'layout = "${1:public}"', "Select a layout for this page"],
["state", "state ${1:count} = ${2:0}", "Reactive state seeded into the scope"],
["props", 'props {\n\t${1:label} = ${2:"Label"}\n}', "Component props with typed defaults"],
["view", "view {\n\t$0\n}", "Server-rendered HTML"],
["seo", 'seo {\n\ttitle = "$1"\n\tdescription = "$2"\n}', "Per-page SEO metadata"],
["api", "api ${1|GET,POST,PUT,PATCH,DELETE|} ${2:/path} {\n\t$0\n}", "Colocated API route"],
["ssr", "ssr {\n\tapi ${1:load} GET ${2:/api/data} {\n\t\t$0\n\t}\n}", "Server-side data block"],
[
"client",
"client {\n\tapi ${1:load} GET ${2:/api/data} {\n\t\t$0\n\t}\n}",
"Client-side data block",
],
[
"realtime",
"realtime ${1:chat} {\n\ton ${2:message}(${3:data}) {\n\t\t$0\n\t}\n}",
"Realtime channel",
],
["functions", "functions {\n\t$0\n}", "Shared helper functions"],
["style", "style {\n\t$0\n}", "Scoped CSS"],
];
/** `data-*` attributes understood by the reactive runtime. */
const DATA_ATTRS = [
["data-component", 'data-component="$1"', "Mount a component by name"],
["data-for", 'data-for="${1:item} in ${2:items}"', "Repeat this element per item"],
["data-show", 'data-show="${1:condition}"', "Toggle visibility reactively"],
["data-text", 'data-text="${1:expr}"', "Bind text content to an expression"],
["data-scope", 'data-scope="${1:key}: ${2:value}"', "Declare a local reactive scope"],
["data-slot", 'data-slot="${1:name}"', "Fill a named <slot> on a component"],
];
/** Client event bindings. */
const EVENTS = [
"click",
"input",
"change",
"submit",
"keydown",
"keyup",
"focus",
"blur",
"mouseenter",
"mouseleave",
];
/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
const diagnostics = vscode.languages.createDiagnosticCollection("wrn");
context.subscriptions.push(diagnostics);
const timers = new Map();
const runDiagnostics = (doc) => {
if (doc.languageId !== "wrn") return;
if (!vscode.workspace.getConfiguration("wrnexus").get("diagnostics.enable", true)) {
diagnostics.delete(doc.uri);
return;
}
if (!compiler || typeof compiler.compileWireFile !== "function") return;
const text = doc.getText();
/** @type {vscode.Diagnostic[]} */
const found = [];
try {
compiler.compileWireFile(text);
} catch (err) {
found.push(toDiagnostic(doc, err));
}
diagnostics.set(doc.uri, found);
};
const schedule = (doc) => {
const key = doc.uri.toString();
clearTimeout(timers.get(key));
timers.set(
key,
setTimeout(() => {
timers.delete(key);
runDiagnostics(doc);
}, 250),
);
};
// Lint on open, edit and save; clear on close.
vscode.workspace.textDocuments.forEach(runDiagnostics);
context.subscriptions.push(
vscode.workspace.onDidOpenTextDocument(runDiagnostics),
vscode.workspace.onDidChangeTextDocument((e) => schedule(e.document)),
vscode.workspace.onDidSaveTextDocument(runDiagnostics),
vscode.workspace.onDidCloseTextDocument((doc) => diagnostics.delete(doc.uri)),
);
// Completions.
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
"wrn",
{ provideCompletionItems: provideCompletions },
"-",
"@",
"{",
":",
),
);
}
/**
* Map a thrown ParseError to a VS Code diagnostic. The compiler encodes the
* failure position as `... at offset <N>` in the message; we resolve it to a
* range. Errors without an offset (e.g. "Unexpected end of input") anchor to the
* end of the document.
* @param {vscode.TextDocument} doc
* @param {unknown} err
* @returns {vscode.Diagnostic}
*/
function toDiagnostic(doc, err) {
const message = err && err.message ? String(err.message) : "Failed to parse .wrn file";
const match = /offset\s+(\d+)/.exec(message);
let range;
if (match) {
const offset = Number(match[1]);
const start = doc.positionAt(offset);
const wordRange = doc.getWordRangeAtPosition(start);
range = wordRange || new vscode.Range(start, doc.positionAt(offset + 1));
} else {
const last = doc.lineAt(Math.max(0, doc.lineCount - 1));
range = new vscode.Range(last.range.start, last.range.end);
}
const diag = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);
diag.source = "wrn";
return diag;
}
/**
* @param {vscode.TextDocument} document
* @param {vscode.Position} position
* @returns {vscode.CompletionItem[]}
*/
function provideCompletions(document, position) {
const line = document.lineAt(position).text;
const before = line.slice(0, position.character);
const full = document.getText();
const upto = document.offsetAt(position);
const inView = isInsideBlock(full, upto, "view");
// `{t:` — offer nothing structured, just let the user type the key.
// `@` inside a view — event bindings.
if (inView && /@[A-Za-z-]*$/.test(before)) {
return EVENTS.map((ev) => {
const item = new vscode.CompletionItem(ev, vscode.CompletionItemKind.Event);
item.insertText = new vscode.SnippetString(`${ev}="$0"`);
item.detail = "wrn event binding";
// Replace the `@`-less part already typed.
return item;
});
}
// `data-` inside a view — reactive attributes.
if (inView && /(^|\s)data-[A-Za-z-]*$/.test(before)) {
return DATA_ATTRS.map(([label, snippet, doc]) => {
const item = new vscode.CompletionItem(label, vscode.CompletionItemKind.Property);
item.insertText = new vscode.SnippetString(String(snippet));
item.documentation = new vscode.MarkdownString(String(doc));
item.detail = "wrn runtime attribute";
const dashIdx = before.lastIndexOf("data-");
item.range = new vscode.Range(position.line, dashIdx, position.line, position.character);
return item;
});
}
// Top-level block keywords, when not inside a view/style/functions body.
if (!inView && !isInsideBlock(full, upto, "style") && !isInsideBlock(full, upto, "functions")) {
return BLOCK_KEYWORDS.map(([label, snippet, doc]) => {
const item = new vscode.CompletionItem(String(label), vscode.CompletionItemKind.Keyword);
item.insertText = new vscode.SnippetString(String(snippet));
item.documentation = new vscode.MarkdownString(String(doc));
item.detail = "wrn block";
return item;
});
}
return [];
}
/**
* Very small heuristic: is `offset` inside a `<name> { ... }` block? Finds the
* nearest preceding `name {` opener and checks braces don't balance before the
* cursor. Good enough to gate view/style/functions-aware completions.
* @param {string} text
* @param {number} offset
* @param {string} name
* @returns {boolean}
*/
function isInsideBlock(text, offset, name) {
const re = new RegExp("\\b" + name + "\\b\\s*(?:[^\\n{]*)\\{", "g");
let opener = -1;
let m;
while ((m = re.exec(text)) !== null) {
const bracePos = m.index + m[0].length - 1;
if (bracePos >= offset) break;
opener = bracePos;
}
if (opener === -1) return false;
// Count braces between the opener and the cursor; still open => inside.
let depth = 0;
for (let i = opener; i < offset && i < text.length; i++) {
const c = text[i];
if (c === "{") depth++;
else if (c === "}") depth--;
}
return depth > 0;
}
function deactivate() {}
module.exports = { activate, deactivate };
@@ -0,0 +1,20 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "Wire attributes",
"scopeName": "wrn.attributes.injection",
"injectionSelector": "L:meta.tag -comment -string",
"patterns": [{ "include": "#wire-event" }, { "include": "#wire-directive" }],
"repository": {
"wire-event": {
"match": "(@)([A-Za-z][A-Za-z0-9_-]*)",
"captures": {
"1": { "name": "punctuation.definition.keyword.wrn" },
"2": { "name": "entity.other.attribute-name.wrn.event" }
}
},
"wire-directive": {
"match": "\\b(data-(?:component|scope|for|text|show|slot|on-[A-Za-z0-9_-]+|wire-[A-Za-z0-9_-]+|wrnexus-[A-Za-z0-9_-]+))\\b",
"name": "entity.other.attribute-name.wrn.directive"
}
}
}
+306
View File
@@ -0,0 +1,306 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "Wire",
"scopeName": "source.wrn",
"patterns": [{ "include": "#comments" }, { "include": "#declaration" }, { "include": "#blocks" }],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.double-slash.wrn",
"match": "//.*$"
}
]
},
"declaration": {
"match": "\\b(page|component)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)?",
"captures": {
"1": { "name": "storage.type.wrn keyword.control.wrn" },
"2": { "name": "entity.name.type.wrn" }
}
},
"blocks": {
"patterns": [
{ "include": "#view-block" },
{ "include": "#style-block" },
{ "include": "#seo-block" },
{ "include": "#props-block" },
{ "include": "#realtime-block" },
{ "include": "#mode-block" },
{ "include": "#api-block" },
{ "include": "#functions-block" },
{ "include": "#state-decl" },
{ "include": "#layout-decl" },
{ "include": "#strings" }
]
},
"layout-decl": {
"match": "\\b(layout)\\b\\s*(=)",
"captures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "keyword.operator.assignment.wrn" }
}
},
"state-decl": {
"begin": "\\b(state)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*(=)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "variable.other.wrn" },
"3": { "name": "keyword.operator.assignment.wrn" }
},
"end": "$",
"contentName": "meta.embedded.expr.wrn",
"patterns": [{ "include": "source.ts" }]
},
"props-block": {
"begin": "\\b(props)\\b\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"patterns": [
{ "include": "#comments" },
{
"match": "\\b([A-Za-z_][A-Za-z0-9_]*)\\s*(=)",
"captures": {
"1": { "name": "variable.parameter.wrn" },
"2": { "name": "keyword.operator.assignment.wrn" }
}
},
{ "include": "#strings" }
]
},
"seo-block": {
"begin": "\\b(seo)\\b\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"patterns": [
{ "include": "#comments" },
{
"match": "\\b([A-Za-z_][A-Za-z0-9_-]*)\\s*(=)",
"captures": {
"1": { "name": "support.type.property-name.wrn" },
"2": { "name": "keyword.operator.assignment.wrn" }
}
},
{ "include": "#strings" }
]
},
"style-block": {
"begin": "\\b(style)\\b\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"contentName": "meta.embedded.block.css",
"patterns": [{ "include": "#css-braces" }, { "include": "source.css" }]
},
"css-braces": {
"begin": "\\{",
"end": "\\}",
"contentName": "meta.embedded.block.css",
"patterns": [{ "include": "#css-braces" }, { "include": "source.css" }]
},
"functions-block": {
"begin": "\\b(functions)\\b\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"contentName": "meta.embedded.block.ts",
"patterns": [{ "include": "#ts-braces" }, { "include": "source.ts" }]
},
"ts-braces": {
"begin": "\\{",
"end": "\\}",
"contentName": "meta.embedded.block.ts",
"patterns": [{ "include": "#ts-braces" }, { "include": "source.ts" }]
},
"api-block": {
"begin": "\\b(api)\\b\\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\b\\s*([^\\s{]*)\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "constant.language.http-method.wrn" },
"3": { "name": "string.unquoted.route.wrn" },
"4": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"contentName": "meta.embedded.block.ts",
"patterns": [{ "include": "#ts-braces" }, { "include": "source.ts" }]
},
"mode-block": {
"begin": "\\b(ssr|client)\\b\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"patterns": [
{ "include": "#comments" },
{
"begin": "\\b(api)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)\\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\b\\s*([^\\s{]*)\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "entity.name.function.wrn" },
"3": { "name": "constant.language.http-method.wrn" },
"4": { "name": "string.unquoted.route.wrn" },
"5": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"contentName": "meta.embedded.block.ts",
"patterns": [{ "include": "#ts-braces" }, { "include": "source.ts" }]
},
{ "include": "#functions-block" }
]
},
"realtime-block": {
"begin": "\\b(realtime)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "entity.name.function.wrn" },
"3": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"patterns": [
{ "include": "#comments" },
{
"begin": "\\b(on)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*(\\([^)]*\\))\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "entity.name.function.event.wrn" },
"3": { "name": "variable.parameter.wrn" },
"4": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"contentName": "meta.embedded.block.ts",
"patterns": [{ "include": "#ts-braces" }, { "include": "source.ts" }]
}
]
},
"view-block": {
"begin": "\\b(view)\\b\\s*(\\{)",
"beginCaptures": {
"1": { "name": "keyword.control.wrn" },
"2": { "name": "punctuation.definition.block.begin.wrn" }
},
"end": "\\}",
"endCaptures": {
"0": { "name": "punctuation.definition.block.end.wrn" }
},
"contentName": "meta.embedded.block.html",
"patterns": [
{ "include": "#interpolation" },
{ "include": "#wire-event" },
{ "include": "text.html.basic" }
]
},
"wire-event": {
"match": "(@)([A-Za-z][A-Za-z0-9_-]*)(=)",
"captures": {
"1": { "name": "punctuation.definition.keyword.wrn" },
"2": { "name": "entity.other.attribute-name.event.wrn" },
"3": { "name": "keyword.operator.assignment.wrn" }
}
},
"interpolation": {
"patterns": [
{
"name": "meta.interpolation.i18n.wrn",
"match": "(\\{)(t:)([^}]*)(\\})",
"captures": {
"1": { "name": "punctuation.section.interpolation.begin.wrn" },
"2": { "name": "keyword.control.i18n.wrn" },
"3": { "name": "string.other.i18n-key.wrn" },
"4": { "name": "punctuation.section.interpolation.end.wrn" }
}
},
{
"name": "meta.interpolation.wrn",
"begin": "\\{",
"end": "\\}",
"beginCaptures": {
"0": { "name": "punctuation.section.interpolation.begin.wrn" }
},
"endCaptures": {
"0": { "name": "punctuation.section.interpolation.end.wrn" }
},
"contentName": "meta.embedded.expr.wrn",
"patterns": [{ "include": "#interp-braces" }, { "include": "source.ts" }]
}
]
},
"interp-braces": {
"begin": "\\{",
"end": "\\}",
"contentName": "meta.embedded.expr.wrn",
"patterns": [{ "include": "#interp-braces" }, { "include": "source.ts" }]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.wrn",
"begin": "\"",
"end": "\"",
"patterns": [{ "name": "constant.character.escape.wrn", "match": "\\\\." }]
},
{
"name": "string.quoted.single.wrn",
"begin": "'",
"end": "'",
"patterns": [{ "name": "constant.character.escape.wrn", "match": "\\\\." }]
}
]
}
}
}
+62
View File
@@ -0,0 +1,62 @@
// Sanity checks for the VS Code extension assets. Run with `node test/validate.mjs`.
// Verifies the JSON contribution files parse and that the bundled compiler both
// accepts valid .wrn and reports an offset for invalid .wrn (what diagnostics need).
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { createRequire } from "node:module";
const here = dirname(fileURLToPath(import.meta.url));
const root = join(here, "..");
const require = createRequire(import.meta.url);
let failures = 0;
const ok = (name) => console.log(` ok ${name}`);
const bad = (name, detail) => {
failures++;
console.error(` FAIL ${name}${detail ? " — " + detail : ""}`);
};
// 1. All JSON contribution files parse.
for (const rel of [
"package.json",
"language-configuration.json",
"syntaxes/wrn.tmLanguage.json",
"snippets/wrn.json",
]) {
try {
JSON.parse(readFileSync(join(root, rel), "utf8"));
ok(`json parses: ${rel}`);
} catch (e) {
bad(`json parses: ${rel}`, e.message);
}
}
// 2. Grammar wires up the right scope + embedded languages.
const grammar = JSON.parse(readFileSync(join(root, "syntaxes/wrn.tmLanguage.json"), "utf8"));
grammar.scopeName === "source.wrn" ? ok("grammar scopeName") : bad("grammar scopeName");
// 3. Bundled compiler: valid source compiles, invalid source throws with an offset.
try {
const compiler = require(join(root, "src/compiler.cjs"));
const good = `page Home {\n view { <h1>Hi</h1> }\n}`;
compiler.compileWireFile(good);
ok("compiler accepts valid .wrn");
const badSrc = `page Home {\n view { <h1>Hi</h2> }\n}`;
let threw = false;
try {
compiler.compileWireFile(badSrc);
} catch (e) {
threw = true;
/offset\s+\d+|end of input/.test(e.message)
? ok("compiler reports a locatable error")
: bad("compiler reports a locatable error", e.message);
}
if (!threw) bad("compiler rejects invalid .wrn");
} catch (e) {
bad("compiler bundle loads", e.message);
}
console.log(failures === 0 ? "\nAll extension checks passed." : `\n${failures} check(s) failed.`);
process.exit(failures === 0 ? 0 : 1);
Binary file not shown.