@tsrx/core 0.1.4 → 0.1.7
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 +39 -6
- package/src/diagnostics.js +1 -0
- package/src/index.js +8 -2
- package/src/plugin.js +31 -1
- package/src/runtime/events.js +10 -0
- package/src/runtime/hash.js +1 -0
- package/src/runtime/html.js +3 -0
- package/src/runtime/iterable.js +110 -0
- package/src/source-map-utils.js +19 -2
- package/src/transform/jsx/ast-builders.js +120 -0
- package/src/transform/jsx/index.js +632 -1415
- package/src/transform/lazy.js +301 -205
- package/src/transform/scoping.js +9 -45
- package/src/transform/segments.js +164 -11
- package/src/utils/ast.js +13 -0
- package/src/utils/builders.js +51 -13
- package/src/utils/dom.js +158 -0
- package/src/utils.js +6 -159
- package/types/index.d.ts +1 -0
- package/types/jsx-platform.d.ts +12 -0
- package/types/runtime/events.d.ts +11 -0
- package/types/runtime/hash.d.ts +2 -0
- package/types/runtime/html.d.ts +4 -0
- package/types/runtime/iterable.d.ts +13 -0
- package/types/runtime/language-helpers.d.ts +17 -0
package/src/transform/lazy.js
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
2
|
|
|
3
|
+
import * as b from '../utils/builders.js';
|
|
4
|
+
import { is_function_or_component_node } from '../utils/ast.js';
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* Lazy destructuring transform — framework-agnostic.
|
|
5
8
|
*
|
|
6
|
-
* Shared between `@tsrx/react
|
|
7
|
-
* references to names introduced by `&{ ... }` /
|
|
8
|
-
* patterns into member-expression accesses on a
|
|
9
|
+
* Shared between `@tsrx/react`, `@tsrx/preact`, `@tsrx/solid`, and `@tsrx/vue`.
|
|
10
|
+
* Walks an AST and rewrites references to names introduced by `&{ ... }` /
|
|
11
|
+
* `&[ ... ]` destructuring patterns into member-expression accesses on a
|
|
12
|
+
* generated source identifier.
|
|
9
13
|
*
|
|
10
14
|
* Usage:
|
|
11
15
|
* 1. Create a context with `createLazyContext()` (or provide any object with
|
|
12
16
|
* a `lazy_next_id: number` field).
|
|
13
17
|
* 2. Run `preallocateLazyIds(root, context)` once over the full program to
|
|
14
|
-
* assign stable `metadata.lazy_id` values to every lazy pattern
|
|
15
|
-
*
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* assign stable `metadata.lazy_id` values to every lazy pattern and to
|
|
19
|
+
* flag function-like nodes whose subtree contains any lazy pattern via
|
|
20
|
+
* `metadata.has_lazy_descendants`.
|
|
21
|
+
* 3. After converting components to functions, call `applyLazyTransforms(fn,
|
|
22
|
+
* new Map())` on each top-level function. The function-handler walks the
|
|
23
|
+
* whole subtree, collects param + body bindings, replaces lazy patterns
|
|
24
|
+
* with their generated identifiers, and rewrites every reference.
|
|
20
25
|
*
|
|
21
26
|
* The transform is purely AST-to-AST and has no framework-specific knowledge.
|
|
22
27
|
*/
|
|
@@ -68,7 +73,7 @@ function set_source_location(node, loc_info) {
|
|
|
68
73
|
* @returns {any}
|
|
69
74
|
*/
|
|
70
75
|
function create_generated_identifier(name, loc_info, source_name, source_length) {
|
|
71
|
-
const id =
|
|
76
|
+
const id = b.id(name);
|
|
72
77
|
if (source_name && source_name !== name) id.metadata.source_name = source_name;
|
|
73
78
|
if (source_length != null) id.metadata.source_length = source_length;
|
|
74
79
|
return set_source_location(id, loc_info);
|
|
@@ -111,40 +116,19 @@ function create_lazy_object_type_annotation(pattern) {
|
|
|
111
116
|
const key = prop.key;
|
|
112
117
|
if (key.type !== 'Identifier' && key.type !== 'Literal') continue;
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
type
|
|
116
|
-
|
|
117
|
-
key
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
readonly: false,
|
|
123
|
-
static: false,
|
|
124
|
-
kind: 'init',
|
|
125
|
-
typeAnnotation: {
|
|
126
|
-
type: 'TSTypeAnnotation',
|
|
127
|
-
typeAnnotation: {
|
|
128
|
-
type: 'TSAnyKeyword',
|
|
129
|
-
metadata: { path: [] },
|
|
130
|
-
},
|
|
131
|
-
metadata: { path: [] },
|
|
132
|
-
},
|
|
133
|
-
metadata: { path: [] },
|
|
134
|
-
});
|
|
119
|
+
const member_key =
|
|
120
|
+
key.type === 'Identifier'
|
|
121
|
+
? create_generated_identifier(key.name, key)
|
|
122
|
+
: set_source_location({ ...key, metadata: { path: [] } }, key);
|
|
123
|
+
|
|
124
|
+
members.push(
|
|
125
|
+
b.ts_property_signature(member_key, b.ts_type_annotation(b.ts_keyword_type('any'))),
|
|
126
|
+
);
|
|
135
127
|
}
|
|
136
128
|
|
|
137
129
|
if (members.length === 0) return null;
|
|
138
130
|
|
|
139
|
-
return
|
|
140
|
-
type: 'TSTypeAnnotation',
|
|
141
|
-
typeAnnotation: {
|
|
142
|
-
type: 'TSTypeLiteral',
|
|
143
|
-
members,
|
|
144
|
-
metadata: { path: [] },
|
|
145
|
-
},
|
|
146
|
-
metadata: { path: [] },
|
|
147
|
-
};
|
|
131
|
+
return b.ts_type_annotation(b.ts_type_literal(members));
|
|
148
132
|
}
|
|
149
133
|
|
|
150
134
|
/**
|
|
@@ -215,36 +199,60 @@ function set_lazy_param_binding_mappings(lazy_id, pattern) {
|
|
|
215
199
|
* Collect lazy bindings from a destructuring pattern.
|
|
216
200
|
*
|
|
217
201
|
* For `&{ name, age }` on source `S`, maps `name` → `S.name`, `age` → `S.age`.
|
|
218
|
-
* For `&[a, b]` on source `S`, maps `a` → `S[0]`, `b` → `S[1]`.
|
|
219
|
-
* `
|
|
202
|
+
* For `&[a, b]` on source `S`, maps `a` → `S[0]`, `b` → `S[1]`. Recurses into
|
|
203
|
+
* nested `ObjectPattern` / `ArrayPattern` values so that `&{ outer: &{ inner } }`
|
|
204
|
+
* on source `S` maps `inner` → `S.outer.inner`, and `&{ pair: &[first, second] }`
|
|
205
|
+
* maps `first` → `S.pair[0]`. Handles `AssignmentPattern` (default values lost,
|
|
206
|
+
* but the binding still resolves to the member chain). Skips `RestElement`.
|
|
220
207
|
*
|
|
221
208
|
* @param {any} pattern
|
|
222
209
|
* @param {string} source_name
|
|
223
210
|
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
224
211
|
*/
|
|
225
212
|
export function collect_lazy_bindings(pattern, source_name, lazy_bindings) {
|
|
213
|
+
collect_lazy_bindings_at(
|
|
214
|
+
pattern,
|
|
215
|
+
source_name,
|
|
216
|
+
() => create_generated_identifier(source_name),
|
|
217
|
+
lazy_bindings,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Walk a destructure pattern and register a `LazyBinding` for each leaf
|
|
223
|
+
* `Identifier`, where `build_parent` produces the AST expression that reaches
|
|
224
|
+
* this pattern's value from the synthesized source identifier. Each nested
|
|
225
|
+
* level composes its accessor onto `build_parent`, so leaves get the full
|
|
226
|
+
* member chain (e.g. `source.outer.inner` for `&{ outer: &{ inner } }`).
|
|
227
|
+
*
|
|
228
|
+
* @param {any} pattern
|
|
229
|
+
* @param {string} source_name
|
|
230
|
+
* @param {(reference?: any) => any} build_parent
|
|
231
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
232
|
+
*/
|
|
233
|
+
function collect_lazy_bindings_at(pattern, source_name, build_parent, lazy_bindings) {
|
|
226
234
|
if (pattern.type === 'ObjectPattern') {
|
|
227
235
|
for (const prop of pattern.properties || []) {
|
|
228
236
|
if (prop.type === 'RestElement') continue;
|
|
229
237
|
const value = prop.value;
|
|
230
238
|
const actual = value.type === 'AssignmentPattern' ? value.left : value;
|
|
239
|
+
const key = prop.key;
|
|
240
|
+
const computed = prop.computed || key.type !== 'Identifier';
|
|
241
|
+
|
|
242
|
+
/** @type {(reference?: any) => any} */
|
|
243
|
+
const build_self = (reference) =>
|
|
244
|
+
b.member(
|
|
245
|
+
build_parent(),
|
|
246
|
+
computed || key.type !== 'Identifier'
|
|
247
|
+
? { ...key }
|
|
248
|
+
: create_generated_identifier(key.name, reference, reference?.name),
|
|
249
|
+
computed,
|
|
250
|
+
);
|
|
251
|
+
|
|
231
252
|
if (actual.type === 'Identifier') {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
source_name,
|
|
236
|
-
read: (reference) => ({
|
|
237
|
-
type: 'MemberExpression',
|
|
238
|
-
object: create_generated_identifier(source_name),
|
|
239
|
-
property:
|
|
240
|
-
computed || key.type !== 'Identifier'
|
|
241
|
-
? { ...key }
|
|
242
|
-
: create_generated_identifier(key.name, reference, reference?.name),
|
|
243
|
-
computed,
|
|
244
|
-
optional: false,
|
|
245
|
-
metadata: { path: [] },
|
|
246
|
-
}),
|
|
247
|
-
});
|
|
253
|
+
lazy_bindings.set(actual.name, { source_name, read: build_self });
|
|
254
|
+
} else if (actual.type === 'ObjectPattern' || actual.type === 'ArrayPattern') {
|
|
255
|
+
collect_lazy_bindings_at(actual, source_name, build_self, lazy_bindings);
|
|
248
256
|
}
|
|
249
257
|
}
|
|
250
258
|
} else if (pattern.type === 'ArrayPattern') {
|
|
@@ -253,55 +261,18 @@ export function collect_lazy_bindings(pattern, source_name, lazy_bindings) {
|
|
|
253
261
|
if (!element) continue;
|
|
254
262
|
if (element.type === 'RestElement') continue;
|
|
255
263
|
const actual = element.type === 'AssignmentPattern' ? element.left : element;
|
|
256
|
-
|
|
257
|
-
const index = i;
|
|
258
|
-
lazy_bindings.set(actual.name, {
|
|
259
|
-
source_name,
|
|
260
|
-
read: () => ({
|
|
261
|
-
type: 'MemberExpression',
|
|
262
|
-
object: create_generated_identifier(source_name),
|
|
263
|
-
property: { type: 'Literal', value: index, raw: String(index), metadata: { path: [] } },
|
|
264
|
-
computed: true,
|
|
265
|
-
optional: false,
|
|
266
|
-
metadata: { path: [] },
|
|
267
|
-
}),
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
264
|
+
const index = i;
|
|
273
265
|
|
|
274
|
-
/**
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
* @returns {Map<string, LazyBinding>}
|
|
282
|
-
*/
|
|
283
|
-
export function collect_lazy_bindings_from_component(params, body, context) {
|
|
284
|
-
/** @type {Map<string, LazyBinding>} */
|
|
285
|
-
const lazy_bindings = new Map();
|
|
286
|
-
|
|
287
|
-
for (const param of params) {
|
|
288
|
-
const pattern = param.type === 'AssignmentPattern' ? param.left : param;
|
|
289
|
-
if ((pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') && pattern.lazy) {
|
|
290
|
-
const lazy_name = pattern.metadata?.lazy_id || generate_lazy_id(context);
|
|
291
|
-
if (!pattern.metadata?.lazy_id) {
|
|
292
|
-
pattern.metadata = { ...pattern.metadata, lazy_id: lazy_name };
|
|
266
|
+
/** @type {() => any} */
|
|
267
|
+
const build_self = () => b.member(build_parent(), b.literal(index), true);
|
|
268
|
+
|
|
269
|
+
if (actual.type === 'Identifier') {
|
|
270
|
+
lazy_bindings.set(actual.name, { source_name, read: build_self });
|
|
271
|
+
} else if (actual.type === 'ObjectPattern' || actual.type === 'ArrayPattern') {
|
|
272
|
+
collect_lazy_bindings_at(actual, source_name, build_self, lazy_bindings);
|
|
293
273
|
}
|
|
294
|
-
collect_lazy_bindings(pattern, lazy_name, lazy_bindings);
|
|
295
274
|
}
|
|
296
275
|
}
|
|
297
|
-
|
|
298
|
-
// VariableDeclaration lazy patterns already have their `lazy_id` assigned
|
|
299
|
-
// by `preallocate_lazy_ids` (run once over the whole program by the target
|
|
300
|
-
// transforms), so `collect_lazy_bindings_from_statements` handles them
|
|
301
|
-
// alongside the expression-statement assignment form.
|
|
302
|
-
collect_lazy_bindings_from_statements(body, lazy_bindings);
|
|
303
|
-
|
|
304
|
-
return lazy_bindings;
|
|
305
276
|
}
|
|
306
277
|
|
|
307
278
|
/**
|
|
@@ -317,61 +288,164 @@ export function collect_lazy_bindings_from_statements(statements, lazy_bindings)
|
|
|
317
288
|
for (const stmt of statements || []) {
|
|
318
289
|
if (stmt.type === 'VariableDeclaration') {
|
|
319
290
|
for (const declarator of stmt.declarations || []) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
(
|
|
323
|
-
|
|
324
|
-
pattern.metadata?.lazy_id &&
|
|
325
|
-
!lazy_bindings_contains(lazy_bindings, pattern)
|
|
326
|
-
) {
|
|
327
|
-
collect_lazy_bindings(pattern, pattern.metadata.lazy_id, lazy_bindings);
|
|
328
|
-
}
|
|
291
|
+
visit_topmost_lazy_patterns(declarator.id, (lazy) => {
|
|
292
|
+
if (!lazy.metadata?.lazy_id) return;
|
|
293
|
+
collect_lazy_bindings(lazy, lazy.metadata.lazy_id, lazy_bindings);
|
|
294
|
+
});
|
|
329
295
|
}
|
|
330
296
|
} else if (
|
|
331
297
|
stmt.type === 'ExpressionStatement' &&
|
|
332
298
|
stmt.expression?.type === 'AssignmentExpression' &&
|
|
333
|
-
stmt.expression.operator === '='
|
|
334
|
-
(stmt.expression.left?.type === 'ObjectPattern' ||
|
|
335
|
-
stmt.expression.left?.type === 'ArrayPattern') &&
|
|
336
|
-
stmt.expression.left.lazy &&
|
|
337
|
-
stmt.expression.left.metadata?.lazy_id
|
|
299
|
+
stmt.expression.operator === '='
|
|
338
300
|
) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
);
|
|
301
|
+
visit_topmost_lazy_patterns(stmt.expression.left, (lazy) => {
|
|
302
|
+
if (!lazy.metadata?.lazy_id) return;
|
|
303
|
+
collect_lazy_bindings(lazy, lazy.metadata.lazy_id, lazy_bindings);
|
|
304
|
+
});
|
|
344
305
|
}
|
|
345
306
|
}
|
|
346
307
|
}
|
|
347
308
|
|
|
348
309
|
/**
|
|
349
|
-
*
|
|
310
|
+
* Walk a destructure pattern tree, calling `visit` on every *topmost-lazy*
|
|
311
|
+
* descendant — a lazy `ObjectPattern` / `ArrayPattern` with no lazy ancestor
|
|
312
|
+
* within the same pattern tree. Descends through `AssignmentPattern`,
|
|
313
|
+
* `RestElement`, and non-lazy `ObjectPattern` / `ArrayPattern`. Stops at lazy
|
|
314
|
+
* patterns: their inner leaves are reached via accessor chains rooted at the
|
|
315
|
+
* lazy pattern's synthesized id, not by further descent here.
|
|
316
|
+
*
|
|
350
317
|
* @param {any} pattern
|
|
351
|
-
* @
|
|
318
|
+
* @param {(node: any) => void} visit
|
|
352
319
|
*/
|
|
353
|
-
function
|
|
320
|
+
function visit_topmost_lazy_patterns(pattern, visit) {
|
|
321
|
+
if (!pattern || typeof pattern !== 'object') return;
|
|
322
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
323
|
+
visit_topmost_lazy_patterns(pattern.left, visit);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (pattern.type === 'RestElement') {
|
|
327
|
+
visit_topmost_lazy_patterns(pattern.argument, visit);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (pattern.type !== 'ObjectPattern' && pattern.type !== 'ArrayPattern') return;
|
|
331
|
+
|
|
332
|
+
if (pattern.lazy) {
|
|
333
|
+
visit(pattern);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
354
337
|
if (pattern.type === 'ObjectPattern') {
|
|
355
338
|
for (const prop of pattern.properties || []) {
|
|
356
|
-
if (prop.type === 'RestElement')
|
|
357
|
-
|
|
358
|
-
const actual = value?.type === 'AssignmentPattern' ? value.left : value;
|
|
359
|
-
if (actual?.type === 'Identifier' && lazy_bindings.has(actual.name)) return true;
|
|
339
|
+
if (prop.type === 'RestElement') visit_topmost_lazy_patterns(prop.argument, visit);
|
|
340
|
+
else visit_topmost_lazy_patterns(prop.value, visit);
|
|
360
341
|
}
|
|
361
|
-
} else
|
|
342
|
+
} else {
|
|
362
343
|
for (const element of pattern.elements || []) {
|
|
363
|
-
if (
|
|
364
|
-
const actual = element.type === 'AssignmentPattern' ? element.left : element;
|
|
365
|
-
if (actual?.type === 'Identifier' && lazy_bindings.has(actual.name)) return true;
|
|
344
|
+
if (element) visit_topmost_lazy_patterns(element, visit);
|
|
366
345
|
}
|
|
367
346
|
}
|
|
368
|
-
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Build the replacement identifier for a lazy pattern. When `is_top` is true
|
|
351
|
+
* (the pattern is itself a function parameter) we attach the original
|
|
352
|
+
* `typeAnnotation`, synthesize an object-shaped annotation for untyped object
|
|
353
|
+
* params so TypeScript sees prop names, and register source-mapping info.
|
|
354
|
+
* Nested replacements (inside a non-lazy outer destructure) can't carry an
|
|
355
|
+
* inline type annotation — that's not valid syntax — so they get a plain
|
|
356
|
+
* identifier with just source-range info.
|
|
357
|
+
*
|
|
358
|
+
* @param {any} pattern
|
|
359
|
+
* @param {boolean} is_top
|
|
360
|
+
* @returns {any}
|
|
361
|
+
*/
|
|
362
|
+
function build_lazy_id_for_pattern(pattern, is_top) {
|
|
363
|
+
const pattern_range = get_lazy_pattern_mapping_range(pattern);
|
|
364
|
+
const lazy_id = pattern_range
|
|
365
|
+
? create_generated_identifier(
|
|
366
|
+
pattern.metadata.lazy_id,
|
|
367
|
+
pattern_range,
|
|
368
|
+
undefined,
|
|
369
|
+
pattern_range.source_length,
|
|
370
|
+
)
|
|
371
|
+
: create_generated_identifier(pattern.metadata.lazy_id);
|
|
372
|
+
if (!is_top) return lazy_id;
|
|
373
|
+
if (pattern.typeAnnotation) {
|
|
374
|
+
lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
375
|
+
} else {
|
|
376
|
+
const type_annotation = create_lazy_object_type_annotation(pattern);
|
|
377
|
+
if (type_annotation) lazy_id.typeAnnotation = type_annotation;
|
|
378
|
+
}
|
|
379
|
+
set_lazy_param_binding_mappings(lazy_id, pattern);
|
|
380
|
+
return lazy_id;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Walk a destructure pattern tree and replace every topmost-lazy pattern with
|
|
385
|
+
* its synthesized id identifier. Non-lazy outer patterns are preserved so a
|
|
386
|
+
* source like `{ pair: &[a, b] }` becomes `{ pair: __lazy0 }`. The `is_top`
|
|
387
|
+
* flag is true only when the caller is invoking on a position that itself
|
|
388
|
+
* binds a single param (so a directly-lazy pattern can carry param-level type
|
|
389
|
+
* info); recursive descent into child patterns passes `false`.
|
|
390
|
+
*
|
|
391
|
+
* @param {any} pattern
|
|
392
|
+
* @param {boolean} [is_top]
|
|
393
|
+
* @returns {any}
|
|
394
|
+
*/
|
|
395
|
+
function replace_lazy_in_pattern(pattern, is_top = true) {
|
|
396
|
+
if (!pattern || typeof pattern !== 'object') return pattern;
|
|
397
|
+
|
|
398
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
399
|
+
const new_left = replace_lazy_in_pattern(pattern.left, is_top);
|
|
400
|
+
return new_left === pattern.left ? pattern : { ...pattern, left: new_left };
|
|
401
|
+
}
|
|
402
|
+
if (pattern.type === 'RestElement') {
|
|
403
|
+
const new_arg = replace_lazy_in_pattern(pattern.argument, false);
|
|
404
|
+
return new_arg === pattern.argument ? pattern : { ...pattern, argument: new_arg };
|
|
405
|
+
}
|
|
406
|
+
if (pattern.type !== 'ObjectPattern' && pattern.type !== 'ArrayPattern') return pattern;
|
|
407
|
+
|
|
408
|
+
if (pattern.lazy && pattern.metadata?.lazy_id) {
|
|
409
|
+
return build_lazy_id_for_pattern(pattern, is_top);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (pattern.type === 'ObjectPattern') {
|
|
413
|
+
let changed = false;
|
|
414
|
+
const new_properties = (pattern.properties || []).map((/** @type {any} */ prop) => {
|
|
415
|
+
if (prop.type === 'RestElement') {
|
|
416
|
+
const new_arg = replace_lazy_in_pattern(prop.argument, false);
|
|
417
|
+
if (new_arg === prop.argument) return prop;
|
|
418
|
+
changed = true;
|
|
419
|
+
return { ...prop, argument: new_arg };
|
|
420
|
+
}
|
|
421
|
+
const new_value = replace_lazy_in_pattern(prop.value, false);
|
|
422
|
+
if (new_value === prop.value) return prop;
|
|
423
|
+
changed = true;
|
|
424
|
+
return { ...prop, value: new_value };
|
|
425
|
+
});
|
|
426
|
+
return changed ? { ...pattern, properties: new_properties } : pattern;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let changed = false;
|
|
430
|
+
const new_elements = (pattern.elements || []).map((/** @type {any} */ element) => {
|
|
431
|
+
if (!element) return element;
|
|
432
|
+
const new_element = replace_lazy_in_pattern(element, false);
|
|
433
|
+
if (new_element !== element) changed = true;
|
|
434
|
+
return new_element;
|
|
435
|
+
});
|
|
436
|
+
return changed ? { ...pattern, elements: new_elements } : pattern;
|
|
369
437
|
}
|
|
370
438
|
|
|
371
439
|
/**
|
|
372
440
|
* Walk the AST and pre-allocate `lazy_id` metadata on every lazy destructuring
|
|
373
441
|
* pattern: function/component params, variable declarator ids, and statement-level
|
|
374
|
-
* assignment LHS.
|
|
442
|
+
* assignment LHS. Walks into non-lazy outer patterns to find nested lazy ones,
|
|
443
|
+
* e.g. `{ pair: &[a, b] }` allocates an id for the inner `&[a, b]`. Idempotent:
|
|
444
|
+
* skips patterns that already have a `lazy_id`.
|
|
445
|
+
*
|
|
446
|
+
* Also stamps `metadata.has_lazy_descendants = true` on every function-like
|
|
447
|
+
* node whose subtree contains any lazy pattern, so `apply_lazy_transforms`
|
|
448
|
+
* can take a constant-time early-return path for purely non-lazy functions.
|
|
375
449
|
*
|
|
376
450
|
* @param {any} root
|
|
377
451
|
* @param {LazyContext} context
|
|
@@ -379,32 +453,29 @@ function lazy_bindings_contains(lazy_bindings, pattern) {
|
|
|
379
453
|
export function preallocate_lazy_ids(root, context) {
|
|
380
454
|
/** @param {any} pattern */
|
|
381
455
|
const assign_id = (pattern) => {
|
|
382
|
-
|
|
383
|
-
(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
) {
|
|
387
|
-
pattern.metadata = {
|
|
388
|
-
...pattern.metadata,
|
|
389
|
-
lazy_id: generate_lazy_id(context),
|
|
390
|
-
};
|
|
391
|
-
}
|
|
456
|
+
visit_topmost_lazy_patterns(pattern, (lazy) => {
|
|
457
|
+
if (lazy.metadata?.lazy_id) return;
|
|
458
|
+
lazy.metadata = { ...lazy.metadata, lazy_id: generate_lazy_id(context) };
|
|
459
|
+
});
|
|
392
460
|
};
|
|
393
461
|
|
|
394
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* @param {any} node
|
|
464
|
+
* @returns {boolean} true if `node`'s subtree contains any lazy pattern.
|
|
465
|
+
*/
|
|
395
466
|
const visit = (node) => {
|
|
396
|
-
if (!node || typeof node !== 'object') return;
|
|
467
|
+
if (!node || typeof node !== 'object') return false;
|
|
397
468
|
if (Array.isArray(node)) {
|
|
398
|
-
|
|
399
|
-
|
|
469
|
+
let found = false;
|
|
470
|
+
for (const child of node) {
|
|
471
|
+
if (visit(child)) found = true;
|
|
472
|
+
}
|
|
473
|
+
return found;
|
|
400
474
|
}
|
|
401
475
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
node.type === 'ArrowFunctionExpression' ||
|
|
406
|
-
node.type === 'Component'
|
|
407
|
-
) {
|
|
476
|
+
const is_function_like = is_function_or_component_node(node);
|
|
477
|
+
|
|
478
|
+
if (is_function_like) {
|
|
408
479
|
for (const param of node.params || []) {
|
|
409
480
|
assign_id(param?.type === 'AssignmentPattern' ? param.left : param);
|
|
410
481
|
}
|
|
@@ -422,10 +493,19 @@ export function preallocate_lazy_ids(root, context) {
|
|
|
422
493
|
assign_id(node.expression.left);
|
|
423
494
|
}
|
|
424
495
|
|
|
496
|
+
let found =
|
|
497
|
+
(node.type === 'ObjectPattern' || node.type === 'ArrayPattern') && node.lazy === true;
|
|
498
|
+
|
|
425
499
|
for (const key of Object.keys(node)) {
|
|
426
500
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
427
|
-
visit(node[key]);
|
|
501
|
+
if (visit(node[key])) found = true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (is_function_like && found) {
|
|
505
|
+
node.metadata = { ...node.metadata, has_lazy_descendants: true };
|
|
428
506
|
}
|
|
507
|
+
|
|
508
|
+
return found;
|
|
429
509
|
};
|
|
430
510
|
|
|
431
511
|
visit(root);
|
|
@@ -468,15 +548,11 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
468
548
|
const own_bindings = new Map();
|
|
469
549
|
let had_lazy_param = false;
|
|
470
550
|
for (const param of node.params || []) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
(pattern?.type === 'ObjectPattern' || pattern?.type === 'ArrayPattern') &&
|
|
474
|
-
pattern.lazy &&
|
|
475
|
-
pattern.metadata?.lazy_id
|
|
476
|
-
) {
|
|
551
|
+
visit_topmost_lazy_patterns(param, (lazy) => {
|
|
552
|
+
if (!lazy.metadata?.lazy_id) return;
|
|
477
553
|
had_lazy_param = true;
|
|
478
|
-
collect_lazy_bindings(
|
|
479
|
-
}
|
|
554
|
+
collect_lazy_bindings(lazy, lazy.metadata.lazy_id, own_bindings);
|
|
555
|
+
});
|
|
480
556
|
}
|
|
481
557
|
|
|
482
558
|
// Own bindings override any outer binding with the same name.
|
|
@@ -485,10 +561,20 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
485
561
|
? new Map([...outer_minus_shadow, ...own_bindings])
|
|
486
562
|
: outer_minus_shadow;
|
|
487
563
|
|
|
488
|
-
if (
|
|
564
|
+
if (
|
|
565
|
+
inner_bindings.size === 0 &&
|
|
566
|
+
!params_changed &&
|
|
567
|
+
!had_lazy_param &&
|
|
568
|
+
!node.metadata?.has_lazy_descendants
|
|
569
|
+
) {
|
|
570
|
+
return node;
|
|
571
|
+
}
|
|
489
572
|
|
|
490
|
-
|
|
491
|
-
|
|
573
|
+
// Past the early-return: either we have active lazy bindings, lazy
|
|
574
|
+
// params to replace, defaults referencing outer lazy, or the body
|
|
575
|
+
// contains lazy descendants the BlockStatement handler will collect.
|
|
576
|
+
// In every case the body needs to be walked.
|
|
577
|
+
const new_body = apply_lazy_transforms(node.body, inner_bindings);
|
|
492
578
|
|
|
493
579
|
const final_params_src = params_changed ? new_params : node.params;
|
|
494
580
|
const final_params = had_lazy_param ? replace_lazy_params(final_params_src) : final_params_src;
|
|
@@ -625,19 +711,31 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
625
711
|
const lazy_id = create_generated_identifier(pattern.metadata.lazy_id);
|
|
626
712
|
if (pattern.typeAnnotation) lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
627
713
|
const init = apply_lazy_transforms(node.expression.right, lazy_bindings);
|
|
628
|
-
return
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
714
|
+
return b.const(lazy_id, init);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Non-lazy outer assignment whose LHS contains nested lazy patterns:
|
|
718
|
+
// `{ pair: &[a, b] } = obj` → `{ pair: __lazy0 } = obj`. JS reference
|
|
719
|
+
// semantics carry writes from `__lazy0[0] = x` back into `obj.pair[0]`.
|
|
720
|
+
if (
|
|
721
|
+
node.type === 'ExpressionStatement' &&
|
|
722
|
+
node.expression?.type === 'AssignmentExpression' &&
|
|
723
|
+
node.expression.operator === '=' &&
|
|
724
|
+
(node.expression.left?.type === 'ObjectPattern' ||
|
|
725
|
+
node.expression.left?.type === 'ArrayPattern') &&
|
|
726
|
+
!node.expression.left.lazy
|
|
727
|
+
) {
|
|
728
|
+
const new_left = replace_lazy_in_pattern(node.expression.left);
|
|
729
|
+
if (new_left !== node.expression.left) {
|
|
730
|
+
return {
|
|
731
|
+
...node,
|
|
732
|
+
expression: {
|
|
733
|
+
...node.expression,
|
|
734
|
+
left: new_left,
|
|
735
|
+
right: apply_lazy_transforms(node.expression.right, lazy_bindings),
|
|
637
736
|
},
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
});
|
|
737
|
+
};
|
|
738
|
+
}
|
|
641
739
|
}
|
|
642
740
|
|
|
643
741
|
// AssignmentExpression / UpdateExpression whose target is a lazy identifier.
|
|
@@ -674,6 +772,23 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
674
772
|
};
|
|
675
773
|
}
|
|
676
774
|
|
|
775
|
+
// Non-lazy outer declarator whose id contains nested lazy patterns:
|
|
776
|
+
// `let { pair: &[a, b] } = data` → `let { pair: __lazy0 } = data`.
|
|
777
|
+
if (
|
|
778
|
+
node.type === 'VariableDeclarator' &&
|
|
779
|
+
(node.id?.type === 'ObjectPattern' || node.id?.type === 'ArrayPattern') &&
|
|
780
|
+
!node.id.lazy
|
|
781
|
+
) {
|
|
782
|
+
const new_id = replace_lazy_in_pattern(node.id);
|
|
783
|
+
if (new_id !== node.id) {
|
|
784
|
+
return {
|
|
785
|
+
...node,
|
|
786
|
+
id: new_id,
|
|
787
|
+
init: apply_lazy_transforms(node.init, lazy_bindings),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
677
792
|
// Shorthand object properties `{ name }` → `{ name: __lazy0.name }`.
|
|
678
793
|
if (node.type === 'Property' && node.shorthand && node.value?.type === 'Identifier') {
|
|
679
794
|
const binding = lazy_bindings.get(node.value.name);
|
|
@@ -799,38 +914,19 @@ function remove_shadowed(lazy_bindings, shadowed) {
|
|
|
799
914
|
|
|
800
915
|
/**
|
|
801
916
|
* Replace any lazy `&{}` / `&[]` patterns in a parameter list with their
|
|
802
|
-
* generated lazy identifiers
|
|
917
|
+
* generated lazy identifiers, including patterns nested inside non-lazy outer
|
|
918
|
+
* patterns. For `({ pair: &[a, b] })` returns `({ pair: __lazy0 })`. Leaves
|
|
919
|
+
* params without any lazy descendants untouched.
|
|
803
920
|
*
|
|
804
921
|
* @param {any[]} params
|
|
805
922
|
* @returns {any[]}
|
|
806
923
|
*/
|
|
807
924
|
export function replace_lazy_params(params) {
|
|
808
925
|
return params.map((param) => {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
pattern.lazy &&
|
|
813
|
-
pattern.metadata?.lazy_id
|
|
814
|
-
) {
|
|
815
|
-
const pattern_range = get_lazy_pattern_mapping_range(pattern);
|
|
816
|
-
const lazy_id = pattern_range
|
|
817
|
-
? create_generated_identifier(
|
|
818
|
-
pattern.metadata.lazy_id,
|
|
819
|
-
pattern_range,
|
|
820
|
-
undefined,
|
|
821
|
-
pattern_range.source_length,
|
|
822
|
-
)
|
|
823
|
-
: create_generated_identifier(pattern.metadata.lazy_id);
|
|
824
|
-
if (pattern.typeAnnotation) {
|
|
825
|
-
lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
826
|
-
} else {
|
|
827
|
-
const type_annotation = create_lazy_object_type_annotation(pattern);
|
|
828
|
-
if (type_annotation) lazy_id.typeAnnotation = type_annotation;
|
|
829
|
-
}
|
|
830
|
-
set_lazy_param_binding_mappings(lazy_id, pattern);
|
|
831
|
-
if (param.type === 'AssignmentPattern') return { ...param, left: lazy_id };
|
|
832
|
-
return lazy_id;
|
|
926
|
+
if (param.type === 'AssignmentPattern') {
|
|
927
|
+
const new_left = replace_lazy_in_pattern(param.left);
|
|
928
|
+
return new_left === param.left ? param : { ...param, left: new_left };
|
|
833
929
|
}
|
|
834
|
-
return param;
|
|
930
|
+
return replace_lazy_in_pattern(param);
|
|
835
931
|
});
|
|
836
932
|
}
|