release: WRNexusJS 0.3.5
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/syntax",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.5",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -8,6 +8,7 @@ export type {
|
||||
DataApiBlock,
|
||||
DataMode,
|
||||
EffectBlock,
|
||||
EventDecl,
|
||||
LifecycleBlock,
|
||||
LoadBlock,
|
||||
ModeFunctionsBlock,
|
||||
|
||||
@@ -165,6 +165,11 @@ export interface PropDecl {
|
||||
default: string;
|
||||
}
|
||||
|
||||
export interface EventDecl {
|
||||
/** Public event name used by consumers as `@name="handler(event)"`. */
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface PageAst {
|
||||
type: "page";
|
||||
/** Static ES module imports declared before the WRN root declaration. */
|
||||
@@ -183,6 +188,8 @@ export interface PageAst {
|
||||
hydrate?: string;
|
||||
/** Declared component props (empty for pages). */
|
||||
props: PropDecl[];
|
||||
/** Public events exposed by a reusable component. */
|
||||
events: EventDecl[];
|
||||
/** Raw declarations from `types { ... }`, emitted as TypeScript. */
|
||||
types: string[];
|
||||
states: StateDecl[];
|
||||
@@ -289,6 +296,7 @@ export function parse(source: string): PageAst {
|
||||
let runtime: PageAst["runtime"];
|
||||
let hydrate: string | undefined;
|
||||
const props: PropDecl[] = [];
|
||||
const events: EventDecl[] = [];
|
||||
const types: string[] = [];
|
||||
const states: StateDecl[] = [];
|
||||
const computed: ComputedDecl[] = [];
|
||||
@@ -341,12 +349,31 @@ export function parse(source: string): PageAst {
|
||||
break;
|
||||
}
|
||||
case "props": {
|
||||
// props { name: Type = <default> } — omit the default for required props.
|
||||
// props { name: Type = <default>; @event name = function }
|
||||
lx.next();
|
||||
expect("lbrace");
|
||||
while (lx.peek().type !== "rbrace") {
|
||||
const t = lx.peek();
|
||||
if (t.type === "eof") throw new ParseError("Unexpected end of input inside props");
|
||||
if (t.type === "at") {
|
||||
lx.next();
|
||||
const declarationKind = expect("ident");
|
||||
if (declarationKind.value !== "event") {
|
||||
throw new ParseError(
|
||||
`Expected '@event' but got '@${declarationKind.value}' at offset ${declarationKind.pos}`,
|
||||
);
|
||||
}
|
||||
const eventName = expect("ident").value;
|
||||
expect("eq");
|
||||
const marker = lx.readPropInitializer();
|
||||
if (marker !== "function") {
|
||||
throw new ParseError(
|
||||
`Event '${eventName}' must be declared as '@event ${eventName} = function'`,
|
||||
);
|
||||
}
|
||||
events.push({ name: eventName });
|
||||
continue;
|
||||
}
|
||||
if (t.type !== "ident") {
|
||||
throw new ParseError(`Expected a prop name at offset ${t.pos}`);
|
||||
}
|
||||
@@ -627,6 +654,7 @@ export function parse(source: string): PageAst {
|
||||
runtime,
|
||||
hydrate,
|
||||
props,
|
||||
events,
|
||||
types,
|
||||
states,
|
||||
computed,
|
||||
|
||||
@@ -92,3 +92,17 @@ test("parses keyed each blocks without changing legacy loop syntax", () => {
|
||||
expect.objectContaining({ type: "each", key: undefined }),
|
||||
);
|
||||
});
|
||||
|
||||
test("parses public component event declarations inside props", () => {
|
||||
const ast = parse(`component SearchBox {
|
||||
props {
|
||||
value = ""
|
||||
@event search = function
|
||||
@event clear = function
|
||||
}
|
||||
view { <input value="{value}" /> }
|
||||
}`);
|
||||
|
||||
expect(ast.props.map((prop) => prop.name)).toEqual(["value"]);
|
||||
expect(ast.events).toEqual([{ name: "search" }, { name: "clear" }]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user