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,15 @@
1
- /** @import * as ESTree from 'estree' */
1
+ /** @import * as AST from 'estree'; */
2
+ /** @import { SourceMapMappings } from '@jridgewell/sourcemap-codec'; */
3
+ /** @import { TSESTree } from '@typescript-eslint/types'; */
4
+ /**
5
+ @import {
6
+ TransformServerContext,
7
+ TransformServerState,
8
+ Visitors,
9
+ AnalysisResult,
10
+ ScopeInterface,
11
+ Visitor
12
+ } from '#compiler' */
2
13
 
3
14
  import * as b from '../../../../utils/builders.js';
4
15
  import { walk } from 'zimmerframe';
@@ -19,6 +30,9 @@ import { is_event_attribute } from '../../../../utils/events.js';
19
30
  import { render_stylesheets } from '../stylesheet.js';
20
31
  import { createHash } from 'node:crypto';
21
32
 
33
+ /**
34
+ * @param {TransformServerContext} context
35
+ */
22
36
  function add_ripple_internal_import(context) {
23
37
  if (!context.state.to_ts) {
24
38
  if (!context.state.imports.has(`import * as _$_ from 'ripple/internal/server'`)) {
@@ -27,13 +41,17 @@ function add_ripple_internal_import(context) {
27
41
  }
28
42
  }
29
43
 
44
+ /**
45
+ * @param {AST.Node[]} children
46
+ * @param {TransformServerContext} context
47
+ */
30
48
  function transform_children(children, context) {
31
- const { visit, state, root } = context;
49
+ const { visit, state } = context;
32
50
  const normalized = normalize_children(children, context);
33
51
 
34
52
  for (const node of normalized) {
35
53
  if (node.type === 'BreakStatement') {
36
- state.init.push(b.break);
54
+ state.init?.push(b.break);
37
55
  continue;
38
56
  }
39
57
  if (
@@ -48,33 +66,41 @@ function transform_children(children, context) {
48
66
  node.type === 'Component'
49
67
  ) {
50
68
  const metadata = { await: false };
51
- state.init.push(visit(node, { ...state, metadata }));
69
+ state.init?.push(/** @type {AST.Statement} */ (visit(node, { ...state, metadata })));
52
70
  if (metadata.await) {
53
- state.init.push(b.if(b.call('_$_.aborted'), b.return(null)));
71
+ state.init?.push(b.if(b.call('_$_.aborted'), b.return(null)));
54
72
  if (state.metadata?.await === false) {
55
73
  state.metadata.await = true;
56
74
  }
57
75
  }
58
76
  } else {
59
- visit(node, { ...state, root: false });
77
+ visit(node, { ...state });
60
78
  }
61
79
  }
62
80
  }
63
81
 
64
- function transform_body(body, { visit, state }) {
82
+ /**
83
+ * @param {AST.Node[]} body
84
+ * @param {TransformServerContext} context
85
+ * @returns {AST.Statement[]}
86
+ */
87
+ function transform_body(body, context) {
88
+ const { state } = context;
89
+ /** @type {TransformServerState} */
65
90
  const body_state = {
66
91
  ...state,
67
92
  init: [],
68
93
  metadata: state.metadata,
69
94
  };
70
95
 
71
- transform_children(body, { visit, state: body_state, root: true });
96
+ transform_children(body, { ...context, state: body_state });
72
97
 
73
- return body_state.init;
98
+ return /** @type {AST.Statement[]} */ (body_state.init);
74
99
  }
75
100
 
101
+ /** @type {Visitors<AST.Node, TransformServerState>} */
76
102
  const visitors = {
77
- _: function set_scope(node, { next, state }) {
103
+ _: (node, { next, state }) => {
78
104
  const scope = state.scopes.get(node);
79
105
 
80
106
  if (scope && scope !== state.scope) {
@@ -120,6 +146,26 @@ const visitors = {
120
146
  component_fn = b.async(component_fn);
121
147
  }
122
148
 
149
+ // Anonymous components return a FunctionExpression
150
+ if (!node.id) {
151
+ // For async anonymous components, we need to set .async on the function
152
+ if (metadata.await) {
153
+ // Use IIFE pattern: (fn => (fn.async = true, fn))(function() { ... })
154
+ return b.call(
155
+ b.arrow(
156
+ [b.id('fn')],
157
+ b.sequence([
158
+ b.assignment('=', b.member(b.id('fn'), b.id('async')), b.true),
159
+ b.id('fn'),
160
+ ]),
161
+ ),
162
+ component_fn,
163
+ );
164
+ }
165
+ return component_fn;
166
+ }
167
+
168
+ // Named components return a FunctionDeclaration
123
169
  const declaration = b.function_declaration(
124
170
  node.id,
125
171
  component_fn.params,
@@ -129,8 +175,8 @@ const visitors = {
129
175
 
130
176
  if (metadata.await) {
131
177
  const parent = context.path.at(-1);
132
- if (parent.type === 'Program' || parent.type === 'BlockStatement') {
133
- const body = parent.body;
178
+ if (parent?.type === 'Program' || parent?.type === 'BlockStatement') {
179
+ const body = /** @type {AST.RippleProgram} */ (parent).body;
134
180
  const index = body.indexOf(node);
135
181
  body.splice(
136
182
  index + 1,
@@ -160,7 +206,10 @@ const visitors = {
160
206
  const argsToUse = node.arguments.length > 0 ? node.arguments : callee.arguments;
161
207
  // For SSR, use regular Map/Set
162
208
  const constructorName = callee.type === 'TrackedMapExpression' ? 'Map' : 'Set';
163
- return b.new(b.id(constructorName), ...argsToUse.map((arg) => context.visit(arg)));
209
+ return b.new(
210
+ b.id(constructorName),
211
+ .../** @type {AST.Expression[]} */ (argsToUse.map((arg) => context.visit(arg))),
212
+ );
164
213
  }
165
214
 
166
215
  if (!context.state.to_ts) {
@@ -231,7 +280,7 @@ const visitors = {
231
280
  TSInstantiationExpression(node, context) {
232
281
  if (!context.state.to_ts) {
233
282
  // In JavaScript, just return the expression wrapped in parentheses
234
- return b.sequence([context.visit(node.expression)]);
283
+ return b.sequence(/** @type {AST.Expression[]} */ ([context.visit(node.expression)]));
235
284
  }
236
285
  return context.next();
237
286
  },
@@ -264,7 +313,8 @@ const visitors = {
264
313
  b.assignment(
265
314
  '=',
266
315
  b.member(b.id('_$_server_$_'), b.id(declaration.id.name)),
267
- context.visit(declaration),
316
+ /** @type {AST.Expression} */
317
+ (context.visit(declaration)),
268
318
  ),
269
319
  );
270
320
  } else {
@@ -288,6 +338,7 @@ const visitors = {
288
338
 
289
339
  const is_dom_element = is_element_dom_element(node);
290
340
  const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
341
+ /** @type {(AST.Property | AST.SpreadElement)[] | null} */
291
342
  const spread_attributes = is_spreading ? [] : null;
292
343
  const child_namespace = is_dom_element
293
344
  ? determine_namespace_for_children(node.id.name, state.namespace)
@@ -296,11 +347,15 @@ const visitors = {
296
347
  if (is_dom_element) {
297
348
  const is_void = is_void_element(node.id.name);
298
349
 
299
- state.init.push(
350
+ state.init?.push(
300
351
  b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`<${node.id.name}`))),
301
352
  );
302
353
  let class_attribute = null;
303
354
 
355
+ /**
356
+ * @param {string} name
357
+ * @param {string | number | bigint | boolean | RegExp | null | undefined} value
358
+ */
304
359
  const handle_static_attr = (name, value) => {
305
360
  const attr_str = ` ${name}${
306
361
  is_boolean_attribute(name) && value === true
@@ -314,9 +369,9 @@ const visitors = {
314
369
  is_boolean_attribute(name) && value === true
315
370
  ? b.literal(true)
316
371
  : b.literal(value === true ? '' : value);
317
- spread_attributes.push(b.prop('init', b.literal(name), actual_value));
372
+ spread_attributes?.push(b.prop('init', b.literal(name), actual_value));
318
373
  } else {
319
- state.init.push(
374
+ state.init?.push(
320
375
  b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(attr_str))),
321
376
  );
322
377
  }
@@ -347,9 +402,11 @@ const visitors = {
347
402
  continue;
348
403
  }
349
404
  const metadata = { tracking: false, await: false };
350
- const expression = visit(attr.value, { ...state, metadata });
405
+ const expression = /** @type {AST.Expression} */ (
406
+ visit(attr.value, { ...state, metadata })
407
+ );
351
408
 
352
- state.init.push(
409
+ state.init?.push(
353
410
  b.stmt(
354
411
  b.call(
355
412
  b.member(b.id('__output'), b.id('push')),
@@ -359,29 +416,34 @@ const visitors = {
359
416
  );
360
417
  }
361
418
  } else if (attr.type === 'SpreadAttribute') {
362
- spread_attributes.push(b.spread(visit(attr.argument, state)));
419
+ spread_attributes?.push(
420
+ b.spread(/** @type {AST.Expression} */ (visit(attr.argument, state))),
421
+ );
363
422
  }
364
423
  }
365
424
 
366
425
  if (class_attribute !== null) {
367
- if (class_attribute.value.type === 'Literal') {
368
- let value = class_attribute.value.value;
426
+ const attr_value = /** @type {AST.Expression} */ (class_attribute.value);
427
+ if (attr_value.type === 'Literal') {
428
+ let value = attr_value.value;
369
429
 
370
- if (node.metadata.scoped && state.component.css) {
430
+ if (node.metadata.scoped && state.component?.css) {
371
431
  value = `${state.component.css.hash} ${value}`;
372
432
  }
373
433
 
374
434
  handle_static_attr(class_attribute.name.name, value);
375
435
  } else {
376
436
  const metadata = { tracking: false, await: false };
377
- let expression = visit(class_attribute.value, { ...state, metadata });
437
+ let expression = /** @type {AST.Expression} */ (
438
+ visit(attr_value, { ...state, metadata })
439
+ );
378
440
 
379
- if (node.metadata.scoped && state.component.css) {
441
+ if (node.metadata.scoped && state.component?.css) {
380
442
  // Pass array to clsx so it can handle objects properly
381
443
  expression = b.array([expression, b.literal(state.component.css.hash)]);
382
444
  }
383
445
 
384
- state.init.push(
446
+ state.init?.push(
385
447
  b.stmt(
386
448
  b.call(
387
449
  b.member(b.id('__output'), b.id('push')),
@@ -390,21 +452,21 @@ const visitors = {
390
452
  ),
391
453
  );
392
454
  }
393
- } else if (node.metadata.scoped && state.component.css) {
455
+ } else if (node.metadata.scoped && state.component?.css) {
394
456
  const value = state.component.css.hash;
395
457
 
396
458
  handle_static_attr('class', value);
397
459
  }
398
460
 
399
461
  if (spread_attributes !== null && spread_attributes.length > 0) {
400
- state.init.push(
462
+ state.init?.push(
401
463
  b.stmt(
402
464
  b.call(
403
465
  b.member(b.id('__output'), b.id('push')),
404
466
  b.call(
405
467
  '_$_.spread_attrs',
406
468
  b.object(spread_attributes),
407
- node.metadata.scoped && state.component.css
469
+ node.metadata.scoped && state.component?.css
408
470
  ? b.literal(state.component.css.hash)
409
471
  : undefined,
410
472
  ),
@@ -413,24 +475,34 @@ const visitors = {
413
475
  );
414
476
  }
415
477
 
416
- state.init.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`>`))));
478
+ state.init?.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`>`))));
417
479
 
418
480
  if (!is_void) {
419
- transform_children(node.children, { visit, state: { ...state, root: false } });
481
+ transform_children(
482
+ node.children,
483
+ /** @type {TransformServerContext} */ ({ visit, state: { ...state } }),
484
+ );
420
485
 
421
- state.init.push(
486
+ state.init?.push(
422
487
  b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`</${node.id.name}>`))),
423
488
  );
424
489
  }
425
490
  } else {
491
+ /** @type {(AST.Property | AST.SpreadElement)[]} */
426
492
  const props = [];
493
+ /** @type {AST.Expression | null} */
427
494
  let children_prop = null;
428
495
 
429
496
  for (const attr of node.attributes) {
430
497
  if (attr.type === 'Attribute') {
431
498
  if (attr.name.type === 'Identifier') {
432
499
  const metadata = { tracking: false, await: false };
433
- let property = visit(attr.value, { ...state, metadata });
500
+ let property = /** @type {AST.Expression} */ (
501
+ visit(/** @type {AST.Expression} */ (attr.value), {
502
+ ...state,
503
+ metadata,
504
+ })
505
+ );
434
506
 
435
507
  if (attr.name.name === 'children') {
436
508
  children_prop = b.thunk(property);
@@ -438,13 +510,15 @@ const visitors = {
438
510
  }
439
511
 
440
512
  props.push(b.prop('init', attr.name, property));
441
- } else if (attr.type === 'SpreadAttribute') {
442
- props.push(
443
- b.spread(
444
- visit(attr.argument, { ...state, metadata: { ...state.metadata, spread: true } }),
445
- ),
446
- );
447
513
  }
514
+ } else if (attr.type === 'SpreadAttribute') {
515
+ props.push(
516
+ b.spread(
517
+ /** @type {AST.Expression} */ (
518
+ visit(attr.argument, { ...state, metadata: { ...state.metadata } })
519
+ ),
520
+ ),
521
+ );
448
522
  }
449
523
  }
450
524
 
@@ -452,30 +526,52 @@ const visitors = {
452
526
 
453
527
  for (const child of node.children) {
454
528
  if (child.type === 'Component') {
455
- const id = child.id;
456
- props.push(b.prop('init', id, visit(child, { ...state, namespace: child_namespace })));
529
+ // in this case, id cannot be null
530
+ // as these are direct children of the component
531
+ const id = /** @type {AST.Identifier} */ (child.id);
532
+ props.push(
533
+ b.prop(
534
+ 'init',
535
+ id,
536
+ /** @type {AST.Expression} */ (
537
+ visit(child, { ...state, namespace: child_namespace })
538
+ ),
539
+ ),
540
+ );
457
541
  } else {
458
542
  children_filtered.push(child);
459
543
  }
460
544
  }
461
545
 
462
546
  if (children_filtered.length > 0) {
463
- const component_scope = context.state.scopes.get(node);
464
- const children = visit(b.component(b.id('children'), [], children_filtered), {
465
- ...context.state,
466
- scope: component_scope,
467
- namespace: child_namespace,
468
- });
547
+ const component_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node));
548
+ const children = /** @type {AST.Expression} */ (
549
+ visit(b.component(b.id('children'), [], children_filtered), {
550
+ ...context.state,
551
+ scope: component_scope,
552
+ namespace: child_namespace,
553
+ })
554
+ );
469
555
 
470
556
  if (children_prop) {
471
- children_prop.body = b.logical('??', children_prop.body, children);
557
+ /** @type {AST.ArrowFunctionExpression} */ (children_prop).body = b.logical(
558
+ '??',
559
+ /** @type {AST.Expression} */ (
560
+ /** @type {AST.ArrowFunctionExpression} */ (children_prop).body
561
+ ),
562
+ children,
563
+ );
472
564
  } else {
473
565
  props.push(b.prop('init', b.id('children'), children));
474
566
  }
475
567
  }
476
568
 
477
569
  // For SSR, determine if we should await based on component metadata
478
- const component_call = b.call(visit(node.id, state), b.id('__output'), b.object(props));
570
+ const component_call = b.call(
571
+ /** @type {AST.Expression} */ (visit(node.id, state)),
572
+ b.id('__output'),
573
+ b.object(props),
574
+ );
479
575
 
480
576
  // Check if this is a locally defined component and if it's async
481
577
  const component_name = node.id.type === 'Identifier' ? node.id.name : null;
@@ -486,16 +582,16 @@ const visitors = {
486
582
  if (local_metadata) {
487
583
  // Component is defined locally - we know if it's async or not
488
584
  if (local_metadata.async) {
489
- state.init.push(b.stmt(b.await(component_call)));
585
+ state.init?.push(b.stmt(b.await(component_call)));
490
586
  } else {
491
- state.init.push(b.stmt(component_call));
587
+ state.init?.push(b.stmt(component_call));
492
588
  }
493
589
  } else {
494
590
  // Component is imported or dynamic - check .async property at runtime
495
591
  // Use if-statement instead of ternary to avoid parser issues with await in conditionals
496
- state.init.push(
592
+ state.init?.push(
497
593
  b.if(
498
- b.member(visit(node.id, state), b.id('async')),
594
+ b.member(/** @type {AST.Expression} */ (visit(node.id, state)), b.id('async')),
499
595
  b.block([b.stmt(b.await(component_call))]),
500
596
  b.block([b.stmt(component_call)]),
501
597
  ),
@@ -523,10 +619,15 @@ const visitors = {
523
619
  }),
524
620
  );
525
621
  cases.push(
526
- b.switch_case(switch_case.test ? context.visit(switch_case.test) : null, consequent.body),
622
+ b.switch_case(
623
+ switch_case.test ? /** @type {AST.Expression} */ (context.visit(switch_case.test)) : null,
624
+ consequent.body,
625
+ ),
527
626
  );
528
627
  }
529
- context.state.init.push(b.switch(context.visit(node.discriminant), cases));
628
+ context.state.init?.push(
629
+ b.switch(/** @type {AST.Expression} */ (context.visit(node.discriminant)), cases),
630
+ );
530
631
  },
531
632
 
532
633
  ForOfStatement(node, context) {
@@ -536,18 +637,23 @@ const visitors = {
536
637
  }
537
638
  const body_scope = context.state.scopes.get(node.body);
538
639
 
539
- const body = transform_body(node.body.body, {
640
+ const body = transform_body(/** @type {AST.BlockStatement} */ (node.body).body, {
540
641
  ...context,
541
- state: { ...context.state, scope: body_scope },
642
+ state: { ...context.state, scope: /** @type {ScopeInterface} */ (body_scope) },
542
643
  });
543
644
 
544
645
  if (node.index) {
545
- context.state.init.push(b.var(node.index, b.literal(0)));
546
- body.push(b.update('++', node.index));
646
+ context.state.init?.push(b.var(node.index, b.literal(0)));
647
+ body.push(b.stmt(b.update('++', node.index)));
547
648
  }
548
649
 
549
- context.state.init.push(
550
- b.for_of(context.visit(node.left), context.visit(node.right), b.block(body)),
650
+ context.state.init?.push(
651
+ b.for_of(
652
+ /** @type {AST.VariableDeclaration} */ (context.visit(node.left)),
653
+ /** @type {AST.Expression} */
654
+ (context.visit(node.right)),
655
+ b.block(body),
656
+ ),
551
657
  );
552
658
  },
553
659
 
@@ -558,17 +664,23 @@ const visitors = {
558
664
  }
559
665
 
560
666
  const consequent = b.block(
561
- transform_body(node.consequent.body, {
667
+ transform_body(/** @type {AST.BlockStatement} */ (node.consequent).body, {
562
668
  ...context,
563
- state: { ...context.state, scope: context.state.scopes.get(node.consequent) },
669
+ state: {
670
+ ...context.state,
671
+ scope: /** @type {ScopeInterface} */ (context.state.scopes.get(node.consequent)),
672
+ },
564
673
  }),
565
674
  );
566
675
 
676
+ /** @type {AST.BlockStatement | AST.IfStatement | null} */
567
677
  let alternate = null;
568
678
  if (node.alternate) {
569
679
  const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
570
680
  const alternate_body_nodes =
571
- node.alternate.type === 'IfStatement' ? [node.alternate] : node.alternate.body;
681
+ node.alternate.type === 'IfStatement'
682
+ ? [node.alternate]
683
+ : /** @type {AST.BlockStatement} */ (node.alternate).body;
572
684
 
573
685
  alternate = b.block(
574
686
  transform_body(alternate_body_nodes, {
@@ -578,7 +690,9 @@ const visitors = {
578
690
  );
579
691
  }
580
692
 
581
- context.state.init.push(b.if(context.visit(node.test), consequent, alternate));
693
+ context.state.init?.push(
694
+ b.if(/** @type {AST.Expression} */ (context.visit(node.test)), consequent, alternate),
695
+ );
582
696
  },
583
697
 
584
698
  AssignmentExpression(node, context) {
@@ -587,8 +701,10 @@ const visitors = {
587
701
  if (left.type === 'Identifier' && left.tracked) {
588
702
  return b.call(
589
703
  'set',
590
- context.visit(left, { ...context.state, metadata: { tracking: false } }),
591
- context.visit(node.right),
704
+ /** @type {AST.Expression} */
705
+ (context.visit(left, { ...context.state, metadata: { tracking: false } })),
706
+ /** @type {AST.Expression} */
707
+ (context.visit(node.right)),
592
708
  );
593
709
  }
594
710
 
@@ -599,17 +715,18 @@ const visitors = {
599
715
  return b.id('_$_server_$_');
600
716
  },
601
717
 
718
+ /** @type {Visitor<AST.ImportDeclaration, TransformServerState, AST.Node>} */
602
719
  ImportDeclaration(node, context) {
603
720
  if (!context.state.to_ts && node.importKind === 'type') {
604
721
  return b.empty;
605
722
  }
606
723
 
607
- return {
724
+ return /** @type {AST.ImportDeclaration} */ ({
608
725
  ...node,
609
726
  specifiers: node.specifiers
610
- .filter((spec) => spec.importKind !== 'type')
727
+ .filter((spec) => /** @type {AST.ImportSpecifier} */ (spec).importKind !== 'type')
611
728
  .map((spec) => context.visit(spec)),
612
- };
729
+ });
613
730
  },
614
731
 
615
732
  TryStatement(node, context) {
@@ -641,79 +758,97 @@ const visitors = {
641
758
  if (node.pending) {
642
759
  const pending_body = transform_body(node.pending.body, {
643
760
  ...context,
644
- state: { ...context.state, scope: context.state.scopes.get(node.pending) },
761
+ state: {
762
+ ...context.state,
763
+ scope: /** @type {ScopeInterface} */ (context.state.scopes.get(node.pending)),
764
+ },
645
765
  });
646
- context.state.init.push(...pending_body);
766
+ context.state.init?.push(...pending_body);
647
767
  }
648
768
 
649
769
  // For SSR with pending block: render the resolved content wrapped in async
650
770
  // In a streaming SSR implementation, we'd render pending first, then stream resolved
651
- const try_statements =
652
- node.handler !== null
653
- ? [
654
- b.try(
655
- b.block(body),
656
- b.catch_clause(
657
- node.handler.param || b.id('error'),
658
- b.block(
659
- transform_body(node.handler.body.body, {
660
- ...context,
661
- state: {
662
- ...context.state,
663
- scope: context.state.scopes.get(node.handler.body),
664
- },
665
- }),
666
- ),
667
- ),
771
+ const handler = node.handler;
772
+ /** @type {AST.Statement[]} */
773
+ let try_statements = body;
774
+ if (handler != null) {
775
+ try_statements = [
776
+ b.try(
777
+ b.block(body),
778
+ b.catch_clause(
779
+ handler.param || b.id('error'),
780
+ b.block(
781
+ transform_body(handler.body.body, {
782
+ ...context,
783
+ state: {
784
+ ...context.state,
785
+ scope: /** @type {ScopeInterface} */ (context.state.scopes.get(handler.body)),
786
+ },
787
+ }),
668
788
  ),
669
- ]
670
- : body;
789
+ ),
790
+ ),
791
+ ];
792
+ }
671
793
 
672
- context.state.init.push(
794
+ context.state.init?.push(
673
795
  b.stmt(b.await(b.call('_$_.async', b.thunk(b.block(try_statements), true)))),
674
796
  );
675
797
  } else {
676
798
  // No async, just regular try/catch
677
- if (node.handler !== null) {
799
+ if (node.handler != null) {
678
800
  const handler_body = transform_body(node.handler.body.body, {
679
801
  ...context,
680
- state: { ...context.state, scope: context.state.scopes.get(node.handler.body) },
802
+ state: {
803
+ ...context.state,
804
+ scope: /** @type {ScopeInterface} */ (context.state.scopes.get(node.handler.body)),
805
+ },
681
806
  });
682
807
 
683
- context.state.init.push(
808
+ context.state.init?.push(
684
809
  b.try(
685
810
  b.block(body),
686
811
  b.catch_clause(node.handler.param || b.id('error'), b.block(handler_body)),
687
812
  ),
688
813
  );
689
814
  } else {
690
- context.state.init.push(...body);
815
+ context.state.init?.push(...body);
691
816
  }
692
817
  }
693
818
  },
694
819
 
695
820
  AwaitExpression(node, context) {
696
- context.state.scope.server_block = true;
697
- context.inside_server_block = true;
698
- if (context.state.to_ts) {
821
+ const { state } = context;
822
+ state.scope.server_block = true;
823
+ state.inside_server_block = true;
824
+ if (state.to_ts) {
699
825
  return context.next();
700
826
  }
701
827
 
702
- if (context.state.metadata?.await === false) {
703
- context.state.metadata.await = true;
828
+ if (state.metadata?.await === false) {
829
+ state.metadata.await = true;
704
830
  }
705
831
 
706
- return b.await(context.visit(node.argument));
832
+ return b.await(/** @type {AST.AwaitExpression} */ (context.visit(node.argument)));
707
833
  },
708
834
 
709
835
  TrackedObjectExpression(node, context) {
710
836
  // For SSR, we just evaluate the object as-is since there's no reactivity
711
- return b.object(node.properties.map((prop) => context.visit(prop)));
837
+ return b.object(
838
+ /** @type {(AST.Property | AST.SpreadElement)[]} */
839
+ (node.properties.map((prop) => context.visit(prop))),
840
+ );
712
841
  },
713
842
 
714
843
  TrackedArrayExpression(node, context) {
715
844
  // For SSR, we just evaluate the array as-is since there's no reactivity
716
- return b.array(node.elements.map((el) => context.visit(el)));
845
+ return b.array(
846
+ /** @type {(AST.Expression | AST.SpreadElement)[]} */
847
+ (
848
+ /** @param {AST.Node} el */
849
+ node.elements.map((el) => context.visit(/** @type {AST.Node} */ (el)))
850
+ ),
851
+ );
717
852
  },
718
853
 
719
854
  MemberExpression(node, context) {
@@ -723,8 +858,11 @@ const visitors = {
723
858
  return b.call(
724
859
  'get',
725
860
  b.member(
726
- context.visit(node.object),
727
- node.computed ? context.visit(node.property) : node.property,
861
+ /** @type {AST.Expression} */
862
+ (context.visit(node.object)),
863
+ node.computed
864
+ ? /** @type {AST.Expression} */ (context.visit(node.property))
865
+ : node.property,
728
866
  node.computed,
729
867
  node.optional,
730
868
  ),
@@ -736,20 +874,20 @@ const visitors = {
736
874
 
737
875
  Text(node, { visit, state }) {
738
876
  const metadata = { await: false };
739
- let expression = visit(node.expression, { ...state, metadata });
877
+ let expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
740
878
 
741
879
  if (expression.type === 'Identifier' && expression.tracked) {
742
880
  expression = b.call('get', expression);
743
881
  }
744
882
 
745
883
  if (expression.type === 'Literal') {
746
- state.init.push(
884
+ state.init?.push(
747
885
  b.stmt(
748
886
  b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
749
887
  ),
750
888
  );
751
889
  } else {
752
- state.init.push(
890
+ state.init?.push(
753
891
  b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.call('_$_.escape', expression))),
754
892
  );
755
893
  }
@@ -757,16 +895,18 @@ const visitors = {
757
895
 
758
896
  Html(node, { visit, state }) {
759
897
  const metadata = { await: false };
760
- const expression = visit(node.expression, { ...state, metadata });
898
+ const expression = /** @type {AST.Expression} */ (
899
+ visit(node.expression, { ...state, metadata })
900
+ );
761
901
 
762
902
  // For Html nodes, we render the content as-is without escaping
763
903
  if (expression.type === 'Literal') {
764
- state.init.push(
904
+ state.init?.push(
765
905
  b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(expression.value))),
766
906
  );
767
907
  } else {
768
908
  // If it's dynamic, we need to evaluate it and push it directly (not escaped)
769
- state.init.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), expression)));
909
+ state.init?.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), expression)));
770
910
  }
771
911
  },
772
912
 
@@ -777,7 +917,9 @@ const visitors = {
777
917
  return context.visit(node.body);
778
918
  }
779
919
  const file_path = context.state.filename;
780
- const block = context.visit(node.body, { ...context.state, inside_server_block: true });
920
+ const block = /** @type {AST.BlockStatement} */ (
921
+ context.visit(node.body, { ...context.state, inside_server_block: true })
922
+ );
781
923
  const rpc_modules = globalThis.rpc_modules;
782
924
 
783
925
  if (rpc_modules) {
@@ -806,10 +948,18 @@ const visitors = {
806
948
  },
807
949
  };
808
950
 
951
+ /**
952
+ * @param {string} filename
953
+ * @param {string} source
954
+ * @param {AnalysisResult} analysis
955
+ * @param {boolean} minify_css
956
+ * @returns {{ ast: AST.Program; js: { code: string; map: SourceMapMappings | null }; css: string; }}
957
+ */
809
958
  export function transform_server(filename, source, analysis, minify_css) {
810
959
  // Use component metadata collected during the analyze phase
811
960
  const component_metadata = analysis.component_metadata || [];
812
961
 
962
+ /** @type {TransformServerState} */
813
963
  const state = {
814
964
  imports: new Set(),
815
965
  init: null,
@@ -819,11 +969,13 @@ export function transform_server(filename, source, analysis, minify_css) {
819
969
  component_metadata,
820
970
  inside_server_block: false,
821
971
  filename,
972
+ namespace: 'html',
973
+ // TODO: should we remove all `to_ts` usages we use the client rendering for that?
974
+ to_ts: false,
975
+ metadata: {},
822
976
  };
823
977
 
824
- const program = /** @type {ESTree.Program} */ (
825
- walk(analysis.ast, { ...state, namespace: 'html' }, visitors)
826
- );
978
+ const program = walk(analysis.ast, { ...state }, visitors);
827
979
 
828
980
  const css = render_stylesheets(state.stylesheets, minify_css);
829
981
 
@@ -832,7 +984,7 @@ export function transform_server(filename, source, analysis, minify_css) {
832
984
  // Register each stylesheet's CSS
833
985
  for (const stylesheet of state.stylesheets) {
834
986
  const css_for_component = render_stylesheets([stylesheet]);
835
- program.body.push(
987
+ /** @type {AST.Program} */ (program).body.push(
836
988
  b.stmt(
837
989
  b.call('_$_.register_css', b.literal(stylesheet.hash), b.literal(css_for_component)),
838
990
  ),
@@ -843,16 +995,21 @@ export function transform_server(filename, source, analysis, minify_css) {
843
995
  // Add async property to component functions
844
996
 
845
997
  for (const import_node of state.imports) {
846
- program.body.unshift(b.stmt(b.id(import_node)));
998
+ /** @type {AST.Program} */ (program).body.unshift(b.stmt(b.id(import_node)));
847
999
  }
848
1000
 
849
- const js = print(program, ts(), {
1001
+ // ESRap unfortunately hard codes the type from '@typescript-eslint/types'
1002
+ // Our types from 'estree' are incompatible
1003
+ // So we'll just give it what it wants with an unknown cast.
1004
+ // Functionally, none of the properties that are different between the two types
1005
+ // are used by the printer, so this is safe.
1006
+ const js = print(/** @type {TSESTree.Node} */ (/** @type {unknown} */ (program)), ts(), {
850
1007
  sourceMapContent: source,
851
1008
  sourceMapSource: path.basename(filename),
852
1009
  });
853
1010
 
854
1011
  return {
855
- ast: program,
1012
+ ast: /** @type {AST.Program} */ (program),
856
1013
  js,
857
1014
  css,
858
1015
  };