@tsrx/core 0.1.63 → 0.1.65
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.
- package/package.json +2 -2
- package/src/analyze/index.js +65 -18
- package/src/analyze/validation.js +19 -0
- package/src/diagnostics.js +1 -0
- package/src/index.js +3 -0
- package/src/plugin.js +100 -37
- package/src/source-map-utils.js +21 -66
- package/src/transform/jsx/index.js +48 -24
- package/src/transform/lazy.js +527 -72
- package/src/transform/segments.js +17 -4
- package/src/utils/ast.js +90 -7
- package/types/index.d.ts +2 -0
package/src/transform/lazy.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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,
|
|
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 {
|
|
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
|
|
544
|
+
if (!value || typeof value !== 'object') return 0;
|
|
438
545
|
if (Array.isArray(value)) {
|
|
439
|
-
let
|
|
546
|
+
let flags = 0;
|
|
440
547
|
for (const child of value) {
|
|
441
|
-
|
|
548
|
+
flags |= visit(child);
|
|
442
549
|
}
|
|
443
|
-
return
|
|
550
|
+
return flags;
|
|
444
551
|
}
|
|
445
|
-
if (!is_ast_node(value)) return
|
|
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 === '
|
|
462
|
-
node.
|
|
463
|
-
node.expression.operator === '='
|
|
568
|
+
(node.type === 'ForOfStatement' || node.type === 'ForInStatement') &&
|
|
569
|
+
node.left.type !== 'VariableDeclaration'
|
|
464
570
|
) {
|
|
465
|
-
assign_id(node.
|
|
571
|
+
assign_id(node.left);
|
|
466
572
|
}
|
|
467
573
|
|
|
468
|
-
|
|
469
|
-
(node.
|
|
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
|
-
|
|
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
|
|
478
|
-
|
|
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
|
|
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
|
|
995
|
+
const initial_bindings =
|
|
667
996
|
block_lazy.size > 0 ? new Map([...after_shadow, ...block_lazy]) : after_shadow;
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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)
|
|
1075
|
+
if (decl.id) collect_non_lazy_shadowed_names(decl.id, lazy_bindings, shadowed);
|
|
695
1076
|
}
|
|
696
1077
|
}
|
|
697
|
-
|
|
1078
|
+
let effective_bindings =
|
|
698
1079
|
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
699
1080
|
let changed = false;
|
|
700
|
-
|
|
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
|
|
1112
|
+
const after_shadow =
|
|
723
1113
|
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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
|
|
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
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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:
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|