@frontiers-labs/argon 0.4.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/annotations.d.ts +23 -0
  4. package/dist/annotations.d.ts.map +1 -0
  5. package/dist/annotations.js +23 -0
  6. package/dist/annotations.js.map +1 -0
  7. package/dist/codegen/client.d.ts +14 -0
  8. package/dist/codegen/client.d.ts.map +1 -0
  9. package/dist/codegen/client.js +964 -0
  10. package/dist/codegen/client.js.map +1 -0
  11. package/dist/codegen/dts.d.ts +3 -0
  12. package/dist/codegen/dts.d.ts.map +1 -0
  13. package/dist/codegen/dts.js +82 -0
  14. package/dist/codegen/dts.js.map +1 -0
  15. package/dist/codegen/js.d.ts +3 -0
  16. package/dist/codegen/js.d.ts.map +1 -0
  17. package/dist/codegen/js.js +278 -0
  18. package/dist/codegen/js.js.map +1 -0
  19. package/dist/codegen/rust.d.ts +6 -0
  20. package/dist/codegen/rust.d.ts.map +1 -0
  21. package/dist/codegen/rust.js +1076 -0
  22. package/dist/codegen/rust.js.map +1 -0
  23. package/dist/component.d.ts +35 -0
  24. package/dist/component.d.ts.map +1 -0
  25. package/dist/component.js +120 -0
  26. package/dist/component.js.map +1 -0
  27. package/dist/diagnostics.d.ts +20 -0
  28. package/dist/diagnostics.d.ts.map +1 -0
  29. package/dist/diagnostics.js +40 -0
  30. package/dist/diagnostics.js.map +1 -0
  31. package/dist/docs.d.ts +3 -0
  32. package/dist/docs.d.ts.map +1 -0
  33. package/dist/docs.js +42 -0
  34. package/dist/docs.js.map +1 -0
  35. package/dist/error-catalog.d.ts +11 -0
  36. package/dist/error-catalog.d.ts.map +1 -0
  37. package/dist/error-catalog.js +92 -0
  38. package/dist/error-catalog.js.map +1 -0
  39. package/dist/html-attrs.d.ts +2 -0
  40. package/dist/html-attrs.d.ts.map +1 -0
  41. package/dist/html-attrs.js +37 -0
  42. package/dist/html-attrs.js.map +1 -0
  43. package/dist/html.d.ts +41 -0
  44. package/dist/html.d.ts.map +1 -0
  45. package/dist/html.js +188 -0
  46. package/dist/html.js.map +1 -0
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +139 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/ir.d.ts +321 -0
  52. package/dist/ir.d.ts.map +1 -0
  53. package/dist/ir.js +243 -0
  54. package/dist/ir.js.map +1 -0
  55. package/dist/jsx.d.ts +31 -0
  56. package/dist/jsx.d.ts.map +1 -0
  57. package/dist/jsx.js +242 -0
  58. package/dist/jsx.js.map +1 -0
  59. package/dist/lower.d.ts +6 -0
  60. package/dist/lower.d.ts.map +1 -0
  61. package/dist/lower.js +1341 -0
  62. package/dist/lower.js.map +1 -0
  63. package/dist/parser.d.ts +3 -0
  64. package/dist/parser.d.ts.map +1 -0
  65. package/dist/parser.js +714 -0
  66. package/dist/parser.js.map +1 -0
  67. package/dist/ssr-subset.d.ts +29 -0
  68. package/dist/ssr-subset.d.ts.map +1 -0
  69. package/dist/ssr-subset.js +119 -0
  70. package/dist/ssr-subset.js.map +1 -0
  71. package/dist/template.d.ts +32 -0
  72. package/dist/template.d.ts.map +1 -0
  73. package/dist/template.js +90 -0
  74. package/dist/template.js.map +1 -0
  75. package/package.json +67 -0
