@tsrx/core 0.1.63 → 0.1.64

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.
@@ -3,7 +3,14 @@
3
3
  /** @import { BaseNodeMetaData, LazyBinding, LazyContext, LazyPattern, MaybeLocated } from '../../types/index' */
4
4
 
5
5
  import * as b from '../utils/builders.js';
6
- import { has_location, is_ast_node, is_function_or_component_node } from '../utils/ast.js';
6
+ import {
7
+ child_nodes,
8
+ has_location,
9
+ is_ast_node,
10
+ is_function_or_component_node,
11
+ is_supported_lazy_assignment_position,
12
+ is_transparent_expression_wrapper,
13
+ } from '../utils/ast.js';
7
14
 
8
15
  /**
9
16
  * Lazy destructuring transform — framework-agnostic.
@@ -262,12 +269,10 @@ export function collect_lazy_bindings_from_statements(statements, lazy_bindings)
262
269
  collect_lazy_bindings(lazy, lazy.metadata.lazy_id, lazy_bindings);
263
270
  });
264
271
  }
265
- } else if (
266
- stmt.type === 'ExpressionStatement' &&
267
- stmt.expression?.type === 'AssignmentExpression' &&
268
- stmt.expression.operator === '='
269
- ) {
270
- visit_topmost_lazy_patterns(stmt.expression.left, (lazy) => {
272
+ } else if (stmt.type === 'ExpressionStatement') {
273
+ const assignment = get_standalone_assignment(stmt);
274
+ if (!assignment || assignment.operator !== '=') continue;
275
+ visit_topmost_lazy_patterns(assignment.left, (lazy) => {
271
276
  if (!lazy.metadata?.lazy_id) return;
272
277
  collect_lazy_bindings(lazy, lazy.metadata.lazy_id, lazy_bindings);
273
278
  });
@@ -275,6 +280,27 @@ export function collect_lazy_bindings_from_statements(statements, lazy_bindings)
275
280
  }
276
281
  }
277
282
 
283
+ /**
284
+ * Unwrap the expression-only wrappers accepted by the lazy-assignment
285
+ * eligibility check and return the standalone assignment they contain.
286
+ * Keeping collection and lowering on the same wrapper definition prevents
287
+ * editor ASTs (`preserveParens`) from silently taking the eager path.
288
+ *
289
+ * @param {AST.ExpressionStatement} statement
290
+ * @returns {AST.AssignmentExpression | null}
291
+ */
292
+ function get_standalone_assignment(statement) {
293
+ /** @type {AST.Node} */
294
+ let expression = statement.expression;
295
+ while (expression.type !== 'AssignmentExpression') {
296
+ /** @type {AST.Node | undefined} */
297
+ const child = /** @type {{ expression?: AST.Node }} */ (expression).expression;
298
+ if (!child || !is_transparent_expression_wrapper(expression, child)) return null;
299
+ expression = child;
300
+ }
301
+ return expression;
302
+ }
303
+
278
304
  /**
279
305
  * Walk a destructure pattern tree, calling `visit` on every *topmost-lazy*
280
306
  * descendant — a lazy `ObjectPattern` / `ArrayPattern` with no lazy ancestor
@@ -406,10 +432,76 @@ function replace_lazy_in_pattern(pattern, is_top = true) {
406
432
  return changed ? { ...pattern, elements: new_elements } : pattern;
407
433
  }
408
434
 
435
+ /**
436
+ * Rewrite expression-bearing slots in a loop target without walking binding
437
+ * identifiers or non-computed property keys. Computed keys and default RHS
438
+ * values are evaluated while the target is established, so they see the lazy
439
+ * environment active at that point in the loop header.
440
+ *
441
+ * This runs before `replace_lazy_in_pattern`: computed keys inside a lazy
442
+ * pattern become part of the deferred member access collected from that
443
+ * pattern, while defaults surrounding a nested lazy pattern remain in the
444
+ * emitted binding target.
445
+ *
446
+ * @template {AST.Pattern} T
447
+ * @param {T} pattern
448
+ * @param {Map<string, LazyBinding>} lazy_bindings
449
+ * @returns {T}
450
+ */
451
+ function transform_loop_pattern_expressions(pattern, lazy_bindings) {
452
+ if (!pattern || typeof pattern !== 'object') return pattern;
453
+
454
+ if (pattern.type === 'AssignmentPattern') {
455
+ const new_left = transform_loop_pattern_expressions(pattern.left, lazy_bindings);
456
+ const new_right = apply_lazy_transforms_to_slot(pattern.right, lazy_bindings);
457
+ return new_left !== pattern.left || new_right !== pattern.right
458
+ ? /** @type {T} */ ({ ...pattern, left: new_left, right: new_right })
459
+ : pattern;
460
+ }
461
+ if (pattern.type === 'RestElement') {
462
+ const new_argument = transform_loop_pattern_expressions(pattern.argument, lazy_bindings);
463
+ return new_argument === pattern.argument
464
+ ? pattern
465
+ : /** @type {T} */ ({ ...pattern, argument: new_argument });
466
+ }
467
+ if (pattern.type === 'ObjectPattern') {
468
+ let changed = false;
469
+ const properties = pattern.properties.map((property) => {
470
+ if (property.type === 'RestElement') {
471
+ const new_argument = transform_loop_pattern_expressions(property.argument, lazy_bindings);
472
+ if (new_argument === property.argument) return property;
473
+ changed = true;
474
+ return rebuild(property, { argument: new_argument });
475
+ }
476
+
477
+ const new_key = property.computed
478
+ ? apply_lazy_transforms_to_slot(property.key, lazy_bindings)
479
+ : property.key;
480
+ const new_value = transform_loop_pattern_expressions(property.value, lazy_bindings);
481
+ if (new_key === property.key && new_value === property.value) return property;
482
+ changed = true;
483
+ return rebuild(property, { key: new_key, value: new_value });
484
+ });
485
+ return changed ? /** @type {T} */ ({ ...pattern, properties }) : pattern;
486
+ }
487
+ if (pattern.type === 'ArrayPattern') {
488
+ let changed = false;
489
+ const elements = pattern.elements.map((element) => {
490
+ if (!element) return element;
491
+ const new_element = transform_loop_pattern_expressions(element, lazy_bindings);
492
+ if (new_element !== element) changed = true;
493
+ return new_element;
494
+ });
495
+ return changed ? /** @type {T} */ ({ ...pattern, elements }) : pattern;
496
+ }
497
+
498
+ return pattern;
499
+ }
500
+
409
501
  /**
410
502
  * Walk the AST and pre-allocate `lazy_id` metadata on every lazy destructuring
411
- * pattern: function params, variable declarator ids, and statement-level
412
- * assignment LHS. Walks into non-lazy outer patterns to
503
+ * pattern: function params, variable declarator ids, bare for-of/in targets,
504
+ * and supported standalone assignment LHS. Walks into non-lazy outer patterns to
413
505
  * find nested lazy ones, e.g. `{ pair: &[a, b] }` allocates an id for the inner
414
506
  * `&[a, b]`. Idempotent: skips patterns that already have a `lazy_id`.
415
507
  *
@@ -419,8 +511,14 @@ function replace_lazy_in_pattern(pattern, is_top = true) {
419
511
  *
420
512
  * @param {AST.Node} root
421
513
  * @param {LazyContext} context
514
+ * @param {boolean} [assignments_only]
422
515
  */
