release: WRNexusJS 0.2.77

This commit is contained in:
2026-07-22 01:26:10 +05:30
parent 569365143b
commit 7ff5b3e8c5
95 changed files with 1077 additions and 177 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/compiler",
"version": "0.2.76",
"version": "0.2.77",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -295,7 +295,7 @@ export function parse(source: string): PageAst {
expect("eq");
hasDefault = true;
}
const defaultValue = hasDefault ? lx.readToLineEnd() : "undefined";
const defaultValue = hasDefault ? lx.readPropInitializer() : "undefined";
props.push({ name: pName, valueType, required: !hasDefault, default: defaultValue });
}
expect("rbrace");
+68
View File
@@ -138,6 +138,74 @@ export class Lexer {
return v;
}
/**
* Read a prop default initializer. The initializer may contain nested arrays,
* objects, calls, strings, or template literals. At top level it ends at a
* newline, the closing brace of the props block, or the next inline prop
* declaration (`name = ...` / `name: Type = ...`).
*/
readPropInitializer(): string {
const { src } = this;
while (this.pos < src.length && (src[this.pos] === " " || src[this.pos] === "\t")) {
this.pos++;
}
const start = this.pos;
let square = 0;
let brace = 0;
let paren = 0;
let angle = 0;
let quote: string | null = null;
const atTopLevel = () => square === 0 && brace === 0 && paren === 0 && angle === 0;
while (this.pos < src.length) {
const c = src[this.pos]!;
if (quote) {
this.pos++;
if (c === "\\" && this.pos < src.length) {
this.pos++;
} else if (c === quote) {
quote = null;
}
continue;
}
if (c === '"' || c === "'" || c === "`") {
quote = c;
this.pos++;
continue;
}
if (atTopLevel()) {
if (c === "\n" || c === "\r" || c === "}") break;
if (c === " " || c === "\t") {
let look = this.pos;
while (look < src.length && (src[look] === " " || src[look] === "\t")) look++;
const rest = src.slice(look);
if (/^[A-Za-z_][A-Za-z0-9_]*(?:\s*:[^=\r\n{}]+)?\s*=/.test(rest)) break;
}
}
if (c === "[") square++;
else if (c === "]" && square > 0) square--;
else if (c === "{") brace++;
else if (c === "}" && brace > 0) brace--;
else if (c === "(") paren++;
else if (c === ")" && paren > 0) paren--;
else if (c === "<") angle++;
else if (c === ">" && angle > 0) angle--;
this.pos++;
}
const value = src.slice(start, this.pos).trim();
if (!value) throw new LexError(`Expected a prop initializer at offset ${start}`);
return value;
}
/** Read the rest of the current line (used for `state x = <expr>`). */
readToLineEnd(): string {
const { src } = this;
@@ -0,0 +1,33 @@
import { describe, expect, test } from "bun:test";
import { parse, type PropDecl } from "../src/parser.ts";
const compact = (source: string) =>
parse(source).props.map(({ name, default: value }: PropDecl) => ({ name, value }));
describe("inline props", () => {
test("parses multiple props declared on one line", () => {
expect(
compact(
`component Example { props { eyebrow = "" title = "" description = "" align = "start" class = "" } view { <div></div> } }`,
),
).toEqual([
{ name: "eyebrow", value: '""' },
{ name: "title", value: '""' },
{ name: "description", value: '""' },
{ name: "align", value: '"start"' },
{ name: "class", value: '""' },
]);
});
test("preserves nested object and array defaults", () => {
expect(
compact(
`component Example { props { items = [{ label: "A" }, { label: "B" }] options = { gap: 4, dense: false } class = "" } view { <div></div> } }`,
),
).toEqual([
{ name: "items", value: '[{ label: "A" }, { label: "B" }]' },
{ name: "options", value: "{ gap: 4, dense: false }" },
{ name: "class", value: '""' },
]);
});
});