release: WRNexusJS 0.2.29

This commit is contained in:
2026-07-14 17:34:51 +05:30
parent e27c92e3b6
commit e6dbb5b0fc
62 changed files with 513 additions and 292 deletions
+1 -1
View File
@@ -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.5",
"version": "0.2.6",
"publisher": "wrnexus",
"private": true,
"license": "SEE LICENSE IN LICENSE",
+8 -25
View File
@@ -2,14 +2,10 @@
const vscode = require("vscode");
const COMPONENT_DECLARATION =
/^\s*(component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/gm;
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$]*/,
);
const range = document.getWordRangeAtPosition(position, /[A-Za-z_$][\w$]*/);
if (!range) {
return null;
@@ -23,17 +19,13 @@ function getTagAtPosition(document, position) {
const line = document.lineAt(position.line).text;
const offset = document.offsetAt(position);
const lineStart = document.offsetAt(
new vscode.Position(position.line, 0),
);
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(">");
const insideTag = before.lastIndexOf("<") > before.lastIndexOf(">") && after.includes(">");
if (!insideTag) {
return null;
@@ -75,21 +67,12 @@ async function findDeclaration(name) {
continue;
}
const nameOffset =
match.index +
match[0].indexOf(declarationName);
const nameOffset = match.index + match[0].indexOf(declarationName);
const start = document.positionAt(nameOffset);
const end = document.positionAt(
nameOffset + declarationName.length,
);
const end = document.positionAt(nameOffset + declarationName.length);
matches.push(
new vscode.Location(
uri,
new vscode.Range(start, end),
),
);
matches.push(new vscode.Location(uri, new vscode.Range(start, end)));
}
}
@@ -131,4 +114,4 @@ module.exports = {
getTagAtPosition,
provideDefinition,
registerDefinitionProvider,
};
};
+55 -160
View File
@@ -5,14 +5,9 @@ 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 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_TOP_LEVEL_KINDS = new Set(["page", "component", "layout"]);
const VALID_MEMBERS = {
page: new Set([
@@ -28,21 +23,9 @@ const VALID_MEMBERS = {
"realtime",
]),
component: new Set([
"props",
"state",
"view",
"style",
"functions",
]),
component: new Set(["props", "state", "view", "style", "functions"]),
layout: new Set([
"props",
"state",
"view",
"style",
"functions",
]),
layout: new Set(["props", "state", "view", "style", "functions"]),
};
function createDiagnostic(
@@ -54,10 +37,7 @@ function createDiagnostic(
code,
) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(
document.positionAt(startOffset),
document.positionAt(endOffset),
),
new vscode.Range(document.positionAt(startOffset), document.positionAt(endOffset)),
message,
severity,
);
@@ -80,11 +60,7 @@ function lineDiagnostic(
) {
const line = document.lineAt(lineNumber);
const diagnostic = new vscode.Diagnostic(
line.range,
message,
severity,
);
const diagnostic = new vscode.Diagnostic(line.range, message, severity);
diagnostic.source = "WRNexus";
@@ -96,18 +72,14 @@ function lineDiagnostic(
}
function stripComments(source) {
return source.replace(/<!--[\s\S]*?-->/g, (comment) =>
comment.replace(/[^\n]/g, " "),
);
return source.replace(/<!--[\s\S]*?-->/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);
const firstMeaningfulLine = source.split(/\r?\n/).findIndex((line) => line.trim().length > 0);
return {
diagnostic: lineDiagnostic(
@@ -166,9 +138,7 @@ function validateBalancedCharacters(document, source) {
continue;
}
if (
source.startsWith("<!--", index)
) {
if (source.startsWith("<!--", index)) {
const commentEnd = source.indexOf("-->", index + 4);
if (commentEnd === -1) {
@@ -190,11 +160,7 @@ function validateBalancedCharacters(document, source) {
continue;
}
if (
character === "{" ||
character === "[" ||
character === "("
) {
if (character === "{" || character === "[" || character === "(") {
stack.push({
character,
offset: index,
@@ -203,11 +169,7 @@ function validateBalancedCharacters(document, source) {
continue;
}
if (
character === "}" ||
character === "]" ||
character === ")"
) {
if (character === "}" || character === "]" || character === ")") {
const expectedOpening = pairs[character];
const opening = stack.pop();
@@ -227,12 +189,7 @@ function validateBalancedCharacters(document, source) {
}
for (const opening of stack) {
const expectedClosing =
opening.character === "{"
? "}"
: opening.character === "["
? "]"
: ")";
const expectedClosing = opening.character === "{" ? "}" : opening.character === "[" ? "]" : ")";
diagnostics.push(
createDiagnostic(
@@ -284,8 +241,7 @@ function validateHtmlTags(document, source) {
]);
const cleaned = stripComments(source);
const tagPattern =
/<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g;
const tagPattern = /<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g;
let match;
@@ -358,18 +314,13 @@ function validateHtmlTags(document, source) {
}
function getRootBodyRange(source, rootMatch) {
const openingBrace =
rootMatch.index + rootMatch[0].lastIndexOf("{");
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
) {
for (let index = openingBrace; index < source.length; index += 1) {
const character = source[index];
if (quote !== null) {
@@ -425,6 +376,12 @@ function findRootMembers(source, bodyStart, bodyEnd) {
let quote = null;
let escaped = false;
const skipLine = () => {
while (index < bodyEnd && source[index] !== "\n" && source[index] !== "\r") {
index += 1;
}
};
while (index < bodyEnd) {
const character = source[index];
@@ -473,17 +430,11 @@ function findRootMembers(source, bodyStart, bodyEnd) {
continue;
}
if (
depth === 0 &&
/[A-Za-z_]/.test(character)
) {
if (depth === 0 && /[A-Za-z_]/.test(character)) {
const start = index;
index += 1;
while (
index < bodyEnd &&
/[A-Za-z0-9_-]/.test(source[index])
) {
while (index < bodyEnd && /[A-Za-z0-9_-]/.test(source[index])) {
index += 1;
}
@@ -495,6 +446,11 @@ function findRootMembers(source, bodyStart, bodyEnd) {
end: index,
});
// Single-line declarations must skip the rest of the line.
if (name === "state" || name === "layout") {
skipLine();
}
continue;
}
@@ -504,12 +460,7 @@ function findRootMembers(source, bodyStart, bodyEnd) {
return members;
}
function validateRootMembers(
document,
source,
rootKind,
rootMatch,
) {
function validateRootMembers(document, source, rootKind, rootMatch) {
const diagnostics = [];
const allowed = VALID_MEMBERS[rootKind];
@@ -518,11 +469,7 @@ function validateRootMembers(
}
const bodyRange = getRootBodyRange(source, rootMatch);
const members = findRootMembers(
source,
bodyRange.start,
bodyRange.end,
);
const members = findRootMembers(source, bodyRange.start, bodyRange.end);
for (const member of members) {
if (allowed.has(member.name)) {
@@ -544,12 +491,7 @@ function validateRootMembers(
return diagnostics;
}
function validateRequiredView(
document,
source,
rootKind,
rootMatch,
) {
function validateRequiredView(document, source, rootKind, rootMatch) {
const bodyRange = getRootBodyRange(source, rootMatch);
const body = source.slice(bodyRange.start, bodyRange.end);
@@ -569,17 +511,10 @@ function validateRequiredView(
];
}
function validateLayoutUsage(
document,
source,
rootKind,
) {
function validateLayoutUsage(document, source, rootKind) {
const diagnostics = [];
if (
rootKind !== "page" &&
/^\s*layout\s*=/m.test(source)
) {
if (rootKind !== "page" && /^\s*layout\s*=/m.test(source)) {
const match = /^\s*layout\s*=/m.exec(source);
if (match) {
@@ -588,7 +523,7 @@ function validateLayoutUsage(
document,
match.index,
match.index + match[0].length,
"`layout = \"...\"` is only valid inside a page.",
'`layout = "..."` is only valid inside a page.',
vscode.DiagnosticSeverity.Error,
"wrn-invalid-layout-member",
),
@@ -611,16 +546,11 @@ function validateDocument(document) {
}
const diagnostics = [];
const declaration = findTopLevelDeclaration(
document,
source,
);
const declaration = findTopLevelDeclaration(document, source);
if (declaration.diagnostic) {
diagnostics.push(declaration.diagnostic);
diagnostics.push(
...validateBalancedCharacters(document, source),
);
diagnostics.push(...validateBalancedCharacters(document, source));
return diagnostics;
}
@@ -630,8 +560,7 @@ function validateDocument(document) {
createDiagnostic(
document,
declaration.match.index,
declaration.match.index +
declaration.match[0].length,
declaration.match.index + declaration.match[0].length,
`Unsupported WRN declaration \`${declaration.kind}\`.`,
vscode.DiagnosticSeverity.Error,
"wrn-invalid-kind",
@@ -641,48 +570,21 @@ function validateDocument(document) {
return diagnostics;
}
diagnostics.push(
...validateBalancedCharacters(document, source),
);
diagnostics.push(...validateBalancedCharacters(document, source));
diagnostics.push(
...validateHtmlTags(document, source),
);
diagnostics.push(...validateHtmlTags(document, source));
diagnostics.push(
...validateRootMembers(
document,
source,
declaration.kind,
declaration.match,
),
);
diagnostics.push(...validateRootMembers(document, source, declaration.kind, declaration.match));
diagnostics.push(
...validateRequiredView(
document,
source,
declaration.kind,
declaration.match,
),
);
diagnostics.push(...validateRequiredView(document, source, declaration.kind, declaration.match));
diagnostics.push(
...validateLayoutUsage(
document,
source,
declaration.kind,
),
);
diagnostics.push(...validateLayoutUsage(document, source, declaration.kind));
return diagnostics;
}
function registerDiagnostics(context) {
const collection =
vscode.languages.createDiagnosticCollection(
COLLECTION_NAME,
);
const collection = vscode.languages.createDiagnosticCollection(COLLECTION_NAME);
const timers = new Map();
@@ -691,9 +593,7 @@ function registerDiagnostics(context) {
return;
}
const previousTimer = timers.get(
document.uri.toString(),
);
const previousTimer = timers.get(document.uri.toString());
if (previousTimer) {
clearTimeout(previousTimer);
@@ -702,10 +602,7 @@ function registerDiagnostics(context) {
const timer = setTimeout(() => {
timers.delete(document.uri.toString());
collection.set(
document.uri,
validateDocument(document),
);
collection.set(document.uri, validateDocument(document));
}, 150);
timers.set(document.uri.toString(), timer);
@@ -726,19 +623,17 @@ function registerDiagnostics(context) {
vscode.workspace.onDidSaveTextDocument(update),
vscode.workspace.onDidCloseTextDocument(
(document) => {
const key = document.uri.toString();
const timer = timers.get(key);
vscode.workspace.onDidCloseTextDocument((document) => {
const key = document.uri.toString();
const timer = timers.get(key);
if (timer) {
clearTimeout(timer);
timers.delete(key);
}
if (timer) {
clearTimeout(timer);
timers.delete(key);
}
collection.delete(document.uri);
},
),
collection.delete(document.uri);
}),
{
dispose() {
@@ -758,4 +653,4 @@ module.exports = {
validateDocument,
validateHtmlTags,
validateRootMembers,
};
};
Binary file not shown.
Binary file not shown.