@tsrx/core 0.1.49 → 0.1.51

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,7 +1,9 @@
1
1
  /** @import * as AST from 'estree' */
2
+ /** @import * as ESTreeJSX from 'estree-jsx' */
3
+ /** @import { BaseNodeMetaData, LazyBinding, LazyContext, LazyPattern, MaybeLocated } from '../../types/index' */
2
4
 
3
5
  import * as b from '../utils/builders.js';
4
- import { has_location, is_function_or_component_node } from '../utils/ast.js';
6
+ import { has_location, is_ast_node, is_function_or_component_node } from '../utils/ast.js';
5
7
 
6
8
  /**
7
9
  * Lazy destructuring transform — framework-agnostic.
@@ -26,14 +28,6 @@ import { has_location, is_function_or_component_node } from '../utils/ast.js';
26
28
  * The transform is purely AST-to-AST and has no framework-specific knowledge.
27
29
  */
28
30
 
29
- /**
30
- * @typedef {{ lazy_next_id: number }} LazyContext
31
- */
32
-
33
- /**
34
- * @typedef {{ source_name: string, read: (reference?: any) => any }} LazyBinding
35
- */
36
-
37
31
  /**
38
32
  * Create a fresh lazy-id allocation context.
39
33
  *
@@ -52,9 +46,10 @@ function generate_lazy_id(context) {
52
46
  }
53
47
 
54
48
  /**
55
- * @param {any} node
56
- * @param {any} [loc_info]
57
- * @returns {any}
49
+ * @template {AST.Node} T
50
+ * @param {T} node
51
+ * @param {MaybeLocated | null} [loc_info]
52
+ * @returns {T}
58
53
  */
