@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.
@@ -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
+ }