@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.
- package/package.json +10 -1
- package/src/analyze/prune.js +62 -77
- package/src/analyze/validation.js +7 -7
- package/src/parse/index.js +59 -80
- package/src/plugin.js +219 -107
- package/src/runtime/language-helpers.js +34 -25
- package/src/runtime/ref.js +57 -36
- package/src/scope.js +4 -5
- package/src/source-map-utils.js +8 -22
- package/src/transform/imports.js +8 -14
- package/src/transform/jsx/ast-builders.js +4 -1
- package/src/transform/jsx/index.js +1 -13
- package/src/transform/jsx/server-module.js +102 -96
- package/src/transform/jsx-hoist.js +4 -4
- package/src/transform/lazy.js +196 -137
- package/src/transform/scoping.js +108 -100
- package/src/transform/segments.js +24 -49
- package/src/transform/style-ref.js +92 -106
- package/src/transform/stylesheet.js +5 -21
- package/src/utils/ast.js +25 -20
- package/src/utils/builders.js +66 -2
- package/src/vite/dep-scan.js +135 -0
- package/types/index.d.ts +317 -17
- package/types/parse.d.ts +98 -13
- package/types/runtime/language-helpers.d.ts +10 -5
- package/types/runtime/ref.d.ts +63 -14
- package/types/vite/dep-scan.d.ts +26 -0
package/src/transform/lazy.js
CHANGED
|
@@ -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
|
-
* @
|
|
56
|
-
* @param {
|
|
57
|
-
* @
|
|
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 {
|
|
65
|
+
* @param {MaybeLocated | null} [loc_info]
|
|
71
66
|
* @param {string} [source_name]
|
|
72
67
|
* @param {number} [source_length]
|
|
73
|
-
* @returns {
|
|
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 {
|
|
84
|
-
* @returns {{ start: number, end: number, loc:
|
|
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 {
|
|
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
|
-
*
|
|
114
|
-
*
|
|
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
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
for (const member of members) {
|
|
122
|
-
if (member.type !== 'TSPropertySignature'
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
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 {
|
|
137
|
-
* @param {
|
|
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 {
|
|
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 {
|
|
197
|
+
* @param {LazyPattern} pattern
|
|
197
198
|
* @param {string} source_name
|
|
198
|
-
* @param {
|
|
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 {
|
|
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 <
|
|
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 {
|
|
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 {
|
|
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 {
|
|
286
|
-
* @param {(node:
|
|
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 {
|
|
329
|
+
* @param {LazyPattern} pattern
|
|
330
|
+
* @param {string} lazy_id_name the id `preallocate_lazy_ids` assigned
|
|
329
331
|
* @param {boolean} is_top
|
|
330
|
-
* @returns {
|
|
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
|
-
|
|
338
|
+
lazy_id_name,
|
|
337
339
|
pattern_range,
|
|
338
340
|
undefined,
|
|
339
341
|
pattern_range.source_length,
|
|
340
342
|
)
|
|
341
|
-
: create_generated_identifier(
|
|
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 {
|
|
360
|
+
* @param {AST.Pattern} pattern
|
|
359
361
|
* @param {boolean} [is_top]
|
|
360
|
-
* @returns {
|
|
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
|
-
|
|
376
|
-
|
|
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 =
|
|
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 =
|
|
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 {
|
|
420
|
+
* @param {AST.Node} root
|
|
418
421
|
* @param {LazyContext} context
|
|
419
422
|
*/
|
|
420
423
|
export function preallocate_lazy_ids(root, context) {
|
|
421
|
-
/** @param {
|
|
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 {
|
|
431
|
-
* @returns {boolean} true if `
|
|
433
|
+
* @param {unknown} value
|
|
434
|
+
* @returns {boolean} true if `value`'s subtree contains any lazy pattern.
|
|
432
435
|
*/
|
|
433
|
-
const visit = (
|
|
434
|
-
if (!
|
|
435
|
-
if (Array.isArray(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
489
|
-
* @param {
|
|
490
|
-
* @returns {
|
|
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
|
|
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 {
|
|
516
|
+
* @param {ESTreeJSX.TSRXJSXOpeningElement['name'] | null | undefined} name
|
|
511
517
|
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
512
|
-
* @returns {
|
|
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 {
|
|
585
|
+
* @param {AST.Node} node
|
|
532
586
|
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
533
|
-
* @returns {
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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(
|
|
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((
|
|
616
|
-
const transformed =
|
|
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 ?
|
|
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 =
|
|
630
|
-
if (new_body !== node.body) return
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
?
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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((
|
|
766
|
+
const new_cases = node.cases.map((switch_case) => {
|
|
713
767
|
let case_changed = false;
|
|
714
768
|
const new_test = switch_case.test
|
|
715
|
-
?
|
|
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((
|
|
719
|
-
const transformed =
|
|
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
|
|
779
|
+
return rebuild(switch_case, { test: new_test, consequent: new_consequent });
|
|
726
780
|
}
|
|
727
781
|
return switch_case;
|
|
728
782
|
});
|
|
729
|
-
return changed ?
|
|
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
|
|
791
|
+
node.expression.type === 'AssignmentExpression' &&
|
|
738
792
|
node.expression.operator === '=' &&
|
|
739
|
-
(node.expression.left
|
|
740
|
-
node.expression.left
|
|
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
|
|
746
|
-
if (
|
|
747
|
-
|
|
748
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 {
|
|
845
|
-
|
|
846
|
-
|
|
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
|
|
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 =
|
|
876
|
-
if (new_value !==
|
|
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
|
-
* @
|
|
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
|
|
890
|
-
const new_right =
|
|
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 {
|
|
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 {
|
|
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
|
|
944
|
-
|
|
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 {
|
|
971
|
-
* @returns {
|
|
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) => {
|