feat(wrn): support native structured prop expressions
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/syntax",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -712,13 +712,30 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en
|
||||
while (i < src.length && isWs(src[i]!)) i++;
|
||||
};
|
||||
|
||||
/** Read a `{...}` interpolation (brace-balanced), braces included. */
|
||||
/** Read a `{...}` interpolation (brace-balanced and quote-aware), braces included. */
|
||||
const readInterpolation = (): string => {
|
||||
const start = i;
|
||||
let depth = 0;
|
||||
let quote: string | null = null;
|
||||
for (; i < src.length; i++) {
|
||||
if (src[i] === "{") depth++;
|
||||
else if (src[i] === "}" && --depth === 0) {
|
||||
const char = src[i]!;
|
||||
|
||||
if (quote) {
|
||||
if (char === "\\" && i + 1 < src.length) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (char === quote) quote = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"' || char === "'" || char === "`") {
|
||||
quote = char;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "{") depth++;
|
||||
else if (char === "}" && --depth === 0) {
|
||||
i++;
|
||||
return src.slice(start, i);
|
||||
}
|
||||
@@ -819,7 +836,13 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en
|
||||
if (src[i] === "=") {
|
||||
i++;
|
||||
skipWs();
|
||||
attrs.push({ name, value: readQuoted(), event: false });
|
||||
const value =
|
||||
src[i] === '"' || src[i] === "'"
|
||||
? readQuoted()
|
||||
: src[i] === "{"
|
||||
? readInterpolation()
|
||||
: fail(`Expected a quoted value or {...} expression after '${name}='`);
|
||||
attrs.push({ name, value, event: false });
|
||||
} else {
|
||||
attrs.push({ name, value: "", event: false, boolean: true });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,35 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { diagnose, formatDiagnostic, parse } from "../src/index.ts";
|
||||
|
||||
test("parses native array and object literals in state and component props", () => {
|
||||
const ast = parse(`
|
||||
component Navigation {
|
||||
state items = [{"label":"Home","href":"/"}]
|
||||
state options = {"dense":true,"nested":{"label":"A {brace}"}}
|
||||
view {
|
||||
<Navbar items={items} />
|
||||
<Sidebar items={[{"label":"Cases","href":"/cases"}]} options={{"dense":true}} />
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
expect(ast.states[0]?.expr).toBe('[{"label":"Home","href":"/"}]');
|
||||
expect(ast.states[1]?.expr).toBe('{"dense":true,"nested":{"label":"A {brace}"}}');
|
||||
|
||||
const elements = ast.view.filter((node) => node.type === "element");
|
||||
const navbar = elements[0];
|
||||
const sidebar = elements[1];
|
||||
expect(navbar?.type).toBe("element");
|
||||
expect(sidebar?.type).toBe("element");
|
||||
if (navbar?.type !== "element" || sidebar?.type !== "element") return;
|
||||
|
||||
expect(navbar.attrs.find((attr) => attr.name === "items")?.value).toBe("{items}");
|
||||
expect(sidebar.attrs.find((attr) => attr.name === "items")?.value).toBe(
|
||||
'{[{"label":"Cases","href":"/cases"}]}',
|
||||
);
|
||||
expect(sidebar.attrs.find((attr) => attr.name === "options")?.value).toBe('{{"dense":true}}');
|
||||
});
|
||||
|
||||
test("parses WRN 0.3 execution, data, security, and reactivity blocks", () => {
|
||||
const ast = parse(`page Dashboard {
|
||||
runtime = "universal"
|
||||
|
||||
Reference in New Issue
Block a user