release: WRNexusJS 0.3.0
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.0
|
||||
|
||||
- Added language support for `runtime`, `hydrate`, `computed`, `effect`, `security`,
|
||||
`load server`, `load client`, and named `action` declarations.
|
||||
- Added snippets and completion entries for execution boundaries, deferred hydration,
|
||||
derived state, effects, data loading, actions, and security metadata.
|
||||
- Updated root-member diagnostics to understand prefixed blocks without reporting
|
||||
action names, HTTP methods, load targets, or realtime handler names as members.
|
||||
- Rebuilt the bundled compiler from the shared 0.3 syntax package and added a
|
||||
reproducible TypeScript-based bundle script.
|
||||
- Fixed multiline tag formatting so long inline-closing elements remain
|
||||
idempotent across repeated format-on-save passes.
|
||||
- Aligned the extension version with the WRNexusJS 0.3.0 framework release.
|
||||
|
||||
## 0.2.14
|
||||
|
||||
- Added top-level ES module import highlighting, completion, snippets, formatting,
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wrnexus",
|
||||
"version": "0.2.14",
|
||||
"version": "0.3.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wrnexus",
|
||||
"version": "0.2.14",
|
||||
"version": "0.3.0",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"devDependencies": {
|
||||
"@vscode/vsce": "^3.9.2"
|
||||
|
||||
@@ -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.15",
|
||||
"version": "0.3.0",
|
||||
"publisher": "wrnexus",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
@@ -257,7 +257,7 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build:compiler": "bun build ../../packages/compiler/src/index.ts --target=node --format=cjs --outfile=src/compiler.cjs",
|
||||
"build:compiler": "node ../../scripts/build-editor-compiler.mjs",
|
||||
"build": "bun run build:compiler",
|
||||
"test": "node --test test/*.test.js",
|
||||
"validate": "node test/validate.mjs",
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN dynamic page": {
|
||||
"prefix": ["wrn-dynamic-page", "dynamic-page"],
|
||||
"description": "Create a WRN page using a route parameter",
|
||||
@@ -42,7 +41,6 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN component": {
|
||||
"prefix": ["wrn-component", "component"],
|
||||
"description": "Create a reusable WRN component",
|
||||
@@ -62,7 +60,6 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN reactive component": {
|
||||
"prefix": ["wrn-reactive-component", "reactive-component"],
|
||||
"description": "Create a component with functions, lifecycle hooks, and a watcher",
|
||||
@@ -96,7 +93,6 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN layout": {
|
||||
"prefix": ["wrn-layout", "layout"],
|
||||
"description": "Create a reusable WRN layout",
|
||||
@@ -110,31 +106,26 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN state": {
|
||||
"prefix": ["wrn-state", "state"],
|
||||
"description": "Create reactive state",
|
||||
"body": ["state ${1:name}: ${2:string} = ${3:\"value\"}"]
|
||||
},
|
||||
|
||||
"WRN functions block": {
|
||||
"prefix": ["wrn-functions", "functions"],
|
||||
"description": "Create a browser-side functions block",
|
||||
"body": ["functions {", " function ${1:handler}(${2}) {", " $0", " }", "}"]
|
||||
},
|
||||
|
||||
"WRN function": {
|
||||
"prefix": ["wrn-function", "function"],
|
||||
"description": "Create a function inside a functions block",
|
||||
"body": ["function ${1:name}(${2}) {", " $0", "}"]
|
||||
},
|
||||
|
||||
"WRN async function": {
|
||||
"prefix": ["wrn-async-function", "async-function"],
|
||||
"description": "Create an asynchronous component function",
|
||||
"body": ["async function ${1:name}(${2}) {", " $0", "}"]
|
||||
},
|
||||
|
||||
"WRN lifecycle": {
|
||||
"prefix": ["wrn-lifecycle", "lifecycle"],
|
||||
"description": "Create mount, update, and unmount lifecycle hooks",
|
||||
@@ -154,37 +145,31 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN mount hook": {
|
||||
"prefix": ["wrn-mount", "mount"],
|
||||
"description": "Create a component mount lifecycle hook",
|
||||
"body": ["mount {", " $0", "}"]
|
||||
},
|
||||
|
||||
"WRN update hook": {
|
||||
"prefix": ["wrn-update", "update-hook"],
|
||||
"description": "Create a batched component update lifecycle hook",
|
||||
"body": ["update {", " $0", "}"]
|
||||
},
|
||||
|
||||
"WRN unmount hook": {
|
||||
"prefix": ["wrn-unmount", "unmount"],
|
||||
"description": "Create a component unmount lifecycle hook",
|
||||
"body": ["unmount {", " $0", "}"]
|
||||
},
|
||||
|
||||
"WRN watcher": {
|
||||
"prefix": ["wrn-watch", "watch"],
|
||||
"description": "Watch a declared state value",
|
||||
"body": ["watch ${1:stateName} {", " console.log(value, previous)", " $0", "}"]
|
||||
},
|
||||
|
||||
"WRN watcher with condition": {
|
||||
"prefix": ["wrn-watch-if", "watch-if"],
|
||||
"description": "Watch state and react to a specific value",
|
||||
"body": ["watch ${1:stateName} {", " if (value === ${2:true}) {", " $0", " }", "}"]
|
||||
},
|
||||
|
||||
"WRN SEO block": {
|
||||
"prefix": ["wrn-seo", "seo"],
|
||||
"description": "Create SEO metadata",
|
||||
@@ -195,7 +180,6 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN form": {
|
||||
"prefix": ["wrn-form", "form"],
|
||||
"description": "Create a POST form",
|
||||
@@ -212,7 +196,6 @@
|
||||
"</form>"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN input": {
|
||||
"prefix": ["wrn-input", "input"],
|
||||
"description": "Create a labeled input",
|
||||
@@ -229,61 +212,51 @@
|
||||
"</label>"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN conditional class": {
|
||||
"prefix": ["wrn-class-if", "class-if"],
|
||||
"description": "Add a conditional class directive",
|
||||
"body": ["class:${1:border-indigo-500}=\"${2:condition}\""]
|
||||
},
|
||||
|
||||
"WRN conditional class pair": {
|
||||
"prefix": ["wrn-class-toggle", "class-toggle"],
|
||||
"description": "Toggle two classes from one condition",
|
||||
"body": ["class:${1:opacity-100}=\"${3:visible}\"", "class:${2:opacity-0}=\"!${3:visible}\""]
|
||||
},
|
||||
|
||||
"WRN click event": {
|
||||
"prefix": ["wrn-click", "click"],
|
||||
"description": "Add a click event handler",
|
||||
"body": ["@click=\"${1:handler()}\""]
|
||||
},
|
||||
|
||||
"WRN window scroll event": {
|
||||
"prefix": ["wrn-window-scroll", "window-scroll"],
|
||||
"description": "Add a window scroll event handler",
|
||||
"body": ["@window:scroll=\"${1:handler()}\""]
|
||||
},
|
||||
|
||||
"WRN window resize event": {
|
||||
"prefix": ["wrn-window-resize", "window-resize"],
|
||||
"description": "Add a window resize event handler",
|
||||
"body": ["@window:resize=\"${1:handler()}\""]
|
||||
},
|
||||
|
||||
"WRN document event": {
|
||||
"prefix": ["wrn-document-event", "document-event"],
|
||||
"description": "Add a document-level event handler",
|
||||
"body": ["@document:${1:click}=\"${2:handler()}\""]
|
||||
},
|
||||
|
||||
"WRN show directive": {
|
||||
"prefix": ["wrn-show", "show"],
|
||||
"description": "Conditionally show an element",
|
||||
"body": ["data-show=\"${1:condition}\""]
|
||||
},
|
||||
|
||||
"WRN loop": {
|
||||
"prefix": ["wrn-for", "for"],
|
||||
"description": "Create a reactive list loop",
|
||||
"body": ["<div data-for=\"${1:item} in ${2:items}\">", " <span>{${1:item}}</span>", "</div>"]
|
||||
},
|
||||
|
||||
"WRN dynamic link": {
|
||||
"prefix": ["wrn-route-link", "route-link"],
|
||||
"description": "Create a link using route state",
|
||||
"body": ["<a href=\"/${1:resource}/{${2:id}}\">", " ${3:Open}", "</a>"]
|
||||
},
|
||||
|
||||
"WRN API GET": {
|
||||
"prefix": ["wrn-api-get", "api-get"],
|
||||
"description": "Create a GET API route",
|
||||
@@ -295,7 +268,6 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN API POST": {
|
||||
"prefix": ["wrn-api-post", "api-post"],
|
||||
"description": "Create a POST API route",
|
||||
@@ -310,7 +282,6 @@
|
||||
"}"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN loading state": {
|
||||
"prefix": ["wrn-loading", "loading-state"],
|
||||
"description": "Create loading state and a reactive button",
|
||||
@@ -327,7 +298,6 @@
|
||||
"</button>"
|
||||
]
|
||||
},
|
||||
|
||||
"WRN BackToTop component": {
|
||||
"prefix": ["wrn-back-to-top", "back-to-top"],
|
||||
"description": "Create an optimized BackToTop component using lifecycle cleanup",
|
||||
@@ -385,5 +355,56 @@
|
||||
" }",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"WRN runtime target": {
|
||||
"prefix": ["wrn-runtime", "runtime"],
|
||||
"description": "Set server, client, or universal execution",
|
||||
"body": ["runtime = \"${1|universal,server,client|}\""]
|
||||
},
|
||||
"WRN hydration strategy": {
|
||||
"prefix": ["wrn-hydrate", "hydrate"],
|
||||
"description": "Control when a declaration hydrates in the browser",
|
||||
"body": ["hydrate = \"${1|load,idle,visible,interaction,none|}\""]
|
||||
},
|
||||
"WRN media hydration": {
|
||||
"prefix": ["wrn-hydrate-media", "hydrate-media"],
|
||||
"description": "Hydrate only when a media query matches",
|
||||
"body": ["hydrate = \"media:${1:(min-width: 768px)}\""]
|
||||
},
|
||||
"WRN computed block": {
|
||||
"prefix": ["wrn-computed", "computed"],
|
||||
"description": "Declare cached reactive derived values",
|
||||
"body": ["computed {", " ${1:displayName} = ${2:firstName + \" \" + lastName}", "}"]
|
||||
},
|
||||
"WRN effect block": {
|
||||
"prefix": ["wrn-effect", "effect"],
|
||||
"description": "Run a side effect when reactive dependencies change",
|
||||
"body": ["effect {", " ${1:console.log(value)}", "}"]
|
||||
},
|
||||
"WRN security block": {
|
||||
"prefix": ["wrn-security", "security"],
|
||||
"description": "Declare route security policy metadata",
|
||||
"body": [
|
||||
"security {",
|
||||
" auth = \"${1|required,optional,public|}\"",
|
||||
" csrf = \"${2:true}\"",
|
||||
" rateLimit = \"${3:standard}\"",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"WRN server load": {
|
||||
"prefix": ["wrn-load-server", "load-server"],
|
||||
"description": "Load data only on the server",
|
||||
"body": ["load server {", " ${1:return {}}", "}"]
|
||||
},
|
||||
"WRN client load": {
|
||||
"prefix": ["wrn-load-client", "load-client"],
|
||||
"description": "Load data in the browser",
|
||||
"body": ["load client {", " ${1:return {}}", "}"]
|
||||
},
|
||||
"WRN action": {
|
||||
"prefix": ["wrn-action", "action"],
|
||||
"description": "Declare a named server action",
|
||||
"body": ["action ${1:save}(${2:input}) {", " $0", "}"]
|
||||
}
|
||||
}
|
||||
|
||||
+2900
-1939
File diff suppressed because it is too large
Load Diff
@@ -106,6 +106,56 @@ const BLOCK_COMPLETIONS = [
|
||||
documentation: "Declare reactive state owned by the current component, layout, or page.",
|
||||
snippet: 'state ${1:name}: ${2:string} = ${3:"value"}',
|
||||
},
|
||||
{
|
||||
label: "runtime",
|
||||
detail: "WRN execution target",
|
||||
documentation: "Choose whether this declaration executes on the server, client, or both.",
|
||||
snippet: 'runtime = "${1|universal,server,client|}"',
|
||||
},
|
||||
{
|
||||
label: "hydrate",
|
||||
detail: "WRN hydration strategy",
|
||||
documentation: "Choose when client interactivity is initialized for this declaration.",
|
||||
snippet: 'hydrate = "${1|load,idle,visible,interaction,none|}"',
|
||||
},
|
||||
{
|
||||
label: "computed",
|
||||
detail: "Derived reactive values block",
|
||||
documentation:
|
||||
"Declare values that are recomputed only when their reactive dependencies change.",
|
||||
snippet: ["computed {", " ${1:displayName} = ${2:firstName + ' ' + lastName}", "}"].join(
|
||||
"\n",
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "effect",
|
||||
detail: "Reactive side-effect block",
|
||||
documentation: "Run browser-side code whenever its referenced reactive values change.",
|
||||
snippet: ["effect {", " ${1:console.log(value)}", "}"].join("\n"),
|
||||
},
|
||||
{
|
||||
label: "security",
|
||||
detail: "Route security metadata block",
|
||||
documentation: "Declare authentication, authorization, CSRF, and rate-limit policy metadata.",
|
||||
snippet: [
|
||||
"security {",
|
||||
' auth = "${1|required,optional,public|}"',
|
||||
' csrf = "${2:true}"',
|
||||
"}",
|
||||
].join("\n"),
|
||||
},
|
||||
{
|
||||
label: "load",
|
||||
detail: "Typed data-loading block",
|
||||
documentation: "Load data on the server or client with an explicit execution boundary.",
|
||||
snippet: ["load ${1|server,client|} {", " ${2:return {}}", "}"].join("\n"),
|
||||
},
|
||||
{
|
||||
label: "action",
|
||||
detail: "Named server action",
|
||||
documentation: "Declare a callable mutation with an explicit name and arguments.",
|
||||
snippet: ["action ${1:save}(${2:input}) {", " $0", "}"].join("\n"),
|
||||
},
|
||||
{
|
||||
label: "functions",
|
||||
detail: "Browser component functions block",
|
||||
@@ -215,6 +265,8 @@ const CONTEXT_COMPLETIONS = [
|
||||
["ctx.user", "Authenticated user"],
|
||||
["ctx.session", "Current session"],
|
||||
["ctx.locals", "Request-local data"],
|
||||
["ctx.tenant", "Resolved tenant context"],
|
||||
["ctx.tracer", "Request tracing interface"],
|
||||
];
|
||||
|
||||
const WATCH_VALUE_COMPLETIONS = [
|
||||
|
||||
@@ -11,33 +11,34 @@ const VALID_TOP_LEVEL_KINDS = new Set(["page", "component", "layout"]);
|
||||
|
||||
const VALID_LIFECYCLE_HOOKS = new Set(["mount", "update", "unmount"]);
|
||||
|
||||
const ROOT_MEMBER_NAMES = [
|
||||
"layout",
|
||||
"runtime",
|
||||
"hydrate",
|
||||
"client",
|
||||
"types",
|
||||
"props",
|
||||
"state",
|
||||
"computed",
|
||||
"effect",
|
||||
"watch",
|
||||
"lifecycle",
|
||||
"view",
|
||||
"seo",
|
||||
"security",
|
||||
"load",
|
||||
"action",
|
||||
"api",
|
||||
"ssr",
|
||||
"realtime",
|
||||
"style",
|
||||
"functions",
|
||||
];
|
||||
|
||||
const VALID_MEMBERS = {
|
||||
page: new Set([
|
||||
"layout",
|
||||
"state",
|
||||
"types",
|
||||
"view",
|
||||
"seo",
|
||||
"style",
|
||||
"functions",
|
||||
"api",
|
||||
"ssr",
|
||||
"client",
|
||||
"realtime",
|
||||
]),
|
||||
|
||||
component: new Set([
|
||||
"types",
|
||||
"props",
|
||||
"state",
|
||||
"view",
|
||||
"style",
|
||||
"functions",
|
||||
"lifecycle",
|
||||
"watch",
|
||||
]),
|
||||
|
||||
layout: new Set(["types", "props", "state", "view", "style", "functions", "lifecycle", "watch"]),
|
||||
page: new Set(ROOT_MEMBER_NAMES),
|
||||
component: new Set(ROOT_MEMBER_NAMES),
|
||||
layout: new Set(ROOT_MEMBER_NAMES),
|
||||
};
|
||||
|
||||
function createDiagnostic(
|
||||
@@ -544,21 +545,87 @@ function findRootMembers(source, bodyStart, bodyEnd) {
|
||||
end: identifier.end,
|
||||
});
|
||||
|
||||
if (identifier.name === "state" || identifier.name === "layout") {
|
||||
const assignmentMembers = new Set(["layout", "runtime", "hydrate", "state"]);
|
||||
|
||||
if (assignmentMembers.has(identifier.name)) {
|
||||
skipLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (identifier.name === "watch") {
|
||||
index = skipWhitespace(source, index, bodyEnd);
|
||||
if (identifier.name === "client") {
|
||||
const next = skipWhitespace(source, index, bodyEnd);
|
||||
|
||||
const watchedState = readIdentifier(source, index, bodyEnd);
|
||||
|
||||
if (watchedState) {
|
||||
index = watchedState.end;
|
||||
if (source[next] === "=") {
|
||||
skipLine();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const blockMembers = new Set([
|
||||
"types",
|
||||
"props",
|
||||
"computed",
|
||||
"effect",
|
||||
"watch",
|
||||
"lifecycle",
|
||||
"view",
|
||||
"seo",
|
||||
"security",
|
||||
"load",
|
||||
"action",
|
||||
"api",
|
||||
"ssr",
|
||||
"client",
|
||||
"realtime",
|
||||
"style",
|
||||
"functions",
|
||||
]);
|
||||
|
||||
if (blockMembers.has(identifier.name)) {
|
||||
let cursor = index;
|
||||
let parenthesisDepth = 0;
|
||||
let bracketDepth = 0;
|
||||
let memberQuote = null;
|
||||
let memberEscaped = false;
|
||||
|
||||
while (cursor < bodyEnd) {
|
||||
const current = source[cursor];
|
||||
|
||||
if (memberQuote !== null) {
|
||||
if (memberEscaped) {
|
||||
memberEscaped = false;
|
||||
} else if (current === "\\") {
|
||||
memberEscaped = true;
|
||||
} else if (current === memberQuote) {
|
||||
memberQuote = null;
|
||||
}
|
||||
|
||||
cursor += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === '"' || current === "'") {
|
||||
memberQuote = current;
|
||||
cursor += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === "(") parenthesisDepth += 1;
|
||||
if (current === ")") parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
||||
if (current === "[") bracketDepth += 1;
|
||||
if (current === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
||||
|
||||
if (current === "{" && parenthesisDepth === 0 && bracketDepth === 0) {
|
||||
index = cursor;
|
||||
break;
|
||||
}
|
||||
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
if (cursor >= bodyEnd) index = bodyEnd;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -179,11 +179,14 @@ function parseOpeningTag(value) {
|
||||
|
||||
const inlineClosing = remainder === `</${tagName}>`;
|
||||
|
||||
const closesInRemainder = remainder.startsWith(`</${tagName}>`);
|
||||
|
||||
return {
|
||||
tagName,
|
||||
attributes,
|
||||
selfClosing,
|
||||
inlineClosing,
|
||||
closesInRemainder,
|
||||
remainder,
|
||||
};
|
||||
}
|
||||
@@ -202,7 +205,11 @@ function formatOpeningTag(value, unit, depth, printWidth = 100) {
|
||||
|
||||
const attributeIndent = unit.repeat(depth + 1);
|
||||
|
||||
const normalizedSingleLine = value.replace(/\s+/g, " ").trim();
|
||||
const normalizedOpening = `<${parsed.tagName}${
|
||||
parsed.attributes.length ? ` ${parsed.attributes.join(" ")}` : ""
|
||||
}${parsed.selfClosing ? " /" : ""}>`;
|
||||
|
||||
const normalizedSingleLine = `${normalizedOpening}${parsed.remainder}`;
|
||||
|
||||
const shouldBreak =
|
||||
parsed.attributes.length > 1 ||
|
||||
@@ -212,6 +219,7 @@ function formatOpeningTag(value, unit, depth, printWidth = 100) {
|
||||
const opensElement =
|
||||
!parsed.selfClosing &&
|
||||
!parsed.inlineClosing &&
|
||||
!parsed.closesInRemainder &&
|
||||
!VOID_ELEMENTS.has(parsed.tagName.toLowerCase());
|
||||
|
||||
if (!shouldBreak) {
|
||||
@@ -375,7 +383,9 @@ function collectOpeningTag(inputLines, startIndex) {
|
||||
}
|
||||
|
||||
return {
|
||||
value: collected.join(" "),
|
||||
// Preserve the fact that the opening tag was already multiline so a
|
||||
// second formatter pass cannot collapse it back to one line.
|
||||
value: collected.join("\n"),
|
||||
endIndex: index,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,6 +82,24 @@
|
||||
{
|
||||
"include": "#realtime-block"
|
||||
},
|
||||
{
|
||||
"include": "#execution-decl"
|
||||
},
|
||||
{
|
||||
"include": "#computed-block"
|
||||
},
|
||||
{
|
||||
"include": "#effect-block"
|
||||
},
|
||||
{
|
||||
"include": "#security-block"
|
||||
},
|
||||
{
|
||||
"include": "#load-block"
|
||||
},
|
||||
{
|
||||
"include": "#action-block"
|
||||
},
|
||||
{
|
||||
"include": "#mode-block"
|
||||
},
|
||||
@@ -143,14 +161,24 @@
|
||||
"types-block": {
|
||||
"begin": "\\b(types)\\b\\s*(\\{)",
|
||||
"beginCaptures": {
|
||||
"1": { "name": "keyword.control.wrn" },
|
||||
"2": { "name": "punctuation.definition.block.begin.wrn" }
|
||||
"1": {
|
||||
"name": "keyword.control.wrn"
|
||||
},
|
||||
"2": {
|
||||
"name": "punctuation.definition.block.begin.wrn"
|
||||
}
|
||||
},
|
||||
"end": "\\}",
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.block.end.wrn" }
|
||||
"0": {
|
||||
"name": "punctuation.definition.block.end.wrn"
|
||||
}
|
||||
},
|
||||
"patterns": [{ "include": "source.ts" }]
|
||||
"patterns": [
|
||||
{
|
||||
"include": "source.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"props-block": {
|
||||
"begin": "\\b(props)\\b\\s*(\\{)",
|
||||
@@ -757,6 +785,182 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"execution-decl": {
|
||||
"begin": "\\b(runtime|hydrate|client)\\b\\s*(=)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.control.wrn"
|
||||
},
|
||||
"2": {
|
||||
"name": "keyword.operator.assignment.wrn"
|
||||
}
|
||||
},
|
||||
"end": "$",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#strings"
|
||||
}
|
||||
]
|
||||
},
|
||||
"computed-block": {
|
||||
"begin": "\\b(computed)\\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.other.readwrite.declaration.wrn"
|
||||
},
|
||||
"2": {
|
||||
"name": "keyword.operator.assignment.wrn"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": "source.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"effect-block": {
|
||||
"begin": "\\b(effect)\\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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"security-block": {
|
||||
"begin": "\\b(security)\\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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"load-block": {
|
||||
"begin": "\\b(load)\\b\\s+(server|client)\\s*(\\{)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.control.wrn"
|
||||
},
|
||||
"2": {
|
||||
"name": "storage.modifier.wrn"
|
||||
},
|
||||
"3": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"action-block": {
|
||||
"begin": "\\b(action)\\b\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s*(\\([^)]*\\))\\s*(\\{)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.control.wrn"
|
||||
},
|
||||
"2": {
|
||||
"name": "entity.name.function.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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ const {
|
||||
findTopLevelDeclaration,
|
||||
maskLeadingTrivia,
|
||||
validateBalancedCharacters,
|
||||
validateRootMembers,
|
||||
} = require("../src/diagnostics");
|
||||
Module._load = originalLoad;
|
||||
|
||||
@@ -92,3 +93,25 @@ component ThemeToggle {
|
||||
|
||||
assert.deepEqual(diagnostics, []);
|
||||
});
|
||||
|
||||
test("accepts all WRN 0.3 root members without false unknown-member errors", () => {
|
||||
const source = `page Dashboard {
|
||||
runtime = "universal"
|
||||
hydrate = "visible"
|
||||
computed { doubled = count * 2 }
|
||||
effect { console.log(doubled) }
|
||||
security { auth = "required" }
|
||||
load server { return {} }
|
||||
action save(input) { return input }
|
||||
client { functions { function ready() {} } }
|
||||
view { <main>{doubled}</main> }
|
||||
}`;
|
||||
const declaration = findTopLevelDeclaration({}, source);
|
||||
const document = {
|
||||
positionAt(offset) {
|
||||
return offset;
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepEqual(validateRootMembers(document, source, declaration.kind, declaration.match), []);
|
||||
});
|
||||
|
||||
@@ -19,3 +19,21 @@ test("preserves nested prop defaults while formatting", () => {
|
||||
assert.match(formatted, /options = \{ gap: 4, dense: false \}/);
|
||||
assert.equal(formatWrn(formatted, { insertSpaces: true, tabSize: 2 }), formatted);
|
||||
});
|
||||
|
||||
test("keeps long inline-closing tags idempotent after multiline expansion", () => {
|
||||
const source = `component Status {
|
||||
view {
|
||||
<section class="overflow-hidden rounded-3xl border border-[var(--wire-color-border,#e2e8f0)] bg-[var(--wire-color-surface,#ffffff)]">
|
||||
<span class="inline-flex items-center gap-2 rounded-full bg-[var(--wire-color-surface-2,#f8fafc)] px-3 py-1.5 text-xs font-semibold">
|
||||
<span class="h-2 w-2 animate-pulse rounded-full bg-[var(--wire-color-primary,#059669)]"></span>Loading
|
||||
</span>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
`;
|
||||
const options = { insertSpaces: true, tabSize: 2, printWidth: 100 };
|
||||
const formatted = formatWrn(source, options);
|
||||
|
||||
assert.equal(formatWrn(formatted, options), formatted);
|
||||
assert.match(formatted, /<\/span>Loading/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user