59
54
  function set_source_location(node, loc_info) {
60
55
  if (has_location(loc_info)) {
@@ -67,10 +62,10 @@ function set_source_location(node, loc_info) {
67
62
 
68
63
  /**
69
64
  * @param {string} name
70
- * @param {any} [loc_info]
65
+ * @param {MaybeLocated | null} [loc_info]
71
66
  * @param {string} [source_name]
72
67
  * @param {number} [source_length]
73
- * @returns {any}
68
+ * @returns {AST.Identifier}
74
69
  */
75
70
  function create_generated_identifier(name, loc_info, source_name, source_length) {
76
71
  const id = b.id(name);
@@ -80,8 +75,8 @@ function create_generated_identifier(name, loc_info, source_name, source_length)
80
75
  }
81
76
 
82
77
  /**
83
- * @param {any} pattern
84
- * @returns {{ start: number, end: number, loc: any, source_length: number } | null}
78
+ * @param {LazyPattern} pattern
79
+ * @returns {{ start: number, end: number, loc: AST.SourceLocation, source_length: number } | null}
85
80
  */
86
81
  function get_lazy_pattern_mapping_range(pattern) {
87
82
  if (!has_location(pattern)) return null;
@@ -100,7 +95,7 @@ function get_lazy_pattern_mapping_range(pattern) {
100
95
  }
101
96
 
102
97
  /**
103
- * @param {any} node
98
+ * @param {AST.Node} node
104
99
  * @returns {string | null}
105
100
  */
106
101
  function get_static_property_name(node) {
@@ -110,18 +105,23 @@ function get_static_property_name(node) {
110
105
  }
111
106
 
112
107
  /**
113
- * @param {any} type_annotation
114
- * @returns {Map<string, any>}
108
+ * The property keys of an inline object type annotation, by name.
109
+ *
110
+ * @param {AST.TSTypeAnnotation | undefined} type_annotation
111
+ * @returns {Map<string, AST.Identifier | AST.Literal>}
115
112
  */
116
113
  function get_type_property_keys(type_annotation) {
114
+ /** @type {Map<string, AST.Identifier | AST.Literal>} */
117
115
  const keys = new Map();
118
- const members = type_annotation?.typeAnnotation?.members;
119
- if (!Array.isArray(members)) return keys;
120
-
121
- for (const member of members) {
122
- if (member.type !== 'TSPropertySignature' || !member.key) continue;
123
- const name = get_static_property_name(member.key);
124
- if (name != null && !keys.has(name)) keys.set(name, member.key);
116
+ const type = type_annotation?.typeAnnotation;
117
+ if (type?.type !== 'TSTypeLiteral') return keys;
118
+
119
+ for (const member of type.members) {
120
+ if (member.type !== 'TSPropertySignature') continue;
121
+ const key = member.key;
122
+ if (key.type !== 'Identifier' && key.type !== 'Literal') continue;
123
+ const name = get_static_property_name(key);
124
+ if (name != null && !keys.has(name)) keys.set(name, key);
125
125
  }
126
126
 
127
127
  return keys;
@@ -133,8 +133,8 @@ function get_type_property_keys(type_annotation) {
133
133
  * names (`&{ a: value, value }`), while the virtual param only exposes object
134
134
  * properties (`__lazy0: { a: ...; value: ... }`).
135
135
  *
136
- * @param {any} lazy_id
137
- * @param {any} pattern
136
+ * @param {AST.Identifier} lazy_id
137
+ * @param {LazyPattern} pattern
138
138
  */
139
139
  function set_lazy_param_binding_mappings(lazy_id, pattern) {
140
140
  if (pattern.type !== 'ObjectPattern') return;
@@ -142,8 +142,9 @@ function set_lazy_param_binding_mappings(lazy_id, pattern) {
142
142
  const type_keys = get_type_property_keys(lazy_id.typeAnnotation);
143
143
  if (type_keys.size === 0) return;
144
144
 
145
+ /** @type {NonNullable<BaseNodeMetaData['lazy_param_binding_mappings']>} */
145
146
  const mappings = [];
146
- for (const prop of pattern.properties || []) {
147
+ for (const prop of pattern.properties) {
147
148
  if (prop.type === 'RestElement' || prop.computed) continue;
148
149
 
149
150
  const value = prop.value;
@@ -173,7 +174,7 @@ function set_lazy_param_binding_mappings(lazy_id, pattern) {
173
174
  * maps `first` → `S.pair[0]`. Handles `AssignmentPattern` (default values lost,
174
175
  * but the binding still resolves to the member chain). Skips `RestElement`.
175
176
  *
176
- * @param {any} pattern
177
+ * @param {LazyPattern} pattern
177
178
  * @param {string} source_name
178
179
  * @param {Map<string, LazyBinding>} lazy_bindings
179
180
  */
@@ -193,21 +194,21 @@ export function collect_lazy_bindings(pattern, source_name, lazy_bindings) {
193
194
  * level composes its accessor onto `build_parent`, so leaves get the full
194
195
  * member chain (e.g. `source.outer.inner` for `&{ outer: &{ inner } }`).
195
196
  *
196
- * @param {any} pattern
197
+ * @param {LazyPattern} pattern
197
198
  * @param {string} source_name
198
- * @param {(reference?: any) => any} build_parent
199
+ * @param {LazyBinding['read']} build_parent
199
200
  * @param {Map<string, LazyBinding>} lazy_bindings
200
201
  */
201
202
  function collect_lazy_bindings_at(pattern, source_name, build_parent, lazy_bindings) {
202
203
  if (pattern.type === 'ObjectPattern') {
203
- for (const prop of pattern.properties || []) {
204
+ for (const prop of pattern.properties) {
204
205
  if (prop.type === 'RestElement') continue;
205
206
  const value = prop.value;
206
207
  const actual = value.type === 'AssignmentPattern' ? value.left : value;
207
208
  const key = prop.key;
208
209
  const computed = prop.computed || key.type !== 'Identifier';
209
210
 
210
- /** @type {(reference?: any) => any} */
211
+ /** @type {LazyBinding['read']} */
211
212
  const build_self = (reference) =>
212
213
  b.member(
213
214
  build_parent(),
@@ -224,14 +225,14 @@ function collect_lazy_bindings_at(pattern, source_name, build_parent, lazy_bindi
224
225
  }
225
226
  }
226
227
  } else if (pattern.type === 'ArrayPattern') {
227
- for (let i = 0; i < (pattern.elements || []).length; i++) {
228
+ for (let i = 0; i < pattern.elements.length; i++) {
228
229
  const element = pattern.elements[i];
229
230
  if (!element) continue;
230
231
  if (element.type === 'RestElement') continue;
231
232
  const actual = element.type === 'AssignmentPattern' ? element.left : element;
232
233
  const index = i;
233
234
 
234
- /** @type {() => any} */
235
+ /** @type {LazyBinding['read']} */
235
236
  const build_self = () => b.member(build_parent(), b.literal(index), true);
236
237
 
237
238
  if (actual.type === 'Identifier') {
@@ -249,13 +250,13 @@ function collect_lazy_bindings_at(pattern, source_name, build_parent, lazy_bindi
249
250
  * `let &[x] = ...` variable declarations and statement-level `&[x] = expr;`
250
251
  * assignment expressions.
251
252
  *
252
- * @param {any[]} statements
253
+ * @param {AST.Program['body']} statements
253
254
  * @param {Map<string, LazyBinding>} lazy_bindings
254
255
  */
255
256
  export function collect_lazy_bindings_from_statements(statements, lazy_bindings) {
256
- for (const stmt of statements || []) {
257
+ for (const stmt of statements) {
257
258
  if (stmt.type === 'VariableDeclaration') {
258
- for (const declarator of stmt.declarations || []) {
259
+ for (const declarator of stmt.declarations) {
259
260
  visit_topmost_lazy_patterns(declarator.id, (lazy) => {
260
261
  if (!lazy.metadata?.lazy_id) return;
261
262
  collect_lazy_bindings(lazy, lazy.metadata.lazy_id, lazy_bindings);
@@ -282,8 +283,8 @@ export function collect_lazy_bindings_from_statements(statements, lazy_bindings)
282
283
  * patterns: their inner leaves are reached via accessor chains rooted at the
283
284
  * lazy pattern's synthesized id, not by further descent here.
284
285
  *
285
- * @param {any} pattern
286
- * @param {(node: any) => void} visit
286
+ * @param {AST.Node | null | undefined} pattern
287
+ * @param {(node: LazyPattern) => void} visit
287
288
  */
288
289
  function visit_topmost_lazy_patterns(pattern, visit) {
289
290
  if (!pattern || typeof pattern !== 'object') return;
@@ -303,12 +304,12 @@ function visit_topmost_lazy_patterns(pattern, visit) {
303
304
  }
304
305
 
305
306
  if (pattern.type === 'ObjectPattern') {
306
- for (const prop of pattern.properties || []) {
307
+ for (const prop of pattern.properties) {
307
308
  if (prop.type === 'RestElement') visit_topmost_lazy_patterns(prop.argument, visit);
308
309
  else visit_topmost_lazy_patterns(prop.value, visit);
309
310
  }
310
311
  } else {
311
- for (const element of pattern.elements || []) {
312
+ for (const element of pattern.elements) {
312
313
  if (element) visit_topmost_lazy_patterns(element, visit);
313
314
  }
314
315
  }
@@ -325,20 +326,21 @@ function visit_topmost_lazy_patterns(pattern, visit) {
325
326
  * that's not valid syntax — so they get a plain identifier with just source-range
326
327
  * info.
327
328
  *
328
- * @param {any} pattern
329
+ * @param {LazyPattern} pattern
330
+ * @param {string} lazy_id_name the id `preallocate_lazy_ids` assigned
329
331
  * @param {boolean} is_top
330
- * @returns {any}
332
+ * @returns {AST.Identifier}
331
333
  */
332
- function build_lazy_id_for_pattern(pattern, is_top) {
334
+ function build_lazy_id_for_pattern(pattern, lazy_id_name, is_top) {
333
335
  const pattern_range = get_lazy_pattern_mapping_range(pattern);
334
336
  const lazy_id = pattern_range
335
337
  ? create_generated_identifier(
336
- pattern.metadata.lazy_id,
338
+ lazy_id_name,
337
339
  pattern_range,
338
340
  undefined,
339
341
  pattern_range.source_length,
340
342
  )
341
- : create_generated_identifier(pattern.metadata.lazy_id);
343
+ : create_generated_identifier(lazy_id_name);
342
344
  if (!is_top) return lazy_id;
343
345
  if (pattern.typeAnnotation) {
344
346
  lazy_id.typeAnnotation = pattern.typeAnnotation;
@@ -355,9 +357,9 @@ function build_lazy_id_for_pattern(pattern, is_top) {
355
357
  * binds a single param (so a directly-lazy pattern can carry param-level type
356
358
  * info); recursive descent into child patterns passes `false`.
357
359
  *
358
- * @param {any} pattern
360
+ * @param {AST.Pattern} pattern
359
361
  * @param {boolean} [is_top]
360
- * @returns {any}
362
+ * @returns {AST.Pattern}
361
363
  */
362
364
  function replace_lazy_in_pattern(pattern, is_top = true) {
363
365
  if (!pattern || typeof pattern !== 'object') return pattern;
@@ -372,13 +374,14 @@ function replace_lazy_in_pattern(pattern, is_top = true) {
372
374
  }
373
375
  if (pattern.type !== 'ObjectPattern' && pattern.type !== 'ArrayPattern') return pattern;
374
376
 
375
- if (pattern.lazy && pattern.metadata?.lazy_id) {
376
- return build_lazy_id_for_pattern(pattern, is_top);
377
+ const lazy_id = pattern.metadata?.lazy_id;
378
+ if (pattern.lazy && lazy_id) {
379
+ return build_lazy_id_for_pattern(pattern, lazy_id, is_top);
377
380
  }
378
381
 
379
382
  if (pattern.type === 'ObjectPattern') {
380
383
  let changed = false;
381
- const new_properties = (pattern.properties || []).map((/** @type {any} */ prop) => {
384
+ const new_properties = pattern.properties.map((prop) => {
382
385
  if (prop.type === 'RestElement') {
383
386
  const new_arg = replace_lazy_in_pattern(prop.argument, false);
384
387
  if (new_arg === prop.argument) return prop;
@@ -394,7 +397,7 @@ function replace_lazy_in_pattern(pattern, is_top = true) {
394
397
  }
395
398
 
396
399
  let changed = false;
397
- const new_elements = (pattern.elements || []).map((/** @type {any} */ element) => {
400
+ const new_elements = pattern.elements.map((element) => {
398
401
  if (!element) return element;
399
402
  const new_element = replace_lazy_in_pattern(element, false);
400
403
  if (new_element !== element) changed = true;
@@ -414,11 +417,11 @@ function replace_lazy_in_pattern(pattern, is_top = true) {
414
417
  * node whose subtree contains any lazy pattern, so `apply_lazy_transforms`
415
418
  * can take a constant-time fast path for purely non-lazy functions.
416
419
  *
417
- * @param {any} root
420
+ * @param {AST.Node} root
418
421
  * @param {LazyContext} context
419
422
  */
420
423
  export function preallocate_lazy_ids(root, context) {
421
- /** @param {any} pattern */
424
+ /** @param {AST.Node | null | undefined} pattern */
422
425
  const assign_id = (pattern) => {
423
426
  visit_topmost_lazy_patterns(pattern, (lazy) => {
424
427
  if (lazy.metadata?.lazy_id) return;
@@ -427,24 +430,26 @@ export function preallocate_lazy_ids(root, context) {
427
430
  };
428
431
 
429
432
  /**
430
- * @param {any} node
431
- * @returns {boolean} true if `node`'s subtree contains any lazy pattern.
433
+ * @param {unknown} value
434
+ * @returns {boolean} true if `value`'s subtree contains any lazy pattern.
432
435
  */
433
- const visit = (node) => {
434
- if (!node || typeof node !== 'object') return false;
435
- if (Array.isArray(node)) {
436
+ const visit = (value) => {
437
+ if (!value || typeof value !== 'object') return false;
438
+ if (Array.isArray(value)) {
436
439
  let found = false;
437
- for (const child of node) {
440
+ for (const child of value) {
438
441
  if (visit(child)) found = true;
439
442
  }
440
443
  return found;
441
444
  }
445
+ if (!is_ast_node(value)) return false;
442
446
 
447
+ const node = value;
443
448
  const is_function_like = is_function_or_component_node(node);
444
449
 
445
450
  if (is_function_like) {
446
- for (const param of node.params || []) {
447
- assign_id(param?.type === 'AssignmentPattern' ? param.left : param);
451
+ for (const param of node.params) {
452
+ assign_id(param.type === 'AssignmentPattern' ? param.left : param);
448
453
  }
449
454
  }
450
455
 
@@ -454,7 +459,7 @@ export function preallocate_lazy_ids(root, context) {
454
459
 
455
460
  if (
456
461
  node.type === 'ExpressionStatement' &&
457
- node.expression?.type === 'AssignmentExpression' &&
462
+ node.expression.type === 'AssignmentExpression' &&
458
463
  node.expression.operator === '='
459
464
  ) {
460
465
  assign_id(node.expression.left);
@@ -463,9 +468,10 @@ export function preallocate_lazy_ids(root, context) {
463
468
  let found =
464
469
  (node.type === 'ObjectPattern' || node.type === 'ArrayPattern') && node.lazy === true;
465
470
 
466
- for (const key of Object.keys(node)) {
471
+ const entries = /** @type {AST.TraversableAstNode} */ (node);
472
+ for (const key of Object.keys(entries)) {
467
473
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
468
- if (visit(node[key])) found = true;
474
+ if (visit(entries[key])) found = true;
469
475
  }
470
476
 
471
477
  if (is_function_like && found) {
@@ -485,15 +491,15 @@ export function preallocate_lazy_ids(root, context) {
485
491
  * to `<__lazy0.Item>`. Returns null for a computed access (`__lazy0[0]`, from an
486
492
  * array destructure), which has no JSX-name form.
487
493
  *
488
- * @param {any} expr
489
- * @param {any} [source]
490
- * @returns {any | null}
494
+ * @param {AST.Expression | AST.Super} expr
495
+ * @param {MaybeLocated | null} [source]
496
+ * @returns {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXMemberExpression | null}
491
497
  */
492
498
  function estree_member_to_jsx_name(expr, source) {
493
499
  if (expr.type === 'Identifier') {
494
500
  return set_source_location(b.jsx_id(expr.name), source);
495
501
  }
496
- if (expr.type === 'MemberExpression' && !expr.computed && expr.property?.type === 'Identifier') {
502
+ if (expr.type === 'MemberExpression' && !expr.computed && expr.property.type === 'Identifier') {
497
503
  const object = estree_member_to_jsx_name(expr.object, source);
498
504
  if (!object) return null;
499
505
  return set_source_location(b.jsx_member(object, b.jsx_id(expr.property.name)), source);
@@ -507,9 +513,9 @@ function estree_member_to_jsx_name(expr, source) {
507
513
  * Returns null when the name does not reference a lazy binding (or the access has
508
514
  * no JSX-name form), so the caller leaves the original name untouched.
509
515
  *
510
- * @param {any} name
516
+ * @param {ESTreeJSX.TSRXJSXOpeningElement['name'] | null | undefined} name
511
517
  * @param {Map<string, LazyBinding>} lazy_bindings
512
- * @returns {any | null}
518
+ * @returns {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXMemberExpression | null}
513
519
  */
514
520
  function rewrite_lazy_jsx_name(name, lazy_bindings) {
515
521
  if (!name) return null;
@@ -525,16 +531,63 @@ function rewrite_lazy_jsx_name(name, lazy_bindings) {
525
531
  return null;
526
532
  }
527
533
 
534
+ /**
535
+ * Rewrite lazy-binding references in an arbitrary property value: node arrays
536
+ * and single nodes are transformed, everything else (strings, numbers, `null`,
537
+ * positional data) is returned as-is.
538
+ *
539
+ * @param {unknown} value
540
+ * @param {Map<string, LazyBinding>} lazy_bindings
541
+ * @returns {unknown}
542
+ */
543
+ function apply_lazy_transforms_to_value(value, lazy_bindings) {
544
+ if (Array.isArray(value)) {
545
+ return value.map((child) => apply_lazy_transforms_to_value(child, lazy_bindings));
546
+ }
547
+ if (!is_ast_node(value)) return value;
548
+ return apply_lazy_transforms(value, lazy_bindings);
549
+ }
550
+
551
+ /**
552
+ * Rebuild `node` with rewritten slots. The transform never changes a slot's
553
+ * syntactic category — an expression stays an expression, a statement stays a
554
+ * statement — so the clone is the same kind of node as the original, which TS
555
+ * cannot verify through a spread of a widened slot value.
556
+ *
557
+ * @template {AST.Node} T
558
+ * @param {T} node
559
+ * @param {Partial<Record<keyof T, unknown>>} changes
560
+ * @returns {T}
561
+ */
562
+ function rebuild(node, changes) {
563
+ return /** @type {T} */ ({ ...node, ...changes });
564
+ }
565
+
566
+ /**
567
+ * Rewrite a node that occupies a typed slot (an expression, a statement, a
568
+ * function body). The transform preserves the syntactic category of what it
569
+ * rewrites — an expression stays an expression, a statement stays a statement
570
+ * (a standalone `&[x] = …` statement becomes a `const` declaration) — so the
571
+ * result is re-typed to the slot it came from.
572
+ *
573
+ * @template {AST.Node} T
574
+ * @param {T} node
575
+ * @param {Map<string, LazyBinding>} lazy_bindings
576
+ * @returns {T}
577
+ */
578
+ function apply_lazy_transforms_to_slot(node, lazy_bindings) {
579
+ return /** @type {T} */ (apply_lazy_transforms(node, lazy_bindings));
580
+ }
581
+
528
582
  /**
529
583
  * Recursively rewrite lazy-binding references in `node`.
530
584
  *
531
- * @param {any} node
585
+ * @param {AST.Node} node
532
586
  * @param {Map<string, LazyBinding>} lazy_bindings
533
- * @returns {any}
587
+ * @returns {AST.Node}
534
588
  */
535
589
  export function apply_lazy_transforms(node, lazy_bindings) {
536
590
  if (!node || typeof node !== 'object') return node;
537
- if (Array.isArray(node)) return node.map((child) => apply_lazy_transforms(child, lazy_bindings));
538
591
 
539
592
  if (
540
593
  node.type === 'FunctionDeclaration' ||
@@ -543,7 +596,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
543
596
  ) {
544
597
  // Default parameter values are evaluated in the outer scope — transform them first.
545
598
  let params_changed = false;
546
- const new_params = (node.params || []).map((/** @type {any} */ param) => {
599
+ const new_params = node.params.map((param) => {
547
600
  const transformed = transform_param_defaults(param, lazy_bindings);
548
601
  if (transformed !== param) params_changed = true;
549
602
  return transformed;
@@ -551,7 +604,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
551
604
 
552
605
  /** @type {Set<string>} */
553
606
  const shadowed = new Set();
554
- for (const param of node.params || []) {
607
+ for (const param of node.params) {
555
608
  collect_shadowed_names(param, lazy_bindings, shadowed);
556
609
  }
557
610
 
@@ -561,7 +614,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
561
614
  /** @type {Map<string, LazyBinding>} */
562
615
  const own_bindings = new Map();
563
616
  let had_lazy_param = false;
564
- for (const param of node.params || []) {
617
+ for (const param of node.params) {
565
618
  visit_topmost_lazy_patterns(param, (lazy) => {
566
619
  if (!lazy.metadata?.lazy_id) return;
567
620
  had_lazy_param = true;
@@ -588,36 +641,38 @@ export function apply_lazy_transforms(node, lazy_bindings) {
588
641
  // params to replace, defaults referencing outer lazy, or the body
589
642
  // contains lazy descendants the BlockStatement handler will collect.
590
643
  // In every case the body needs to be walked.
591
- const new_body = apply_lazy_transforms(node.body, inner_bindings);
644
+ const new_body = apply_lazy_transforms_to_slot(node.body, inner_bindings);
592
645
 
593
646
  const final_params_src = params_changed ? new_params : node.params;
594
647
  const final_params = had_lazy_param ? replace_lazy_params(final_params_src) : final_params_src;
595
648
 
596
649
  if (new_body !== node.body || final_params !== node.params) {
597
- return { ...node, params: final_params, body: new_body };
650
+ return rebuild(node, { params: final_params, body: new_body });
598
651
  }
599
652
  return node;
600
653
  }
601
654
 
602
655
  if (node.type === 'BlockStatement' || node.type === 'Program') {
603
- const block_bindings = collect_block_shadowed_names(node.body, lazy_bindings);
656
+ /** @type {AST.Program['body']} */
657
+ const body = node.body;
658
+ const block_bindings = collect_block_shadowed_names(body, lazy_bindings);
604
659
  const after_shadow =
605
660
  block_bindings.size > 0 ? remove_shadowed(lazy_bindings, block_bindings) : lazy_bindings;
606
661
 
607
662
  /** @type {Map<string, LazyBinding>} */
608
663
  const block_lazy = new Map();
609
- collect_lazy_bindings_from_statements(node.body, block_lazy);
664
+ collect_lazy_bindings_from_statements(body, block_lazy);
610
665
 
611
666
  const effective_bindings =
612
667
  block_lazy.size > 0 ? new Map([...after_shadow, ...block_lazy]) : after_shadow;
613
668
 
614
669
  let changed = false;
615
- const new_body = node.body.map((/** @type {any} */ stmt) => {
616
- const transformed = apply_lazy_transforms(stmt, effective_bindings);
670
+ const new_body = node.body.map((stmt) => {
671
+ const transformed = apply_lazy_transforms_to_slot(stmt, effective_bindings);
617
672
  if (transformed !== stmt) changed = true;
618
673
  return transformed;
619
674
  });
620
- return changed ? { ...node, body: new_body } : node;
675
+ return changed ? rebuild(node, { body: new_body }) : node;
621
676
  }
622
677
 
623
678
  if (node.type === 'CatchClause') {
@@ -626,8 +681,8 @@ export function apply_lazy_transforms(node, lazy_bindings) {
626
681
  if (node.param) collect_shadowed_names(node.param, lazy_bindings, shadowed);
627
682
  const effective_bindings =
628
683
  shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
629
- const new_body = apply_lazy_transforms(node.body, effective_bindings);
630
- if (new_body !== node.body) return { ...node, body: new_body };
684
+ const new_body = apply_lazy_transforms_to_slot(node.body, effective_bindings);
685
+ if (new_body !== node.body) return rebuild(node, { body: new_body });
631
686
  return node;
632
687
  }
633
688
 
@@ -642,16 +697,17 @@ export function apply_lazy_transforms(node, lazy_bindings) {
642
697
  const effective_bindings =
643
698
  shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
644
699
  let changed = false;
645
- const new_init = apply_lazy_transforms(node.init, effective_bindings);
700
+ const new_init = node.init && apply_lazy_transforms_to_slot(node.init, effective_bindings);
646
701
  if (new_init !== node.init) changed = true;
647
- const new_test = apply_lazy_transforms(node.test, effective_bindings);
702
+ const new_test = node.test && apply_lazy_transforms_to_slot(node.test, effective_bindings);
648
703
  if (new_test !== node.test) changed = true;
649
- const new_update = apply_lazy_transforms(node.update, effective_bindings);
704
+ const new_update =
705
+ node.update && apply_lazy_transforms_to_slot(node.update, effective_bindings);
650
706
  if (new_update !== node.update) changed = true;
651
- const new_body = apply_lazy_transforms(node.body, effective_bindings);
707
+ const new_body = apply_lazy_transforms_to_slot(node.body, effective_bindings);
652
708
  if (new_body !== node.body) changed = true;
653
709
  return changed
654
- ? { ...node, init: new_init, test: new_test, update: new_update, body: new_body }
710
+ ? rebuild(node, { init: new_init, test: new_test, update: new_update, body: new_body })
655
711
  : node;
656
712
  }
657
713
 
@@ -675,11 +731,11 @@ export function apply_lazy_transforms(node, lazy_bindings) {
675
731
  let changed = false;
676
732
  // The right-hand side is evaluated in the outer scope (before the loop
677
733
  // variable is bound), so use the unshadowed bindings there.
678
- const new_right = apply_lazy_transforms(node.right, lazy_bindings);
734
+ const new_right = apply_lazy_transforms_to_slot(node.right, lazy_bindings);
679
735
  if (new_right !== node.right) changed = true;
680
- const new_body = apply_lazy_transforms(node.body, effective_bindings);
736
+ const new_body = apply_lazy_transforms_to_slot(node.body, effective_bindings);
681
737
  if (new_body !== node.body) changed = true;
682
- return changed ? { ...node, right: new_right, body: new_body } : node;
738
+ return changed ? rebuild(node, { right: new_right, body: new_body }) : node;
683
739
  }
684
740
 
685
741
  if (node.type === 'SwitchStatement') {
@@ -690,9 +746,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
690
746
  // consequent — like the BlockStatement handler does for a block body — so
691
747
  // references in any case resolve to the generated id, and an inner
692
748
  // declaration shadowing an outer lazy name is dropped.
693
- const all_consequents = node.cases.flatMap(
694
- (/** @type {any} */ switch_case) => switch_case.consequent,
695
- );
749
+ const all_consequents = node.cases.flatMap((switch_case) => switch_case.consequent);
696
750
  const block_shadowed = collect_block_shadowed_names(all_consequents, lazy_bindings);
697
751
  const after_shadow =
698
752
  block_shadowed.size > 0 ? remove_shadowed(lazy_bindings, block_shadowed) : lazy_bindings;
@@ -707,26 +761,26 @@ export function apply_lazy_transforms(node, lazy_bindings) {
707
761
  // The discriminant is evaluated before any case body runs, so it sees the
708
762
  // bindings visible at the switch (outer, minus inner shadows), not a lazy
709
763
  // binding declared inside a case body.
710
- const new_discriminant = apply_lazy_transforms(node.discriminant, after_shadow);
764
+ const new_discriminant = apply_lazy_transforms_to_slot(node.discriminant, after_shadow);
711
765
  if (new_discriminant !== node.discriminant) changed = true;
712
- const new_cases = node.cases.map((/** @type {any} */ switch_case) => {
766
+ const new_cases = node.cases.map((switch_case) => {
713
767
  let case_changed = false;
714
768
  const new_test = switch_case.test
715
- ? apply_lazy_transforms(switch_case.test, effective_bindings)
769
+ ? apply_lazy_transforms_to_slot(switch_case.test, effective_bindings)
716
770
  : null;
717
771
  if (new_test !== switch_case.test) case_changed = true;
718
- const new_consequent = switch_case.consequent.map((/** @type {any} */ stmt) => {
719
- const transformed = apply_lazy_transforms(stmt, effective_bindings);
772
+ const new_consequent = switch_case.consequent.map((stmt) => {
773
+ const transformed = apply_lazy_transforms_to_slot(stmt, effective_bindings);
720
774
  if (transformed !== stmt) case_changed = true;
721
775
  return transformed;
722
776
  });
723
777
  if (case_changed) {
724
778
  changed = true;
725
- return { ...switch_case, test: new_test, consequent: new_consequent };
779
+ return rebuild(switch_case, { test: new_test, consequent: new_consequent });
726
780
  }
727
781
  return switch_case;
728
782
  });
729
- return changed ? { ...node, discriminant: new_discriminant, cases: new_cases } : node;
783
+ return changed ? rebuild(node, { discriminant: new_discriminant, cases: new_cases }) : node;
730
784
  }
731
785
 
732
786
  // Standalone lazy destructuring assignment: `&[data] = track(0);` becomes
@@ -734,18 +788,20 @@ export function apply_lazy_transforms(node, lazy_bindings) {
734
788
  // via the enclosing BlockStatement handler.
735
789
  if (
736
790
  node.type === 'ExpressionStatement' &&
737
- node.expression?.type === 'AssignmentExpression' &&
791
+ node.expression.type === 'AssignmentExpression' &&
738
792
  node.expression.operator === '=' &&
739
- (node.expression.left?.type === 'ObjectPattern' ||
740
- node.expression.left?.type === 'ArrayPattern') &&
741
- node.expression.left.lazy &&
742
- node.expression.left.metadata?.lazy_id
793
+ (node.expression.left.type === 'ObjectPattern' ||
794
+ node.expression.left.type === 'ArrayPattern') &&
795
+ node.expression.left.lazy
743
796
  ) {
744
797
  const pattern = node.expression.left;
745
- const lazy_id = create_generated_identifier(pattern.metadata.lazy_id);
746
- if (pattern.typeAnnotation) lazy_id.typeAnnotation = pattern.typeAnnotation;
747
- const init = apply_lazy_transforms(node.expression.right, lazy_bindings);
748
- return b.const(lazy_id, init);
798
+ const lazy_id_name = pattern.metadata?.lazy_id;
799
+ if (lazy_id_name !== undefined) {
800
+ const lazy_id = create_generated_identifier(lazy_id_name);
801
+ if (pattern.typeAnnotation) lazy_id.typeAnnotation = pattern.typeAnnotation;
802
+ const init = apply_lazy_transforms_to_slot(node.expression.right, lazy_bindings);
803
+ return b.const(lazy_id, init);
804
+ }
749
805
  }
750
806
 
751
807
  // Non-lazy outer assignment whose LHS contains nested lazy patterns:
@@ -766,7 +822,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
766
822
  expression: {
767
823
  ...node.expression,
768
824
  left: new_left,
769
- right: apply_lazy_transforms(node.expression.right, lazy_bindings),
825
+ right: apply_lazy_transforms_to_slot(node.expression.right, lazy_bindings),
770
826
  },
771
827
  };
772
828
  }
@@ -782,7 +838,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
782
838
  return {
783
839
  ...node,
784
840
  left: binding.read(node.left),
785
- right: apply_lazy_transforms(node.right, lazy_bindings),
841
+ right: apply_lazy_transforms_to_slot(node.right, lazy_bindings),
786
842
  };
787
843
  }
788
844
 
@@ -802,7 +858,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
802
858
  return {
803
859
  ...node,
804
860
  id: lazy_id,
805
- init: apply_lazy_transforms(node.init, lazy_bindings),
861
+ init: node.init && apply_lazy_transforms_to_slot(node.init, lazy_bindings),
806
862
  };
807
863
  }
808
864
 
@@ -818,7 +874,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
818
874
  return {
819
875
  ...node,
820
876
  id: new_id,
821
- init: apply_lazy_transforms(node.init, lazy_bindings),
877
+ init: node.init && apply_lazy_transforms_to_slot(node.init, lazy_bindings),
822
878
  };
823
879
  }
824
880
  }
@@ -841,9 +897,10 @@ export function apply_lazy_transforms(node, lazy_bindings) {
841
897
  if (node.type === 'JSXIdentifier') return node;
842
898
 
843
899
  let changed = false;
844
- /** @type {any} */
845
- const clone = { ...node };
846
- for (const key of Object.keys(node)) {
900
+ const entries = /** @type {AST.TraversableAstNode} */ (node);
901
+ /** @type {Record<string, unknown>} */
902
+ const clone = { ...entries };
903
+ for (const key of Object.keys(entries)) {
847
904
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
848
905
 
849
906
  // Skip non-computed, non-shorthand property keys (they are labels).
@@ -864,7 +921,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
864
921
  key === 'name' &&
865
922
  (node.type === 'JSXOpeningElement' || node.type === 'JSXClosingElement')
866
923
  ) {
867
- const jsx_name = rewrite_lazy_jsx_name(node[key], lazy_bindings);
924
+ const jsx_name = rewrite_lazy_jsx_name(node.name, lazy_bindings);
868
925
  if (jsx_name) {
869
926
  clone[key] = jsx_name;
870
927
  changed = true;
@@ -872,29 +929,31 @@ export function apply_lazy_transforms(node, lazy_bindings) {
872
929
  }
873
930
  }
874
931
 
875
- const new_value = apply_lazy_transforms(node[key], lazy_bindings);
876
- if (new_value !== node[key]) {
932
+ const new_value = apply_lazy_transforms_to_value(entries[key], lazy_bindings);
933
+ if (new_value !== entries[key]) {
877
934
  clone[key] = new_value;
878
935
  changed = true;
879
936
  }
880
937
  }
881
- return changed ? clone : node;
938
+ return changed ? /** @type {AST.Node} */ (clone) : node;
882
939
  }
883
940
 
884
941
  /**
885
- * @param {any} param
942
+ * @template {AST.Pattern} T
943
+ * @param {T} param
886
944
  * @param {Map<string, LazyBinding>} lazy_bindings
945
+ * @returns {T | AST.AssignmentPattern}
887
946
  */
888
947
  function transform_param_defaults(param, lazy_bindings) {
889
- if (param?.type === 'AssignmentPattern') {
890
- const new_right = apply_lazy_transforms(param.right, lazy_bindings);
948
+ if (param.type === 'AssignmentPattern') {
949
+ const new_right = apply_lazy_transforms_to_slot(param.right, lazy_bindings);
891
950
  if (new_right !== param.right) return { ...param, right: new_right };
892
951
  }
893
952
  return param;
894
953
  }
895
954
 
896
955
  /**
897
- * @param {any} pattern
956
+ * @param {AST.Node | null | undefined} pattern
898
957
  * @param {Map<string, LazyBinding>} lazy_bindings
899
958
  * @param {Set<string>} shadowed
900
959
  */
@@ -913,7 +972,7 @@ function collect_shadowed_names(pattern, lazy_bindings, shadowed) {
913
972
  return;
914
973
  }
915
974
  if (pattern.type === 'ObjectPattern') {
916
- for (const prop of pattern.properties || []) {
975
+ for (const prop of pattern.properties) {
917
976
  if (prop.type === 'RestElement') {
918
977
  collect_shadowed_names(prop.argument, lazy_bindings, shadowed);
919
978
  } else {
@@ -923,14 +982,14 @@ function collect_shadowed_names(pattern, lazy_bindings, shadowed) {
923
982
  return;
924
983
  }
925
984
  if (pattern.type === 'ArrayPattern') {
926
- for (const element of pattern.elements || []) {
985
+ for (const element of pattern.elements) {
927
986
  if (element) collect_shadowed_names(element, lazy_bindings, shadowed);
928
987
  }
929
988
  }
930
989
  }
931
990
 
932
991
  /**
933
- * @param {any[]} statements
992
+ * @param {AST.Program['body']} statements
934
993
  * @param {Map<string, LazyBinding>} lazy_bindings
935
994
  * @returns {Set<string>}
936
995
  */
@@ -940,8 +999,8 @@ function collect_block_shadowed_names(statements, lazy_bindings) {
940
999
  for (const stmt of statements) {
941
1000
  if (stmt.type === 'VariableDeclaration') {
942
1001
  for (const decl of stmt.declarations) {
943
- if (decl.id?.metadata?.lazy_id) continue;
944
- if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
1002
+ if (decl.id.metadata?.lazy_id) continue;
1003
+ collect_shadowed_names(decl.id, lazy_bindings, shadowed);
945
1004
  }
946
1005
  } else if (stmt.type === 'FunctionDeclaration' && stmt.id) {
947
1006
  if (lazy_bindings.has(stmt.id.name)) shadowed.add(stmt.id.name);
@@ -967,8 +1026,8 @@ function remove_shadowed(lazy_bindings, shadowed) {
967
1026
  * patterns. For `({ pair: &[a, b] })` returns `({ pair: __lazy0 })`. Leaves
968
1027
  * params without any lazy descendants untouched.
969
1028
  *
970
- * @param {any[]} params
971
- * @returns {any[]}
1029
+ * @param {AST.Pattern[]} params
1030
+ * @returns {AST.Pattern[]}
972
1031
  */
973
1032
  export function replace_lazy_params(params) {
974
1033
  return params.map((param) => {