@lithent/lithent-template-parser 0.1.0

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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/codegen/createElement.d.ts +10 -0
  4. package/dist/codegen/index.d.ts +1 -0
  5. package/dist/compiler.d.ts +26 -0
  6. package/dist/docPipe/index.d.ts +40 -0
  7. package/dist/docPipe/scan.d.ts +6 -0
  8. package/dist/index.d.ts +13 -0
  9. package/dist/index.mjs +2302 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/index.umd.cjs +13 -0
  12. package/dist/index.umd.cjs.map +1 -0
  13. package/dist/parser/ast.d.ts +207 -0
  14. package/dist/parser/lexer.d.ts +109 -0
  15. package/dist/parser/parser.d.ts +79 -0
  16. package/dist/parser/tokens.d.ts +51 -0
  17. package/dist/transform/directives.d.ts +30 -0
  18. package/dist/transform/index.d.ts +14 -0
  19. package/dist/transform/optimize.d.ts +21 -0
  20. package/package.json +53 -0
  21. package/src/codegen/createElement.ts +661 -0
  22. package/src/codegen/index.ts +1 -0
  23. package/src/compiler.ts +70 -0
  24. package/src/docPipe/index.ts +104 -0
  25. package/src/docPipe/scan.ts +318 -0
  26. package/src/index.ts +16 -0
  27. package/src/parser/ast.ts +478 -0
  28. package/src/parser/lexer.ts +561 -0
  29. package/src/parser/parser.ts +514 -0
  30. package/src/parser/tokens.ts +84 -0
  31. package/src/playground/main.ts +206 -0
  32. package/src/playground/style.css +135 -0
  33. package/src/shims/babel-env.ts +17 -0
  34. package/src/tests/compiler.test.ts +434 -0
  35. package/src/tests/docPipe.test.ts +41 -0
  36. package/src/tests/helpers.ts +96 -0
  37. package/src/tests/lexer.test.ts +282 -0
  38. package/src/tests/parser.test.ts +691 -0
  39. package/src/transform/directives.ts +160 -0
  40. package/src/transform/index.ts +29 -0
  41. package/src/transform/optimize.ts +203 -0