package/dist/parser.js ADDED
@@ -0,0 +1,714 @@
1
+ import ts from 'typescript';
2
+ class ParseError extends Error {
3
+ node;
4
+ constructor(msg, node) {
5
+ super(msg);
6
+ this.node = node;
7
+ }
8
+ }
9
+ // ── Entry point ────────────────────────────────────────────────────────────────
10
+ export function parseModule(source, fileName) {
11
+ const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true);
12
+ const interfaces = [];
13
+ const dataConsts = [];
14
+ const functions = [];
15
+ const jsFunctions = [];
16
+ let component = null;
17
+ let stylesConstName = null;
18
+ let stylesConstText = '';
19
+ let functionComponent = null;
20
+ for (const stmt of sf.statements) {
21
+ if (ts.isImportDeclaration(stmt))
22
+ continue;
23
+ if (ts.isInterfaceDeclaration(stmt)) {
24
+ interfaces.push(parseInterface(stmt, source));
25
+ continue;
26
+ }
27
+ if (ts.isVariableStatement(stmt)) {
28
+ for (const decl of stmt.declarationList.declarations) {
29
+ if (!decl.initializer)
30
+ continue;
31
+ if (ts.isObjectLiteralExpression(decl.initializer)) {
32
+ const name = decl.name.text;
33
+ dataConsts.push({ name, value: parseLiteral(decl.initializer) });
34
+ }
35
+ // Capture top-level styles const: const styles = css`...`
36
+ if (ts.isTaggedTemplateExpression(decl.initializer) ||
37
+ ts.isNoSubstitutionTemplateLiteral(decl.initializer)) {
38
+ const name = ts.isIdentifier(decl.name) ? decl.name.text : '';
39
+ if (name) {
40
+ stylesConstName = name;
41
+ stylesConstText = extractTemplateText(decl.initializer);
42
+ }
43
+ }
44
+ }
45
+ continue;
46
+ }
47
+ if (ts.isFunctionDeclaration(stmt) && stmt.name) {
48
+ const tagName = extractComponentTagName(stmt);
49
+ if (tagName) {
50
+ functionComponent = { fn: stmt, tagName };
51
+ }
52
+ else if (hasDomParams(stmt)) {
53
+ // Functions with DOM-typed params are JS-only — skip IR parsing entirely.
54
+ jsFunctions.push({ name: stmt.name.text, source: source.slice(stmt.pos, stmt.end).trim() });
55
+ }
56
+ else {
57
+ try {
58
+ functions.push(parseFunction(stmt, source));
59
+ }
60
+ catch {
61
+ jsFunctions.push({ name: stmt.name.text, source: source.slice(stmt.pos, stmt.end).trim() });
62
+ }
63
+ }
64
+ continue;
65
+ }
66
+ if (ts.isClassDeclaration(stmt)) {
67
+ const tagName = extractComponentTagName(stmt);
68
+ if (tagName) {
69
+ component = parseClass(stmt, tagName, source, sf);
70
+ }
71
+ continue;
72
+ }
73
+ }
74
+ if (!component && functionComponent) {
75
+ component = parseFunctionComponent(functionComponent.fn, functionComponent.tagName, stylesConstName, stylesConstText, source);
76
+ }
77
+ if (!component)
78
+ throw new ParseError('No component found (expected @component class or exported PascalCase function)');
79
+ return { sourceFile: fileName, interfaces, dataConsts, functions, jsFunctions, component };
80
+ }
81
+ // ── Decorator ──────────────────────────────────────────────────────────────────
82
+ function extractComponentTagName(cls) {
83
+ // ts.canHaveDecorators returns false for function declarations even though the
84
+ // parser captures decorators in modifiers. Read them directly.
85
+ const mods = ts.canHaveDecorators(cls)
86
+ ? ts.getDecorators(cls) ?? []
87
+ : cls.modifiers ?? [];
88
+ for (const mod of mods) {
89
+ if (mod.kind !== ts.SyntaxKind.Decorator)
90
+ continue;
91
+ const dec = mod;
92
+ if (ts.isCallExpression(dec.expression) &&
93
+ ts.isIdentifier(dec.expression.expression) &&
94
+ dec.expression.expression.text === 'component' &&
95
+ dec.expression.arguments.length >= 1) {
96
+ const arg = dec.expression.arguments[0];
97
+ if (ts.isStringLiteral(arg))
98
+ return arg.text;
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+ // ── Interface ──────────────────────────────────────────────────────────────────
104
+ function parseInterface(node, _src) {
105
+ const fields = [];
106
+ for (const member of node.members) {
107
+ if (ts.isPropertySignature(member) && member.name) {
108
+ fields.push({
109
+ name: member.name.text,
110
+ type: member.type ? parseType(member.type) : { kind: 'unknown' },
111
+ optional: !!member.questionToken,
112
+ });
113
+ }
114
+ }
115
+ return { name: node.name.text, fields };
116
+ }
117
+ // ── Data const ────────────────────────────────────────────────────────────────
118
+ function parseLiteral(node) {
119
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
120
+ return { kind: 'string', value: node.text };
121
+ }
122
+ if (ts.isNumericLiteral(node)) {
123
+ return { kind: 'number', value: Number(node.text) };
124
+ }
125
+ if (node.kind === ts.SyntaxKind.TrueKeyword)
126
+ return { kind: 'boolean', value: true };
127
+ if (node.kind === ts.SyntaxKind.FalseKeyword)
128
+ return { kind: 'boolean', value: false };
129
+ // Negative number: -42
130
+ if (ts.isPrefixUnaryExpression(node) &&
131
+ node.operator === ts.SyntaxKind.MinusToken &&
132
+ ts.isNumericLiteral(node.operand)) {
133
+ return { kind: 'number', value: -Number(node.operand.text) };
134
+ }
135
+ if (ts.isArrayLiteralExpression(node)) {
136
+ return { kind: 'array', elements: node.elements.map(parseLiteral) };
137
+ }
138
+ if (ts.isObjectLiteralExpression(node)) {
139
+ const properties = [];
140
+ for (const prop of node.properties) {
141
+ if (ts.isPropertyAssignment(prop) && prop.initializer) {
142
+ const key = ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)
143
+ ? prop.name.text
144
+ : '';
145
+ if (key)
146
+ properties.push({ key, value: parseLiteral(prop.initializer) });
147
+ }
148
+ }
149
+ return { kind: 'object', properties };
150
+ }
151
+ // Template literal used as a string value (e.g. inside object)
152
+ if (ts.isAsExpression(node))
153
+ return parseLiteral(node.expression);
154
+ throw new ParseError(`Unsupported literal node: ${ts.SyntaxKind[node.kind]}`, node);
155
+ }
156
+ // ── Type ───────────────────────────────────────────────────────────────────────
157
+ function parseType(node) {
158
+ if (ts.isTypeReferenceNode(node)) {
159
+ const name = ts.isIdentifier(node.typeName) ? node.typeName.text : '';
160
+ if (name === 'Record' && node.typeArguments?.length === 2) {
161
+ return { kind: 'record', key: parseType(node.typeArguments[0]), value: parseType(node.typeArguments[1]) };
162
+ }
163
+ if (name === 'Array' && node.typeArguments?.length === 1) {
164
+ return { kind: 'array', element: parseType(node.typeArguments[0]) };
165
+ }
166
+ return { kind: 'named', name };
167
+ }
168
+ if (ts.isArrayTypeNode(node)) {
169
+ return { kind: 'array', element: parseType(node.elementType) };
170
+ }
171
+ if (ts.isTupleTypeNode(node)) {
172
+ return { kind: 'tuple', elements: node.elements.map(e => parseType(e)) };
173
+ }
174
+ if (ts.isTypeLiteralNode(node)) {
175
+ const fields = [];
176
+ for (const m of node.members) {
177
+ if (ts.isPropertySignature(m) && m.name) {
178
+ fields.push({
179
+ name: m.name.text,
180
+ type: m.type ? parseType(m.type) : { kind: 'unknown' },
181
+ optional: !!m.questionToken,
182
+ });
183
+ }
184
+ }
185
+ return { kind: 'object', fields };
186
+ }
187
+ switch (node.kind) {
188
+ case ts.SyntaxKind.StringKeyword: return { kind: 'string' };
189
+ case ts.SyntaxKind.NumberKeyword: return { kind: 'number' };
190
+ case ts.SyntaxKind.BooleanKeyword: return { kind: 'boolean' };
191
+ case ts.SyntaxKind.VoidKeyword: return { kind: 'void' };
192
+ default: return { kind: 'unknown' };
193
+ }
194
+ }
195
+ // ── Function ───────────────────────────────────────────────────────────────────
196
+ function parseFunction(node, source) {
197
+ const name = node.name.text;
198
+ const params = (node.parameters ?? []).map(p => ({
199
+ name: ts.isIdentifier(p.name) ? p.name.text : '?',
200
+ type: p.type ? parseType(p.type) : { kind: 'unknown' },
201
+ }));
202
+ const returnType = node.type ? parseType(node.type) : { kind: 'unknown' };
203
+ const body = node.body
204
+ ? node.body.statements.flatMap(s => parseStatement(s))
205
+ : [];
206
+ const bodySource = (() => {
207
+ if (!node.body)
208
+ return '';
209
+ const bodyText = source.slice(node.body.pos, node.body.end);
210
+ const open = bodyText.indexOf('{');
211
+ const close = bodyText.lastIndexOf('}');
212
+ return open !== -1 ? bodyText.slice(open + 1, close).trim() : '';
213
+ })();
214
+ return { name, params, returnType, bodySource, body };
215
+ }
216
+ // ── Statements ─────────────────────────────────────────────────────────────────
217
+ function parseStatement(node) {
218
+ if (ts.isVariableStatement(node)) {
219
+ return node.declarationList.declarations.flatMap(decl => {
220
+ if (!decl.initializer)
221
+ return [];
222
+ const names = extractBindingNames(decl.name);
223
+ return [{ kind: 'const', names, init: parseExpr(decl.initializer) }];
224
+ });
225
+ }
226
+ if (ts.isReturnStatement(node) && node.expression) {
227
+ return [{ kind: 'return', value: parseExpr(node.expression) }];
228
+ }
229
+ if (ts.isExpressionStatement(node)) {
230
+ return [{ kind: 'expr_stmt', expr: parseExpr(node.expression) }];
231
+ }
232
+ throw new ParseError(`Unsupported statement: ${ts.SyntaxKind[node.kind]}`, node);
233
+ }
234
+ function extractBindingNames(name) {
235
+ if (ts.isIdentifier(name))
236
+ return [name.text];
237
+ if (ts.isArrayBindingPattern(name)) {
238
+ return name.elements.flatMap(el => ts.isBindingElement(el) ? extractBindingNames(el.name) : []);
239
+ }
240
+ if (ts.isObjectBindingPattern(name)) {
241
+ return name.elements.flatMap(el => extractBindingNames(el.name));
242
+ }
243
+ return [];
244
+ }
245
+ // ── Expressions ────────────────────────────────────────────────────────────────
246
+ function parseExpr(node) {
247
+ // Parenthesized expression — unwrap
248
+ if (ts.isParenthesizedExpression(node))
249
+ return parseExpr(node.expression);
250
+ // Type assertion: expr as T — drop the cast
251
+ if (ts.isAsExpression(node))
252
+ return { kind: 'as_expr', expr: parseExpr(node.expression) };
253
+ // Literals
254
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
255
+ return { kind: 'literal', value: { kind: 'string', value: node.text } };
256
+ }
257
+ if (ts.isNumericLiteral(node)) {
258
+ return { kind: 'literal', value: { kind: 'number', value: Number(node.text) } };
259
+ }
260
+ if (node.kind === ts.SyntaxKind.TrueKeyword) {
261
+ return { kind: 'literal', value: { kind: 'boolean', value: true } };
262
+ }
263
+ if (node.kind === ts.SyntaxKind.FalseKeyword) {
264
+ return { kind: 'literal', value: { kind: 'boolean', value: false } };
265
+ }
266
+ // -N
267
+ if (ts.isPrefixUnaryExpression(node) &&
268
+ node.operator === ts.SyntaxKind.MinusToken &&
269
+ ts.isNumericLiteral(node.operand)) {
270
+ return { kind: 'literal', value: { kind: 'number', value: -Number(node.operand.text) } };
271
+ }
272
+ // this.field
273
+ if (ts.isPropertyAccessExpression(node) &&
274
+ node.expression.kind === ts.SyntaxKind.ThisKeyword) {
275
+ return { kind: 'self_field', field: node.name.text };
276
+ }
277
+ // a.b
278
+ if (ts.isPropertyAccessExpression(node)) {
279
+ return {
280
+ kind: 'prop_access',
281
+ object: parseExpr(node.expression),
282
+ property: node.name.text,
283
+ };
284
+ }
285
+ // a[b]
286
+ if (ts.isElementAccessExpression(node)) {
287
+ return {
288
+ kind: 'index',
289
+ object: parseExpr(node.expression),
290
+ index: parseExpr(node.argumentExpression),
291
+ };
292
+ }
293
+ // Identifier
294
+ if (ts.isIdentifier(node))
295
+ return { kind: 'local_ref', name: node.text };
296
+ // Method call: receiver.method(args)
297
+ if (ts.isCallExpression(node) &&
298
+ ts.isPropertyAccessExpression(node.expression)) {
299
+ const pae = node.expression;
300
+ // Check for Math.min / Math.max as standalone calls
301
+ if (ts.isIdentifier(pae.expression) &&
302
+ pae.expression.text === 'Math') {
303
+ const callee = `Math.${pae.name.text}`;
304
+ return { kind: 'call', callee, args: node.arguments.map(parseExpr) };
305
+ }
306
+ return {
307
+ kind: 'method_call',
308
+ receiver: parseExpr(pae.expression),
309
+ method: pae.name.text,
310
+ args: node.arguments.map(parseExpr),
311
+ };
312
+ }
313
+ // Standalone call: fn(args)
314
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
315
+ return {
316
+ kind: 'call',
317
+ callee: node.expression.text,
318
+ args: node.arguments.map(parseExpr),
319
+ };
320
+ }
321
+ // Spread: ...x
322
+ if (ts.isSpreadElement(node)) {
323
+ return { kind: 'spread', expr: parseExpr(node.expression) };
324
+ }
325
+ // Binary: a OP b
326
+ if (ts.isBinaryExpression(node)) {
327
+ const op = node.operatorToken.kind;
328
+ if (op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsToken) {
329
+ return { kind: 'eq', left: parseExpr(node.left), right: parseExpr(node.right) };
330
+ }
331
+ const opStr = binaryOpStr(op);
332
+ return { kind: 'binary', op: opStr, left: parseExpr(node.left), right: parseExpr(node.right) };
333
+ }
334
+ // Ternary: a ? b : c
335
+ if (ts.isConditionalExpression(node)) {
336
+ return {
337
+ kind: 'ternary',
338
+ condition: parseExpr(node.condition),
339
+ then: parseExpr(node.whenTrue),
340
+ else: parseExpr(node.whenFalse),
341
+ };
342
+ }
343
+ // Arrow function: (params) => body
344
+ if (ts.isArrowFunction(node)) {
345
+ const params = node.parameters.map((p, i) => {
346
+ if (ts.isArrayBindingPattern(p.name)) {
347
+ const destructured = p.name.elements.flatMap(el => ts.isBindingElement(el) && ts.isIdentifier(el.name) ? [el.name.text] : []);
348
+ return { name: `_arg${i}`, destructured };
349
+ }
350
+ const name = ts.isIdentifier(p.name) ? p.name.text : `_arg${i}`;
351
+ return { name };
352
+ });
353
+ if (ts.isBlock(node.body)) {
354
+ const body = node.body.statements.flatMap(parseStatement);
355
+ return { kind: 'arrow_fn', params, body };
356
+ }
357
+ return { kind: 'arrow_fn', params, body: parseExpr(node.body) };
358
+ }
359
+ // Array literal
360
+ if (ts.isArrayLiteralExpression(node)) {
361
+ return { kind: 'array_literal', elements: node.elements.map(parseExpr) };
362
+ }
363
+ // Object literal
364
+ if (ts.isObjectLiteralExpression(node)) {
365
+ const properties = [];
366
+ for (const prop of node.properties) {
367
+ if (ts.isPropertyAssignment(prop) && prop.initializer) {
368
+ const key = ts.isIdentifier(prop.name) ? prop.name.text
369
+ : ts.isStringLiteral(prop.name) ? prop.name.text : '';
370
+ if (key)
371
+ properties.push({ key, value: parseExpr(prop.initializer) });
372
+ }
373
+ if (ts.isShorthandPropertyAssignment(prop)) {
374
+ const key = prop.name.text;
375
+ properties.push({ key, value: { kind: 'local_ref', name: key } });
376
+ }
377
+ }
378
+ return { kind: 'object_literal', properties };
379
+ }
380
+ // Template expression (non-tagged): `text ${expr} text`
381
+ if (ts.isTemplateExpression(node)) {
382
+ const parts = [{ kind: 'static', text: node.head.text }];
383
+ for (const span of node.templateSpans) {
384
+ parts.push({ kind: 'expr', expr: parseExpr(span.expression) });
385
+ parts.push({ kind: 'static', text: span.literal.text });
386
+ }
387
+ return { kind: 'template_str', parts };
388
+ }
389
+ throw new ParseError(`Unsupported expression: ${ts.SyntaxKind[node.kind]} — ` +
390
+ `add support in parseExpr() for this pattern`, node);
391
+ }
392
+ function binaryOpStr(kind) {
393
+ switch (kind) {
394
+ case ts.SyntaxKind.PlusToken: return '+';
395
+ case ts.SyntaxKind.MinusToken: return '-';
396
+ case ts.SyntaxKind.AsteriskToken: return '*';
397
+ case ts.SyntaxKind.SlashToken: return '/';
398
+ case ts.SyntaxKind.PercentToken: return '%';
399
+ case ts.SyntaxKind.BarBarToken: return '||';
400
+ case ts.SyntaxKind.AmpersandAmpersandToken: return '&&';
401
+ case ts.SyntaxKind.LessThanToken: return '<';
402
+ case ts.SyntaxKind.GreaterThanToken: return '>';
403
+ case ts.SyntaxKind.LessThanEqualsToken: return '<=';
404
+ case ts.SyntaxKind.GreaterThanEqualsToken: return '>=';
405
+ case ts.SyntaxKind.ExclamationEqualsEqualsToken: return '!==';
406
+ default: throw new ParseError(`Unsupported binary operator: ${ts.SyntaxKind[kind]}`);
407
+ }
408
+ }
409
+ // ── Class ──────────────────────────────────────────────────────────────────────
410
+ function parseClass(cls, tagName, source, sf) {
411
+ const className = cls.name?.text ?? 'UnknownComponent';
412
+ const props = [];
413
+ let styles = '';
414
+ let renderBindings = [];
415
+ let renderTemplate = [];
416
+ let eventBindings = [];
417
+ let renderBodySource = '';
418
+ let hydrateBodySource = '';
419
+ const privateMethods = [];
420
+ const printer = ts.createPrinter({ removeComments: false });
421
+ for (const member of cls.members) {
422
+ // Static readonly styles
423
+ if (ts.isPropertyDeclaration(member) &&
424
+ hasModifier(member, ts.SyntaxKind.StaticKeyword) &&
425
+ ts.isIdentifier(member.name) &&
426
+ member.name.text === 'styles' &&
427
+ member.initializer) {
428
+ styles = extractTemplateText(member.initializer);
429
+ continue;
430
+ }
431
+ // Private fields (#period, etc.) — skip; not needed in IR
432
+ if (ts.isPropertyDeclaration(member) && ts.isPrivateIdentifier(member.name)) {
433
+ continue;
434
+ }
435
+ // Instance props (public class fields with defaults)
436
+ if (ts.isPropertyDeclaration(member) &&
437
+ ts.isIdentifier(member.name) &&
438
+ !hasModifier(member, ts.SyntaxKind.StaticKeyword) &&
439
+ member.initializer) {
440
+ props.push({
441
+ name: member.name.text,
442
+ type: member.type ? parseType(member.type) : { kind: 'unknown' },
443
+ defaultValue: parseLiteral(member.initializer),
444
+ });
445
+ continue;
446
+ }
447
+ // render() method
448
+ if (ts.isMethodDeclaration(member) &&
449
+ ts.isIdentifier(member.name) &&
450
+ member.name.text === 'render' &&
451
+ member.body) {
452
+ const parsed = parseRenderBody(member.body, className, source);
453
+ renderBindings = parsed.bindings;
454
+ renderTemplate = parsed.template;
455
+ eventBindings = parsed.eventBindings;
456
+ const renderText = source.slice(member.body.pos, member.body.end);
457
+ const rOpen = renderText.indexOf('{');
458
+ const rClose = renderText.lastIndexOf('}');
459
+ renderBodySource = rOpen !== -1 ? renderText.slice(rOpen + 1, rClose).trim() : '';
460
+ continue;
461
+ }
462
+ // hydrate() method
463
+ if (ts.isMethodDeclaration(member) &&
464
+ ts.isIdentifier(member.name) &&
465
+ member.name.text === 'hydrate' &&
466
+ member.body) {
467
+ const hydrateText = source.slice(member.body.pos, member.body.end);
468
+ const hyOpen = hydrateText.indexOf('{');
469
+ const hyClose = hydrateText.lastIndexOf('}');
470
+ hydrateBodySource = hyOpen !== -1 ? hydrateText.slice(hyOpen + 1, hyClose).trim() : '';
471
+ continue;
472
+ }
473
+ // Private methods (#name)
474
+ if (ts.isMethodDeclaration(member) &&
475
+ ts.isPrivateIdentifier(member.name)) {
476
+ const name = member.name.text.slice(1); // strip leading #
477
+ const methodSrc = printer.printNode(ts.EmitHint.Unspecified, member, sf);
478
+ privateMethods.push({ name, source: methodSrc });
479
+ continue;
480
+ }
481
+ }
482
+ return {
483
+ tagName,
484
+ className,
485
+ props,
486
+ styles,
487
+ renderBindings,
488
+ renderTemplate,
489
+ eventBindings,
490
+ renderBodySource,
491
+ hydrateBodySource,
492
+ privateMethods,
493
+ };
494
+ }
495
+ function parseFunctionComponent(fn, tagName, stylesConstName, stylesText, source) {
496
+ const funcName = fn.name.text;
497
+ const props = [];
498
+ const param = fn.parameters[0];
499
+ if (param && ts.isObjectBindingPattern(param.name)) {
500
+ const typeFields = new Map();
501
+ if (param.type && ts.isTypeLiteralNode(param.type)) {
502
+ for (const member of param.type.members) {
503
+ if (ts.isPropertySignature(member) && member.name && member.type) {
504
+ typeFields.set(member.name.text, parseType(member.type));
505
+ }
506
+ }
507
+ }
508
+ for (const el of param.name.elements) {
509
+ if (!ts.isBindingElement(el) || !ts.isIdentifier(el.name))
510
+ continue;
511
+ const name = el.name.text;
512
+ const type = typeFields.get(name) ?? { kind: 'unknown' };
513
+ const defaultValue = el.initializer
514
+ ? parseLiteral(el.initializer)
515
+ : { kind: 'string', value: '' };
516
+ props.push({ name, type, defaultValue });
517
+ }
518
+ }
519
+ if (!fn.body)
520
+ throw new ParseError('Function component must have a body', fn);
521
+ const { bindings, template, eventBindings } = parseRenderBody(fn.body, funcName, source);
522
+ const propNames = new Set(props.map(p => p.name));
523
+ const stylesNames = new Set(stylesConstName ? [stylesConstName] : []);
524
+ const renderBindings = bindings.map(b => ({
525
+ name: b.name,
526
+ init: rewriteExprRefs(b.init, propNames, stylesNames),
527
+ }));
528
+ const renderTemplate = template.map(node => node.kind === 'expr'
529
+ ? { kind: 'expr', expr: rewriteExprRefs(node.expr, propNames, stylesNames) }
530
+ : node);
531
+ return {
532
+ tagName,
533
+ className: funcName,
534
+ props,
535
+ styles: stylesText,
536
+ renderBindings,
537
+ renderTemplate,
538
+ eventBindings,
539
+ renderBodySource: '',
540
+ hydrateBodySource: '',
541
+ privateMethods: [],
542
+ };
543
+ }
544
+ function rewriteExprRefs(expr, propNames, stylesNames) {
545
+ const rw = (e) => rewriteExprRefs(e, propNames, stylesNames);
546
+ switch (expr.kind) {
547
+ case 'local_ref':
548
+ if (propNames.has(expr.name))
549
+ return { kind: 'self_field', field: expr.name };
550
+ if (stylesNames.has(expr.name))
551
+ return { kind: 'local_ref', name: '__styles__' };
552
+ return expr;
553
+ case 'self_field':
554
+ case 'literal':
555
+ return expr;
556
+ case 'as_expr':
557
+ return { kind: 'as_expr', expr: rw(expr.expr) };
558
+ case 'prop_access':
559
+ return { kind: 'prop_access', object: rw(expr.object), property: expr.property };
560
+ case 'index':
561
+ return { kind: 'index', object: rw(expr.object), index: rw(expr.index) };
562
+ case 'call':
563
+ return { kind: 'call', callee: expr.callee, args: expr.args.map(rw) };
564
+ case 'method_call':
565
+ return { kind: 'method_call', receiver: rw(expr.receiver), method: expr.method, args: expr.args.map(rw) };
566
+ case 'binary':
567
+ return { kind: 'binary', op: expr.op, left: rw(expr.left), right: rw(expr.right) };
568
+ case 'eq':
569
+ return { kind: 'eq', left: rw(expr.left), right: rw(expr.right) };
570
+ case 'ternary':
571
+ return { kind: 'ternary', condition: rw(expr.condition), then: rw(expr.then), else: rw(expr.else) };
572
+ case 'arrow_fn': {
573
+ const body = Array.isArray(expr.body)
574
+ ? expr.body.map(s => rewriteStmtRefs(s, propNames, stylesNames))
575
+ : rw(expr.body);
576
+ return { kind: 'arrow_fn', params: expr.params, body };
577
+ }
578
+ case 'array_literal':
579
+ return { kind: 'array_literal', elements: expr.elements.map(rw) };
580
+ case 'object_literal':
581
+ return { kind: 'object_literal', properties: expr.properties.map(p => ({ key: p.key, value: rw(p.value) })) };
582
+ case 'spread':
583
+ return { kind: 'spread', expr: rw(expr.expr) };
584
+ case 'template_str':
585
+ return {
586
+ kind: 'template_str',
587
+ parts: expr.parts.map(p => p.kind === 'expr' ? { kind: 'expr', expr: rw(p.expr) } : p),
588
+ };
589
+ }
590
+ }
591
+ function rewriteStmtRefs(stmt, propNames, stylesNames) {
592
+ const rw = (e) => rewriteExprRefs(e, propNames, stylesNames);
593
+ if (stmt.kind === 'const')
594
+ return { kind: 'const', names: stmt.names, init: rw(stmt.init) };
595
+ if (stmt.kind === 'return')
596
+ return { kind: 'return', value: rw(stmt.value) };
597
+ return { kind: 'expr_stmt', expr: rw(stmt.expr) };
598
+ }
599
+ const DOM_TYPES = new Set([
600
+ 'Element', 'HTMLElement', 'EventTarget', 'ShadowRoot', 'Document', 'Window', 'Node',
601
+ 'Event', 'MouseEvent', 'KeyboardEvent', 'SubmitEvent', 'InputEvent', 'FocusEvent',
602
+ 'CustomEvent', 'MutationObserver', 'ResizeObserver', 'IntersectionObserver',
603
+ ]);
604
+ function hasDomParams(fn) {
605
+ return fn.parameters.some(p => {
606
+ if (!p.type || !ts.isTypeReferenceNode(p.type) || !ts.isIdentifier(p.type.typeName))
607
+ return false;
608
+ const name = p.type.typeName.text;
609
+ return DOM_TYPES.has(name) || name.startsWith('HTML') || name.startsWith('SVG');
610
+ });
611
+ }
612
+ function hasModifier(node, kind) {
613
+ return !!ts.getModifiers(node)?.some(m => m.kind === kind);
614
+ }
615
+ function extractTemplateText(node) {
616
+ // css`...` or just a template literal
617
+ if (ts.isTaggedTemplateExpression(node)) {
618
+ return extractTemplateText(node.template);
619
+ }
620
+ if (ts.isNoSubstitutionTemplateLiteral(node))
621
+ return node.text;
622
+ if (ts.isStringLiteral(node))
623
+ return node.text;
624
+ return '';
625
+ }
626
+ function parseRenderBody(body, componentClassName, source) {
627
+ const bindings = [];
628
+ let template = [];
629
+ let eventBindings = [];
630
+ for (const stmt of body.statements) {
631
+ if (ts.isVariableStatement(stmt)) {
632
+ for (const decl of stmt.declarationList.declarations) {
633
+ if (!decl.initializer)
634
+ continue;
635
+ const name = ts.isIdentifier(decl.name) ? decl.name.text : '';
636
+ if (name) {
637
+ bindings.push({ name, init: parseExpr(decl.initializer) });
638
+ }
639
+ }
640
+ continue;
641
+ }
642
+ if (ts.isReturnStatement(stmt) && stmt.expression) {
643
+ const parsed = parseHtmlTemplate(stmt.expression, componentClassName, source);
644
+ template = parsed.nodes;
645
+ eventBindings = parsed.eventBindings;
646
+ }
647
+ }
648
+ return { bindings, template, eventBindings };
649
+ }
650
+ const eventAttrRe = /@(\w+)="$/;
651
+ function parseHtmlTemplate(node, componentClassName, source) {
652
+ let tmpl;
653
+ if (ts.isTaggedTemplateExpression(node)) {
654
+ tmpl = node.template;
655
+ }
656
+ else if (ts.isTemplateExpression(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
657
+ tmpl = node;
658
+ }
659
+ else {
660
+ throw new ParseError('render() must return an html`` tagged template literal');
661
+ }
662
+ if (ts.isNoSubstitutionTemplateLiteral(tmpl)) {
663
+ return { nodes: [{ kind: 'static', text: tmpl.text }], eventBindings: [] };
664
+ }
665
+ const te = tmpl;
666
+ const nodes = [];
667
+ const eventBindings = [];
668
+ let nextIsEventHandler = false;
669
+ // Emit a chunk of static text, checking if it ends with @event="
670
+ // If so, records an event binding slot and injects data-eid="N".
671
+ // Returns true when the next expression is an event handler.
672
+ function pushText(text) {
673
+ const m = text.match(eventAttrRe);
674
+ if (m) {
675
+ const elementId = eventBindings.length;
676
+ const trimmed = text.slice(0, -m[0].length) + `data-e${elementId}`;
677
+ if (trimmed)
678
+ nodes.push({ kind: 'static', text: trimmed });
679
+ eventBindings.push({ eventName: m[1], handlerSource: '', elementId });
680
+ return true;
681
+ }
682
+ if (text)
683
+ nodes.push({ kind: 'static', text });
684
+ return false;
685
+ }
686
+ nextIsEventHandler = pushText(te.head.text);
687
+ for (const span of te.templateSpans) {
688
+ if (nextIsEventHandler) {
689
+ // Capture raw handler source; strip the closing `"` from the following literal.
690
+ const handlerSource = source.slice(span.expression.pos, span.expression.end).trim();
691
+ eventBindings[eventBindings.length - 1].handlerSource = handlerSource;
692
+ const litText = span.literal.text.startsWith('"')
693
+ ? span.literal.text.slice(1)
694
+ : span.literal.text;
695
+ nextIsEventHandler = pushText(litText);
696
+ }
697
+ else {
698
+ // Regular expression: ClassName.styles → __styles__, everything else as-is.
699
+ const expr = span.expression;
700
+ if (ts.isPropertyAccessExpression(expr) &&
701
+ ts.isIdentifier(expr.expression) &&
702
+ expr.expression.text === componentClassName &&
703
+ expr.name.text === 'styles') {
704
+ nodes.push({ kind: 'expr', expr: { kind: 'local_ref', name: '__styles__' } });
705
+ }
706
+ else {
707
+ nodes.push({ kind: 'expr', expr: parseExpr(expr) });
708
+ }
709
+ nextIsEventHandler = pushText(span.literal.text);
710
+ }
711
+ }
712
+ return { nodes, eventBindings };
713
+ }
714
+ //# sourceMappingURL=parser.js.map