fix(vscode): repair WRN language diagnostics and activation
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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, []);
|
||||
});
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user