@@ -0,0 +1,478 @@
1
+ import { Position } from './tokens';
2
+
3
+ /**
4
+ * Base AST Node interface
5
+ */
6
+ export interface BaseNode {
7
+ type: string;
8
+ start: Position;
9
+ end: Position;
10
+ }
11
+
12
+ /**
13
+ * Node types
14
+ */
15
+ export enum NodeType {
16
+ ROOT = 'Root',
17
+ ELEMENT = 'Element',
18
+ FRAGMENT = 'Fragment',
19
+ TEXT = 'Text',
20
+ INTERPOLATION = 'Interpolation',
21
+ COMMENT = 'Comment',
22
+ ATTRIBUTE = 'Attribute',
23
+ DIRECTIVE_IF = 'DirectiveIf',
24
+ DIRECTIVE_ELSE_IF = 'DirectiveElseIf',
25
+ DIRECTIVE_ELSE = 'DirectiveElse',
26
+ DIRECTIVE_FOR = 'DirectiveFor',
27
+ }
28
+
29
+ /**
30
+ * Root node - represents the entire template
31
+ */
32
+ export interface RootNode extends BaseNode {
33
+ type: NodeType.ROOT;
34
+ children: TemplateNode[];
35
+ }
36
+
37
+ /**
38
+ * Element node - represents HTML element or component
39
+ */
40
+ export interface ElementNode extends BaseNode {
41
+ type: NodeType.ELEMENT;
42
+ tag: string;
43
+ isComponent: boolean; // true if tag starts with uppercase
44
+ isSelfClosing: boolean;
45
+ attributes: AttributeNode[];
46
+ directives: DirectiveNode[];
47
+ children: TemplateNode[];
48
+ ref?: string; // ref attribute value
49
+ }
50
+
51
+ /**
52
+ * Fragment node - represents <></>
53
+ */
54
+ export interface FragmentNode extends BaseNode {
55
+ type: NodeType.FRAGMENT;
56
+ children: TemplateNode[];
57
+ }
58
+
59
+ /**
60
+ * Text node - represents plain text content
61
+ */
62
+ export interface TextNode extends BaseNode {
63
+ type: NodeType.TEXT;
64
+ content: string;
65
+ }
66
+
67
+ /**
68
+ * Interpolation node - represents {expression}
69
+ */
70
+ export interface InterpolationNode extends BaseNode {
71
+ type: NodeType.INTERPOLATION;
72
+ expression: string;
73
+ }
74
+
75
+ /**
76
+ * Comment node - represents HTML comments
77
+ */
78
+ export interface CommentNode extends BaseNode {
79
+ type: NodeType.COMMENT;
80
+ content: string;
81
+ }
82
+
83
+ /**
84
+ * Attribute node - represents element attributes
85
+ */
86
+ export interface AttributeNode extends BaseNode {
87
+ type: NodeType.ATTRIBUTE;
88
+ name: string;
89
+ value: AttributeValue | null;
90
+ isDynamic: boolean; // true if value is an expression
91
+ }
92
+
93
+ /**
94
+ * Attribute value can be static string or expression
95
+ */
96
+ export type AttributeValue = string | { expression: string };
97
+
98
+ /**
99
+ * Base directive node
100
+ */
101
+ export interface BaseDirectiveNode extends BaseNode {
102
+ type:
103
+ | NodeType.DIRECTIVE_IF
104
+ | NodeType.DIRECTIVE_ELSE_IF
105
+ | NodeType.DIRECTIVE_ELSE
106
+ | NodeType.DIRECTIVE_FOR;
107
+ }
108
+
109
+ /**
110
+ * l-if directive
111
+ */
112
+ export interface DirectiveIfNode extends BaseDirectiveNode {
113
+ type: NodeType.DIRECTIVE_IF;
114
+ condition: string;
115
+ }
116
+
117
+ /**
118
+ * l-else-if directive
119
+ */
120
+ export interface DirectiveElseIfNode extends BaseDirectiveNode {
121
+ type: NodeType.DIRECTIVE_ELSE_IF;
122
+ condition: string;
123
+ }
124
+
125
+ /**
126
+ * l-else directive
127
+ */
128
+ export interface DirectiveElseNode extends BaseDirectiveNode {
129
+ type: NodeType.DIRECTIVE_ELSE;
130
+ }
131
+
132
+ /**
133
+ * l-for directive
134
+ */
135
+ export interface DirectiveForNode extends BaseDirectiveNode {
136
+ type: NodeType.DIRECTIVE_FOR;
137
+ item: string; // loop variable name (e.g., "item")
138
+ index?: string; // optional index variable name (e.g., "index")
139
+ list: string; // expression for the list to iterate
140
+ }
141
+
142
+ /**
143
+ * Union type for all directive nodes
144
+ */
145
+ export type DirectiveNode =
146
+ | DirectiveIfNode
147
+ | DirectiveElseIfNode
148
+ | DirectiveElseNode
149
+ | DirectiveForNode;
150
+
151
+ /**
152
+ * Union type for all template nodes (excluding Root)
153
+ */
154
+ export type TemplateNode =
155
+ | ElementNode
156
+ | FragmentNode
157
+ | TextNode
158
+ | InterpolationNode
159
+ | CommentNode;
160
+
161
+ /**
162
+ * Type guard for Element nodes
163
+ */
164
+ export function isElementNode(node: TemplateNode): node is ElementNode {
165
+ return node.type === NodeType.ELEMENT;
166
+ }
167
+
168
+ /**
169
+ * Type guard for Fragment nodes
170
+ */
171
+ export function isFragmentNode(node: TemplateNode): node is FragmentNode {
172
+ return node.type === NodeType.FRAGMENT;
173
+ }
174
+
175
+ /**
176
+ * Type guard for Text nodes
177
+ */
178
+ export function isTextNode(node: TemplateNode): node is TextNode {
179
+ return node.type === NodeType.TEXT;
180
+ }
181
+
182
+ /**
183
+ * Type guard for Interpolation nodes
184
+ */
185
+ export function isInterpolationNode(
186
+ node: TemplateNode
187
+ ): node is InterpolationNode {
188
+ return node.type === NodeType.INTERPOLATION;
189
+ }
190
+
191
+ /**
192
+ * Type guard for Comment nodes
193
+ */
194
+ export function isCommentNode(node: TemplateNode): node is CommentNode {
195
+ return node.type === NodeType.COMMENT;
196
+ }
197
+
198
+ /**
199
+ * Type guard for directive nodes
200
+ */
201
+ export function isDirective(
202
+ node: AttributeNode | DirectiveNode
203
+ ): node is DirectiveNode {
204
+ return (
205
+ node.type === NodeType.DIRECTIVE_IF ||
206
+ node.type === NodeType.DIRECTIVE_ELSE_IF ||
207
+ node.type === NodeType.DIRECTIVE_ELSE ||
208
+ node.type === NodeType.DIRECTIVE_FOR
209
+ );
210
+ }
211
+
212
+ /**
213
+ * Create a root node
214
+ */
215
+ export function createRootNode(
216
+ children: TemplateNode[],
217
+ start: Position,
218
+ end: Position
219
+ ): RootNode {
220
+ return {
221
+ type: NodeType.ROOT,
222
+ children,
223
+ start,
224
+ end,
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Create an element node
230
+ */
231
+ export function createElementNode(
232
+ tag: string,
233
+ attributes: AttributeNode[],
234
+ directives: DirectiveNode[],
235
+ children: TemplateNode[],
236
+ isSelfClosing: boolean,
237
+ start: Position,
238
+ end: Position
239
+ ): ElementNode {
240
+ const isComponent = /^[A-Z]/.test(tag) || tag.includes('.');
241
+
242
+ // Extract ref from attributes
243
+ const refAttr = attributes.find(attr => attr.name === 'ref');
244
+ const ref = refAttr?.value
245
+ ? typeof refAttr.value === 'string'
246
+ ? refAttr.value
247
+ : refAttr.value.expression
248
+ : undefined;
249
+
250
+ return {
251
+ type: NodeType.ELEMENT,
252
+ tag,
253
+ isComponent,
254
+ isSelfClosing,
255
+ attributes,
256
+ directives,
257
+ children,
258
+ ref,
259
+ start,
260
+ end,
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Create a fragment node
266
+ */
267
+ export function createFragmentNode(
268
+ children: TemplateNode[],
269
+ start: Position,
270
+ end: Position
271
+ ): FragmentNode {
272
+ return {
273
+ type: NodeType.FRAGMENT,
274
+ children,
275
+ start,
276
+ end,
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Create a text node
282
+ */
283
+ export function createTextNode(
284
+ content: string,
285
+ start: Position,
286
+ end: Position
287
+ ): TextNode {
288
+ return {
289
+ type: NodeType.TEXT,
290
+ content,
291
+ start,
292
+ end,
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Create an interpolation node
298
+ */
299
+ export function createInterpolationNode(
300
+ expression: string,
301
+ start: Position,
302
+ end: Position
303
+ ): InterpolationNode {
304
+ return {
305
+ type: NodeType.INTERPOLATION,
306
+ expression,
307
+ start,
308
+ end,
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Create a comment node
314
+ */
315
+ export function createCommentNode(
316
+ content: string,
317
+ start: Position,
318
+ end: Position
319
+ ): CommentNode {
320
+ return {
321
+ type: NodeType.COMMENT,
322
+ content,
323
+ start,
324
+ end,
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Create an attribute node
330
+ */
331
+ export function createAttributeNode(
332
+ name: string,
333
+ value: AttributeValue | null,
334
+ isDynamic: boolean,
335
+ start: Position,
336
+ end: Position
337
+ ): AttributeNode {
338
+ return {
339
+ type: NodeType.ATTRIBUTE,
340
+ name,
341
+ value,
342
+ isDynamic,
343
+ start,
344
+ end,
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Create a l-if directive node
350
+ */
351
+ export function createDirectiveIfNode(
352
+ condition: string,
353
+ start: Position,
354
+ end: Position
355
+ ): DirectiveIfNode {
356
+ return {
357
+ type: NodeType.DIRECTIVE_IF,
358
+ condition,
359
+ start,
360
+ end,
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Create a l-else-if directive node
366
+ */
367
+ export function createDirectiveElseIfNode(
368
+ condition: string,
369
+ start: Position,
370
+ end: Position
371
+ ): DirectiveElseIfNode {
372
+ return {
373
+ type: NodeType.DIRECTIVE_ELSE_IF,
374
+ condition,
375
+ start,
376
+ end,
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Create a l-else directive node
382
+ */
383
+ export function createDirectiveElseNode(
384
+ start: Position,
385
+ end: Position
386
+ ): DirectiveElseNode {
387
+ return {
388
+ type: NodeType.DIRECTIVE_ELSE,
389
+ start,
390
+ end,
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Create a l-for directive node
396
+ */
397
+ export function createDirectiveForNode(
398
+ item: string,
399
+ list: string,
400
+ index: string | undefined,
401
+ start: Position,
402
+ end: Position
403
+ ): DirectiveForNode {
404
+ return {
405
+ type: NodeType.DIRECTIVE_FOR,
406
+ item,
407
+ index,
408
+ list,
409
+ start,
410
+ end,
411
+ };
412
+ }
413
+
414
+ /**
415
+ * Walk through AST and call visitor function for each node
416
+ */
417
+ export function walk(
418
+ node: RootNode | TemplateNode,
419
+ visitor: (node: TemplateNode) => void
420
+ ): void {
421
+ if (node.type === NodeType.ROOT) {
422
+ node.children.forEach(child => {
423
+ visitor(child);
424
+ walk(child, visitor);
425
+ });
426
+ } else if (
427
+ node.type === NodeType.ELEMENT ||
428
+ node.type === NodeType.FRAGMENT
429
+ ) {
430
+ node.children.forEach(child => {
431
+ visitor(child);
432
+ walk(child, visitor);
433
+ });
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Transform AST by applying a transformer function to each node
439
+ */
440
+ export function transformTree(
441
+ node: RootNode | TemplateNode,
442
+ transformer: (node: TemplateNode) => TemplateNode
443
+ ): RootNode | TemplateNode {
444
+ if (node.type === NodeType.ROOT) {
445
+ return {
446
+ ...node,
447
+ children: node.children.map(child => {
448
+ const transformed = transformer(child);
449
+ return transformTree(transformed, transformer) as TemplateNode;
450
+ }),
451
+ };
452
+ } else if (node.type === NodeType.ELEMENT) {
453
+ const transformed = transformer(node);
454
+ if (transformed.type === NodeType.ELEMENT) {
455
+ return {
456
+ ...transformed,
457
+ children: transformed.children.map(child => {
458
+ const childTransformed = transformer(child);
459
+ return transformTree(childTransformed, transformer) as TemplateNode;
460
+ }),
461
+ };
462
+ }
463
+ return transformed;
464
+ } else if (node.type === NodeType.FRAGMENT) {
465
+ const transformed = transformer(node);
466
+ if (transformed.type === NodeType.FRAGMENT) {
467
+ return {
468
+ ...transformed,
469
+ children: transformed.children.map(child => {
470
+ const childTransformed = transformer(child);
471
+ return transformTree(childTransformed, transformer) as TemplateNode;
472
+ }),
473
+ };
474
+ }
475
+ return transformed;
476
+ }
477
+ return transformer(node);
478
+ }