release: WRNexusJS 0.2.77
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.76",
|
||||
"version": "0.2.77",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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: '""' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user