423
- export function preallocate_lazy_ids(root, context) {
516
+ export function preallocate_lazy_ids(root, context, assignments_only = false) {
517
+ const HAS_LAZY = 1;
518
+ const HAS_VAR_LOOP = 2;
519
+ /** @type {AST.Node[]} */
520
+ const path = [];
521
+
424
522
  /** @param {AST.Node | null | undefined} pattern */
425
523
  const assign_id = (pattern) => {
426
524
  visit_topmost_lazy_patterns(pattern, (lazy) => {
@@ -429,56 +527,109 @@ export function preallocate_lazy_ids(root, context) {
429
527
  });
430
528
  };
431
529
 
530
+ /** @param {AST.Node | null | undefined} pattern */
531
+ const has_preallocated_lazy_binding = (pattern) => {
532
+ let found = false;
533
+ visit_topmost_lazy_patterns(pattern, (lazy) => {
534
+ if (lazy.metadata?.lazy_id) found = true;
535
+ });
536
+ return found;
537
+ };
538
+
432
539
  /**
433
540
  * @param {unknown} value
434
- * @returns {boolean} true if `value`'s subtree contains any lazy pattern.
541
+ * @returns {number} bit flags for lazy syntax and current-scope lazy var loops.
435
542
  */
436
543
  const visit = (value) => {
437
- if (!value || typeof value !== 'object') return false;
544
+ if (!value || typeof value !== 'object') return 0;
438
545
  if (Array.isArray(value)) {
439
- let found = false;
546
+ let flags = 0;
440
547
  for (const child of value) {
441
- if (visit(child)) found = true;
548
+ flags |= visit(child);
442
549
  }
443
- return found;
550
+ return flags;
444
551
  }
445
- if (!is_ast_node(value)) return false;
552
+ if (!is_ast_node(value)) return 0;
446
553
 
447
554
  const node = value;
448
555
  const is_function_like = is_function_or_component_node(node);
449
556
 
450
- if (is_function_like) {
557
+ if (is_function_like && !assignments_only) {
451
558
  for (const param of node.params) {
452
559
  assign_id(param.type === 'AssignmentPattern' ? param.left : param);
453
560
  }
454
561
  }
455
562
 
456
- if (node.type === 'VariableDeclarator') {
563
+ if (node.type === 'VariableDeclarator' && !assignments_only) {
457
564
  assign_id(node.id);
458
565
  }
459
566
 
460
567
  if (
461
- node.type === 'ExpressionStatement' &&
462
- node.expression.type === 'AssignmentExpression' &&
463
- node.expression.operator === '='
568
+ (node.type === 'ForOfStatement' || node.type === 'ForInStatement') &&
569
+ node.left.type !== 'VariableDeclaration'
464
570
  ) {
465
- assign_id(node.expression.left);
571
+ assign_id(node.left);
466
572
  }
467
573
 
468
- let found =
469
- (node.type === 'ObjectPattern' || node.type === 'ArrayPattern') && node.lazy === true;
574
+ if (node.type === 'AssignmentExpression' && is_supported_lazy_assignment_position(node, path)) {
575
+ assign_id(node.left);
576
+ }
577
+
578
+ let flags =
579
+ (node.type === 'ObjectPattern' || node.type === 'ArrayPattern') && node.lazy === true
580
+ ? HAS_LAZY
581
+ : 0;
470
582
 
471
583
  const entries = /** @type {AST.TraversableAstNode} */ (node);
584
+ path.push(node);
472
585
  for (const key of Object.keys(entries)) {
473
586
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
474
- if (visit(entries[key])) found = true;
587
+ flags |= visit(entries[key]);
588
+ }
589
+ path.pop();
590
+
591
+ const loop_declaration =
592
+ node.type === 'ForStatement'
593
+ ? node.init
594
+ : node.type === 'ForOfStatement' || node.type === 'ForInStatement'
595
+ ? node.left
596
+ : null;
597
+ if (
598
+ loop_declaration?.type === 'VariableDeclaration' &&
599
+ loop_declaration.kind === 'var' &&
600
+ loop_declaration.declarations.some((declarator) =>
601
+ has_preallocated_lazy_binding(declarator.id),
602
+ )
603
+ ) {
604
+ flags |= HAS_VAR_LOOP;
475
605
  }
476
606
 
477
- if (is_function_like && found) {
478
- node.metadata = { ...node.metadata, has_lazy_descendants: true };
607
+ if (is_function_like) {
608
+ if (flags & HAS_LAZY) {
609
+ node.metadata = { ...node.metadata, has_lazy_descendants: true };
610
+ }
611
+ if (flags & HAS_VAR_LOOP) {
612
+ node.metadata = { ...node.metadata, has_lazy_var_loop_descendants: true };
613
+ }
614
+ // A nested function owns its own var environment. Its caller still
615
+ // needs the general lazy-descendant signal so traversal reaches it,
616
+ // but must not scan for the nested function's loop bindings.
617
+ flags &= ~HAS_VAR_LOOP;
618
+ } else if (node.type === 'StaticBlock') {
619
+ if (flags & HAS_VAR_LOOP) {
620
+ node.metadata = { ...node.metadata, has_lazy_var_loop_descendants: true };
621
+ }
622
+ // Class static blocks own a distinct var environment. Their loop
623
+ // bindings must not be collected by the surrounding function/module.
624
+ flags &= ~HAS_VAR_LOOP;
625
+ } else if (flags & HAS_VAR_LOOP) {
626
+ // Statement containers and their direct statements use this signal to
627
+ // advance function-scoped lazy `var` bindings in source order without
628
+ // rescanning unaffected subtrees during the transform.
629
+ node.metadata = { ...node.metadata, has_lazy_var_loop_descendants: true };
479
630
  }
480
631
 
481
- return found;
632
+ return flags;
482
633
  };
483
634
 
484
635
  visit(root);
@@ -579,6 +730,185 @@ function apply_lazy_transforms_to_slot(node, lazy_bindings) {
579
730
  return /** @type {T} */ (apply_lazy_transforms(node, lazy_bindings));
580
731
  }
581
732
 
733
+ /**
734
+ * Add every preallocated lazy binding below a binding pattern to `lazy_bindings`.
735
+ *
736
+ * @param {AST.Node | null | undefined} pattern
737
+ * @param {Map<string, LazyBinding>} lazy_bindings
738
+ */
739
+ function collect_preallocated_lazy_bindings(pattern, lazy_bindings) {
740
+ visit_topmost_lazy_patterns(pattern, (lazy) => {
741
+ if (!lazy.metadata?.lazy_id) return;
742
+ collect_lazy_bindings(lazy, lazy.metadata.lazy_id, lazy_bindings);
743
+ });
744
+ }
745
+
746
+ /**
747
+ * Collect lazy `var` bindings introduced by JavaScript loop headers in one
748
+ * statement. The caller installs the collected mappings after transforming
749
+ * that statement, including when the loop is nested in ordinary control flow.
750
+ * Nested functions own a different var environment and are not visited.
751
+ *
752
+ * @param {AST.Node} node
753
+ * @param {Map<string, LazyBinding>} lazy_bindings
754
+ */
755
+ function collect_statement_var_loop_bindings(node, lazy_bindings) {
756
+ if (is_function_or_component_node(node) || node.type === 'StaticBlock') return;
757
+
758
+ if (
759
+ node.type === 'ForStatement' &&
760
+ node.init?.type === 'VariableDeclaration' &&
761
+ node.init.kind === 'var'
762
+ ) {
763
+ for (const declarator of node.init.declarations) {
764
+ collect_preallocated_lazy_bindings(declarator.id, lazy_bindings);
765
+ }
766
+ } else if (
767
+ (node.type === 'ForOfStatement' || node.type === 'ForInStatement') &&
768
+ node.left.type === 'VariableDeclaration' &&
769
+ node.left.kind === 'var'
770
+ ) {
771
+ for (const declarator of node.left.declarations) {
772
+ collect_preallocated_lazy_bindings(declarator.id, lazy_bindings);
773
+ }
774
+ }
775
+
776
+ for (const child of child_nodes(node)) {
777
+ collect_statement_var_loop_bindings(child, lazy_bindings);
778
+ }
779
+ }
780
+
781
+ /**
782
+ * Install lazy `var` loop bindings introduced by a statement slot before
783
+ * transforming a later-evaluated slot of the same compound statement. The
784
+ * preallocation pass marks only subtrees that can contribute bindings in the
785
+ * current var environment, so unaffected slots keep the existing map without
786
+ * a rescan or allocation.
787
+ *
788
+ * @param {AST.Node} node
789
+ * @param {Map<string, LazyBinding>} lazy_bindings
790
+ * @returns {Map<string, LazyBinding>}
791
+ */
792
+ function advance_statement_var_loop_bindings(node, lazy_bindings) {
793
+ if (!node.metadata?.has_lazy_var_loop_descendants) return lazy_bindings;
794
+
795
+ /** @type {Map<string, LazyBinding>} */
796
+ const statement_bindings = new Map();
797
+ collect_statement_var_loop_bindings(node, statement_bindings);
798
+ return statement_bindings.size > 0
799
+ ? new Map([...lazy_bindings, ...statement_bindings])
800
+ : lazy_bindings;
801
+ }
802
+
803
+ /**
804
+ * Rewrite a statement list in source order, installing lazy `var` loop
805
+ * bindings only after the statement that establishes them. The mapping then
806
+ * remains visible to subsequent statements in the current var environment.
807
+ *
808
+ * @param {AST.Program['body']} statements
809
+ * @param {Map<string, LazyBinding>} lazy_bindings
810
+ * @param {boolean} has_lazy_var_loop_descendants
811
+ * @returns {{ body: AST.Program['body'], lazy_bindings: Map<string, LazyBinding>, changed: boolean }}
812
+ */
813
+ function transform_statement_list(statements, lazy_bindings, has_lazy_var_loop_descendants) {
814
+ let effective_bindings = lazy_bindings;
815
+ let changed = false;
816
+ const body = statements.map((statement) => {
817
+ const transformed = apply_lazy_transforms_to_slot(statement, effective_bindings);
818
+ if (transformed !== statement) changed = true;
819
+
820
+ if (has_lazy_var_loop_descendants && statement.metadata?.has_lazy_var_loop_descendants) {
821
+ effective_bindings = advance_statement_var_loop_bindings(statement, effective_bindings);
822
+ }
823
+
824
+ return transformed;
825
+ });
826
+
827
+ return { body, lazy_bindings: effective_bindings, changed };
828
+ }
829
+
830
+ /**
831
+ * Rewrite a classic-for declaration in source order. Later initializers can
832
+ * read lazy names introduced by earlier declarators, while the initializer of
833
+ * each declarator is evaluated before that declarator's own lazy bindings are
834
+ * installed.
835
+ *
836
+ * @param {AST.VariableDeclaration} declaration
837
+ * @param {Map<string, LazyBinding>} lazy_bindings
838
+ * @returns {{ declaration: AST.VariableDeclaration, lazy_bindings: Map<string, LazyBinding> }}
839
+ */
840
+ function transform_classic_for_declaration(declaration, lazy_bindings) {
841
+ let effective_bindings = lazy_bindings;
842
+ /** @type {Map<string, LazyBinding> | null} */
843
+ let mutable_bindings = null;
844
+ let changed = false;
845
+ const declarations = declaration.declarations.map((declarator) => {
846
+ const new_init =
847
+ declarator.init && apply_lazy_transforms_to_slot(declarator.init, effective_bindings);
848
+ const expression_id = transform_loop_pattern_expressions(declarator.id, effective_bindings);
849
+ const new_id = replace_lazy_in_pattern(expression_id);
850
+ if (new_init !== declarator.init || new_id !== declarator.id) changed = true;
851
+
852
+ /** @type {Map<string, LazyBinding>} */
853
+ const own_bindings = new Map();
854
+ collect_preallocated_lazy_bindings(expression_id, own_bindings);
855
+ if (own_bindings.size > 0) {
856
+ mutable_bindings ??= new Map(effective_bindings);
857
+ for (const [name, binding] of own_bindings) mutable_bindings.set(name, binding);
858
+ effective_bindings = mutable_bindings;
859
+ }
860
+
861
+ return new_init !== declarator.init || new_id !== declarator.id
862
+ ? rebuild(declarator, { id: new_id, init: new_init })
863
+ : declarator;
864
+ });
865
+
866
+ return {
867
+ declaration: changed ? rebuild(declaration, { declarations }) : declaration,
868
+ lazy_bindings: effective_bindings,
869
+ };
870
+ }
871
+
872
+ /**
873
+ * Rewrite a for-of/in binding target and return the lazy bindings visible in
874
+ * the loop body. Bare targets become per-iteration `const` declarations so all
875
+ * generated source identifiers have an owning declaration.
876
+ *
877
+ * @param {AST.ForOfStatement['left'] | AST.ForInStatement['left']} left
878
+ * @param {Map<string, LazyBinding>} lazy_bindings
879
+ * @returns {{ left: AST.VariableDeclaration, lazy_bindings: Map<string, LazyBinding> } | { left: AST.ForOfStatement['left'] | AST.ForInStatement['left'], lazy_bindings: Map<string, LazyBinding> }}
880
+ */
881
+ function transform_for_each_left(left, lazy_bindings) {
882
+ /** @type {Map<string, LazyBinding>} */
883
+ const own_bindings = new Map();
884
+
885
+ if (left.type === 'VariableDeclaration') {
886
+ let changed = false;
887
+ const declarations = left.declarations.map((declarator) => {
888
+ const expression_id = transform_loop_pattern_expressions(declarator.id, lazy_bindings);
889
+ collect_preallocated_lazy_bindings(expression_id, own_bindings);
890
+ const new_id = replace_lazy_in_pattern(expression_id);
891
+ if (new_id === declarator.id) return declarator;
892
+ changed = true;
893
+ return rebuild(declarator, { id: new_id });
894
+ });
895
+ return {
896
+ left: changed ? rebuild(left, { declarations }) : left,
897
+ lazy_bindings: own_bindings,
898
+ };
899
+ }
900
+
901
+ const expression_left = transform_loop_pattern_expressions(left, lazy_bindings);
902
+ collect_preallocated_lazy_bindings(expression_left, own_bindings);
903
+ if (own_bindings.size === 0) return { left: expression_left, lazy_bindings: own_bindings };
904
+
905
+ const new_id = replace_lazy_in_pattern(expression_left);
906
+ return {
907
+ left: b.declaration('const', [b.declarator(new_id)]),
908
+ lazy_bindings: own_bindings,
909
+ };
910
+ }
911
+
582
912
  /**
583
913
  * Recursively rewrite lazy-binding references in `node`.
584
914
  *
@@ -627,7 +957,6 @@ export function apply_lazy_transforms(node, lazy_bindings) {
627
957
  own_bindings.size > 0
628
958
  ? new Map([...outer_minus_shadow, ...own_bindings])
629
959
  : outer_minus_shadow;
630
-
631
960
  if (
632
961
  inner_bindings.size === 0 &&
633
962
  !params_changed &&
@@ -652,7 +981,7 @@ export function apply_lazy_transforms(node, lazy_bindings) {
652
981
  return node;
653
982
  }
654
983
 
655
- if (node.type === 'BlockStatement' || node.type === 'Program') {
984
+ if (node.type === 'BlockStatement' || node.type === 'Program' || node.type === 'StaticBlock') {
656
985
  /** @type {AST.Program['body']} */
657
986
  const body = node.body;
658
987
  const block_bindings = collect_block_shadowed_names(body, lazy_bindings);
@@ -663,16 +992,14 @@ export function apply_lazy_transforms(node, lazy_bindings) {
663
992
  const block_lazy = new Map();
664
993
  collect_lazy_bindings_from_statements(body, block_lazy);
665
994
 
666
- const effective_bindings =
995
+ const initial_bindings =
667
996
  block_lazy.size > 0 ? new Map([...after_shadow, ...block_lazy]) : after_shadow;
668
-
669
- let changed = false;
670
- const new_body = node.body.map((stmt) => {
671
- const transformed = apply_lazy_transforms_to_slot(stmt, effective_bindings);
672
- if (transformed !== stmt) changed = true;
673
- return transformed;
674
- });
675
- return changed ? rebuild(node, { body: new_body }) : node;
997
+ const transformed = transform_statement_list(
998
+ node.body,
999
+ initial_bindings,
1000
+ node.metadata?.has_lazy_var_loop_descendants === true,
1001
+ );
1002
+ return transformed.changed ? rebuild(node, { body: transformed.body }) : node;
676
1003
  }
677
1004
 
678
1005
  if (node.type === 'CatchClause') {
@@ -686,18 +1013,79 @@ export function apply_lazy_transforms(node, lazy_bindings) {
686
1013
  return node;
687
1014
  }
688
1015
 
1016
+ if (node.type === 'DoWhileStatement') {
1017
+ const new_body = apply_lazy_transforms_to_slot(node.body, lazy_bindings);
1018
+ const test_bindings = advance_statement_var_loop_bindings(node.body, lazy_bindings);
1019
+ const new_test = apply_lazy_transforms_to_slot(node.test, test_bindings);
1020
+ if (new_body !== node.body || new_test !== node.test) {
1021
+ return rebuild(node, { body: new_body, test: new_test });
1022
+ }
1023
+ return node;
1024
+ }
1025
+
1026
+ if (node.type === 'IfStatement') {
1027
+ const new_test = apply_lazy_transforms_to_slot(node.test, lazy_bindings);
1028
+ const new_consequent = apply_lazy_transforms_to_slot(node.consequent, lazy_bindings);
1029
+ const alternate_bindings = advance_statement_var_loop_bindings(node.consequent, lazy_bindings);
1030
+ const new_alternate =
1031
+ node.alternate && apply_lazy_transforms_to_slot(node.alternate, alternate_bindings);
1032
+ if (
1033
+ new_test !== node.test ||
1034
+ new_consequent !== node.consequent ||
1035
+ new_alternate !== node.alternate
1036
+ ) {
1037
+ return rebuild(node, {
1038
+ test: new_test,
1039
+ consequent: new_consequent,
1040
+ alternate: new_alternate,
1041
+ });
1042
+ }
1043
+ return node;
1044
+ }
1045
+
1046
+ if (node.type === 'TryStatement') {
1047
+ const new_block = apply_lazy_transforms_to_slot(node.block, lazy_bindings);
1048
+ const handler_bindings = advance_statement_var_loop_bindings(node.block, lazy_bindings);
1049
+ const new_handler =
1050
+ node.handler && apply_lazy_transforms_to_slot(node.handler, handler_bindings);
1051
+ const finalizer_bindings = node.handler
1052
+ ? advance_statement_var_loop_bindings(node.handler, handler_bindings)
1053
+ : handler_bindings;
1054
+ const new_finalizer =
1055
+ node.finalizer && apply_lazy_transforms_to_slot(node.finalizer, finalizer_bindings);
1056
+ if (
1057
+ new_block !== node.block ||
1058
+ new_handler !== node.handler ||
1059
+ new_finalizer !== node.finalizer
1060
+ ) {
1061
+ return rebuild(node, {
1062
+ block: new_block,
1063
+ handler: new_handler,
1064
+ finalizer: new_finalizer,
1065
+ });
1066
+ }
1067
+ return node;
1068
+ }
1069
+
689
1070
  if (node.type === 'ForStatement') {
690
1071
  /** @type {Set<string>} */
691
1072
  const shadowed = new Set();
692
1073
  if (node.init?.type === 'VariableDeclaration') {
693
1074
  for (const decl of node.init.declarations) {
694
- if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
1075
+ if (decl.id) collect_non_lazy_shadowed_names(decl.id, lazy_bindings, shadowed);
695
1076
  }
696
1077
  }
697
- const effective_bindings =
1078
+ let effective_bindings =
698
1079
  shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
699
1080
  let changed = false;
700
- const new_init = node.init && apply_lazy_transforms_to_slot(node.init, effective_bindings);
1081
+ let new_init = node.init;
1082
+ if (node.init?.type === 'VariableDeclaration') {
1083
+ const transformed = transform_classic_for_declaration(node.init, effective_bindings);
1084
+ new_init = transformed.declaration;
1085
+ effective_bindings = transformed.lazy_bindings;
1086
+ } else if (node.init) {
1087
+ new_init = apply_lazy_transforms_to_slot(node.init, effective_bindings);
1088
+ }
701
1089
  if (new_init !== node.init) changed = true;
702
1090
  const new_test = node.test && apply_lazy_transforms_to_slot(node.test, effective_bindings);
703
1091
  if (new_test !== node.test) changed = true;
@@ -718,24 +1106,27 @@ export function apply_lazy_transforms(node, lazy_bindings) {
718
1106
  for (const decl of node.left.declarations) {
719
1107
  if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
720
1108
  }
1109
+ } else {
1110
+ collect_shadowed_names(node.left, lazy_bindings, shadowed);
721
1111
  }
722
- const effective_bindings =
1112
+ const after_shadow =
723
1113
  shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
724
- // `node.left` is a binding site, not an expression context: a declaration
725
- // like `const x` or `const [a, b]` has no outer references to rewrite,
726
- // and recursing here would hit the VariableDeclarator handler and
727
- // rewrite a lazy declarator id that `preallocate_lazy_ids` already
728
- // tagged — double-processing the loop variable. Leave `node.left`
729
- // untouched; the body and right-hand side are the only scopes with
730
- // live references.
1114
+ const transformed_left = transform_for_each_left(node.left, after_shadow);
1115
+ const effective_bindings =
1116
+ transformed_left.lazy_bindings.size > 0
1117
+ ? new Map([...after_shadow, ...transformed_left.lazy_bindings])
1118
+ : after_shadow;
731
1119
  let changed = false;
1120
+ if (transformed_left.left !== node.left) changed = true;
732
1121
  // The right-hand side is evaluated in the outer scope (before the loop
733
1122
  // variable is bound), so use the unshadowed bindings there.
734
1123
  const new_right = apply_lazy_transforms_to_slot(node.right, lazy_bindings);
735
1124
  if (new_right !== node.right) changed = true;
736
1125
  const new_body = apply_lazy_transforms_to_slot(node.body, effective_bindings);
737
1126
  if (new_body !== node.body) changed = true;
738
- return changed ? rebuild(node, { right: new_right, body: new_body }) : node;
1127
+ return changed
1128
+ ? rebuild(node, { left: transformed_left.left, right: new_right, body: new_body })
1129
+ : node;
739
1130
  }
740
1131
 
741
1132
  if (node.type === 'SwitchStatement') {
@@ -763,20 +1154,23 @@ export function apply_lazy_transforms(node, lazy_bindings) {
763
1154
  // binding declared inside a case body.
764
1155
  const new_discriminant = apply_lazy_transforms_to_slot(node.discriminant, after_shadow);
765
1156
  if (new_discriminant !== node.discriminant) changed = true;
1157
+ let consequent_bindings = effective_bindings;
766
1158
  const new_cases = node.cases.map((switch_case) => {
767
1159
  let case_changed = false;
768
1160
  const new_test = switch_case.test
769
1161
  ? apply_lazy_transforms_to_slot(switch_case.test, effective_bindings)
770
1162
  : null;
771
1163
  if (new_test !== switch_case.test) case_changed = true;
772
- const new_consequent = switch_case.consequent.map((stmt) => {
773
- const transformed = apply_lazy_transforms_to_slot(stmt, effective_bindings);
774
- if (transformed !== stmt) case_changed = true;
775
- return transformed;
776
- });
1164
+ const transformed = transform_statement_list(
1165
+ switch_case.consequent,
1166
+ consequent_bindings,
1167
+ node.metadata?.has_lazy_var_loop_descendants === true,
1168
+ );
1169
+ consequent_bindings = transformed.lazy_bindings;
1170
+ if (transformed.changed) case_changed = true;
777
1171
  if (case_changed) {
778
1172
  changed = true;
779
- return rebuild(switch_case, { test: new_test, consequent: new_consequent });
1173
+ return rebuild(switch_case, { test: new_test, consequent: transformed.body });
780
1174
  }
781
1175
  return switch_case;
782
1176
  });
@@ -786,21 +1180,21 @@ export function apply_lazy_transforms(node, lazy_bindings) {
786
1180
  // Standalone lazy destructuring assignment: `&[data] = track(0);` becomes
787
1181
  // `const __lazy0 = track(0);`. Individual name bindings are already in scope
788
1182
  // via the enclosing BlockStatement handler.
789
- if (
790
- node.type === 'ExpressionStatement' &&
791
- node.expression.type === 'AssignmentExpression' &&
792
- node.expression.operator === '=' &&
793
- (node.expression.left.type === 'ObjectPattern' ||
794
- node.expression.left.type === 'ArrayPattern') &&
795
- node.expression.left.lazy
796
- ) {
797
- const pattern = node.expression.left;
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);
1183
+ if (node.type === 'ExpressionStatement') {
1184
+ const assignment = get_standalone_assignment(node);
1185
+ const pattern = assignment?.left;
1186
+ if (
1187
+ assignment?.operator === '=' &&
1188
+ (pattern?.type === 'ObjectPattern' || pattern?.type === 'ArrayPattern') &&
1189
+ pattern.lazy
1190
+ ) {
1191
+ const lazy_id_name = pattern.metadata?.lazy_id;
1192
+ if (lazy_id_name !== undefined) {
1193
+ const lazy_id = build_lazy_id_for_pattern(pattern, lazy_id_name, false);
1194
+ if (pattern.typeAnnotation) lazy_id.typeAnnotation = pattern.typeAnnotation;
1195
+ const init = apply_lazy_transforms_to_slot(assignment.right, lazy_bindings);
1196
+ return b.const(lazy_id, init);
1197
+ }
804
1198
  }
805
1199
  }
806
1200
 
@@ -828,6 +1222,24 @@ export function apply_lazy_transforms(node, lazy_bindings) {
828
1222
  }
829
1223
  }
830
1224
 
1225
+ // Diagnosed lazy assignments remain ordinary destructuring assignments in
1226
+ // best-effort output. Wrapping the recovery expression is valid in every
1227
+ // expression slot, including braceless statement bodies and nested
1228
+ // assignments where an object-pattern target would otherwise be ambiguous.
1229
+ // Supported standalone assignments have a preallocated ID and are handled
1230
+ // by the declaration branch above before traversal reaches this node.
1231
+ if (
1232
+ node.type === 'AssignmentExpression' &&
1233
+ node.operator === '=' &&
1234
+ (node.left.type === 'ObjectPattern' || node.left.type === 'ArrayPattern') &&
1235
+ node.left.lazy &&
1236
+ !node.left.metadata?.lazy_id
1237
+ ) {
1238
+ const new_right = apply_lazy_transforms_to_slot(node.right, lazy_bindings);
1239
+ const recovered = new_right === node.right ? node : { ...node, right: new_right };
1240
+ return b.parenthesized(recovered, has_location(node) ? node : undefined);
1241
+ }
1242
+
831
1243
  // AssignmentExpression / UpdateExpression whose target is a lazy identifier.
832
1244
  if (
833
1245
  node.type === 'AssignmentExpression' &&
@@ -988,6 +1400,49 @@ function collect_shadowed_names(pattern, lazy_bindings, shadowed) {
988
1400
  }
989
1401
  }
990
1402
 
1403
+ /**
1404
+ * Collect only ordinary bindings from a declaration pattern. Lazy bindings
1405
+ * remain mapped while their own initializer runs and are installed afterward
1406
+ * by `transform_classic_for_declaration` in declarator source order.
1407
+ *
1408
+ * @param {AST.Node | null | undefined} pattern
1409
+ * @param {Map<string, LazyBinding>} lazy_bindings
1410
+ * @param {Set<string>} shadowed
1411
+ */
1412
+ function collect_non_lazy_shadowed_names(pattern, lazy_bindings, shadowed) {
1413
+ if (!pattern || typeof pattern !== 'object') return;
1414
+ if ((pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') && pattern.lazy) {
1415
+ return;
1416
+ }
1417
+ if (pattern.type === 'Identifier' && lazy_bindings.has(pattern.name)) {
1418
+ shadowed.add(pattern.name);
1419
+ return;
1420
+ }
1421
+ if (pattern.type === 'AssignmentPattern') {
1422
+ collect_non_lazy_shadowed_names(pattern.left, lazy_bindings, shadowed);
1423
+ return;
1424
+ }
1425
+ if (pattern.type === 'RestElement') {
1426
+ collect_non_lazy_shadowed_names(pattern.argument, lazy_bindings, shadowed);
1427
+ return;
1428
+ }
1429
+ if (pattern.type === 'ObjectPattern') {
1430
+ for (const prop of pattern.properties) {
1431
+ collect_non_lazy_shadowed_names(
1432
+ prop.type === 'RestElement' ? prop.argument : prop.value,
1433
+ lazy_bindings,
1434
+ shadowed,
1435
+ );
1436
+ }
1437
+ return;
1438
+ }
1439
+ if (pattern.type === 'ArrayPattern') {
1440
+ for (const element of pattern.elements) {
1441
+ if (element) collect_non_lazy_shadowed_names(element, lazy_bindings, shadowed);
1442
+ }
1443
+ }
1444
+ }
1445
+
991
1446
  /**
992
1447
  * @param {AST.Program['body']} statements
993
1448
  * @param {Map<string, LazyBinding>} lazy_bindings