@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.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/codegen/createElement.d.ts +10 -0
- package/dist/codegen/index.d.ts +1 -0
- package/dist/compiler.d.ts +26 -0
- package/dist/docPipe/index.d.ts +40 -0
- package/dist/docPipe/scan.d.ts +6 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.mjs +2302 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.cjs +13 -0
- package/dist/index.umd.cjs.map +1 -0
- package/dist/parser/ast.d.ts +207 -0
- package/dist/parser/lexer.d.ts +109 -0
- package/dist/parser/parser.d.ts +79 -0
- package/dist/parser/tokens.d.ts +51 -0
- package/dist/transform/directives.d.ts +30 -0
- package/dist/transform/index.d.ts +14 -0
- package/dist/transform/optimize.d.ts +21 -0
- package/package.json +53 -0
- package/src/codegen/createElement.ts +661 -0
- package/src/codegen/index.ts +1 -0
- package/src/compiler.ts +70 -0
- package/src/docPipe/index.ts +104 -0
- package/src/docPipe/scan.ts +318 -0
- package/src/index.ts +16 -0
- package/src/parser/ast.ts +478 -0
- package/src/parser/lexer.ts +561 -0
- package/src/parser/parser.ts +514 -0
- package/src/parser/tokens.ts +84 -0
- package/src/playground/main.ts +206 -0
- package/src/playground/style.css +135 -0
- package/src/shims/babel-env.ts +17 -0
- package/src/tests/compiler.test.ts +434 -0
- package/src/tests/docPipe.test.ts +41 -0
- package/src/tests/helpers.ts +96 -0
- package/src/tests/lexer.test.ts +282 -0
- package/src/tests/parser.test.ts +691 -0
- package/src/transform/directives.ts +160 -0
- package/src/transform/index.ts +29 -0
- 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
|
+
}
|