@tsrx/core 0.0.4 → 0.0.6
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 +1 -1
- package/src/index.js +32 -1
- package/src/parse/index.js +64 -1
- package/src/parse/style.js +1 -1
- package/src/plugin.js +41 -25
- package/src/source-map-utils.js +1 -1
- package/src/transform/await.js +59 -0
- package/src/transform/jsx-interleave.js +99 -0
- package/src/transform/lazy.js +664 -0
- package/src/transform/scoping.js +180 -0
- package/src/transform/segments.js +218 -52
- package/types/index.d.ts +1 -1
- package/types/parse.d.ts +6 -1
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
/** @import * as AST from 'estree' */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lazy destructuring transform — framework-agnostic.
|
|
5
|
+
*
|
|
6
|
+
* Shared between `@tsrx/react` and `@tsrx/solid`. Walks an AST and rewrites
|
|
7
|
+
* references to names introduced by `&{ ... }` / `&[ ... ]` destructuring
|
|
8
|
+
* patterns into member-expression accesses on a generated source identifier.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* 1. Create a context with `createLazyContext()` (or provide any object with
|
|
12
|
+
* a `lazy_next_id: number` field).
|
|
13
|
+
* 2. Run `preallocateLazyIds(root, context)` once over the full program to
|
|
14
|
+
* assign stable `metadata.lazy_id` values to every lazy pattern.
|
|
15
|
+
* 3. For each function/component scope, collect bindings with
|
|
16
|
+
* `collectLazyBindingsFromComponent(params, body, context)` and pass the
|
|
17
|
+
* resulting map into `applyLazyTransforms(body, map)`.
|
|
18
|
+
* 4. If a component declares lazy params, pass its params through
|
|
19
|
+
* `replaceLazyParams(params)` before emitting.
|
|
20
|
+
*
|
|
21
|
+
* The transform is purely AST-to-AST and has no framework-specific knowledge.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {{ lazy_next_id: number }} LazyContext
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {{ source_name: string, read: () => any }} LazyBinding
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a fresh lazy-id allocation context.
|
|
34
|
+
*
|
|
35
|
+
* @returns {LazyContext}
|
|
36
|
+
*/
|
|
37
|
+
export function create_lazy_context() {
|
|
38
|
+
return { lazy_next_id: 0 };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {LazyContext} context
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
function generate_lazy_id(context) {
|
|
46
|
+
return `__lazy${context.lazy_next_id++}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} name
|
|
51
|
+
* @returns {any}
|
|
52
|
+
*/
|
|
53
|
+
function create_generated_identifier(name) {
|
|
54
|
+
return /** @type {any} */ ({ type: 'Identifier', name, metadata: { path: [] } });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Collect lazy bindings from a destructuring pattern.
|
|
59
|
+
*
|
|
60
|
+
* For `&{ name, age }` on source `S`, maps `name` → `S.name`, `age` → `S.age`.
|
|
61
|
+
* For `&[a, b]` on source `S`, maps `a` → `S[0]`, `b` → `S[1]`. Handles nested
|
|
62
|
+
* `AssignmentPattern` (default values); skips `RestElement`.
|
|
63
|
+
*
|
|
64
|
+
* @param {any} pattern
|
|
65
|
+
* @param {string} source_name
|
|
66
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
67
|
+
*/
|
|
68
|
+
export function collect_lazy_bindings(pattern, source_name, lazy_bindings) {
|
|
69
|
+
if (pattern.type === 'ObjectPattern') {
|
|
70
|
+
for (const prop of pattern.properties || []) {
|
|
71
|
+
if (prop.type === 'RestElement') continue;
|
|
72
|
+
const value = prop.value;
|
|
73
|
+
const actual = value.type === 'AssignmentPattern' ? value.left : value;
|
|
74
|
+
if (actual.type === 'Identifier') {
|
|
75
|
+
const key = prop.key;
|
|
76
|
+
const computed = prop.computed || key.type !== 'Identifier';
|
|
77
|
+
lazy_bindings.set(actual.name, {
|
|
78
|
+
source_name,
|
|
79
|
+
read: () => ({
|
|
80
|
+
type: 'MemberExpression',
|
|
81
|
+
object: create_generated_identifier(source_name),
|
|
82
|
+
property: computed
|
|
83
|
+
? { ...key }
|
|
84
|
+
: { type: 'Identifier', name: key.name, metadata: { path: [] } },
|
|
85
|
+
computed,
|
|
86
|
+
optional: false,
|
|
87
|
+
metadata: { path: [] },
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} else if (pattern.type === 'ArrayPattern') {
|
|
93
|
+
for (let i = 0; i < (pattern.elements || []).length; i++) {
|
|
94
|
+
const element = pattern.elements[i];
|
|
95
|
+
if (!element) continue;
|
|
96
|
+
if (element.type === 'RestElement') continue;
|
|
97
|
+
const actual = element.type === 'AssignmentPattern' ? element.left : element;
|
|
98
|
+
if (actual.type === 'Identifier') {
|
|
99
|
+
const index = i;
|
|
100
|
+
lazy_bindings.set(actual.name, {
|
|
101
|
+
source_name,
|
|
102
|
+
read: () => ({
|
|
103
|
+
type: 'MemberExpression',
|
|
104
|
+
object: create_generated_identifier(source_name),
|
|
105
|
+
property: { type: 'Literal', value: index, raw: String(index), metadata: { path: [] } },
|
|
106
|
+
computed: true,
|
|
107
|
+
optional: false,
|
|
108
|
+
metadata: { path: [] },
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Collect lazy bindings from a component's params and top-level body declarations.
|
|
118
|
+
* Mutates each lazy pattern's `metadata.lazy_id` in place (idempotent if already set).
|
|
119
|
+
*
|
|
120
|
+
* @param {any[]} params
|
|
121
|
+
* @param {any[]} body
|
|
122
|
+
* @param {LazyContext} context
|
|
123
|
+
* @returns {Map<string, LazyBinding>}
|
|
124
|
+
*/
|
|
125
|
+
export function collect_lazy_bindings_from_component(params, body, context) {
|
|
126
|
+
/** @type {Map<string, LazyBinding>} */
|
|
127
|
+
const lazy_bindings = new Map();
|
|
128
|
+
|
|
129
|
+
for (const param of params) {
|
|
130
|
+
const pattern = param.type === 'AssignmentPattern' ? param.left : param;
|
|
131
|
+
if ((pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') && pattern.lazy) {
|
|
132
|
+
const lazy_name = pattern.metadata?.lazy_id || generate_lazy_id(context);
|
|
133
|
+
if (!pattern.metadata?.lazy_id) {
|
|
134
|
+
pattern.metadata = { ...pattern.metadata, lazy_id: lazy_name };
|
|
135
|
+
}
|
|
136
|
+
collect_lazy_bindings(pattern, lazy_name, lazy_bindings);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// VariableDeclaration lazy patterns already have their `lazy_id` assigned
|
|
141
|
+
// by `preallocate_lazy_ids` (run once over the whole program by the target
|
|
142
|
+
// transforms), so `collect_lazy_bindings_from_statements` handles them
|
|
143
|
+
// alongside the expression-statement assignment form.
|
|
144
|
+
collect_lazy_bindings_from_statements(body, lazy_bindings);
|
|
145
|
+
|
|
146
|
+
return lazy_bindings;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Collect lazy bindings from statements at the top level of a block. Reads
|
|
151
|
+
* already-allocated `lazy_id` values from pattern metadata. Handles both
|
|
152
|
+
* `let &[x] = ...` variable declarations and statement-level `&[x] = expr;`
|
|
153
|
+
* assignment expressions.
|
|
154
|
+
*
|
|
155
|
+
* @param {any[]} statements
|
|
156
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
157
|
+
*/
|
|
158
|
+
export function collect_lazy_bindings_from_statements(statements, lazy_bindings) {
|
|
159
|
+
for (const stmt of statements || []) {
|
|
160
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
161
|
+
for (const declarator of stmt.declarations || []) {
|
|
162
|
+
const pattern = declarator.id;
|
|
163
|
+
if (
|
|
164
|
+
(pattern?.type === 'ObjectPattern' || pattern?.type === 'ArrayPattern') &&
|
|
165
|
+
pattern.lazy &&
|
|
166
|
+
pattern.metadata?.lazy_id &&
|
|
167
|
+
!lazy_bindings_contains(lazy_bindings, pattern)
|
|
168
|
+
) {
|
|
169
|
+
collect_lazy_bindings(pattern, pattern.metadata.lazy_id, lazy_bindings);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else if (
|
|
173
|
+
stmt.type === 'ExpressionStatement' &&
|
|
174
|
+
stmt.expression?.type === 'AssignmentExpression' &&
|
|
175
|
+
stmt.expression.operator === '=' &&
|
|
176
|
+
(stmt.expression.left?.type === 'ObjectPattern' ||
|
|
177
|
+
stmt.expression.left?.type === 'ArrayPattern') &&
|
|
178
|
+
stmt.expression.left.lazy &&
|
|
179
|
+
stmt.expression.left.metadata?.lazy_id
|
|
180
|
+
) {
|
|
181
|
+
collect_lazy_bindings(
|
|
182
|
+
stmt.expression.left,
|
|
183
|
+
stmt.expression.left.metadata.lazy_id,
|
|
184
|
+
lazy_bindings,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
192
|
+
* @param {any} pattern
|
|
193
|
+
* @returns {boolean}
|
|
194
|
+
*/
|
|
195
|
+
function lazy_bindings_contains(lazy_bindings, pattern) {
|
|
196
|
+
if (pattern.type === 'ObjectPattern') {
|
|
197
|
+
for (const prop of pattern.properties || []) {
|
|
198
|
+
if (prop.type === 'RestElement') continue;
|
|
199
|
+
const value = prop.value;
|
|
200
|
+
const actual = value?.type === 'AssignmentPattern' ? value.left : value;
|
|
201
|
+
if (actual?.type === 'Identifier' && lazy_bindings.has(actual.name)) return true;
|
|
202
|
+
}
|
|
203
|
+
} else if (pattern.type === 'ArrayPattern') {
|
|
204
|
+
for (const element of pattern.elements || []) {
|
|
205
|
+
if (!element || element.type === 'RestElement') continue;
|
|
206
|
+
const actual = element.type === 'AssignmentPattern' ? element.left : element;
|
|
207
|
+
if (actual?.type === 'Identifier' && lazy_bindings.has(actual.name)) return true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Walk the AST and pre-allocate `lazy_id` metadata on every lazy destructuring
|
|
215
|
+
* pattern: function/component params, variable declarator ids, and statement-level
|
|
216
|
+
* assignment LHS. Idempotent: skips patterns that already have a `lazy_id`.
|
|
217
|
+
*
|
|
218
|
+
* @param {any} root
|
|
219
|
+
* @param {LazyContext} context
|
|
220
|
+
*/
|
|
221
|
+
export function preallocate_lazy_ids(root, context) {
|
|
222
|
+
/** @param {any} pattern */
|
|
223
|
+
const assign_id = (pattern) => {
|
|
224
|
+
if (
|
|
225
|
+
(pattern?.type === 'ObjectPattern' || pattern?.type === 'ArrayPattern') &&
|
|
226
|
+
pattern.lazy &&
|
|
227
|
+
!pattern.metadata?.lazy_id
|
|
228
|
+
) {
|
|
229
|
+
pattern.metadata = {
|
|
230
|
+
...pattern.metadata,
|
|
231
|
+
lazy_id: generate_lazy_id(context),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/** @param {any} node */
|
|
237
|
+
const visit = (node) => {
|
|
238
|
+
if (!node || typeof node !== 'object') return;
|
|
239
|
+
if (Array.isArray(node)) {
|
|
240
|
+
for (const child of node) visit(child);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
node.type === 'FunctionDeclaration' ||
|
|
246
|
+
node.type === 'FunctionExpression' ||
|
|
247
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
248
|
+
node.type === 'Component'
|
|
249
|
+
) {
|
|
250
|
+
for (const param of node.params || []) {
|
|
251
|
+
assign_id(param?.type === 'AssignmentPattern' ? param.left : param);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (node.type === 'VariableDeclarator') {
|
|
256
|
+
assign_id(node.id);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
node.type === 'ExpressionStatement' &&
|
|
261
|
+
node.expression?.type === 'AssignmentExpression' &&
|
|
262
|
+
node.expression.operator === '='
|
|
263
|
+
) {
|
|
264
|
+
assign_id(node.expression.left);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (const key of Object.keys(node)) {
|
|
268
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
269
|
+
visit(node[key]);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
visit(root);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Recursively rewrite lazy-binding references in `node`.
|
|
278
|
+
*
|
|
279
|
+
* @param {any} node
|
|
280
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
281
|
+
* @returns {any}
|
|
282
|
+
*/
|
|
283
|
+
export function apply_lazy_transforms(node, lazy_bindings) {
|
|
284
|
+
if (!node || typeof node !== 'object') return node;
|
|
285
|
+
if (Array.isArray(node)) return node.map((child) => apply_lazy_transforms(child, lazy_bindings));
|
|
286
|
+
|
|
287
|
+
if (
|
|
288
|
+
node.type === 'FunctionDeclaration' ||
|
|
289
|
+
node.type === 'FunctionExpression' ||
|
|
290
|
+
node.type === 'ArrowFunctionExpression'
|
|
291
|
+
) {
|
|
292
|
+
// Default parameter values are evaluated in the outer scope — transform them first.
|
|
293
|
+
let params_changed = false;
|
|
294
|
+
const new_params = (node.params || []).map((/** @type {any} */ param) => {
|
|
295
|
+
const transformed = transform_param_defaults(param, lazy_bindings);
|
|
296
|
+
if (transformed !== param) params_changed = true;
|
|
297
|
+
return transformed;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
/** @type {Set<string>} */
|
|
301
|
+
const shadowed = new Set();
|
|
302
|
+
for (const param of node.params || []) {
|
|
303
|
+
collect_shadowed_names(param, lazy_bindings, shadowed);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const outer_minus_shadow =
|
|
307
|
+
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
308
|
+
|
|
309
|
+
/** @type {Map<string, LazyBinding>} */
|
|
310
|
+
const own_bindings = new Map();
|
|
311
|
+
let had_lazy_param = false;
|
|
312
|
+
for (const param of node.params || []) {
|
|
313
|
+
const pattern = param?.type === 'AssignmentPattern' ? param.left : param;
|
|
314
|
+
if (
|
|
315
|
+
(pattern?.type === 'ObjectPattern' || pattern?.type === 'ArrayPattern') &&
|
|
316
|
+
pattern.lazy &&
|
|
317
|
+
pattern.metadata?.lazy_id
|
|
318
|
+
) {
|
|
319
|
+
had_lazy_param = true;
|
|
320
|
+
collect_lazy_bindings(pattern, pattern.metadata.lazy_id, own_bindings);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Own bindings override any outer binding with the same name.
|
|
325
|
+
const inner_bindings =
|
|
326
|
+
own_bindings.size > 0
|
|
327
|
+
? new Map([...outer_minus_shadow, ...own_bindings])
|
|
328
|
+
: outer_minus_shadow;
|
|
329
|
+
|
|
330
|
+
if (inner_bindings.size === 0 && !params_changed && !had_lazy_param) return node;
|
|
331
|
+
|
|
332
|
+
const new_body =
|
|
333
|
+
inner_bindings.size > 0 ? apply_lazy_transforms(node.body, inner_bindings) : node.body;
|
|
334
|
+
|
|
335
|
+
const final_params_src = params_changed ? new_params : node.params;
|
|
336
|
+
const final_params = had_lazy_param ? replace_lazy_params(final_params_src) : final_params_src;
|
|
337
|
+
|
|
338
|
+
if (new_body !== node.body || final_params !== node.params) {
|
|
339
|
+
return { ...node, params: final_params, body: new_body };
|
|
340
|
+
}
|
|
341
|
+
return node;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (node.type === 'BlockStatement' || node.type === 'Program') {
|
|
345
|
+
const block_bindings = collect_block_shadowed_names(node.body, lazy_bindings);
|
|
346
|
+
const after_shadow =
|
|
347
|
+
block_bindings.size > 0 ? remove_shadowed(lazy_bindings, block_bindings) : lazy_bindings;
|
|
348
|
+
|
|
349
|
+
/** @type {Map<string, LazyBinding>} */
|
|
350
|
+
const block_lazy = new Map();
|
|
351
|
+
collect_lazy_bindings_from_statements(node.body, block_lazy);
|
|
352
|
+
|
|
353
|
+
const effective_bindings =
|
|
354
|
+
block_lazy.size > 0 ? new Map([...after_shadow, ...block_lazy]) : after_shadow;
|
|
355
|
+
|
|
356
|
+
let changed = false;
|
|
357
|
+
const new_body = node.body.map((/** @type {any} */ stmt) => {
|
|
358
|
+
const transformed = apply_lazy_transforms(stmt, effective_bindings);
|
|
359
|
+
if (transformed !== stmt) changed = true;
|
|
360
|
+
return transformed;
|
|
361
|
+
});
|
|
362
|
+
return changed ? { ...node, body: new_body } : node;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (node.type === 'CatchClause') {
|
|
366
|
+
/** @type {Set<string>} */
|
|
367
|
+
const shadowed = new Set();
|
|
368
|
+
if (node.param) collect_shadowed_names(node.param, lazy_bindings, shadowed);
|
|
369
|
+
const effective_bindings =
|
|
370
|
+
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
371
|
+
const new_body = apply_lazy_transforms(node.body, effective_bindings);
|
|
372
|
+
if (new_body !== node.body) return { ...node, body: new_body };
|
|
373
|
+
return node;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (node.type === 'ForStatement') {
|
|
377
|
+
/** @type {Set<string>} */
|
|
378
|
+
const shadowed = new Set();
|
|
379
|
+
if (node.init?.type === 'VariableDeclaration') {
|
|
380
|
+
for (const decl of node.init.declarations) {
|
|
381
|
+
if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const effective_bindings =
|
|
385
|
+
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
386
|
+
let changed = false;
|
|
387
|
+
const new_init = apply_lazy_transforms(node.init, effective_bindings);
|
|
388
|
+
if (new_init !== node.init) changed = true;
|
|
389
|
+
const new_test = apply_lazy_transforms(node.test, effective_bindings);
|
|
390
|
+
if (new_test !== node.test) changed = true;
|
|
391
|
+
const new_update = apply_lazy_transforms(node.update, effective_bindings);
|
|
392
|
+
if (new_update !== node.update) changed = true;
|
|
393
|
+
const new_body = apply_lazy_transforms(node.body, effective_bindings);
|
|
394
|
+
if (new_body !== node.body) changed = true;
|
|
395
|
+
return changed
|
|
396
|
+
? { ...node, init: new_init, test: new_test, update: new_update, body: new_body }
|
|
397
|
+
: node;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
|
|
401
|
+
/** @type {Set<string>} */
|
|
402
|
+
const shadowed = new Set();
|
|
403
|
+
if (node.left?.type === 'VariableDeclaration') {
|
|
404
|
+
for (const decl of node.left.declarations) {
|
|
405
|
+
if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const effective_bindings =
|
|
409
|
+
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
410
|
+
// `node.left` is a binding site, not an expression context: a declaration
|
|
411
|
+
// like `const x` or `const [a, b]` has no outer references to rewrite,
|
|
412
|
+
// and recursing here would hit the VariableDeclarator handler and
|
|
413
|
+
// rewrite a lazy declarator id that `preallocate_lazy_ids` already
|
|
414
|
+
// tagged — double-processing the loop variable. Leave `node.left`
|
|
415
|
+
// untouched; the body and right-hand side are the only scopes with
|
|
416
|
+
// live references.
|
|
417
|
+
let changed = false;
|
|
418
|
+
// The right-hand side is evaluated in the outer scope (before the loop
|
|
419
|
+
// variable is bound), so use the unshadowed bindings there.
|
|
420
|
+
const new_right = apply_lazy_transforms(node.right, lazy_bindings);
|
|
421
|
+
if (new_right !== node.right) changed = true;
|
|
422
|
+
const new_body = apply_lazy_transforms(node.body, effective_bindings);
|
|
423
|
+
if (new_body !== node.body) changed = true;
|
|
424
|
+
return changed ? { ...node, right: new_right, body: new_body } : node;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (node.type === 'SwitchStatement') {
|
|
428
|
+
let changed = false;
|
|
429
|
+
const new_discriminant = apply_lazy_transforms(node.discriminant, lazy_bindings);
|
|
430
|
+
if (new_discriminant !== node.discriminant) changed = true;
|
|
431
|
+
const new_cases = node.cases.map((/** @type {any} */ switch_case) => {
|
|
432
|
+
const case_bindings = collect_block_shadowed_names(switch_case.consequent, lazy_bindings);
|
|
433
|
+
const effective_bindings =
|
|
434
|
+
case_bindings.size > 0 ? remove_shadowed(lazy_bindings, case_bindings) : lazy_bindings;
|
|
435
|
+
let case_changed = false;
|
|
436
|
+
const new_test = switch_case.test
|
|
437
|
+
? apply_lazy_transforms(switch_case.test, lazy_bindings)
|
|
438
|
+
: null;
|
|
439
|
+
if (new_test !== switch_case.test) case_changed = true;
|
|
440
|
+
const new_consequent = switch_case.consequent.map((/** @type {any} */ stmt) => {
|
|
441
|
+
const transformed = apply_lazy_transforms(stmt, effective_bindings);
|
|
442
|
+
if (transformed !== stmt) case_changed = true;
|
|
443
|
+
return transformed;
|
|
444
|
+
});
|
|
445
|
+
if (case_changed) {
|
|
446
|
+
changed = true;
|
|
447
|
+
return { ...switch_case, test: new_test, consequent: new_consequent };
|
|
448
|
+
}
|
|
449
|
+
return switch_case;
|
|
450
|
+
});
|
|
451
|
+
return changed ? { ...node, discriminant: new_discriminant, cases: new_cases } : node;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Standalone lazy destructuring assignment: `&[data] = track(0);` becomes
|
|
455
|
+
// `const __lazy0 = track(0);`. Individual name bindings are already in scope
|
|
456
|
+
// via the enclosing BlockStatement handler.
|
|
457
|
+
if (
|
|
458
|
+
node.type === 'ExpressionStatement' &&
|
|
459
|
+
node.expression?.type === 'AssignmentExpression' &&
|
|
460
|
+
node.expression.operator === '=' &&
|
|
461
|
+
(node.expression.left?.type === 'ObjectPattern' ||
|
|
462
|
+
node.expression.left?.type === 'ArrayPattern') &&
|
|
463
|
+
node.expression.left.lazy &&
|
|
464
|
+
node.expression.left.metadata?.lazy_id
|
|
465
|
+
) {
|
|
466
|
+
const pattern = node.expression.left;
|
|
467
|
+
const lazy_id = create_generated_identifier(pattern.metadata.lazy_id);
|
|
468
|
+
if (pattern.typeAnnotation) lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
469
|
+
const init = apply_lazy_transforms(node.expression.right, lazy_bindings);
|
|
470
|
+
return /** @type {any} */ ({
|
|
471
|
+
type: 'VariableDeclaration',
|
|
472
|
+
kind: 'const',
|
|
473
|
+
declarations: [
|
|
474
|
+
{
|
|
475
|
+
type: 'VariableDeclarator',
|
|
476
|
+
id: lazy_id,
|
|
477
|
+
init,
|
|
478
|
+
metadata: { path: [] },
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
metadata: { path: [] },
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// AssignmentExpression / UpdateExpression whose target is a lazy identifier.
|
|
486
|
+
if (
|
|
487
|
+
node.type === 'AssignmentExpression' &&
|
|
488
|
+
node.left?.type === 'Identifier' &&
|
|
489
|
+
lazy_bindings.has(node.left.name)
|
|
490
|
+
) {
|
|
491
|
+
const binding = /** @type {LazyBinding} */ (lazy_bindings.get(node.left.name));
|
|
492
|
+
return {
|
|
493
|
+
...node,
|
|
494
|
+
left: binding.read(),
|
|
495
|
+
right: apply_lazy_transforms(node.right, lazy_bindings),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (
|
|
500
|
+
node.type === 'UpdateExpression' &&
|
|
501
|
+
node.argument?.type === 'Identifier' &&
|
|
502
|
+
lazy_bindings.has(node.argument.name)
|
|
503
|
+
) {
|
|
504
|
+
const binding = /** @type {LazyBinding} */ (lazy_bindings.get(node.argument.name));
|
|
505
|
+
return { ...node, argument: binding.read() };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Replace lazy variable declaration patterns with generated identifiers.
|
|
509
|
+
if (node.type === 'VariableDeclarator' && node.id?.metadata?.lazy_id) {
|
|
510
|
+
const lazy_id = create_generated_identifier(node.id.metadata.lazy_id);
|
|
511
|
+
if (node.id.typeAnnotation) lazy_id.typeAnnotation = node.id.typeAnnotation;
|
|
512
|
+
return {
|
|
513
|
+
...node,
|
|
514
|
+
id: lazy_id,
|
|
515
|
+
init: apply_lazy_transforms(node.init, lazy_bindings),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Shorthand object properties `{ name }` → `{ name: __lazy0.name }`.
|
|
520
|
+
if (node.type === 'Property' && node.shorthand && node.value?.type === 'Identifier') {
|
|
521
|
+
const binding = lazy_bindings.get(node.value.name);
|
|
522
|
+
if (binding) {
|
|
523
|
+
return { ...node, shorthand: false, value: binding.read() };
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Bare identifier reference.
|
|
528
|
+
if (node.type === 'Identifier' && lazy_bindings.has(node.name)) {
|
|
529
|
+
const binding = /** @type {LazyBinding} */ (lazy_bindings.get(node.name));
|
|
530
|
+
return binding.read();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// JSXIdentifier is a label (component/element name), never a reference.
|
|
534
|
+
if (node.type === 'JSXIdentifier') return node;
|
|
535
|
+
|
|
536
|
+
let changed = false;
|
|
537
|
+
/** @type {any} */
|
|
538
|
+
const clone = { ...node };
|
|
539
|
+
for (const key of Object.keys(node)) {
|
|
540
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
541
|
+
|
|
542
|
+
// Skip non-computed, non-shorthand property keys (they are labels).
|
|
543
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) continue;
|
|
544
|
+
// Skip non-computed member expression property access.
|
|
545
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) continue;
|
|
546
|
+
// Skip JSXMemberExpression property (label, not reference).
|
|
547
|
+
if (key === 'property' && node.type === 'JSXMemberExpression') continue;
|
|
548
|
+
// Skip JSXAttribute names (labels).
|
|
549
|
+
if (key === 'name' && node.type === 'JSXAttribute') continue;
|
|
550
|
+
// Skip VariableDeclarator id (already handled above).
|
|
551
|
+
if (key === 'id' && node.type === 'VariableDeclarator') continue;
|
|
552
|
+
|
|
553
|
+
const new_value = apply_lazy_transforms(node[key], lazy_bindings);
|
|
554
|
+
if (new_value !== node[key]) {
|
|
555
|
+
clone[key] = new_value;
|
|
556
|
+
changed = true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return changed ? clone : node;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* @param {any} param
|
|
564
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
565
|
+
*/
|
|
566
|
+
function transform_param_defaults(param, lazy_bindings) {
|
|
567
|
+
if (param?.type === 'AssignmentPattern') {
|
|
568
|
+
const new_right = apply_lazy_transforms(param.right, lazy_bindings);
|
|
569
|
+
if (new_right !== param.right) return { ...param, right: new_right };
|
|
570
|
+
}
|
|
571
|
+
return param;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* @param {any} pattern
|
|
576
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
577
|
+
* @param {Set<string>} shadowed
|
|
578
|
+
*/
|
|
579
|
+
function collect_shadowed_names(pattern, lazy_bindings, shadowed) {
|
|
580
|
+
if (!pattern || typeof pattern !== 'object') return;
|
|
581
|
+
if (pattern.type === 'Identifier' && lazy_bindings.has(pattern.name)) {
|
|
582
|
+
shadowed.add(pattern.name);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
586
|
+
collect_shadowed_names(pattern.left, lazy_bindings, shadowed);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (pattern.type === 'RestElement') {
|
|
590
|
+
collect_shadowed_names(pattern.argument, lazy_bindings, shadowed);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (pattern.type === 'ObjectPattern') {
|
|
594
|
+
for (const prop of pattern.properties || []) {
|
|
595
|
+
if (prop.type === 'RestElement') {
|
|
596
|
+
collect_shadowed_names(prop.argument, lazy_bindings, shadowed);
|
|
597
|
+
} else {
|
|
598
|
+
collect_shadowed_names(prop.value, lazy_bindings, shadowed);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (pattern.type === 'ArrayPattern') {
|
|
604
|
+
for (const element of pattern.elements || []) {
|
|
605
|
+
if (element) collect_shadowed_names(element, lazy_bindings, shadowed);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* @param {any[]} statements
|
|
612
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
613
|
+
* @returns {Set<string>}
|
|
614
|
+
*/
|
|
615
|
+
function collect_block_shadowed_names(statements, lazy_bindings) {
|
|
616
|
+
/** @type {Set<string>} */
|
|
617
|
+
const shadowed = new Set();
|
|
618
|
+
for (const stmt of statements) {
|
|
619
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
620
|
+
for (const decl of stmt.declarations) {
|
|
621
|
+
if (decl.id?.metadata?.lazy_id) continue;
|
|
622
|
+
if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
|
|
623
|
+
}
|
|
624
|
+
} else if (stmt.type === 'FunctionDeclaration' && stmt.id) {
|
|
625
|
+
if (lazy_bindings.has(stmt.id.name)) shadowed.add(stmt.id.name);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return shadowed;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
633
|
+
* @param {Set<string>} shadowed
|
|
634
|
+
* @returns {Map<string, LazyBinding>}
|
|
635
|
+
*/
|
|
636
|
+
function remove_shadowed(lazy_bindings, shadowed) {
|
|
637
|
+
const result = new Map(lazy_bindings);
|
|
638
|
+
for (const name of shadowed) result.delete(name);
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Replace any lazy `&{}` / `&[]` patterns in a parameter list with their
|
|
644
|
+
* generated lazy identifiers. Leaves non-lazy params untouched.
|
|
645
|
+
*
|
|
646
|
+
* @param {any[]} params
|
|
647
|
+
* @returns {any[]}
|
|
648
|
+
*/
|
|
649
|
+
export function replace_lazy_params(params) {
|
|
650
|
+
return params.map((param) => {
|
|
651
|
+
const pattern = param.type === 'AssignmentPattern' ? param.left : param;
|
|
652
|
+
if (
|
|
653
|
+
(pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') &&
|
|
654
|
+
pattern.lazy &&
|
|
655
|
+
pattern.metadata?.lazy_id
|
|
656
|
+
) {
|
|
657
|
+
const lazy_id = create_generated_identifier(pattern.metadata.lazy_id);
|
|
658
|
+
if (pattern.typeAnnotation) lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
659
|
+
if (param.type === 'AssignmentPattern') return { ...param, left: lazy_id };
|
|
660
|
+
return lazy_id;
|
|
661
|
+
}
|
|
662
|
+
return param;
|
|
663
|
+
});
|
|
664
|
+
}
|