@xaendar/compiler 0.7.31 → 0.7.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3531 +0,0 @@
1
- import { Stack, indent } from "@xaendar/common";
2
- import { ScriptTarget, SyntaxKind, createSourceFile, forEachChild, getNameOfDeclaration, isAccessor, isArrayLiteralExpression, isCallExpression, isClassDeclaration, isExpressionStatement, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isStringLiteral } from "typescript";
3
- //#region \0rolldown/runtime.js
4
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
5
- //#endregion
6
- //#region ../packages/compiler/src/parser/types/node.enum.ts
7
- /**
8
- * Discriminant values that identify the type of each AST node produced by the parser.
9
- */
10
- var ASTNodeType = /* @__PURE__ */ function(ASTNodeType) {
11
- /**
12
- * An HTML element node with a tag name, attributes, events, and children.
13
- */
14
- ASTNodeType[ASTNodeType["Element"] = 0] = "Element";
15
- /**
16
- * A plain text node.
17
- */
18
- ASTNodeType[ASTNodeType["Text"] = 1] = "Text";
19
- /**
20
- * An inline interpolation expression or literal.
21
- */
22
- ASTNodeType[ASTNodeType["Interpolation"] = 2] = "Interpolation";
23
- /**
24
- * An `@if` conditional node.
25
- */
26
- ASTNodeType[ASTNodeType["If"] = 3] = "If";
27
- /**
28
- * An `@else` branch node attached to an `@if`.
29
- */
30
- ASTNodeType[ASTNodeType["Else"] = 4] = "Else";
31
- /**
32
- * An `@else if` branch node attached to an `@if`.
33
- */
34
- ASTNodeType[ASTNodeType["ElseIf"] = 5] = "ElseIf";
35
- /**
36
- * An `@for` iteration node.
37
- */
38
- ASTNodeType[ASTNodeType["For"] = 6] = "For";
39
- /**
40
- * An `@switch` node containing one or more case nodes.
41
- */
42
- ASTNodeType[ASTNodeType["Switch"] = 7] = "Switch";
43
- /**
44
- * A `@case` or `@default` branch inside a `@switch`.
45
- */
46
- ASTNodeType[ASTNodeType["Case"] = 8] = "Case";
47
- /**
48
- *
49
- */
50
- ASTNodeType[ASTNodeType["Import"] = 9] = "Import";
51
- return ASTNodeType;
52
- }({});
53
- //#endregion
54
- //#region ../packages/compiler/src/generator/models/compiler-context.model.ts
55
- /**
56
- * Tracks identifier scope during render code generation.
57
- * Each `Context` instance represents one lexical scope (e.g. a `@for` loop body)
58
- * and can be chained to a parent context for outer-scope resolution.
59
- */
60
- var CompilerContext = class {
61
- _parent;
62
- /**
63
- * Identifiers declared directly in this scope, mapped to whether they
64
- * hold a plain value or a signal (e.g. `@for` implicit variables like
65
- * `$index` are signals; the loop item itself is a plain value).
66
- */
67
- _identifiers = /* @__PURE__ */ new Map();
68
- /**
69
- * List of identifiers that should not be resolved
70
- * and touched in anyway
71
- * (e.g. $event)
72
- */
73
- _unresolvableIdentifiers = new Array();
74
- /**
75
- * Creates a new scope context.
76
- *
77
- * @param identifiers - Named identifier bindings declared in this scope.
78
- * Plain strings default to kind `'value'`; pass a `[name, kind]` tuple
79
- * to declare a signal-backed identifier.
80
- * @param _parent - Optional parent context representing the enclosing scope.
81
- */
82
- constructor(identifiers = [], _parent) {
83
- this._parent = _parent;
84
- for (const identifier of identifiers) typeof identifier === "string" ? this._identifiers.set(identifier, "value") : this._identifiers.set(identifier[0], identifier[1]);
85
- }
86
- /**
87
- * Registers a new identifier name in this scope.
88
- *
89
- * @param name - The identifier name to register.
90
- * @param kind - Whether the identifier holds a plain value or a signal.
91
- * Defaults to `'value'`.
92
- * @throws When an identifier with the same name is already declared in this scope.
93
- */
94
- addIdentifier(name, kind = "value") {
95
- if (this.hasIdentifier(name)) throw new Error(`Identifier "${name}" is already declared in this scope.`);
96
- this._identifiers.set(name, kind);
97
- }
98
- /**
99
- * Registers a new unresolvable identifier name in this scope.
100
- * Unresolvable identifiers (e.g. `$event`) are tracked so lookups via
101
- * {@link hasIdentifier} recognize them, but they are never meant to be
102
- * resolved or otherwise manipulated by the compiler.
103
- *
104
- * @param name - The identifier name to register.
105
- * @throws When an identifier with the same name is already declared in this scope.
106
- */
107
- addUnresolvableIdentifier(name) {
108
- if (this.hasIdentifier(name)) throw new Error(`Identifier "${name}" is already declared in this scope.`);
109
- this._unresolvableIdentifiers.push(name);
110
- }
111
- /**
112
- * Removes a previously registered identifier from this scope, if present.
113
- * Does nothing if no identifier with the given name is declared in this scope.
114
- * Note: this only affects the current scope, not any ancestor scopes.
115
- *
116
- * @param name - The identifier name to remove.
117
- */
118
- removeIdentifier(name) {
119
- this._identifiers.delete(name);
120
- }
121
- /**
122
- * Removes a previously registered unresolvable identifier from this scope, if present.
123
- * Does nothing if no unresolvable identifier with the given name is declared in this scope.
124
- * Note: this only affects the current scope, not any ancestor scopes.
125
- *
126
- * @param name - The identifier name to remove.
127
- */
128
- removeUnresolvabledIdentifier(name) {
129
- this._unresolvableIdentifiers = this._unresolvableIdentifiers.filter((identifier) => identifier !== name);
130
- }
131
- /**
132
- * Returns `true` if an identifier with the given name is declared in this
133
- * scope or any of its ancestor scopes.
134
- *
135
- * @param name - The identifier name to look up.
136
- * @returns `true` if the identifier exists in the scope chain, `false` otherwise.
137
- */
138
- hasIdentifier(name) {
139
- return this._identifiers.has(name) || (this._parent?.hasIdentifier(name) ?? false);
140
- }
141
- /**
142
- * Resolves the kind (`'value'` or `'signal'`) of a declared identifier,
143
- * walking up the scope chain if not found in this scope.
144
- *
145
- * @param name - The identifier name to look up.
146
- * @returns The identifier's kind, or `undefined` if it isn't declared
147
- * anywhere in the scope chain.
148
- */
149
- getIdentifierKind(name) {
150
- return this._identifiers.get(name) ?? this._parent?.getIdentifierKind(name);
151
- }
152
- /**
153
- * Returns `true` if an identifier with the given name is declared in this
154
- * scope or any of its ancestor scopes.
155
- *
156
- * @param name - The identifier name to look up.
157
- * @returns `true` if the identifier exists in the scope chain, `false` otherwise.
158
- */
159
- hasUnresolvableIdentifier(name) {
160
- return this._unresolvableIdentifiers.includes(name) || (this._parent?.hasUnresolvableIdentifier(name) ?? false);
161
- }
162
- };
163
- //#endregion
164
- //#region ../packages/compiler/src/generator/utils/generator.utils.ts
165
- /**
166
- * Complete set of JavaScript global identifiers up to ES2026.
167
- *
168
- * These are identifiers that TypeScript's parser classifies as
169
- * `SyntaxKind.Identifier` (unlike true keywords such as `typeof`,
170
- * `instanceof`, `true`, `false`, `null` which have their own SyntaxKind)
171
- * but that must never be prefixed with `this.` inside a template expression
172
- * because they refer to well-known globals, not to component properties.
173
- *
174
- * Organised by ECMAScript category, mirroring the MDN "Standard built-in
175
- * objects" reference, plus the ES2026 additions (Temporal, DisposableStack,
176
- * AsyncDisposableStack, SuppressedError, Math.sumPrecise surface).
177
- *
178
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
179
- */
180
- var GLOBAL_IDENTIFIERS = /* @__PURE__ */ new Set([
181
- "undefined",
182
- "NaN",
183
- "Infinity",
184
- "globalThis",
185
- "eval",
186
- "isFinite",
187
- "isNaN",
188
- "parseFloat",
189
- "parseInt",
190
- "decodeURI",
191
- "decodeURIComponent",
192
- "encodeURI",
193
- "encodeURIComponent",
194
- "escape",
195
- "unescape",
196
- "Object",
197
- "Function",
198
- "Boolean",
199
- "Symbol",
200
- "Error",
201
- "AggregateError",
202
- "EvalError",
203
- "RangeError",
204
- "ReferenceError",
205
- "SyntaxError",
206
- "TypeError",
207
- "URIError",
208
- "SuppressedError",
209
- "InternalError",
210
- "Number",
211
- "BigInt",
212
- "Math",
213
- "Date",
214
- "Temporal",
215
- "String",
216
- "RegExp",
217
- "Array",
218
- "TypedArray",
219
- "Int8Array",
220
- "Uint8Array",
221
- "Uint8ClampedArray",
222
- "Int16Array",
223
- "Uint16Array",
224
- "Int32Array",
225
- "Uint32Array",
226
- "BigInt64Array",
227
- "BigUint64Array",
228
- "Float16Array",
229
- "Float32Array",
230
- "Float64Array",
231
- "Map",
232
- "Set",
233
- "WeakMap",
234
- "WeakSet",
235
- "ArrayBuffer",
236
- "SharedArrayBuffer",
237
- "DataView",
238
- "Atomics",
239
- "JSON",
240
- "WeakRef",
241
- "FinalizationRegistry",
242
- "Iterator",
243
- "AsyncIterator",
244
- "Promise",
245
- "GeneratorFunction",
246
- "AsyncGeneratorFunction",
247
- "Generator",
248
- "AsyncGenerator",
249
- "AsyncFunction",
250
- "DisposableStack",
251
- "AsyncDisposableStack",
252
- "Reflect",
253
- "Proxy",
254
- "Intl",
255
- "WebAssembly",
256
- "window",
257
- "document",
258
- "navigator",
259
- "location",
260
- "history",
261
- "screen",
262
- "console",
263
- "performance",
264
- "crypto",
265
- "fetch",
266
- "alert",
267
- "confirm",
268
- "prompt",
269
- "setTimeout",
270
- "setInterval",
271
- "clearTimeout",
272
- "clearInterval",
273
- "requestAnimationFrame",
274
- "cancelAnimationFrame",
275
- "queueMicrotask",
276
- "structuredClone",
277
- "URL",
278
- "URLSearchParams",
279
- "FormData",
280
- "Headers",
281
- "Request",
282
- "Response",
283
- "AbortController",
284
- "AbortSignal",
285
- "CustomEvent",
286
- "Event",
287
- "EventTarget",
288
- "MutationObserver",
289
- "IntersectionObserver",
290
- "ResizeObserver",
291
- "PerformanceObserver",
292
- "Worker",
293
- "SharedWorker",
294
- "ServiceWorker",
295
- "Blob",
296
- "File",
297
- "FileReader",
298
- "ReadableStream",
299
- "WritableStream",
300
- "TransformStream",
301
- "TextEncoder",
302
- "TextDecoder",
303
- "ImageData",
304
- "Canvas",
305
- "Storage",
306
- "localStorage",
307
- "sessionStorage",
308
- "indexedDB",
309
- "WebSocket",
310
- "XMLHttpRequest",
311
- "HTMLElement",
312
- "HTMLInputElement",
313
- "HTMLButtonElement",
314
- "HTMLFormElement",
315
- "HTMLAnchorElement",
316
- "HTMLImageElement",
317
- "HTMLVideoElement",
318
- "HTMLAudioElement",
319
- "HTMLCanvasElement",
320
- "HTMLSelectElement",
321
- "HTMLTextAreaElement",
322
- "HTMLDivElement",
323
- "HTMLSpanElement",
324
- "HTMLParagraphElement",
325
- "HTMLHeadingElement",
326
- "HTMLTableElement",
327
- "HTMLTableRowElement",
328
- "HTMLTableCellElement",
329
- "HTMLUListElement",
330
- "HTMLOListElement",
331
- "HTMLLIElement",
332
- "HTMLLabelElement",
333
- "HTMLDialogElement",
334
- "HTMLDetailsElement",
335
- "HTMLSlotElement",
336
- "HTMLTemplateElement",
337
- "SVGElement",
338
- "SVGSVGElement",
339
- "Element",
340
- "Node",
341
- "NodeList",
342
- "DocumentFragment",
343
- "ShadowRoot",
344
- "Document",
345
- "Window"
346
- ]);
347
- var ROOT_NODE = "root";
348
- /**
349
- * Resolves references to component properties inside a template expression.
350
- *
351
- * Identifiers that are not found in the active scope chain and are not
352
- * well-known globals are prefixed with `this.` so they resolve against
353
- * the component instance at runtime. Identifiers found in the scope chain
354
- * are resolved either as a bare local reference (if declared directly in
355
- * the current generated function's own scope) or via a runtime
356
- * `parentContext.get(...)` traversal (if inherited from an enclosing
357
- * scope) — and a trailing `()` is appended in either case if the
358
- * identifier was declared as a signal, so the generated code always
359
- * correctly unwraps it.
360
- *
361
- * The original formatting of the expression — parentheses, spacing,
362
- * operator tokens, member access dots — is preserved verbatim by delegating
363
- * to `node.getText()` for any subtree that contains no resolvable identifiers.
364
- *
365
- * @param expression - Either a raw identifier string or a validated
366
- * `Expression` node produced by `validateExpression`.
367
- * @param compilerContext - The active template scope context.
368
- * @returns The resolved expression as a JavaScript string ready for codegen.
369
- *
370
- * @example
371
- * // Simple identifier
372
- * resolveExpression('items', context) // → 'this.items'
373
- *
374
- * @example
375
- * // Signal identifier inherited from an ancestor scope (e.g. `$index` in a
376
- * // nested @for body)
377
- * resolveExpression('$index', context) // → "parentContext.get('$index')()"
378
- *
379
- * @example
380
- * // Complex expression — formatting preserved
381
- * resolveExpression(node, context)
382
- * // typeof id !== 'boolean' || pippo instanceof HTMLElement
383
- * // → typeof this.id !== 'boolean' || this.pippo instanceof HTMLElement
384
- */
385
- function resolveExpression(expression, compilerContext, options) {
386
- return emitNode(expression, expression, compilerContext, mapDefaultOptions(options));
387
- }
388
- /**
389
- * Emits the resolved text for a node.
390
- *
391
- * - If the node has no resolvable identifiers in its subtree, emits
392
- * `node.getText()` verbatim — preserving all original spacing,
393
- * parentheses, dots, and punctuation.
394
- * - If the node is a resolvable Identifier, emits the resolved access
395
- * expression (see {@link resolveIdentifierAccess}).
396
- * - Otherwise recurses into children and concatenates their output.
397
- */
398
- function emitNode(node, parent, compilerContext, options) {
399
- if (isIdentifier(node) && needsResolution(node, parent)) {
400
- const text = node.text;
401
- if (compilerContext?.hasUnresolvableIdentifier(text) || options.skipResolution) return text;
402
- if (options.resolver) return `${options.resolver}.${text}`;
403
- if (compilerContext) return resolveIdentifierAccess(text, compilerContext);
404
- return text;
405
- }
406
- if (!containsResolvableIdentifier(node, parent)) return node.getText();
407
- const sourceText = node.getSourceFile().text;
408
- let result = "";
409
- let lastEnd = node.getStart();
410
- forEachChild(node, (child) => {
411
- result = `${result}${sourceText.slice(lastEnd, child.getStart())}${emitNode(child, node, compilerContext, options)}`;
412
- lastEnd = child.getEnd();
413
- });
414
- return `${result}${sourceText.slice(lastEnd, node.getEnd())}`;
415
- }
416
- /**
417
- * Decides how to access a resolvable identifier's value in generated code,
418
- * based purely on compile-time scope information — no runtime type
419
- * detection is ever needed:
420
- *
421
- * - Declared directly in the CURRENT generated function's own scope (e.g.
422
- * destructured from `vars` in a `@for` body) → bare reference: `name`,
423
- * or `name()` if it's a signal.
424
- * - Declared in an ANCESTOR scope (an enclosing `@for`/`@if`/element-children
425
- * function) → must cross the closure boundary through the runtime
426
- * `Context` chain: `parentContext.get('name')`, or with a trailing `()`
427
- * if it's a signal.
428
- * - Not declared anywhere in the template scope chain → assumed to be a
429
- * component member: `this.name`.
430
- *
431
- * @param text - The identifier name to resolve.
432
- * @param compilerContext - The active template scope context.
433
- * @returns The generated code expression that yields the identifier's value.
434
- */
435
- function resolveIdentifierAccess(text, compilerContext) {
436
- if (compilerContext.hasIdentifier(text)) {
437
- const kind = compilerContext.getIdentifierKind(text);
438
- const access = `context.get('${text}')`;
439
- return kind === "signal" ? `${access}()` : access;
440
- }
441
- return text;
442
- }
443
- /**
444
- * Returns true if the subtree rooted at `node` contains at least one
445
- * Identifier that needs context resolution.
446
- *
447
- * Short-circuits as soon as one is found to avoid visiting the whole tree.
448
- */
449
- function containsResolvableIdentifier(node, parent) {
450
- if (isIdentifier(node) && needsResolution(node, parent)) return true;
451
- let found = false;
452
- forEachChild(node, (child) => {
453
- if (!found) found = containsResolvableIdentifier(child, node);
454
- });
455
- return found;
456
- }
457
- /**
458
- * Returns true if the identifier needs to be resolved against the context
459
- * or prefixed with `this.` — i.e. it is not a global/builtin identifier
460
- * and not the property-name side of a member access expression.
461
- */
462
- function needsResolution(node, parent) {
463
- return !(isPropertyAccessExpression(parent) && parent.name === node || isPropertyAssignment(parent) && parent.name === node || GLOBAL_IDENTIFIERS.has(node.text) || !node.text);
464
- }
465
- /**
466
- * Generates a unique variable name for a DOM element based on its tag name
467
- * and parent node context.
468
- *
469
- * When the parent is the root node (`this._root`), the identifier is based
470
- * solely on the tag name; otherwise the parent name is prepended to ensure
471
- * uniqueness within nested structures. Hyphens in tag names are replaced
472
- * with underscores to produce a valid JavaScript identifier.
473
- *
474
- * @param node - The `ElementNode` for which to generate the identifier.
475
- * @param parentNode - The variable name of the parent node.
476
- * @param index - A numeric suffix to disambiguate sibling elements of the same type.
477
- * @returns A unique variable name string for the element.
478
- */
479
- function getElementIdentifier(node, parentNode, index) {
480
- return getIdentifier(node.tagName, parentNode, index);
481
- }
482
- /**
483
- * Generates a unique variable name for a text or interpolation node.
484
- *
485
- * @param prefix - Optional prefix to use instead of the default `'text'`.
486
- * @param parentNode - The variable name of the parent node.
487
- * @param index - A numeric suffix to disambiguate sibling text nodes.
488
- * @returns A unique variable name string for the text node.
489
- */
490
- function getTextIdentifier(prefix = "text", parentNode, index) {
491
- return getIdentifier(prefix, parentNode, index);
492
- }
493
- /**
494
- * Generates a unique variable name for a control-flow block (if, else-if, else,
495
- * for, switch, case, or default).
496
- *
497
- * @param prefix - The block type prefix (`'if'`, `'elseIf'`, `'else'`, etc.).
498
- * @param parentNode - The variable name of the parent node.
499
- * @param index - A suffix to disambiguate sibling blocks.
500
- * @returns A unique variable name string for the block.
501
- */
502
- function getBlockIdentifier(prefix, parentNode, index) {
503
- return getIdentifier(prefix, parentNode, index);
504
- }
505
- function getIdentifier(prefix, parentNode, index) {
506
- return (parentNode !== "root" ? `${parentNode}__${prefix}${index}` : `${prefix}${index}`).replace(/-/g, "_");
507
- }
508
- function mapDefaultOptions(options) {
509
- return {
510
- skipResolution: options?.skipResolution ?? false,
511
- resolver: options?.resolver ?? "this"
512
- };
513
- }
514
- //#endregion
515
- //#region ../packages/compiler/src/generator/states/generate-element.state.ts
516
- /**
517
- * Generates code for an HTML element node: creates the DOM element, sets attributes,
518
- * attaches event listeners, appends it to the parent, and recursively processes children.
519
- *
520
- * @param node - The `ElementNode` to process.
521
- * @param index - Variable name to use for the created DOM element.
522
- * @param parentNode - Variable name of the parent DOM node to append to.
523
- * @param compilerContext - Current render scope context.
524
- * @returns Array of generated code lines.
525
- */
526
- function generateElement(node, parentNode, index, compilerContext, anchor) {
527
- const attributes = mapAttributes(node.attributes, compilerContext);
528
- const events = mapEvents(node.events, compilerContext);
529
- const nodeName = getElementIdentifier(node, parentNode, index);
530
- const tagName = node.tagName;
531
- const retVal = {
532
- code: [],
533
- functionsToProcess: /* @__PURE__ */ new Map()
534
- };
535
- switch (tagName) {
536
- case "svg":
537
- retVal.code.push("context.createElement = createSVGElement");
538
- break;
539
- case "math": retVal.code.push("context.createElement = createMATHMLElement");
540
- }
541
- retVal.code.push(`const ${nodeName} = _renderElement(${parentNode}, context, ${anchor}, '${tagName}',`);
542
- attributes.length ? retVal.code.push(...indent([
543
- "[",
544
- ...indent(attributes),
545
- "],"
546
- ])) : retVal.code[retVal.code.length - 1] = `${retVal.code[retVal.code.length - 1]} [],`;
547
- events.length ? retVal.code.push(...indent([
548
- "[",
549
- ...indent(events),
550
- "]"
551
- ]), ");") : retVal.code[retVal.code.length - 1] = `${retVal.code[retVal.code.length - 1]} []);`;
552
- switch (tagName) {
553
- case "svg":
554
- case "math": retVal.code.push("context.createElement = createElement");
555
- }
556
- if (node.children.length) {
557
- retVal.functionsToProcess.set(`${nodeName}Children`, {
558
- fn: {
559
- node,
560
- parentNode: nodeName,
561
- context: compilerContext,
562
- precode: getPrecode(tagName)
563
- },
564
- args: [nodeName, "parentContext"]
565
- });
566
- retVal.code.push(`this.${nodeName}Children(${nodeName}, context);`);
567
- }
568
- return retVal;
569
- }
570
- /**
571
- * Maps attribute nodes to their corresponding generated code lines.
572
- *
573
- * @param attributes - The attribute nodes to map onto the element.
574
- * @param compilerContext - Current render scope context, used to resolve identifier references.
575
- * @returns Array of generated code strings, one per attribute.
576
- */
577
- function mapAttributes(attributes, compilerContext) {
578
- return attributes?.map(({ name, value }) => {
579
- const isLiteral = typeof value === "string";
580
- return `{ name: '${name}', value: () => ${isLiteral ? `'${value}'` : resolveExpression(value.expression, compilerContext)}, literal: ${isLiteral} },`;
581
- });
582
- }
583
- /**
584
- * Generates code that attaches event listeners to a DOM element.
585
- *
586
- * For each event node an `addEventListener` call is emitted, binding the event
587
- * to the component instance handler and exposing the native event as `$event`.
588
- *
589
- * @param events - The event nodes to bind to the element.
590
- * @param compilerContext - Current render scope context, used to resolve identifier references.
591
- * @returns Array of generated code lines, one per event listener.
592
- */
593
- function mapEvents(events, compilerContext) {
594
- compilerContext.addUnresolvableIdentifier("$event");
595
- const mappedEvents = events.map((event) => {
596
- let parsedEventParameter = false;
597
- const parameters = event.parameters.map((parameter) => {
598
- const resolvedParameter = resolveExpression(parameter, compilerContext);
599
- if (!parsedEventParameter && resolvedParameter === "$event") {
600
- parsedEventParameter = true;
601
- return `($event) => ${resolvedParameter},`;
602
- } else return `() => ${resolvedParameter},`;
603
- });
604
- const eventCode = ["{", ...indent([
605
- `name: '${event.name}',`,
606
- `handler: '${event.handler}',`,
607
- "parameters: ["
608
- ])];
609
- if (parameters.length) eventCode.push(...indent([...indent(parameters), "]"]), "},");
610
- else {
611
- eventCode[eventCode.length - 1] = `${eventCode[eventCode.length - 1]}]`;
612
- eventCode.push("},");
613
- }
614
- return eventCode;
615
- }).flat();
616
- compilerContext.removeIdentifier("$event");
617
- return mappedEvents;
618
- }
619
- function getPrecode(tagName) {
620
- switch (tagName) {
621
- case "svg": return "context.createElement = createSVGElement;";
622
- case "math": return "context.createElement = createMATHMLElement;";
623
- default: return "";
624
- }
625
- }
626
- //#endregion
627
- //#region ../packages/compiler/src/generator/states/generate-for.state.ts
628
- /**
629
- * Generates code for a `@for` iteration node.
630
- *
631
- * Emits a classic index-based `for` loop with all implicit variables
632
- * declared at the top of the loop body:
633
- *
634
- * ```javascript
635
- * for (let $i = 0; $i < ctx_items.length; $i++) {
636
- * const item = items[$i];
637
- * const $index = $i;
638
- * const $first = $i === 0;
639
- * const $last = $i === items.length - 1;
640
- * const $even = $i % 2 === 0;
641
- * const $odd = $i % 2 !== 0;
642
- * // ... child nodes
643
- * }
644
- * ```
645
- *
646
- * The iterable identifier is resolved through the active {@link Context}:
647
- * if found in scope it is used as-is, otherwise `this.` is prepended.
648
- *
649
- * The internal loop counter is always named `$i_<nodeName>` to avoid
650
- * collisions when `@for` blocks are nested.
651
- *
652
- * @param node - The `ForNode` to process.
653
- * @param index - Base variable name prefix used for child nodes and
654
- * to produce a unique loop counter identifier.
655
- * @param parentNode - Variable name of the parent DOM node.
656
- * @param compilerContext - The enclosing scope context.
657
- * @returns Array of generated code lines.
658
- */
659
- function generateFor(node, parentNode, index, compilerContext) {
660
- const retVal = {
661
- code: [],
662
- functionsToProcess: /* @__PURE__ */ new Map()
663
- };
664
- const iterableSource = node.iterableSource;
665
- const iterableExpr = compilerContext.hasIdentifier(iterableSource) ? iterableSource : `this.${iterableSource}`;
666
- const itemsName = getTextIdentifier("items", parentNode, index);
667
- const counterName = getTextIdentifier("i", parentNode, index);
668
- const indexName = resolveImplicit$1(node, "$index");
669
- const firstName = resolveImplicit$1(node, "$first");
670
- const lastName = resolveImplicit$1(node, "$last");
671
- const evenName = resolveImplicit$1(node, "$even");
672
- const oddName = resolveImplicit$1(node, "$odd");
673
- const forContext = new CompilerContext([
674
- node.itemAlias,
675
- [indexName, "signal"],
676
- [firstName, "signal"],
677
- [lastName, "signal"],
678
- [evenName, "signal"],
679
- [oddName, "signal"]
680
- ], compilerContext);
681
- const forKey = getBlockIdentifier("for", parentNode, index);
682
- retVal.functionsToProcess.set(forKey, {
683
- fn: {
684
- precode: `const { vars, update } = _iterationVariables(context, ${itemsName}, ${counterName}, '${node.itemAlias}', {
685
- $index: '${indexName}',
686
- $first: '${firstName}',
687
- $last: '${lastName}',
688
- $even: '${evenName}',
689
- $odd: '${oddName}'
690
- });
691
- const { ${node.itemAlias}, ${indexName}, ${firstName}, ${lastName}, ${evenName}, ${oddName} } = vars;`,
692
- node,
693
- parentNode: forKey,
694
- context: forContext,
695
- anchor: "anchor",
696
- isForBody: true
697
- },
698
- args: [
699
- forKey,
700
- "parentContext",
701
- itemsName,
702
- counterName,
703
- "anchor"
704
- ]
705
- });
706
- retVal.code.push(`_for(${parentNode}, context, () => ${iterableExpr}, ${node.itemAlias} => ${resolveExpression(node.trackExpression, forContext, { skipResolution: true })}, this.${forKey}.bind(this));`);
707
- return retVal;
708
- }
709
- /**
710
- * Resolves the name that should be used in generated code for a given
711
- * implicit variable.
712
- *
713
- * If the template declared an explicit alias for the variable
714
- * (e.g. `; $index = i`) that alias is returned. Otherwise the default
715
- * implicit variable name (e.g. `$index`) is used.
716
- *
717
- * @param node - The `ForNode` whose implicit alias map is consulted.
718
- * @param implicit - The implicit variable to look up (e.g. `'$index'`).
719
- * @returns The alias string if one was declared, otherwise `implicit` itself.
720
- */
721
- function resolveImplicit$1(node, implicit) {
722
- return node.implicitAliases.get(implicit) ?? implicit;
723
- }
724
- //#endregion
725
- //#region ../packages/compiler/src/generator/states/generate-if.state.ts
726
- function generateIf(node, parentNode, index, compilerContext) {
727
- const ifContext = new CompilerContext([], compilerContext);
728
- const retVal = {
729
- code: [],
730
- functionsToProcess: /* @__PURE__ */ new Map()
731
- };
732
- retVal.code.push(`_if(${parentNode}, context, [`);
733
- const ifKey = getBlockIdentifier("if", parentNode, index);
734
- retVal.code.push(...indent([
735
- "{",
736
- ...indent([`condition: () => ${resolveExpression(node.conditionNode, compilerContext)},`, `block: this.${ifKey}.bind(this)`]),
737
- "},"
738
- ]));
739
- retVal.functionsToProcess.set(ifKey, {
740
- fn: {
741
- node,
742
- parentNode: ifKey,
743
- context: ifContext,
744
- anchor: "anchor"
745
- },
746
- args: [
747
- ifKey,
748
- "parentContext",
749
- "anchor"
750
- ]
751
- });
752
- let alt = node.alternate;
753
- let i = 0;
754
- while (alt?.type === ASTNodeType.ElseIf) {
755
- const elseIfContext = new CompilerContext([], compilerContext);
756
- const keyElseIf = getBlockIdentifier("elseIf", parentNode, `${index}_${i}`);
757
- const conditionNode = alt.conditionNode;
758
- retVal.code.push(...indent([
759
- "{",
760
- ...indent([`condition: () => ${resolveExpression(conditionNode, compilerContext)},`, `block: this.${keyElseIf}.bind(this)`]),
761
- "},"
762
- ]));
763
- retVal.functionsToProcess.set(keyElseIf, {
764
- fn: {
765
- node: alt,
766
- parentNode,
767
- context: elseIfContext,
768
- anchor: "anchor"
769
- },
770
- args: [
771
- parentNode,
772
- "parentContext",
773
- "anchor"
774
- ]
775
- });
776
- alt = alt.alternate;
777
- i++;
778
- }
779
- if (alt) {
780
- const elseContext = new CompilerContext([], compilerContext);
781
- const keyElse = getBlockIdentifier("else", parentNode, index);
782
- retVal.code.push(...indent([
783
- "{",
784
- ...indent([`block: this.${keyElse}.bind(this)`]),
785
- "},"
786
- ]));
787
- retVal.functionsToProcess.set(keyElse, {
788
- fn: {
789
- node: alt,
790
- parentNode,
791
- context: elseContext,
792
- anchor: "anchor"
793
- },
794
- args: [
795
- parentNode,
796
- "parentContext",
797
- "anchor"
798
- ]
799
- });
800
- }
801
- retVal.code.push("]);");
802
- return retVal;
803
- }
804
- //#endregion
805
- //#region ../packages/compiler/src/generator/states/generate-switch.state.ts
806
- /**
807
- * Generates code for a `@switch` node.
808
- * Emits a `_switch(...)` call that delegates to the reactive `_switch` runtime utility.
809
- *
810
- * @param node - The `SwitchNode` to process.
811
- * @param index - Base variable name prefix for child nodes.
812
- * @param parentNode - Variable name of the parent DOM node.
813
- * @param compilerContext - Current render scope context.
814
- * @returns An object with the main block code lines and a map of helper functions to register.
815
- */
816
- function generateSwitch(node, parentNode, index, compilerContext) {
817
- const retVal = {
818
- code: [],
819
- functionsToProcess: /* @__PURE__ */ new Map()
820
- };
821
- retVal.code.push(`_switch(${parentNode}, context, () => ${resolveExpression(node.expression, compilerContext)}, [`);
822
- node.children.forEach((caseNode, i) => {
823
- const caseContext = new CompilerContext([], compilerContext);
824
- const caseKey = caseNode.condition ? getBlockIdentifier("case", parentNode, `${index}_${i}`) : getBlockIdentifier("default", parentNode, index);
825
- retVal.functionsToProcess.set(caseKey, {
826
- fn: {
827
- node: caseNode,
828
- parentNode: caseKey,
829
- context: caseContext
830
- },
831
- args: [
832
- caseKey,
833
- "parentContext",
834
- "anchor"
835
- ]
836
- });
837
- const fnName = `this.${caseKey}.bind(this)`;
838
- retVal.code.push(...indent([
839
- "{",
840
- ...indent([`condition: ${caseNode.condition ? `[${caseNode.condition.join(", ")}]` : `null`},`, `block: ${fnName}`]),
841
- "},"
842
- ]));
843
- });
844
- retVal.code.push("])");
845
- return retVal;
846
- }
847
- //#endregion
848
- //#region ../packages/compiler/src/generator/states/generate-text-and-interpolation.state.ts
849
- /**
850
- * Generates code for a text or interpolation node.
851
- *
852
- * Emits a `_renderLiteralText` call for plain text nodes and a
853
- * `_renderText` call for interpolation nodes, both appending a DOM text
854
- * node to the parent.
855
- *
856
- * @param node - A `TextNode` or `InterpolationNode` to process.
857
- * @param parentNode - Variable name of the parent DOM node to append to.
858
- * @returns Array of generated code lines.
859
- */
860
- function generateTextAndInterpolation(node, parentNode, _index, compilerContext) {
861
- return { code: [`${node.type === ASTNodeType.Text ? `_renderLiteralText(${parentNode}, context, '${node.value}');` : `_renderText(${parentNode}, context, () => ${resolveExpression(node.expression, compilerContext)});`}`] };
862
- }
863
- //#endregion
864
- //#region ../packages/compiler/src/generator/states/skip-generation.state.ts
865
- function skipGeneration(_node, _parentNode, _index, _compilerContext) {}
866
- //#endregion
867
- //#region ../packages/compiler/src/generator/generator.ts
868
- var Generator = class {
869
- _ast;
870
- _nodeToProcess = /* @__PURE__ */ new Map();
871
- _states = {
872
- [ASTNodeType.Text]: generateTextAndInterpolation,
873
- [ASTNodeType.Interpolation]: generateTextAndInterpolation,
874
- [ASTNodeType.Element]: generateElement,
875
- [ASTNodeType.If]: generateIf,
876
- [ASTNodeType.For]: generateFor,
877
- [ASTNodeType.Switch]: generateSwitch,
878
- [ASTNodeType.Import]: skipGeneration
879
- };
880
- constructor(_ast) {
881
- this._ast = _ast;
882
- }
883
- generate(cssVariableName) {
884
- this._nodeToProcess.clear();
885
- const compilerContext = new CompilerContext();
886
- const generatedCode = ["_render() {", ...indent([`const ${ROOT_NODE} = this._root;`, "const context = new Context(this, { createElement: document.createElement.bind(document), get: () => undefined });"])];
887
- if (cssVariableName) generatedCode.push(indent(`${ROOT_NODE}.adoptedStyleSheets = [${cssVariableName}];`));
888
- for (let i = 0; i < this._ast.length; i++) {
889
- const result = this._processNode(this._ast[i], ROOT_NODE, i.toString(), compilerContext, null);
890
- if (result) {
891
- const { code, functionsToProcess } = result;
892
- functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
893
- generatedCode.push(...indent(code));
894
- }
895
- }
896
- generatedCode.push(...indent(["return context;"]), "}");
897
- for (const [key, fnData] of this._nodeToProcess.entries()) {
898
- const { node, parentNode, context, precode, anchor } = fnData.fn;
899
- generatedCode.push(`\n${key}(${fnData.args?.join(", ")}) {`, ...indent(["const context = new Context(this, parentContext);"]));
900
- if (precode) generatedCode.push(indent(precode));
901
- generatedCode.push(...indent([...node.children.map((child, i) => {
902
- const result = this._processNode(child, parentNode, i.toString(), context, anchor ?? null);
903
- if (result) {
904
- const { code, functionsToProcess } = result;
905
- functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
906
- return code;
907
- }
908
- return "";
909
- }).flat(), fnData.fn.isForBody ? "return { context, update };" : "return context;"]), "}");
910
- }
911
- return generatedCode.join("\n");
912
- }
913
- _processNode(node, parentNode, index, compilerContext, anchor) {
914
- const state = this._states[node.type];
915
- if (!state) throw new Error(`[Generator] No transition function for token type ${ASTNodeType[node.type]}`);
916
- return state(node, parentNode, index, compilerContext, anchor);
917
- }
918
- };
919
- //#endregion
920
- //#region ../packages/compiler/src/lexer/types/lexer-state.enum.ts
921
- /**
922
- * Represents the set of states the lexer can be in while processing template input.
923
- */
924
- var LexerState = /* @__PURE__ */ function(LexerState) {
925
- /**
926
- * Consuming plain text content between tags or at the top level.
927
- */
928
- LexerState["TEXT"] = "text";
929
- /**
930
- * Consuming the opening tag name after `<`.
931
- */
932
- LexerState["TAG_OPEN_NAME"] = "tag-open-name";
933
- /**
934
- * Inside an open tag body, scanning for attributes, events, or the closing `>`.
935
- */
936
- LexerState["TAG_BODY"] = "tag-body";
937
- /**
938
- * Processing the end of an open tag: `>` or `/>`.
939
- */
940
- LexerState["TAG_OPEN_END"] = "tag-open-end";
941
- /**
942
- * Consuming a closing tag `</tagName>`.
943
- */
944
- LexerState["TAG_CLOSE"] = "tag-close";
945
- /**
946
- * Consuming an HTML attribute name and its optional value.
947
- */
948
- LexerState["ATTRIBUTE"] = "attribute";
949
- /**
950
- * Consuming a DOM event binding starting with `@`.
951
- */
952
- LexerState["EVENT"] = "event";
953
- /**
954
- * Consuming a DOM event parameter
955
- */
956
- LexerState["EVENT_PARAMETER"] = "parameter";
957
- /**
958
- * Dispatching a flow-control keyword (@if, @for, @switch, etc.).
959
- */
960
- LexerState["FLOW_CONTROL"] = "flow-control";
961
- /**
962
- * Consuming the condition expression `(...)` of a flow-control directive.
963
- */
964
- LexerState["FLOW_CONTROL_CONDITION"] = "flow-control-condition";
965
- /**
966
- * Consuming the condition expression `(...)` of a @case directive.
967
- * This is needed to correctly handle special consecutives @case
968
- */
969
- LexerState["CASE_FLOW_CONTROL_CONDITION"] = "case-flow-control-condition";
970
- /**
971
- * Consuming the opening `{` of a flow-control block body.
972
- */
973
- LexerState["FLOW_CONTROL_BLOCK"] = "flow-control-block";
974
- /**
975
- * Consuming an attribute literal value
976
- */
977
- LexerState["ATTRIBUTE_VALUE"] = "attribute-value";
978
- /**
979
- * Dispatching between an expression or literal interpolation after `{`.
980
- */
981
- LexerState["INTERPOLATION"] = "interpolation";
982
- /**
983
- * Consuming a JavaScript expression inside `{ }`.
984
- */
985
- LexerState["INTERPOLATION_EXPRESSION"] = "interpolation-expression";
986
- /**
987
- * Consuming a template-literal string inside `` {`...`} ``.
988
- */
989
- LexerState["INTERPOLATION_LITERAL"] = "interpolation-literal";
990
- /**
991
- * Consuming an import statement `@import { X, Y, ... }
992
- */
993
- LexerState["IMPORT"] = "import";
994
- LexerState["IMPORT_PATH"] = "import-path";
995
- return LexerState;
996
- }({});
997
- //#endregion
998
- //#region ../packages/compiler/src/lexer/types/token-type.enum.ts
999
- /**
1000
- * Discriminant values that identify the type of each token emitted by the lexer.
1001
- */
1002
- var TokenType = /* @__PURE__ */ function(TokenType) {
1003
- /**
1004
- * A plain text node between tags or at the top level.
1005
- */
1006
- TokenType[TokenType["TEXT"] = 0] = "TEXT";
1007
- /**
1008
- * The name portion of an opening tag, e.g. `div` in `<div`.
1009
- */
1010
- TokenType[TokenType["TAG_OPEN_NAME"] = 1] = "TAG_OPEN_NAME";
1011
- /**
1012
- * A self-closing tag marker `/>`.
1013
- */
1014
- TokenType[TokenType["TAG_SELF_CLOSE"] = 2] = "TAG_SELF_CLOSE";
1015
- /**
1016
- * The closing `>` of an opening tag.
1017
- */
1018
- TokenType[TokenType["TAG_OPEN_END"] = 3] = "TAG_OPEN_END";
1019
- /**
1020
- * The name portion of a closing tag, e.g. `div` in `</div>`.
1021
- */
1022
- TokenType[TokenType["TAG_CLOSE_NAME"] = 4] = "TAG_CLOSE_NAME";
1023
- /**
1024
- * An HTML attribute
1025
- */
1026
- TokenType[TokenType["ATTRIBUTE"] = 5] = "ATTRIBUTE";
1027
- /**
1028
- * An HTML attribute literal value
1029
- */
1030
- TokenType[TokenType["ATTRIBUTE_VALUE"] = 6] = "ATTRIBUTE_VALUE";
1031
- /**
1032
- * A DOM event binding declared with `@eventName=handler`.
1033
- */
1034
- TokenType[TokenType["EVENT"] = 7] = "EVENT";
1035
- /**
1036
- * An event paremeter included in a event call '()'
1037
- */
1038
- TokenType[TokenType["EVENT_PAREMETER"] = 8] = "EVENT_PAREMETER";
1039
- /**
1040
- * A template-literal interpolation string enclosed in `` {`...`} ``.
1041
- */
1042
- TokenType[TokenType["INTERPOLATION_LITERAL"] = 9] = "INTERPOLATION_LITERAL";
1043
- /**
1044
- * A JavaScript expression interpolation enclosed in `{ }`.
1045
- */
1046
- TokenType[TokenType["INTERPOLATION_EXPRESSION"] = 10] = "INTERPOLATION_EXPRESSION";
1047
- /**
1048
- * Opening keyword of an `@if` directive.
1049
- */
1050
- TokenType[TokenType["IF"] = 11] = "IF";
1051
- /**
1052
- * Opening keyword of a `@for` directive.
1053
- */
1054
- TokenType[TokenType["FOR"] = 12] = "FOR";
1055
- /**
1056
- * Opening keyword of an `@else` branch.
1057
- */
1058
- TokenType[TokenType["ELSE"] = 13] = "ELSE";
1059
- /**
1060
- * Opening keyword of an `@else if` branch.
1061
- */
1062
- TokenType[TokenType["ELSE_IF"] = 14] = "ELSE_IF";
1063
- /**
1064
- * Opening keyword of a `@switch` directive.
1065
- */
1066
- TokenType[TokenType["SWITCH"] = 15] = "SWITCH";
1067
- /**
1068
- * Opening keyword of a `@case` branch.
1069
- */
1070
- TokenType[TokenType["CASE"] = 16] = "CASE";
1071
- /**
1072
- * Opening keyword of a `@default` branch.
1073
- */
1074
- TokenType[TokenType["DEFAULT"] = 17] = "DEFAULT";
1075
- /**
1076
- * The condition expression `(...)` associated with a flow-control directive.
1077
- */
1078
- TokenType[TokenType["CONDITION"] = 18] = "CONDITION";
1079
- /**
1080
- * The opening `{` of a flow-control block body.
1081
- */
1082
- TokenType[TokenType["BLOCK_OPEN"] = 19] = "BLOCK_OPEN";
1083
- /**
1084
- * The closing `}` of a flow-control block body.
1085
- */
1086
- TokenType[TokenType["BLOCK_CLOSE"] = 20] = "BLOCK_CLOSE";
1087
- TokenType[TokenType["IMPORT"] = 21] = "IMPORT";
1088
- TokenType[TokenType["IMPORT_PATH"] = 22] = "IMPORT_PATH";
1089
- /**
1090
- * Sentinel token emitted when the end of the input is reached.
1091
- */
1092
- TokenType[TokenType["EOF"] = 23] = "EOF";
1093
- return TokenType;
1094
- }({});
1095
- //#endregion
1096
- //#region ../packages/compiler/src/lexer/states/lex-attribute-value.state.ts
1097
- /**
1098
- * Consumes a quoted attribute value `"..."`, collecting characters until
1099
- * the closing `"` is found. Emits an ATTRIBUTE_VALUE token and transitions
1100
- * back to TAG_BODY.
1101
- *
1102
- * @param cursor - The lexer cursor positioned at the first character of the value (after the opening `"`).
1103
- * @param _context - Unused lexer context.
1104
- * @returns Transition result with the ATTRIBUTE_VALUE token and the TAG_BODY state.
1105
- */
1106
- function lexAttributeValue(cursor, _context) {
1107
- let read = true;
1108
- let value = "";
1109
- let retVal;
1110
- while (read) switch (cursor.peek()) {
1111
- case 34:
1112
- cursor.advance();
1113
- read = false;
1114
- retVal = {
1115
- state: LexerState.TAG_BODY,
1116
- tokens: [{
1117
- type: TokenType.ATTRIBUTE_VALUE,
1118
- parts: [value]
1119
- }],
1120
- popState: true
1121
- };
1122
- break;
1123
- default:
1124
- cursor.advance();
1125
- value = `${value}${cursor.currentChar.value}`;
1126
- }
1127
- return retVal;
1128
- }
1129
- //#endregion
1130
- //#region ../packages/compiler/src/lexer/states/lex-attribute.state.ts
1131
- /**
1132
- * Consumes an attribute name and optional value from the current position,
1133
- * transitioning back to TAG_BODY when a space, `/`, or `>` is encountered.
1134
- * If the attribute value is an interpolation, pushes the INTERPOLATION state.
1135
- *
1136
- * @param cursor - The lexer cursor positioned at the start of the attribute.
1137
- * @param _context - Unused lexer context.
1138
- * @returns Transition result with the ATTRIBUTE token and next state.
1139
- */
1140
- function lexAttribute(cursor, _context) {
1141
- let read = true;
1142
- let attribute = "";
1143
- let retVal;
1144
- while (read) switch (cursor.peek()) {
1145
- case 32:
1146
- cursor.advance();
1147
- read = false;
1148
- retVal = {
1149
- state: LexerState.TAG_BODY,
1150
- tokens: [{
1151
- type: TokenType.ATTRIBUTE,
1152
- parts: [attribute]
1153
- }]
1154
- };
1155
- break;
1156
- case 61:
1157
- cursor.advance();
1158
- if (cursor.peek() !== 34) {
1159
- const { row, column } = cursor.position;
1160
- throw new Error(`Attribute value must start with double quotes '"'.Row ${row} Col ${column}`);
1161
- }
1162
- cursor.advance();
1163
- read = false;
1164
- retVal = {
1165
- state: cursor.peek() === 123 ? LexerState.INTERPOLATION : LexerState.ATTRIBUTE_VALUE,
1166
- pushState: true,
1167
- tokens: [{
1168
- type: TokenType.ATTRIBUTE,
1169
- parts: [attribute]
1170
- }]
1171
- };
1172
- break;
1173
- default:
1174
- cursor.advance();
1175
- attribute = `${attribute}${cursor.currentChar.value}`;
1176
- }
1177
- return retVal;
1178
- }
1179
- //#endregion
1180
- //#region ../packages/compiler/src/lexer/utils/consume-flow-control-condition.utils.ts
1181
- /**
1182
- * Consumes a parenthesised flow-control condition expression from the cursor.
1183
- *
1184
- * Skips leading whitespace, then expects `(` followed by a balanced
1185
- * parenthesised expression. Handles nested parentheses and returns the
1186
- * raw expression string (excluding the outer `(` and `)`).
1187
- *
1188
- * @param cursor - The lexer cursor positioned before the opening `(`.
1189
- * @param _context - Unused lexer context (kept for signature consistency).
1190
- * @returns The raw expression string extracted from inside the parentheses.
1191
- * @throws When the next non-space character is not `(`.
1192
- */
1193
- function consumeFlowControlCondition(cursor, _context) {
1194
- cursor.skipSpaces();
1195
- if (cursor.peek() !== 40) throw new Error(`Expected '(' but got '${String.fromCharCode(cursor.peek())}' at row ${cursor.position.row}, col ${cursor.position.column}`);
1196
- cursor.advance();
1197
- let expression = "";
1198
- let depth = 1;
1199
- while (depth > 0) switch (cursor.peek()) {
1200
- case 40:
1201
- depth++;
1202
- expression = addCharacter$3(cursor, expression);
1203
- break;
1204
- case 41:
1205
- depth--;
1206
- if (!depth) {
1207
- cursor.advance();
1208
- break;
1209
- }
1210
- expression = addCharacter$3(cursor, expression);
1211
- break;
1212
- default: expression = addCharacter$3(cursor, expression);
1213
- }
1214
- return expression;
1215
- }
1216
- /**
1217
- * Advances the cursor by one character and appends it to the accumulator string.
1218
- *
1219
- * @param cursor - The lexer cursor to advance.
1220
- * @param expression - The current accumulated expression string.
1221
- * @returns The updated string with the newly consumed character appended.
1222
- */
1223
- function addCharacter$3(cursor, expression) {
1224
- cursor.advance();
1225
- return `${expression}${cursor.currentChar.value}`;
1226
- }
1227
- //#endregion
1228
- //#region ../packages/compiler/src/lexer/states/lex-case-flow-control-condition.state.ts
1229
- /**
1230
- * Consumes the condition expression `(...)` of a flow-control directive,
1231
- * handling nested parentheses correctly. Emits a CONDITION token with the
1232
- * raw expression string and transitions to FLOW_CONTROL_BLOCK.
1233
- *
1234
- * @param cursor - The lexer cursor positioned at the opening `(`.
1235
- * @param _context - Unused lexer context.
1236
- * @returns Transition result with the CONDITION token and the FLOW_CONTROL_BLOCK state.
1237
- */
1238
- function lexCaseFlowControlCondition(cursor, _context) {
1239
- const condition = consumeFlowControlCondition(cursor, _context);
1240
- cursor.skipSpaces();
1241
- return {
1242
- state: cursor.peekMatch("@case") ? LexerState.FLOW_CONTROL : LexerState.FLOW_CONTROL_BLOCK,
1243
- tokens: [{
1244
- type: TokenType.CONDITION,
1245
- parts: [condition]
1246
- }],
1247
- popState: true
1248
- };
1249
- }
1250
- //#endregion
1251
- //#region ../packages/compiler/src/lexer/states/lex-default-flow-control-condition.state.ts
1252
- /**
1253
- * Consumes the condition expression `(...)` of a flow-control directive,
1254
- * handling nested parentheses correctly. Emits a CONDITION token with the
1255
- * raw expression string and transitions to FLOW_CONTROL_BLOCK.
1256
- *
1257
- * @param cursor - The lexer cursor positioned at the opening `(`.
1258
- * @param _context - Unused lexer context.
1259
- * @returns Transition result with the CONDITION token and the FLOW_CONTROL_BLOCK state.
1260
- */
1261
- function lexDefaultFlowControlCondition(cursor, _context) {
1262
- return {
1263
- state: LexerState.FLOW_CONTROL_BLOCK,
1264
- tokens: [{
1265
- type: TokenType.CONDITION,
1266
- parts: [consumeFlowControlCondition(cursor, _context)]
1267
- }],
1268
- popState: true
1269
- };
1270
- }
1271
- //#endregion
1272
- //#region ../packages/compiler/src/lexer/states/lex-event-parameter.state.ts
1273
- /**
1274
- * Consumes an event parameter and reads until a ',' or ')'.
1275
- * Emits an EVENT_ATTRIBUTE token containing the raw paremeter string.
1276
- *
1277
- * @param cursor - The lexer cursor positioned on the `@` character.
1278
- * @param _context - Unused lexer context.
1279
- * @returns Transition result with the EVENT_PAREMETER token and the EVENT state.
1280
- */
1281
- function lexEventParameter(cursor, _context) {
1282
- let read = true;
1283
- let eventParameter = "";
1284
- let charDelimiter = "";
1285
- const retVal = {
1286
- state: LexerState.TAG_BODY,
1287
- tokens: []
1288
- };
1289
- while (read) switch (cursor.peek()) {
1290
- case 91:
1291
- case 93:
1292
- case 123:
1293
- case 125:
1294
- case 34:
1295
- case 39:
1296
- case 40:
1297
- eventParameter = addCharacter$2(cursor, eventParameter);
1298
- if (!charDelimiter) charDelimiter = cursor.currentChar.value;
1299
- else if (charDelimiter === cursor.currentChar.value || charDelimiter === "[" && cursor.currentChar.value === "]" || charDelimiter === "{" && cursor.currentChar.value === "}") charDelimiter = "";
1300
- break;
1301
- case 44:
1302
- if (!charDelimiter) {
1303
- retVal.tokens.push({
1304
- type: TokenType.EVENT_PAREMETER,
1305
- parts: [eventParameter]
1306
- });
1307
- cursor.advance();
1308
- cursor.skipSpaces();
1309
- eventParameter = "";
1310
- } else eventParameter = addCharacter$2(cursor, eventParameter);
1311
- break;
1312
- case 41:
1313
- cursor.advance();
1314
- if (!charDelimiter) {
1315
- retVal.tokens.push({
1316
- type: TokenType.EVENT_PAREMETER,
1317
- parts: [eventParameter]
1318
- });
1319
- read = false;
1320
- }
1321
- default: eventParameter = addCharacter$2(cursor, eventParameter);
1322
- }
1323
- return retVal;
1324
- }
1325
- function addCharacter$2(cursor, eventParameter) {
1326
- cursor.advance();
1327
- return `${eventParameter}${cursor.currentChar.value}`;
1328
- }
1329
- //#endregion
1330
- //#region ../packages/compiler/src/lexer/states/lex-event.state.ts
1331
- /**
1332
- * Consumes a DOM event binding starting with `@` and reads until a delimiter
1333
- * (space, `/`, or `>`) is found. Emits an EVENT token containing the raw binding string.
1334
- *
1335
- * @param cursor - The lexer cursor positioned on the `@` character.
1336
- * @param _context - Unused lexer context.
1337
- * @returns Transition result with the EVENT token and the TAG_BODY state.
1338
- */
1339
- function lexEvent(cursor, _context) {
1340
- let read = true;
1341
- let event = "";
1342
- let retVal;
1343
- cursor.advance();
1344
- while (read) switch (cursor.peek()) {
1345
- case 32:
1346
- case 47:
1347
- case 62:
1348
- retVal = { state: LexerState.TAG_BODY };
1349
- read = false;
1350
- break;
1351
- case 40:
1352
- let state = LexerState.EVENT_PARAMETER;
1353
- cursor.advance();
1354
- cursor.skipSpaces();
1355
- if (cursor.peek() === 41) {
1356
- state = LexerState.TAG_BODY;
1357
- cursor.advance(2);
1358
- }
1359
- retVal = {
1360
- state,
1361
- tokens: [{
1362
- type: TokenType.EVENT,
1363
- parts: [event]
1364
- }]
1365
- };
1366
- read = false;
1367
- break;
1368
- default:
1369
- cursor.advance();
1370
- event = `${event}${cursor.currentChar.value}`;
1371
- }
1372
- return retVal;
1373
- }
1374
- //#endregion
1375
- //#region ../packages/compiler/src/lexer/states/lex-flow-control.ts
1376
- /**
1377
- * Dispatches on a `@keyword` to determine which flow-control directive begins here.
1378
- * Recognises `@if`, `@for`, `@else`, `@switch`, `@case`, `@default`, and `@const`.
1379
- * Advances the cursor past the keyword and transitions to the appropriate next state.
1380
- *
1381
- * @param cursor - The lexer cursor positioned on the `@` character.
1382
- * @param _context - Unused lexer context.
1383
- * @returns Transition result with the matching flow-control token and next state.
1384
- */
1385
- function lexFlowControl(cursor, _context) {
1386
- let retVal;
1387
- cursor.advance();
1388
- if (cursor.peekMatch("for ")) {
1389
- cursor.advance(4);
1390
- retVal = {
1391
- state: LexerState.FLOW_CONTROL_CONDITION,
1392
- tokens: [{ type: TokenType.FOR }],
1393
- pushState: true
1394
- };
1395
- } else if (cursor.peekMatch("if ")) {
1396
- cursor.advance(2);
1397
- retVal = {
1398
- state: LexerState.FLOW_CONTROL_CONDITION,
1399
- tokens: [{ type: TokenType.IF }],
1400
- pushState: true
1401
- };
1402
- } else if (cursor.peekMatch("else if ")) {
1403
- cursor.advance(8);
1404
- retVal = {
1405
- state: LexerState.FLOW_CONTROL_CONDITION,
1406
- tokens: [{ type: TokenType.ELSE_IF }],
1407
- pushState: true
1408
- };
1409
- } else if (cursor.peekMatch("else ")) {
1410
- cursor.advance(5);
1411
- retVal = {
1412
- state: LexerState.FLOW_CONTROL_BLOCK,
1413
- tokens: [{ type: TokenType.ELSE }]
1414
- };
1415
- } else if (cursor.peekMatch("switch ")) {
1416
- cursor.advance(7);
1417
- retVal = {
1418
- state: LexerState.FLOW_CONTROL_CONDITION,
1419
- tokens: [{ type: TokenType.SWITCH }],
1420
- pushState: true
1421
- };
1422
- } else if (cursor.peekMatch("case ")) {
1423
- cursor.advance(5);
1424
- retVal = {
1425
- state: LexerState.CASE_FLOW_CONTROL_CONDITION,
1426
- tokens: [{ type: TokenType.CASE }],
1427
- pushState: true
1428
- };
1429
- } else if (cursor.peekMatch("default ")) {
1430
- cursor.advance(8);
1431
- retVal = {
1432
- state: LexerState.FLOW_CONTROL_BLOCK,
1433
- tokens: [{ type: TokenType.DEFAULT }]
1434
- };
1435
- } else if (cursor.peekMatch("import ")) {
1436
- cursor.advance(7);
1437
- retVal = { state: LexerState.IMPORT };
1438
- }
1439
- return retVal;
1440
- }
1441
- //#endregion
1442
- //#region ../packages/compiler/src/lexer/states/lex-flow-control-block.state.ts
1443
- /**
1444
- * Consumes the opening `{` of a flow-control block body,
1445
- * skipping any leading whitespace before it.
1446
- *
1447
- * Emits a BLOCK_OPEN token and transitions to TEXT, pushing FLOW_CONTROL_BLOCK
1448
- * onto the state stack so that `consumeText` later recognises the matching `}`
1449
- * as a BLOCK_CLOSE rather than an interpolation boundary.
1450
- *
1451
- * Used by: `@if`, `@for`, `@switch`, `@case`, `@else`, `@default`.
1452
- *
1453
- * @param cursor - The lexer cursor positioned before the opening `{`.
1454
- * @param _context - Unused lexer context.
1455
- * @returns Transition result with the BLOCK_OPEN token and the TEXT state.
1456
- */
1457
- function lexFlowControlBlock(cursor, _context) {
1458
- cursor.skipSpaces();
1459
- if (cursor.peek() !== 123) throw new Error(`Expected '{' but got '${String.fromCharCode(cursor.peek())}' at row ${cursor.position.row}, col ${cursor.position.column}`);
1460
- cursor.advance();
1461
- return {
1462
- state: LexerState.TEXT,
1463
- tokens: [{ type: TokenType.BLOCK_OPEN }],
1464
- pushState: true
1465
- };
1466
- }
1467
- //#endregion
1468
- //#region ../packages/compiler/src/lexer/states/lex-import-path.state.ts
1469
- function lexImportPath(cursor, _context) {
1470
- let read = true;
1471
- let path = "";
1472
- let retVal;
1473
- let singleQuote = false;
1474
- cursor.skipSpaces();
1475
- if (!cursor.peekMatch("from")) throw new Error(`Expected from keywork after list of imports at ${cursor.currentChar}`);
1476
- cursor.advance(4);
1477
- cursor.skipSpaces();
1478
- cursor.advance();
1479
- switch (cursor.currentChar.code) {
1480
- case 39:
1481
- singleQuote = true;
1482
- break;
1483
- case 34: break;
1484
- default: throw new Error(`Import statement must start with ' or ".\nFound character ${cursor.currentChar.value} at ${cursor.formattedPosition}`);
1485
- }
1486
- const delimiter = singleQuote ? 39 : 34;
1487
- while (read) switch (cursor.peek()) {
1488
- case delimiter:
1489
- cursor.advance();
1490
- read = false;
1491
- retVal = {
1492
- state: LexerState.TEXT,
1493
- tokens: [{
1494
- type: TokenType.IMPORT_PATH,
1495
- parts: [path]
1496
- }]
1497
- };
1498
- break;
1499
- default:
1500
- cursor.advance();
1501
- path = `${path}${cursor.currentChar.value}`;
1502
- }
1503
- return retVal;
1504
- }
1505
- //#endregion
1506
- //#region ../packages/compiler/src/lexer/states/lex-import.state.ts
1507
- function lexImport(cursor, _context) {
1508
- let read = true;
1509
- let importValue = "";
1510
- let retVal = {
1511
- state: LexerState.IMPORT_PATH,
1512
- tokens: []
1513
- };
1514
- cursor.skipSpaces();
1515
- cursor.advance();
1516
- if (cursor.currentChar.code !== 123) throw new Error(`Expected { after @import at ${cursor.formattedPosition}`);
1517
- while (read) switch (cursor.peek()) {
1518
- case 32:
1519
- cursor.skipSpaces();
1520
- if (importValue && cursor.peek() !== 44 && cursor.peek() !== 125) importValue = `${importValue} `;
1521
- break;
1522
- case 44:
1523
- addImport(retVal, cursor, importValue);
1524
- importValue = "";
1525
- break;
1526
- case 125:
1527
- addImport(retVal, cursor, importValue);
1528
- read = false;
1529
- break;
1530
- default:
1531
- cursor.advance();
1532
- importValue = `${importValue}${cursor.currentChar.value}`;
1533
- }
1534
- return retVal;
1535
- }
1536
- function addImport(retVal, cursor, value) {
1537
- cursor.advance();
1538
- retVal.tokens.push({
1539
- type: TokenType.IMPORT,
1540
- parts: [value]
1541
- });
1542
- }
1543
- //#endregion
1544
- //#region ../packages/compiler/src/lexer/states/lex-interpolation-expression.state.ts
1545
- /**
1546
- * Consumes a JavaScript expression interpolation `{ expression }`, tracking nested
1547
- * brace depth. Emits an INTERPOLATION_EXPRESSION token and pops the state stack to
1548
- * return to the previous state (ATTRIBUTE or TEXT).
1549
- *
1550
- * @param cursor - The lexer cursor positioned at the first character of the expression.
1551
- * @param context - The lexer context used to retrieve the previous state for restoration.
1552
- * @returns Transition result with the INTERPOLATION_EXPRESSION token and restored state.
1553
- */
1554
- function lexInterpolationExpression(cursor, context) {
1555
- let read = true;
1556
- let interpolation = "";
1557
- let deep = 1;
1558
- let retVal;
1559
- while (read) switch (cursor.peek()) {
1560
- case 123:
1561
- deep++;
1562
- interpolation = addCharacter$1(cursor, interpolation);
1563
- break;
1564
- case 125:
1565
- deep--;
1566
- if (deep === 0) {
1567
- cursor.advance();
1568
- interpolation = interpolation.trimEnd();
1569
- const previousState = context.history.pop();
1570
- let state;
1571
- switch (previousState) {
1572
- case LexerState.ATTRIBUTE:
1573
- if (cursor.peek() !== 34) throw new Error(`Interpolation must end with double quotes '"' Found ${String.fromCharCode(cursor.peek())} at ${cursor.formattedPosition}`);
1574
- cursor.advance();
1575
- state = LexerState.TAG_BODY;
1576
- break;
1577
- case LexerState.TEXT: state = LexerState.TEXT;
1578
- }
1579
- retVal = {
1580
- state,
1581
- tokens: [{
1582
- type: TokenType.INTERPOLATION_EXPRESSION,
1583
- parts: [interpolation]
1584
- }],
1585
- popState: true
1586
- };
1587
- read = false;
1588
- } else interpolation = addCharacter$1(cursor, interpolation);
1589
- break;
1590
- default: interpolation = addCharacter$1(cursor, interpolation);
1591
- }
1592
- return retVal;
1593
- }
1594
- /**
1595
- * Advances the cursor by one character and appends it to the accumulator string.
1596
- *
1597
- * @param cursor - The lexer cursor to advance.
1598
- * @param interpolation - The current accumulated expression string.
1599
- * @returns The updated string with the newly consumed character appended.
1600
- */
1601
- function addCharacter$1(cursor, interpolation) {
1602
- cursor.advance(1);
1603
- return `${interpolation}${cursor.currentChar.value}`;
1604
- }
1605
- //#endregion
1606
- //#region ../packages/compiler/src/lexer/states/lex-interpolation-literal.state.ts
1607
- /**
1608
- * Consumes a template-literal interpolation `` {`...`} ``, collecting characters
1609
- * until the closing backtick followed by `}`. Emits an INTERPOLATION_LITERAL token
1610
- * and pops the state stack to return to the previous state.
1611
- *
1612
- * @param cursor - The lexer cursor positioned at the opening backtick.
1613
- * @param context - The lexer context used to retrieve the previous state for restoration.
1614
- * @returns Transition result with the INTERPOLATION_LITERAL token and restored state.
1615
- */
1616
- function lexInterpolationliteral(cursor, context) {
1617
- let read = true;
1618
- let interpolation = "`";
1619
- let retVal;
1620
- cursor.advance();
1621
- while (read) switch (cursor.peek()) {
1622
- case 96:
1623
- interpolation = addCharacter(cursor, interpolation);
1624
- if (cursor.peek() === 125) {
1625
- cursor.advance();
1626
- const previousState = context.history.pop();
1627
- let state;
1628
- switch (previousState) {
1629
- case LexerState.ATTRIBUTE:
1630
- if (cursor.peek() !== 34) throw new Error(`Attribute interpolation expression must end with double quotes at ${cursor.formattedPosition}`);
1631
- cursor.advance();
1632
- state = LexerState.TAG_BODY;
1633
- break;
1634
- case LexerState.TEXT: state = LexerState.TEXT;
1635
- }
1636
- retVal = {
1637
- state,
1638
- tokens: [{
1639
- type: TokenType.INTERPOLATION_LITERAL,
1640
- parts: [interpolation]
1641
- }],
1642
- popState: true
1643
- };
1644
- read = false;
1645
- } else interpolation = `${interpolation}${cursor.currentChar.value}`;
1646
- break;
1647
- default: interpolation = addCharacter(cursor, interpolation);
1648
- }
1649
- return retVal;
1650
- }
1651
- /**
1652
- * Advances the cursor by one character and appends it to the accumulator string.
1653
- *
1654
- * @param cursor - The lexer cursor to advance.
1655
- * @param interpolation - The current accumulated expression string.
1656
- * @returns The updated string with the newly consumed character appended.
1657
- */
1658
- function addCharacter(cursor, interpolation) {
1659
- cursor.advance();
1660
- return `${interpolation}${cursor.currentChar.value}`;
1661
- }
1662
- //#endregion
1663
- //#region ../packages/compiler/src/lexer/states/lex-interpolation.state.ts
1664
- /**
1665
- * Dispatches between an expression and a literal interpolation after the opening `{`.
1666
- * Advances past `{` and any leading spaces, then inspects the next character:
1667
- * a backtick routes to INTERPOLATION_LITERAL, a JS identifier start routes to INTERPOLATION_EXPRESSION.
1668
- *
1669
- * @param cursor - The lexer cursor positioned on the `{` character.
1670
- * @param _context - Unused lexer context.
1671
- * @returns Transition result with the appropriate interpolation sub-state.
1672
- */
1673
- function lexInterpolation(cursor, _context) {
1674
- cursor.advance();
1675
- cursor.skipSpaces();
1676
- return cursor.peek() === 96 ? { state: LexerState.INTERPOLATION_LITERAL } : { state: LexerState.INTERPOLATION_EXPRESSION };
1677
- }
1678
- //#endregion
1679
- //#region ../packages/compiler/src/lexer/states/lex-tag-body.state.ts
1680
- /**
1681
- * Scans the body of an open tag to determine what comes next:
1682
- * an event binding (`@`), an attribute, the end of the tag (`>` or `/`), or whitespace.
1683
- * Transitions to the appropriate state without emitting any tokens.
1684
- *
1685
- * @param cursor - The lexer cursor positioned inside a tag body.
1686
- * @param _context - Unused lexer context.
1687
- * @returns Transition result with the next state and no tokens.
1688
- */
1689
- function lexTagBody(cursor, _context) {
1690
- let read = true;
1691
- let retVal;
1692
- while (read) switch (cursor.peek()) {
1693
- case 64:
1694
- retVal = { state: LexerState.EVENT };
1695
- read = false;
1696
- break;
1697
- case 32:
1698
- cursor.skipSpaces();
1699
- break;
1700
- case 62:
1701
- case 47:
1702
- retVal = { state: LexerState.TAG_OPEN_END };
1703
- read = false;
1704
- break;
1705
- default:
1706
- retVal = { state: LexerState.ATTRIBUTE };
1707
- read = false;
1708
- }
1709
- return retVal;
1710
- }
1711
- //#endregion
1712
- //#region ../packages/compiler/src/lexer/states/lex-tag-close.state.ts
1713
- /**
1714
- * Consumes a closing tag `</tagName>`, skipping the `</` prefix and any surrounding
1715
- * whitespace. Emits a TAG_CLOSE_NAME token with the tag name and transitions to TEXT.
1716
- *
1717
- * @param cursor - The lexer cursor positioned at the `<` of a closing tag.
1718
- * @param _context - Unused lexer context.
1719
- * @returns Transition result with the TAG_CLOSE_NAME token and the TEXT state.
1720
- */
1721
- function lexTagClose(cursor, _context) {
1722
- let read = true;
1723
- let tagName = "";
1724
- let retVal;
1725
- cursor.advance(2);
1726
- cursor.skipSpaces();
1727
- while (read) switch (cursor.peek()) {
1728
- case 62:
1729
- cursor.advance();
1730
- retVal = {
1731
- state: LexerState.TEXT,
1732
- tokens: [{
1733
- type: TokenType.TAG_CLOSE_NAME,
1734
- parts: [tagName]
1735
- }]
1736
- };
1737
- read = false;
1738
- break;
1739
- case 32: throw new Error("Tag Close Name cannot contains spaces");
1740
- default:
1741
- cursor.advance();
1742
- tagName = `${tagName}${cursor.currentChar.value}`;
1743
- }
1744
- return retVal;
1745
- }
1746
- //#endregion
1747
- //#region ../packages/compiler/src/lexer/states/lex-tag-open-end.state.ts
1748
- /**
1749
- * Consumes the closing characters of an open tag: `>` emits TAG_OPEN_END and
1750
- * transitions to TEXT, while `/>` emits TAG_SELF_CLOSE and also transitions to TEXT.
1751
- *
1752
- * @param cursor - The lexer cursor positioned at `>` or `/`.
1753
- * @param _context - Unused lexer context.
1754
- * @returns Transition result with TAG_OPEN_END or TAG_SELF_CLOSE and the TEXT state.
1755
- */
1756
- function lexTagOpenEnd(cursor, _context) {
1757
- let retVal;
1758
- if (cursor.peek() === 62) {
1759
- cursor.advance();
1760
- retVal = {
1761
- state: LexerState.TEXT,
1762
- tokens: [{
1763
- type: TokenType.TAG_OPEN_END,
1764
- parts: []
1765
- }]
1766
- };
1767
- } else {
1768
- cursor.advance();
1769
- const nextChar = cursor.peek();
1770
- if (nextChar === 62) {
1771
- cursor.advance();
1772
- retVal = {
1773
- state: LexerState.TEXT,
1774
- tokens: [{
1775
- type: TokenType.TAG_SELF_CLOSE,
1776
- parts: []
1777
- }]
1778
- };
1779
- } else throw new Error(`Unexpected character ${nextChar} for closing tag.\nExpected />\nRead of /${String.fromCharCode(nextChar)} at ${cursor.formattedPosition}`);
1780
- }
1781
- return retVal;
1782
- }
1783
- //#endregion
1784
- //#region ../packages/compiler/src/lexer/states/lex-tag-open-name.state.ts
1785
- /**
1786
- * Consumes an opening tag name after `<`, reading until a space, `/`, or `>` is found.
1787
- * Emits a TAG_OPEN_NAME token with the tag name and transitions to TAG_BODY.
1788
- *
1789
- * @param cursor - The lexer cursor positioned at the `<` character.
1790
- * @param _context - Unused lexer context.
1791
- * @returns Transition result with the TAG_OPEN_NAME token and the TAG_BODY state.
1792
- */
1793
- function lexTagOpenName(cursor, _context) {
1794
- let read = true;
1795
- let tagName = "";
1796
- let retVal;
1797
- cursor.advance();
1798
- cursor.skipSpaces();
1799
- while (read) switch (cursor.peek()) {
1800
- case 32:
1801
- case 47:
1802
- case 62:
1803
- retVal = {
1804
- state: LexerState.TAG_BODY,
1805
- tokens: [{
1806
- type: TokenType.TAG_OPEN_NAME,
1807
- parts: [tagName]
1808
- }]
1809
- };
1810
- read = false;
1811
- break;
1812
- default:
1813
- cursor.advance();
1814
- tagName = `${tagName}${cursor.currentChar.value}`;
1815
- }
1816
- return retVal;
1817
- }
1818
- //#endregion
1819
- //#region ../packages/compiler/src/lexer/utils/chars.utils.ts
1820
- /**
1821
- * Checks whether a string contains at least one non-whitespace character.
1822
- *
1823
- * Whitespace characters include space, `\n`, `\r`, `\t`, `\f`, and `\v`.
1824
- *
1825
- * @param str - The string to check.
1826
- * @returns `true` if the string is not blank, `false` if it consists entirely of whitespace.
1827
- */
1828
- function isNotBlank(str) {
1829
- return /\S/.test(str);
1830
- }
1831
- //#endregion
1832
- //#region ../packages/compiler/src/lexer/states/lex-text.state.ts
1833
- /**
1834
- * Consumes plain text content, accumulating characters until a structural boundary
1835
- * is reached: `<` (tag open/close), `{` (interpolation), `@` (flow-control or event),
1836
- * or `}` (block close). Emits a TEXT token if non-blank text was accumulated.
1837
- *
1838
- * @param cursor - The lexer cursor positioned at the start of text content.
1839
- * @param context - The lexer context used to detect flow-control block boundaries.
1840
- * @returns Transition result with an optional TEXT token and the next state.
1841
- */
1842
- function lexText(cursor, context) {
1843
- let read = true;
1844
- let text = "";
1845
- let retVal;
1846
- while (read) switch (cursor.peek()) {
1847
- case 60:
1848
- retVal = { state: cursor.peek(1, { offset: 1 }) === 47 ? LexerState.TAG_CLOSE : LexerState.TAG_OPEN_NAME };
1849
- read = false;
1850
- break;
1851
- case 123:
1852
- retVal = {
1853
- state: LexerState.INTERPOLATION,
1854
- pushState: true
1855
- };
1856
- read = false;
1857
- break;
1858
- case 64:
1859
- retVal = { state: LexerState.FLOW_CONTROL };
1860
- read = false;
1861
- break;
1862
- case 125:
1863
- if (context.history[context.history.length - 1] === LexerState.FLOW_CONTROL_BLOCK) {
1864
- cursor.advance();
1865
- retVal = {
1866
- state: LexerState.TEXT,
1867
- tokens: [{ type: TokenType.BLOCK_CLOSE }],
1868
- popState: true
1869
- };
1870
- read = false;
1871
- } else {
1872
- cursor.advance();
1873
- text = `${text}${cursor.currentChar.value}`;
1874
- }
1875
- break;
1876
- case 10:
1877
- case 13:
1878
- cursor.advance();
1879
- break;
1880
- default:
1881
- cursor.advance();
1882
- text = `${text}${cursor.currentChar.value}`;
1883
- }
1884
- retVal.tokens ??= isNotBlank(text) ? [{
1885
- type: TokenType.TEXT,
1886
- parts: [text]
1887
- }] : void 0;
1888
- return retVal;
1889
- }
1890
- //#endregion
1891
- //#region ../packages/compiler/src/lexer/types/lexer-cursor.model.ts
1892
- /**
1893
- * Cursor abstraction used by the Lexer to navigate the input source.
1894
- *
1895
- * The LexerCursor is responsible for:
1896
- * - Sequential character consumption
1897
- * - Lookahead (peek) operations without state mutation
1898
- * - Tracking logical position (row, column)
1899
- * - Handling end-of-file conditions
1900
- *
1901
- * This class deliberately contains **no lexer logic**:
1902
- * it does not know about tokens, states, or grammar rules.
1903
- * Its sole responsibility is controlled navigation of the input stream.
1904
- */
1905
- var LexerCursor = class {
1906
- input;
1907
- /**
1908
- * Representation of the current character.
1909
- *
1910
- * - `index`: absolute index within the input string
1911
- * - `code`: Unicode code point of the character
1912
- * - `value`: actual character value
1913
- *
1914
- * An index of `-1` indicates that the cursor has not yet consumed
1915
- * any character or has reached EOF.
1916
- */
1917
- _currentChar = {
1918
- code: 0,
1919
- index: -1,
1920
- value: ""
1921
- };
1922
- /**
1923
- * Returns a read-only snapshot of the current character.
1924
- */
1925
- get currentChar() {
1926
- return this._currentChar;
1927
- }
1928
- /**
1929
- * Cache used by peek operations to avoid re-reading
1930
- * the same character positions multiple times.
1931
- *
1932
- * Key: absolute character index
1933
- * Value: Unicode code point
1934
- */
1935
- _peekCache = /* @__PURE__ */ new Map();
1936
- /**
1937
- * Logical position of the cursor in the input.
1938
- *
1939
- * - `row`: zero-based line number
1940
- * - `column`: zero-based column number
1941
- */
1942
- _position = {
1943
- row: 0,
1944
- column: 0
1945
- };
1946
- /**
1947
- * Returns a read-only snapshot of the current cursor position.
1948
- */
1949
- get position() {
1950
- return this._position;
1951
- }
1952
- get formattedPosition() {
1953
- return `[Ln ${this._position.row}, Col ${this._position.column}]`;
1954
- }
1955
- /**
1956
- * Creates a new cursor for the given input source.
1957
- *
1958
- * @param input - Full source string to be tokenised.
1959
- */
1960
- constructor(input) {
1961
- this.input = input;
1962
- }
1963
- /**
1964
- * Advances the cursor by the specified number of characters.
1965
- *
1966
- * This method:
1967
- * - Updates the current character
1968
- * - Updates row/column position
1969
- * - Detects line breaks (LF / CR)
1970
- * - Throws an EOF error when the end of the input is reached
1971
- *
1972
- * @param chars - Number of characters to consume. Must be >= 1.
1973
- * @throws When `chars` is less than 1 or when advancing past the end of the input.
1974
- */
1975
- advance(chars = 1) {
1976
- if (chars < 1) throw new Error(`${chars} is not a valid value. Please enter a number equal or greater than 1`);
1977
- const newIndex = this._currentChar.index + chars;
1978
- if (newIndex >= this.input.length) {
1979
- this._currentChar.code = 0;
1980
- this._currentChar.index = -1;
1981
- this._currentChar.value = "";
1982
- this.throwEOFError();
1983
- } else {
1984
- if ([10, 13].includes(this._currentChar.code)) {
1985
- this._position.row++;
1986
- this._position.column = 0;
1987
- } else this._position.column++;
1988
- this._currentChar.index = newIndex;
1989
- this._currentChar.value = this.input[newIndex];
1990
- this._currentChar.code = this.input.charCodeAt(newIndex);
1991
- }
1992
- }
1993
- peekMatch(pattern, length) {
1994
- if (typeof pattern === "string") {
1995
- const peekedChars = this.peek(pattern.length);
1996
- for (let i = 0; i < pattern.length; i++) if (peekedChars[i] !== pattern.charCodeAt(i)) return false;
1997
- return true;
1998
- }
1999
- const start = this._currentChar.index + 1;
2000
- const slice = this.input.slice(start, start + length);
2001
- return pattern.test(slice);
2002
- }
2003
- peek(charsOrOptions, options) {
2004
- const cache = this._peekCache;
2005
- const chars = typeof charsOrOptions === "number" ? charsOrOptions : 1;
2006
- const offset = (typeof charsOrOptions === "object" ? charsOrOptions : options)?.offset ?? 0;
2007
- return chars === 1 ? this.peekOneChar(this._currentChar.index + offset + 1, cache) : this.peekMany(chars + offset, cache);
2008
- }
2009
- /**
2010
- * Skips all consecutive space characters from the current position.
2011
- */
2012
- skipSpaces() {
2013
- while (this.peek() === 32 || this.peek() === 10 || this.peek() === 13) this.advance();
2014
- }
2015
- /**
2016
- * Peeks multiple characters ahead.
2017
- */
2018
- peekMany(chars, cache) {
2019
- const peekedChars = new Array();
2020
- const nextCharIndex = this._currentChar.index + 1;
2021
- for (let i = nextCharIndex; i < nextCharIndex + chars; i++) peekedChars.push(this.peekOneChar(i, cache));
2022
- return peekedChars;
2023
- }
2024
- /**
2025
- * Peeks a single character at the given absolute index.
2026
- */
2027
- peekOneChar(index, cache) {
2028
- if (cache.has(index)) return cache.get(index);
2029
- if (index >= this.input.length) this.throwEOFError();
2030
- const charCode = this.input.charCodeAt(index);
2031
- cache.set(index, charCode);
2032
- return charCode;
2033
- }
2034
- /**
2035
- * Throws a standardized EOF error used by the lexer engine
2036
- * to terminate tokenization.
2037
- */
2038
- throwEOFError() {
2039
- throw new Error("", { cause: 0 });
2040
- }
2041
- };
2042
- //#endregion
2043
- //#region ../packages/compiler/src/lexer/lexer.ts
2044
- /**
2045
- * Utility class that emulates a cursor navigating through a template string.
2046
- *
2047
- * The cursor keeps track of the current character, its absolute position
2048
- * within the text, and its logical position expressed as row and column.
2049
- * This is useful when parsing or analyzing template content character by character.
2050
- */
2051
- var Lexer = class {
2052
- input;
2053
- /**
2054
- * Cursor for navigating the input character stream.
2055
- */
2056
- _cursor;
2057
- /**
2058
- * Current lexer state.
2059
- */
2060
- _state = LexerState.TEXT;
2061
- /**
2062
- * State stack used to support nested states (e.g. interpolations).
2063
- */
2064
- _stack = new Stack();
2065
- /**
2066
- * Accumulated list of tokens emitted during tokenization.
2067
- */
2068
- _tokens = new Array();
2069
- /**
2070
- * Maps each lexer state to its corresponding transition function.
2071
- */
2072
- _states = {
2073
- [LexerState.TEXT]: lexText,
2074
- [LexerState.TAG_OPEN_NAME]: lexTagOpenName,
2075
- [LexerState.TAG_BODY]: lexTagBody,
2076
- [LexerState.TAG_OPEN_END]: lexTagOpenEnd,
2077
- [LexerState.TAG_CLOSE]: lexTagClose,
2078
- [LexerState.ATTRIBUTE]: lexAttribute,
2079
- [LexerState.ATTRIBUTE_VALUE]: lexAttributeValue,
2080
- [LexerState.EVENT]: lexEvent,
2081
- [LexerState.EVENT_PARAMETER]: lexEventParameter,
2082
- [LexerState.FLOW_CONTROL]: lexFlowControl,
2083
- [LexerState.FLOW_CONTROL_CONDITION]: lexDefaultFlowControlCondition,
2084
- [LexerState.CASE_FLOW_CONTROL_CONDITION]: lexCaseFlowControlCondition,
2085
- [LexerState.FLOW_CONTROL_BLOCK]: lexFlowControlBlock,
2086
- [LexerState.INTERPOLATION]: lexInterpolation,
2087
- [LexerState.INTERPOLATION_EXPRESSION]: lexInterpolationExpression,
2088
- [LexerState.INTERPOLATION_LITERAL]: lexInterpolationliteral,
2089
- [LexerState.IMPORT]: lexImport,
2090
- [LexerState.IMPORT_PATH]: lexImportPath
2091
- };
2092
- /**
2093
- * Creates a new Lexer instance for the given template content.
2094
- *
2095
- * @param input - The full template text to tokenise.
2096
- */
2097
- constructor(input) {
2098
- this.input = input;
2099
- this._cursor = new LexerCursor(this.input);
2100
- }
2101
- /**
2102
- * Runs the lexer over the input string and returns the full token array.
2103
- * Drives the state machine until EOF is reached.
2104
- *
2105
- * @returns Array of all tokens produced from the input.
2106
- */
2107
- tokenize() {
2108
- let eof = false;
2109
- while (!eof) try {
2110
- const transitionFunction = this._states[this._state];
2111
- const { state, tokens, popState, pushState } = transitionFunction(this._cursor, {
2112
- history: this._stack.values,
2113
- tokens: [...this._tokens]
2114
- });
2115
- if (tokens?.length) this._tokens.push(...tokens);
2116
- if (pushState) this._stack.push(this._state);
2117
- if (popState) this._stack.pop();
2118
- this._state = state;
2119
- } catch (err) {
2120
- if (err.cause === 0) eof = true;
2121
- else {
2122
- console.log(`Something went wrong while computing state ${this._state} at ${this._cursor.formattedPosition}`);
2123
- throw err;
2124
- }
2125
- }
2126
- return this._tokens;
2127
- }
2128
- };
2129
- //#endregion
2130
- //#region ../packages/compiler/src/parser/models/parser-cursor.model.ts
2131
- /**
2132
- * Cursor abstraction used by the Parser to navigate
2133
- * through a sequence of tokens produced by the Lexer.
2134
- *
2135
- * Responsibilities:
2136
- * - Sequential token consumption
2137
- * - Lookahead (peek) operations without mutating state
2138
- * - Handling end-of-file conditions
2139
- *
2140
- * This class does not perform parsing itself: it only
2141
- * manages position and access to the token stream.
2142
- */
2143
- var ParserCursor = class {
2144
- _tokens;
2145
- /**
2146
- * Representation of the current token.
2147
- *
2148
- * - `index`: absolute index within the token array
2149
- * - `value`: current token object (or EOF token)
2150
- *
2151
- * An index of `-1` indicates that the cursor has not
2152
- * yet consumed any token or has reached EOF.
2153
- */
2154
- _currentToken = {
2155
- value: { type: TokenType.EOF },
2156
- index: -1
2157
- };
2158
- /**
2159
- * Returns a read-only snapshot of the current token.
2160
- */
2161
- getCurrentToken() {
2162
- return this._currentToken;
2163
- }
2164
- /**
2165
- * Creates a new ParserCursor for the given token array.
2166
- *
2167
- * @param _tokens - The array of tokens to navigate.
2168
- */
2169
- constructor(_tokens) {
2170
- this._tokens = _tokens;
2171
- }
2172
- /**
2173
- * Advances the cursor by the specified number of tokens.
2174
- *
2175
- * Updates the current token and its index.
2176
- *
2177
- * @param chars - Number of tokens to advance. Must be >= 1.
2178
- * @throws When `chars` is less than 1.
2179
- */
2180
- advance(chars = 1) {
2181
- if (chars < 1) throw new Error(`${chars} is not a valid value. Please enter a number equal or greater than 1`);
2182
- const newIndex = this._currentToken.index + chars;
2183
- if (newIndex >= this._tokens.length) {
2184
- this._currentToken.value = { type: TokenType.EOF };
2185
- this._currentToken.index = -1;
2186
- } else {
2187
- this._currentToken.index = newIndex;
2188
- this._currentToken.value = this._tokens[newIndex];
2189
- }
2190
- }
2191
- peek(charsOrOptions, options) {
2192
- const tokens = typeof charsOrOptions === "number" ? charsOrOptions : 1;
2193
- const offset = (typeof charsOrOptions === "object" ? charsOrOptions : options)?.offset ?? 0;
2194
- return tokens === 1 ? this.peekOneToken(this._currentToken.index + offset + 1) : this.peekMany(tokens + offset);
2195
- }
2196
- /**
2197
- * Peeks multiple tokens ahead.
2198
- */
2199
- peekMany(chars) {
2200
- const peekedTokens = new Array();
2201
- const nextTokenIndex = this._currentToken.index + 1;
2202
- for (let i = nextTokenIndex; i < nextTokenIndex + chars; i++) peekedTokens.push(this.peekOneToken(i));
2203
- return peekedTokens;
2204
- }
2205
- /**
2206
- * Peeks a single token at the given absolute index.
2207
- */
2208
- peekOneToken(index) {
2209
- return index < this._tokens.length ? this._tokens[index] : { type: TokenType.EOF };
2210
- }
2211
- };
2212
- //#endregion
2213
- //#region ../packages/compiler/src/parser/utils/expression-validator.ts
2214
- /**
2215
- * Validates that a string contains a single expression belonging to the
2216
- * permitted subset of JavaScript supported inside Xaendar template expressions.
2217
- *
2218
- * ## Permitted constructs
2219
- *
2220
- * - **Literals** — strings, numbers, bigints, booleans, `null`, `undefined`
2221
- * - **Identifiers** — resolved at runtime against the active scope chain
2222
- * - **Member access** — `user.name`, `user.address.city`, `items[0]`
2223
- * - **Call expressions** — `user.getFullName()`, `user.hasRole('admin')`
2224
- * - **Binary expressions** — arithmetic (`+`, `-`, `*`, `/`, `%`, `**`),
2225
- * comparison (`===`, `!==`, `<`, `>`, `<=`, `>=`),
2226
- * logical (`&&`, `||`, `??`),
2227
- * bitwise (`&`, `|`, `^`, `<<`, `>>`, `>>>`)
2228
- * - **Unary expressions** — `!`, `~`, `+`, `-`, `typeof`, `void`
2229
- * - **Conditional (ternary)** — `isAdmin ? 'yes' : 'no'`
2230
- * - **Parenthesised expressions** — `(user.age > 18)`
2231
- * - **Template literals** — `` `Hello ${user.name}` ``
2232
- * - **Array literals** — `[1, 2, 3]`, `[...items]`
2233
- * - **Object literals** — `{ key: value }`, `{ ...defaults, name }`
2234
- * - **Spread** — `foo(...args)`, `[...items]`, `{ ...obj }`
2235
- * - **`typeof` / `instanceof`** — `typeof user.role`, `user instanceof AdminUser`
2236
- *
2237
- * ## Prohibited constructs
2238
- *
2239
- * - Assignments (`=`, `+=`, `&&=`, etc.) — use `@const` for local bindings
2240
- * - `await` and `yield`
2241
- * - `new` expressions
2242
- * - Function and arrow function expressions
2243
- * - Tagged template expressions
2244
- *
2245
- * ## Scope resolution
2246
- *
2247
- * This function performs **syntactic** validation only. Identifier resolution
2248
- * (scope chain walk → `ctx.` prefix injection) is the responsibility of the
2249
- * caller and must be performed on the returned `node` after this function
2250
- * reports no diagnostics.
2251
- *
2252
- * @param source - The raw expression string extracted from the template.
2253
- * @returns A {@link ExpressionValidationResult} containing the parsed AST node
2254
- * (when valid) and any diagnostics produced during validation.
2255
- *
2256
- * @example
2257
- * const result = validateExpression('user.hasRole("admin") && isVerified');
2258
- * if (result.diagnostics.length === 0) {
2259
- * // result.node is safe to use
2260
- * }
2261
- *
2262
- * @example
2263
- * const result = validateExpression('await user.load()');
2264
- * // result.diagnostics[0].message →
2265
- * // "'await' is not allowed inside template expressions."
2266
- */
2267
- function validateExpression(source) {
2268
- const expression = createSourceFile("expression.ts", `const x = ${source}`, ScriptTarget.ESNext, true).statements[0].declarationList.declarations[0].initializer;
2269
- const diagnostics = new Array();
2270
- visitNode(expression, 10, diagnostics);
2271
- if (diagnostics.length) throw new Error(diagnostics.reduce((acc, d) => `${acc}${d.message}\n`, ""));
2272
- return { node: expression };
2273
- }
2274
- /**
2275
- * Recursively visits an AST node and appends a diagnostic for every node
2276
- * kind that is not part of the permitted expression subset.
2277
- *
2278
- * Recursion stops at the first disallowed node to avoid producing a cascade
2279
- * of redundant diagnostics for its children.
2280
- *
2281
- * @param node - The AST node to inspect.
2282
- * @param offset - Number of characters to subtract from raw node positions
2283
- * to obtain offsets relative to the original expression string.
2284
- * @param diagnostics - Accumulator for diagnostics found during the walk.
2285
- */
2286
- function visitNode(node, offset, diagnostics) {
2287
- if (!isAllowedNode(node)) throw new Error(buildDisallowedMessage(node));
2288
- forEachChild(node, (child) => visitNode(child, offset, diagnostics));
2289
- if (diagnostics.length) throw new Error(diagnostics[0].message);
2290
- }
2291
- /**
2292
- * Returns `true` if the given AST node kind is permitted inside a
2293
- * Xaendar template expression.
2294
- *
2295
- * Assignment operators nested inside a `BinaryExpression` are handled
2296
- * separately in {@link buildDisallowedMessage} since TypeScript does not
2297
- * distinguish them at the node-kind level.
2298
- */
2299
- function isAllowedNode(node) {
2300
- switch (node.kind) {
2301
- case SyntaxKind.StringLiteral:
2302
- case SyntaxKind.NumericLiteral:
2303
- case SyntaxKind.BigIntLiteral:
2304
- case SyntaxKind.TrueKeyword:
2305
- case SyntaxKind.FalseKeyword:
2306
- case SyntaxKind.NullKeyword:
2307
- case SyntaxKind.UndefinedKeyword:
2308
- case SyntaxKind.Identifier:
2309
- case SyntaxKind.PropertyAccessExpression:
2310
- case SyntaxKind.ElementAccessExpression:
2311
- case SyntaxKind.CallExpression:
2312
- case SyntaxKind.BinaryExpression:
2313
- case SyntaxKind.EqualsEqualsToken:
2314
- case SyntaxKind.EqualsEqualsEqualsToken:
2315
- case SyntaxKind.ExclamationEqualsToken:
2316
- case SyntaxKind.ExclamationEqualsEqualsToken:
2317
- case SyntaxKind.LessThanToken:
2318
- case SyntaxKind.LessThanEqualsToken:
2319
- case SyntaxKind.GreaterThanToken:
2320
- case SyntaxKind.GreaterThanEqualsToken:
2321
- case SyntaxKind.PlusToken:
2322
- case SyntaxKind.MinusToken:
2323
- case SyntaxKind.AsteriskToken:
2324
- case SyntaxKind.SlashToken:
2325
- case SyntaxKind.PercentToken:
2326
- case SyntaxKind.AsteriskAsteriskToken:
2327
- case SyntaxKind.AmpersandAmpersandToken:
2328
- case SyntaxKind.BarBarToken:
2329
- case SyntaxKind.QuestionQuestionToken:
2330
- case SyntaxKind.QuestionDotToken:
2331
- case SyntaxKind.QuestionToken:
2332
- case SyntaxKind.ColonToken:
2333
- case SyntaxKind.AmpersandToken:
2334
- case SyntaxKind.BarToken:
2335
- case SyntaxKind.CaretToken:
2336
- case SyntaxKind.LessThanLessThanToken:
2337
- case SyntaxKind.GreaterThanGreaterThanToken:
2338
- case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
2339
- case SyntaxKind.InstanceOfKeyword:
2340
- case SyntaxKind.InKeyword:
2341
- case SyntaxKind.PrefixUnaryExpression:
2342
- case SyntaxKind.PostfixUnaryExpression:
2343
- case SyntaxKind.TypeOfExpression:
2344
- case SyntaxKind.VoidExpression:
2345
- case SyntaxKind.ConditionalExpression:
2346
- case SyntaxKind.ParenthesizedExpression:
2347
- case SyntaxKind.TemplateExpression:
2348
- case SyntaxKind.NoSubstitutionTemplateLiteral:
2349
- case SyntaxKind.TemplateHead:
2350
- case SyntaxKind.TemplateMiddle:
2351
- case SyntaxKind.TemplateTail:
2352
- case SyntaxKind.TemplateSpan:
2353
- case SyntaxKind.ArrayLiteralExpression:
2354
- case SyntaxKind.ObjectLiteralExpression:
2355
- case SyntaxKind.PropertyAssignment:
2356
- case SyntaxKind.ShorthandPropertyAssignment:
2357
- case SyntaxKind.SpreadAssignment:
2358
- case SyntaxKind.SpreadElement:
2359
- case SyntaxKind.TaggedTemplateExpression:
2360
- case SyntaxKind.SyntaxList: return true;
2361
- default: return false;
2362
- }
2363
- }
2364
- /**
2365
- * Builds a human-readable diagnostic message for a node that is not permitted
2366
- * inside a Xaendar template expression.
2367
- *
2368
- * Provides specific messages for the most common mistakes (assignments, `await`,
2369
- * `new`, functions) and falls back to a generic message for anything else.
2370
- */
2371
- function buildDisallowedMessage(node) {
2372
- switch (node.kind) {
2373
- case SyntaxKind.AwaitExpression: return "'await' is not allowed inside template expressions.";
2374
- case SyntaxKind.YieldExpression: return "'yield' is not allowed inside template expressions.";
2375
- case SyntaxKind.NewExpression: return "'new' is not allowed inside template expressions.";
2376
- case SyntaxKind.ArrowFunction:
2377
- case SyntaxKind.FunctionExpression: return "Function expressions are not allowed inside template expressions.";
2378
- case SyntaxKind.BinaryExpression: return isAssignmentOperator(node.operatorToken.kind) ? "Assignments are not allowed inside template expressions. Use @const to declare local template variables instead." : `'${SyntaxKind[node.kind]}' is not allowed inside template expressions.`;
2379
- default: return `'${SyntaxKind[node.kind]}' is not allowed inside template expressions.`;
2380
- }
2381
- }
2382
- /**
2383
- * Returns `true` if the given {@link SyntaxKind} is an assignment operator.
2384
- *
2385
- * Covers simple assignment (`=`) as well as all compound assignment operators
2386
- * (`+=`, `-=`, `&&=`, `||=`, `??=`, etc.) as defined by the TypeScript
2387
- * `FirstAssignment`–`LastAssignment` range.
2388
- */
2389
- function isAssignmentOperator(kind) {
2390
- return kind >= SyntaxKind.FirstAssignment && kind <= SyntaxKind.LastAssignment;
2391
- }
2392
- //#endregion
2393
- //#region ../packages/compiler/src/parser/states/parse-interpolation.state.ts
2394
- /**
2395
- * Parses an interpolation expression or literal token into an `InterpolationNode`.
2396
- *
2397
- * @param cursor - Parser cursor; advanced past the interpolation token.
2398
- * @param _parseNode - Unused parser function (kept for signature consistency).
2399
- * @param token - The INTERPOLATION_EXPRESSION or INTERPOLATION_LITERAL token.
2400
- * @returns The parsed `InterpolationNode`.
2401
- */
2402
- function parseInterpolation(cursor, _parseNode, token) {
2403
- cursor.advance();
2404
- return {
2405
- type: ASTNodeType.Interpolation,
2406
- expression: validateExpression(token.parts[0]).node
2407
- };
2408
- }
2409
- //#endregion
2410
- //#region ../packages/compiler/src/parser/states/parse-attribute.state.ts
2411
- /**
2412
- * Parses an ATTRIBUTE token into an `AttributeNode`.
2413
- * Handles boolean attributes (no `=`), string values, and interpolation values.
2414
- *
2415
- * @param cursor - Parser cursor; advanced past the ATTRIBUTE token.
2416
- * @param parseNode - Parser function for recursive child parsing.
2417
- * @param token - The ATTRIBUTE token to parse.
2418
- * @returns The parsed `AttributeNode`.
2419
- */
2420
- function parseAttribute(cursor, parseNode, token) {
2421
- cursor.advance();
2422
- const name = token.parts[0];
2423
- const nextToken = cursor.peek();
2424
- if (nextToken.type === TokenType.ATTRIBUTE || nextToken.type === TokenType.TAG_CLOSE_NAME || nextToken.type === TokenType.EVENT) return {
2425
- name,
2426
- value: "true"
2427
- };
2428
- if (nextToken.type === TokenType.INTERPOLATION_EXPRESSION || nextToken.type === TokenType.INTERPOLATION_LITERAL) return {
2429
- name,
2430
- value: parseInterpolation(cursor, parseNode, nextToken)
2431
- };
2432
- if (nextToken.type !== TokenType.ATTRIBUTE_VALUE) throw new Error(`[Parser] Attribute value missing for ${name} in: ${name}`);
2433
- cursor.advance();
2434
- return {
2435
- name,
2436
- value: nextToken.parts[0]
2437
- };
2438
- }
2439
- //#endregion
2440
- //#region ../packages/compiler/src/parser/states/parse-event.state.ts
2441
- /**
2442
- * Parses an EVENT token into an `EventNode` by splitting the raw
2443
- * `eventName=handler` string.
2444
- *
2445
- * @param cursor - Parser cursor; advanced past the EVENT token.
2446
- * @param _parseNode - Unused parser function (kept for signature consistency).
2447
- * @param token - The EVENT token to parse.
2448
- * @returns The parsed `EventNode`.
2449
- */
2450
- function parseEvent(cursor, _parseNode, token) {
2451
- cursor.advance();
2452
- const raw = token.parts[0];
2453
- const [name, value] = raw.split("=");
2454
- if (!name || !value) throw new Error(`[Parser] Invalid event format: ${raw}`);
2455
- const parameters = new Array();
2456
- while (cursor.peek().type === TokenType.EVENT_PAREMETER) {
2457
- cursor.advance();
2458
- parameters.push(validateExpression(cursor.getCurrentToken().value.parts[0]).node);
2459
- }
2460
- return {
2461
- name,
2462
- handler: value.replace(/^[""]|[""]$/g, ""),
2463
- parameters
2464
- };
2465
- }
2466
- //#endregion
2467
- //#region ../packages/compiler/src/parser/states/parse-element.state.ts
2468
- /**
2469
- * Parses a TAG_OPEN_NAME token and the subsequent attributes, events, and children
2470
- * into an `ElementNode`. Handles both regular and self-closing tags.
2471
- *
2472
- * @param cursor - Parser cursor positioned at the TAG_OPEN_NAME token.
2473
- * @param parseNode - Parser function for recursive child parsing.
2474
- * @param token - The TAG_OPEN_NAME token containing the tag name.
2475
- * @returns The parsed `ElementNode`.
2476
- */
2477
- function parseElement(cursor, parseNode, token) {
2478
- cursor.advance();
2479
- const tagName = token.parts[0];
2480
- const attributes = new Array();
2481
- const events = new Array();
2482
- let read = true;
2483
- while (read) {
2484
- const token = cursor.peek();
2485
- switch (token.type) {
2486
- case TokenType.ATTRIBUTE:
2487
- attributes.push(parseAttribute(cursor, parseNode, token));
2488
- break;
2489
- case TokenType.EVENT:
2490
- events.push(parseEvent(cursor, parseNode, token));
2491
- break;
2492
- default: read = false;
2493
- }
2494
- }
2495
- if (cursor.peek().type === TokenType.TAG_OPEN_END) cursor.advance();
2496
- if (cursor.peek().type === TokenType.TAG_SELF_CLOSE) {
2497
- cursor.advance();
2498
- return {
2499
- type: ASTNodeType.Element,
2500
- tagName,
2501
- attributes,
2502
- events,
2503
- children: []
2504
- };
2505
- }
2506
- const children = new Array();
2507
- while (!isTagClose(cursor, tagName)) {
2508
- const child = parseNode();
2509
- if (child) children.push(child);
2510
- }
2511
- cursor.advance();
2512
- return {
2513
- type: ASTNodeType.Element,
2514
- tagName,
2515
- attributes,
2516
- events,
2517
- children
2518
- };
2519
- }
2520
- /**
2521
- * Returns `true` if the next token in the stream is a closing tag for the given tag name.
2522
- *
2523
- * @param cursor - Parser cursor to peek from.
2524
- * @param tagName - The expected tag name to match.
2525
- * @returns `true` if the next token is TAG_CLOSE_NAME matching `tagName`.
2526
- */
2527
- function isTagClose(cursor, tagName) {
2528
- const nextToken = cursor.peek();
2529
- return nextToken.type === TokenType.TAG_CLOSE_NAME && nextToken.parts[0] === tagName;
2530
- }
2531
- //#endregion
2532
- //#region ../packages/compiler/src/parser/states/parse-block-children.state.ts
2533
- /**
2534
- * Parses child AST nodes inside a flow-control block until a BLOCK_CLOSE token is reached.
2535
- * Consumes the BLOCK_CLOSE token before returning.
2536
- *
2537
- * @param cursor - Parser cursor positioned at the first token inside the block.
2538
- * @param parseNode - Parser function for recursive child parsing.
2539
- * @returns Array of parsed child `ASTNode`s.
2540
- */
2541
- function parseBlockChildren(cursor, parseNode) {
2542
- const children = new Array();
2543
- while (cursor.peek().type !== TokenType.BLOCK_CLOSE) {
2544
- const child = parseNode();
2545
- if (child) children.push(child);
2546
- }
2547
- cursor.advance();
2548
- return children;
2549
- }
2550
- //#endregion
2551
- //#region ../packages/compiler/src/parser/states/parse-for.state.ts
2552
- /**
2553
- * Parses a `@for` directive, consuming the FOR token, the CONDITION token,
2554
- * the BLOCK_OPEN token, and all child nodes until BLOCK_CLOSE.
2555
- *
2556
- * @param cursor - Parser cursor positioned at the FOR token.
2557
- * @param parseNode - Parser function for recursive child parsing.
2558
- * @param _token - The FOR token (consumed for position advancement).
2559
- * @returns The parsed `ForNode`.
2560
- */
2561
- function parseForControlFlow(cursor, parseNode, _token) {
2562
- cursor.advance();
2563
- const conditionToken = cursor.peek();
2564
- if (conditionToken.type !== TokenType.CONDITION) throw new Error(`[Parser] Expected CONDITION after FOR, got ${TokenType[conditionToken.type]}`);
2565
- const expression = parseForExpression(conditionToken.parts[0], 0);
2566
- cursor.advance(2);
2567
- const children = parseBlockChildren(cursor, parseNode);
2568
- return {
2569
- type: ASTNodeType.For,
2570
- ...expression,
2571
- children
2572
- };
2573
- }
2574
- /**
2575
- * Parses the body of an `@for` block into a structured {@link ForExpression}.
2576
- *
2577
- * The expected format is:
2578
- * ```
2579
- * item of iterable; track expr[; $implicit = alias, ...]
2580
- * ```
2581
- *
2582
- * @param source - The raw string content of the `@for(...)` expression.
2583
- * @param baseOffset - Character offset of `source` within the original template,
2584
- * used to produce accurate diagnostic positions.
2585
- * @returns A {@link ForExpression} object. When unrecoverable syntax errors are
2586
- * found the returned object contains only `diagnostics`.
2587
- */
2588
- function parseForExpression(source, baseOffset) {
2589
- const sections = splitForSections(source);
2590
- if (sections.length < 2) throw new Error(`[Parser] @for requires at least "item of iterable; track expr".`);
2591
- const iterSection = sections[0].trim();
2592
- const ofIndex = iterSection.indexOf(" of ");
2593
- if (ofIndex === -1) throw new Error(`[Parser] @for expression must be in the form "item of iterable".`);
2594
- const itemAlias = iterSection.slice(0, ofIndex).trim();
2595
- const iterableSource = iterSection.slice(ofIndex + 4).trim();
2596
- if (!isValidIdentifier(itemAlias)) throw new Error(`[Parser] '${itemAlias}' is not a valid item alias.`);
2597
- const iterValidation = validateExpression(iterableSource);
2598
- const trackSection = sections[1].trim();
2599
- if (!trackSection.startsWith("track ")) throw new Error(`[Parser] Second section of @for must start with "track".`);
2600
- const trackSource = trackSection.slice(6).trim();
2601
- const trackValidation = validateExpression(trackSource);
2602
- const implicitAliases = /* @__PURE__ */ new Map();
2603
- if (sections.length >= 3 && sections[2] !== void 0) parseImplicitAliases(sections[2].trim(), baseOffset + source.indexOf(sections[2]), implicitAliases);
2604
- return {
2605
- itemAlias,
2606
- iterableExpression: iterValidation.node,
2607
- iterableSource,
2608
- trackExpression: trackValidation.node,
2609
- trackSource,
2610
- implicitAliases
2611
- };
2612
- }
2613
- /**
2614
- * Parses the optional third section of an `@for` expression, which declares
2615
- * aliases for implicit loop variables (e.g. `$index = i, $last = l, $even = isEven`).
2616
- *
2617
- * Valid entries are comma-separated pairs in the form `$implicit = alias`.
2618
- *
2619
- * @param source - The raw alias-declarations string (everything after the second `;`).
2620
- * @param baseOffset - Character offset of `source` within the original template.
2621
- * @param out - Map to populate with `alias → implicit-variable` entries.
2622
- */
2623
- function parseImplicitAliases(source, baseOffset, out) {
2624
- const entries = source.split(",");
2625
- let cursor = 0;
2626
- const IMPLICIT_VARIABLES = /* @__PURE__ */ new Set([
2627
- "$index",
2628
- "$last",
2629
- "$first",
2630
- "$even",
2631
- "$odd"
2632
- ]);
2633
- for (const entry of entries) {
2634
- const trimmed = entry.trim();
2635
- const eqIndex = trimmed.indexOf("=");
2636
- if (eqIndex === -1) throw new Error(`[Parser] Invalid alias declaration '${trimmed}'. Expected '$implicit = alias'.`);
2637
- cursor += entry.length + 1;
2638
- const alias = trimmed.slice(0, eqIndex).trim();
2639
- const implicit = trimmed.slice(eqIndex + 1).trim();
2640
- const isImplicitVariable = (value) => IMPLICIT_VARIABLES.has(value);
2641
- if (!isImplicitVariable(implicit)) throw new Error(`[Parser] '${implicit}' is not a known implicit variable. Known variables: ${[...IMPLICIT_VARIABLES].join(", ")}.`);
2642
- cursor += entry.length + 1;
2643
- if (!isValidIdentifier(alias)) throw new Error(`[Parser] '${alias}' is not a valid alias identifier.`);
2644
- cursor += entry.length + 1;
2645
- if (out.has(implicit)) throw new Error(`[Parser] '${implicit}' is already aliased in this @for expression.`);
2646
- else out.set(implicit, alias);
2647
- cursor += entry.length + 1;
2648
- }
2649
- }
2650
- /**
2651
- * Splits the raw `@for(...)` body into its semicolon-delimited sections,
2652
- * respecting nested brackets and string literals so that semicolons inside
2653
- * them are never treated as section separators.
2654
- *
2655
- * Example input: `"item of items; track item.id; $index = i"`
2656
- * Example output: `["item of items", " track item.id", " $index = i"]`
2657
- *
2658
- * @param source - The raw content of the `@for(...)` expression.
2659
- * @returns An array of section strings (without the `;` separators).
2660
- */
2661
- function splitForSections(source) {
2662
- const sections = new Array();
2663
- let current = "";
2664
- let depth = 0;
2665
- let inString;
2666
- for (let i = 0; i < source.length; i++) {
2667
- const char = source[i];
2668
- if (!current && char === " ") continue;
2669
- if (inString) {
2670
- current += char;
2671
- if (char === inString && source[i - 1] !== "\\") inString = null;
2672
- continue;
2673
- }
2674
- if (char === "\"" || char === "'" || char === "`") {
2675
- inString = char;
2676
- current += char;
2677
- continue;
2678
- }
2679
- if (char === "(" || char === "[" || char === "{") {
2680
- depth++;
2681
- current += char;
2682
- continue;
2683
- }
2684
- if (char === ")" || char === "]" || char === "}") {
2685
- depth--;
2686
- current += char;
2687
- continue;
2688
- }
2689
- if (char === ";" && depth === 0) {
2690
- sections.push(current);
2691
- current = "";
2692
- continue;
2693
- }
2694
- current += char;
2695
- }
2696
- if (current.trim().length) sections.push(current);
2697
- return sections;
2698
- }
2699
- /**
2700
- * Checks whether `name` is a valid JavaScript identifier by delegating to
2701
- * the TypeScript parser.
2702
- *
2703
- * A string is considered valid when the TS parser produces a single
2704
- * `ExpressionStatement` whose expression is an `Identifier` with the
2705
- * same text.
2706
- *
2707
- * @param name - The string to validate.
2708
- * @returns `true` if `name` is a valid JS identifier, `false` otherwise.
2709
- */
2710
- function isValidIdentifier(name) {
2711
- if (!name.length) return false;
2712
- const statement = createSourceFile("__id.ts", name, ScriptTarget.ESNext, false).statements[0];
2713
- return !!statement && isExpressionStatement(statement) && isIdentifier(statement.expression) && statement.expression.text === name;
2714
- }
2715
- //#endregion
2716
- //#region ../packages/compiler/src/parser/states/parse-if.state.ts
2717
- /**
2718
- * Parses an `@if` directive, consuming the IF token, the CONDITION token,
2719
- * the BLOCK_OPEN token, all consequent children, and an optional `@else` branch.
2720
- *
2721
- * @param cursor - Parser cursor positioned at the IF token.
2722
- * @param context - Parser function for recursive child parsing.
2723
- * @param token - The IF token (consumed for position advancement).
2724
- * @returns The parsed `IfNode`.
2725
- */
2726
- function parseIfControlFlow(cursor, parseNode, token) {
2727
- return parseIfOrElseIf(cursor, parseNode, token);
2728
- }
2729
- function parseElseIfRecursively(cursor, parseNode, token) {
2730
- switch (token.type) {
2731
- case TokenType.ELSE_IF: return parseIfOrElseIf(cursor, parseNode, token);
2732
- case TokenType.ELSE:
2733
- cursor.advance(2);
2734
- const elseChildren = parseBlockChildren(cursor, parseNode);
2735
- return {
2736
- type: ASTNodeType.Else,
2737
- children: elseChildren
2738
- };
2739
- }
2740
- }
2741
- function parseIfOrElseIf(cursor, parseNode, token) {
2742
- cursor.advance();
2743
- const conditionToken = cursor.peek();
2744
- if (conditionToken.type !== TokenType.CONDITION) throw new Error(`[Parser] Expected CONDITION after ${TokenType[token.type]}, got ${TokenType[conditionToken.type]}`);
2745
- cursor.advance(2);
2746
- const condition = conditionToken.parts[0];
2747
- const validationResult = validateExpression(condition);
2748
- const consequent = parseBlockChildren(cursor, parseNode);
2749
- return {
2750
- type: token.type === TokenType.IF ? ASTNodeType.If : ASTNodeType.ElseIf,
2751
- condition,
2752
- conditionNode: validationResult.node,
2753
- children: consequent,
2754
- alternate: parseElseIfRecursively(cursor, parseNode, cursor.peek())
2755
- };
2756
- }
2757
- //#endregion
2758
- //#region ../packages/compiler/src/parser/states/parse-import.state.ts
2759
- /**
2760
- * Parses an `@import` statement.
2761
- *
2762
- * Extracts specifiers with optional aliases and maps them to structured
2763
- * `ImportSpecifier` objects. Supports:
2764
- * - `foo` → imported: 'foo', local: 'foo'
2765
- * - `foo as bar` → imported: 'foo', local: 'bar'
2766
- * - `default as D` → imported: 'default', local: 'D'
2767
- * - `* as ns` → imported: '*', local: 'ns'
2768
- */
2769
- function parseImport(cursor, _parseNode, _token) {
2770
- const specifiers = new Array();
2771
- while (cursor.peek().type === TokenType.IMPORT) {
2772
- cursor.advance();
2773
- const rawSpecifier = cursor.getCurrentToken().value.parts[0];
2774
- const specifier = parseSpecifier(rawSpecifier);
2775
- specifiers.push(specifier);
2776
- }
2777
- cursor.advance();
2778
- return {
2779
- type: ASTNodeType.Import,
2780
- specifiers,
2781
- path: cursor.getCurrentToken().value.parts[0]
2782
- };
2783
- }
2784
- /**
2785
- * Parses a single import specifier string.
2786
- *
2787
- * @param raw - The raw specifier string (may include 'as' alias).
2788
- * @returns An `ImportSpecifier` with `imported` and `local` fields.
2789
- * @throws If the specifier format is invalid.
2790
- */
2791
- function parseSpecifier(raw) {
2792
- const trimmed = raw.trim();
2793
- const namespaceMatch = trimmed.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
2794
- if (namespaceMatch) return {
2795
- imported: "*",
2796
- local: namespaceMatch[1]
2797
- };
2798
- const defaultMatch = trimmed.match(/^default\s+as\s+([A-Za-z_$][\w$]*)$/);
2799
- if (defaultMatch) return {
2800
- imported: "default",
2801
- local: defaultMatch[1]
2802
- };
2803
- const namedMatch = trimmed.match(/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
2804
- if (namedMatch) return {
2805
- imported: namedMatch[1],
2806
- local: namedMatch[2] ?? namedMatch[1]
2807
- };
2808
- throw new Error(`[Parser] Invalid import specifier "${raw}".`);
2809
- }
2810
- //#endregion
2811
- //#region ../packages/compiler/src/parser/states/parse-switch.state.ts
2812
- /**
2813
- * Parses a `@switch` directive, consuming the SWITCH token, the CONDITION token,
2814
- * the outer BLOCK_OPEN, all `@case` and `@default` branches, and the outer BLOCK_CLOSE.
2815
- *
2816
- * @param cursor - Parser cursor positioned at the SWITCH token.
2817
- * @param parseNode - Parser function for recursive child parsing.
2818
- * @param _token - The SWITCH token (consumed for position advancement).
2819
- * @returns The parsed `SwitchNode`.
2820
- */
2821
- function parseSwitchControlFlow(cursor, parseNode, _token) {
2822
- cursor.advance();
2823
- const conditionToken = cursor.peek();
2824
- if (conditionToken.type !== TokenType.CONDITION) throw new Error(`[Parser] Expected CONDITION after SWITCH, got ${TokenType[conditionToken.type]}`);
2825
- const expression = validateExpression(conditionToken.parts[0]).node;
2826
- cursor.advance(2);
2827
- const cases = new Array();
2828
- while (cursor.peek().type !== TokenType.BLOCK_CLOSE) switch (cursor.peek().type) {
2829
- case TokenType.CASE:
2830
- const condition = new Array();
2831
- do {
2832
- cursor.advance();
2833
- const caseCondition = cursor.peek();
2834
- if (caseCondition.type !== TokenType.CONDITION) throw new Error(`[Parser] Expected CONDITION after CASE`);
2835
- condition.push(caseCondition.parts[0]);
2836
- cursor.advance();
2837
- } while (cursor.peek().type !== TokenType.BLOCK_OPEN);
2838
- cursor.advance();
2839
- cases.push({
2840
- type: ASTNodeType.Case,
2841
- condition,
2842
- children: parseBlockChildren(cursor, parseNode)
2843
- });
2844
- break;
2845
- case TokenType.DEFAULT:
2846
- cursor.advance(2);
2847
- cases.push({
2848
- type: ASTNodeType.Case,
2849
- condition: null,
2850
- children: parseBlockChildren(cursor, parseNode)
2851
- });
2852
- break;
2853
- }
2854
- cursor.advance();
2855
- return {
2856
- type: ASTNodeType.Switch,
2857
- expression,
2858
- children: cases
2859
- };
2860
- }
2861
- //#endregion
2862
- //#region ../packages/compiler/src/parser/states/parse-text.state.ts
2863
- /**
2864
- * Parses a TEXT token into a `TextNode`.
2865
- *
2866
- * @param cursor - Parser cursor; advanced past the TEXT token.
2867
- * @param _parseNode - Unused parser function (kept for signature consistency).
2868
- * @param token - The TEXT token containing the raw text content.
2869
- * @returns The parsed `TextNode`.
2870
- */
2871
- function parseText(cursor, _parseNode, token) {
2872
- cursor.advance();
2873
- return {
2874
- type: ASTNodeType.Text,
2875
- value: token.parts[0]
2876
- };
2877
- }
2878
- //#endregion
2879
- //#region ../packages/compiler/src/parser/parser.ts
2880
- /**
2881
- * Parser class that transforms a stream of tokens (from the Lexer)
2882
- * into an Abstract Syntax Tree (AST) representing the template structure.
2883
- *
2884
- * Responsibilities:
2885
- * - Parse text nodes, elements, attributes, events, and interpolations
2886
- * - Maintain cursor state for sequential token consumption
2887
- * - Detect tag boundaries and nested structures
2888
- *
2889
- * The parser assumes that the token stream is syntactically valid according
2890
- * to the Lexer rules. Parsing errors are thrown as exceptions.
2891
- */
2892
- var Parser = class {
2893
- /**
2894
- * Internal cursor for navigating tokens
2895
- */
2896
- _cursor;
2897
- /**
2898
- * Mapping of token types to their corresponding parser transition functions,
2899
- * which handle the logic for parsing each token type into AST nodes.
2900
- */
2901
- _states = {
2902
- [TokenType.TEXT]: parseText,
2903
- [TokenType.INTERPOLATION_EXPRESSION]: parseInterpolation,
2904
- [TokenType.INTERPOLATION_LITERAL]: parseInterpolation,
2905
- [TokenType.TAG_OPEN_NAME]: parseElement,
2906
- [TokenType.IF]: parseIfControlFlow,
2907
- [TokenType.FOR]: parseForControlFlow,
2908
- [TokenType.SWITCH]: parseSwitchControlFlow,
2909
- [TokenType.IMPORT]: parseImport
2910
- };
2911
- /**
2912
- * Creates a new Parser instance.
2913
- *
2914
- * @param tokens - Array of tokens produced by the Lexer.
2915
- */
2916
- constructor(tokens) {
2917
- this._cursor = new ParserCursor(tokens);
2918
- }
2919
- /**
2920
- * Entry point for parsing the token stream into AST nodes.
2921
- *
2922
- * @returns Array of top-level AST nodes.
2923
- */
2924
- parse() {
2925
- const nodes = new Array();
2926
- while (this._cursor.peek().type !== TokenType.EOF) {
2927
- const parseNode = this.parseNode();
2928
- if (parseNode) nodes.push(parseNode);
2929
- }
2930
- return nodes;
2931
- }
2932
- /**
2933
- * Parses the next AST node based on the current token.
2934
- *
2935
- * @returns The parsed AST node, or `undefined` for EOF.
2936
- * @throws When no transition function is registered for the current token type.
2937
- */
2938
- parseNode() {
2939
- const token = this._cursor.peek();
2940
- if (token.type === TokenType.EOF) return;
2941
- const state = this._states[token.type];
2942
- if (!state) throw new Error(`[Parser] No transition function for token type ${TokenType[token.type]}`);
2943
- return state(this._cursor, this.parseNode.bind(this), token);
2944
- }
2945
- };
2946
- //#endregion
2947
- //#region ../packages/compiler/src/type-checker/models/type-checker-context.ts
2948
- /**
2949
- * Type checker context that manages imported component and directive metadata
2950
- * for the compilation process. Extends the base CompilerContext to provide
2951
- * import-specific functionality.
2952
- */
2953
- var TypeCheckContext = class extends CompilerContext {
2954
- /**
2955
- * Array of component and directive imports to be tracked during type checking.
2956
- * Stores metadata extracted from @WebComponent and @Directive decorators.
2957
- */
2958
- _imports = new Array();
2959
- /**
2960
- * Adds a new component or directive import to the type checker context.
2961
- *
2962
- * @param value - The component or directive import metadata to be added
2963
- */
2964
- addImport(value) {
2965
- this._imports.push(value);
2966
- }
2967
- /**
2968
- * Retrieves all registered component and directive imports.
2969
- *
2970
- * @returns Array of all tracked imports
2971
- */
2972
- getImportBySelector(tagName) {
2973
- return this._imports.find((importValue) => importValue.type === "component" && importValue.selectors.includes(tagName));
2974
- }
2975
- };
2976
- //#endregion
2977
- //#region ../packages/compiler/src/type-checker/states/type-check-element.state.ts
2978
- /**
2979
- * Type-checks an element node: its attribute/property bindings, its event
2980
- * handlers, and recursively its children.
2981
- *
2982
- * Two completely different validation paths, depending on the tag:
2983
- *
2984
- * - **Native tags** (`div`, `button`, ...): bindings are just resolved
2985
- * expressions/handler calls, exactly as before — there's no component
2986
- * contract to check them against.
2987
- * - **Custom elements** (tag contains a hyphen): every binding is checked
2988
- * by NAME against the `@Property`/`@Event` metadata extracted from the
2989
- * imported component class. An unknown name is a hard error (the
2990
- * template is binding to something the component doesn't expose) — not
2991
- * a warning, consistent with the "no matching @import" check already in
2992
- * place for the tag itself.
2993
- *
2994
- * No variable is declared for the element itself (see the module doc on
2995
- * `TypeChecker` for why) — attribute expressions and event calls are
2996
- * emitted as bare statements, validated in place.
2997
- */
2998
- function typeCheckElement(node, processNode, context) {
2999
- const lines = new Array();
3000
- if (isCustomElementTag(node.tagName)) {
3001
- const metadata = context.getImportBySelector(node.tagName);
3002
- if (!metadata) throw new Error(`[Type Checker] ${node.tagName} selector is not associated to any WebComponent imported in the template`);
3003
- lines.push(...typeCheckComponentBindings(node, metadata, context));
3004
- } else {
3005
- node.attributes.forEach(({ value }) => {
3006
- if (typeof value !== "string") lines.push(`${resolveExpression(value.expression, context, { resolver: "root" })};`);
3007
- });
3008
- node.events.forEach(({ handler, parameters }) => {
3009
- const eventContext = new CompilerContext([], context);
3010
- eventContext.addUnresolvableIdentifier("$event");
3011
- const args = parameters.map((parameter) => resolveExpression(parameter, eventContext, { resolver: "root" })).join(", ");
3012
- lines.push(`root.${handler}(${args});`);
3013
- });
3014
- }
3015
- node.children.forEach((child) => lines.push(...processNode(child, context)));
3016
- return lines;
3017
- }
3018
- /**
3019
- * Validates every attribute/property and event binding on a custom-element
3020
- * node against the `@Property`/`@Event` metadata of the resolved component.
3021
- *
3022
- * Property values are checked with `satisfies` against `property.type` —
3023
- * chosen over a typed `const` so an unused local never shows up if the
3024
- * consuming project has `noUnusedLocals` enabled. Event handlers get a
3025
- * block-scoped `$event` typed from `event.detailType`, wrapped in its own
3026
- * `{ }` block so multiple event bindings on sibling elements never clash
3027
- * on the `$event` name.
3028
- *
3029
- * ⚠️ Assumptions I couldn't verify against your actual types — please
3030
- * confirm/adjust:
3031
- * - `node.events` entries carry a `name` field (the template-facing event
3032
- * name used to look up `@Event` metadata), the same way attributes carry
3033
- * `name`. The snippets I've seen only destructured `handler`/`parameters`
3034
- * for events, never `name` — if the field is called something else,
3035
- * swap it in `findEvent`'s call site below.
3036
- * - Literal string attribute values (`value` is a plain `string`, no
3037
- * `{ expression }`) are only checked for NAME existence, not type — I
3038
- * don't validate e.g. `collapsed="true"` against a `boolean` property,
3039
- * since I don't know whether your DSL treats un-bound string attributes
3040
- * as always-string or allows some literal coercion syntax. Flagging
3041
- * rather than guessing.
3042
- * - `property.type`/`event.detailType` are text that must resolve as a
3043
- * standalone type expression in the shim's scope (e.g. `'NavItem[]'`
3044
- * requires `NavItem` to be importable there too, not just the component
3045
- * class). If your shim only imports the component class today, custom
3046
- * exported types referenced by `type`/`detailType` will fail to resolve
3047
- * as "cannot find name" — unrelated to the actual binding being right or
3048
- * wrong. Worth double-checking against a component that has a non-
3049
- * primitive `@Property` type, like `NavItem[]` in your own example.
3050
- */
3051
- function typeCheckComponentBindings(node, metadata, context) {
3052
- const lines = new Array();
3053
- node.attributes.forEach(({ name, value }) => {
3054
- const property = findProperty(metadata, name);
3055
- if (!property) throw new Error(`[Type Checker] Unknown property "${name}" on <${node.tagName}> (${metadata.className} has no @Property with this name or alias).`);
3056
- if (typeof value === "string") return;
3057
- const expression = resolveExpression(value.expression, context, { resolver: "root" });
3058
- lines.push(property.type ? `(${expression}) satisfies ${property.type};` : `${expression};`);
3059
- });
3060
- node.events.forEach(({ name, handler, parameters }) => {
3061
- const event = findEvent(metadata, name);
3062
- if (!event) throw new Error(`[Type Checker] Unknown event "${name}" on <${node.tagName}> (${metadata.className} has no @Event with this name).`);
3063
- const eventContext = new CompilerContext([], context);
3064
- eventContext.addUnresolvableIdentifier("$event");
3065
- const args = parameters.map((parameter) => resolveExpression(parameter, eventContext, { resolver: "root" })).join(", ");
3066
- lines.push("{");
3067
- lines.push(` let $event!: ${event.detailType ?? "unknown"};`);
3068
- lines.push(` root.${handler}(${args});`);
3069
- lines.push("}");
3070
- });
3071
- return lines;
3072
- }
3073
- function findProperty(metadata, externalName) {
3074
- return metadata.properties.find((property) => (property.alias ?? property.name) === externalName);
3075
- }
3076
- function findEvent(metadata, externalName) {
3077
- return metadata.events.find((event) => event.name === externalName);
3078
- }
3079
- /**
3080
- * Returns true if `tagName` has the shape of a custom element (per the
3081
- * Custom Elements spec: contains a hyphen). This is a pure classification
3082
- * check — it does NOT validate whether the name is actually usable as a
3083
- * new custom element (reserved names, etc.); use `isValidCustomElementName`
3084
- * for that, at declaration time.
3085
- */
3086
- function isCustomElementTag(tagName) {
3087
- return /^[a-z][a-z0-9._\-]*-[a-z0-9._\-]*$/.test(tagName);
3088
- }
3089
- //#endregion
3090
- //#region ../packages/compiler/src/type-checker/states/type-check-for.state.ts
3091
- /**
3092
- * Type-checks an `@for` block using a real `for...of` loop.
3093
- *
3094
- * This replaces the previous "synthetic function with a `typeof array`
3095
- * parameter" trick, which mistyped the loop variable as the *whole array*
3096
- * rather than a single element. A real `for (const item of array)` lets
3097
- * TypeScript infer `item`'s type correctly as the array's element type —
3098
- * exactly like it would for a loop written by hand — with no synthetic
3099
- * function boundary needed.
3100
- */
3101
- function typeCheckFor(node, processNode, context) {
3102
- const forContext = new TypeCheckContext([], context);
3103
- const indexName = resolveImplicit(node, "$index");
3104
- const firstName = resolveImplicit(node, "$first");
3105
- const lastName = resolveImplicit(node, "$last");
3106
- const evenName = resolveImplicit(node, "$even");
3107
- const oddName = resolveImplicit(node, "$odd");
3108
- [
3109
- indexName,
3110
- firstName,
3111
- lastName,
3112
- evenName,
3113
- oddName,
3114
- node.itemAlias
3115
- ].forEach((identifier) => forContext.addUnresolvableIdentifier(identifier));
3116
- const lines = new Array();
3117
- lines.push(`for (const ${node.itemAlias} of root.${node.iterableSource}) {`);
3118
- lines.push(...indent([
3119
- `let ${indexName}!: number;`,
3120
- `let ${firstName}!: boolean;`,
3121
- `let ${lastName}!: boolean;`,
3122
- `let ${evenName}!: boolean;`,
3123
- `let ${oddName}!: boolean;`,
3124
- `${resolveExpression(node.trackExpression, context, { skipResolution: true })};`,
3125
- ...node.children.flatMap((child) => processNode(child, forContext))
3126
- ]));
3127
- lines.push("}");
3128
- return lines;
3129
- }
3130
- /**
3131
- * Resolves the name that should be used in generated code for a given
3132
- * implicit variable.
3133
- *
3134
- * If the template declared an explicit alias for the variable
3135
- * (e.g. `; $index = i`) that alias is returned. Otherwise the default
3136
- * implicit variable name (e.g. `$index`) is used.
3137
- *
3138
- * @param node - The `ForNode` whose implicit alias map is consulted.
3139
- * @param implicit - The implicit variable to look up (e.g. `'$index'`).
3140
- * @returns The alias string if one was declared, otherwise `implicit` itself.
3141
- */
3142
- function resolveImplicit(node, implicit) {
3143
- return node.implicitAliases.get(implicit) ?? implicit;
3144
- }
3145
- //#endregion
3146
- //#region ../packages/compiler/src/type-checker/states/type-check-if.state.ts
3147
- /**
3148
- * Type-checks an `@if`/`@else if`/`@else` chain using real TypeScript
3149
- * `if` / `else if` / `else` blocks.
3150
- *
3151
- * This is a genuine correctness improvement over the previous
3152
- * sibling-functions approach, not just a simplification: a real
3153
- * `if`/`else if` chain gives the TS compiler's control-flow analysis the
3154
- * negated narrowing of every preceding condition for free (e.g. inside an
3155
- * `else if`, TS already knows the first condition was false) — something
3156
- * flat sibling functions could never express.
3157
- */
3158
- function typeCheckIf(node, processNode, context) {
3159
- const lines = new Array();
3160
- const condition = resolveExpression(node.conditionNode, context, { resolver: "root" });
3161
- lines.push(`if (${condition}) {`);
3162
- lines.push(...indent(node.children.flatMap((child) => processNode(child, context))));
3163
- lines.push("}");
3164
- let alt = node.alternate;
3165
- while (alt?.type === ASTNodeType.ElseIf) {
3166
- const elseIfCondition = resolveExpression(alt.conditionNode, context, { resolver: "root" });
3167
- lines.push(`else if (${elseIfCondition}) {`);
3168
- lines.push(...indent(alt.children.flatMap((child) => processNode(child, context))));
3169
- lines.push("}");
3170
- alt = alt.alternate;
3171
- }
3172
- if (alt) {
3173
- lines.push("else {");
3174
- lines.push(...indent(alt.children.flatMap((child) => processNode(child, context))));
3175
- lines.push("}");
3176
- }
3177
- return lines;
3178
- }
3179
- //#endregion
3180
- //#region ../packages/compiler/src/type-checker/states/type-check-import.state.ts
3181
- /**
3182
- * Type-checks an `@import` node.
3183
- *
3184
- * Emits a typed variable declaration for each specifier so symbols are
3185
- * available in template expressions. Metadata extraction (component/directive
3186
- * decorator analysis) happens in a separate async phase via
3187
- * `TypeChecker.populateImportMetadata()`.
3188
- */
3189
- function typeCheckImport(node, _processNode, _context) {
3190
- const source = node.path;
3191
- const escapedSource = escapeTypeString(source);
3192
- const lines = new Array();
3193
- for (const specifier of node.specifiers) {
3194
- const { imported, local } = specifier;
3195
- switch (imported) {
3196
- case "*":
3197
- lines.push(`let ${local}!: typeof import('${escapedSource}');`);
3198
- break;
3199
- case "default":
3200
- lines.push(`let ${local}!: typeof import('${escapedSource}')['default'];`);
3201
- break;
3202
- default:
3203
- const escapedImported = escapeTypeString(imported);
3204
- lines.push(`let ${local}!: typeof import('${escapedSource}')['${escapedImported}'];`);
3205
- break;
3206
- }
3207
- }
3208
- return lines;
3209
- }
3210
- /**
3211
- * Escapes path/specifier text for safe use in TS single-quoted type strings.
3212
- */
3213
- function escapeTypeString(value) {
3214
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
3215
- }
3216
- //#endregion
3217
- //#region ../packages/compiler/src/type-checker/states/type-check-switch.state.ts
3218
- /**
3219
- * Type-checks a `@switch` block using a real TypeScript `switch` statement.
3220
- *
3221
- * This drops the previous `const case_0: typeof switchExpr = 'literal';`
3222
- * trick entirely: a real `switch (expr) { case 'literal': ... }` already
3223
- * makes TS validate that each case value is assignable to the switch
3224
- * expression's type as part of ordinary switch-statement semantics — and,
3225
- * as a bonus, narrows the switch expression's type inside each case block
3226
- * (e.g. from a `'loading' | 'error' | 'idle'` union down to just
3227
- * `'loading'`), which the previous approach never provided.
3228
- *
3229
- * Multiple case labels that shared one body in the AST (fallthrough) are
3230
- * emitted as stacked `case` labels sharing that same body, matching real
3231
- * JS/TS fallthrough syntax directly.
3232
- */
3233
- function typeCheckSwitch(node, processNode, context) {
3234
- const lines = [`switch (${resolveExpression(node.expression, context, { resolver: "root" })}) {`];
3235
- node.children.forEach((caseNode) => {
3236
- caseNode.condition?.length ? caseNode.condition.forEach((conditionValue) => lines.push(` case ${conditionValue}:`)) : lines.push(" default:");
3237
- lines.push(...indent(indent([...caseNode.children.flatMap((child) => processNode(child, context)), "break;"])));
3238
- });
3239
- lines.push("}");
3240
- return lines;
3241
- }
3242
- //#endregion
3243
- //#region ../packages/compiler/src/type-checker/states/type-check-text-and-interpolation.state.ts
3244
- /**
3245
- * Type-checks a text or interpolation node.
3246
- *
3247
- * Plain text nodes carry no expression, so they produce no lines. An
3248
- * interpolation's expression is emitted as a bare statement — enough for
3249
- * TS to validate it, with no name needing to be bound to the result.
3250
- */
3251
- function typeCheckTextAndInterpolation(node, _processNode, context) {
3252
- return node.type === ASTNodeType.Interpolation ? [`${resolveExpression(node.expression, context, { resolver: "root" })};`] : [];
3253
- }
3254
- //#endregion
3255
- //#region ../packages/compiler/src/type-checker/utils/component-metadata-extractor.ts
3256
- var import___vite_browser_external = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3257
- module.exports = {};
3258
- })))();
3259
- /**
3260
- * Extracts component metadata from a source file by parsing decorators.
3261
- *
3262
- * @param modulePath - The import module path (e.g., './button.component')
3263
- * @param symbolName - The exported symbol name to look for
3264
- * @param baseDir - The directory context for resolving relative paths
3265
- * @returns Component metadata if found, undefined otherwise
3266
- */
3267
- async function extractComponentMetadata(modulePath, symbolName, baseDir = process.cwd()) {
3268
- try {
3269
- const filePath = resolveModulePath(modulePath, baseDir);
3270
- if (!filePath) return;
3271
- const sourceFile = createSourceFile(filePath, await (0, import___vite_browser_external.readFile)(filePath, "utf8"), ScriptTarget.Latest, true);
3272
- let componentMetadata;
3273
- forEachChild(sourceFile, (node) => {
3274
- if (componentMetadata) return;
3275
- if (isClassDeclaration(node)) {
3276
- const className = node.name?.text;
3277
- if (className !== symbolName) return;
3278
- const modifiers = node.modifiers ?? [];
3279
- const webComponentDecorator = Array.from(modifiers).find((member) => isWebComponentDecorator(member));
3280
- if (!webComponentDecorator) return;
3281
- const selectors = extractSelectorsFromDecorator(webComponentDecorator);
3282
- if (!selectors?.length) return;
3283
- const properties = new Array();
3284
- const events = new Array();
3285
- node.members.forEach((member) => {
3286
- if (!isAccessor(member)) return;
3287
- const memberModifiers = member.modifiers ?? [];
3288
- const propDecorator = Array.from(memberModifiers).find((member) => isPropertyDecorator(member));
3289
- if (propDecorator) {
3290
- const nameNode = getNameOfDeclaration(member);
3291
- const propName = nameNode && isIdentifier(nameNode) ? nameNode.text : void 0;
3292
- if (propName) {
3293
- const metadata = extractPropertyMetadata(propName, propDecorator);
3294
- if (metadata) properties.push(metadata);
3295
- }
3296
- return;
3297
- }
3298
- const eventDecorator = Array.from(memberModifiers).find((member) => isEventDecorator(member));
3299
- if (eventDecorator) {
3300
- const nameNode = getNameOfDeclaration(member);
3301
- const eventName = nameNode && isIdentifier(nameNode) ? nameNode.text : void 0;
3302
- if (eventName) {
3303
- const metadata = extractEventMetadata(eventName, eventDecorator);
3304
- if (metadata) events.push(metadata);
3305
- }
3306
- }
3307
- });
3308
- componentMetadata = {
3309
- type: "component",
3310
- className,
3311
- selectors,
3312
- properties,
3313
- events
3314
- };
3315
- }
3316
- });
3317
- return componentMetadata;
3318
- } catch (error) {
3319
- console.error(`Failed to extract component metadata from ${modulePath}:`, error);
3320
- return;
3321
- }
3322
- }
3323
- /**
3324
- * Resolves a module import path to an actual file system path.
3325
- * Handles both relative paths (./button.component) and package paths (@scope/pkg).
3326
- *
3327
- * @param modulePath - The import module path
3328
- * @param baseDir - The directory to resolve relative imports from
3329
- * @returns The resolved file path, or undefined if not found
3330
- */
3331
- function resolveModulePath(modulePath, baseDir) {
3332
- if (modulePath.startsWith(".")) {
3333
- const resolvedPath = (0, import___vite_browser_external.resolve)(baseDir, modulePath);
3334
- if ((0, import___vite_browser_external.existsSync)(`${resolvedPath}.ts`)) return resolvedPath + ".ts";
3335
- if ((0, import___vite_browser_external.existsSync)(`${resolvedPath}/index.ts`)) return `${resolvedPath}/index.ts`;
3336
- if ((0, import___vite_browser_external.existsSync)(resolvedPath)) return resolvedPath;
3337
- }
3338
- }
3339
- /**
3340
- * Checks if a modifier is a @WebComponent decorator.
3341
- */
3342
- function isWebComponentDecorator(modifier) {
3343
- if (modifier.kind !== SyntaxKind.Decorator) return false;
3344
- const expr = isCallExpression(modifier.expression) ? modifier.expression.expression : modifier.expression;
3345
- return isIdentifier(expr) && expr.text === "WebComponent";
3346
- }
3347
- /**
3348
- * Checks if a modifier is a @Property decorator.
3349
- */
3350
- function isPropertyDecorator(modifier) {
3351
- if (modifier.kind !== SyntaxKind.Decorator) return false;
3352
- const expr = modifier.expression;
3353
- if (isCallExpression(expr)) return isIdentifier(expr.expression) && expr.expression.text === "Property";
3354
- if (isPropertyAccessExpression(expr)) return isIdentifier(expr.expression) && expr.expression.text === "Property" && isIdentifier(expr.name) && expr.name.text === "required";
3355
- return isIdentifier(expr) && expr.text === "Property";
3356
- }
3357
- /**
3358
- * Checks if a modifier is an @Event decorator.
3359
- */
3360
- function isEventDecorator(modifier) {
3361
- if (modifier.kind !== SyntaxKind.Decorator) return false;
3362
- const expr = isCallExpression(modifier.expression) ? modifier.expression.expression : modifier.expression;
3363
- return isIdentifier(expr) && expr.text === "Event";
3364
- }
3365
- /**
3366
- * Extracts selector(s) from @WebComponent decorator arguments.
3367
- *
3368
- * @example
3369
- * @WebComponent({ selector: 'my-button' })
3370
- * @WebComponent({ selector: ['my-btn', 'button-el'] })
3371
- */
3372
- function extractSelectorsFromDecorator(modifier) {
3373
- try {
3374
- const expr = modifier.expression;
3375
- if (!isCallExpression(expr)) return [];
3376
- const args = expr.arguments;
3377
- if (args.length === 0) return [];
3378
- const arg = args[0];
3379
- if (!arg || !isObjectLiteralExpression(arg)) return [];
3380
- return extractStringOrStringArray(Array.from(arg.properties).find((prop) => isPropertyAssignment(prop) && isIdentifier(prop.name) && prop.name.text === "selector").initializer);
3381
- } catch {
3382
- return [];
3383
- }
3384
- }
3385
- /**
3386
- * Extracts a string or string array value from a TypeScript node.
3387
- */
3388
- function extractStringOrStringArray(node) {
3389
- if (isStringLiteral(node)) return [node.text];
3390
- return isArrayLiteralExpression(node) ? node.elements?.filter((node) => isStringLiteral(node)).map((node) => node.text) : [];
3391
- }
3392
- /**
3393
- * Extracts property metadata from @Property or @Property.required decorator.
3394
- */
3395
- function extractPropertyMetadata(propName, modifier) {
3396
- const metadata = {
3397
- name: propName,
3398
- required: false
3399
- };
3400
- const expr = modifier.expression;
3401
- if (isPropertyAccessExpression(expr)) {
3402
- if (isIdentifier(expr.name) && expr.name.text === "required") metadata.required = true;
3403
- }
3404
- if (isCallExpression(expr)) {
3405
- const args = expr.arguments;
3406
- if (args.length) {
3407
- const arg = args[0];
3408
- if (isObjectLiteralExpression(arg)) {
3409
- const aliasNode = arg.properties?.find((prop) => isPropertyAssignment(prop) && isIdentifier(prop.name) && prop.name.text === "alias");
3410
- if (aliasNode && isStringLiteral(aliasNode.initializer)) metadata.alias = aliasNode.initializer.text;
3411
- }
3412
- }
3413
- }
3414
- return metadata;
3415
- }
3416
- /**
3417
- * Extracts event metadata from @Event decorator.
3418
- */
3419
- function extractEventMetadata(eventName, modifier) {
3420
- return { name: eventName };
3421
- }
3422
- //#endregion
3423
- //#region ../packages/compiler/src/type-checker/type-checker.ts
3424
- /**
3425
- * Generates a single, flat TypeScript function body ("shim") from a
3426
- * template AST, meant only to be fed to the TS compiler / LanguageService
3427
- * for diagnostics — it is never executed and never emitted as real output.
3428
- *
3429
- * This deliberately does NOT mirror the JS code generator's structure:
3430
- *
3431
- * - No variable is declared per HTML element. Element identifiers exist in
3432
- * the JS output purely so runtime code can create/reference the actual
3433
- * DOM node; a type-check expression never references "the element
3434
- * itself" (the DSL has no template-ref syntax), so an `HTMLElement`
3435
- * local would add zero type-checking value.
3436
- * - No control-flow block gets its own function. In the JS output, each
3437
- * `@if`/`@for`/`@switch` becomes a separate function because it needs
3438
- * its own runtime closure over the `Context` chain. The type checker has
3439
- * no runtime at all, so real, nested TypeScript blocks — `if`, `for`,
3440
- * `switch` — give correct scoping and (as a bonus) real control-flow
3441
- * narrowing, for free, with no synthetic machinery.
3442
- *
3443
- * Every AST node turns directly into TypeScript lines, recursively, inside
3444
- * one single `typeCheck()` function.
3445
- */
3446
- var TypeChecker = class {
3447
- _ast;
3448
- _context = new TypeCheckContext();
3449
- _states = {
3450
- [ASTNodeType.Text]: typeCheckTextAndInterpolation,
3451
- [ASTNodeType.Interpolation]: typeCheckTextAndInterpolation,
3452
- [ASTNodeType.Element]: typeCheckElement,
3453
- [ASTNodeType.If]: typeCheckIf,
3454
- [ASTNodeType.For]: typeCheckFor,
3455
- [ASTNodeType.Switch]: typeCheckSwitch,
3456
- [ASTNodeType.Import]: typeCheckImport
3457
- };
3458
- constructor(_ast) {
3459
- this._ast = _ast;
3460
- }
3461
- /**
3462
- * Pre-populates the shared context with component and directive metadata
3463
- * by parsing source files for all `@import` nodes in the AST.
3464
- *
3465
- * Must be awaited before calling `generate()` if metadata-driven
3466
- * validation (e.g. unknown component inputs) is desired.
3467
- *
3468
- * @param baseDir - Absolute path used to resolve relative import paths.
3469
- */
3470
- async populateImportMetadata(baseDir) {
3471
- const importNodes = this._ast.filter((node) => node.type === ASTNodeType.Import);
3472
- await Promise.all(importNodes.flatMap((node) => node.specifiers.filter(({ imported }) => imported !== "*").map(async ({ imported, local }) => {
3473
- const symbolName = imported === "default" ? local : imported;
3474
- const metadata = await extractComponentMetadata(node.path, symbolName, baseDir);
3475
- if (metadata) this._context.addImport(metadata);
3476
- })));
3477
- }
3478
- /**
3479
- * Generates the full `function typeCheck() { ... }` shim body for the
3480
- * component's template.
3481
- *
3482
- * A `let $event!: Event;` declaration is prepended only if the generated
3483
- * body actually references `$event` (event handler bindings), so shims
3484
- * for templates with no event bindings don't carry an unused local —
3485
- * relevant if the consuming project has `noUnusedLocals` enabled.
3486
- *
3487
- * @param baseDir - Absolute path used to resolve relative import paths
3488
- */
3489
- generate(baseDir) {
3490
- this.populateImportMetadata(baseDir);
3491
- return [
3492
- "function typeCheck() {",
3493
- ...indent(this._ast.flatMap((node) => this._processNode(node, this._context))),
3494
- "}"
3495
- ].join("\n");
3496
- }
3497
- /**
3498
- * Dispatches a single AST node to its state function, passing itself
3499
- * back down as `processNode` so state functions can recurse into their
3500
- * own children inline.
3501
- */
3502
- _processNode = (node, context) => {
3503
- const state = this._states[node.type];
3504
- if (!state) throw new Error(`[Type Checker] No transition function for token type ${ASTNodeType[node.type]}`);
3505
- return state(node, this._processNode, context);
3506
- };
3507
- };
3508
- //#endregion
3509
- //#region ../packages/compiler/src/compile.ts
3510
- /**
3511
- * Compiles a template string into a Javascript render function body.
3512
- *
3513
- * Runs the three-stage pipeline:
3514
- * 1. **Lexer** — tokenises the raw template text.
3515
- * 2. **Parser** — transforms the token stream into an AST.
3516
- * 3. **Render generator** — emits Javascript source lines from the AST.
3517
- *
3518
- * @param input - The raw HTML-like template source to compile.
3519
- * @param cssVariableName - Optional name of the CSS variable to inject
3520
- * into the generated `adoptedStyleSheets` assignment.
3521
- * @returns A string containing the compiled Javascript render method body.
3522
- */
3523
- function compile(input, cssVariableName) {
3524
- const nodes = new Parser(new Lexer(input).tokenize()).parse();
3525
- return {
3526
- javascript: new Generator(nodes).generate(cssVariableName),
3527
- typescript: new TypeChecker(nodes).generate()
3528
- };
3529
- }
3530
- //#endregion
3531
- export { compile };