ripple 0.2.180 → 0.2.183

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,4 +1,18 @@
1
- /** @import {Expression, FunctionExpression, Node, Program, Statement} from 'estree' */
1
+ /** @import * as AST from 'estree' */
2
+ /** @import * as ESTreeJSX from 'estree-jsx' */
3
+ /** @import { SourceMapMappings } from '@jridgewell/sourcemap-codec' */
4
+ /** @import * as ESRap from 'esrap' */
5
+ /**
6
+ @import {
7
+ AnalysisResult,
8
+ TransformClientContext,
9
+ VisitorClientContext,
10
+ TransformClientState,
11
+ ScopeInterface,
12
+ Visitor,
13
+ Visitors
14
+ } from '#compiler';
15
+ */
2
16
 
3
17
  /** @typedef {Map<number, {offset: number, delta: number}>} PostProcessingChanges */
4
18
  /** @typedef {number[]} LineOffsets */
@@ -47,6 +61,9 @@ import {
47
61
  } from '../../../../utils/events.js';
48
62
  import { createHash } from 'node:crypto';
49
63
 
64
+ /**
65
+ * @param {TransformClientContext} context
66
+ */
50
67
  function add_ripple_internal_import(context) {
51
68
  if (!context.state.to_ts) {
52
69
  if (!context.state.imports.has(`import * as _$_ from 'ripple/internal/client'`)) {
@@ -55,6 +72,11 @@ function add_ripple_internal_import(context) {
55
72
  }
56
73
  }
57
74
 
75
+ /**
76
+ *
77
+ * @param {AST.FunctionDeclaration | AST.FunctionExpression | AST.ArrowFunctionExpression} node
78
+ * @param {TransformClientContext} context
79
+ */
58
80
  function visit_function(node, context) {
59
81
  // Function overload signatures don't have a body - they're TypeScript-only
60
82
  // Remove them when compiling to JavaScript
@@ -62,11 +84,12 @@ function visit_function(node, context) {
62
84
  return b.empty;
63
85
  }
64
86
 
87
+ const state = context.state;
88
+ const metadata = /** @type {AST.FunctionExpression['metadata']} */ (node.metadata);
89
+
65
90
  if (context.state.to_ts) {
66
- return context.next(context.state);
91
+ return context.next(state);
67
92
  }
68
- const metadata = node.metadata;
69
- const state = context.state;
70
93
 
71
94
  delete node.returnType;
72
95
  delete node.typeParameters;
@@ -96,46 +119,68 @@ function visit_function(node, context) {
96
119
  new_body.push(...body.body);
97
120
  }
98
121
 
99
- return /** @type {FunctionExpression} */ ({
122
+ return {
100
123
  ...node,
101
124
  params: node.params.map((param) => context.visit(param, state)),
102
125
  body: body.type === 'BlockStatement' ? { ...body, body: new_body } : body,
103
- });
126
+ };
104
127
  }
105
128
 
106
- context.next(state);
129
+ return context.next(state);
107
130
  }
108
131
 
132
+ /**
133
+ * @param {AST.Element} node
134
+ * @param {TransformClientContext} context
135
+ */
109
136
  function visit_head_element(node, context) {
110
137
  const { state, visit } = context;
111
138
 
139
+ /** @type {TransformClientState['init']} */
112
140
  const init = [];
141
+ /** @type {TransformClientState['update']} */
113
142
  const update = [];
143
+ /** @type {TransformClientState['final']} */
114
144
  const final = [];
145
+ /** @type {TransformClientState['template']} */
115
146
  const template = [];
116
147
 
117
- transform_children(node.children, {
118
- visit,
119
- state: { ...state, init, update, final, template, inside_head: true },
120
- root: true,
121
- });
148
+ transform_children(
149
+ node.children,
150
+ /** @type {VisitorClientContext} */ ({
151
+ visit,
152
+ state: { ...state, init, update, final, template, inside_head: true },
153
+ root: true,
154
+ }),
155
+ );
122
156
 
123
157
  if (init.length > 0 || update.length > 0 || final.length > 0) {
124
- context.state.init.push(
125
- b.call(
126
- '_$_.head',
127
- b.arrow(
128
- [b.id('__anchor')],
129
- b.block([...init, ...update.map((u) => u.operation), ...final]),
158
+ context.state.init?.push(
159
+ b.stmt(
160
+ b.call(
161
+ '_$_.head',
162
+ b.arrow(
163
+ [b.id('__anchor')],
164
+ b.block([
165
+ ...init,
166
+ .../** @type {AST.Statement[]} */ (update.map((u) => u.operation())),
167
+ ...final,
168
+ ]),
169
+ ),
130
170
  ),
131
171
  ),
132
172
  );
133
173
  }
134
174
  }
135
175
 
176
+ /**
177
+ * @param {NonNullable<TransformClientState['init']>} init
178
+ * @param {NonNullable<TransformClientState['update']>} update
179
+ * @param {TransformClientState} state
180
+ */
136
181
  function apply_updates(init, update, state) {
137
- if (update.length === 1 && !update[0].needsPrevTracking) {
138
- init.push(
182
+ if (update?.length === 1 && !update[0].needsPrevTracking) {
183
+ init?.push(
139
184
  b.stmt(
140
185
  b.call(
141
186
  '_$_.render',
@@ -145,7 +190,7 @@ function apply_updates(init, update, state) {
145
190
  if (u.initial) {
146
191
  return u.operation(u.expression);
147
192
  }
148
- return u.operation;
193
+ return u.operation();
149
194
  }),
150
195
  ),
151
196
  !!update.async,
@@ -154,7 +199,6 @@ function apply_updates(init, update, state) {
154
199
  ),
155
200
  );
156
201
  } else {
157
- const index_map = new Map();
158
202
  const initial = [];
159
203
  const render_statements = [];
160
204
  let index = 0;
@@ -164,7 +208,9 @@ function apply_updates(init, update, state) {
164
208
  for (const u of update) {
165
209
  if (u.initial) {
166
210
  const id =
167
- u.identity.type === 'Identifier' ? state.scope.get(u.identity.name)?.initial : u.identity;
211
+ u.identity?.type === 'Identifier'
212
+ ? state.scope.get(u.identity.name)?.initial
213
+ : u.identity;
168
214
  let updates = grouped_updates.get(id);
169
215
 
170
216
  if (updates === undefined) {
@@ -179,7 +225,6 @@ function apply_updates(init, update, state) {
179
225
  if (updates.length === 1) {
180
226
  const u = updates[0];
181
227
  const key = index_to_key(index);
182
- index_map.set(u.operation, key);
183
228
  initial.push(b.prop('init', b.id(key), u.initial));
184
229
  render_statements.push(
185
230
  b.var('__' + key, u.expression),
@@ -204,7 +249,7 @@ function apply_updates(init, update, state) {
204
249
  index++;
205
250
  } else {
206
251
  const key = index_to_key(index);
207
- /** @type {Array<Statement>} */
252
+ /** @type {Array<AST.Statement>} */
208
253
  const if_body = [];
209
254
  initial.push(b.prop('init', b.id(key), updates[0].initial));
210
255
  render_statements.push(
@@ -215,7 +260,6 @@ function apply_updates(init, update, state) {
215
260
  ),
216
261
  );
217
262
  for (const u of updates) {
218
- index_map.set(u.operation, key);
219
263
  if_body.push(
220
264
  u.needsPrevTracking
221
265
  ? u.operation(b.id('__' + key), b.member(b.id('__prev'), b.id(key)))
@@ -232,7 +276,7 @@ function apply_updates(init, update, state) {
232
276
 
233
277
  for (const u of update) {
234
278
  if (!u.initial && !u.needsPrevTracking) {
235
- render_statements.push(u.operation);
279
+ render_statements.push(u.operation());
236
280
  }
237
281
  }
238
282
 
@@ -248,30 +292,37 @@ function apply_updates(init, update, state) {
248
292
  }
249
293
  }
250
294
 
295
+ /**
296
+ * @param {AST.Element} node
297
+ * @param {TransformClientContext} context
298
+ */
251
299
  function visit_title_element(node, context) {
252
300
  const normalized = normalize_children(node.children, context);
253
301
  const content = normalized[0];
254
302
 
255
303
  const metadata = { tracking: false, await: false };
256
- const result = context.visit(content, { ...context.state, metadata }).expression;
304
+ const visited = context.visit(content, { ...context.state, metadata });
305
+ const result = /** @type {AST.Expression} */ (
306
+ /** @type {{expression?: AST.Expression}} */ (visited).expression
307
+ );
257
308
 
258
309
  if (metadata.tracking) {
259
- context.state.init.push(
310
+ context.state.init?.push(
260
311
  b.stmt(
261
312
  b.call(
262
313
  '_$_.render',
263
- b.thunk(b.block([b.assignment('=', b.id('_$_.document.title'), result)])),
314
+ b.thunk(b.block([b.stmt(b.assignment('=', b.id('_$_.document.title'), result))])),
264
315
  ),
265
316
  ),
266
317
  );
267
318
  } else {
268
- context.state.init.push(b.stmt(b.assignment('=', b.id('_$_.document.title'), result)));
319
+ context.state.init?.push(b.stmt(b.assignment('=', b.id('_$_.document.title'), result)));
269
320
  }
270
321
  }
271
322
 
272
323
  /**
273
324
  * @param {string} name
274
- * @param {any} context
325
+ * @param {TransformClientContext} context
275
326
  * @returns {string}
276
327
  */
277
328
  function import_from_ripple_if_needed(name, context) {
@@ -284,8 +335,15 @@ function import_from_ripple_if_needed(name, context) {
284
335
  return alias ?? name;
285
336
  }
286
337
 
338
+ /** @type {Visitors<AST.Node, TransformClientState>} */
287
339
  const visitors = {
288
- _: function set_scope(node, { next, state }) {
340
+ _(node, { next, state, path }) {
341
+ if (!node.metadata) {
342
+ node.metadata = { path: [...path] };
343
+ } else {
344
+ node.metadata.path = [...path];
345
+ }
346
+
289
347
  const scope = state.scopes.get(node);
290
348
 
291
349
  if (scope && scope !== state.scope) {
@@ -296,7 +354,7 @@ const visitors = {
296
354
  },
297
355
 
298
356
  Identifier(node, context) {
299
- const parent = /** @type {Node} */ (context.path.at(-1));
357
+ const parent = /** @type {AST.Node} */ (context.path.at(-1));
300
358
 
301
359
  if (is_reference(node, parent)) {
302
360
  if (context.state.to_ts) {
@@ -353,26 +411,31 @@ const visitors = {
353
411
  return b.id('_$_server_$_');
354
412
  },
355
413
 
414
+ /** @type {Visitor<AST.ImportDeclaration, TransformClientState, AST.Node>} */
356
415
  ImportDeclaration(node, context) {
357
416
  if (!context.state.to_ts && node.importKind === 'type') {
358
417
  return b.empty;
359
418
  }
360
419
 
361
- return {
420
+ return /** @type {AST.ImportDeclaration} */ ({
362
421
  ...node,
363
422
  specifiers: node.specifiers
364
- .filter((spec) => context.state.to_ts || spec.importKind !== 'type')
423
+ .filter(
424
+ (spec) =>
425
+ context.state.to_ts || /** @type {AST.ImportSpecifier} */ (spec).importKind !== 'type',
426
+ )
365
427
  .map((spec) => context.visit(spec)),
366
- };
428
+ });
367
429
  },
368
430
 
369
431
  TSNonNullExpression(node, context) {
370
432
  if (context.state.to_ts) {
371
433
  return context.next();
372
434
  }
373
- return context.visit(node.expression);
435
+ return context.visit(/** @type {AST.Expression} */ (node.expression));
374
436
  },
375
437
 
438
+ /** @type {Visitor<AST.CallExpression, TransformClientState, AST.Node>} */
376
439
  CallExpression(node, context) {
377
440
  if (!context.state.to_ts) {
378
441
  delete node.typeArguments;
@@ -385,7 +448,7 @@ const visitors = {
385
448
  }
386
449
 
387
450
  if (!context.state.to_ts && is_ripple_track_call(callee, context)) {
388
- if (callee.name === 'track') {
451
+ if (callee.type === 'Identifier' && callee.name === 'track') {
389
452
  if (node.arguments.length === 0) {
390
453
  node.arguments.push(b.void0, b.void0, b.void0);
391
454
  } else if (node.arguments.length === 1) {
@@ -396,7 +459,10 @@ const visitors = {
396
459
  }
397
460
  return {
398
461
  ...node,
399
- arguments: [...node.arguments.map((arg) => context.visit(arg)), b.id('__block')],
462
+ arguments: /** @type {(AST.Expression | AST.SpreadElement)[]} */ ([
463
+ ...node.arguments.map((arg) => context.visit(arg)),
464
+ b.id('__block'),
465
+ ]),
400
466
  };
401
467
  }
402
468
 
@@ -422,11 +488,11 @@ const visitors = {
422
488
  b.thunk(
423
489
  b.call(
424
490
  '_$_.call_property',
425
- context.visit(callee.object),
426
- context.visit(property),
491
+ /** @type {AST.Expression} */ (context.visit(callee.object)),
492
+ /** @type {AST.Expression} */ (context.visit(property)),
427
493
  callee.optional ? b.true : undefined,
428
- node.optional ? b.true : undefined,
429
- ...node.arguments.map((arg) => context.visit(arg)),
494
+ /** @type {AST.SimpleCallExpression} */ (node).optional ? b.true : undefined,
495
+ .../** @type {AST.Expression[]} */ (node.arguments.map((arg) => context.visit(arg))),
430
496
  ),
431
497
  ),
432
498
  );
@@ -439,8 +505,10 @@ const visitors = {
439
505
  b.thunk(
440
506
  {
441
507
  ...node,
442
- callee: context.visit(callee),
443
- arguments: node.arguments.map((arg) => context.visit(arg)),
508
+ callee: /** @type {AST.Expression} */ (context.visit(callee)),
509
+ arguments: /** @type {(AST.Expression | AST.SpreadElement)[]} */ (
510
+ node.arguments.map((arg) => context.visit(arg))
511
+ ),
444
512
  },
445
513
  context.state.metadata?.await ?? false,
446
514
  ),
@@ -489,15 +557,19 @@ const visitors = {
489
557
  calleeId.loc = callee.loc;
490
558
  calleeId.metadata = {
491
559
  tracked_shorthand: callee.type === 'TrackedMapExpression' ? '#Map' : '#Set',
560
+ path: [...context.path],
492
561
  };
493
- return b.new(calleeId, ...argsToUse.map((arg) => context.visit(arg)));
562
+ return b.new(
563
+ calleeId,
564
+ .../** @type {AST.Expression[]} */ (argsToUse.map((arg) => context.visit(arg))),
565
+ );
494
566
  }
495
567
 
496
568
  const helperName = callee.type === 'TrackedMapExpression' ? 'tracked_map' : 'tracked_set';
497
569
  return b.call(
498
570
  `_$_.${helperName}`,
499
571
  b.id('__block'),
500
- ...argsToUse.map((arg) => context.visit(arg)),
572
+ .../** @type {AST.Expression[]} */ (argsToUse.map((arg) => context.visit(arg))),
501
573
  );
502
574
  }
503
575
 
@@ -514,10 +586,13 @@ const visitors = {
514
586
  return context.next();
515
587
  }
516
588
 
589
+ /** @type {AST.NewExpression} */
517
590
  const new_node = {
518
591
  ...node,
519
- callee: context.visit(callee),
520
- arguments: node.arguments.map((arg) => context.visit(arg)),
592
+ callee: /** @type {AST.Expression} */ (context.visit(callee)),
593
+ arguments: /** @type {(AST.Expression | AST.SpreadElement)[]} */ (
594
+ node.arguments.map((arg) => context.visit(arg))
595
+ ),
521
596
  };
522
597
  if (!context.state.to_ts) {
523
598
  delete new_node.typeArguments;
@@ -532,13 +607,21 @@ const visitors = {
532
607
 
533
608
  return b.call(
534
609
  b.member(b.id(arrayAlias), b.id('from')),
535
- b.array(node.elements.map((el) => context.visit(el))),
610
+ b.array(
611
+ /** @type {(AST.Expression | AST.SpreadElement)[]} */ (
612
+ node.elements.map((el) => context.visit(/** @type {AST.Node} */ (el)))
613
+ ),
614
+ ),
536
615
  );
537
616
  }
538
617
 
539
618
  return b.call(
540
619
  '_$_.tracked_array',
541
- b.array(node.elements.map((el) => context.visit(el))),
620
+ b.array(
621
+ /** @type {(AST.Expression | AST.SpreadElement)[]} */ (
622
+ node.elements.map((el) => context.visit(/** @type {AST.Node} */ (el)))
623
+ ),
624
+ ),
542
625
  b.id('__block'),
543
626
  );
544
627
  },
@@ -547,23 +630,33 @@ const visitors = {
547
630
  if (context.state.to_ts) {
548
631
  const objectAlias = import_from_ripple_if_needed('TrackedObject', context);
549
632
 
550
- return b.new(b.id(objectAlias), b.object(node.properties.map((prop) => context.visit(prop))));
633
+ return b.new(
634
+ b.id(objectAlias),
635
+ b.object(
636
+ /** @type {(AST.Property | AST.SpreadElement)[]} */ (
637
+ node.properties.map((prop) => context.visit(prop))
638
+ ),
639
+ ),
640
+ );
551
641
  }
552
642
 
553
643
  return b.call(
554
644
  '_$_.tracked_object',
555
- b.object(node.properties.map((prop) => context.visit(prop))),
645
+ b.object(
646
+ /** @type {(AST.Property | AST.SpreadElement)[]} */ (
647
+ node.properties.map((prop) => context.visit(prop))
648
+ ),
649
+ ),
556
650
  b.id('__block'),
557
651
  );
558
652
  },
559
653
 
560
654
  TrackedExpression(node, context) {
561
- return b.call('_$_.get', context.visit(node.argument));
655
+ return b.call('_$_.get', /** @type {AST.Expression} */ (context.visit(node.argument)));
562
656
  },
563
657
 
658
+ /** @type {Visitor<AST.MemberExpression, TransformClientState, AST.Node>} */
564
659
  MemberExpression(node, context) {
565
- const parent = context.path.at(-1);
566
-
567
660
  if (context.state.metadata?.tracking === false) {
568
661
  context.state.metadata.tracking = true;
569
662
  }
@@ -575,8 +668,10 @@ const visitors = {
575
668
  if (!context.state.to_ts) {
576
669
  return b.call(
577
670
  '_$_.get_property',
578
- context.visit(node.object),
579
- node.computed ? context.visit(node.property) : b.literal(node.property.name),
671
+ /** @type {AST.Expression} */ (context.visit(node.object)),
672
+ node.computed
673
+ ? /** @type {AST.Expression} */ (context.visit(node.property))
674
+ : b.literal(/** @type {AST.Identifier} */ (node.property).name),
580
675
  node.optional ? b.true : undefined,
581
676
  );
582
677
  }
@@ -588,15 +683,15 @@ const visitors = {
588
683
  const object = context.visit(node.object, { ...context.state, metadata });
589
684
 
590
685
  if (metadata.tracking) {
591
- if (context.state.metadata?.tracking === false) {
686
+ if (/** @type {boolean | undefined} */ (context.state.metadata?.tracking) === false) {
592
687
  context.state.metadata.tracking = true;
593
688
  }
594
689
 
595
690
  return {
596
691
  ...node,
597
692
  optional: true,
598
- object,
599
- property: context.visit(node.property),
693
+ object: /** @type {AST.Expression} */ (object),
694
+ property: /** @type {AST.Expression} */ (context.visit(node.property)),
600
695
  };
601
696
  }
602
697
  if (metadata.await) {
@@ -626,13 +721,14 @@ const visitors = {
626
721
  return context.next();
627
722
  },
628
723
 
724
+ /** @type {Visitor<AST.VariableDeclarator, TransformClientState, AST.Node>} */
629
725
  VariableDeclarator(node, context) {
630
726
  // In TypeScript mode, capitalize identifiers that are used as dynamic components
631
727
  if (context.state.to_ts) {
632
728
  /**
633
729
  * Recursively capitalize identifiers in patterns (ArrayPattern, ObjectPattern)
634
- * @param {any} pattern - The pattern node to process
635
- * @returns {any} The transformed pattern
730
+ * @param {AST.Pattern} pattern - The pattern node to process
731
+ * @returns {AST.Pattern} The transformed pattern
636
732
  */
637
733
  const capitalize_pattern = (pattern) => {
638
734
  if (pattern.type === 'Identifier') {
@@ -685,7 +781,7 @@ const visitors = {
685
781
  return {
686
782
  ...pattern,
687
783
  left: capitalize_pattern(pattern.left),
688
- right: context.visit(pattern.right),
784
+ right: /** @type {AST.Expression} */ (context.visit(pattern.right)),
689
785
  };
690
786
  }
691
787
  return pattern;
@@ -696,7 +792,7 @@ const visitors = {
696
792
  return {
697
793
  ...node,
698
794
  id: transformed_id,
699
- init: node.init ? context.visit(node.init) : null,
795
+ init: node.init ? /** @type {AST.Expression} */ (context.visit(node.init)) : null,
700
796
  };
701
797
  }
702
798
  }
@@ -704,15 +800,19 @@ const visitors = {
704
800
  },
705
801
 
706
802
  FunctionDeclaration(node, context) {
707
- return visit_function(node, context);
803
+ return /** @type AST.FunctionDeclaration | AST.EmptyStatement */ (
804
+ visit_function(node, context)
805
+ );
708
806
  },
709
807
 
710
808
  ArrowFunctionExpression(node, context) {
711
- return visit_function(node, context);
809
+ return /** @type AST.ArrowFunctionExpression | AST.EmptyStatement */ (
810
+ visit_function(node, context)
811
+ );
712
812
  },
713
813
 
714
814
  FunctionExpression(node, context) {
715
- return visit_function(node, context);
815
+ return /** @type AST.FunctionExpression | AST.EmptyStatement */ (visit_function(node, context));
716
816
  },
717
817
 
718
818
  JSXText(node, context) {
@@ -759,9 +859,16 @@ const visitors = {
759
859
  const props = b.object(
760
860
  attributes.map((attr) => {
761
861
  if (attr.type === 'JSXAttribute') {
762
- return b.prop('init', context.visit(attr.name), context.visit(attr.value));
763
- } else if (attr.type === 'JSXSpreadAttribute') {
764
- return b.spread(context.visit(attr.argument));
862
+ return b.prop(
863
+ 'init',
864
+ /** @type {AST.Expression} */ (context.visit(attr.name)),
865
+ attr.value
866
+ ? /** @type {AST.Expression} */ (context.visit(attr.value))
867
+ : b.literal(true),
868
+ );
869
+ } else {
870
+ // attr.type === 'JSXSpreadAttribute'
871
+ return b.spread(/** @type {AST.Expression} */ (context.visit(attr.argument)));
765
872
  }
766
873
  }),
767
874
  );
@@ -772,8 +879,15 @@ const visitors = {
772
879
  'init',
773
880
  b.id('children'),
774
881
  normalized_children.length === 1
775
- ? context.visit(normalized_children[0])
776
- : b.array(normalized_children.map((child) => context.visit(child))),
882
+ ? /** @type {AST.Expression} */ (
883
+ context.visit(/** @type {AST.Node} */ (normalized_children[0]))
884
+ )
885
+ : b.array(
886
+ normalized_children.map(
887
+ (child) =>
888
+ /** @type {AST.Expression} */ (context.visit(/** @type {AST.Node} */ (child))),
889
+ ),
890
+ ),
777
891
  ),
778
892
  );
779
893
  }
@@ -798,9 +912,16 @@ const visitors = {
798
912
  const props = b.object(
799
913
  attributes.map((attr) => {
800
914
  if (attr.type === 'JSXAttribute') {
801
- return b.prop('init', context.visit(attr.name), context.visit(attr.value));
802
- } else if (attr.type === 'JSXSpreadAttribute') {
803
- return b.spread(context.visit(attr.argument));
915
+ return b.prop(
916
+ 'init',
917
+ /** @type {AST.Expression} */ (context.visit(attr.name)),
918
+ attr.value
919
+ ? /** @type {AST.Expression} */ (context.visit(attr.value))
920
+ : b.literal(true),
921
+ );
922
+ } else {
923
+ // attr.type === 'JSXSpreadAttribute'
924
+ return b.spread(/** @type {AST.Expression} */ (context.visit(attr.argument)));
804
925
  }
805
926
  }),
806
927
  );
@@ -811,8 +932,15 @@ const visitors = {
811
932
  'init',
812
933
  b.id('children'),
813
934
  normalized_children.length === 1
814
- ? context.visit(normalized_children[0])
815
- : b.array(normalized_children.map((child) => context.visit(child))),
935
+ ? /** @type {AST.Expression} */ (
936
+ context.visit(/** @type {AST.Node} */ (normalized_children[0]))
937
+ )
938
+ : b.array(
939
+ normalized_children.map(
940
+ (child) =>
941
+ /** @type {AST.Expression} */ (context.visit(/** @type {AST.Node} */ (child))),
942
+ ),
943
+ ),
816
944
  ),
817
945
  );
818
946
  }
@@ -821,7 +949,7 @@ const visitors = {
821
949
  normalized_children.length > 1 ? '__compat.jsxs' : '__compat.jsx',
822
950
  name.type === 'JSXIdentifier' && name.name[0].toLowerCase() === name.name[0]
823
951
  ? b.literal(name.name)
824
- : context.visit(name),
952
+ : /** @type {AST.Expression} */ (context.visit(name)),
825
953
  props,
826
954
  );
827
955
  },
@@ -829,13 +957,13 @@ const visitors = {
829
957
  TsxCompat(node, context) {
830
958
  const { state, visit } = context;
831
959
 
832
- state.template.push('<!>');
960
+ state.template?.push('<!>');
833
961
 
834
962
  const normalized_children = node.children.filter((child) => {
835
963
  return child.type !== 'JSXText' || child.value.trim() !== '';
836
964
  });
837
965
  const needs_fragment = normalized_children.length !== 1;
838
- const id = state.flush_node();
966
+ const id = state.flush_node?.();
839
967
  const children_fn = b.arrow(
840
968
  [b.id('__compat')],
841
969
  needs_fragment
@@ -846,14 +974,22 @@ const visitors = {
846
974
  b.prop(
847
975
  'init',
848
976
  b.id('children'),
849
- b.array(normalized_children.map((child) => visit(child, state))),
977
+ b.array(
978
+ /** @type {(AST.Expression | AST.SpreadElement | null)[]} */ (
979
+ normalized_children.map((child) =>
980
+ visit(/** @type {AST.Node} */ (child), state),
981
+ )
982
+ ),
983
+ ),
850
984
  ),
851
985
  ]),
852
986
  )
853
- : visit(normalized_children[0], state),
987
+ : /** @type {AST.Expression} */ (
988
+ visit(/** @type {AST.Node} */ (normalized_children[0]), state)
989
+ ),
854
990
  );
855
991
 
856
- context.state.init.push(
992
+ context.state.init?.push(
857
993
  b.stmt(b.call('_$_.tsx_compat', b.literal(node.kind), id, children_fn)),
858
994
  );
859
995
  },
@@ -863,13 +999,13 @@ const visitors = {
863
999
 
864
1000
  if (context.state.inside_head) {
865
1001
  if (node.id.type === 'Identifier' && node.id.name === 'style') {
866
- state.template.push(`<style>${sanitize_template_string(node.css)}</style>`);
1002
+ state.template?.push(`<style>${sanitize_template_string(node.css)}</style>`);
867
1003
  return;
868
1004
  }
869
1005
  if (node.id.type === 'Identifier' && node.id.name === 'script') {
870
- const id = state.flush_node();
871
- state.template.push('<!>');
872
- context.state.init.push(
1006
+ const id = state.flush_node?.();
1007
+ state.template?.push('<!>');
1008
+ context.state.init?.push(
873
1009
  b.stmt(b.call('_$_.script', id, b.literal(sanitize_template_string(node.content)))),
874
1010
  );
875
1011
  return;
@@ -878,17 +1014,22 @@ const visitors = {
878
1014
 
879
1015
  const is_dom_element = is_element_dom_element(node);
880
1016
  const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
1017
+ /** @type {(AST.Property | AST.SpreadElement)[] | null} */
881
1018
  const spread_attributes = is_spreading ? [] : null;
882
1019
  const child_namespace = is_dom_element
883
1020
  ? determine_namespace_for_children(node.id.name, state.namespace)
884
1021
  : state.namespace;
885
1022
 
1023
+ /**
1024
+ * @param {string} name
1025
+ * @param {string | number | bigint | boolean | RegExp | null | undefined} value
1026
+ */
886
1027
  const handle_static_attr = (name, value) => {
887
1028
  const attr_value = b.literal(
888
1029
  ` ${name}${
889
1030
  is_boolean_attribute(name) && value === true
890
1031
  ? ''
891
- : `="${value === true ? '' : escape_html(value, true)}"`
1032
+ : `="${value === true ? '' : escape_html(/** @type {string} */ (value), true)}"`
892
1033
  }`,
893
1034
  );
894
1035
 
@@ -898,21 +1039,23 @@ const visitors = {
898
1039
  is_boolean_attribute(name) && value === true
899
1040
  ? b.literal(true)
900
1041
  : b.literal(value === true ? '' : value);
901
- spread_attributes.push(b.prop('init', b.literal(name), actual_value));
1042
+ spread_attributes?.push(b.prop('init', b.literal(name), actual_value));
902
1043
  } else {
903
- state.template.push(attr_value);
1044
+ state.template?.push(attr_value);
904
1045
  }
905
1046
  };
906
1047
 
907
1048
  if (is_dom_element) {
908
1049
  let class_attribute = null;
909
1050
  let style_attribute = null;
1051
+ const component = /** @type {AST.Component} */ (state.component);
1052
+ /** @type {TransformClientState['update']} */
910
1053
  const local_updates = [];
911
1054
  const is_void = is_void_element(node.id.name);
912
1055
 
913
1056
  let scoping_hash = null;
914
- if (node.metadata.scoped && state.component.css) {
915
- scoping_hash = state.component.css.hash;
1057
+ if (node.metadata?.scoped && component.css) {
1058
+ scoping_hash = component.css.hash;
916
1059
  } else {
917
1060
  let inside_dynamic_children = false;
918
1061
  for (let i = context.path.length - 1; i >= 0; i--) {
@@ -933,7 +1076,7 @@ const visitors = {
933
1076
  }
934
1077
  }
935
1078
 
936
- state.template.push(`<${node.id.name}`);
1079
+ state.template?.push(`<${node.id.name}`);
937
1080
 
938
1081
  for (const attr of node.attributes) {
939
1082
  if (attr.type === 'Attribute') {
@@ -963,9 +1106,11 @@ const visitors = {
963
1106
  }
964
1107
 
965
1108
  if (name === 'value') {
966
- const id = state.flush_node();
1109
+ const id = state.flush_node?.();
967
1110
  const metadata = { tracking: false, await: false };
968
- const expression = visit(attr.value, { ...state, metadata });
1111
+ const expression = /** @type {AST.Expression} */ (
1112
+ visit(attr.value, { ...state, metadata })
1113
+ );
969
1114
 
970
1115
  if (metadata.tracking) {
971
1116
  local_updates.push({
@@ -975,16 +1120,18 @@ const visitors = {
975
1120
  initial: b.void0,
976
1121
  });
977
1122
  } else {
978
- state.init.push(b.stmt(b.call('_$_.set_value', id, expression)));
1123
+ state.init?.push(b.stmt(b.call('_$_.set_value', id, expression)));
979
1124
  }
980
1125
 
981
1126
  continue;
982
1127
  }
983
1128
 
984
1129
  if (name === 'checked') {
985
- const id = state.flush_node();
1130
+ const id = state.flush_node?.();
986
1131
  const metadata = { tracking: false, await: false };
987
- const expression = visit(attr.value, { ...state, metadata });
1132
+ const expression = /** @type {AST.Expression} */ (
1133
+ visit(attr.value, { ...state, metadata })
1134
+ );
988
1135
 
989
1136
  if (metadata.tracking) {
990
1137
  local_updates.push({
@@ -994,15 +1141,17 @@ const visitors = {
994
1141
  initial: b.void0,
995
1142
  });
996
1143
  } else {
997
- state.init.push(b.stmt(b.call('_$_.set_checked', id, expression)));
1144
+ state.init?.push(b.stmt(b.call('_$_.set_checked', id, expression)));
998
1145
  }
999
1146
  continue;
1000
1147
  }
1001
1148
 
1002
1149
  if (name === 'selected') {
1003
- const id = state.flush_node();
1150
+ const id = state.flush_node?.();
1004
1151
  const metadata = { tracking: false, await: false };
1005
- const expression = visit(attr.value, { ...state, metadata });
1152
+ const expression = /** @type {AST.Expression} */ (
1153
+ visit(attr.value, { ...state, metadata })
1154
+ );
1006
1155
 
1007
1156
  if (metadata.tracking) {
1008
1157
  local_updates.push({
@@ -1012,15 +1161,17 @@ const visitors = {
1012
1161
  initial: b.void0,
1013
1162
  });
1014
1163
  } else {
1015
- state.init.push(b.stmt(b.call('_$_.set_selected', id, expression)));
1164
+ state.init?.push(b.stmt(b.call('_$_.set_selected', id, expression)));
1016
1165
  }
1017
1166
  continue;
1018
1167
  }
1019
1168
 
1020
1169
  if (is_event_attribute(name)) {
1021
1170
  const metadata = { tracking: false, await: false };
1022
- let handler = visit(attr.value, { ...state, metadata });
1023
- const id = state.flush_node();
1171
+ let handler = /** @type {AST.Expression} */ (
1172
+ visit(attr.value, { ...state, metadata })
1173
+ );
1174
+ const id = state.flush_node?.();
1024
1175
 
1025
1176
  if (attr.metadata?.delegated) {
1026
1177
  const event_name = normalize_event_name(name);
@@ -1029,34 +1180,49 @@ const visitors = {
1029
1180
  state.events.add(event_name);
1030
1181
  }
1031
1182
 
1032
- state.init.push(
1033
- b.stmt(b.assignment('=', b.member(id, '__' + event_name), handler)),
1183
+ state.init?.push(
1184
+ b.stmt(
1185
+ b.assignment(
1186
+ '=',
1187
+ b.member(/** @type {AST.Identifier} */ (id), '__' + event_name),
1188
+ handler,
1189
+ ),
1190
+ ),
1034
1191
  );
1035
1192
  } else {
1036
1193
  const event_name = get_original_event_name(name);
1037
1194
  // Check if handler is reactive (contains tracking)
1038
1195
  if (metadata.tracking) {
1039
1196
  // Use reactive_event with a thunk to re-evaluate when dependencies change
1040
- state.init.push(
1197
+ state.init?.push(
1041
1198
  b.stmt(b.call('_$_.render_event', b.literal(event_name), id, b.thunk(handler))),
1042
1199
  );
1043
1200
  } else {
1044
- state.init.push(b.stmt(b.call('_$_.event', b.literal(event_name), id, handler)));
1201
+ state.init?.push(b.stmt(b.call('_$_.event', b.literal(event_name), id, handler)));
1045
1202
  }
1046
1203
  }
1047
1204
 
1048
1205
  continue;
1049
1206
  }
1050
1207
  const metadata = { tracking: false, await: false };
1051
- const expression = visit(attr.value, { ...state, metadata });
1208
+ const expression = /** @type {AST.Expression} */ (
1209
+ visit(attr.value, { ...state, metadata })
1210
+ );
1052
1211
  // All other attributes
1053
1212
  if (metadata.tracking) {
1054
1213
  const attribute = name;
1055
- const id = state.flush_node();
1214
+ const id = state.flush_node?.();
1056
1215
 
1057
1216
  if (is_dom_property(attribute)) {
1058
1217
  local_updates.push({
1059
- operation: b.stmt(b.assignment('=', b.member(id, attribute), expression)),
1218
+ operation: () =>
1219
+ b.stmt(
1220
+ b.assignment(
1221
+ '=',
1222
+ b.member(/** @type {AST.Identifier} */ (id), attribute),
1223
+ expression,
1224
+ ),
1225
+ ),
1060
1226
  });
1061
1227
  } else {
1062
1228
  local_updates.push({
@@ -1068,28 +1234,47 @@ const visitors = {
1068
1234
  });
1069
1235
  }
1070
1236
  } else {
1071
- const id = state.flush_node();
1237
+ const id = state.flush_node?.();
1072
1238
 
1073
1239
  if (is_dom_property(name)) {
1074
- state.init.push(b.stmt(b.assignment('=', b.member(id, name), expression)));
1240
+ state.init?.push(
1241
+ b.stmt(
1242
+ b.assignment(
1243
+ '=',
1244
+ b.member(/** @type {AST.Identifier} */ (id), name),
1245
+ expression,
1246
+ ),
1247
+ ),
1248
+ );
1075
1249
  } else {
1076
- state.init.push(
1250
+ state.init?.push(
1077
1251
  b.stmt(b.call('_$_.set_attribute', id, b.literal(name), expression)),
1078
1252
  );
1079
1253
  }
1080
1254
  }
1081
1255
  }
1082
1256
  } else if (attr.type === 'SpreadAttribute') {
1083
- spread_attributes.push(b.spread(visit(attr.argument, state)));
1257
+ spread_attributes?.push(
1258
+ b.spread(/** @type {AST.Expression} */ (visit(attr.argument, state))),
1259
+ );
1084
1260
  } else if (attr.type === 'RefAttribute') {
1085
- const id = state.flush_node();
1086
- state.init.push(b.stmt(b.call('_$_.ref', id, b.thunk(visit(attr.argument, state)))));
1261
+ const id = state.flush_node?.();
1262
+ state.init?.push(
1263
+ b.stmt(
1264
+ b.call(
1265
+ '_$_.ref',
1266
+ id,
1267
+ b.thunk(/** @type {AST.Expression} */ (visit(attr.argument, state))),
1268
+ ),
1269
+ ),
1270
+ );
1087
1271
  }
1088
1272
  }
1089
1273
 
1090
1274
  if (class_attribute !== null) {
1091
- if (class_attribute.value.type === 'Literal') {
1092
- let value = class_attribute.value.value;
1275
+ const attr_value = /** @type {AST.Expression} */ (class_attribute.value);
1276
+ if (attr_value.type === 'Literal') {
1277
+ let value = attr_value.value;
1093
1278
 
1094
1279
  if (scoping_hash) {
1095
1280
  value = `${scoping_hash} ${value}`;
@@ -1097,9 +1282,11 @@ const visitors = {
1097
1282
 
1098
1283
  handle_static_attr(class_attribute.name.name, value);
1099
1284
  } else {
1100
- const id = state.flush_node();
1285
+ const id = state.flush_node?.();
1101
1286
  const metadata = { tracking: false, await: false };
1102
- let expression = visit(class_attribute.value, { ...state, metadata });
1287
+ const expression = /** @type {AST.Expression} */ (
1288
+ visit(attr_value, { ...state, metadata })
1289
+ );
1103
1290
 
1104
1291
  const hash_arg = scoping_hash ? b.literal(scoping_hash) : undefined;
1105
1292
  const is_html = context.state.namespace === 'html' && node.id.name !== 'svg';
@@ -1109,11 +1296,11 @@ const visitors = {
1109
1296
  operation: (key) =>
1110
1297
  b.stmt(b.call('_$_.set_class', id, key, hash_arg, b.literal(is_html))),
1111
1298
  expression,
1112
- identity: class_attribute.value,
1299
+ identity: attr_value,
1113
1300
  initial: b.literal(''),
1114
1301
  });
1115
1302
  } else {
1116
- state.init.push(
1303
+ state.init?.push(
1117
1304
  b.stmt(b.call('_$_.set_class', id, expression, hash_arg, b.literal(is_html))),
1118
1305
  );
1119
1306
  }
@@ -1123,60 +1310,63 @@ const visitors = {
1123
1310
  }
1124
1311
 
1125
1312
  if (style_attribute !== null) {
1126
- if (style_attribute.value.type === 'Literal') {
1127
- handle_static_attr(style_attribute.name.name, style_attribute.value.value);
1313
+ const attr_value = /** @type {AST.Expression} */ (style_attribute.value);
1314
+ if (attr_value.type === 'Literal') {
1315
+ handle_static_attr(style_attribute.name.name, attr_value.value);
1128
1316
  } else {
1129
- const id = state.flush_node();
1317
+ const id = state.flush_node?.();
1130
1318
  const metadata = { tracking: false, await: false };
1131
- const expression = visit(style_attribute.value, { ...state, metadata });
1319
+ const expression = /** @type {AST.Expression} */ (
1320
+ visit(attr_value, { ...state, metadata })
1321
+ );
1132
1322
 
1133
1323
  if (metadata.tracking) {
1134
- if (
1135
- style_attribute.value.type === 'Literal' ||
1136
- style_attribute.value.type === 'TemplateLiteral'
1137
- ) {
1324
+ if (attr_value.type === 'TemplateLiteral') {
1138
1325
  // Doesn't need prev tracking
1139
1326
  local_updates.push({
1140
- operation: b.stmt(b.call('_$_.set_style', id, expression, b.void0)),
1327
+ operation: () => b.stmt(b.call('_$_.set_style', id, expression, b.void0)),
1141
1328
  });
1142
1329
  } else {
1143
1330
  // Object or unknown - needs prev tracking
1144
1331
  local_updates.push({
1145
1332
  operation: (new_value, prev_value) =>
1146
1333
  b.stmt(b.call('_$_.set_style', id, new_value, prev_value)),
1147
- identity: style_attribute.value,
1334
+ identity: attr_value,
1148
1335
  expression,
1149
1336
  initial: b.void0,
1150
1337
  needsPrevTracking: true,
1151
1338
  });
1152
1339
  }
1153
1340
  } else {
1154
- state.init.push(b.stmt(b.call('_$_.set_style', id, expression, b.void0)));
1341
+ state.init?.push(b.stmt(b.call('_$_.set_style', id, expression, b.void0)));
1155
1342
  }
1156
1343
  }
1157
1344
  }
1158
1345
 
1159
- state.template.push('>');
1346
+ state.template?.push('>');
1160
1347
 
1161
1348
  if (spread_attributes !== null && spread_attributes.length > 0) {
1162
- const id = state.flush_node();
1163
- state.init.push(
1349
+ const id = state.flush_node?.();
1350
+ state.init?.push(
1164
1351
  b.stmt(b.call('_$_.render_spread', id, b.thunk(b.object(spread_attributes)))),
1165
1352
  );
1166
1353
  }
1167
1354
 
1168
- /** @type {Array<Statement>} */
1355
+ /** @type {TransformClientState['init']} */
1169
1356
  const init = [];
1170
- /** @type {Array<Statement>} */
1357
+ /** @type {TransformClientState['update']} */
1171
1358
  const update = [];
1172
1359
 
1173
1360
  if (!is_void) {
1174
- transform_children(node.children, {
1175
- visit,
1176
- state: { ...state, init, update, namespace: child_namespace },
1177
- root: false,
1178
- });
1179
- state.template.push(`</${node.id.name}>`);
1361
+ transform_children(
1362
+ node.children,
1363
+ /** @type {VisitorClientContext} */ ({
1364
+ visit,
1365
+ state: { ...state, init, update, namespace: child_namespace },
1366
+ root: false,
1367
+ }),
1368
+ );
1369
+ state.template?.push(`</${node.id.name}>`);
1180
1370
  }
1181
1371
 
1182
1372
  update.push(...local_updates);
@@ -1185,20 +1375,22 @@ const visitors = {
1185
1375
  if (state.scope.declarations.size > 0) {
1186
1376
  apply_updates(init, update, state);
1187
1377
  } else {
1188
- state.update.push(...update);
1378
+ state.update?.push(...update);
1189
1379
  }
1190
1380
  }
1191
1381
 
1192
1382
  if (init.length > 0) {
1193
- state.init.push(b.block(init));
1383
+ state.init?.push(b.block(init));
1194
1384
  }
1195
1385
  } else {
1196
- const id = state.flush_node();
1386
+ const id = state.flush_node?.();
1197
1387
 
1198
- state.template.push('<!>');
1388
+ state.template?.push('<!>');
1199
1389
 
1200
1390
  const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
1391
+ /** @type {(AST.Property | AST.SpreadElement)[]} */
1201
1392
  const props = [];
1393
+ /** @type {AST.Expression | AST.BlockStatement | null} */
1202
1394
  let children_prop = null;
1203
1395
 
1204
1396
  for (const attr of node.attributes) {
@@ -1206,9 +1398,11 @@ const visitors = {
1206
1398
  if (attr.name.type === 'Identifier') {
1207
1399
  const metadata = { tracking: false, await: false };
1208
1400
  let property =
1209
- attr.value === null ? b.literal(true) : visit(attr.value, { ...state, metadata });
1401
+ attr.value === null
1402
+ ? b.literal(true)
1403
+ : /** @type {AST.Expression} */ (visit(attr.value, { ...state, metadata }));
1210
1404
 
1211
- if (attr.name.name === 'class' && node.metadata.scoped && state.component.css) {
1405
+ if (attr.name.name === 'class' && node.metadata?.scoped && state.component?.css) {
1212
1406
  if (property.type === 'Literal') {
1213
1407
  property = b.literal(`${state.component.css.hash} ${property.value}`);
1214
1408
  } else {
@@ -1233,24 +1427,38 @@ const visitors = {
1233
1427
  props.push(b.prop('init', b.key(attr.name.name), property));
1234
1428
  }
1235
1429
  } else {
1236
- props.push(b.prop('init', b.key(attr.name.name), visit(attr.value, state)));
1430
+ props.push(
1431
+ b.prop(
1432
+ 'init',
1433
+ b.key(attr.name.name),
1434
+ /** @type {AST.Expression} */ (visit(/** @type {AST.Node} */ (attr.value), state)),
1435
+ ),
1436
+ );
1237
1437
  }
1238
1438
  } else if (attr.type === 'SpreadAttribute') {
1239
1439
  props.push(
1240
1440
  b.spread(
1241
- visit(attr.argument, { ...state, metadata: { ...state.metadata, spread: true } }),
1441
+ /** @type {AST.Expression} */
1442
+ (visit(attr.argument, { ...state, metadata: { ...state.metadata } })),
1242
1443
  ),
1243
1444
  );
1244
1445
  } else if (attr.type === 'RefAttribute') {
1245
1446
  const ref_id = state.scope.generate('ref');
1246
- state.setup.push(b.var(ref_id, b.call('_$_.ref_prop')));
1247
- props.push(b.prop('init', b.id(ref_id), visit(attr.argument, state), true));
1447
+ state.setup?.push(b.var(ref_id, b.call('_$_.ref_prop')));
1448
+ props.push(
1449
+ b.prop(
1450
+ 'init',
1451
+ b.id(ref_id),
1452
+ /** @type {AST.Expression} */ (visit(attr.argument, state)),
1453
+ true,
1454
+ ),
1455
+ );
1248
1456
  } else {
1249
1457
  throw new Error('TODO');
1250
1458
  }
1251
1459
  }
1252
1460
 
1253
- if (node.metadata.scoped && state.component.css) {
1461
+ if (node.metadata?.scoped && state.component?.css) {
1254
1462
  const hasClassAttr = node.attributes.some(
1255
1463
  (attr) =>
1256
1464
  attr.type === 'Attribute' &&
@@ -1268,8 +1476,18 @@ const visitors = {
1268
1476
 
1269
1477
  for (const child of node.children) {
1270
1478
  if (child.type === 'Component') {
1271
- const id = child.id;
1272
- props.push(b.prop('init', id, visit(child, { ...state, namespace: child_namespace })));
1479
+ // in this case, id cannot be null
1480
+ // as these are direct children of the component
1481
+ const id = /** @type {AST.Identifier} */ (child.id);
1482
+ props.push(
1483
+ b.prop(
1484
+ 'init',
1485
+ id,
1486
+ /** @type {AST.Expression} */ (
1487
+ visit(child, { ...state, namespace: child_namespace })
1488
+ ),
1489
+ ),
1490
+ );
1273
1491
  } else {
1274
1492
  children_filtered.push(child);
1275
1493
  }
@@ -1284,14 +1502,22 @@ const visitors = {
1284
1502
  inherited_css: true,
1285
1503
  };
1286
1504
 
1287
- const children = visit(children_component, {
1288
- ...context.state,
1289
- scope: component_scope,
1290
- namespace: child_namespace,
1291
- });
1505
+ const children = /** @type {AST.Expression} */ (
1506
+ visit(children_component, {
1507
+ ...context.state,
1508
+ scope: /** @type {ScopeInterface} */ (component_scope),
1509
+ namespace: child_namespace,
1510
+ })
1511
+ );
1292
1512
 
1293
1513
  if (children_prop) {
1294
- children_prop.body = b.logical('??', children_prop.body, children);
1514
+ /** @type {AST.ArrowFunctionExpression} */ (children_prop).body = b.logical(
1515
+ '??',
1516
+ /** @type {AST.Expression} */ (
1517
+ /** @type {AST.ArrowFunctionExpression} */ (children_prop).body
1518
+ ),
1519
+ children,
1520
+ );
1295
1521
  } else {
1296
1522
  props.push(b.prop('init', b.id('children'), children));
1297
1523
  }
@@ -1299,7 +1525,7 @@ const visitors = {
1299
1525
 
1300
1526
  const metadata = { tracking: false, await: false };
1301
1527
  // We visit, but only to gather metadata
1302
- b.call(visit(node.id, { ...state, metadata }));
1528
+ b.call(/** @type {AST.Expression} */ (visit(node.id, { ...state, metadata })));
1303
1529
 
1304
1530
  // We're calling a component from within svg/mathml context
1305
1531
  const is_with_ns = state.namespace !== DEFAULT_NAMESPACE;
@@ -1340,17 +1566,27 @@ const visitors = {
1340
1566
  object_props = b.object(props);
1341
1567
  }
1342
1568
  if (metadata.tracking) {
1343
- const shared = b.call('_$_.composite', b.thunk(visit(node.id, state)), id, object_props);
1344
- state.init.push(
1569
+ const shared = b.call(
1570
+ '_$_.composite',
1571
+ b.thunk(/** @type {AST.Expression} */ (visit(node.id, state))),
1572
+ id,
1573
+ object_props,
1574
+ );
1575
+ state.init?.push(
1345
1576
  is_with_ns
1346
- ? b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(shared))
1577
+ ? b.stmt(b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(shared)))
1347
1578
  : b.stmt(shared),
1348
1579
  );
1349
1580
  } else {
1350
- const shared = b.call(visit(node.id, state), id, object_props, b.id('_$_.active_block'));
1351
- state.init.push(
1581
+ const shared = b.call(
1582
+ /** @type {AST.Expression} */ (visit(node.id, state)),
1583
+ id,
1584
+ object_props,
1585
+ b.id('_$_.active_block'),
1586
+ );
1587
+ state.init?.push(
1352
1588
  is_with_ns
1353
- ? b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(shared))
1589
+ ? b.stmt(b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(shared)))
1354
1590
  : b.stmt(shared),
1355
1591
  );
1356
1592
  }
@@ -1374,11 +1610,17 @@ const visitors = {
1374
1610
 
1375
1611
  const func = b.function(
1376
1612
  node.id,
1377
- node.params.map((param) => context.visit(param, { ...context.state, metadata })),
1613
+ node.params.map(
1614
+ (param) =>
1615
+ /** @type {AST.Pattern} */ (context.visit(param, { ...context.state, metadata })),
1616
+ ),
1378
1617
  b.block(body_statements),
1379
1618
  );
1380
1619
  // Mark that this function was originally a component
1381
- func.metadata = { ...func.metadata, was_component: true };
1620
+ func.metadata = /** @type {AST.FunctionExpression['metadata']} */ ({
1621
+ ...func.metadata,
1622
+ was_component: true,
1623
+ });
1382
1624
  func.loc = node.loc; // Copy source location for Volar mappings
1383
1625
  return func;
1384
1626
  }
@@ -1422,7 +1664,10 @@ const visitors = {
1422
1664
  ]),
1423
1665
  );
1424
1666
  // Mark that this function was originally a component
1425
- func.metadata = { ...func.metadata, was_component: true };
1667
+ func.metadata = /** @type {AST.FunctionExpression['metadata']} */ ({
1668
+ ...func.metadata,
1669
+ was_component: true,
1670
+ });
1426
1671
  func.loc = node.loc; // Copy source location for Volar mappings
1427
1672
  return func;
1428
1673
  },
@@ -1448,14 +1693,18 @@ const visitors = {
1448
1693
 
1449
1694
  return b.call(
1450
1695
  '_$_.set_property',
1451
- context.visit(left.object, { ...context.state, metadata: { tracking: false } }),
1452
- left.computed ? context.visit(left.property) : b.literal(left.property.name),
1696
+ /** @type {AST.Expression} */ (
1697
+ context.visit(left.object, { ...context.state, metadata: { tracking: false } })
1698
+ ),
1699
+ left.computed
1700
+ ? /** @type {AST.Expression} */ (context.visit(left.property))
1701
+ : b.literal(/** @type {AST.Identifier} */ (left.property).name),
1453
1702
  operator === '='
1454
- ? context.visit(right)
1703
+ ? /** @type {AST.Expression} */ (context.visit(right))
1455
1704
  : b.binary(
1456
1705
  operator === '+=' ? '+' : operator === '-=' ? '-' : operator === '*=' ? '*' : '/',
1457
- /** @type {Expression} */ (context.visit(left)),
1458
- /** @type {Expression} */ (context.visit(right)),
1706
+ /** @type {AST.Expression} */ (context.visit(left)),
1707
+ /** @type {AST.Expression} */ (context.visit(right)),
1459
1708
  ),
1460
1709
  );
1461
1710
  }
@@ -1467,15 +1716,17 @@ const visitors = {
1467
1716
 
1468
1717
  return b.call(
1469
1718
  '_$_.set',
1470
- context.visit(left, { ...context.state, metadata: { tracking: null } }),
1719
+ /** @type {AST.Expression} */ (
1720
+ context.visit(left, { ...context.state, metadata: { tracking: null } })
1721
+ ),
1471
1722
  operator === '='
1472
- ? context.visit(right)
1723
+ ? /** @type {AST.Expression} */ (context.visit(right))
1473
1724
  : b.binary(
1474
1725
  operator === '+=' ? '+' : operator === '-=' ? '-' : operator === '*=' ? '*' : '/',
1475
- /** @type {Expression} */ (
1726
+ /** @type {AST.Expression} */ (
1476
1727
  context.visit(left, { ...context.state, metadata: { tracking: false } })
1477
1728
  ),
1478
- /** @type {Expression} */ (context.visit(right)),
1729
+ /** @type {AST.Expression} */ (context.visit(right)),
1479
1730
  ),
1480
1731
  );
1481
1732
  }
@@ -1500,8 +1751,11 @@ const visitors = {
1500
1751
 
1501
1752
  return b.call(
1502
1753
  node.prefix ? '_$_.update_pre_property' : '_$_.update_property',
1503
- context.visit(argument.object, { ...context.state, metadata: { tracking: false } }),
1504
- argument.computed ? context.visit(argument.property) : b.literal(argument.property.name),
1754
+ /** @type {AST.Expression} */
1755
+ (context.visit(argument.object, { ...context.state, metadata: { tracking: false } })),
1756
+ argument.computed
1757
+ ? /** @type {AST.Expression} */ (context.visit(argument.property))
1758
+ : b.literal(/** @type {AST.Identifier} */ (argument.property).name),
1505
1759
  node.operator === '--' ? b.literal(-1) : undefined,
1506
1760
  );
1507
1761
  }
@@ -1509,7 +1763,8 @@ const visitors = {
1509
1763
  if (argument.type === 'Identifier' && argument.tracked) {
1510
1764
  return b.call(
1511
1765
  node.prefix ? '_$_.update_pre' : '_$_.update',
1512
- context.visit(argument, { ...context.state, metadata: { tracking: null } }),
1766
+ /** @type {AST.Expression} */
1767
+ (context.visit(argument, { ...context.state, metadata: { tracking: null } })),
1513
1768
  node.operator === '--' ? b.literal(-1) : undefined,
1514
1769
  );
1515
1770
  }
@@ -1517,17 +1772,18 @@ const visitors = {
1517
1772
  if (argument.type === 'TrackedExpression') {
1518
1773
  return b.call(
1519
1774
  node.prefix ? '_$_.update_pre' : '_$_.update',
1520
- context.visit(argument.argument, { ...context.state, metadata: { tracking: null } }),
1775
+ /** @type {AST.Expression} */
1776
+ (context.visit(argument.argument, { ...context.state, metadata: { tracking: null } })),
1521
1777
  node.operator === '--' ? b.literal(-1) : undefined,
1522
1778
  );
1523
1779
  }
1524
1780
 
1525
- const left = object(argument);
1781
+ const left = object(/** @type {AST.MemberExpression | AST.Identifier} */ (argument));
1526
1782
  const binding = left && context.state.scope.get(left.name);
1527
1783
  const transformers = left && binding?.transform;
1528
1784
 
1529
1785
  if (left === argument) {
1530
- const update_fn = transformers?.update || transformers?.update_tracked;
1786
+ const update_fn = transformers?.update;
1531
1787
  if (update_fn) {
1532
1788
  return update_fn(node);
1533
1789
  }
@@ -1551,23 +1807,23 @@ const visitors = {
1551
1807
 
1552
1808
  // do only if not controller
1553
1809
  if (!is_controlled) {
1554
- context.state.template.push('<!>');
1810
+ context.state.template?.push('<!>');
1555
1811
  }
1556
1812
 
1557
- const id = context.state.flush_node(is_controlled);
1558
- const pattern = node.left.declarations[0].id;
1559
- const body_scope = context.state.scopes.get(node.body);
1813
+ const id = context.state.flush_node?.(is_controlled);
1814
+ const pattern = /** @type {AST.VariableDeclaration} */ (node.left).declarations[0].id;
1815
+ const body_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node.body));
1560
1816
 
1561
- context.state.init.push(
1817
+ context.state.init?.push(
1562
1818
  b.stmt(
1563
1819
  b.call(
1564
1820
  key != null ? '_$_.for_keyed' : '_$_.for',
1565
1821
  id,
1566
- b.thunk(context.visit(node.right)),
1822
+ b.thunk(/** @type {AST.Expression} */ (context.visit(node.right))),
1567
1823
  b.arrow(
1568
1824
  index ? [b.id('__anchor'), pattern, index] : [b.id('__anchor'), pattern],
1569
1825
  b.block(
1570
- transform_body(node.body.body, {
1826
+ transform_body(/** @type {AST.BlockStatement} */ (node.body).body, {
1571
1827
  ...context,
1572
1828
  state: { ...context.state, scope: body_scope, namespace: context.state.namespace },
1573
1829
  }),
@@ -1575,7 +1831,10 @@ const visitors = {
1575
1831
  ),
1576
1832
  b.literal(flags),
1577
1833
  key != null
1578
- ? b.arrow(index ? [pattern, index] : [pattern], context.visit(key))
1834
+ ? b.arrow(
1835
+ index ? [pattern, index] : [pattern],
1836
+ /** @type {AST.Expression} */ (context.visit(key)),
1837
+ )
1579
1838
  : undefined,
1580
1839
  ),
1581
1840
  ),
@@ -1586,9 +1845,9 @@ const visitors = {
1586
1845
  if (!is_inside_component(context)) {
1587
1846
  return context.next();
1588
1847
  }
1589
- context.state.template.push('<!>');
1848
+ context.state.template?.push('<!>');
1590
1849
 
1591
- const id = context.state.flush_node();
1850
+ const id = context.state.flush_node?.();
1592
1851
  const statements = [];
1593
1852
  const cases = [];
1594
1853
 
@@ -1610,9 +1869,10 @@ const visitors = {
1610
1869
  statements.push(b.var(b.id(consequent_id), b.arrow([b.id('__anchor')], consequent)));
1611
1870
 
1612
1871
  cases.push(
1613
- b.switch_case(switch_case.test ? context.visit(switch_case.test) : null, [
1614
- b.return(b.id(consequent_id)),
1615
- ]),
1872
+ b.switch_case(
1873
+ switch_case.test ? /** @type {AST.Expression} */ (context.visit(switch_case.test)) : null,
1874
+ [b.return(b.id(consequent_id))],
1875
+ ),
1616
1876
  );
1617
1877
  i++;
1618
1878
  }
@@ -1622,26 +1882,32 @@ const visitors = {
1622
1882
  b.call(
1623
1883
  '_$_.switch',
1624
1884
  id,
1625
- b.thunk(b.block([b.switch(context.visit(node.discriminant), cases)])),
1885
+ b.thunk(
1886
+ b.block([
1887
+ b.switch(/** @type {AST.Expression} */ (context.visit(node.discriminant)), cases),
1888
+ ]),
1889
+ ),
1626
1890
  ),
1627
1891
  ),
1628
1892
  );
1629
1893
 
1630
- context.state.init.push(b.block(statements));
1894
+ context.state.init?.push(b.block(statements));
1631
1895
  },
1632
1896
 
1633
1897
  IfStatement(node, context) {
1634
1898
  if (!is_inside_component(context)) {
1635
1899
  return context.next();
1636
1900
  }
1637
- context.state.template.push('<!>');
1901
+ context.state.template?.push('<!>');
1638
1902
 
1639
- const id = context.state.flush_node();
1903
+ const id = context.state.flush_node?.();
1640
1904
  const statements = [];
1641
1905
 
1642
- const consequent_scope = context.state.scopes.get(node.consequent);
1906
+ const consequent_scope = /** @type {ScopeInterface} */ (
1907
+ context.state.scopes.get(node.consequent)
1908
+ );
1643
1909
  const consequent = b.block(
1644
- transform_body(node.consequent.body, {
1910
+ transform_body(/** @type {AST.BlockStatement} */ (node.consequent).body, {
1645
1911
  ...context,
1646
1912
  state: { ...context.state, scope: consequent_scope },
1647
1913
  }),
@@ -1653,19 +1919,18 @@ const visitors = {
1653
1919
  let alternate_id;
1654
1920
 
1655
1921
  if (node.alternate !== null) {
1656
- const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
1657
- let alternate_body = node.alternate.body;
1658
- if (node.alternate.type === 'IfStatement') {
1659
- alternate_body = [node.alternate];
1660
- }
1661
- const alternate = b.block(
1922
+ const alternate = /** @type {AST.BlockStatement | AST.IfStatement} */ (node.alternate);
1923
+ const alternate_scope = context.state.scopes.get(alternate) || context.state.scope;
1924
+ /** @type {AST.Node[]} */
1925
+ let alternate_body = alternate.type === 'IfStatement' ? [alternate] : alternate.body;
1926
+ const alternate_block = b.block(
1662
1927
  transform_body(alternate_body, {
1663
1928
  ...context,
1664
1929
  state: { ...context.state, scope: alternate_scope },
1665
1930
  }),
1666
1931
  );
1667
1932
  alternate_id = context.state.scope.generate('alternate');
1668
- statements.push(b.var(b.id(alternate_id), b.arrow([b.id('__anchor')], alternate)));
1933
+ statements.push(b.var(b.id(alternate_id), b.arrow([b.id('__anchor')], alternate_block)));
1669
1934
  }
1670
1935
 
1671
1936
  statements.push(
@@ -1677,7 +1942,7 @@ const visitors = {
1677
1942
  [b.id('__render')],
1678
1943
  b.block([
1679
1944
  b.if(
1680
- context.visit(node.test),
1945
+ /** @type {AST.Expression} */ (context.visit(node.test)),
1681
1946
  b.stmt(b.call(b.id('__render'), b.id(consequent_id))),
1682
1947
  alternate_id
1683
1948
  ? b.stmt(
@@ -1695,12 +1960,12 @@ const visitors = {
1695
1960
  ),
1696
1961
  );
1697
1962
 
1698
- context.state.init.push(b.block(statements));
1963
+ context.state.init?.push(b.block(statements));
1699
1964
  },
1700
1965
 
1701
1966
  TSAsExpression(node, context) {
1702
1967
  if (!context.state.to_ts) {
1703
- return context.visit(node.expression);
1968
+ return context.visit(/** @type {AST.Expression} */ (node.expression));
1704
1969
  }
1705
1970
  return context.next();
1706
1971
  },
@@ -1708,7 +1973,11 @@ const visitors = {
1708
1973
  TSInstantiationExpression(node, context) {
1709
1974
  if (!context.state.to_ts) {
1710
1975
  // In JavaScript, just return the expression wrapped in parentheses
1711
- return b.sequence([context.visit(node.expression)]);
1976
+ return b.sequence([
1977
+ /** @type {AST.Expression} */ (
1978
+ context.visit(/** @type {AST.Expression} */ (node.expression))
1979
+ ),
1980
+ ]);
1712
1981
  }
1713
1982
  return context.next();
1714
1983
  },
@@ -1719,7 +1988,10 @@ const visitors = {
1719
1988
  }
1720
1989
 
1721
1990
  // Remove TSDeclareFunction nodes (function overload signatures) in JavaScript mode
1722
- if (!context.state.to_ts && node.declaration?.type === 'TSDeclareFunction') {
1991
+ if (
1992
+ !context.state.to_ts &&
1993
+ /** @type {AST.RippleDeclaration} */ (node.declaration)?.type === 'TSDeclareFunction'
1994
+ ) {
1723
1995
  return b.empty;
1724
1996
  }
1725
1997
 
@@ -1741,34 +2013,37 @@ const visitors = {
1741
2013
  if (!is_inside_component(context)) {
1742
2014
  return context.next();
1743
2015
  }
1744
- context.state.template.push('<!>');
2016
+ context.state.template?.push('<!>');
1745
2017
 
1746
- const id = context.state.flush_node();
2018
+ const id = context.state.flush_node?.();
1747
2019
  const metadata = { await: false };
1748
2020
  let body = transform_body(node.block.body, {
1749
2021
  ...context,
1750
2022
  state: { ...context.state, metadata },
1751
2023
  });
1752
2024
 
1753
- if (metadata.pending) {
2025
+ if (node.pending) {
1754
2026
  body = [b.stmt(b.call('_$_.async', b.thunk(b.block(body), true)))];
1755
2027
  }
1756
2028
 
1757
- context.state.init.push(
2029
+ const handler = /** @type {AST.CatchClause | null} */ (node.handler);
2030
+ const pending = /** @type {AST.BlockStatement | null} */ (node.pending);
2031
+
2032
+ context.state.init?.push(
1758
2033
  b.stmt(
1759
2034
  b.call(
1760
2035
  '_$_.try',
1761
2036
  id,
1762
2037
  b.arrow([b.id('__anchor')], b.block(body)),
1763
- node.handler === null
2038
+ handler === null
1764
2039
  ? b.literal(null)
1765
2040
  : b.arrow(
1766
- [b.id('__anchor'), ...(node.handler.param ? [node.handler.param] : [])],
1767
- b.block(transform_body(node.handler.body.body, context)),
2041
+ [b.id('__anchor'), ...(handler.param ? [handler.param] : [])],
2042
+ b.block(transform_body(handler.body.body, context)),
1768
2043
  ),
1769
- node.pending === null
2044
+ pending === null
1770
2045
  ? undefined
1771
- : b.arrow([b.id('__anchor')], b.block(transform_body(node.pending.body, context))),
2046
+ : b.arrow([b.id('__anchor')], b.block(transform_body(pending.body, context))),
1772
2047
  ),
1773
2048
  ),
1774
2049
  );
@@ -1783,11 +2058,19 @@ const visitors = {
1783
2058
  context.state.metadata.await = true;
1784
2059
  }
1785
2060
 
1786
- return b.call(b.await(b.call('_$_.maybe_tracked', context.visit(node.argument))));
2061
+ return b.call(
2062
+ b.await(
2063
+ b.call('_$_.maybe_tracked', /** @type {AST.Expression} */ (context.visit(node.argument))),
2064
+ ),
2065
+ );
1787
2066
  },
1788
2067
 
1789
2068
  BinaryExpression(node, context) {
1790
- return b.binary(node.operator, context.visit(node.left), context.visit(node.right));
2069
+ return b.binary(
2070
+ node.operator,
2071
+ /** @type {AST.Expression} */ (context.visit(node.left)),
2072
+ /** @type {AST.Expression} */ (context.visit(node.right)),
2073
+ );
1791
2074
  },
1792
2075
 
1793
2076
  TemplateLiteral(node, context) {
@@ -1797,15 +2080,18 @@ const visitors = {
1797
2080
  return b.literal(node.quasis[0].value.cooked);
1798
2081
  }
1799
2082
 
1800
- const expressions = node.expressions.map((expr) => context.visit(expr));
2083
+ const expressions = /** @type {AST.Expression[]} */ (
2084
+ node.expressions.map((expr) => context.visit(expr))
2085
+ );
1801
2086
  return b.template(node.quasis, expressions);
1802
2087
  },
1803
2088
 
1804
2089
  BlockStatement(node, context) {
2090
+ /** @type {AST.Statement[]} */
1805
2091
  const statements = [];
1806
2092
 
1807
2093
  for (const statement of node.body) {
1808
- statements.push(context.visit(statement));
2094
+ statements.push(/** @type {AST.Statement} */ (context.visit(statement)));
1809
2095
  }
1810
2096
 
1811
2097
  return b.block(statements);
@@ -1822,7 +2108,7 @@ const visitors = {
1822
2108
  return b.const(
1823
2109
  '_$_server_$_',
1824
2110
  b.object(
1825
- exports.map((name) => {
2111
+ /** @type {AST.ServerBlock['metadata']['exports']} */ (exports).map((name) => {
1826
2112
  const func_path = file_path + '#' + name;
1827
2113
  // needs to be a sha256 hash of func_path, to avoid leaking file structure
1828
2114
  const hash = createHash('sha256').update(func_path).digest('hex').slice(0, 8);
@@ -1841,11 +2127,17 @@ const visitors = {
1841
2127
  );
1842
2128
  },
1843
2129
 
2130
+ /** @type {Visitor<AST.Program, TransformClientState, AST.Node>} */
1844
2131
  Program(node, context) {
2132
+ /** @type {Array<AST.Statement | AST.Directive | AST.ModuleDeclaration>} */
1845
2133
  const statements = [];
1846
2134
 
1847
2135
  for (const statement of node.body) {
1848
- statements.push(context.visit(statement));
2136
+ statements.push(
2137
+ /** @type {AST.Statement | AST.Directive | AST.ModuleDeclaration} */ (
2138
+ context.visit(statement)
2139
+ ),
2140
+ );
1849
2141
  }
1850
2142
 
1851
2143
  return { ...node, body: statements };
@@ -1853,14 +2145,14 @@ const visitors = {
1853
2145
  };
1854
2146
 
1855
2147
  /**
1856
- * @param {Array<string | Expression>} items
2148
+ * @param {Array<string | AST.Expression>} items
1857
2149
  */
1858
2150
  function join_template(items) {
1859
2151
  let quasi = b.quasi('');
1860
2152
  const template = b.template([quasi], []);
1861
2153
 
1862
2154
  /**
1863
- * @param {Expression} expression
2155
+ * @param {AST.Expression} expression
1864
2156
  */
1865
2157
  function push(expression) {
1866
2158
  if (expression.type === 'TemplateLiteral') {
@@ -1872,8 +2164,8 @@ function join_template(items) {
1872
2164
  push(e);
1873
2165
  }
1874
2166
 
1875
- const last = /** @type {any} */ (expression.quasis.at(-1));
1876
- quasi.value.cooked += /** @type {string} */ (last.value.cooked);
2167
+ const last = expression.quasis.at(-1);
2168
+ quasi.value.cooked += /** @type {string} */ (last?.value.cooked);
1877
2169
  } else if (expression.type === 'Literal') {
1878
2170
  /** @type {string} */ (quasi.value.cooked) += expression.value;
1879
2171
  } else {
@@ -1899,27 +2191,33 @@ function join_template(items) {
1899
2191
  return template;
1900
2192
  }
1901
2193
 
2194
+ /**
2195
+ * @param {AST.Node} node
2196
+ * @param {TransformClientContext} context
2197
+ */
1902
2198
  function transform_ts_child(node, context) {
1903
2199
  const { state, visit } = context;
1904
2200
 
1905
2201
  if (node.type === 'Text') {
1906
- state.init.push(b.stmt(visit(node.expression, { ...state })));
2202
+ state.init?.push(b.stmt(/** @type {AST.Expression} */ (visit(node.expression, { ...state }))));
1907
2203
  } else if (node.type === 'Html') {
1908
2204
  // Do we need to do something special here?
1909
- state.init.push(b.stmt(visit(node.expression, { ...state })));
2205
+ state.init?.push(b.stmt(/** @type {AST.Expression} */ (visit(node.expression, { ...state }))));
1910
2206
  } else if (node.type === 'Element') {
1911
2207
  // Use capitalized name for dynamic components/elements in TypeScript output
1912
2208
  // If node.id is not an Identifier (e.g., MemberExpression like props.children),
1913
2209
  // we need to visit it to get the proper expression
2210
+ /** @type {string | AST.Node} */
1914
2211
  let type_expression;
1915
2212
  let type_is_expression = false;
1916
- if (node.id.type === 'MemberExpression') {
2213
+ if (/** @type {AST.Node} */ (node.id).type === 'MemberExpression') {
1917
2214
  // For MemberExpressions, we need to create a JSXExpression, not a JSXIdentifier
1918
2215
  type_expression = visit(node.id, state);
1919
2216
  type_is_expression = true;
1920
2217
  } else {
1921
2218
  type_expression = node.metadata?.ts_name || node.id.name;
1922
2219
  }
2220
+ /** @type {ESTreeJSX.JSXElement['children']} */
1923
2221
  const children = [];
1924
2222
  let has_children_props = false;
1925
2223
 
@@ -1947,27 +2245,35 @@ function transform_ts_child(node, context) {
1947
2245
  }
1948
2246
  jsx_name.loc = attr.name.loc || name.loc;
1949
2247
 
1950
- const jsx_attr = b.jsx_attribute(jsx_name, b.jsx_expression_container(value));
2248
+ const jsx_attr = b.jsx_attribute(
2249
+ jsx_name,
2250
+ b.jsx_expression_container(/** @type {AST.Expression} */ (value)),
2251
+ );
1951
2252
  // Preserve shorthand flag from parser (set for {identifier} syntax)
1952
2253
  jsx_attr.shorthand = attr.shorthand ?? false;
1953
2254
  return jsx_attr;
1954
2255
  } else if (attr.type === 'SpreadAttribute') {
1955
2256
  const metadata = { await: false };
1956
2257
  const argument = visit(attr.argument, { ...state, metadata });
1957
- return b.jsx_spread_attribute(argument);
2258
+ return b.jsx_spread_attribute(/** @type {AST.Expression} */ (argument));
1958
2259
  } else if (attr.type === 'RefAttribute') {
1959
2260
  const createRefKeyAlias = import_from_ripple_if_needed('createRefKey', context);
1960
2261
  const metadata = { await: false };
1961
2262
  const argument = visit(attr.argument, { ...state, metadata });
1962
- const wrapper = b.object([b.prop('init', b.call(createRefKeyAlias), argument, true)]);
2263
+ const wrapper = b.object([
2264
+ b.prop('init', b.call(createRefKeyAlias), /** @type {AST.Expression} */ (argument), true),
2265
+ ]);
1963
2266
  return b.jsx_spread_attribute(wrapper);
2267
+ } else {
2268
+ // Should not happen
2269
+ throw new Error(`Unexpected attribute type: ${/** @type {AST.Attribute} */ (attr).type}`);
1964
2270
  }
1965
2271
  });
1966
2272
 
1967
2273
  if (!node.selfClosing && !node.unclosed && !has_children_props && node.children.length > 0) {
1968
2274
  const is_dom_element = is_element_dom_element(node);
1969
2275
 
1970
- const component_scope = context.state.scopes.get(node);
2276
+ const component_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node));
1971
2277
  const thunk = b.thunk(
1972
2278
  b.block(
1973
2279
  transform_body(node.children, {
@@ -1984,15 +2290,19 @@ function transform_ts_child(node, context) {
1984
2290
  }
1985
2291
  }
1986
2292
 
1987
- let opening_name_element, closing_name_element;
2293
+ /** @type {ESTreeJSX.JSXIdentifier | AST.Node | undefined} */
2294
+ let opening_name_element;
2295
+ /** @type {AST.Node | ESTreeJSX.JSXClosingElement['name'] | undefined} */
2296
+ let closing_name_element;
1988
2297
 
1989
2298
  if (type_is_expression) {
1990
2299
  // For dynamic/expression-based components (e.g., props.children),
1991
2300
  // use JSX expression instead of identifier
1992
- opening_name_element = type_expression;
1993
- closing_name_element = node.selfClosing || node.unclosed ? undefined : type_expression;
2301
+ opening_name_element = /** @type {AST.Node} */ (type_expression);
2302
+ closing_name_element =
2303
+ node.selfClosing || node.unclosed ? undefined : /** @type {AST.Node} */ (type_expression);
1994
2304
  } else {
1995
- opening_name_element = b.jsx_id(type_expression);
2305
+ opening_name_element = b.jsx_id(/** @type {string} */ (type_expression));
1996
2306
  // For tracked identifiers (dynamic components), adjust the loc to skip the '@' prefix
1997
2307
  // and add metadata for mapping
1998
2308
  if (node.id.tracked && node.id.loc) {
@@ -2009,6 +2319,7 @@ function transform_ts_child(node, context) {
2009
2319
  opening_name_element.metadata = {
2010
2320
  original_name: node.metadata.original_name,
2011
2321
  is_capitalized: true,
2322
+ path: [...node.metadata.path],
2012
2323
  };
2013
2324
  }
2014
2325
  } else {
@@ -2020,18 +2331,19 @@ function transform_ts_child(node, context) {
2020
2331
  },
2021
2332
  end: {
2022
2333
  line: node.loc.start.line,
2023
- column: node.loc.start.column + 2 + type_expression.length,
2334
+ column: node.loc.start.column + 2 + /** @type {string} */ (type_expression).length,
2024
2335
  },
2025
2336
  };
2026
2337
  }
2027
2338
 
2028
2339
  if (!node.selfClosing && !node.unclosed) {
2029
- closing_name_element = b.jsx_id(type_expression);
2340
+ closing_name_element = b.jsx_id(/** @type {string} */ (type_expression));
2030
2341
  // For tracked identifiers, also adjust closing tag location
2031
2342
  if (node.id.tracked && node.id.loc) {
2032
2343
  // Calculate position relative to closing tag
2033
2344
  // Format: </@identifier>
2034
- const closing_tag_start = node.loc.end.column - type_expression.length - 3; // </@
2345
+ const closing_tag_start =
2346
+ node.loc.end.column - /** @type {string} */ (type_expression).length - 3; // </@
2035
2347
  closing_name_element.loc = {
2036
2348
  start: {
2037
2349
  line: node.loc.end.line,
@@ -2042,7 +2354,8 @@ function transform_ts_child(node, context) {
2042
2354
  column:
2043
2355
  closing_tag_start +
2044
2356
  3 +
2045
- (node.metadata?.original_name?.length || type_expression.length),
2357
+ (node.metadata?.original_name?.length ||
2358
+ /** @type {string} */ (type_expression).length),
2046
2359
  },
2047
2360
  };
2048
2361
  // Add metadata if this was capitalized
@@ -2050,13 +2363,14 @@ function transform_ts_child(node, context) {
2050
2363
  closing_name_element.metadata = {
2051
2364
  original_name: node.metadata.original_name,
2052
2365
  is_capitalized: true,
2366
+ path: [...node.metadata.path],
2053
2367
  };
2054
2368
  }
2055
2369
  } else {
2056
2370
  closing_name_element.loc = {
2057
2371
  start: {
2058
2372
  line: node.loc.end.line,
2059
- column: node.loc.end.column - type_expression.length - 1,
2373
+ column: node.loc.end.column - /** @type {string} */ (type_expression).length - 1,
2060
2374
  },
2061
2375
  end: {
2062
2376
  line: node.loc.end.line,
@@ -2068,13 +2382,11 @@ function transform_ts_child(node, context) {
2068
2382
  }
2069
2383
 
2070
2384
  let jsxElement = b.jsx_element(
2071
- opening_name_element,
2072
- node.loc,
2385
+ /** @type {ESTreeJSX.JSXIdentifier} */ (opening_name_element),
2386
+ node,
2073
2387
  attributes,
2074
2388
  children,
2075
- node.selfClosing,
2076
- node.unclosed,
2077
- closing_name_element,
2389
+ /** @type {ESTreeJSX.JSXClosingElement['name'] | undefined} */ (closing_name_element),
2078
2390
  );
2079
2391
 
2080
2392
  // Calculate the location for the entire JSXClosingElement (including </ and >)
@@ -2084,8 +2396,9 @@ function transform_ts_child(node, context) {
2084
2396
  // - '<' is at node.loc.end.column - type_expression.length - 3
2085
2397
  // - '>' is at node.loc.end.column - 1
2086
2398
  const tag_name_length = node.id.tracked
2087
- ? (node.metadata?.original_name?.length || type_expression.length) + 1 // +1 for '@'
2088
- : type_expression.length;
2399
+ ? (node.metadata?.original_name?.length || /** @type {string} */ (type_expression).length) +
2400
+ 1 // +1 for '@'
2401
+ : /** @type {string} */ (type_expression).length;
2089
2402
 
2090
2403
  jsxElement.closingElement.loc = {
2091
2404
  start: {
@@ -2104,19 +2417,22 @@ function transform_ts_child(node, context) {
2104
2417
  jsxElement.metadata = {
2105
2418
  ts_name: node.metadata.ts_name,
2106
2419
  original_name: node.metadata.original_name,
2420
+ path: [...node.metadata.path],
2107
2421
  };
2108
2422
  }
2109
2423
  // For unclosed elements, push the JSXElement directly without wrapping in ExpressionStatement
2110
2424
  // This keeps it in the AST for mappings but avoids adding a semicolon
2111
2425
  if (node.unclosed) {
2112
- state.init.push(jsxElement);
2426
+ state.init?.push(/** @type {AST.Statement} */ (/** @type {unknown} */ (jsxElement)));
2113
2427
  } else {
2114
- state.init.push(b.stmt(jsxElement));
2428
+ state.init?.push(b.stmt(jsxElement));
2115
2429
  }
2116
2430
  } else if (node.type === 'IfStatement') {
2117
- const consequent_scope = context.state.scopes.get(node.consequent);
2431
+ const consequent_scope = /** @type {ScopeInterface} */ (
2432
+ context.state.scopes.get(node.consequent)
2433
+ );
2118
2434
  const consequent = b.block(
2119
- transform_body(node.consequent.body, {
2435
+ transform_body(/** @type {AST.BlockStatement} */ (node.consequent).body, {
2120
2436
  ...context,
2121
2437
  state: { ...context.state, scope: consequent_scope },
2122
2438
  }),
@@ -2125,11 +2441,10 @@ function transform_ts_child(node, context) {
2125
2441
  let alternate;
2126
2442
 
2127
2443
  if (node.alternate !== null) {
2128
- const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
2129
- let alternate_body = node.alternate.body;
2130
- if (node.alternate.type === 'IfStatement') {
2131
- alternate_body = [node.alternate];
2132
- }
2444
+ const alternate_node = /** @type {AST.BlockStatement | AST.IfStatement} */ (node.alternate);
2445
+ const alternate_scope = context.state.scopes.get(alternate_node) || context.state.scope;
2446
+ const alternate_body =
2447
+ alternate_node.type === 'IfStatement' ? [alternate_node] : alternate_node.body;
2133
2448
  alternate = b.block(
2134
2449
  transform_body(alternate_body, {
2135
2450
  ...context,
@@ -2138,7 +2453,7 @@ function transform_ts_child(node, context) {
2138
2453
  );
2139
2454
  }
2140
2455
 
2141
- state.init.push(b.if(visit(node.test), consequent, alternate));
2456
+ state.init?.push(b.if(/** @type {AST.Expression} */ (visit(node.test)), consequent, alternate));
2142
2457
  } else if (node.type === 'SwitchStatement') {
2143
2458
  const cases = [];
2144
2459
 
@@ -2151,28 +2466,40 @@ function transform_ts_child(node, context) {
2151
2466
  });
2152
2467
 
2153
2468
  cases.push(
2154
- b.switch_case(switch_case.test ? context.visit(switch_case.test) : null, consequent_body),
2469
+ b.switch_case(
2470
+ switch_case.test ? /** @type {AST.Expression} */ (context.visit(switch_case.test)) : null,
2471
+ consequent_body,
2472
+ ),
2155
2473
  );
2156
2474
  }
2157
2475
 
2158
- context.state.init.push(b.switch(context.visit(node.discriminant), cases));
2476
+ context.state.init?.push(
2477
+ b.switch(/** @type {AST.Expression} */ (context.visit(node.discriminant)), cases),
2478
+ );
2159
2479
  } else if (node.type === 'ForOfStatement') {
2160
- const body_scope = context.state.scopes.get(node.body);
2161
- const block_body = transform_body(node.body.body, {
2480
+ const body_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node.body));
2481
+ const block_body = transform_body(/** @type {AST.BlockStatement} */ (node.body).body, {
2162
2482
  ...context,
2163
2483
  state: { ...context.state, scope: body_scope },
2164
2484
  });
2165
2485
  if (node.key) {
2166
- block_body.unshift(b.stmt(visit(node.key)));
2486
+ block_body.unshift(b.stmt(/** @type {AST.Expression} */ (visit(node.key))));
2167
2487
  }
2168
2488
  if (node.index) {
2169
- block_body.unshift(b.let(visit(node.index), b.literal(0)));
2489
+ block_body.unshift(b.let(/** @type {AST.Identifier} */ (visit(node.index)), b.literal(0)));
2170
2490
  }
2171
2491
  const body = b.block(block_body);
2172
2492
 
2173
- state.init.push(b.for_of(visit(node.left), visit(node.right), body, node.await));
2493
+ state.init?.push(
2494
+ b.for_of(
2495
+ /** @type {AST.Pattern} */ (visit(node.left)),
2496
+ /** @type {AST.Expression} */ (visit(node.right)),
2497
+ body,
2498
+ node.await,
2499
+ ),
2500
+ );
2174
2501
  } else if (node.type === 'TryStatement') {
2175
- const try_scope = context.state.scopes.get(node.block);
2502
+ const try_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node.block));
2176
2503
  const try_body = b.block(
2177
2504
  transform_body(node.block.body, {
2178
2505
  ...context,
@@ -2182,7 +2509,9 @@ function transform_ts_child(node, context) {
2182
2509
 
2183
2510
  let catch_handler = null;
2184
2511
  if (node.handler) {
2185
- const catch_scope = context.state.scopes.get(node.handler.body);
2512
+ const catch_scope = /** @type {ScopeInterface} */ (
2513
+ context.state.scopes.get(node.handler.body)
2514
+ );
2186
2515
  const catch_body = b.block(
2187
2516
  transform_body(node.handler.body.body, {
2188
2517
  ...context,
@@ -2194,7 +2523,7 @@ function transform_ts_child(node, context) {
2194
2523
 
2195
2524
  let pending_block = null;
2196
2525
  if (node.pending) {
2197
- const pending_scope = context.state.scopes.get(node.pending);
2526
+ const pending_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node.pending));
2198
2527
  pending_block = b.try_item_block(
2199
2528
  transform_body(node.pending.body, {
2200
2529
  ...context,
@@ -2206,7 +2535,9 @@ function transform_ts_child(node, context) {
2206
2535
 
2207
2536
  let finally_block = null;
2208
2537
  if (node.finalizer) {
2209
- const finally_scope = context.state.scopes.get(node.finalizer);
2538
+ const finally_scope = /** @type {ScopeInterface} */ (
2539
+ context.state.scopes.get(node.finalizer)
2540
+ );
2210
2541
  finally_block = b.block(
2211
2542
  transform_body(node.finalizer.body, {
2212
2543
  ...context,
@@ -2215,33 +2546,46 @@ function transform_ts_child(node, context) {
2215
2546
  );
2216
2547
  }
2217
2548
 
2218
- state.init.push(b.try(try_body, catch_handler, finally_block, pending_block));
2549
+ state.init?.push(b.try(try_body, catch_handler, finally_block, pending_block));
2219
2550
  } else if (node.type === 'Component') {
2220
2551
  const component = visit(node, state);
2221
2552
 
2222
- state.init.push(component);
2553
+ state.init?.push(/** @type {AST.Statement} */ (component));
2223
2554
  } else if (node.type === 'BreakStatement') {
2224
- state.init.push(b.break);
2555
+ state.init?.push(/** @type {AST.Statement} */ (b.break));
2225
2556
  } else if (node.type === 'TsxCompat') {
2226
- const children = node.children
2227
- .map((child) => visit(child, state))
2228
- .filter((child) => child.type !== 'JSXText' || child.value.trim() !== '');
2557
+ const children = /** @type {AST.TsxCompat['children']} */ (
2558
+ node.children
2559
+ .map((child) => visit(/** @type {AST.Node} */ (child), state))
2560
+ .filter((child) => child.type !== 'JSXText' || child.value.trim() !== '')
2561
+ );
2229
2562
 
2230
- state.init.push(b.stmt(b.jsx_fragment(children)));
2563
+ state.init?.push(b.stmt(b.jsx_fragment(children)));
2231
2564
  } else if (node.type === 'JSXExpressionContainer') {
2232
2565
  // JSX comments {/* ... */} are JSXExpressionContainer with JSXEmptyExpression
2233
2566
  // These should be preserved in the output as-is for prettier to handle
2234
- state.init.push(b.stmt(b.jsx_expression_container(visit(node.expression, state))));
2567
+ const jsx_container = b.jsx_expression_container(
2568
+ /** @type {AST.Expression} */ (visit(node.expression, state)),
2569
+ );
2570
+ state.init?.push(/** @type {AST.Statement} */ (/** @type {unknown} */ (jsx_container)));
2235
2571
  } else {
2236
2572
  throw new Error('TODO');
2237
2573
  }
2238
2574
  }
2239
2575
 
2576
+ /**
2577
+ *
2578
+ * @param {AST.Node[]} children
2579
+ * @param {VisitorClientContext} context
2580
+ */
2240
2581
  function transform_children(children, context) {
2241
2582
  const { visit, state, root } = context;
2242
2583
  const normalized = normalize_children(children, context);
2243
- const head_elements = children.filter(
2244
- (node) => node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'head',
2584
+
2585
+ const head_elements = /** @type {AST.Element[]} */ (
2586
+ children.filter(
2587
+ (node) => node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'head',
2588
+ )
2245
2589
  );
2246
2590
 
2247
2591
  const is_fragment =
@@ -2259,10 +2603,12 @@ function transform_children(children, context) {
2259
2603
  normalized.filter(
2260
2604
  (node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement',
2261
2605
  ).length > 1;
2606
+ /** @type {AST.Identifier | null} */
2262
2607
  let initial = null;
2263
2608
  let prev = null;
2264
2609
  let template_id = null;
2265
2610
 
2611
+ /** @param {AST.Node} node */
2266
2612
  const get_id = (node) => {
2267
2613
  return b.id(
2268
2614
  node.type == 'Element' && is_element_dom_element(node)
@@ -2273,11 +2619,12 @@ function transform_children(children, context) {
2273
2619
  );
2274
2620
  };
2275
2621
 
2622
+ /** @param {AST.Node} node */
2276
2623
  const create_initial = (node) => {
2277
2624
  const id = is_fragment ? b.id(state.scope.generate('fragment')) : get_id(node);
2278
2625
  initial = id;
2279
2626
  template_id = state.scope.generate('root');
2280
- state.setup.push(b.var(id, b.call(template_id)));
2627
+ state.setup?.push(b.var(id, b.call(template_id)));
2281
2628
  };
2282
2629
 
2283
2630
  for (const node of normalized) {
@@ -2293,22 +2640,23 @@ function transform_children(children, context) {
2293
2640
  node.type === 'Component'
2294
2641
  ) {
2295
2642
  const metadata = { await: false };
2296
- state.init.push(visit(node, { ...state, metadata }));
2643
+ state.init?.push(/** @type {AST.Statement} */ (visit(node, { ...state, metadata })));
2297
2644
  if (metadata.await) {
2298
- state.init.push(b.if(b.call('_$_.aborted'), b.return(null)));
2645
+ state.init?.push(b.if(b.call('_$_.aborted'), b.return(null)));
2299
2646
  if (state.metadata?.await === false) {
2300
2647
  state.metadata.await = true;
2301
2648
  }
2302
2649
  }
2303
2650
  } else if (state.to_ts) {
2304
- transform_ts_child(node, { visit, state });
2651
+ transform_ts_child(node, /** @type {VisitorClientContext} */ ({ visit, state }));
2305
2652
  } else {
2306
2653
  let metadata;
2307
- let expression;
2654
+ /** @type {AST.Expression | undefined} */
2655
+ let expression = undefined;
2308
2656
  let isCreateTextOnly = false;
2309
2657
  if (node.type === 'Text' || node.type === 'Html') {
2310
2658
  metadata = { tracking: false, await: false };
2311
- expression = visit(node.expression, { ...state, metadata });
2659
+ expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
2312
2660
  isCreateTextOnly =
2313
2661
  node.type === 'Text' && normalized.length === 1 && expression.type === 'Literal';
2314
2662
  }
@@ -2318,30 +2666,32 @@ function transform_children(children, context) {
2318
2666
  }
2319
2667
 
2320
2668
  const current_prev = prev;
2669
+ /** @type {AST.Identifier | null} */
2321
2670
  let cached;
2671
+ /** @param {boolean} [is_controlled] */
2322
2672
  const flush_node = (is_controlled) => {
2323
2673
  if (cached && !is_controlled) {
2324
2674
  return cached;
2325
2675
  } else if (current_prev !== null) {
2326
2676
  const id = get_id(node);
2327
- state.setup.push(b.var(id, b.call('_$_.sibling', current_prev())));
2677
+ state.setup?.push(b.var(id, b.call('_$_.sibling', current_prev())));
2328
2678
  cached = id;
2329
2679
  return id;
2330
2680
  } else if (initial !== null) {
2331
2681
  if (is_fragment) {
2332
2682
  const id = get_id(node);
2333
- state.setup.push(b.var(id, b.call('_$_.child_frag', initial)));
2683
+ state.setup?.push(b.var(id, b.call('_$_.child_frag', initial)));
2334
2684
  cached = id;
2335
2685
  return id;
2336
2686
  }
2337
2687
  return initial;
2338
2688
  } else if (state.flush_node !== null) {
2339
2689
  if (is_controlled) {
2340
- return state.flush_node();
2690
+ return state.flush_node?.();
2341
2691
  }
2342
2692
 
2343
2693
  const id = get_id(node);
2344
- state.setup.push(b.var(id, b.call('_$_.child', state.flush_node())));
2694
+ state.setup?.push(b.var(id, b.call('_$_.child', state.flush_node?.())));
2345
2695
  cached = id;
2346
2696
  return id;
2347
2697
  } else {
@@ -2354,80 +2704,117 @@ function transform_children(children, context) {
2354
2704
  const is_controlled = normalized.length === 1 && !root;
2355
2705
 
2356
2706
  if (node.type === 'Element') {
2357
- visit(node, { ...state, flush_node, namespace: state.namespace });
2358
- } else if (node.type === 'HeadElement') {
2359
- visit(node, { ...state, flush_node, namespace: state.namespace });
2707
+ visit(node, {
2708
+ ...state,
2709
+ flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
2710
+ namespace: state.namespace,
2711
+ });
2360
2712
  } else if (node.type === 'TsxCompat') {
2361
- visit(node, { ...state, flush_node, namespace: state.namespace });
2713
+ visit(node, {
2714
+ ...state,
2715
+ flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
2716
+ namespace: state.namespace,
2717
+ });
2362
2718
  } else if (node.type === 'Html') {
2363
- context.state.template.push('<!>');
2719
+ context.state.template?.push('<!>');
2364
2720
 
2365
2721
  const id = flush_node();
2366
- state.update.push({
2367
- operation: b.stmt(
2368
- b.call(
2369
- '_$_.html',
2370
- id,
2371
- b.thunk(expression),
2372
- state.namespace === 'svg' && b.true,
2373
- state.namespace === 'mathml' && b.true,
2722
+ state.update?.push({
2723
+ operation: () =>
2724
+ b.stmt(
2725
+ b.call(
2726
+ '_$_.html',
2727
+ id,
2728
+ b.thunk(/** @type {AST.Expression} */ (expression)),
2729
+ state.namespace === 'svg' && b.true,
2730
+ state.namespace === 'mathml' && b.true,
2731
+ ),
2374
2732
  ),
2375
- ),
2376
2733
  });
2377
2734
  } else if (node.type === 'Text') {
2378
- if (metadata.tracking) {
2379
- state.template.push(' ');
2735
+ if (metadata?.tracking) {
2736
+ state.template?.push(' ');
2380
2737
  const id = flush_node();
2381
- state.update.push({
2738
+ state.update?.push({
2382
2739
  operation: (key) => b.stmt(b.call('_$_.set_text', id, key)),
2383
- expression,
2740
+ expression: /** @type {AST.Expression} */ (expression),
2384
2741
  identity: node.expression,
2385
2742
  initial: b.literal(' '),
2386
2743
  });
2387
2744
  if (metadata.await) {
2388
- state.update.async = true;
2745
+ /** @type {NonNullable<TransformClientState['update']>} */ (state.update).async = true;
2389
2746
  }
2390
2747
  } else if (normalized.length === 1) {
2391
- if (expression.type === 'Literal') {
2392
- if (state.template.length > 0) {
2393
- state.template.push(escape_html(expression.value));
2748
+ const expr = /** @type {AST.Expression} */ (expression);
2749
+ if (expr.type === 'Literal') {
2750
+ if (
2751
+ /** @type {NonNullable<TransformClientState['template']>} */ (state.template).length >
2752
+ 0
2753
+ ) {
2754
+ state.template?.push(escape_html(expr.value));
2394
2755
  } else {
2395
2756
  const id = flush_node();
2396
- state.init.push(b.var(id, b.call('_$_.create_text', expression)));
2397
- state.final.push(b.stmt(b.call('_$_.append', b.id('__anchor'), id)));
2757
+ state.init?.push(
2758
+ b.var(/** @type {AST.Identifier} */ (id), b.call('_$_.create_text', expr)),
2759
+ );
2760
+ state.final?.push(b.stmt(b.call('_$_.append', b.id('__anchor'), id)));
2398
2761
  }
2399
2762
  } else {
2400
2763
  const id = flush_node();
2401
- state.template.push(' ');
2764
+ state.template?.push(' ');
2402
2765
  // avoid set_text overhead for single text nodes
2403
- state.init.push(b.stmt(b.assignment('=', b.member(id, b.id('nodeValue')), expression)));
2766
+ state.init?.push(
2767
+ b.stmt(
2768
+ b.assignment(
2769
+ '=',
2770
+ b.member(/** @type {AST.Identifier} */ (id), b.id('nodeValue')),
2771
+ expr,
2772
+ ),
2773
+ ),
2774
+ );
2404
2775
  }
2405
2776
  } else {
2406
2777
  // Handle Text nodes in fragments
2407
- state.template.push(' ');
2778
+ state.template?.push(' ');
2408
2779
  const id = flush_node();
2409
- state.update.push({
2780
+ state.update?.push({
2410
2781
  operation: (key) => b.stmt(b.call('_$_.set_text', id, key)),
2411
- expression,
2782
+ expression: /** @type {AST.Expression} */ (expression),
2412
2783
  identity: node.expression,
2413
2784
  initial: b.literal(' '),
2414
2785
  });
2415
- if (metadata.await) {
2416
- state.update.async = true;
2786
+ if (metadata?.await) {
2787
+ /** @type {NonNullable<TransformClientState['update']>} */ (state.update).async = true;
2417
2788
  }
2418
2789
  }
2419
2790
  } else if (node.type === 'ForOfStatement') {
2420
2791
  node.is_controlled = is_controlled;
2421
- visit(node, { ...state, flush_node, namespace: state.namespace });
2792
+ visit(node, {
2793
+ ...state,
2794
+ flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
2795
+ namespace: state.namespace,
2796
+ });
2422
2797
  } else if (node.type === 'IfStatement') {
2423
2798
  node.is_controlled = is_controlled;
2424
- visit(node, { ...state, flush_node, namespace: state.namespace });
2799
+ visit(node, {
2800
+ ...state,
2801
+ flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
2802
+ namespace: state.namespace,
2803
+ });
2425
2804
  } else if (node.type === 'TryStatement') {
2426
2805
  node.is_controlled = is_controlled;
2427
- visit(node, { ...state, flush_node, namespace: state.namespace });
2806
+ visit(node, {
2807
+ ...state,
2808
+ flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
2809
+ namespace: state.namespace,
2810
+ });
2428
2811
  } else if (node.type === 'SwitchStatement') {
2429
2812
  node.is_controlled = is_controlled;
2430
- visit(node, { ...state, flush_node, namespace: state.namespace });
2813
+ visit(node, {
2814
+ ...state,
2815
+ flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
2816
+ namespace: state.namespace,
2817
+ });
2431
2818
  } else if (node.type === 'BreakStatement') {
2432
2819
  // do nothing
2433
2820
  } else {
@@ -2441,9 +2828,11 @@ function transform_children(children, context) {
2441
2828
  }
2442
2829
 
2443
2830
  if (context.state.inside_head) {
2444
- const title_element = children.find(
2445
- (node) =>
2446
- node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'title',
2831
+ const title_element = /** @type {AST.Element} */ (
2832
+ children.find(
2833
+ (node) =>
2834
+ node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'title',
2835
+ )
2447
2836
  );
2448
2837
 
2449
2838
  if (title_element) {
@@ -2460,14 +2849,29 @@ function transform_children(children, context) {
2460
2849
  } else if (template_namespace === 'mathml') {
2461
2850
  flags |= TEMPLATE_MATHML_NAMESPACE;
2462
2851
  }
2463
- state.final.push(b.stmt(b.call('_$_.append', b.id('__anchor'), initial)));
2852
+ state.final?.push(b.stmt(b.call('_$_.append', b.id('__anchor'), initial)));
2464
2853
  state.hoisted.push(
2465
- b.var(template_id, b.call('_$_.template', join_template(state.template), b.literal(flags))),
2854
+ b.var(
2855
+ template_id,
2856
+ b.call(
2857
+ '_$_.template',
2858
+ join_template(
2859
+ /** @type {NonNullable<TransformClientState['template']>} */ (state.template),
2860
+ ),
2861
+ b.literal(flags),
2862
+ ),
2863
+ ),
2466
2864
  );
2467
2865
  }
2468
2866
  }
2469
2867
 
2868
+ /**
2869
+ * @param {AST.Node[]} body
2870
+ * @param {TransformClientContext} context
2871
+ * @returns {AST.Statement[]}
2872
+ */
2470
2873
  function transform_body(body, { visit, state }) {
2874
+ /** @type {TransformClientState} */
2471
2875
  const body_state = {
2472
2876
  ...state,
2473
2877
  template: [],
@@ -2480,18 +2884,29 @@ function transform_body(body, { visit, state }) {
2480
2884
  inside_head: state.inside_head || false,
2481
2885
  };
2482
2886
 
2483
- transform_children(body, { visit, state: body_state, root: true });
2887
+ transform_children(
2888
+ body,
2889
+ /** @type {VisitorClientContext} */ ({ visit, state: body_state, root: true }),
2890
+ );
2484
2891
 
2485
- if (body_state.update.length > 0) {
2486
- if (state.to_ts) {
2487
- // In TypeScript mode, just add the update statements directly
2488
- body_state.init.push(...body_state.update);
2489
- } else {
2490
- apply_updates(body_state.init, body_state.update, state);
2892
+ if (/** @type {NonNullable<TransformClientState['update']>} */ (body_state.update).length > 0) {
2893
+ if (!state.to_ts) {
2894
+ apply_updates(
2895
+ /** @type {NonNullable<TransformClientState['init']>} */ (body_state.init),
2896
+ /** @type {NonNullable<TransformClientState['update']>} */ (body_state.update),
2897
+ state,
2898
+ );
2491
2899
  }
2900
+
2901
+ // NOTE: transform_children in `to_ts` mode does NOT add to body_state.update
2902
+ // So, we skip adding doing any actions with body_state.update
2492
2903
  }
2493
2904
 
2494
- return [...body_state.setup, ...body_state.init, ...body_state.final];
2905
+ return [
2906
+ .../** @type {NonNullable<TransformClientState['setup']>} */ (body_state.setup),
2907
+ .../** @type {AST.Statement[]} */ (body_state.init),
2908
+ .../** @type {NonNullable<TransformClientState['final']>} */ (body_state.final),
2909
+ ];
2495
2910
  }
2496
2911
 
2497
2912
  /**
@@ -2499,12 +2914,16 @@ function transform_body(body, { visit, state }) {
2499
2914
  * @returns {Object} TSX language handler with TypeScript return type support
2500
2915
  */
2501
2916
  function create_tsx_with_typescript_support() {
2502
- const base_tsx = tsx();
2917
+ const base_tsx = /** @type {ESRap.Visitors<AST.Node>} */ (tsx());
2503
2918
 
2504
2919
  // Add custom TypeScript node handlers that aren't in tsx
2505
2920
 
2506
- // Shared handler for function-like nodes to support component->function mapping
2507
- // Creates source maps for 'function' keyword by passing node to context.write()
2921
+ /**
2922
+ * Shared handler for function-like nodes to support component->function mapping
2923
+ * Creates source maps for 'function' keyword by passing node to context.write()
2924
+ * @param {AST.Function} node
2925
+ * @param {ESRap.Context} context
2926
+ */
2508
2927
  const handle_function = (node, context) => {
2509
2928
  if (node.async) {
2510
2929
  context.write('async ');
@@ -2516,12 +2935,15 @@ function create_tsx_with_typescript_support() {
2516
2935
  if (node.generator) {
2517
2936
  context.write('*');
2518
2937
  }
2938
+
2939
+ const id = /** @type {AST.FunctionExpression | AST.FunctionDeclaration} */ (node).id;
2940
+
2519
2941
  // FunctionDeclaration always has a space before id, FunctionExpression only if id exists
2520
- if (node.type === 'FunctionDeclaration' || node.id) {
2942
+ if (node.type === 'FunctionDeclaration' || id) {
2521
2943
  context.write(' ');
2522
2944
  }
2523
- if (node.id) {
2524
- context.visit(node.id);
2945
+ if (id) {
2946
+ context.visit(id);
2525
2947
  }
2526
2948
  if (node.typeParameters) {
2527
2949
  context.visit(node.typeParameters);
@@ -2543,10 +2965,14 @@ function create_tsx_with_typescript_support() {
2543
2965
 
2544
2966
  return {
2545
2967
  ...base_tsx,
2546
- // Custom handler for Property nodes to prevent method shorthand for components
2547
- // When a component is transformed to a FunctionExpression, we want to preserve
2548
- // the explicit syntax (key: function name() {}) instead of method shorthand (key() {})
2549
- // This ensures proper source mapping for the 'function'/'component' keyword
2968
+ /**
2969
+ * Custom handler for Property nodes to prevent method shorthand for components
2970
+ * When a component is transformed to a FunctionExpression, we want to preserve
2971
+ * the explicit syntax (key: function name() {}) instead of method shorthand (key() {})
2972
+ * This ensures proper source mapping for the 'function'/'component' keyword
2973
+ * @param {AST.Property} node
2974
+ * @param {ESRap.Context} context
2975
+ */
2550
2976
  Property(node, context) {
2551
2977
  // Check if the value is a function that was originally a component
2552
2978
  const isComponent =
@@ -2593,14 +3019,18 @@ function create_tsx_with_typescript_support() {
2593
3019
  context.write(node.computed ? ']: ' : ': ');
2594
3020
  context.visit(node.value);
2595
3021
  } else {
2596
- base_tsx.Property(node, context);
3022
+ base_tsx.Property?.(node, context);
2597
3023
  }
2598
3024
  } else {
2599
3025
  // Use default handler for non-component properties
2600
- base_tsx.Property(node, context);
3026
+ base_tsx.Property?.(node, context);
2601
3027
  }
2602
3028
  },
2603
- // Custom handler for JSXClosingElement to ensure closing tag brackets have source mappings
3029
+ /**
3030
+ * Custom handler for JSXClosingElement to ensure closing tag brackets have source mappings
3031
+ * @param {ESTreeJSX.JSXClosingElement} node
3032
+ * @param {ESRap.Context} context
3033
+ */
2604
3034
  JSXClosingElement(node, context) {
2605
3035
  // Set location for '<' then write '</'
2606
3036
  if (node.loc) {
@@ -2620,14 +3050,18 @@ function create_tsx_with_typescript_support() {
2620
3050
  context.write('>');
2621
3051
  }
2622
3052
  },
2623
- // Custom handler for ArrayPattern to ensure typeAnnotation is visited
2624
- // esrap's TypeScript handler doesn't visit typeAnnotation for ArrayPattern (only for ObjectPattern)
3053
+ /**
3054
+ * Custom handler for ArrayPattern to ensure typeAnnotation is visited
3055
+ * esrap's TypeScript handler doesn't visit typeAnnotation for ArrayPattern (only for ObjectPattern)
3056
+ * @param {AST.ArrayPattern} node
3057
+ * @param {ESRap.Context} context
3058
+ */
2625
3059
  ArrayPattern(node, context) {
2626
3060
  context.write('[');
2627
3061
  for (let i = 0; i < node.elements.length; i++) {
2628
3062
  if (i > 0) context.write(', ');
2629
3063
  if (node.elements[i]) {
2630
- context.visit(node.elements[i]);
3064
+ context.visit(/** @type {AST.Pattern} */ (node.elements[i]));
2631
3065
  }
2632
3066
  }
2633
3067
  context.write(']');
@@ -2636,19 +3070,31 @@ function create_tsx_with_typescript_support() {
2636
3070
  context.visit(node.typeAnnotation);
2637
3071
  }
2638
3072
  },
2639
- // Custom handler for FunctionDeclaration to support component->function mapping
2640
- // Needed for volar mappings and intellisense on function or component keyword
3073
+ /**
3074
+ * Custom handler for FunctionDeclaration to support component->function mapping
3075
+ * Needed for volar mappings and intellisense on function or component keyword
3076
+ * @param {AST.FunctionDeclaration} node
3077
+ * @param {ESRap.Context} context
3078
+ */
2641
3079
  FunctionDeclaration(node, context) {
2642
3080
  handle_function(node, context);
2643
3081
  },
2644
- // Custom handler for FunctionExpression to support component->function mapping
2645
- // This is used for components transformed by the Component visitor
3082
+ /**
3083
+ * Custom handler for FunctionExpression to support component->function mapping
3084
+ * This is used for components transformed by the Component visitor
3085
+ * @param {AST.FunctionExpression} node
3086
+ * @param {ESRap.Context} context
3087
+ */
2646
3088
  FunctionExpression(node, context) {
2647
3089
  handle_function(node, context);
2648
3090
  },
2649
- // Custom handler for ImportDeclaration to ensure 'import' keyword has source mapping
2650
- // This creates a source map entry at the start of the import statement
2651
- // Esrap's default handler writes 'import' without passing the node, so no source map entry
3091
+ /**
3092
+ * Custom handler for ImportDeclaration to ensure 'import' keyword has source mapping
3093
+ * This creates a source map entry at the start of the import statement
3094
+ * Esrap's default handler writes 'import' without passing the node, so no source map entry
3095
+ * @param {AST.ImportDeclaration} node
3096
+ * @param {ESRap.Context} context
3097
+ */
2652
3098
  ImportDeclaration(node, context) {
2653
3099
  // Write 'import' keyword with node location for source mapping
2654
3100
  context.write('import', node);
@@ -2702,9 +3148,13 @@ function create_tsx_with_typescript_support() {
2702
3148
  // Write source
2703
3149
  context.visit(node.source);
2704
3150
  },
2705
- // Custom handler for JSXOpeningElement to ensure '<' and '>' have source mappings
2706
- // Esrap's default handler only maps the tag name, not the brackets
2707
- // This creates mappings for the brackets so auto-close can find the cursor position
3151
+ /**
3152
+ * Custom handler for JSXOpeningElement to ensure '<' and '>' have source mappings
3153
+ * Esrap's default handler only maps the tag name, not the brackets
3154
+ * This creates mappings for the brackets so auto-close can find the cursor position
3155
+ * @param {ESTreeJSX.JSXOpeningElement} node
3156
+ * @param {ESRap.Context} context
3157
+ */
2708
3158
  JSXOpeningElement(node, context) {
2709
3159
  // Set location for '<'
2710
3160
  if (node.loc) {
@@ -2732,13 +3182,21 @@ function create_tsx_with_typescript_support() {
2732
3182
  context.write('>');
2733
3183
  }
2734
3184
  },
2735
- // Custom handler for TSParenthesizedType: (Type)
3185
+ /**
3186
+ * Custom handler for TSParenthesizedType: (Type)
3187
+ * @param {AST.TSParenthesizedType} node
3188
+ * @param {ESRap.Context} context
3189
+ */
2736
3190
  TSParenthesizedType(node, context) {
2737
3191
  context.write('(');
2738
- context.visit(node.typeAnnotation);
3192
+ context.visit(/** @type {AST.TSTypeAnnotation} */ (node.typeAnnotation));
2739
3193
  context.write(')');
2740
3194
  },
2741
- // Custom handler for TSMappedType: { [K in keyof T]: T[K] }
3195
+ /**
3196
+ * Custom handler for TSMappedType: { [K in keyof T]: T[K] }
3197
+ * @param {AST.TSMappedType} node
3198
+ * @param {ESRap.Context} context
3199
+ */
2742
3200
  TSMappedType(node, context) {
2743
3201
  context.write('{ ');
2744
3202
  if (node.readonly) {
@@ -2770,8 +3228,12 @@ function create_tsx_with_typescript_support() {
2770
3228
  }
2771
3229
  context.write(' }');
2772
3230
  },
2773
- // Custom handler for TSTypeParameter: K in T (for mapped types)
2774
- // acorn-ts has a bug where `in` is printed as `extends`, so we override it here
3231
+ /**
3232
+ * Custom handler for TSTypeParameter: K in T (for mapped types)
3233
+ * acorn-ts has a bug where `in` is printed as `extends`, so we override it here
3234
+ * @param {AST.TSTypeParameter} node
3235
+ * @param {ESRap.Context} context
3236
+ */
2775
3237
  TSTypeParameter(node, context) {
2776
3238
  // For mapped types, the name is just a string, not an Identifier node
2777
3239
  // Pass the node as second parameter to context.write() to create source map entry
@@ -2789,7 +3251,11 @@ function create_tsx_with_typescript_support() {
2789
3251
  context.visit(node.default);
2790
3252
  }
2791
3253
  },
2792
- // Override the ArrowFunctionExpression handler to support TypeScript return types
3254
+ /**
3255
+ * Override the ArrowFunctionExpression handler to support TypeScript return types
3256
+ * @param {AST.ArrowFunctionExpression} node
3257
+ * @param {ESRap.Context} context
3258
+ */
2793
3259
  ArrowFunctionExpression(node, context) {
2794
3260
  if (node.async) context.write('async ');
2795
3261
 
@@ -2821,7 +3287,11 @@ function create_tsx_with_typescript_support() {
2821
3287
  context.visit(node.body);
2822
3288
  }
2823
3289
  },
2824
- // Custom handler for TryStatement to support Ripple's pending block
3290
+ /**
3291
+ * Custom handler for TryStatement to support Ripple's pending block
3292
+ * @param {AST.TryStatement} node
3293
+ * @param {ESRap.Context} context
3294
+ */
2825
3295
  TryStatement(node, context) {
2826
3296
  context.write('try ');
2827
3297
  context.visit(node.block);
@@ -2830,8 +3300,10 @@ function create_tsx_with_typescript_support() {
2830
3300
  // Output the pending block with source mapping for the 'pending' keyword
2831
3301
  context.write(' ');
2832
3302
  context.location(
2833
- node.pending.loc.start.line,
2834
- node.pending.loc.start.column - 'pending '.length,
3303
+ /** @type {AST.SourceLocation} */
3304
+ (node.pending.loc).start.line,
3305
+ /** @type {AST.SourceLocation} */
3306
+ (node.pending.loc).start.column - 'pending '.length,
2835
3307
  );
2836
3308
  context.write('pending ');
2837
3309
  context.visit(node.pending);
@@ -2860,10 +3332,10 @@ function create_tsx_with_typescript_support() {
2860
3332
  * Transform Ripple AST to JavaScript/TypeScript
2861
3333
  * @param {string} filename - Source filename
2862
3334
  * @param {string} source - Original source code
2863
- * @param {any} analysis - Analysis result
3335
+ * @param {AnalysisResult} analysis - Analysis result
2864
3336
  * @param {boolean} to_ts - Whether to generate TypeScript output
2865
3337
  * @param {boolean} minify_css - Whether to minify CSS output
2866
- * @returns {{ ast: any, js: { code: string, map: any, post_processing_changes?: PostProcessingChanges, line_offsets?: LineOffsets }, css: any }}
3338
+ * @returns {{ ast: AST.Program, js: { code: string, map: SourceMapMappings, post_processing_changes?: PostProcessingChanges, line_offsets?: LineOffsets }, css: string }}
2867
3339
  */
2868
3340
  export function transform_client(filename, source, analysis, to_ts, minify_css) {
2869
3341
  /**
@@ -2885,13 +3357,17 @@ export function transform_client(filename, source, analysis, to_ts, minify_css)
2885
3357
  ) {
2886
3358
  for (const spec of stmt.specifiers || []) {
2887
3359
  if (spec.type === 'ImportSpecifier' && spec.imported && spec.local) {
2888
- ripple_user_imports.set(spec.imported.name, spec.local.name);
3360
+ ripple_user_imports.set(
3361
+ /** @type {AST.Identifier} */ (spec.imported).name,
3362
+ spec.local.name,
3363
+ );
2889
3364
  }
2890
3365
  }
2891
3366
  }
2892
3367
  }
2893
3368
  }
2894
3369
 
3370
+ /** @type {TransformClientState} */
2895
3371
  const state = {
2896
3372
  imports: new Set(),
2897
3373
  ripple_user_imports,
@@ -2900,6 +3376,7 @@ export function transform_client(filename, source, analysis, to_ts, minify_css)
2900
3376
  hoisted: [],
2901
3377
  setup: null,
2902
3378
  init: null,
3379
+ inside_head: false,
2903
3380
  update: null,
2904
3381
  final: null,
2905
3382
  flush_node: null,
@@ -2908,11 +3385,11 @@ export function transform_client(filename, source, analysis, to_ts, minify_css)
2908
3385
  stylesheets: [],
2909
3386
  to_ts,
2910
3387
  filename,
3388
+ namespace: 'html',
3389
+ metadata: {},
2911
3390
  };
2912
3391
 
2913
- const program = /** @type {Program} */ (
2914
- walk(/** @type {Node} */ (analysis.ast), { ...state, namespace: 'html' }, visitors)
2915
- );
3392
+ const program = /** @type {AST.Program} */ (walk(analysis.ast, { ...state }, visitors));
2916
3393
 
2917
3394
  for (const hoisted of state.hoisted) {
2918
3395
  program.body.unshift(hoisted);