fix(vscode): repair WRN language diagnostics and activation

This commit is contained in:
2026-07-19 14:02:17 +05:30
parent df4c1d3e7d
commit 646d16f83d
16 changed files with 425 additions and 171 deletions
+11
View File
@@ -1,7 +1,18 @@
"use strict";
const assert = require("node:assert");
const { test } = require("node:test");
const Module = require("node:module");
// These extraction helpers are pure, but their module also registers VS Code
// providers at runtime. Supply a minimal host shim for unit tests.
const originalLoad = Module._load;
Module._load = function load(request, parent, isMain) {
if (request === "vscode") return {};
return originalLoad.call(this, request, parent, isMain);
};
const { extractRouteParams, extractStates } = require("../src/completion");
Module._load = originalLoad;
test("extracts dynamic route params from filename", () => {
const document = {
+81
View File
@@ -0,0 +1,81 @@
"use strict";
const assert = require("node:assert");
const { test } = require("node:test");
const Module = require("node:module");
const originalLoad = Module._load;
Module._load = function load(request, parent, isMain) {
if (request === "vscode") {
return {
Diagnostic: class Diagnostic {
constructor(range, message, severity) {
this.range = range;
this.message = message;
this.severity = severity;
}
},
DiagnosticSeverity: { Error: 0, Warning: 1 },
Range: class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
},
};
}
return originalLoad.call(this, request, parent, isMain);
};
const {
findTopLevelDeclaration,
maskLeadingTrivia,
validateBalancedCharacters,
} = require("../src/diagnostics");
Module._load = originalLoad;
function mockDocument(source) {
return {
positionAt(offset) {
return offset;
},
};
}
test("recognizes a WRN declaration after leading line comments", () => {
const source = `// Component summary
// More details
component ThemeToggle {
view { <button>Toggle</button> }
}`;
const declaration = findTopLevelDeclaration({}, source);
assert.equal(declaration.kind, "component");
assert.equal(declaration.name, "ThemeToggle");
assert.equal(declaration.diagnostic, undefined);
});
test("masks only leading trivia and preserves source offsets", () => {
const source = "\uFEFF// Summary\r\npage Home {\n // member comment\n}";
const masked = maskLeadingTrivia(source);
assert.equal(masked.length, source.length);
assert.equal(masked.indexOf("page Home"), source.indexOf("page Home"));
assert.match(masked, /\/\/ member comment/);
});
test("ignores apostrophes and brackets in line comments when checking balance", () => {
const source = `// The framework's theme runtime binds the click }
component ThemeToggle {
props {
label = "Toggle theme"
class = ""
}
view { <button class="{class}" data-wire-theme-toggle><slot>{label}</slot></button> }
}`;
const diagnostics = validateBalancedCharacters(mockDocument(source), source);
assert.deepEqual(diagnostics, []);
});
+38
View File
@@ -37,10 +37,48 @@ for (const rel of [
const grammar = JSON.parse(readFileSync(join(root, "syntaxes/wrn.tmLanguage.json"), "utf8"));
grammar.scopeName === "source.wrn" ? ok("grammar scopeName") : bad("grammar scopeName");
const localIncludes = [];
const visitGrammarNode = (value) => {
if (Array.isArray(value)) {
value.forEach(visitGrammarNode);
return;
}
if (!value || typeof value !== "object") return;
if (typeof value.include === "string" && value.include.startsWith("#")) {
localIncludes.push(value.include.slice(1));
}
Object.values(value).forEach(visitGrammarNode);
};
visitGrammarNode(grammar.patterns);
visitGrammarNode(grammar.repository);
const missingIncludes = [...new Set(localIncludes)].filter((name) => !grammar.repository?.[name]);
missingIncludes.length === 0
? ok("all local grammar includes resolve")
: bad("all local grammar includes resolve", missingIncludes.join(", "));
grammar.repository?.["conditional-class-single"]
? ok("single-quoted class directives are registered")
: bad("single-quoted class directives are registered");
// 3. Marketplace metadata and the gallery icon meet vsce requirements.
try {
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
manifest.publisher === "wrnexus" ? ok("publisher id") : bad("publisher id");
const language = manifest.contributes?.languages?.find((entry) => entry.id === "wrn");
language?.extensions?.includes(".wrn")
? ok(".wrn extension maps to the wrn language")
: bad(".wrn extension maps to the wrn language");
manifest.contributes?.configurationDefaults?.["files.associations"]?.["*.wrn"] === "wrn"
? ok("default file association uses the wrn language id")
: bad("default file association uses the wrn language id");
manifest.activationEvents?.includes("onLanguage:wrn")
? ok("extension activates for the wrn language")
: bad("extension activates for the wrn language");
manifest.activationEvents?.includes("onStartupFinished")
? ok("extension can repair legacy associations at startup")
: bad("extension can repair legacy associations at startup");
language?.firstLine
? ok("first-line language detection is configured")
: bad("first-line language detection is configured");
manifest.icon?.endsWith(".png") ? ok("Marketplace icon is PNG") : bad("Marketplace icon is PNG");
const icon = readFileSync(join(root, manifest.icon));
const isPng = icon.subarray(1, 4).toString("ascii") === "PNG";