@tsrx/core 0.1.48 → 0.1.49

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.
@@ -1,6 +1,6 @@
1
1
  /** @import * as AST from 'estree' */
2
2
  /** @import * as ESTreeJSX from 'estree-jsx' */
3
- /** @import { JsxHelperComponent, JsxPlatform, JsxTransformContext as TransformContext, JsxTransformOptions, JsxTransformResult } from '@tsrx/core/types' */
3
+ /** @import { BaseNodeMetaData, FunctionMetaData, JsxHelperComponent, JsxHelperState, JsxPlatform, JsxStyleContext, JsxTransformContext as TransformContext, JsxTransformOptions, JsxTransformResult, JsxVisitorContext } from '@tsrx/core/types' */
4
4
 
5
5
  import { walk } from 'zimmerframe';
6
6
  import { print } from 'esrap';
@@ -28,12 +28,9 @@ import {
28
28
  flatten_switch_consequent,
29
29
  get_for_of_iteration_params,
30
30
  identifier_to_jsx_identifier,
31
- identifier_to_jsx_name,
32
31
  is_bare_render_expression,
33
32
  is_component_jsx_name,
34
- is_jsx_child,
35
33
  set_loc,
36
- to_text_expression,
37
34
  } from './ast-builders.js';
38
35
  import { render_css_result } from '../stylesheet.js';
39
36
  import {
@@ -63,7 +60,10 @@ import {
63
60
  import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
64
61
  import { lower_server_module_for_types } from './server-module.js';
65
62
  import {
63
+ child_nodes,
66
64
  has_location,
65
+ is_ast_node,
66
+ is_function_node,
67
67
  is_function_or_class_node as is_function_or_class_boundary,
68
68
  is_template_directive as is_jsx_control_flow_expression,
69
69
  } from '../../utils/ast.js';
@@ -109,20 +109,15 @@ function report_jsx_fragment_in_tsrx_error(node, transform_context) {
109
109
  }
110
110
 
111
111
  /**
112
- * @param {any} node
112
+ * @param {AST.Node} node
113
113
  * @param {boolean} [inside_function]
114
- * @param {Set<any>} [seen]
114
+ * @param {Set<AST.Node>} [seen]
115
115
  * @returns {void}
116
116
  */
117
117
  function mark_nested_function_return_jsx(node, inside_function = false, seen = new Set()) {
118
- if (!node || typeof node !== 'object' || seen.has(node)) return;
118
+ if (seen.has(node)) return;
119
119
  seen.add(node);
120
120
 
121
- if (Array.isArray(node)) {
122
- for (const item of node) mark_nested_function_return_jsx(item, inside_function, seen);
123
- return;
124
- }
125
-
126
121
  const now_inside = inside_function || is_function_or_class_boundary(node);
127
122
 
128
123
  if (
@@ -132,12 +127,11 @@ function mark_nested_function_return_jsx(node, inside_function = false, seen = n
132
127
  node.argument?.type === 'JSXElement' ||
133
128
  node.argument?.type === 'JSXStyleElement')
134
129
  ) {
135
- node.argument.metadata = { ...(node.argument.metadata || {}), native_tsrx: true };
130
+ node.argument.metadata = { ...node.argument.metadata, native_tsrx: true };
136
131
  }
137
132
 
138
- for (const key of Object.keys(node)) {
139
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
140
- mark_nested_function_return_jsx(node[key], now_inside, seen);
133
+ for (const child of child_nodes(node)) {
134
+ mark_nested_function_return_jsx(child, now_inside, seen);
141
135
  }
142
136
  }
143
137
 
@@ -157,12 +151,13 @@ function mark_nested_function_return_jsx(node, inside_function = false, seen = n
157
151
  * (`transform_jsx_code_block` / `build_render_statements`).
158
152
  *
159
153
  * Always returns zero or one node.
160
- * @param {any} block
161
- * @returns {any[]}
154
+ * @param {AST.JSXCodeBlock} block
155
+ * @returns {AST.Node[]}
162
156
  */
163
157
  function lower_code_block_child(block) {
164
158
  const body = block.body || [];
165
159
  const render = block.render ?? null;
160
+ const block_loc = has_location(block) ? block : undefined;
166
161
 
167
162
  if (body.length === 0) {
168
163
  if (render == null) return [];
@@ -173,10 +168,10 @@ function lower_code_block_child(block) {
173
168
  if (render?.type === 'JSXCodeBlock') {
174
169
  const inner = lower_code_block_child(render);
175
170
  if (inner.length === 0) {
176
- return [b.block(body, block)];
171
+ return [b.block(body, block_loc)];
177
172
  }
178
173
  if (inner[0].type === 'BlockStatement') {
179
- return [b.block([...body, inner[0]], block)];
174
+ return [b.block([...body, inner[0]], block_loc)];
180
175
  }
181
176
  // The chain still renders — simplify the render to the lowered inner
182
177
  // node and leave the block for the context-aware lowering.
@@ -184,7 +179,7 @@ function lower_code_block_child(block) {
184
179
  }
185
180
 
186
181
  if (render == null) {
187
- return [b.block(body, block)];
182
+ return [b.block(body, block_loc)];
188
183
  }
189
184
 
190
185
  return [block];
@@ -199,49 +194,57 @@ function lower_code_block_child(block) {
199
194
  * The input tree is never mutated: replacements land on a shallow copy of the
200
195
  * owning node (or array), so the return value must be used in place of the
201
196
  * argument. Untouched subtrees are shared by reference with the input.
202
- * @param {any} node
203
- * @param {Set<any>} [seen]
204
- * @returns {any}
197
+ *
198
+ * @template {AST.Node} T
199
+ * @param {T} node
200
+ * @param {Set<AST.Node>} [seen]
201
+ * @returns {T}
205
202
  */
206
203
  function expand_child_code_blocks(node, seen = new Set()) {
207
- if (!node || typeof node !== 'object' || seen.has(node)) return node;
204
+ if (seen.has(node)) return node;
208
205
  seen.add(node);
209
206
 
210
- if (Array.isArray(node)) {
211
- let changed = false;
212
- const result = node.map((item) => {
213
- const walked = expand_child_code_blocks(item, seen);
214
- if (walked !== item) changed = true;
215
- return walked;
216
- });
217
- return changed ? result : node;
218
- }
219
-
220
- let out = node;
221
- const set = (/** @type {string} */ key, /** @type {any} */ value) => {
207
+ const source = /** @type {AST.TraversableAstNode} */ (node);
208
+ let out = source;
209
+ const set = (/** @type {string} */ key, /** @type {unknown} */ value) => {
222
210
  if (out[key] === value) return;
223
- if (out === node) out = { ...node };
211
+ if (out === source) out = { ...source };
224
212
  out[key] = value;
225
213
  };
226
214
 
215
+ const children = source.children;
227
216
  if (
228
- Array.isArray(node.children) &&
229
- node.children.some((/** @type {any} */ c) => c?.type === 'JSXCodeBlock')
217
+ Array.isArray(children) &&
218
+ children.some((c) => is_ast_node(c) && c.type === 'JSXCodeBlock')
230
219
  ) {
231
220
  set(
232
221
  'children',
233
- node.children.flatMap((/** @type {any} */ child) =>
234
- child?.type === 'JSXCodeBlock' ? lower_code_block_child(child) : [child],
222
+ children.flatMap((child) =>
223
+ is_ast_node(child) && child.type === 'JSXCodeBlock'
224
+ ? lower_code_block_child(child)
225
+ : [child],
235
226
  ),
236
227
  );
237
228
  }
238
229
 
239
230
  for (const key of Object.keys(out)) {
240
231
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
241
- set(key, expand_child_code_blocks(out[key], seen));
232
+ const value = out[key];
233
+ if (Array.isArray(value)) {
234
+ let changed = false;
235
+ const result = value.map((entry) => {
236
+ if (!is_ast_node(entry)) return entry;
237
+ const walked = expand_child_code_blocks(entry, seen);
238
+ if (walked !== entry) changed = true;
239
+ return walked;
240
+ });
241
+ if (changed) set(key, result);
242
+ } else if (is_ast_node(value)) {
243
+ set(key, expand_child_code_blocks(value, seen));
244
+ }
242
245
  }
243
246
 
244
- return out;
247
+ return /** @type {T} */ (out);
245
248
  }
246
249
 
247
250
  /**
@@ -250,8 +253,8 @@ function expand_child_code_blocks(node, seen = new Set()) {
250
253
  * GENERATED wrapper (it wraps a control-flow directive / render output so it
251
254
  * lowers to a value) — it is marked `tsrx_generated_wrapper` so the single-child
252
255
  * collapse keeps unwrapping it, unlike an AUTHORED `<> … </>` which is kept.
253
- * @param {any} node
254
- * @returns {any}
256
+ * @param {AST.Node} node
257
+ * @returns {AST.TSRXJSXFragment}
255
258
  */
256
259
  function wrap_in_native_tsrx_fragment(node) {
257
260
  const fragment = b.jsx_fragment([node]);
@@ -267,8 +270,8 @@ function wrap_in_native_tsrx_fragment(node) {
267
270
  * An AUTHORED `<> … </>` fragment (not a compiler-generated wrapper, nor a Ripple
268
271
  * code-block-chain wrapper). These are kept verbatim in the output instead of
269
272
  * being unwrapped to their single child.
270
- * @param {any} node
271
- * @returns {boolean}
273
+ * @param {AST.Node | null | undefined} node
274
+ * @returns {node is AST.TSRXJSXFragment}
272
275
  */
273
276
  function is_authored_native_fragment(node) {
274
277
  return (
@@ -285,7 +288,7 @@ function is_authored_native_fragment(node) {
285
288
  * stray "control flow used as a value". Everything else is a value position:
286
289
  * an unhandled control-flow directive there is the raw-value error case
287
290
  * (a `@for` iterable, an `@if`/`@switch` test, etc.).
288
- * @param {any} parent
291
+ * @param {AST.Node | null | undefined} parent
289
292
  * @param {string} key
290
293
  * @returns {boolean}
291
294
  */
@@ -312,7 +315,7 @@ function is_statement_or_template_slot(parent, key) {
312
315
  * (`const x = @switch …`, `() => @if …`, `return @if …`, `render(@for …)`),
313
316
  * distinct from combining a directive INTO an expression (an operator operand, a
314
317
  * `@for` iterable, an `@if`/`@switch` test), which is an error.
315
- * @param {any} parent
318
+ * @param {AST.Node | null | undefined} parent
316
319
  * @param {string} key
317
320
  * @returns {boolean}
318
321
  */
@@ -344,8 +347,8 @@ function is_render_output_value_slot(parent, key) {
344
347
  * collapse is NOT invisible: a fragment is always a truthy element, but its
345
348
  * collapsed content may be falsy, so `<>{0}</> || 'x'` (renders `0`) must not turn
346
349
  * into `0 || 'x'` (renders `'x'`). Keep the fragment in these positions.
347
- * @param {any} parent
348
- * @param {any} child
350
+ * @param {AST.Node | null | undefined} parent
351
+ * @param {AST.Node} child
349
352
  * @returns {boolean}
350
353
  */
351
354
  function is_combined_expression_position(parent, child) {
@@ -358,7 +361,7 @@ function is_combined_expression_position(parent, child) {
358
361
  return parent.right !== child;
359
362
  case 'CallExpression':
360
363
  case 'NewExpression':
361
- return !(Array.isArray(parent.arguments) && parent.arguments.includes(child));
364
+ return !parent.arguments.some((argument) => argument === child);
362
365
  default:
363
366
  return true;
364
367
  }
@@ -368,18 +371,16 @@ function is_combined_expression_position(parent, child) {
368
371
  * Re-wrap an already-lowered render value in a `<> … </>` fragment so a fragment
369
372
  * combined into an expression keeps its fragment identity (see
370
373
  * `is_combined_expression_position`). A value that is already a fragment is left
371
- * as-is; a JSX element/text nests directly (`<><span /></>`); any other
372
- * expression goes in a `{ … }` container (`<>{0}</>`).
373
- * @param {any} expression
374
- * @param {any} source
375
- * @returns {any}
374
+ * as-is; a JSX element nests directly (`<><span /></>`); any other expression
375
+ * goes in a `{ … }` container (`<>{0}</>`).
376
+ * @param {AST.Expression | ESTreeJSX.JSXExpressionContainer} expression
377
+ * @param {AST.Node} source
378
+ * @returns {AST.TSRXJSXFragment}
376
379
  */
377
380
  function wrap_lowered_value_in_fragment(expression, source) {
378
- if (expression?.type === 'JSXFragment') return expression;
381
+ if (expression.type === 'JSXFragment') return expression;
379
382
  const child =
380
- expression?.type === 'JSXElement' ||
381
- expression?.type === 'JSXText' ||
382
- expression?.type === 'JSXExpressionContainer'
383
+ expression.type === 'JSXElement' || expression.type === 'JSXExpressionContainer'
383
384
  ? expression
384
385
  : to_jsx_expression_container(expression, source);
385
386
  return set_loc(b.jsx_fragment([child]), has_location(source) ? source : undefined);
@@ -408,13 +409,14 @@ function wrap_lowered_value_in_fragment(expression, source) {
408
409
  * the owning node (or array), so the return value must be used in place of the
409
410
  * argument. Untouched subtrees are shared by reference with the input.
410
411
  *
411
- * @param {any} node
412
+ * @template {AST.Node} T
413
+ * @param {T} node
412
414
  * @param {TransformContext} transform_context
413
- * @param {Set<any>} [seen]
414
- * @returns {any}
415
+ * @param {Set<AST.Node>} [seen]
416
+ * @returns {T}
415
417
  */
416
418
  function wrap_control_flow_expression_values(node, transform_context, seen = new Set()) {
417
- if (!node || typeof node !== 'object' || seen.has(node)) return node;
419
+ if (seen.has(node)) return node;
418
420
  seen.add(node);
419
421
 
420
422
  // Dynamic tags on factory platforms must lower before control-flow
@@ -426,8 +428,13 @@ function wrap_control_flow_expression_values(node, transform_context, seen = new
426
428
  // carrying the raw dynamic tag. Alias lowerings return a replacement
427
429
  // fragment, which is swapped into the child's position here.
428
430
  const lower_dynamic = !!transform_context?.platform?.imports?.dynamicFactory;
429
- const lower_child = (/** @type {any} */ child) => {
430
- if (!lower_dynamic || child?.type !== 'JSXElement') return child;
431
+ /**
432
+ * @template {AST.Node} C
433
+ * @param {C} child
434
+ * @returns {C | AST.TSRXJSXElement | AST.TSRXJSXFragment}
435
+ */
436
+ const lower_child = (child) => {
437
+ if (!lower_dynamic || child.type !== 'JSXElement') return child;
431
438
  return lower_dynamic_jsx_element(child, transform_context) ?? child;
432
439
  };
433
440
 
@@ -438,39 +445,36 @@ function wrap_control_flow_expression_values(node, transform_context, seen = new
438
445
  // position the fragment is then KEPT (a fragment is a truthy value); in a
439
446
  // "raw value" slot like a `@for` iterable it collapses to its rendered value
440
447
  // (see the JSXFragment visitor and `is_combined_expression_position`).
441
- const wrap_directive_in_expression = (/** @type {any} */ value) =>
442
- is_jsx_control_flow_expression(value) || value?.type === 'JSXCodeBlock'
448
+ /**
449
+ * @template {AST.Node} V
450
+ * @param {V} value
451
+ * @returns {V | AST.TSRXJSXFragment}
452
+ */
453
+ const wrap_directive_in_expression = (value) =>
454
+ is_jsx_control_flow_expression(value) || value.type === 'JSXCodeBlock'
443
455
  ? wrap_in_native_tsrx_fragment(value)
444
456
  : value;
445
457
 
446
- if (Array.isArray(node)) {
447
- let changed = false;
448
- const result = node.map((entry) => {
449
- const walked = wrap_control_flow_expression_values(
450
- lower_child(entry),
451
- transform_context,
452
- seen,
453
- );
454
- if (walked !== entry) changed = true;
455
- return walked;
456
- });
457
- return changed ? result : node;
458
- }
459
-
460
458
  // Wrap a bare control-flow directive that is the sole value of a render-output
461
459
  // slot in a native TSRX fragment, collapsing to its rendered value. (A `@{ … }`
462
460
  // code block in the same slots already self-lowers to an IIFE, so it is left
463
461
  // as-is.) These render-output slots are the only value positions a directive is
464
462
  // allowed in; see `is_render_output_value_slot`.
465
- const wrap_value = (/** @type {any} */ value) =>
463
+ /**
464
+ * @template {AST.Node} V
465
+ * @param {V} value
466
+ * @returns {V | AST.TSRXJSXFragment}
467
+ */
468
+ const wrap_value = (value) =>
466
469
  is_jsx_control_flow_expression(value) ? wrap_in_native_tsrx_fragment(value) : value;
467
470
 
468
471
  // All replacements land on `out`, a shallow copy made on first write; the
469
472
  // input node's fields are never reassigned.
470
- let out = node;
471
- const set = (/** @type {string} */ key, /** @type {any} */ value) => {
473
+ const source = /** @type {AST.TraversableAstNode} */ (node);
474
+ let out = source;
475
+ const set = (/** @type {string} */ key, /** @type {unknown} */ value) => {
472
476
  if (out[key] === value) return;
473
- if (out === node) out = { ...node };
477
+ if (out === source) out = { ...source };
474
478
  out[key] = value;
475
479
  };
476
480
 
@@ -495,12 +499,9 @@ function wrap_control_flow_expression_values(node, transform_context, seen = new
495
499
  (node.type === 'CallExpression' || node.type === 'NewExpression') &&
496
500
  Array.isArray(node.arguments)
497
501
  ) {
498
- const wrapped = node.arguments.map(wrap_value);
499
- if (
500
- wrapped.some(
501
- (/** @type {any} */ argument, /** @type {number} */ i) => argument !== node.arguments[i],
502
- )
503
- ) {
502
+ const args = node.arguments;
503
+ const wrapped = args.map(wrap_value);
504
+ if (wrapped.some((argument, i) => argument !== args[i])) {
504
505
  set('arguments', wrapped);
505
506
  }
506
507
  }
@@ -516,6 +517,8 @@ function wrap_control_flow_expression_values(node, transform_context, seen = new
516
517
  if (Array.isArray(value)) {
517
518
  let changed = false;
518
519
  const result = value.map((entry) => {
520
+ if (!is_ast_node(entry)) return entry;
521
+ /** @type {AST.Node} */
519
522
  let next = lower_child(entry);
520
523
  if (!allowed_slot) next = wrap_directive_in_expression(next);
521
524
  next = wrap_control_flow_expression_values(next, transform_context, seen);
@@ -523,14 +526,15 @@ function wrap_control_flow_expression_values(node, transform_context, seen = new
523
526
  return next;
524
527
  });
525
528
  if (changed) set(key, result);
526
- } else {
529
+ } else if (is_ast_node(value)) {
530
+ /** @type {AST.Node} */
527
531
  let next = lower_child(value);
528
532
  if (!allowed_slot) next = wrap_directive_in_expression(next);
529
533
  set(key, wrap_control_flow_expression_values(next, transform_context, seen));
530
534
  }
531
535
  }
532
536
 
533
- return out;
537
+ return /** @type {T} */ (out);
534
538
  }
535
539
 
536
540
  /**
@@ -599,6 +603,11 @@ export function createJsxTransform(platform) {
599
603
  errors: collect ? options?.errors : undefined,
600
604
  comments: options?.comments,
601
605
  typeOnly: !!options?.typeOnly,
606
+ // Opt-in navigation origins (see stamp_directive_origin). OFF by
607
+ // default, and the editor pipeline never asks for it: with the flag
608
+ // clear the emitted code, the source map and therefore every Volar
609
+ // mapping are byte-identical to before.
610
+ inspect: !!options?.inspect,
602
611
  // Platforms can seed their own tracking state (e.g. solid's
603
612
  // needs_show / needs_for flags) via `hooks.initialState`.
604
613
  ...(platform.hooks?.initialState?.() ?? {}),
@@ -612,19 +621,22 @@ export function createJsxTransform(platform) {
612
621
  // Copy-on-write: a program without a server block passes through as the
613
622
  // same object.
614
623
  if (transform_context.typeOnly && platform.serverModule) {
615
- ast = /** @type {any} */ (
616
- lower_server_module_for_types(/** @type {any} */ (ast), platform.serverModule)
617
- );
624
+ ast = lower_server_module_for_types(ast, platform.serverModule);
618
625
  }
619
626
 
620
- ast = expand_child_code_blocks(/** @type {any} */ (ast));
621
- ast = wrap_control_flow_expression_values(/** @type {any} */ (ast), transform_context);
627
+ // Both passes are copy-on-write over arbitrary property values, so they
628
+ // hand back the same shape they were given.
629
+ ast = /** @type {AST.Program} */ (expand_child_code_blocks(ast));
630
+ ast = /** @type {AST.Program} */ (wrap_control_flow_expression_values(ast, transform_context));
622
631
 
623
632
  if (!transform_context.typeOnly) {
624
- preallocate_lazy_ids(/** @type {any} */ (ast), transform_context);
633
+ preallocate_lazy_ids(ast, transform_context);
625
634
  }
626
635
 
627
- const transformed = walk(/** @type {any} */ (ast), transform_context, {
636
+ // Walked as `AST.Node` rather than `AST.Program`: zimmerframe keys its
637
+ // visitor map off the node type it is given, and only the `Node` union
638
+ // admits a visitor per node type.
639
+ const transformed = walk(/** @type {AST.Node} */ (ast), transform_context, {
628
640
  _(node, { next, path }) {
629
641
  set_node_path_metadata(node, path);
630
642
  return next();
@@ -635,28 +647,33 @@ export function createJsxTransform(platform) {
635
647
  return next() ?? node;
636
648
  }
637
649
 
638
- const parent = /** @type {AST.ArrowFunctionExpression} */ (path.at(-1));
639
- if (parent?.metadata?.native_tsrx && parent.body === node) {
640
- return /** @type {any} */ (visit(create_native_tsrx_render_block(node, state), state));
650
+ const parent = path.at(-1);
651
+ if (
652
+ parent &&
653
+ is_function_node(parent) &&
654
+ parent.metadata?.native_tsrx &&
655
+ parent.body === node
656
+ ) {
657
+ return visit(create_native_tsrx_render_block(node, state), state);
641
658
  }
642
659
 
643
660
  const style_context = prepare_tsrx_fragment_styles(node, state);
644
- const target = style_context?.fragment ?? next() ?? node;
661
+ const target = /** @type {AST.TSRXJSXElement | AST.TSRXJSXFragment} */ (
662
+ style_context?.fragment ?? next() ?? node
663
+ );
645
664
  // An EMPTY fragment that is the sole expression of a `{ … }` container in a
646
665
  // JSX child slot (`<b>{<></>}</b>`) must stay `<></>`: the container already
647
666
  // supplies the `{}` wrapper, so lowering it to a bare `null` (the default
648
667
  // expression-position behavior) drops the source fragment. This matches how
649
668
  // the same fragment is preserved in an attribute value (`a={<></>}`).
650
669
  // Non-empty fragments keep their existing lowering.
651
- const immediate_parent = /** @type {any} */ (path[path.length - 1]);
670
+ const immediate_parent = path[path.length - 1];
652
671
  const is_empty_container_child =
653
672
  immediate_parent?.type === 'JSXExpressionContainer' &&
654
673
  in_jsx_child_context(path.slice(0, -1)) &&
655
- !(target.children || []).some(
656
- (/** @type {any} */ child) =>
657
- child &&
658
- child.type !== 'EmptyStatement' &&
659
- (child.type !== 'JSXText' || child.value !== ''),
674
+ !node_children(target).some(
675
+ (child) =>
676
+ child.type !== 'EmptyStatement' && (child.type !== 'JSXText' || child.value !== ''),
660
677
  );
661
678
  const in_jsx_child = in_jsx_child_context(path) || is_empty_container_child;
662
679
  let expression = tsrx_node_to_jsx_expression(target, state, in_jsx_child);
@@ -680,7 +697,7 @@ export function createJsxTransform(platform) {
680
697
  )) {
681
698
  add_jsx_setup_declaration(expression, statement);
682
699
  }
683
- return /** @type {any} */ (wrap_jsx_setup_declarations(expression, in_jsx_child));
700
+ return wrap_jsx_setup_declarations(expression, in_jsx_child);
684
701
  },
685
702
 
686
703
  JSXElement(node, { next, path, state, visit }) {
@@ -689,7 +706,7 @@ export function createJsxTransform(platform) {
689
706
  // Alias lowerings replace the element with a fragment; factory
690
707
  // platforms normally lower in the pre-walk pass, so this only
691
708
  // covers elements introduced after it.
692
- return /** @type {any} */ (visit(lowered, state));
709
+ return visit(lowered, state);
693
710
  }
694
711
 
695
712
  if (!node.metadata?.native_tsrx) {
@@ -698,19 +715,15 @@ export function createJsxTransform(platform) {
698
715
 
699
716
  // Capture raw children BEFORE the walker transforms them so platform
700
717
  // hooks can inspect the original JSX child shape.
701
- const raw_children = /** @type {any} */ (node.children || []).map(
702
- (/** @type {any} */ child) => (child && typeof child === 'object' ? { ...child } : child),
703
- );
704
- const inner = /** @type {any} */ (next() ?? node);
718
+ const raw_children = node_children(node).map((child) => ({ ...child }));
719
+ const inner = /** @type {AST.TSRXJSXElement} */ (next() ?? node);
705
720
  const hook = platform.hooks?.transformElement;
706
- if (hook) return /** @type {any} */ (hook(inner, state, raw_children));
707
- return /** @type {any} */ (
708
- to_jsx_element(inner, state, raw_children, in_jsx_child_context(path))
709
- );
721
+ if (hook) return hook(inner, state, raw_children);
722
+ return to_jsx_element(inner, state, raw_children, in_jsx_child_context(path));
710
723
  },
711
724
 
712
725
  JSXExpressionContainer(node, { next, state }) {
713
- const result = /** @type {any} */ (next() ?? node);
726
+ const result = /** @type {ESTreeJSX.JSXExpressionContainer} */ (next() ?? node);
714
727
  const expression = result.expression;
715
728
  // `@if`/`@for`/`@switch`/`@try` used as an expression value (e.g. an
716
729
  // attribute value `content={@if (…) { … }}` or a `{ … }` child) leaks a
@@ -722,8 +735,14 @@ export function createJsxTransform(platform) {
722
735
  is_try_control_node(expression) ||
723
736
  expression?.type === 'JSXForExpression'
724
737
  ) {
725
- const lowered = /** @type {any} */ (to_jsx_child(expression, state));
726
- return { ...result, expression: lowered?.expression ?? lowered };
738
+ const lowered = to_jsx_child(expression, state);
739
+ return {
740
+ ...result,
741
+ expression:
742
+ lowered.type === 'JSXExpressionContainer'
743
+ ? lowered.expression
744
+ : /** @type {AST.Expression} */ (lowered),
745
+ };
727
746
  }
728
747
  return result;
729
748
  },
@@ -734,7 +753,7 @@ export function createJsxTransform(platform) {
734
753
  if (stylesheet) {
735
754
  analyze_css(stylesheet);
736
755
  state.stylesheets.push(prepare_stylesheet_for_render(stylesheet, true));
737
- return /** @type {any} */ (create_style_expression_value(node, stylesheet, state));
756
+ return create_style_expression_value(node, stylesheet, state);
738
757
  }
739
758
  }
740
759
  return b.jsx_element(
@@ -756,7 +775,7 @@ export function createJsxTransform(platform) {
756
775
  ArrowFunctionExpression: transform_function,
757
776
 
758
777
  JSXOpeningElement(node, { next }) {
759
- const visited = /** @type {any} */ (next() || node);
778
+ const visited = /** @type {ESTreeJSX.TSRXJSXOpeningElement} */ (next() || node);
760
779
  if (visited.metadata?.native_tsrx_pretransformed) {
761
780
  return visited;
762
781
  }
@@ -769,7 +788,7 @@ export function createJsxTransform(platform) {
769
788
  ),
770
789
  visited.selfClosing,
771
790
  visited.typeArguments,
772
- visited,
791
+ has_location(visited) ? visited : undefined,
773
792
  );
774
793
  },
775
794
  });
@@ -780,7 +799,7 @@ export function createJsxTransform(platform) {
780
799
  // expansion, import injection) write into the program's `body`, so
781
800
  // detach from the caller's AST first; a changed program is already a
782
801
  // fresh walk-owned node with a fresh `body` array.
783
- if (/** @type {any} */ (transformed_program) === /** @type {any} */ (ast)) {
802
+ if (transformed_program === ast) {
784
803
  transformed_program = { ...transformed_program, body: [...transformed_program.body] };
785
804
  }
786
805
  if (type_only_style_anchors.length > 0) {
@@ -819,13 +838,11 @@ export function createJsxTransform(platform) {
819
838
  // so lazy bindings declared inside a nested block or directive body are
820
839
  // rewritten just like a flat function body.
821
840
  if (!transform_context.typeOnly) {
822
- preallocate_lazy_ids(/** @type {any} */ (lowered_program), transform_context);
841
+ preallocate_lazy_ids(lowered_program, transform_context);
823
842
  }
824
- const final_program = /** @type {any} */ (
825
- transform_context.typeOnly
826
- ? lowered_program
827
- : apply_lazy_transforms(/** @type {any} */ (lowered_program), new Map())
828
- );
843
+ const final_program = transform_context.typeOnly
844
+ ? lowered_program
845
+ : /** @type {AST.Program} */ (apply_lazy_transforms(lowered_program, new Map()));
829
846
 
830
847
  const result = print(
831
848
  final_program,
@@ -842,7 +859,7 @@ export function createJsxTransform(platform) {
842
859
  },
843
860
  );
844
861
 
845
- const { css, cssHash } = render_css_result(/** @type {any} */ (stylesheets));
862
+ const { css, cssHash } = render_css_result(stylesheets);
846
863
 
847
864
  return { ast: final_program, code: result.code, map: result.map, css, cssHash };
848
865
  }
@@ -861,9 +878,9 @@ export function createJsxTransform(platform) {
861
878
  * replacement node (an element, or a fragment for the alias lowering) that
862
879
  * the caller must put in the original element's position.
863
880
  *
864
- * @param {any} node
881
+ * @param {AST.TSRXJSXElement} node
865
882
  * @param {TransformContext} transform_context
866
- * @returns {ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment | undefined}
883
+ * @returns {AST.TSRXJSXElement | AST.TSRXJSXFragment | undefined}
867
884
  */
868
885
  function lower_dynamic_jsx_element(node, transform_context) {
869
886
  const dynamic_name = node.openingElement?.name;
@@ -880,12 +897,13 @@ function lower_dynamic_jsx_element(node, transform_context) {
880
897
  const dynamic_expression = dynamic_name.expression;
881
898
  if (!dynamic_expression) return;
882
899
  const generated_expression = clone_ast_node(dynamic_expression);
883
- if (node.closingElement?.name?.expression) {
900
+ const closing_name = node.closingElement?.name;
901
+ if (closing_name?.type === 'JSXExpressionContainer' && closing_name.expression) {
884
902
  // One generated expression stands in for both tags; record the closing
885
903
  // tag's positions so editor features keep working on `</{expr}>`.
886
904
  add_extra_source_mappings_from_matching_expression(
887
905
  generated_expression,
888
- clone_ast_node(node.closingElement.name.expression),
906
+ clone_ast_node(closing_name.expression),
889
907
  );
890
908
  }
891
909
 
@@ -896,22 +914,23 @@ function lower_dynamic_jsx_element(node, transform_context) {
896
914
  *
897
915
  * @param {ESTreeJSX.JSXIdentifier} name_id
898
916
  * @param {ESTreeJSX.JSXAttribute[]} [extra_attributes]
899
- * @returns {ESTreeJSX.JSXElement}
917
+ * @returns {AST.TSRXJSXElement}
900
918
  */
901
919
  const rebuild_element = (name_id, extra_attributes = []) => {
920
+ const closing = node.closingElement;
902
921
  const element = b.jsx_element_fresh(
903
922
  b.jsx_opening_element(
904
923
  name_id,
905
924
  [...extra_attributes, ...(node.openingElement.attributes || [])],
906
925
  node.openingElement.selfClosing,
907
926
  node.openingElement.typeArguments,
908
- node.openingElement,
927
+ has_location(node.openingElement) ? node.openingElement : undefined,
909
928
  ),
910
- node.closingElement
911
- ? b.jsx_closing_element(b.jsx_id(name_id.name), node.closingElement)
929
+ closing
930
+ ? b.jsx_closing_element(b.jsx_id(name_id.name), has_location(closing) ? closing : undefined)
912
931
  : null,
913
932
  node.children,
914
- node,
933
+ has_location(node) ? node : undefined,
915
934
  );
916
935
  element.metadata = { ...(node.metadata || {}), path: [] };
917
936
  return element;
@@ -922,8 +941,8 @@ function lower_dynamic_jsx_element(node, transform_context) {
922
941
  * helper: type selectors survive pruning and the scope hash lands on the
923
942
  * element's class.
924
943
  *
925
- * @param {ESTreeJSX.JSXElement} element
926
- * @returns {ESTreeJSX.JSXElement}
944
+ * @param {AST.TSRXJSXElement} element
945
+ * @returns {AST.TSRXJSXElement}
927
946
  */
928
947
  const mark_dynamic_element = (element) => {
929
948
  element.metadata.dynamicElement = true;
@@ -952,19 +971,22 @@ function lower_dynamic_jsx_element(node, transform_context) {
952
971
  const element = mark_dynamic_element(rebuild_element(local_id));
953
972
  const wrapper = b.arrow(
954
973
  [],
955
- b.block([b.const(b.id(local), generated_expression), b.return(element)], node),
974
+ b.block(
975
+ [b.const(b.id(local), generated_expression), b.return(element)],
976
+ has_location(node) ? node : undefined,
977
+ ),
956
978
  );
957
979
  // Lets scoped-CSS collection descend into this generated closure;
958
980
  // user function boundaries are otherwise skipped.
959
- wrapper.metadata = /** @type {any} */ ({
981
+ wrapper.metadata = {
960
982
  ...(wrapper.metadata || { path: [] }),
961
983
  tsrx_dynamic_wrapper: true,
962
- });
984
+ };
963
985
  const container = to_jsx_expression_container(b.call(wrapper), element);
964
- container.metadata = /** @type {any} */ ({
986
+ container.metadata = {
965
987
  ...(container.metadata || { path: [] }),
966
988
  tsrx_reactive_block: true,
967
- });
989
+ };
968
990
 
969
991
  return set_loc(wrap_in_native_tsrx_fragment(container), node);
970
992
  }
@@ -989,13 +1011,14 @@ function lower_dynamic_jsx_element(node, transform_context) {
989
1011
  }
990
1012
 
991
1013
  transform_context.needs_dynamic_element = true;
1014
+ const name_loc = has_location(dynamic_name) ? dynamic_name : undefined;
992
1015
  return mark_dynamic_element(
993
1016
  rebuild_element(b.jsx_id(DYNAMIC_IMPORT_LOCAL), [
994
1017
  b.jsx_attribute(
995
1018
  b.jsx_id('is'),
996
- b.jsx_expression_container(generated_expression, dynamic_name),
1019
+ b.jsx_expression_container(generated_expression, name_loc),
997
1020
  false,
998
- dynamic_name,
1021
+ name_loc,
999
1022
  ),
1000
1023
  ]),
1001
1024
  );
@@ -1023,12 +1046,25 @@ function inject_dynamic_import(program, transform_context) {
1023
1046
  );
1024
1047
  }
1025
1048
 
1049
+ /**
1050
+ * The children a node carries, as nodes. Node types differ in whether they
1051
+ * have a `children` slot at all (`JSXCodeBlock` does not) and in what it may
1052
+ * hold, so this reads it uniformly instead of forcing every caller to narrow.
1053
+ *
1054
+ * @param {AST.Node} node
1055
+ * @returns {AST.Node[]}
1056
+ */
1057
+ function node_children(node) {
1058
+ const children = /** @type {AST.TraversableAstNode} */ (node).children;
1059
+ return Array.isArray(children) ? children.filter(is_ast_node) : [];
1060
+ }
1061
+
1026
1062
  /**
1027
1063
  * Attach selector-location metadata used by editor definitions/hover before
1028
1064
  * the shared scoping pass mutates class attributes with the component hash.
1029
1065
  *
1030
- * @param {any} component
1031
- * @param {any} css
1066
+ * @param {AST.NativeTSRXNode} component
1067
+ * @param {AST.CSS.StyleSheet} css
1032
1068
  * @param {TransformContext} transform_context
1033
1069
  * @param {boolean} [export_top_scoped_classes]
1034
1070
  * @returns {void}
@@ -1044,11 +1080,7 @@ function apply_css_definition_metadata(
1044
1080
  const metadata = component.metadata || (component.metadata = { path: [] });
1045
1081
  const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
1046
1082
  const top_scoped_classes = metadata.topScopedClasses || new Map();
1047
- const elements = collect_css_prunable_elements(
1048
- component.body || component.children || [],
1049
- [],
1050
- transform_context,
1051
- );
1083
+ const elements = collect_css_prunable_elements(node_children(component), [], transform_context);
1052
1084
 
1053
1085
  const prune = () => {
1054
1086
  for (const element of elements) {
@@ -1076,17 +1108,13 @@ function apply_css_definition_metadata(
1076
1108
  * ancestor chain (`metadata.path`) here — descendant/sibling selector matching
1077
1109
  * in `prune_css` reads it.
1078
1110
  *
1079
- * @param {any} value
1080
- * @param {any[]} [elements]
1111
+ * @param {AST.Node | AST.Node[]} value
1112
+ * @param {AST.TSRXJSXElement[]} [elements]
1081
1113
  * @param {TransformContext | null} [transform_context]
1082
- * @param {any[]} [path]
1083
- * @returns {any[]}
1114
+ * @param {AST.Node[]} [path]
1115
+ * @returns {AST.TSRXJSXElement[]}
1084
1116
  */
1085
1117
  function collect_css_prunable_elements(value, elements = [], transform_context = null, path = []) {
1086
- if (!value || typeof value !== 'object') {
1087
- return elements;
1088
- }
1089
-
1090
1118
  if (Array.isArray(value)) {
1091
1119
  for (const child of value) {
1092
1120
  collect_css_prunable_elements(child, elements, transform_context, path);
@@ -1113,22 +1141,19 @@ function collect_css_prunable_elements(value, elements = [], transform_context =
1113
1141
  }
1114
1142
  }
1115
1143
 
1116
- const child_path = value.type ? [...path, value] : path;
1144
+ const child_path = [...path, value];
1117
1145
 
1118
- for (const key of Object.keys(value)) {
1119
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
1120
- continue;
1121
- }
1122
- collect_css_prunable_elements(value[key], elements, transform_context, child_path);
1146
+ for (const child of child_nodes(value, 'css')) {
1147
+ collect_css_prunable_elements(child, elements, transform_context, child_path);
1123
1148
  }
1124
1149
 
1125
1150
  return elements;
1126
1151
  }
1127
1152
 
1128
1153
  /**
1129
- * @param {any[]} body_nodes
1154
+ * @param {AST.Node[]} body_nodes
1130
1155
  * @param {TransformContext} transform_context
1131
- * @returns {any[]}
1156
+ * @returns {AST.Statement[]}
1132
1157
  */
1133
1158
  function build_component_statements(body_nodes, transform_context) {
1134
1159
  return build_render_statements(body_nodes, false, transform_context);
@@ -1140,9 +1165,9 @@ function build_component_statements(body_nodes, transform_context) {
1140
1165
  * kept in a nested plain `{ … }` block, so a whole chain shares a single
1141
1166
  * closure while still scoping each level; the generated `return` exits that
1142
1167
  * closure.
1143
- * @param {any} block
1168
+ * @param {AST.JSXCodeBlock} block
1144
1169
  * @param {TransformContext} transform_context
1145
- * @returns {{ statements: any[], has_render: boolean }}
1170
+ * @returns {{ statements: AST.Statement[], has_render: boolean }}
1146
1171
  */
1147
1172
  function code_block_scope_statements(block, transform_context) {
1148
1173
  const statements = [...(block.body || [])];
@@ -1156,7 +1181,7 @@ function code_block_scope_statements(block, transform_context) {
1156
1181
  const inner = code_block_scope_statements(render, transform_context);
1157
1182
  if (inner.statements.length > 0) {
1158
1183
  if ((render.body || []).length > 0) {
1159
- statements.push(b.block(inner.statements, render));
1184
+ statements.push(b.block(inner.statements, has_location(render) ? render : undefined));
1160
1185
  } else {
1161
1186
  statements.push(...inner.statements);
1162
1187
  }
@@ -1181,9 +1206,9 @@ function code_block_scope_statements(block, transform_context) {
1181
1206
  * the render output, with nested chains folded into the one closure.
1182
1207
  *
1183
1208
  * Always returns zero or one node.
1184
- * @param {any} block
1209
+ * @param {AST.JSXCodeBlock} block
1185
1210
  * @param {TransformContext} transform_context
1186
- * @returns {any[]}
1211
+ * @returns {AST.Node[]}
1187
1212
  */
1188
1213
  function lower_code_block_stream_node(block, transform_context) {
1189
1214
  const body = block.body || [];
@@ -1198,24 +1223,25 @@ function lower_code_block_stream_node(block, transform_context) {
1198
1223
  }
1199
1224
 
1200
1225
  const { statements, has_render } = code_block_scope_statements(block, transform_context);
1226
+ const block_loc = has_location(block) ? block : undefined;
1201
1227
 
1202
1228
  if (!has_render) {
1203
- return [b.block(statements, block)];
1229
+ return [b.block(statements, block_loc)];
1204
1230
  }
1205
1231
 
1206
- const iife = b.call(b.arrow([], b.block(statements, block)));
1232
+ const iife = b.call(b.arrow([], b.block(statements, block_loc)));
1207
1233
  return [to_jsx_expression_container(iife, block)];
1208
1234
  }
1209
1235
 
1210
1236
  /**
1211
- * @param {any[]} body_nodes
1237
+ * @param {AST.Node[]} body_nodes
1212
1238
  * @param {boolean} return_null_when_empty
1213
1239
  * @param {TransformContext} transform_context
1214
- * @param {any} [source_authored_fragment] When the render output is an AUTHORED
1240
+ * @param {AST.Node | null} [source_authored_fragment] When the render output is an AUTHORED
1215
1241
  * `<> … </>` (`is_authored_native_fragment`), the built return value is re-wrapped
1216
1242
  * in a fragment so the author's fragment is kept verbatim (not collapsed to its
1217
1243
  * single child), matching value positions. A generated wrapper passes nothing.
1218
- * @returns {any[]}
1244
+ * @returns {AST.Statement[]}
1219
1245
  */
1220
1246
  function build_render_statements(
1221
1247
  body_nodes,
@@ -1238,7 +1264,9 @@ function build_render_statements(
1238
1264
  }
1239
1265
  }
1240
1266
 
1267
+ /** @type {AST.Statement[]} */
1241
1268
  const statements = [];
1269
+ /** @type {ESTreeJSX.JSXRenderChild[]} */
1242
1270
  const render_nodes = [];
1243
1271
  let has_terminal_return = false;
1244
1272
 
@@ -1254,6 +1282,15 @@ function build_render_statements(
1254
1282
  // state of mutable variables.
1255
1283
  const interleaved = is_interleaved_body(body_nodes);
1256
1284
  let capture_index = 0;
1285
+ // When this pass hoists a JSX child into `const _tsrx_child_N = …`, that
1286
+ // NAME is the one anchorable token of the whole expression: a `@if` whose
1287
+ // branch carries hooks lowers to `cond ? (() => { … })() : …`, and neither
1288
+ // the ternary (it starts where its test does) nor an IIFE arm (it starts on
1289
+ // a paren) yields a map segment of its own. `stamp_directive_origin`
1290
+ // confirms the authored spelling, so a hoist of anything else is untouched.
1291
+ /** @type {(id: AST.Identifier, init: AST.Expression) => AST.Identifier} */
1292
+ const anchor_capture_name = (id, init) =>
1293
+ stamp_directive_origin(id, init, '@if', transform_context);
1257
1294
 
1258
1295
  for (let i = 0; i < body_nodes.length; i += 1) {
1259
1296
  const child = body_nodes[i];
@@ -1336,7 +1373,11 @@ function build_render_statements(
1336
1373
  if (hoisted) {
1337
1374
  statements.push(...hoisted.hoist_statements);
1338
1375
  if (interleaved && is_capturable_jsx_child(hoisted.jsx_child)) {
1339
- const { declaration, reference } = captureJsxChild(hoisted.jsx_child, capture_index++);
1376
+ const { declaration, reference } = captureJsxChild(
1377
+ hoisted.jsx_child,
1378
+ capture_index++,
1379
+ anchor_capture_name,
1380
+ );
1340
1381
  statements.push(declaration);
1341
1382
  render_nodes.push(reference);
1342
1383
  } else {
@@ -1350,7 +1391,11 @@ function build_render_statements(
1350
1391
  const jsx = to_jsx_child(child, transform_context);
1351
1392
  statements.push(...extract_jsx_setup_declarations(jsx));
1352
1393
  if (interleaved && is_capturable_jsx_child(jsx)) {
1353
- const { declaration, reference } = captureJsxChild(jsx, capture_index++);
1394
+ const { declaration, reference } = captureJsxChild(
1395
+ jsx,
1396
+ capture_index++,
1397
+ anchor_capture_name,
1398
+ );
1354
1399
  statements.push(declaration);
1355
1400
  render_nodes.push(reference);
1356
1401
  } else {
@@ -1360,7 +1405,8 @@ function build_render_statements(
1360
1405
  render_nodes.push(to_jsx_expression_container(child, child));
1361
1406
  } else {
1362
1407
  mark_nested_function_return_jsx(child);
1363
- statements.push(child);
1408
+ // Anything left after the render-child cases is ordinary setup code.
1409
+ statements.push(/** @type {AST.Statement} */ (child));
1364
1410
  collect_statement_bindings(child, transform_context.available_bindings);
1365
1411
  }
1366
1412
  }
@@ -1394,7 +1440,7 @@ function build_render_statements(
1394
1440
  }
1395
1441
 
1396
1442
  /**
1397
- * @param {any[]} body_nodes
1443
+ * @param {AST.Node[]} body_nodes
1398
1444
  * @returns {boolean}
1399
1445
  */
1400
1446
  function is_interleaved_body(body_nodes) {
@@ -1402,7 +1448,7 @@ function is_interleaved_body(body_nodes) {
1402
1448
  }
1403
1449
 
1404
1450
  /**
1405
- * @param {any[]} body_nodes
1451
+ * @param {AST.Node[]} body_nodes
1406
1452
  * @param {TransformContext} transform_context
1407
1453
  * @param {boolean} include_platform_setup
1408
1454
  * @returns {boolean}
@@ -1418,7 +1464,7 @@ function body_contains_top_level_hook_call(
1418
1464
  }
1419
1465
 
1420
1466
  /**
1421
- * @param {any} node
1467
+ * @param {AST.Node | null | undefined} node
1422
1468
  * @param {TransformContext} transform_context
1423
1469
  * @param {boolean} include_platform_setup
1424
1470
  * @returns {boolean}
@@ -1428,7 +1474,7 @@ function statement_contains_top_level_hook_call(node, transform_context, include
1428
1474
  }
1429
1475
 
1430
1476
  /**
1431
- * @param {any} node
1477
+ * @param {AST.Node | null | undefined} node
1432
1478
  * @param {boolean} inside_nested_function
1433
1479
  * @param {TransformContext} transform_context
1434
1480
  * @param {boolean} include_platform_setup
@@ -1440,10 +1486,12 @@ function node_contains_top_level_hook_call(
1440
1486
  transform_context,
1441
1487
  include_platform_setup,
1442
1488
  ) {
1443
- if (!node || typeof node !== 'object') {
1489
+ if (!node) {
1444
1490
  return false;
1445
1491
  }
1446
1492
 
1493
+ const entries = /** @type {AST.TraversableAstNode} */ (node);
1494
+
1447
1495
  if (
1448
1496
  inside_nested_function &&
1449
1497
  (node.type === 'FunctionDeclaration' ||
@@ -1459,13 +1507,10 @@ function node_contains_top_level_hook_call(
1459
1507
  node.type === 'ArrowFunctionExpression'
1460
1508
  ) {
1461
1509
  const next_inside_nested_function = true;
1462
- for (const key of Object.keys(node)) {
1463
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1464
- continue;
1465
- }
1510
+ for (const child of child_nodes(node)) {
1466
1511
  if (
1467
1512
  node_contains_top_level_hook_call(
1468
- node[key],
1513
+ child,
1469
1514
  next_inside_nested_function,
1470
1515
  transform_context,
1471
1516
  include_platform_setup,
@@ -1498,13 +1543,10 @@ function node_contains_top_level_hook_call(
1498
1543
  );
1499
1544
  }
1500
1545
 
1501
- for (const key of Object.keys(node)) {
1502
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1503
- continue;
1504
- }
1546
+ for (const child of child_nodes(node)) {
1505
1547
  if (
1506
1548
  node_contains_top_level_hook_call(
1507
- node[key],
1549
+ child,
1508
1550
  inside_nested_function,
1509
1551
  transform_context,
1510
1552
  include_platform_setup,
@@ -1518,7 +1560,7 @@ function node_contains_top_level_hook_call(
1518
1560
  }
1519
1561
 
1520
1562
  /**
1521
- * @param {any} callee
1563
+ * @param {AST.Node | null | undefined} callee
1522
1564
  * @returns {boolean}
1523
1565
  */
1524
1566
  function is_hook_callee(callee) {
@@ -1529,8 +1571,8 @@ function is_hook_callee(callee) {
1529
1571
  }
1530
1572
 
1531
1573
  if (
1532
- !callee.computed &&
1533
1574
  callee.type === 'MemberExpression' &&
1575
+ !callee.computed &&
1534
1576
  callee.property?.type === 'Identifier'
1535
1577
  ) {
1536
1578
  return /^use[A-Z0-9]/.test(callee.property.name);
@@ -1545,37 +1587,35 @@ function is_hook_callee(callee) {
1545
1587
  * @returns {AST.ObjectPattern}
1546
1588
  */
1547
1589
  function create_helper_props_pattern(bindings, mapped_bindings = new Set()) {
1548
- return /** @type {any} */ ({
1549
- type: 'ObjectPattern',
1550
- properties: bindings.map((binding) =>
1590
+ return b.object_pattern(
1591
+ bindings.map((binding) =>
1551
1592
  create_helper_props_property(binding, mapped_bindings.has(binding.name)),
1552
1593
  ),
1553
- metadata: { path: [] },
1554
- });
1594
+ );
1555
1595
  }
1556
1596
 
1557
1597
  /**
1558
1598
  * @param {AST.Identifier} binding
1559
1599
  * @param {boolean} [map_binding]
1560
- * @returns {AST.Property}
1600
+ * @returns {AST.AssignmentProperty}
1561
1601
  */
1562
1602
  function create_helper_props_property(binding, map_binding = false) {
1563
1603
  const key = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1564
1604
  const value = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1565
1605
 
1566
- return b.prop('init', key, value, false, true);
1606
+ return /** @type {AST.AssignmentProperty} */ (b.prop('init', key, value, false, true));
1567
1607
  }
1568
1608
 
1569
1609
  /**
1570
1610
  * @param {AST.Identifier} helper_id
1571
1611
  * @param {AST.Identifier[]} bindings
1572
- * @param {any} source_node
1612
+ * @param {AST.Node | AST.NodeWithLocation | null | undefined} source_node
1573
1613
  * @param {{
1574
1614
  * mapWrapper?: boolean,
1575
1615
  * mapBindingNames?: boolean,
1576
1616
  * mapBindingValues?: boolean,
1577
1617
  * }} [mapping]
1578
- * @returns {ESTreeJSX.JSXElement}
1618
+ * @returns {AST.TSRXJSXElement}
1579
1619
  */
1580
1620
  function create_helper_component_element(helper_id, bindings, source_node, mapping = {}) {
1581
1621
  const { mapWrapper = true, mapBindingNames = true, mapBindingValues = true } = mapping;
@@ -1604,7 +1644,7 @@ function create_helper_component_element(helper_id, bindings, source_node, mappi
1604
1644
  }
1605
1645
 
1606
1646
  /**
1607
- * @param {{ base_name: string, next_id: number, helpers: any[], statics: any[] }} helper_state
1647
+ * @param {JsxHelperState} helper_state
1608
1648
  * @param {string} suffix
1609
1649
  * @returns {string}
1610
1650
  */
@@ -1615,7 +1655,7 @@ function create_helper_name(helper_state, suffix) {
1615
1655
 
1616
1656
  /**
1617
1657
  * @param {string} base_name
1618
- * @returns {{ base_name: string, next_id: number, helpers: any[], statics: any[] }}
1658
+ * @returns {JsxHelperState}
1619
1659
  */
1620
1660
  function create_helper_state(base_name) {
1621
1661
  return {
@@ -1627,8 +1667,8 @@ function create_helper_state(base_name) {
1627
1667
  }
1628
1668
 
1629
1669
  /**
1630
- * @param {{ helpers: any[], statics: any[] }} helper_state
1631
- * @returns {{ generated_helpers: any[], generated_statics: any[] } | null}
1670
+ * @param {JsxHelperState} helper_state
1671
+ * @returns {{ generated_helpers: AST.Statement[], generated_statics: AST.Statement[] } | null}
1632
1672
  */
1633
1673
  function create_generated_helper_metadata(helper_state) {
1634
1674
  if (helper_state.helpers.length === 0 && helper_state.statics.length === 0) {
@@ -1641,18 +1681,18 @@ function create_generated_helper_metadata(helper_state) {
1641
1681
  }
1642
1682
 
1643
1683
  /**
1644
- * @param {any} metadata
1645
- * @returns {any}
1684
+ * @param {FunctionMetaData | undefined} metadata
1685
+ * @returns {FunctionMetaData}
1646
1686
  */
1647
1687
  function strip_function_transform_metadata(metadata) {
1648
- const { native_tsrx, ...next_metadata } = metadata || {};
1688
+ const { native_tsrx, ...next_metadata } = metadata ?? { path: [] };
1649
1689
  return next_metadata;
1650
1690
  }
1651
1691
 
1652
1692
  /**
1653
1693
  * @param {AST.BlockStatement} node
1654
- * @param {{ next: () => any, visit: (node: any, state?: TransformContext) => any, state: TransformContext, path: AST.Node[] }} context
1655
- * @returns {any}
1694
+ * @param {JsxVisitorContext} context
1695
+ * @returns {AST.Node}
1656
1696
  */
1657
1697
  function transform_block_statement(node, { next, visit, state, path }) {
1658
1698
  if (node.metadata?.native_return_block) {
@@ -1670,9 +1710,9 @@ function transform_block_statement(node, { next, visit, state, path }) {
1670
1710
  }
1671
1711
 
1672
1712
  /**
1673
- * @param {any} node
1674
- * @param {{ next: () => any, visit: (node: any, state?: TransformContext) => any, state: TransformContext, path: AST.Node[] }} context
1675
- * @returns {any}
1713
+ * @param {AST.ReturnStatement} node
1714
+ * @param {JsxVisitorContext} context
1715
+ * @returns {AST.Node}
1676
1716
  */
1677
1717
  function transform_return_statement(node, { next, visit, state, path }) {
1678
1718
  const active_native_tsrx_function = get_active_native_tsrx_function(path);
@@ -1684,7 +1724,7 @@ function transform_return_statement(node, { next, visit, state, path }) {
1684
1724
  if (statements.length === 1) {
1685
1725
  return visit(statements[0], state);
1686
1726
  }
1687
- const block = b.block(statements, node.argument);
1727
+ const block = b.block(statements, has_location(node.argument) ? node.argument : undefined);
1688
1728
  block.metadata = {
1689
1729
  ...(block.metadata || {}),
1690
1730
  native_return_block: true,
@@ -1698,23 +1738,24 @@ function transform_return_statement(node, { next, visit, state, path }) {
1698
1738
  }
1699
1739
 
1700
1740
  /**
1701
- * @param {any} node
1702
- * @param {{ state: TransformContext, path: AST.Node[], visit: (node: any, state?: TransformContext) => any }} context
1703
- * @returns {any}
1741
+ * @param {AST.JSXCodeBlock} node
1742
+ * @param {JsxVisitorContext} context
1743
+ * @returns {AST.Node}
1704
1744
  */
1705
1745
  function transform_jsx_code_block(node, { state, path, visit }) {
1706
1746
  const body_nodes = get_jsx_code_block_body_nodes(node, state);
1707
- const parent = /** @type {any} */ (path.at(-1));
1747
+ const parent = path.at(-1);
1708
1748
  // Keep an authored `<> … </>` trailing render output verbatim (a generated
1709
1749
  // control-flow wrapper carries `tsrx_generated_wrapper`, so it stays null).
1710
1750
  const render_authored_fragment = is_authored_native_fragment(node.render) ? node.render : null;
1751
+ const node_loc = has_location(node) ? node : undefined;
1711
1752
 
1712
- if (parent && parent.body === node && is_function_or_class_boundary(parent)) {
1753
+ if (parent && is_function_or_class_boundary(parent) && parent.body === node) {
1713
1754
  const block = b.block(
1714
1755
  mark_native_pretransformed_jsx(
1715
1756
  build_render_statements(body_nodes, true, state, render_authored_fragment),
1716
1757
  ),
1717
- node,
1758
+ node_loc,
1718
1759
  );
1719
1760
  block.metadata = {
1720
1761
  ...(block.metadata || {}),
@@ -1730,7 +1771,7 @@ function transform_jsx_code_block(node, { state, path, visit }) {
1730
1771
  mark_native_pretransformed_jsx(
1731
1772
  build_render_statements(body_nodes, true, state, render_authored_fragment),
1732
1773
  ),
1733
- node,
1774
+ node_loc,
1734
1775
  ),
1735
1776
  ),
1736
1777
  );
@@ -1746,11 +1787,11 @@ function transform_jsx_code_block(node, { state, path, visit }) {
1746
1787
 
1747
1788
  /**
1748
1789
  * @param {AST.Node[]} path
1749
- * @returns {any | null}
1790
+ * @returns {AST.Function | AST.ClassDeclaration | AST.ClassExpression | null}
1750
1791
  */
1751
1792
  function get_active_native_tsrx_function(path) {
1752
1793
  for (let i = path.length - 1; i >= 0; i -= 1) {
1753
- const node = /** @type {any} */ (path[i]);
1794
+ const node = path[i];
1754
1795
  if (is_function_or_class_boundary(node)) {
1755
1796
  return node.metadata?.native_tsrx ? node : null;
1756
1797
  }
@@ -1759,9 +1800,9 @@ function get_active_native_tsrx_function(path) {
1759
1800
  }
1760
1801
 
1761
1802
  /**
1762
- * @param {any} node
1763
- * @param {{ next: () => any, visit: (node: any, state?: TransformContext) => any, state: TransformContext, path: AST.Node[] }} context
1764
- * @returns {any}
1803
+ * @param {AST.Function} node
1804
+ * @param {JsxVisitorContext} context
1805
+ * @returns {AST.Node}
1765
1806
  */
1766
1807
  function transform_function(node, context) {
1767
1808
  // Lower a `@{ … }` function body (JSXCodeBlock) to an ordinary block: the
@@ -1800,8 +1841,9 @@ function transform_function(node, context) {
1800
1841
  * Lower a `@{ … }` body (JSXCodeBlock) to an ordinary block on a COPY built
1801
1842
  * with the AST builders — the source function node is never mutated. Returns
1802
1843
  * the input node unchanged when there is nothing to lower.
1803
- * @param {any} node
1804
- * @returns {any}
1844
+ * @template {AST.Function} T
1845
+ * @param {T} node
1846
+ * @returns {T}
1805
1847
  */
1806
1848
  function lower_jsx_code_block_function_body(node) {
1807
1849
  if (node.body?.type !== 'JSXCodeBlock') return node;
@@ -1823,20 +1865,22 @@ function lower_jsx_code_block_function_body(node) {
1823
1865
  };
1824
1866
  render = fragment;
1825
1867
  }
1826
- statements.push(b.return(render, code_block.render));
1868
+ statements.push(
1869
+ b.return(render, has_location(code_block.render) ? code_block.render : undefined),
1870
+ );
1827
1871
  }
1828
1872
  return {
1829
1873
  ...node,
1830
- body: b.block(statements, code_block),
1874
+ body: b.block(statements, has_location(code_block) ? code_block : undefined),
1831
1875
  ...(node.type === 'ArrowFunctionExpression' ? { expression: false } : null),
1832
1876
  };
1833
1877
  }
1834
1878
 
1835
1879
  /**
1836
- * @param {any} node
1837
- * @param {{ next: () => any, state: TransformContext }} context
1880
+ * @param {AST.Function} node
1881
+ * @param {JsxVisitorContext} context
1838
1882
  * @param {{ nativeBody?: boolean }} [options]
1839
- * @returns {any}
1883
+ * @returns {AST.Node}
1840
1884
  */
1841
1885
  function transform_native_tsrx_function(node, { next, state }, { nativeBody = false } = {}) {
1842
1886
  const helper_state =
@@ -1859,11 +1903,12 @@ function transform_native_tsrx_function(node, { next, state }, { nativeBody = fa
1859
1903
 
1860
1904
  validate_native_await(node, state);
1861
1905
 
1862
- const inner = /** @type {any} */ (next() ?? node);
1906
+ const inner = /** @type {AST.Function} */ (next() ?? node);
1863
1907
  if (
1864
1908
  inner !== node &&
1865
1909
  node.type === 'ArrowFunctionExpression' &&
1866
1910
  is_native_tsrx_node(node.body) &&
1911
+ inner.type === 'ArrowFunctionExpression' &&
1867
1912
  inner.body?.type === 'BlockStatement'
1868
1913
  ) {
1869
1914
  inner.expression = false;
@@ -1884,7 +1929,7 @@ function transform_native_tsrx_function(node, { next, state }, { nativeBody = fa
1884
1929
  }
1885
1930
 
1886
1931
  /**
1887
- * @param {any} node
1932
+ * @param {AST.Function} node
1888
1933
  * @param {TransformContext} transform_context
1889
1934
  * @returns {void}
1890
1935
  */
@@ -1912,8 +1957,8 @@ function validate_native_await(node, transform_context) {
1912
1957
  }
1913
1958
 
1914
1959
  /**
1915
- * @param {any} node
1916
- * @returns {any | null}
1960
+ * @param {AST.Function} node
1961
+ * @returns {AST.TSRXAwaitNode | null}
1917
1962
  */
1918
1963
  function find_native_await(node) {
1919
1964
  if (
@@ -1933,8 +1978,8 @@ function find_native_await(node) {
1933
1978
  }
1934
1979
 
1935
1980
  /**
1936
- * @param {any[]} statements
1937
- * @returns {any | null}
1981
+ * @param {AST.Node[]} statements
1982
+ * @returns {AST.TSRXAwaitNode | null}
1938
1983
  */
1939
1984
  function find_native_await_in_list(statements) {
1940
1985
  for (const statement of statements) {
@@ -1945,14 +1990,14 @@ function find_native_await_in_list(statements) {
1945
1990
  }
1946
1991
 
1947
1992
  /**
1948
- * @param {any} statement
1949
- * @returns {any | null}
1993
+ * @param {AST.Node | null | undefined} statement
1994
+ * @returns {AST.TSRXAwaitNode | null}
1950
1995
  */
1951
1996
  function find_native_await_in_statement(statement) {
1952
- if (!statement || typeof statement !== 'object') return null;
1997
+ if (!statement) return null;
1953
1998
 
1954
1999
  if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
1955
- return find_first_top_level_await_in_tsrx_function_body(statement.argument.children || []);
2000
+ return find_first_top_level_await_in_tsrx_function_body(node_children(statement.argument));
1956
2001
  }
1957
2002
 
1958
2003
  if (
@@ -1997,9 +2042,9 @@ function find_native_await_in_statement(statement) {
1997
2042
  }
1998
2043
 
1999
2044
  /**
2000
- * @param {any} node
2001
- * @param {{ next: () => any, state: TransformContext }} context
2002
- * @returns {any}
2045
+ * @param {AST.Function} node
2046
+ * @param {JsxVisitorContext} context
2047
+ * @returns {AST.Node}
2003
2048
  */
2004
2049
  function transform_function_with_hook_helpers(node, { next, state }) {
2005
2050
  if (!state.platform.hooks?.moduleScopedHookComponents) {
@@ -2020,7 +2065,7 @@ function transform_function_with_hook_helpers(node, { next, state }) {
2020
2065
  state.hook_helpers_enabled = true;
2021
2066
  state.available_bindings = collect_function_scope_bindings(node);
2022
2067
 
2023
- const inner = /** @type {any} */ (next() ?? node);
2068
+ const inner = /** @type {AST.Function} */ (next() ?? node);
2024
2069
 
2025
2070
  state.helper_state = saved_helper_state;
2026
2071
  state.available_bindings = saved_bindings;
@@ -2035,7 +2080,7 @@ function transform_function_with_hook_helpers(node, { next, state }) {
2035
2080
  }
2036
2081
 
2037
2082
  /**
2038
- * @param {any} node
2083
+ * @param {AST.Function} node
2039
2084
  * @returns {string}
2040
2085
  */
2041
2086
  function get_function_helper_base_name(node) {
@@ -2043,7 +2088,7 @@ function get_function_helper_base_name(node) {
2043
2088
  }
2044
2089
 
2045
2090
  /**
2046
- * @param {any} node
2091
+ * @param {AST.Function} node
2047
2092
  * @returns {boolean}
2048
2093
  */
2049
2094
  function is_uppercase_function_like(node) {
@@ -2052,15 +2097,15 @@ function is_uppercase_function_like(node) {
2052
2097
  }
2053
2098
 
2054
2099
  /**
2055
- * @param {any} node
2100
+ * @param {AST.Function} node
2056
2101
  * @returns {string | null}
2057
2102
  */
2058
2103
  function get_function_like_name(node) {
2059
- if (node.id?.type === 'Identifier') {
2104
+ if (node.type !== 'ArrowFunctionExpression' && node.id?.type === 'Identifier') {
2060
2105
  return node.id.name;
2061
2106
  }
2062
2107
 
2063
- const parent = /** @type {any} */ (node.metadata?.path?.at(-1));
2108
+ const parent = node.metadata?.path?.at(-1);
2064
2109
  if (!parent) return null;
2065
2110
 
2066
2111
  if (parent.type === 'VariableDeclarator' && parent.init === node) {
@@ -2083,7 +2128,7 @@ function get_function_like_name(node) {
2083
2128
  }
2084
2129
 
2085
2130
  /**
2086
- * @param {any} node
2131
+ * @param {AST.Node | null | undefined} node
2087
2132
  * @returns {string | null}
2088
2133
  */
2089
2134
  function get_static_binding_name(node) {
@@ -2097,7 +2142,7 @@ function get_static_binding_name(node) {
2097
2142
  }
2098
2143
 
2099
2144
  /**
2100
- * @param {any} key
2145
+ * @param {AST.Node | null | undefined} key
2101
2146
  * @returns {string | null}
2102
2147
  */
2103
2148
  function get_static_property_name(key) {
@@ -2111,7 +2156,7 @@ function get_static_property_name(key) {
2111
2156
  }
2112
2157
 
2113
2158
  /**
2114
- * @param {any} node
2159
+ * @param {AST.Function} node
2115
2160
  * @returns {Map<string, AST.Identifier>}
2116
2161
  */
2117
2162
  function collect_function_scope_bindings(node) {
@@ -2144,7 +2189,7 @@ function merge_binding_maps(outer, inner) {
2144
2189
  }
2145
2190
 
2146
2191
  /**
2147
- * @param {any} node
2192
+ * @param {AST.Function | null | undefined} node
2148
2193
  * @returns {boolean}
2149
2194
  */
2150
2195
  function function_has_native_tsrx_return(node) {
@@ -2163,7 +2208,7 @@ function function_has_native_tsrx_return(node) {
2163
2208
  }
2164
2209
 
2165
2210
  /**
2166
- * @param {any[]} statements
2211
+ * @param {AST.Node[]} statements
2167
2212
  * @returns {boolean}
2168
2213
  */
2169
2214
  function statements_contain_native_tsrx_return(statements) {
@@ -2171,11 +2216,11 @@ function statements_contain_native_tsrx_return(statements) {
2171
2216
  }
2172
2217
 
2173
2218
  /**
2174
- * @param {any} statement
2219
+ * @param {AST.Node | null | undefined} statement
2175
2220
  * @returns {boolean}
2176
2221
  */
2177
2222
  function statement_contains_native_tsrx_return(statement) {
2178
- if (!statement || typeof statement !== 'object') return false;
2223
+ if (!statement) return false;
2179
2224
 
2180
2225
  if (statement.type === 'ReturnStatement') {
2181
2226
  return node_contains_native_tsrx_template(statement.argument);
@@ -2197,7 +2242,7 @@ function statement_contains_native_tsrx_return(statement) {
2197
2242
  }
2198
2243
 
2199
2244
  if (is_switch_control_node(statement)) {
2200
- return (statement.cases || []).some((/** @type {any} */ c) =>
2245
+ return (statement.cases || []).some((c) =>
2201
2246
  statements_contain_native_tsrx_return(c.consequent || []),
2202
2247
  );
2203
2248
  }
@@ -2211,14 +2256,8 @@ function statement_contains_native_tsrx_return(statement) {
2211
2256
  );
2212
2257
  }
2213
2258
 
2214
- for (const key of Object.keys(statement)) {
2215
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2216
- continue;
2217
- }
2218
- const value = statement[key];
2219
- if (Array.isArray(value)) {
2220
- if (statements_contain_native_tsrx_return(value)) return true;
2221
- } else if (statement_contains_native_tsrx_return(value)) {
2259
+ for (const child of child_nodes(statement)) {
2260
+ if (statement_contains_native_tsrx_return(child)) {
2222
2261
  return true;
2223
2262
  }
2224
2263
  }
@@ -2227,26 +2266,24 @@ function statement_contains_native_tsrx_return(statement) {
2227
2266
  }
2228
2267
 
2229
2268
  /**
2230
- * @param {any} node
2269
+ * @param {AST.Node | AST.Node[] | null | undefined} node
2231
2270
  * @returns {boolean}
2232
2271
  */
2233
2272
  function node_contains_native_tsrx_template(node) {
2234
- if (!node || typeof node !== 'object') return false;
2273
+ if (!node) return false;
2274
+
2275
+ if (Array.isArray(node)) {
2276
+ return node.some((child) => node_contains_native_tsrx_template(child));
2277
+ }
2278
+
2235
2279
  if (is_native_tsrx_node(node)) return true;
2236
2280
 
2237
2281
  if (is_function_or_class_boundary(node)) {
2238
2282
  return false;
2239
2283
  }
2240
2284
 
2241
- if (Array.isArray(node)) {
2242
- return node.some(node_contains_native_tsrx_template);
2243
- }
2244
-
2245
- for (const key of Object.keys(node)) {
2246
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2247
- continue;
2248
- }
2249
- if (node_contains_native_tsrx_template(node[key])) {
2285
+ for (const child of child_nodes(node)) {
2286
+ if (node_contains_native_tsrx_template(child)) {
2250
2287
  return true;
2251
2288
  }
2252
2289
  }
@@ -2255,13 +2292,13 @@ function node_contains_native_tsrx_template(node) {
2255
2292
  }
2256
2293
 
2257
2294
  /**
2258
- * @param {any} node
2259
- * @returns {any}
2295
+ * @param {AST.NativeTSRXNode} node
2296
+ * @returns {AST.CSS.StyleSheet | null}
2260
2297
  */
2261
2298
  function collect_tsrx_stylesheet(node) {
2262
- /** @type {any[]} */
2299
+ /** @type {AST.CSS.StyleSheet[]} */
2263
2300
  const styles = [];
2264
- collect_style_elements(node.children || [], styles);
2301
+ collect_style_elements(node_children(node), styles);
2265
2302
 
2266
2303
  if (styles.length === 0) return null;
2267
2304
  if (styles.length > 1) {
@@ -2272,9 +2309,9 @@ function collect_tsrx_stylesheet(node) {
2272
2309
  }
2273
2310
 
2274
2311
  /**
2275
- * @param {any} node
2312
+ * @param {AST.NativeTSRXNode} node
2276
2313
  * @param {TransformContext} transform_context
2277
- * @returns {{ css: any, style_refs: any[], fragment: any } | null}
2314
+ * @returns {JsxStyleContext | null}
2278
2315
  */
2279
2316
  function prepare_tsrx_fragment_styles(node, transform_context) {
2280
2317
  const css = collect_tsrx_stylesheet(node);
@@ -2297,9 +2334,9 @@ function prepare_tsrx_fragment_styles(node, transform_context) {
2297
2334
 
2298
2335
  /**
2299
2336
  * @template T
2300
- * @param {any} node
2337
+ * @param {AST.NativeTSRXNode} node
2301
2338
  * @param {TransformContext} transform_context
2302
- * @param {(style_context: { css: any, style_refs: any[], fragment: any } | null) => T} callback
2339
+ * @param {(style_context: JsxStyleContext | null) => T} callback
2303
2340
  * @returns {T}
2304
2341
  */
2305
2342
  function with_tsrx_fragment_styles(node, transform_context, callback) {
@@ -2308,8 +2345,8 @@ function with_tsrx_fragment_styles(node, transform_context, callback) {
2308
2345
  }
2309
2346
 
2310
2347
  /**
2311
- * @param {any} fragment
2312
- * @param {{ css: any, style_refs: any[], fragment: any } | null} style_context
2348
+ * @param {AST.Node} fragment
2349
+ * @param {JsxStyleContext | null} style_context
2313
2350
  * @param {TransformContext} transform_context
2314
2351
  * @returns {AST.Statement[]}
2315
2352
  */
@@ -2330,8 +2367,8 @@ function create_tsrx_style_ref_setup_statements(fragment, style_context, transfo
2330
2367
  }
2331
2368
 
2332
2369
  /**
2333
- * @param {any} node
2334
- * @param {any} stylesheet
2370
+ * @param {AST.JSXStyleElement} node
2371
+ * @param {AST.CSS.StyleSheet} stylesheet
2335
2372
  * @param {TransformContext} transform_context
2336
2373
  * @returns {AST.Expression}
2337
2374
  */
@@ -2346,7 +2383,7 @@ function create_style_expression_value(node, stylesheet, transform_context) {
2346
2383
  }
2347
2384
 
2348
2385
  /**
2349
- * @param {any} node
2386
+ * @param {AST.JSXStyleElement} node
2350
2387
  * @param {TransformContext} transform_context
2351
2388
  */
2352
2389
  function add_type_only_style_anchor(node, transform_context) {
@@ -2401,12 +2438,12 @@ function create_style_ref_temp_name(transform_context) {
2401
2438
  }
2402
2439
 
2403
2440
  /**
2404
- * @param {any} node
2405
- * @param {any[]} styles
2441
+ * @param {AST.Node | AST.Node[] | null | undefined} node
2442
+ * @param {AST.CSS.StyleSheet[]} styles
2406
2443
  * @returns {void}
2407
2444
  */
2408
2445
  function collect_style_elements(node, styles) {
2409
- if (!node || typeof node !== 'object') return;
2446
+ if (!node) return;
2410
2447
 
2411
2448
  if (Array.isArray(node)) {
2412
2449
  for (const child of node) {
@@ -2416,9 +2453,7 @@ function collect_style_elements(node, styles) {
2416
2453
  }
2417
2454
 
2418
2455
  if (is_style_element(node)) {
2419
- const stylesheet = node.children?.find(
2420
- (/** @type {any} */ child) => child.type === 'StyleSheet',
2421
- );
2456
+ const stylesheet = node.children?.find((child) => child.type === 'StyleSheet');
2422
2457
  if (stylesheet) {
2423
2458
  styles.push(stylesheet);
2424
2459
  }
@@ -2460,43 +2495,61 @@ function collect_style_elements(node, styles) {
2460
2495
  }
2461
2496
 
2462
2497
  /**
2463
- * @param {any} node
2498
+ * @template {AST.NativeTSRXNode} T
2499
+ * @param {T} node
2464
2500
  * @param {string} hash
2465
2501
  * @param {'class' | 'className'} jsx_class_attr_name
2466
2502
  * @param {boolean} preserve_style_elements
2467
- * @returns {any}
2503
+ * @returns {T}
2468
2504
  */
2469
2505
  function annotate_tsrx_with_hash(node, hash, jsx_class_attr_name, preserve_style_elements) {
2470
- const annotated = { ...node };
2471
- annotated.children = (node.children || []).map((/** @type {any} */ statement) =>
2472
- annotate_with_hash(
2473
- clone_ast_node(statement),
2474
- hash,
2475
- jsx_class_attr_name,
2476
- preserve_style_elements,
2477
- ),
2478
- );
2506
+ // `annotate_with_hash` returns null for a `<style>` element it drops, so
2507
+ // filter before the children go back on the node.
2508
+ let children = node_children(node)
2509
+ .map((statement) =>
2510
+ annotate_with_hash(
2511
+ clone_ast_node(statement),
2512
+ hash,
2513
+ jsx_class_attr_name,
2514
+ preserve_style_elements,
2515
+ ),
2516
+ )
2517
+ .filter(is_ast_node);
2479
2518
  if (!preserve_style_elements) {
2480
- annotated.children = strip_style_elements(annotated.children);
2519
+ children = strip_style_elements_from_list(children);
2481
2520
  }
2482
- return annotated;
2521
+ // Shallow copy with rewritten children; the spread preserves `node`'s type.
2522
+ return /** @type {T} */ ({ ...node, children });
2483
2523
  }
2484
2524
 
2485
2525
  /**
2486
- * @param {any} node
2487
- * @returns {any}
2526
+ * Drop `<style>` elements from a child list, recursing into the nodes that
2527
+ * survive.
2528
+ *
2529
+ * @param {Array<AST.Node | null | undefined>} nodes
2530
+ * @returns {AST.Node[]}
2488
2531
  */
2489
- function strip_style_elements(node) {
2490
- if (!node || typeof node !== 'object') return node;
2491
-
2492
- if (Array.isArray(node)) {
2493
- return node
2494
- .filter((child) => !is_style_element(child))
2495
- .map((child) => strip_style_elements(child))
2496
- .filter(Boolean);
2532
+ function strip_style_elements_from_list(nodes) {
2533
+ /** @type {AST.Node[]} */
2534
+ const kept = [];
2535
+ for (const child of nodes) {
2536
+ if (is_style_element(child)) continue;
2537
+ const stripped = strip_style_elements(child);
2538
+ if (stripped) kept.push(stripped);
2497
2539
  }
2540
+ return kept;
2541
+ }
2498
2542
 
2499
- if (is_style_element(node)) {
2543
+ /**
2544
+ * Strip `<style>` elements out of one node's render subtree, in place. Returns
2545
+ * `null` when the node itself is a `<style>` element and should be dropped, or
2546
+ * when there is no node — child lists can hold holes.
2547
+ *
2548
+ * @param {AST.Node | null | undefined} node
2549
+ * @returns {AST.Node | null}
2550
+ */
2551
+ function strip_style_elements(node) {
2552
+ if (!node || is_style_element(node)) {
2500
2553
  return null;
2501
2554
  }
2502
2555
 
@@ -2505,39 +2558,45 @@ function strip_style_elements(node) {
2505
2558
  }
2506
2559
 
2507
2560
  if ((node.type === 'JSXElement' || node.type === 'JSXFragment') && node.metadata?.native_tsrx) {
2508
- node.children = strip_style_elements(node.children || []);
2561
+ node.children = strip_style_elements_from_list(node.children || []);
2509
2562
  return node;
2510
2563
  }
2511
2564
 
2512
2565
  if (node.type === 'BlockStatement') {
2513
- node.body = strip_style_elements(node.body || []);
2566
+ node.body = /** @type {AST.Statement[]} */ (strip_style_elements_from_list(node.body || []));
2514
2567
  return node;
2515
2568
  }
2516
2569
 
2517
2570
  if (is_if_control_node(node)) {
2518
- node.consequent = strip_style_elements(node.consequent);
2519
- if (node.alternate) node.alternate = strip_style_elements(node.alternate);
2571
+ const consequent = strip_style_elements(node.consequent);
2572
+ if (consequent) node.consequent = /** @type {AST.Statement} */ (consequent);
2573
+ if (node.alternate) {
2574
+ const alternate = strip_style_elements(node.alternate);
2575
+ if (alternate) node.alternate = /** @type {AST.Statement} */ (alternate);
2576
+ }
2520
2577
  return node;
2521
2578
  }
2522
2579
 
2523
2580
  if (is_switch_control_node(node)) {
2524
2581
  for (const switch_case of node.cases || []) {
2525
- switch_case.consequent = strip_style_elements(switch_case.consequent || []);
2582
+ switch_case.consequent = /** @type {AST.Statement[]} */ (
2583
+ strip_style_elements_from_list(switch_case.consequent || [])
2584
+ );
2526
2585
  }
2527
2586
  return node;
2528
2587
  }
2529
2588
 
2530
2589
  if (is_try_control_node(node)) {
2531
- node.block = strip_style_elements(node.block);
2532
- if (node.handler?.body) node.handler.body = strip_style_elements(node.handler.body);
2533
- if (node.finalizer) node.finalizer = strip_style_elements(node.finalizer);
2590
+ strip_style_elements(node.block);
2591
+ if (node.handler?.body) strip_style_elements(node.handler.body);
2592
+ if (node.finalizer) strip_style_elements(node.finalizer);
2534
2593
  }
2535
2594
 
2536
2595
  return node;
2537
2596
  }
2538
2597
 
2539
2598
  /**
2540
- * @param {any[]} path
2599
+ * @param {AST.Node[]} path
2541
2600
  * @returns {boolean}
2542
2601
  */
2543
2602
  function is_style_expression_position(path) {
@@ -2551,16 +2610,16 @@ function is_style_expression_position(path) {
2551
2610
  }
2552
2611
 
2553
2612
  /**
2554
- * @param {any} fragment
2613
+ * @param {AST.NativeTSRXNode} fragment
2555
2614
  * @param {TransformContext} transform_context
2556
- * @returns {any}
2615
+ * @returns {AST.BlockStatement}
2557
2616
  */
2558
2617
  function create_native_tsrx_render_block(fragment, transform_context) {
2559
2618
  const block = b.block(
2560
2619
  mark_native_pretransformed_jsx(
2561
2620
  create_native_tsrx_render_statements(fragment, transform_context),
2562
2621
  ),
2563
- fragment,
2622
+ has_location(fragment) ? fragment : undefined,
2564
2623
  );
2565
2624
  block.metadata = {
2566
2625
  ...(block.metadata || {}),
@@ -2570,9 +2629,9 @@ function create_native_tsrx_render_block(fragment, transform_context) {
2570
2629
  }
2571
2630
 
2572
2631
  /**
2573
- * @param {any} block
2632
+ * @param {AST.BlockStatement} block
2574
2633
  * @param {TransformContext} transform_context
2575
- * @returns {any | null}
2634
+ * @returns {AST.BlockStatement | null}
2576
2635
  */
2577
2636
  function create_native_tsrx_statement_list_block(block, transform_context) {
2578
2637
  const source_body = block.body || [];
@@ -2582,7 +2641,10 @@ function create_native_tsrx_statement_list_block(block, transform_context) {
2582
2641
  return null;
2583
2642
  }
2584
2643
 
2585
- const next_block = b.block(mark_native_pretransformed_jsx(body), block);
2644
+ const next_block = b.block(
2645
+ mark_native_pretransformed_jsx(body),
2646
+ has_location(block) ? block : undefined,
2647
+ );
2586
2648
  next_block.metadata = {
2587
2649
  ...(next_block.metadata || {}),
2588
2650
  native_return_block: true,
@@ -2591,7 +2653,7 @@ function create_native_tsrx_statement_list_block(block, transform_context) {
2591
2653
  }
2592
2654
 
2593
2655
  /**
2594
- * @param {any} fragment
2656
+ * @param {AST.NativeTSRXNode} fragment
2595
2657
  * @param {TransformContext} transform_context
2596
2658
  * @returns {AST.Statement[]}
2597
2659
  */
@@ -2608,9 +2670,9 @@ function create_native_tsrx_render_statements(fragment, transform_context) {
2608
2670
  }
2609
2671
 
2610
2672
  /**
2611
- * @param {any[]} statements
2673
+ * @param {AST.Statement[]} statements
2612
2674
  * @param {TransformContext} transform_context
2613
- * @returns {any[]}
2675
+ * @returns {AST.Statement[]}
2614
2676
  */
2615
2677
  function expand_native_tsrx_return_statement_list(statements, transform_context) {
2616
2678
  let changed = false;
@@ -2625,13 +2687,11 @@ function expand_native_tsrx_return_statement_list(statements, transform_context)
2625
2687
  }
2626
2688
 
2627
2689
  /**
2628
- * @param {any} statement
2690
+ * @param {AST.Statement} statement
2629
2691
  * @param {TransformContext} transform_context
2630
- * @returns {any[]}
2692
+ * @returns {AST.Statement[]}
2631
2693
  */
2632
2694
  function expand_native_tsrx_return_statement(statement, transform_context) {
2633
- if (!statement || typeof statement !== 'object') return [statement];
2634
-
2635
2695
  if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
2636
2696
  return create_native_tsrx_render_statements(statement.argument, transform_context);
2637
2697
  }
@@ -2642,7 +2702,9 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
2642
2702
 
2643
2703
  if (statement.type === 'BlockStatement') {
2644
2704
  const body = expand_native_tsrx_return_statement_list(statement.body || [], transform_context);
2645
- return body === statement.body ? [statement] : [b.block(body, statement)];
2705
+ return body === statement.body
2706
+ ? [statement]
2707
+ : [b.block(body, has_location(statement) ? statement : undefined)];
2646
2708
  }
2647
2709
 
2648
2710
  if (is_if_control_node(statement)) {
@@ -2661,7 +2723,7 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
2661
2723
 
2662
2724
  if (is_switch_control_node(statement)) {
2663
2725
  let changed = false;
2664
- const cases = (statement.cases || []).map((/** @type {any} */ switch_case) => {
2726
+ const cases = (statement.cases || []).map((switch_case) => {
2665
2727
  const consequent = expand_native_tsrx_return_statement_list(
2666
2728
  switch_case.consequent || [],
2667
2729
  transform_context,
@@ -2695,12 +2757,12 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
2695
2757
  return [statement];
2696
2758
  }
2697
2759
  const handler =
2698
- statement.handler && handler_body !== statement.handler.body
2760
+ statement.handler && handler_body && handler_body !== statement.handler.body
2699
2761
  ? b.catch_clause(
2700
2762
  statement.handler.param,
2701
2763
  statement.handler.resetParam,
2702
2764
  handler_body,
2703
- statement.handler,
2765
+ has_location(statement.handler) ? statement.handler : undefined,
2704
2766
  )
2705
2767
  : statement.handler;
2706
2768
  return [set_loc(b.try(block, handler, finalizer, pending ?? null), statement)];
@@ -2710,68 +2772,78 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
2710
2772
  }
2711
2773
 
2712
2774
  /**
2713
- * @param {any} statement
2775
+ * A block always expands back to a block, so try/catch slots keep their shape.
2776
+ * @overload
2777
+ * @param {AST.BlockStatement} statement
2778
+ * @param {TransformContext} transform_context
2779
+ * @returns {AST.BlockStatement}
2780
+ */
2781
+ /**
2782
+ * @overload
2783
+ * @param {AST.Statement} statement
2714
2784
  * @param {TransformContext} transform_context
2715
- * @returns {any}
2785
+ * @returns {AST.Statement}
2786
+ */
2787
+ /**
2788
+ * @param {AST.Statement} statement
2789
+ * @param {TransformContext} transform_context
2790
+ * @returns {AST.Statement}
2716
2791
  */
2717
2792
  function expand_embedded_native_return_statement(statement, transform_context) {
2718
2793
  const expanded = expand_native_tsrx_return_statement(statement, transform_context);
2719
- return expanded.length === 1 ? expanded[0] : b.block(expanded, statement);
2794
+ return expanded.length === 1
2795
+ ? expanded[0]
2796
+ : b.block(expanded, has_location(statement) ? statement : undefined);
2720
2797
  }
2721
2798
 
2722
2799
  /**
2723
- * @template T
2800
+ * @template {AST.Node | AST.Node[]} T
2724
2801
  * @param {T} node
2725
- * @param {Set<any>} [seen]
2802
+ * @param {Set<AST.Node>} [seen]
2726
2803
  * @returns {T}
2727
2804
  */
2728
2805
  function mark_native_pretransformed_jsx(node, seen = new Set()) {
2729
- if (node == null || typeof node !== 'object' || seen.has(node)) {
2806
+ if (Array.isArray(node)) {
2807
+ for (const item of node) mark_native_pretransformed_jsx(item, seen);
2730
2808
  return node;
2731
2809
  }
2732
- seen.add(node);
2733
2810
 
2734
- if (Array.isArray(node)) {
2735
- for (const item of node) mark_native_pretransformed_jsx(item, seen);
2811
+ if (seen.has(node)) {
2736
2812
  return node;
2737
2813
  }
2814
+ seen.add(node);
2738
2815
 
2739
- const as_node = /** @type {any} */ (node);
2740
- if (as_node.type === 'JSXOpeningElement') {
2741
- as_node.metadata = {
2742
- ...(as_node.metadata || {}),
2816
+ if (node.type === 'JSXOpeningElement') {
2817
+ node.metadata = {
2818
+ ...node.metadata,
2743
2819
  native_tsrx_pretransformed: true,
2744
2820
  };
2745
2821
  }
2746
2822
 
2747
- for (const key of Object.keys(as_node)) {
2748
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2749
- continue;
2750
- }
2751
- mark_native_pretransformed_jsx(as_node[key], seen);
2823
+ for (const child of child_nodes(node)) {
2824
+ mark_native_pretransformed_jsx(child, seen);
2752
2825
  }
2753
2826
 
2754
2827
  return node;
2755
2828
  }
2756
2829
 
2757
2830
  /**
2758
- * @param {any} node
2759
- * @returns {any[]}
2831
+ * @param {AST.NativeTSRXNode} node
2832
+ * @returns {AST.Node[]}
2760
2833
  */
2761
2834
  function get_tsrx_render_children(node) {
2762
- return (node.children || []).filter(
2763
- (/** @type {any} */ child) =>
2764
- child && child.type !== 'EmptyStatement' && (child.type !== 'JSXText' || child.value !== ''),
2835
+ return node_children(node).filter(
2836
+ (child) => child.type !== 'EmptyStatement' && (child.type !== 'JSXText' || child.value !== ''),
2765
2837
  );
2766
2838
  }
2767
2839
 
2768
2840
  /**
2769
- * @param {any} node
2841
+ * @param {AST.Node | null | undefined} node
2770
2842
  * @param {Map<string, AST.Identifier>} bindings
2771
2843
  * @returns {void}
2772
2844
  */
2773
2845
  function collect_descendant_declaration_bindings(node, bindings) {
2774
- if (!node || typeof node !== 'object') {
2846
+ if (!node) {
2775
2847
  return;
2776
2848
  }
2777
2849
 
@@ -2796,23 +2868,13 @@ function collect_descendant_declaration_bindings(node, bindings) {
2796
2868
  return;
2797
2869
  }
2798
2870
 
2799
- if (Array.isArray(node)) {
2800
- for (const child of node) {
2801
- collect_descendant_declaration_bindings(child, bindings);
2802
- }
2803
- return;
2804
- }
2805
-
2806
- for (const key of Object.keys(node)) {
2807
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2808
- continue;
2809
- }
2810
- collect_descendant_declaration_bindings(node[key], bindings);
2871
+ for (const child of child_nodes(node)) {
2872
+ collect_descendant_declaration_bindings(child, bindings);
2811
2873
  }
2812
2874
  }
2813
2875
 
2814
2876
  /**
2815
- * @param {any} node
2877
+ * @param {AST.Function} node
2816
2878
  * @param {TransformContext} transform_context
2817
2879
  * @returns {boolean}
2818
2880
  */
@@ -2821,25 +2883,17 @@ function function_contains_hook_bearing_tsrx(node, transform_context) {
2821
2883
  }
2822
2884
 
2823
2885
  /**
2824
- * @param {any} node
2886
+ * @param {AST.Node | null | undefined} node
2825
2887
  * @param {TransformContext} transform_context
2826
2888
  * @returns {boolean}
2827
2889
  */
2828
2890
  function node_contains_hook_bearing_tsrx(node, transform_context) {
2829
- if (!node || typeof node !== 'object') {
2891
+ if (!node) {
2830
2892
  return false;
2831
2893
  }
2832
2894
 
2833
- if (Array.isArray(node)) {
2834
- return node.some((child) => node_contains_hook_bearing_tsrx(child, transform_context));
2835
- }
2836
-
2837
2895
  if (is_native_tsrx_node(node)) {
2838
- return body_contains_top_level_hook_call(
2839
- 'children' in node ? node.children : [],
2840
- transform_context,
2841
- true,
2842
- );
2896
+ return body_contains_top_level_hook_call(node_children(node), transform_context, true);
2843
2897
  }
2844
2898
 
2845
2899
  if (
@@ -2850,11 +2904,8 @@ function node_contains_hook_bearing_tsrx(node, transform_context) {
2850
2904
  return false;
2851
2905
  }
2852
2906
 
2853
- for (const key of Object.keys(node)) {
2854
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2855
- continue;
2856
- }
2857
- if (node_contains_hook_bearing_tsrx(node[key], transform_context)) {
2907
+ for (const child of child_nodes(node)) {
2908
+ if (node_contains_hook_bearing_tsrx(child, transform_context)) {
2858
2909
  return true;
2859
2910
  }
2860
2911
  }
@@ -2893,7 +2944,7 @@ function create_module_scoped_hook_component_id(helper_id, transform_context) {
2893
2944
  }
2894
2945
 
2895
2946
  /**
2896
- * @param {any[]} params
2947
+ * @param {AST.Pattern[]} params
2897
2948
  * @returns {Map<string, AST.Identifier>}
2898
2949
  */
2899
2950
  export function collect_param_bindings(params) {
@@ -2905,7 +2956,7 @@ export function collect_param_bindings(params) {
2905
2956
  }
2906
2957
 
2907
2958
  /**
2908
- * @param {any} statement
2959
+ * @param {AST.Node | null | undefined} statement
2909
2960
  * @param {Map<string, AST.Identifier>} bindings
2910
2961
  * @returns {void}
2911
2962
  */
@@ -2940,12 +2991,12 @@ export function collect_statement_bindings(statement, bindings) {
2940
2991
  }
2941
2992
 
2942
2993
  /**
2943
- * @param {any} pattern
2994
+ * @param {AST.Node | null | undefined} pattern
2944
2995
  * @param {Map<string, AST.Identifier>} bindings
2945
2996
  * @returns {void}
2946
2997
  */
2947
2998
  function collect_pattern_bindings(pattern, bindings) {
2948
- if (!pattern || typeof pattern !== 'object') return;
2999
+ if (!pattern) return;
2949
3000
 
2950
3001
  if (pattern.type === 'Identifier') {
2951
3002
  bindings.set(pattern.name, pattern);
@@ -2984,12 +3035,12 @@ function collect_pattern_bindings(pattern, bindings) {
2984
3035
  * Check if a node references any of the given scope bindings.
2985
3036
  * Used to determine if a JSX element is static and can be hoisted to module level.
2986
3037
  *
2987
- * @param {any} node
3038
+ * @param {AST.Node | null | undefined} node
2988
3039
  * @param {Map<string, AST.Identifier>} scope_bindings
2989
3040
  * @returns {boolean}
2990
3041
  */
2991
3042
  function references_scope_bindings(node, scope_bindings) {
2992
- if (!node || typeof node !== 'object') return false;
3043
+ if (!node) return false;
2993
3044
  if (scope_bindings.size === 0) return false;
2994
3045
 
2995
3046
  if (node.type === 'Identifier') {
@@ -3002,11 +3053,10 @@ function references_scope_bindings(node, scope_bindings) {
3002
3053
  return scope_bindings.has(node.name);
3003
3054
  }
3004
3055
 
3005
- if (Array.isArray(node)) {
3006
- return node.some((child) => references_scope_bindings(child, scope_bindings));
3007
- }
3008
-
3009
- for (const key of Object.keys(node)) {
3056
+ // Not `child_nodes`: several keys are labels rather than references and must
3057
+ // be skipped based on the owning node's type.
3058
+ const entries = /** @type {AST.TraversableAstNode} */ (node);
3059
+ for (const key of Object.keys(entries)) {
3010
3060
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
3011
3061
 
3012
3062
  // Skip non-computed, non-shorthand property keys (they are labels, not references)
@@ -3021,7 +3071,15 @@ function references_scope_bindings(node, scope_bindings) {
3021
3071
  // Skip JSXAttribute names — they are attribute labels, not variable references
3022
3072
  if (key === 'name' && node.type === 'JSXAttribute') continue;
3023
3073
 
3024
- if (references_scope_bindings(node[key], scope_bindings)) return true;
3074
+ const value = entries[key];
3075
+ if (Array.isArray(value)) {
3076
+ if (
3077
+ value.some((item) => is_ast_node(item) && references_scope_bindings(item, scope_bindings))
3078
+ )
3079
+ return true;
3080
+ } else if (is_ast_node(value) && references_scope_bindings(value, scope_bindings)) {
3081
+ return true;
3082
+ }
3025
3083
  }
3026
3084
 
3027
3085
  return false;
@@ -3033,7 +3091,7 @@ function references_scope_bindings(node, scope_bindings) {
3033
3091
  * Hoisting prevents React from recreating the element on every render, allowing
3034
3092
  * the reconciler to skip diffing when it sees the same element identity.
3035
3093
  *
3036
- * @param {any[]} render_nodes
3094
+ * @param {ESTreeJSX.JSXRenderChild[]} render_nodes
3037
3095
  * @param {TransformContext} transform_context
3038
3096
  */
3039
3097
  function hoist_static_render_nodes(render_nodes, transform_context) {
@@ -3076,7 +3134,7 @@ function hoist_static_render_nodes(render_nodes, transform_context) {
3076
3134
  * identifiers are host DOM tags, which *do* benefit from hoisting because
3077
3135
  * React diffs them against the previous render.
3078
3136
  *
3079
- * @param {any} node
3137
+ * @param {AST.Node | null | undefined} node
3080
3138
  * @returns {boolean}
3081
3139
  */
3082
3140
  function is_bare_component_invocation(node) {
@@ -3115,34 +3173,37 @@ function expand_component_helpers(program) {
3115
3173
  * node (or array), so the return value must be used in place of the argument.
3116
3174
  * Untouched subtrees are shared by reference with the input.
3117
3175
  *
3118
- * @param {any} node
3176
+ * @template {AST.Node} T
3177
+ * @param {T} node
3119
3178
  * @param {TransformContext} transform_context
3120
- * @param {Set<any>} [seen]
3121
- * @returns {any}
3179
+ * @param {Set<AST.Node>} [seen]
3180
+ * @returns {T}
3122
3181
  */
3123
3182
  function lower_remaining_jsx_code_blocks(node, transform_context, seen = new Set()) {
3124
- if (!node || typeof node !== 'object' || seen.has(node)) return node;
3183
+ if (seen.has(node)) return node;
3125
3184
  seen.add(node);
3126
3185
 
3127
3186
  // A code-block function body lowers to a fresh copy of the function node;
3128
3187
  // its children are then walked below like any other node's.
3129
- let out = is_function_or_class_boundary(node) ? lower_jsx_code_block_function_body(node) : node;
3130
- const set = (/** @type {string} */ key, /** @type {any} */ value) => {
3188
+ const source = /** @type {AST.TraversableAstNode} */ (
3189
+ is_function_node(node) ? lower_jsx_code_block_function_body(node) : node
3190
+ );
3191
+ let out = source;
3192
+ const set = (/** @type {string} */ key, /** @type {unknown} */ value) => {
3131
3193
  if (out[key] === value) return;
3132
- if (out === node) out = { ...node };
3194
+ if (out === source) out = { ...source };
3133
3195
  out[key] = value;
3134
3196
  };
3135
3197
 
3136
3198
  for (const key of Object.keys(out)) {
3137
3199
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
3138
3200
  const value = out[key];
3139
- if (!value || typeof value !== 'object') continue;
3140
3201
 
3141
3202
  if (Array.isArray(value)) {
3142
3203
  const expanded =
3143
- key === 'body' && value.some((child) => child?.type === 'JSXCodeBlock')
3204
+ key === 'body' && value.some((child) => is_ast_node(child) && child.type === 'JSXCodeBlock')
3144
3205
  ? value.flatMap((child) => {
3145
- if (child?.type !== 'JSXCodeBlock') return [child];
3206
+ if (!is_ast_node(child) || child.type !== 'JSXCodeBlock') return [child];
3146
3207
  const body_nodes = get_jsx_code_block_body_nodes(child, transform_context);
3147
3208
  return mark_native_pretransformed_jsx(
3148
3209
  build_render_statements(
@@ -3156,17 +3217,18 @@ function lower_remaining_jsx_code_blocks(node, transform_context, seen = new Set
3156
3217
  : value;
3157
3218
  let changed = expanded !== value;
3158
3219
  const result = expanded.map((child) => {
3220
+ if (!is_ast_node(child)) return child;
3159
3221
  const walked = lower_remaining_jsx_code_blocks(child, transform_context, seen);
3160
3222
  if (walked !== child) changed = true;
3161
3223
  return walked;
3162
3224
  });
3163
3225
  if (changed) set(key, result);
3164
- } else {
3226
+ } else if (is_ast_node(value)) {
3165
3227
  set(key, lower_remaining_jsx_code_blocks(value, transform_context, seen));
3166
3228
  }
3167
3229
  }
3168
3230
 
3169
- return out;
3231
+ return /** @type {T} */ (out);
3170
3232
  }
3171
3233
 
3172
3234
  /**
@@ -3174,18 +3236,20 @@ function lower_remaining_jsx_code_blocks(node, transform_context, seen = new Set
3174
3236
  * variable declarations, object literal members, or export-safe expressions,
3175
3237
  * so helper expansion reads metadata from that broader set.
3176
3238
  *
3177
- * @param {any} node
3178
- * @returns {{ generated_helpers?: any[], generated_statics?: any[] }[]}
3239
+ * @param {AST.Node} node
3240
+ * @returns {BaseNodeMetaData[]}
3179
3241
  */
3180
3242
  function get_generated_component_metadata_list(node) {
3181
- /** @type {{ generated_helpers?: any[], generated_statics?: any[] }[]} */
3243
+ /** @type {BaseNodeMetaData[]} */
3182
3244
  const metas = [];
3245
+ /** @type {Set<AST.Node>} */
3183
3246
  const seen_nodes = new Set();
3247
+ /** @type {Set<BaseNodeMetaData>} */
3184
3248
  const seen_metas = new Set();
3185
3249
 
3186
- /** @param {any} current */
3250
+ /** @param {AST.Node} current */
3187
3251
  const visit = (current) => {
3188
- if (!current || typeof current !== 'object' || seen_nodes.has(current)) {
3252
+ if (seen_nodes.has(current)) {
3189
3253
  return;
3190
3254
  }
3191
3255
 
@@ -3207,19 +3271,8 @@ function get_generated_component_metadata_list(node) {
3207
3271
  return;
3208
3272
  }
3209
3273
 
3210
- for (const key of Object.keys(current)) {
3211
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3212
- continue;
3213
- }
3214
-
3215
- const value = current[key];
3216
- if (Array.isArray(value)) {
3217
- for (const child of value) {
3218
- visit(child);
3219
- }
3220
- } else {
3221
- visit(value);
3222
- }
3274
+ for (const child of child_nodes(current)) {
3275
+ visit(child);
3223
3276
  }
3224
3277
  };
3225
3278
 
@@ -3229,11 +3282,11 @@ function get_generated_component_metadata_list(node) {
3229
3282
  }
3230
3283
 
3231
3284
  /**
3232
- * @param {any[]} render_nodes
3233
- * @param {any} source_node
3285
+ * @param {ESTreeJSX.JSXRenderChild[]} render_nodes
3286
+ * @param {AST.Node | null | undefined} source_node
3234
3287
  * @param {boolean} [map_render_node_locations]
3235
3288
  * @param {boolean} [type_only]
3236
- * @returns {any}
3289
+ * @returns {AST.ReturnStatement}
3237
3290
  */
3238
3291
  function create_component_return_statement(
3239
3292
  render_nodes,
@@ -3252,24 +3305,24 @@ function create_component_return_statement(
3252
3305
  }
3253
3306
 
3254
3307
  /**
3255
- * @param {any} node
3256
- * @returns {boolean}
3308
+ * @param {AST.Node | null | undefined} node
3309
+ * @returns {node is AST.ReturnStatement & { metadata: { generated_loop_continue_return: true } }}
3257
3310
  */
3258
3311
  function is_loop_skip_return_statement(node) {
3259
3312
  return node?.type === 'ReturnStatement' && node.metadata?.generated_loop_continue_return === true;
3260
3313
  }
3261
3314
 
3262
3315
  /**
3263
- * @param {any} node
3264
- * @returns {boolean}
3316
+ * @param {AST.Node | null | undefined} node
3317
+ * @returns {node is AST.IfStatement | AST.JSXIfExpression}
3265
3318
  */
3266
3319
  function is_loop_skip_if_statement(node) {
3267
3320
  return get_loop_skip_if_consequent_body(node) !== null;
3268
3321
  }
3269
3322
 
3270
3323
  /**
3271
- * @param {any} node
3272
- * @returns {any[] | null}
3324
+ * @param {AST.Node | null | undefined} node
3325
+ * @returns {AST.Statement[] | null}
3273
3326
  */
3274
3327
  function get_loop_skip_if_consequent_body(node) {
3275
3328
  if (!is_if_control_node(node) || node.alternate) {
@@ -3283,13 +3336,14 @@ function get_loop_skip_if_consequent_body(node) {
3283
3336
  }
3284
3337
 
3285
3338
  /**
3286
- * @param {any} node
3287
- * @param {any[]} render_nodes
3339
+ * @param {AST.IfStatement | AST.JSXIfExpression} node
3340
+ * @param {ESTreeJSX.JSXRenderChild[]} render_nodes
3288
3341
  * @param {TransformContext} transform_context
3289
- * @returns {any}
3342
+ * @returns {AST.IfStatement}
3290
3343
  */
3291
3344
  function create_component_loop_skip_if_statement(node, render_nodes, transform_context) {
3292
- const consequent_body = /** @type {any[]} */ (get_loop_skip_if_consequent_body(node));
3345
+ // `is_loop_skip_if_statement` already proved this is non-null.
3346
+ const consequent_body = /** @type {AST.Statement[]} */ (get_loop_skip_if_consequent_body(node));
3293
3347
  const branch_statements = prepend_render_nodes_to_return_statements(
3294
3348
  build_render_statements(consequent_body, true, transform_context),
3295
3349
  render_nodes,
@@ -3312,27 +3366,28 @@ function create_component_loop_skip_if_statement(node, render_nodes, transform_c
3312
3366
  * rewritten returns land on shallow copies; the returned array must be used in
3313
3367
  * place of the argument.
3314
3368
  *
3315
- * @param {any[]} statements
3316
- * @param {any[]} render_nodes
3369
+ * @param {AST.Statement[]} statements
3370
+ * @param {ESTreeJSX.JSXRenderChild[]} render_nodes
3317
3371
  * @param {boolean} [type_only]
3318
- * @returns {any[]}
3372
+ * @returns {AST.Statement[]}
3319
3373
  */
3320
3374
  function prepend_render_nodes_to_return_statements(statements, render_nodes, type_only = false) {
3321
3375
  if (render_nodes.length === 0) {
3322
3376
  return statements;
3323
3377
  }
3324
3378
 
3325
- return /** @type {any[]} */ (
3326
- prepend_render_nodes_to_return_statement(statements, render_nodes, false, type_only)
3379
+ return statements.map((statement) =>
3380
+ prepend_render_nodes_to_return_statement(statement, render_nodes, false, type_only),
3327
3381
  );
3328
3382
  }
3329
3383
 
3330
3384
  /**
3331
- * @param {any} node
3332
- * @param {any[]} render_nodes
3385
+ * @template {AST.Node} T
3386
+ * @param {T} node
3387
+ * @param {ESTreeJSX.JSXRenderChild[]} render_nodes
3333
3388
  * @param {boolean} inside_nested_function
3334
3389
  * @param {boolean} [type_only]
3335
- * @returns {any}
3390
+ * @returns {T}
3336
3391
  */
3337
3392
  function prepend_render_nodes_to_return_statement(
3338
3393
  node,
@@ -3340,10 +3395,6 @@ function prepend_render_nodes_to_return_statement(
3340
3395
  inside_nested_function,
3341
3396
  type_only = false,
3342
3397
  ) {
3343
- if (!node || typeof node !== 'object') {
3344
- return node;
3345
- }
3346
-
3347
3398
  if (
3348
3399
  node.type === 'FunctionDeclaration' ||
3349
3400
  node.type === 'FunctionExpression' ||
@@ -3359,45 +3410,50 @@ function prepend_render_nodes_to_return_statement(
3359
3410
  };
3360
3411
  }
3361
3412
 
3362
- if (Array.isArray(node)) {
3363
- let changed = false;
3364
- const result = node.map((child) => {
3365
- const walked = prepend_render_nodes_to_return_statement(
3366
- child,
3413
+ const source = /** @type {AST.TraversableAstNode} */ (node);
3414
+ let out = source;
3415
+ for (const key of Object.keys(source)) {
3416
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3417
+ continue;
3418
+ }
3419
+ const value = source[key];
3420
+ /** @type {unknown} */
3421
+ let walked = value;
3422
+ if (Array.isArray(value)) {
3423
+ let changed = false;
3424
+ const result = value.map((child) => {
3425
+ if (!is_ast_node(child)) return child;
3426
+ const next = prepend_render_nodes_to_return_statement(
3427
+ child,
3428
+ render_nodes,
3429
+ inside_nested_function,
3430
+ type_only,
3431
+ );
3432
+ if (next !== child) changed = true;
3433
+ return next;
3434
+ });
3435
+ if (changed) walked = result;
3436
+ } else if (is_ast_node(value)) {
3437
+ walked = prepend_render_nodes_to_return_statement(
3438
+ value,
3367
3439
  render_nodes,
3368
3440
  inside_nested_function,
3369
3441
  type_only,
3370
3442
  );
3371
- if (walked !== child) changed = true;
3372
- return walked;
3373
- });
3374
- return changed ? result : node;
3375
- }
3376
-
3377
- let out = node;
3378
- for (const key of Object.keys(node)) {
3379
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3380
- continue;
3381
3443
  }
3382
- const walked = prepend_render_nodes_to_return_statement(
3383
- node[key],
3384
- render_nodes,
3385
- inside_nested_function,
3386
- type_only,
3387
- );
3388
- if (walked !== node[key]) {
3389
- if (out === node) out = { ...node };
3444
+ if (walked !== value) {
3445
+ if (out === source) out = { ...source };
3390
3446
  out[key] = walked;
3391
3447
  }
3392
3448
  }
3393
- return out;
3449
+ return /** @type {T} */ (out);
3394
3450
  }
3395
3451
 
3396
3452
  /**
3397
- * @param {any[]} render_nodes
3398
- * @param {any} return_argument
3453
+ * @param {ESTreeJSX.JSXRenderChild[]} render_nodes
3454
+ * @param {AST.Expression | null | undefined} return_argument
3399
3455
  * @param {boolean} [type_only]
3400
- * @returns {any}
3456
+ * @returns {AST.Expression}
3401
3457
  */
3402
3458
  function combine_render_return_argument(render_nodes, return_argument, type_only = false) {
3403
3459
  const combined = render_nodes.map((node) => clone_ast_node(node, false));
@@ -3410,8 +3466,8 @@ function combine_render_return_argument(render_nodes, return_argument, type_only
3410
3466
  }
3411
3467
 
3412
3468
  /**
3413
- * @param {any} argument
3414
- * @returns {any}
3469
+ * @param {AST.Expression | ESTreeJSX.JSXExpressionContainer} argument
3470
+ * @returns {ESTreeJSX.JSXRenderChild}
3415
3471
  */
3416
3472
  function return_argument_to_render_node(argument) {
3417
3473
  if (
@@ -3426,7 +3482,7 @@ function return_argument_to_render_node(argument) {
3426
3482
  }
3427
3483
 
3428
3484
  /**
3429
- * @param {any} node
3485
+ * @param {AST.Node | null | undefined} node
3430
3486
  * @returns {boolean}
3431
3487
  */
3432
3488
  function is_null_literal(node) {
@@ -3441,8 +3497,8 @@ function is_null_literal(node) {
3441
3497
  * runtime check skips the copy when the value is already an array.
3442
3498
  *
3443
3499
  * @param {AST.Identifier} source_id
3444
- * @param {any} source_expr
3445
- * @returns {{ source_decl: any, source_normalize_decl: any }}
3500
+ * @param {AST.Expression} source_expr
3501
+ * @returns {{ source_decl: AST.VariableDeclaration, source_normalize_decl: AST.ExpressionStatement }}
3446
3502
  */
3447
3503
  function build_array_normalization_decls(source_id, source_expr) {
3448
3504
  const source_decl = b.let(clone_identifier(source_id), clone_ast_node(source_expr));
@@ -3469,20 +3525,21 @@ function build_array_normalization_decls(source_id, source_expr) {
3469
3525
  * Bails out (returns null) when the loop pattern is destructured — deriving
3470
3526
  * element types from a tuple/object pattern is more involved and deferred.
3471
3527
  *
3472
- * @param {any} node - ForOfStatement
3528
+ * @param {AST.ForOfStatement} node
3473
3529
  * @param {TransformContext} transform_context
3474
- * @returns {{ hoist_statements: any[], jsx_child: any } | null}
3530
+ * @returns {{ hoist_statements: AST.Statement[], jsx_child: ESTreeJSX.JSXExpressionContainer } | null}
3475
3531
  */
3476
3532
  function build_hoisted_for_of_with_hooks(node, transform_context) {
3477
- const loop_params = get_for_of_iteration_params(node.left, node.index);
3478
- for (const param of loop_params) {
3533
+ /** @type {AST.Identifier[]} */
3534
+ const loop_params = [];
3535
+ for (const param of get_for_of_iteration_params(node.left, node.index)) {
3536
+ // Deriving element types from a destructured pattern is deferred.
3479
3537
  if (param.type !== 'Identifier') return null;
3538
+ loop_params.push(param);
3480
3539
  }
3481
3540
 
3482
- const original_loop_body = /** @type {any[]} */ (
3483
- rewrite_loop_continues_to_bare_returns(
3484
- node.body.type === 'BlockStatement' ? node.body.body : [node.body],
3485
- )
3541
+ const original_loop_body = rewrite_loop_continues_to_bare_returns(
3542
+ node.body.type === 'BlockStatement' ? node.body.body : [node.body],
3486
3543
  );
3487
3544
 
3488
3545
  const source_id = create_generated_identifier(
@@ -3498,7 +3555,7 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
3498
3555
 
3499
3556
  const saved_bindings = transform_context.available_bindings;
3500
3557
  transform_context.available_bindings = new Map(saved_bindings);
3501
- const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
3558
+ const loop_scoped_names = new Set(loop_params.map((p) => p.name));
3502
3559
  for (const param of loop_params) {
3503
3560
  collect_pattern_bindings(param, transform_context.available_bindings);
3504
3561
  }
@@ -3562,10 +3619,12 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
3562
3619
  const helper_fn = b.function(clone_identifier(component_id), params, b.block(fn_body_statements));
3563
3620
  helper_fn.metadata = { path: [], is_method: false };
3564
3621
 
3622
+ const node_loc = has_location(node) ? node : undefined;
3623
+ /** @type {AST.Statement | null} */
3565
3624
  let helper_decl;
3566
3625
  if (transform_context.helper_state && use_module_scoped_component) {
3567
3626
  transform_context.helper_state.helpers.push(
3568
- create_helper_declaration(component_id, helper_fn, node, transform_context),
3627
+ create_helper_declaration(component_id, helper_fn, node_loc, transform_context),
3569
3628
  );
3570
3629
  helper_decl = null;
3571
3630
  } else if (transform_context.helper_state) {
@@ -3576,10 +3635,10 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
3576
3635
  helper_decl = create_cached_helper_declaration(
3577
3636
  helper_id,
3578
3637
  cache_id,
3579
- create_helper_init_expression(helper_id, helper_fn, node, transform_context),
3638
+ create_helper_init_expression(helper_id, helper_fn, node_loc, transform_context),
3580
3639
  );
3581
3640
  } else {
3582
- helper_decl = create_helper_declaration(helper_id, helper_fn, node, transform_context);
3641
+ helper_decl = create_helper_declaration(helper_id, helper_fn, node_loc, transform_context);
3583
3642
  }
3584
3643
 
3585
3644
  transform_context.available_bindings = saved_bindings;
@@ -3605,7 +3664,7 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
3605
3664
  );
3606
3665
  }
3607
3666
 
3608
- const callback_params = loop_params.map((/** @type {any} */ p) => clone_identifier(p));
3667
+ const callback_params = loop_params.map((p) => clone_identifier(p));
3609
3668
 
3610
3669
  const iter_callback = b.arrow(callback_params, callback_invocation_element);
3611
3670
 
@@ -3619,6 +3678,7 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
3619
3678
 
3620
3679
  const jsx_child = to_jsx_expression_container(map_call, node);
3621
3680
 
3681
+ /** @type {AST.Statement[]} */
3622
3682
  const hoist_statements = source_normalize_decl
3623
3683
  ? [source_decl, source_normalize_decl]
3624
3684
  : [source_decl];
@@ -3648,9 +3708,9 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
3648
3708
  * @param {AST.Identifier} helper_id
3649
3709
  * @param {AST.Identifier} binding
3650
3710
  * @param {AST.Identifier} source_id
3651
- * @param {any[]} loop_params
3711
+ * @param {AST.Identifier[]} loop_params
3652
3712
  * @param {TransformContext} transform_context
3653
- * @returns {{ id: AST.Identifier, declaration: any }}
3713
+ * @returns {{ id: AST.Identifier, declaration: AST.TSTypeAliasDeclaration & AST.Statement }}
3654
3714
  */
3655
3715
  function create_loop_scoped_type_alias_declaration(
3656
3716
  helper_id,
@@ -3672,7 +3732,7 @@ function create_loop_scoped_type_alias_declaration(
3672
3732
  b.ts_type_parameter_instantiation([b.ts_type_query(clone_identifier(source_id))]),
3673
3733
  );
3674
3734
  })()
3675
- : /** @type {any} */ ({
3735
+ : /** @type {AST.TypeNode} */ ({
3676
3736
  type: 'TSIndexedAccessType',
3677
3737
  objectType: b.ts_type_query(clone_identifier(source_id)),
3678
3738
  indexType: b.ts_keyword_type('number'),
@@ -3694,7 +3754,7 @@ function create_loop_scoped_type_alias_declaration(
3694
3754
  * @param {AST.Identifier[]} bindings
3695
3755
  * @param {{ id: AST.Identifier }[]} aliases
3696
3756
  * @param {boolean[]} use_typeof
3697
- * @returns {any}
3757
+ * @returns {AST.TSTypeLiteral}
3698
3758
  */
3699
3759
  function create_helper_props_type_literal_with_typeof_flags(bindings, aliases, use_typeof) {
3700
3760
  return b.ts_type_literal(
@@ -3711,34 +3771,36 @@ function create_helper_props_type_literal_with_typeof_flags(bindings, aliases, u
3711
3771
  }
3712
3772
 
3713
3773
  /**
3714
- * @param {any} node
3774
+ * @param {AST.TSRXJSXElement | AST.TSRXJSXFragment | AST.JSXStyleElement} node
3715
3775
  * @param {TransformContext} transform_context
3776
+ * @param {AST.Node[]} [raw_children]
3716
3777
  * @param {boolean} [in_jsx_child]
3717
- * @returns {any}
3778
+ * @returns {AST.TSRXJSXElement | AST.TSRXJSXFragment}
3718
3779
  */
3719
3780
  function to_jsx_element(
3720
3781
  node,
3721
3782
  transform_context,
3722
- raw_children = node.children || [],
3783
+ raw_children = node_children(node),
3723
3784
  in_jsx_child = false,
3724
3785
  ) {
3725
3786
  if (node.type === 'JSXElement' && !node.metadata?.native_tsrx) {
3726
3787
  return node;
3727
3788
  }
3728
3789
 
3729
- const source_opening = node.openingElement;
3730
- const source_name = source_opening?.name;
3731
- if (!source_name) {
3790
+ // A fragment has no opening element to take a name from; in a TSRX template
3791
+ // that is the "fragments are not needed here" error case.
3792
+ if (node.type === 'JSXFragment' || !node.openingElement?.name) {
3732
3793
  report_jsx_fragment_in_tsrx_error(node, transform_context);
3733
3794
  return set_loc(b.jsx_fragment(), node);
3734
3795
  }
3735
- const name = clone_jsx_name(source_name);
3796
+ const source_opening = node.openingElement;
3797
+ const name = clone_jsx_name(source_opening.name);
3736
3798
  const attributes = transform_element_attributes_dispatch(
3737
3799
  source_opening.attributes || [],
3738
3800
  transform_context,
3739
- node,
3801
+ /** @type {AST.TSRXJSXElement} */ (node),
3740
3802
  );
3741
- let walked_children = node.children || [];
3803
+ let walked_children = node_children(node);
3742
3804
  // A raw-text `<script>` body (mirrored by the parser as a JSXText child of
3743
3805
  // `node.content`) must not appear in the type-only editor TSX: raw JS/TS
3744
3806
  // (`{`, `<`) doesn't lex as JSX text there and would surface bogus syntactic
@@ -3751,7 +3813,7 @@ function to_jsx_element(
3751
3813
  let selfClosing = !!source_opening.selfClosing;
3752
3814
  let children;
3753
3815
  const child_transform = transform_context.platform.hooks?.transformElementChildren?.(
3754
- node,
3816
+ /** @type {AST.TSRXJSXElement} */ (node),
3755
3817
  walked_children,
3756
3818
  raw_children,
3757
3819
  attributes,
@@ -3767,7 +3829,7 @@ function to_jsx_element(
3767
3829
  children = create_element_children(walked_children, transform_context);
3768
3830
  }
3769
3831
  const has_unmappable_attribute = attributes.some(
3770
- (/** @type {any} */ attribute) => attribute?.metadata?.has_unmappable_value,
3832
+ (attribute) => attribute?.metadata?.has_unmappable_value,
3771
3833
  );
3772
3834
 
3773
3835
  const opening_element_node = b.jsx_opening_element(
@@ -3810,9 +3872,8 @@ function to_jsx_element(
3810
3872
  /**
3811
3873
  * @param {AST.Node[]} children
3812
3874
  * @param {TransformContext} transform_context
3813
- * @returns {ESTreeJSX.JSXElement['children']}
3875
+ * @returns {AST.TSRXJSXElement['children']}
3814
3876
  */
3815
-
3816
3877
  function create_element_children(children, transform_context) {
3817
3878
  if (children.length === 0) {
3818
3879
  return [];
@@ -3891,7 +3952,7 @@ function is_inline_element_child(node) {
3891
3952
  }
3892
3953
 
3893
3954
  /**
3894
- * @param {any[]} body_nodes
3955
+ * @param {AST.Node[]} body_nodes
3895
3956
  * @param {TransformContext} transform_context
3896
3957
  * @returns {ESTreeJSX.JSXExpressionContainer}
3897
3958
  */
@@ -3909,7 +3970,7 @@ function statement_body_to_jsx_child(body_nodes, transform_context) {
3909
3970
  }
3910
3971
 
3911
3972
  /**
3912
- * @param {any[]} body_nodes
3973
+ * @param {AST.Node[]} body_nodes
3913
3974
  * @param {TransformContext} transform_context
3914
3975
  * @returns {ESTreeJSX.JSXExpressionContainer}
3915
3976
  */
@@ -3940,10 +4001,10 @@ function create_local_statement_component_name(transform_context) {
3940
4001
  * Used when a control flow branch contains hook calls that must be moved
3941
4002
  * into their own component boundary to satisfy the Rules of Hooks.
3942
4003
  *
3943
- * @param {any[]} body_nodes
3944
- * @param {any} key_expression - Optional key expression to add to the component element (for `for-of` loops)
4004
+ * @param {AST.Node[]} body_nodes
4005
+ * @param {AST.Expression | undefined} key_expression Optional key expression to add to the component element (for `for-of` loops)
3945
4006
  * @param {TransformContext} transform_context
3946
- * @returns {any[]}
4007
+ * @returns {AST.Statement[]}
3947
4008
  */
3948
4009
  function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
3949
4010
  const source_node = get_body_source_node(body_nodes);
@@ -3961,12 +4022,14 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
3961
4022
  }
3962
4023
 
3963
4024
  /**
3964
- * @param {any[]} body_nodes
4025
+ * @param {AST.Node[]} body_nodes
3965
4026
  * @param {Map<string, AST.Identifier>} available_bindings
3966
4027
  * @returns {AST.Identifier[]}
3967
4028
  */
3968
4029
  function get_referenced_helper_bindings(body_nodes, available_bindings) {
4030
+ /** @type {AST.Identifier[]} */
3969
4031
  const helper_bindings = [];
4032
+ /** @type {Map<string, AST.Identifier>} */
3970
4033
  const local_bindings = new Map();
3971
4034
 
3972
4035
  for (const node of body_nodes) {
@@ -3976,7 +4039,8 @@ function get_referenced_helper_bindings(body_nodes, available_bindings) {
3976
4039
  for (const [name, binding] of available_bindings) {
3977
4040
  if (local_bindings.has(name)) continue;
3978
4041
 
3979
- if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
4042
+ const scope = new Map([[name, binding]]);
4043
+ if (body_nodes.some((node) => references_scope_bindings(node, scope))) {
3980
4044
  helper_bindings.push(binding);
3981
4045
  }
3982
4046
  }
@@ -3985,14 +4049,14 @@ function get_referenced_helper_bindings(body_nodes, available_bindings) {
3985
4049
  }
3986
4050
 
3987
4051
  /**
3988
- * @param {any[]} body_nodes
3989
- * @param {any} key_expression
3990
- * @param {any} source_node
4052
+ * @param {AST.Node[]} body_nodes
4053
+ * @param {AST.Expression | undefined} key_expression
4054
+ * @param {AST.NodeWithLocation | undefined} source_node
3991
4055
  * @param {TransformContext} transform_context
3992
4056
  * @param {AST.Identifier} [preallocated_helper_id] - Optional pre-allocated id.
3993
4057
  * Used by switch lifting to keep generated helper ids stable in source order.
3994
4058
  * @param {{ transientBindings?: Set<string> }} [options]
3995
- * @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
4059
+ * @returns {{ setup_statements: AST.Statement[], component_element: AST.TSRXJSXElement }}
3996
4060
  */
3997
4061
  export function create_hook_safe_helper(
3998
4062
  body_nodes,
@@ -4104,9 +4168,9 @@ export function create_hook_safe_helper(
4104
4168
  /**
4105
4169
  * @param {AST.Identifier} helper_id
4106
4170
  * @param {AST.FunctionExpression} helper_fn
4107
- * @param {any} source_node
4171
+ * @param {AST.NodeWithLocation | undefined} source_node
4108
4172
  * @param {TransformContext} transform_context
4109
- * @returns {any}
4173
+ * @returns {AST.Statement}
4110
4174
  */
4111
4175
  function create_helper_declaration(helper_id, helper_fn, source_node, transform_context) {
4112
4176
  const declaration = create_helper_function_declaration_from_expression(helper_id, helper_fn);
@@ -4117,9 +4181,9 @@ function create_helper_declaration(helper_id, helper_fn, source_node, transform_
4117
4181
  /**
4118
4182
  * @param {AST.Identifier} helper_id
4119
4183
  * @param {AST.FunctionExpression} helper_fn
4120
- * @param {any} source_node
4184
+ * @param {AST.NodeWithLocation | undefined} source_node
4121
4185
  * @param {TransformContext} transform_context
4122
- * @returns {any}
4186
+ * @returns {AST.Expression}
4123
4187
  */
4124
4188
  function create_helper_init_expression(helper_id, helper_fn, source_node, transform_context) {
4125
4189
  const hook = transform_context.platform.hooks?.wrapHelperComponent;
@@ -4140,9 +4204,9 @@ function create_helper_init_expression(helper_id, helper_fn, source_node, transf
4140
4204
  }
4141
4205
 
4142
4206
  /**
4143
- * @param {any[]} setup_statements
4144
- * @param {ESTreeJSX.JSXElement} component_element
4145
- * @returns {any}
4207
+ * @param {AST.Statement[]} setup_statements
4208
+ * @param {AST.TSRXJSXElement} component_element
4209
+ * @returns {AST.CallExpression}
4146
4210
  */
4147
4211
  function create_hook_safe_helper_iife(setup_statements, component_element) {
4148
4212
  return b.call(b.arrow([], b.block([...setup_statements, b.return(component_element)])));
@@ -4151,7 +4215,7 @@ function create_hook_safe_helper_iife(setup_statements, component_element) {
4151
4215
  /**
4152
4216
  * @param {AST.Identifier} helper_id
4153
4217
  * @param {AST.Identifier} binding
4154
- * @returns {{ id: AST.Identifier, declaration: any }}
4218
+ * @returns {{ id: AST.Identifier, declaration: AST.VariableDeclaration }}
4155
4219
  */
4156
4220
  function create_helper_type_alias_declaration(helper_id, binding) {
4157
4221
  const alias_id = create_generated_identifier(`_tsrx_${helper_id.name}_${binding.name}`);
@@ -4165,7 +4229,7 @@ function create_helper_type_alias_declaration(helper_id, binding) {
4165
4229
  /**
4166
4230
  * @param {AST.Identifier[]} bindings
4167
4231
  * @param {({ id: AST.Identifier } | null)[]} aliases
4168
- * @returns {any}
4232
+ * @returns {AST.TSTypeLiteral}
4169
4233
  */
4170
4234
  function create_helper_props_type_literal(bindings, aliases) {
4171
4235
  return b.ts_type_literal(
@@ -4186,19 +4250,19 @@ function create_helper_props_type_literal(bindings, aliases) {
4186
4250
 
4187
4251
  /**
4188
4252
  * @param {AST.Identifier[]} bindings
4189
- * @param {any} props_type
4253
+ * @param {AST.TSTypeLiteral} props_type
4190
4254
  * @param {Set<string>} [mapped_bindings]
4191
4255
  * @returns {AST.ObjectPattern}
4192
4256
  */
4193
4257
  function create_typed_helper_props_pattern(bindings, props_type, mapped_bindings = new Set()) {
4194
4258
  const pattern = create_helper_props_pattern(bindings, mapped_bindings);
4195
- /** @type {any} */ (pattern).typeAnnotation = b.ts_type_annotation(props_type);
4259
+ pattern.typeAnnotation = b.ts_type_annotation(props_type);
4196
4260
  return pattern;
4197
4261
  }
4198
4262
 
4199
4263
  /**
4200
4264
  * @param {AST.Identifier} cache_id
4201
- * @returns {any}
4265
+ * @returns {AST.VariableDeclaration}
4202
4266
  */
4203
4267
  function create_helper_cache_declaration(cache_id) {
4204
4268
  return b.let(clone_identifier(cache_id));
@@ -4207,8 +4271,8 @@ function create_helper_cache_declaration(cache_id) {
4207
4271
  /**
4208
4272
  * @param {AST.Identifier} helper_id
4209
4273
  * @param {AST.Identifier} cache_id
4210
- * @param {any} helper_init
4211
- * @returns {any}
4274
+ * @param {AST.Expression} helper_init
4275
+ * @returns {AST.VariableDeclaration}
4212
4276
  */
4213
4277
  function create_cached_helper_declaration(helper_id, cache_id, helper_init) {
4214
4278
  return b.const(
@@ -4243,8 +4307,11 @@ function create_helper_function_declaration_from_expression(helper_id, helper_fn
4243
4307
  }
4244
4308
 
4245
4309
  /**
4246
- * @param {any[]} body_nodes
4247
- * @returns {any}
4310
+ * The source span covering a body, for stamping onto the nodes generated from
4311
+ * it. Undefined when the body carries no usable positions.
4312
+ *
4313
+ * @param {AST.Node[]} body_nodes
4314
+ * @returns {AST.NodeWithLocation | undefined}
4248
4315
  */
4249
4316
  function get_body_source_node(body_nodes) {
4250
4317
  const first = body_nodes[0];
@@ -4261,22 +4328,46 @@ function get_body_source_node(body_nodes) {
4261
4328
  };
4262
4329
  }
4263
4330
 
4264
- return first;
4331
+ return has_location(first) ? first : undefined;
4265
4332
  }
4266
4333
 
4267
4334
  /**
4268
- * @param {any} node
4269
- * @returns {any}
4335
+ * Retype a directive expression node (`JSXIfExpression`, …) to the statement
4336
+ * form its `statementType` names, so statement-shaped code paths can consume it.
4337
+ *
4338
+ * @overload
4339
+ * @param {AST.JSXIfExpression | AST.IfStatement} node
4340
+ * @returns {AST.IfStatement}
4341
+ */
4342
+ /**
4343
+ * @overload
4344
+ * @param {AST.JSXForExpression | AST.ForOfStatement} node
4345
+ * @returns {AST.ForOfStatement}
4346
+ */
4347
+ /**
4348
+ * @overload
4349
+ * @param {AST.JSXSwitchExpression | AST.SwitchStatement} node
4350
+ * @returns {AST.SwitchStatement}
4351
+ */
4352
+ /**
4353
+ * @overload
4354
+ * @param {AST.JSXTryExpression | AST.TryStatement} node
4355
+ * @returns {AST.TryStatement}
4356
+ */
4357
+ /**
4358
+ * @param {AST.Node} node
4359
+ * @returns {AST.Node}
4270
4360
  */
4271
4361
  function jsx_control_expression_to_statement(node) {
4272
- if (!node?.statementType) return node;
4273
- return { ...node, type: node.statementType };
4362
+ const statement_type = /** @type {AST.JSXTemplateDirective} */ (node).statementType;
4363
+ if (!statement_type) return node;
4364
+ return /** @type {AST.Node} */ ({ ...node, type: statement_type });
4274
4365
  }
4275
4366
 
4276
4367
  /**
4277
- * @param {any} node
4368
+ * @param {AST.JSXCodeBlock} node
4278
4369
  * @param {TransformContext} transform_context
4279
- * @returns {any[]}
4370
+ * @returns {AST.Node[]}
4280
4371
  */
4281
4372
  function get_jsx_code_block_body_nodes(node, transform_context) {
4282
4373
  if (!node.render) {
@@ -4297,8 +4388,8 @@ function get_jsx_code_block_body_nodes(node, transform_context) {
4297
4388
  }
4298
4389
 
4299
4390
  /**
4300
- * @param {any} node
4301
- * @returns {any[]}
4391
+ * @param {AST.JSXCodeBlock} node
4392
+ * @returns {AST.Node[]}
4302
4393
  */
4303
4394
  function get_raw_jsx_code_block_body_nodes(node) {
4304
4395
  return [...(node.body || []), ...(node.render ? [node.render] : [])];
@@ -4357,16 +4448,16 @@ function is_render_child_node(node) {
4357
4448
  }
4358
4449
 
4359
4450
  /**
4360
- * @param {any} node
4361
- * @returns {boolean}
4451
+ * @param {AST.Node | null | undefined} node
4452
+ * @returns {node is AST.SwitchStatement | AST.JSXSwitchExpression}
4362
4453
  */
4363
4454
  function is_switch_control_node(node) {
4364
4455
  return node?.type === 'SwitchStatement' || node?.type === 'JSXSwitchExpression';
4365
4456
  }
4366
4457
 
4367
4458
  /**
4368
- * @param {any} node
4369
- * @returns {boolean}
4459
+ * @param {AST.Node | null | undefined} node
4460
+ * @returns {node is AST.TryStatement | AST.JSXTryExpression}
4370
4461
  */
4371
4462
  function is_try_control_node(node) {
4372
4463
  return node?.type === 'TryStatement' || node?.type === 'JSXTryExpression';
@@ -4605,10 +4696,10 @@ function tsrx_node_to_jsx_expression(node, transform_context, in_jsx_child = fal
4605
4696
  * Explicit return values inside expression-position native templates are JavaScript
4606
4697
  * values, so keep them out of platform render control flow.
4607
4698
  *
4608
- * @param {any[]} body_nodes
4609
- * @param {any} source_node
4699
+ * @param {AST.Node[]} body_nodes
4700
+ * @param {AST.Node | null | undefined} source_node
4610
4701
  * @param {TransformContext} [transform_context]
4611
- * @returns {any | null}
4702
+ * @returns {AST.Expression | null}
4612
4703
  */
4613
4704
  export function return_value_body_to_expression(body_nodes, source_node, transform_context) {
4614
4705
  if (!body_contains_top_level_return_value(body_nodes)) return null;
@@ -4618,13 +4709,17 @@ export function return_value_body_to_expression(body_nodes, source_node, transfo
4618
4709
  if (expression) return expression;
4619
4710
  }
4620
4711
 
4621
- return create_statement_iife(body_nodes, source_node, transform_context);
4712
+ return create_statement_iife(
4713
+ /** @type {AST.Statement[]} */ (body_nodes),
4714
+ source_node,
4715
+ transform_context,
4716
+ );
4622
4717
  }
4623
4718
 
4624
4719
  /**
4625
- * @param {any} node
4720
+ * @param {AST.Node | null | undefined} node
4626
4721
  * @param {TransformContext} [transform_context]
4627
- * @returns {any | null}
4722
+ * @returns {AST.Expression | null}
4628
4723
  */
4629
4724
  function return_value_statement_to_expression(node, transform_context) {
4630
4725
  if (node?.type === 'ReturnStatement' && node.argument != null) {
@@ -4639,11 +4734,11 @@ function return_value_statement_to_expression(node, transform_context) {
4639
4734
  }
4640
4735
 
4641
4736
  /**
4642
- * @param {any} node
4737
+ * @param {AST.Node | AST.Node[] | null | undefined} node
4643
4738
  * @returns {boolean}
4644
4739
  */
4645
4740
  function body_contains_top_level_return_value(node) {
4646
- if (!node || typeof node !== 'object') return false;
4741
+ if (!node) return false;
4647
4742
 
4648
4743
  if (Array.isArray(node)) {
4649
4744
  return node.some(body_contains_top_level_return_value);
@@ -4663,11 +4758,8 @@ function body_contains_top_level_return_value(node) {
4663
4758
  return false;
4664
4759
  }
4665
4760
 
4666
- for (const key of Object.keys(node)) {
4667
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4668
- continue;
4669
- }
4670
- if (body_contains_top_level_return_value(node[key])) {
4761
+ for (const child of child_nodes(node)) {
4762
+ if (body_contains_top_level_return_value(child)) {
4671
4763
  return true;
4672
4764
  }
4673
4765
  }
@@ -4676,10 +4768,10 @@ function body_contains_top_level_return_value(node) {
4676
4768
  }
4677
4769
 
4678
4770
  /**
4679
- * @param {any[]} body_nodes
4680
- * @param {any} source_node
4771
+ * @param {AST.Statement[]} body_nodes
4772
+ * @param {AST.Node | null | undefined} source_node
4681
4773
  * @param {TransformContext} [transform_context]
4682
- * @returns {any}
4774
+ * @returns {AST.Expression}
4683
4775
  */
4684
4776
  function create_statement_iife(body_nodes, source_node, transform_context) {
4685
4777
  return set_generated_expression_loc(
@@ -4701,16 +4793,16 @@ function set_generated_expression_loc(node, source_node, transform_context) {
4701
4793
  }
4702
4794
 
4703
4795
  /**
4704
- * @returns {any}
4796
+ * @returns {AST.UnaryExpression}
4705
4797
  */
4706
4798
  function create_undefined_expression() {
4707
4799
  return b.unary('void', b.literal(0));
4708
4800
  }
4709
4801
 
4710
4802
  /**
4711
- * @param {any} node
4803
+ * @param {AST.Node | null | undefined} node
4712
4804
  * @param {TransformContext} [transform_context]
4713
- * @returns {any | null}
4805
+ * @returns {AST.Expression | null}
4714
4806
  */
4715
4807
  function return_value_block_to_expression(node, transform_context) {
4716
4808
  const body = node?.type === 'BlockStatement' ? node.body : node ? [node] : [];
@@ -4720,9 +4812,9 @@ function return_value_block_to_expression(node, transform_context) {
4720
4812
  }
4721
4813
 
4722
4814
  /**
4723
- * @param {any} node
4815
+ * @param {AST.Node | null | undefined} node
4724
4816
  * @param {TransformContext} [transform_context]
4725
- * @returns {any | null}
4817
+ * @returns {AST.Expression | null}
4726
4818
  */
4727
4819
  function return_value_if_statement_to_conditional_expression(node, transform_context) {
4728
4820
  if (!is_if_control_node(node)) return null;
@@ -4730,10 +4822,12 @@ function return_value_if_statement_to_conditional_expression(node, transform_con
4730
4822
  const consequent = return_value_block_to_expression(node.consequent, transform_context);
4731
4823
  if (!consequent) return null;
4732
4824
 
4825
+ /** @type {AST.Expression} */
4733
4826
  let alternate = create_undefined_expression();
4734
4827
  if (node.alternate) {
4735
- alternate = return_value_block_to_expression(node.alternate, transform_context);
4736
- if (!alternate) return null;
4828
+ const lowered = return_value_block_to_expression(node.alternate, transform_context);
4829
+ if (!lowered) return null;
4830
+ alternate = lowered;
4737
4831
  }
4738
4832
 
4739
4833
  return set_generated_expression_loc(
@@ -4744,7 +4838,7 @@ function return_value_if_statement_to_conditional_expression(node, transform_con
4744
4838
  }
4745
4839
 
4746
4840
  /**
4747
- * @param {any} node
4841
+ * @param {AST.IfStatement | AST.JSXIfExpression} node
4748
4842
  * @param {TransformContext} transform_context
4749
4843
  * @returns {ESTreeJSX.JSXExpressionContainer}
4750
4844
  */
@@ -4752,6 +4846,29 @@ function if_statement_to_jsx_child(node, transform_context) {
4752
4846
  const render_if_statement = create_render_if_statement(node, transform_context);
4753
4847
  const conditional_expression = render_if_statement_to_conditional_expression(render_if_statement);
4754
4848
  if (conditional_expression) {
4849
+ // `@if` / `@else` are lowered away: the directive becomes a ternary and
4850
+ // the clause becomes its alternate, so neither keyword has a counterpart
4851
+ // in the output. Anchor the ternary on `@if` and its alternate on
4852
+ // `@else` — `node` is the AUTHORED directive here, before
4853
+ // `create_render_if_statement` rebuilt it.
4854
+ if (conditional_expression.alternate) {
4855
+ conditional_expression.alternate = stamp_directive_origin(
4856
+ conditional_expression.alternate,
4857
+ node.alternateKeyword,
4858
+ '@else',
4859
+ transform_context,
4860
+ );
4861
+ }
4862
+ // The ternary itself cannot carry the anchor: it begins at the same
4863
+ // generated position as its test, so the test's own mapping wins there.
4864
+ // Each ARM is a distinct position, and pointing a directive at the branch
4865
+ // it renders is what the runtime targets already do.
4866
+ conditional_expression.consequent = stamp_directive_origin(
4867
+ conditional_expression.consequent,
4868
+ node,
4869
+ '@if',
4870
+ transform_context,
4871
+ );
4755
4872
  return to_jsx_expression_container(conditional_expression, node);
4756
4873
  }
4757
4874
 
@@ -4761,8 +4878,8 @@ function if_statement_to_jsx_child(node, transform_context) {
4761
4878
  }
4762
4879
 
4763
4880
  /**
4764
- * @param {any} node
4765
- * @returns {any | null}
4881
+ * @param {AST.Node | null | undefined} node
4882
+ * @returns {AST.ConditionalExpression | null}
4766
4883
  */
4767
4884
  function render_if_statement_to_conditional_expression(node) {
4768
4885
  if (!is_if_control_node(node)) return null;
@@ -4770,23 +4887,22 @@ function render_if_statement_to_conditional_expression(node) {
4770
4887
  const consequent = block_statement_to_return_expression(node.consequent);
4771
4888
  if (!consequent) return null;
4772
4889
 
4890
+ /** @type {AST.Expression} */
4773
4891
  let alternate = create_null_literal();
4774
4892
  if (node.alternate) {
4775
- if (is_if_control_node(node.alternate)) {
4776
- alternate = render_if_statement_to_conditional_expression(node.alternate);
4777
- if (!alternate) return null;
4778
- } else {
4779
- alternate = block_statement_to_return_expression(node.alternate);
4780
- if (!alternate) return null;
4781
- }
4893
+ const lowered = is_if_control_node(node.alternate)
4894
+ ? render_if_statement_to_conditional_expression(node.alternate)
4895
+ : block_statement_to_return_expression(node.alternate);
4896
+ if (!lowered) return null;
4897
+ alternate = lowered;
4782
4898
  }
4783
4899
 
4784
4900
  return set_loc(b.conditional(node.test, consequent, alternate), node);
4785
4901
  }
4786
4902
 
4787
4903
  /**
4788
- * @param {any} block
4789
- * @returns {any | null}
4904
+ * @param {AST.Node | null | undefined} block
4905
+ * @returns {AST.Expression | null}
4790
4906
  */
4791
4907
  function block_statement_to_return_expression(block) {
4792
4908
  if (!block || block.type !== 'BlockStatement' || block.body.length === 0) {
@@ -4803,14 +4919,15 @@ function block_statement_to_return_expression(block) {
4803
4919
  return argument;
4804
4920
  }
4805
4921
 
4806
- return create_hook_safe_helper_iife(block.body.slice(0, -1), argument);
4922
+ // The IIFE's trailing value is a render expression here, not an element.
4923
+ return b.call(b.arrow([], b.block([...block.body.slice(0, -1), b.return(argument)])));
4807
4924
  }
4808
4925
 
4809
4926
  /**
4810
4927
  * Find the first `key` attribute expression in the top-level elements of a body.
4811
4928
  * Used to propagate keys from loop body elements to wrapper components.
4812
- * @param {any[]} body_nodes
4813
- * @returns {any | undefined}
4929
+ * @param {AST.Node[]} body_nodes
4930
+ * @returns {AST.Expression | undefined}
4814
4931
  */
4815
4932
  function find_key_expression_in_body(body_nodes) {
4816
4933
  for (const node of body_nodes) {
@@ -4821,11 +4938,13 @@ function find_key_expression_in_body(body_nodes) {
4821
4938
  attr.name?.type === 'JSXIdentifier' &&
4822
4939
  attr.name.name === 'key'
4823
4940
  ) {
4824
- // Value is a JSXExpressionContainer
4941
+ // Value is a JSXExpressionContainer; a comment-only one has no key
4942
+ // expression to propagate.
4825
4943
  if (attr.value?.type === 'JSXExpressionContainer') {
4826
- return attr.value.expression;
4944
+ const { expression } = attr.value;
4945
+ return expression.type === 'JSXEmptyExpression' ? undefined : expression;
4827
4946
  }
4828
- return attr.value;
4947
+ return attr.value ?? undefined;
4829
4948
  }
4830
4949
  }
4831
4950
  }
@@ -4834,8 +4953,8 @@ function find_key_expression_in_body(body_nodes) {
4834
4953
  }
4835
4954
 
4836
4955
  /**
4837
- * @param {any} source_node
4838
- * @returns {any}
4956
+ * @param {AST.Node} source_node
4957
+ * @returns {AST.ReturnStatement}
4839
4958
  */
4840
4959
  function continue_to_bare_return(source_node) {
4841
4960
  const node = set_loc(b.return(create_null_literal()), source_node);
@@ -4852,9 +4971,21 @@ function continue_to_bare_return(source_node) {
4852
4971
  * Returning null from the callback preserves the item-skip behavior while still
4853
4972
  * producing an explicit "render nothing" value for JSX runtimes.
4854
4973
  *
4855
- * @param {any[] | any} node
4974
+ * @overload
4975
+ * @param {AST.Statement[]} node
4856
4976
  * @param {boolean} [is_root]
4857
- * @returns {any[] | any}
4977
+ * @returns {AST.Statement[]}
4978
+ */
4979
+ /**
4980
+ * @overload
4981
+ * @param {AST.Node} node
4982
+ * @param {boolean} [is_root]
4983
+ * @returns {AST.Node}
4984
+ */
4985
+ /**
4986
+ * @param {AST.Node | AST.Node[]} node
4987
+ * @param {boolean} [is_root]
4988
+ * @returns {AST.Node | AST.Node[]}
4858
4989
  */
4859
4990
  export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
4860
4991
  if (Array.isArray(node)) {
@@ -4870,10 +5001,6 @@ export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
4870
5001
  return changed ? result : node;
4871
5002
  }
4872
5003
 
4873
- if (!node || typeof node !== 'object') {
4874
- return node;
4875
- }
4876
-
4877
5004
  if (node.type === 'ContinueStatement') {
4878
5005
  return continue_to_bare_return(node);
4879
5006
  }
@@ -4882,14 +5009,29 @@ export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
4882
5009
  return node;
4883
5010
  }
4884
5011
 
4885
- let out = node;
4886
- for (const key of Object.keys(node)) {
5012
+ const source = /** @type {AST.TraversableAstNode} */ (node);
5013
+ let out = source;
5014
+ for (const key of Object.keys(source)) {
4887
5015
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4888
5016
  continue;
4889
5017
  }
4890
- const walked = rewrite_loop_continues_to_bare_returns(node[key], false);
4891
- if (walked !== node[key]) {
4892
- if (out === node) out = { ...node };
5018
+ const value = source[key];
5019
+ /** @type {unknown} */
5020
+ let walked = value;
5021
+ if (Array.isArray(value)) {
5022
+ let changed = false;
5023
+ const result = value.map((child) => {
5024
+ if (!is_ast_node(child)) return child;
5025
+ const next = rewrite_loop_continues_to_bare_returns(child, false);
5026
+ if (next !== child) changed = true;
5027
+ return next;
5028
+ });
5029
+ if (changed) walked = result;
5030
+ } else if (is_ast_node(value)) {
5031
+ walked = rewrite_loop_continues_to_bare_returns(value, false);
5032
+ }
5033
+ if (walked !== value) {
5034
+ if (out === source) out = { ...source };
4893
5035
  out[key] = walked;
4894
5036
  }
4895
5037
  }
@@ -4898,7 +5040,7 @@ export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
4898
5040
  }
4899
5041
 
4900
5042
  /**
4901
- * @param {any[] | any} node
5043
+ * @param {AST.Node | AST.Node[] | null | undefined} node
4902
5044
  * @param {TransformContext} transform_context
4903
5045
  * @param {boolean} [is_root]
4904
5046
  */
@@ -4914,7 +5056,7 @@ function validate_for_body_control_flow(node, transform_context, is_root = true)
4914
5056
  return;
4915
5057
  }
4916
5058
 
4917
- if (!node || typeof node !== 'object') {
5059
+ if (!node) {
4918
5060
  return;
4919
5061
  }
4920
5062
 
@@ -4957,16 +5099,13 @@ function validate_for_body_control_flow(node, transform_context, is_root = true)
4957
5099
  return;
4958
5100
  }
4959
5101
 
4960
- for (const key of Object.keys(node)) {
4961
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4962
- continue;
4963
- }
4964
- validate_for_body_control_flow(node[key], transform_context, false);
5102
+ for (const child of child_nodes(node)) {
5103
+ validate_for_body_control_flow(child, transform_context, false);
4965
5104
  }
4966
5105
  }
4967
5106
 
4968
5107
  /**
4969
- * @param {any[] | any} node
5108
+ * @param {AST.Node | AST.Node[] | null | undefined} node
4970
5109
  * @param {TransformContext} transform_context
4971
5110
  */
4972
5111
  function validate_if_body_control_flow(node, transform_context) {
@@ -4977,7 +5116,7 @@ function validate_if_body_control_flow(node, transform_context) {
4977
5116
  return;
4978
5117
  }
4979
5118
 
4980
- if (!node || typeof node !== 'object') {
5119
+ if (!node) {
4981
5120
  return;
4982
5121
  }
4983
5122
 
@@ -5016,16 +5155,13 @@ function validate_if_body_control_flow(node, transform_context) {
5016
5155
  return;
5017
5156
  }
5018
5157
 
5019
- for (const key of Object.keys(node)) {
5020
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
5021
- continue;
5022
- }
5023
- validate_if_body_control_flow(node[key], transform_context);
5158
+ for (const child of child_nodes(node)) {
5159
+ validate_if_body_control_flow(child, transform_context);
5024
5160
  }
5025
5161
  }
5026
5162
 
5027
5163
  /**
5028
- * @param {any} node
5164
+ * @param {AST.Node | null | undefined} node
5029
5165
  * @returns {boolean}
5030
5166
  */
5031
5167
  function is_loop_statement(node) {
@@ -5042,7 +5178,7 @@ function is_loop_statement(node) {
5042
5178
  }
5043
5179
 
5044
5180
  /**
5045
- * @param {any} node
5181
+ * @param {AST.ForOfStatement} node
5046
5182
  * @param {TransformContext} transform_context
5047
5183
  * @returns {ESTreeJSX.JSXExpressionContainer}
5048
5184
  */
@@ -5058,9 +5194,8 @@ function for_of_statement_to_jsx_child(node, transform_context) {
5058
5194
  }
5059
5195
 
5060
5196
  const loop_params = get_for_of_iteration_params(node.left, node.index);
5061
- let loop_body = /** @type {any[]} */ (
5062
- node.body.type === 'BlockStatement' ? node.body.body : [node.body]
5063
- );
5197
+ /** @type {AST.Node[]} */
5198
+ let loop_body = node.body.type === 'BlockStatement' ? node.body.body : [node.body];
5064
5199
  validate_for_body_control_flow(loop_body, transform_context);
5065
5200
  const has_hooks =
5066
5201
  should_extract_hook_helpers(transform_context) &&
@@ -5133,7 +5268,7 @@ function for_of_statement_to_jsx_child(node, transform_context) {
5133
5268
  ),
5134
5269
  false,
5135
5270
  undefined,
5136
- node.empty,
5271
+ has_location(node.empty) ? node.empty : undefined,
5137
5272
  ),
5138
5273
  )
5139
5274
  : null;
@@ -5142,9 +5277,28 @@ function for_of_statement_to_jsx_child(node, transform_context) {
5142
5277
  transform_context.needs_for_of_iterable = true;
5143
5278
  const args = [node.right, iter_callback];
5144
5279
  if (empty_fallback) {
5145
- args.push(b.literal(null), b.arrow([], empty_fallback));
5280
+ // `@empty` is a clause: its block starts at `{`, so the parser records
5281
+ // the keyword's own span. Anchor the arm the clause became on it.
5282
+ args.push(
5283
+ b.literal(null),
5284
+ stamp_directive_origin(
5285
+ b.arrow([], empty_fallback),
5286
+ node.emptyKeyword,
5287
+ '@empty',
5288
+ transform_context,
5289
+ ),
5290
+ );
5146
5291
  }
5147
- return to_jsx_expression_container(b.call(b.id(MAP_ITERABLE_INTERNAL_NAME), ...args));
5292
+ // `@for` itself has no counterpart in the output — the directive is
5293
+ // lowered away. Point its keyword at the helper this became, so tooling
5294
+ // can navigate from the authored construct to the code it produced.
5295
+ const helper_id = stamp_directive_origin(
5296
+ b.id(MAP_ITERABLE_INTERNAL_NAME),
5297
+ node,
5298
+ '@for',
5299
+ transform_context,
5300
+ );
5301
+ return to_jsx_expression_container(b.call(helper_id, ...args));
5148
5302
  }
5149
5303
 
5150
5304
  const map_call = b.call(b.member(node.right, create_generated_identifier('map')), iter_callback);
@@ -5169,9 +5323,9 @@ function for_of_statement_to_jsx_child(node, transform_context) {
5169
5323
  * Returns a copy of `body_nodes` where the first keyable element carries the
5170
5324
  * key attribute on a rebuilt opening element — the source nodes are never
5171
5325
  * mutated (they may belong to the caller's parsed AST).
5172
- * @param {any[]} body_nodes
5173
- * @param {any} key_expression
5174
- * @returns {any[]}
5326
+ * @param {AST.Node[]} body_nodes
5327
+ * @param {AST.Expression} key_expression
5328
+ * @returns {AST.Node[]}
5175
5329
  */
5176
5330
  function apply_key_to_loop_body(body_nodes, key_expression) {
5177
5331
  let applied = false;
@@ -5179,13 +5333,7 @@ function apply_key_to_loop_body(body_nodes, key_expression) {
5179
5333
  if (applied || node.type !== 'JSXElement') return node;
5180
5334
  applied = true;
5181
5335
  const attributes = node.openingElement?.attributes || [];
5182
- const has_key = attributes.some(
5183
- (/** @type {any} */ attr) =>
5184
- attr.type === 'JSXAttribute' &&
5185
- attr.name?.type === 'JSXIdentifier' &&
5186
- attr.name.name === 'key',
5187
- );
5188
- if (has_key) return node;
5336
+ if (attributes.some(is_key_attribute)) return node;
5189
5337
  return {
5190
5338
  ...node,
5191
5339
  openingElement: {
@@ -5203,7 +5351,7 @@ function apply_key_to_loop_body(body_nodes, key_expression) {
5203
5351
  }
5204
5352
 
5205
5353
  /**
5206
- * @param {any[]} body_nodes
5354
+ * @param {AST.Node[]} body_nodes
5207
5355
  * @returns {boolean}
5208
5356
  */
5209
5357
  function should_apply_key_to_loop_body(body_nodes) {
@@ -5221,10 +5369,10 @@ function should_apply_key_to_loop_body(body_nodes) {
5221
5369
  * lands on shallow copies; the returned array must be used in place of the
5222
5370
  * argument.
5223
5371
  *
5224
- * @param {any[]} statements
5225
- * @param {any} key_expression
5372
+ * @param {AST.Statement[]} statements
5373
+ * @param {AST.Expression} key_expression
5226
5374
  * @param {TransformContext} transform_context
5227
- * @returns {any[]}
5375
+ * @returns {AST.Statement[]}
5228
5376
  */
5229
5377
  function apply_key_to_render_statements(statements, key_expression, transform_context) {
5230
5378
  for (let i = statements.length - 1; i >= 0; i -= 1) {
@@ -5252,20 +5400,14 @@ function apply_key_to_render_statements(statements, key_expression, transform_co
5252
5400
  }
5253
5401
 
5254
5402
  /**
5255
- * @param {any} element
5256
- * @param {any} key_expression
5257
- * @returns {any} the element itself when it already has a `key`, otherwise a
5258
- * shallow copy with the key attribute appended.
5403
+ * @param {AST.TSRXJSXElement} element
5404
+ * @param {AST.Expression} key_expression
5405
+ * @returns {AST.TSRXJSXElement} the element itself when it already has a `key`,
5406
+ * otherwise a shallow copy with the key attribute appended.
5259
5407
  */
5260
5408
  function apply_key_to_jsx_element(element, key_expression) {
5261
5409
  const attributes = element.openingElement?.attributes || [];
5262
- const has_key = attributes.some(
5263
- (/** @type {any} */ attr) =>
5264
- attr.type === 'JSXAttribute' &&
5265
- attr.name?.type === 'JSXIdentifier' &&
5266
- attr.name.name === 'key',
5267
- );
5268
- if (has_key) return element;
5410
+ if (attributes.some(is_key_attribute)) return element;
5269
5411
 
5270
5412
  return {
5271
5413
  ...element,
@@ -5283,9 +5425,19 @@ function apply_key_to_jsx_element(element, key_expression) {
5283
5425
  }
5284
5426
 
5285
5427
  /**
5286
- * @param {any} fragment
5287
- * @param {any} key_expression
5288
- * @returns {any}
5428
+ * @param {ESTreeJSX.JSXAttributeNode} attr
5429
+ * @returns {boolean}
5430
+ */
5431
+ function is_key_attribute(attr) {
5432
+ return (
5433
+ attr.type === 'JSXAttribute' && attr.name?.type === 'JSXIdentifier' && attr.name.name === 'key'
5434
+ );
5435
+ }
5436
+
5437
+ /**
5438
+ * @param {AST.TSRXJSXFragment} fragment
5439
+ * @param {AST.Expression} key_expression
5440
+ * @returns {AST.TSRXJSXElement}
5289
5441
  */
5290
5442
  function keyed_fragment_to_jsx_element(fragment, key_expression) {
5291
5443
  const name = b.jsx_id('Fragment');
@@ -5302,7 +5454,7 @@ function keyed_fragment_to_jsx_element(fragment, key_expression) {
5302
5454
  }
5303
5455
 
5304
5456
  /**
5305
- * @param {any} node
5457
+ * @param {AST.SwitchStatement} node
5306
5458
  * @param {TransformContext} transform_context
5307
5459
  * @returns {ESTreeJSX.JSXExpressionContainer}
5308
5460
  */
@@ -5325,7 +5477,7 @@ function switch_statement_to_jsx_child(node, transform_context) {
5325
5477
  * - both → ErrorBoundary wraps Suspense
5326
5478
  * - JavaScript `try/finally` is not part of component template control flow
5327
5479
  *
5328
- * @param {any} node
5480
+ * @param {AST.TryStatement} node
5329
5481
  * @param {TransformContext} transform_context
5330
5482
  * @returns {ESTreeJSX.JSXExpressionContainer}
5331
5483
  */
@@ -5369,7 +5521,7 @@ function try_statement_to_jsx_child(node, transform_context) {
5369
5521
  const try_body_nodes = node.block.body || [];
5370
5522
  const try_content = statement_body_to_jsx_child(try_body_nodes, transform_context);
5371
5523
 
5372
- /** @type {any} */
5524
+ /** @type {ESTreeJSX.JSXRenderNode} */
5373
5525
  let result = try_content;
5374
5526
 
5375
5527
  // Wrap in <Suspense> if pending block exists
@@ -5381,18 +5533,31 @@ function try_statement_to_jsx_child(node, transform_context) {
5381
5533
  ? to_jsx_expression_container(create_null_literal())
5382
5534
  : statement_body_to_jsx_child(pending_body_nodes, transform_context);
5383
5535
 
5384
- result =
5385
- transform_context.platform.hooks?.createPendingBoundary?.(
5386
- result,
5387
- fallback_content,
5536
+ const custom_pending = transform_context.platform.hooks?.createPendingBoundary?.(
5537
+ result,
5538
+ fallback_content,
5539
+ transform_context,
5540
+ node,
5541
+ );
5542
+ if (custom_pending != null) {
5543
+ result = custom_pending;
5544
+ } else {
5545
+ // The keyword the fallback came from, and — since `<Suspense>` is the
5546
+ // outermost thing `@try` produced unless a `@catch` wraps it below —
5547
+ // the keyword the boundary itself came from.
5548
+ const fallback_name = stamp_directive_origin(
5549
+ b.jsx_id('fallback'),
5550
+ node.pendingKeyword,
5551
+ '@pending',
5388
5552
  transform_context,
5389
- node,
5390
- ) ??
5391
- create_jsx_element(
5553
+ );
5554
+ result = create_jsx_element(
5392
5555
  'Suspense',
5393
- [b.jsx_attribute(b.jsx_id('fallback'), fallback_content)],
5556
+ [b.jsx_attribute(fallback_name, fallback_content)],
5394
5557
  [result],
5395
5558
  );
5559
+ stamp_directive_origin(result.openingElement.name, node, '@try', transform_context);
5560
+ }
5396
5561
  }
5397
5562
 
5398
5563
  // Wrap in <TsrxErrorBoundary> if catch block exists
@@ -5423,10 +5588,13 @@ function try_statement_to_jsx_child(node, transform_context) {
5423
5588
 
5424
5589
  const fallback_fn = b.arrow(
5425
5590
  catch_params,
5426
- b.block(build_render_statements(catch_body_nodes, true, transform_context), handler.body),
5591
+ b.block(
5592
+ build_render_statements(catch_body_nodes, true, transform_context),
5593
+ has_location(handler.body) ? handler.body : undefined,
5594
+ ),
5427
5595
  false,
5428
5596
  undefined,
5429
- handler,
5597
+ has_location(handler) ? handler : undefined,
5430
5598
  );
5431
5599
 
5432
5600
  const fallback_component =
@@ -5468,19 +5636,28 @@ function try_statement_to_jsx_child(node, transform_context) {
5468
5636
 
5469
5637
  return result;
5470
5638
  } else {
5639
+ const fallback_name = stamp_directive_origin(
5640
+ b.jsx_id('fallback'),
5641
+ node.handlerKeyword,
5642
+ '@catch',
5643
+ transform_context,
5644
+ );
5471
5645
  result = create_jsx_element(
5472
5646
  'TsrxErrorBoundary',
5473
5647
  [
5474
- b.jsx_attribute(
5475
- b.jsx_id('fallback'),
5476
- to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
5477
- ),
5648
+ b.jsx_attribute(fallback_name, to_jsx_expression_container(fallback_fn)),
5478
5649
  ...(boundary_content
5479
5650
  ? [b.jsx_attribute(b.jsx_id('content'), to_jsx_expression_container(boundary_content))]
5480
5651
  : []),
5481
5652
  ],
5482
5653
  boundary_content ? [] : [result],
5483
5654
  );
5655
+ // `@try` names the OUTERMOST boundary it produced. With a `@pending`
5656
+ // that is the `<Suspense>` stamped above, and this element merely
5657
+ // wraps it; without one, the error boundary is what `@try` became.
5658
+ if (!pending) {
5659
+ stamp_directive_origin(result.openingElement.name, node, '@try', transform_context);
5660
+ }
5484
5661
  }
5485
5662
  }
5486
5663
 
@@ -5490,7 +5667,9 @@ function try_statement_to_jsx_child(node, transform_context) {
5490
5667
  return to_jsx_expression_container(result);
5491
5668
  }
5492
5669
 
5493
- return result;
5670
+ return result.type === 'JSXExpressionContainer'
5671
+ ? result
5672
+ : to_jsx_expression_container(/** @type {AST.Expression} */ (result));
5494
5673
  }
5495
5674
 
5496
5675
  /**
@@ -5498,8 +5677,8 @@ function try_statement_to_jsx_child(node, transform_context) {
5498
5677
  *
5499
5678
  * @param {string} tag_name
5500
5679
  * @param {ESTreeJSX.JSXAttributeNode[]} attributes
5501
- * @param {ESTreeJSX.JSXElement['children']} children
5502
- * @returns {ESTreeJSX.JSXElement}
5680
+ * @param {AST.TSRXJSXElement['children']} children
5681
+ * @returns {AST.TSRXJSXElement}
5503
5682
  */
5504
5683
  function create_jsx_element(tag_name, attributes, children) {
5505
5684
  const self_closing = children.length === 0;
@@ -5711,8 +5890,8 @@ function summarize_switch_case_body(consequent) {
5711
5890
  * lifted function) keeps the source position so editor IntelliSense doesn't
5712
5891
  * see double/triple hits per source range.
5713
5892
  *
5714
- * @param {{ component_element: ESTreeJSX.JSXElement }} helper
5715
- * @returns {ESTreeJSX.JSXElement}
5893
+ * @param {{ component_element: AST.TSRXJSXElement }} helper
5894
+ * @returns {AST.TSRXJSXElement}
5716
5895
  */
5717
5896
  export function clone_switch_helper_invocation(helper) {
5718
5897
  return clone_ast_node(helper.component_element, false);
@@ -5773,10 +5952,11 @@ export function plan_switch_lift(switch_node, transform_context) {
5773
5952
  if (!needs_helper[i]) continue;
5774
5953
  const { own_body } = case_info[i];
5775
5954
 
5955
+ const source_case = switch_node.cases[i];
5776
5956
  case_helpers[i] = create_hook_safe_helper(
5777
5957
  own_body,
5778
5958
  undefined,
5779
- switch_node.cases[i],
5959
+ has_location(source_case) ? source_case : undefined,
5780
5960
  transform_context,
5781
5961
  /** @type {AST.Identifier} */ (helper_ids[i]),
5782
5962
  );
@@ -5836,6 +6016,7 @@ function build_switch_with_lift(switch_node, transform_context) {
5836
6016
  /** @type {AST.Statement[]} */
5837
6017
  const case_body = [];
5838
6018
  /** @type {ESTreeJSX.JSXElement['children']} */
6019
+ /** @type {ESTreeJSX.JSXRenderChild[]} */
5839
6020
  const render_nodes = [];
5840
6021
  let has_terminal = false;
5841
6022
 
@@ -5853,11 +6034,7 @@ function build_switch_with_lift(switch_node, transform_context) {
5853
6034
  break;
5854
6035
  }
5855
6036
  if (is_render_child_node(child)) {
5856
- render_nodes.push(
5857
- /** @type {ESTreeJSX.JSXElement['children'][number]} */ (
5858
- to_jsx_child(child, transform_context)
5859
- ),
5860
- );
6037
+ render_nodes.push(to_jsx_child(child, transform_context));
5861
6038
  } else if (is_bare_render_expression(child)) {
5862
6039
  render_nodes.push(to_jsx_expression_container(child, child));
5863
6040
  } else {
@@ -5904,7 +6081,7 @@ function create_null_return_statement() {
5904
6081
 
5905
6082
  /**
5906
6083
  * @param {AST.Expression} expression
5907
- * @param {AST.Node} [source_node]
6084
+ * @param {AST.Node | AST.NodeWithLocation} [source_node]
5908
6085
  * @returns {ESTreeJSX.JSXExpressionContainer}
5909
6086
  */
5910
6087
  function to_jsx_expression_container(expression, source_node = expression) {
@@ -6103,9 +6280,9 @@ function collect_jsx_setup_declarations(value, seen, declarations) {
6103
6280
  }
6104
6281
 
6105
6282
  /**
6106
- * @param {any} expression
6283
+ * @param {AST.Expression | ESTreeJSX.JSXExpressionContainer} expression
6107
6284
  * @param {boolean} in_jsx_child
6108
- * @returns {any}
6285
+ * @returns {AST.Expression | ESTreeJSX.JSXExpressionContainer}
6109
6286
  */
6110
6287
  function wrap_jsx_setup_declarations(expression, in_jsx_child) {
6111
6288
  const declarations = extract_jsx_setup_declarations(expression);
@@ -6114,14 +6291,15 @@ function wrap_jsx_setup_declarations(expression, in_jsx_child) {
6114
6291
  }
6115
6292
 
6116
6293
  const return_expression =
6117
- expression?.type === 'JSXExpressionContainer' ? expression.expression : expression;
6294
+ expression.type === 'JSXExpressionContainer' ? expression.expression : expression;
6295
+ const expression_loc = has_location(expression) ? expression : undefined;
6118
6296
  const call = b.call(
6119
6297
  b.arrow(
6120
6298
  [],
6121
- b.block([...declarations, b.return(return_expression)], expression),
6299
+ b.block([...declarations, b.return(return_expression)], expression_loc),
6122
6300
  false,
6123
6301
  undefined,
6124
- expression,
6302
+ expression_loc,
6125
6303
  ),
6126
6304
  );
6127
6305
 
@@ -6283,6 +6461,52 @@ export const MERGE_REFS_INTERNAL_NAME = '__mergeRefs';
6283
6461
  export const NORMALIZE_SPREAD_PROPS_INTERNAL_NAME = '__normalize_spread_props';
6284
6462
  export const NORMALIZE_SPREAD_PROPS_FOR_REF_ATTR_INTERNAL_NAME =
6285
6463
  '__normalize_spread_props_for_ref_attr';
6464
+ /**
6465
+ * Point a generated node at the DIRECTIVE KEYWORD that produced it.
6466
+ *
6467
+ * A template directive is lowered away entirely — `@for` becomes a
6468
+ * `map_iterable` call, `@if` a conditional — so the keyword the author actually
6469
+ * wrote has no counterpart in the output and nothing in the source map reaches
6470
+ * it. Navigation tooling (the playground's compiled-output pane; anything else
6471
+ * tracing authored syntax to emitted code) therefore cannot resolve a cursor
6472
+ * placed on it.
6473
+ *
6474
+ * Opt-in via `options.inspect`. With the flag clear this returns the node
6475
+ * untouched, so the emitted bytes, the print's source map and every Volar
6476
+ * mapping derived from it are exactly what they were — the editor pipeline is
6477
+ * unaffected by construction.
6478
+ *
6479
+ * Only ONE identifying token per construct is stamped (the helper's callee, an
6480
+ * arm's identifier). Stamping a whole subtree would give the keyword a span
6481
+ * wide enough to shadow the finer mappings inside it, which is precisely the
6482
+ * failure the synthesized-`return` clamp used to produce.
6483
+ *
6484
+ * @template {AST.Node} T
6485
+ * @param {T} node the generated node to anchor
6486
+ * @param {AST.Node | AST.NodeWithLocation | null | undefined} directive the authored
6487
+ * directive node; its `start`/`loc.start` IS the keyword
6488
+ * @param {string} keyword the authored spelling, `@` included
6489
+ * @param {TransformContext} transform_context
6490
+ * @returns {T}
6491
+ */
6492
+ function stamp_directive_origin(node, directive, keyword, transform_context) {
6493
+ if (!transform_context.inspect) return node;
6494
+ const start = directive?.start;
6495
+ const loc_start = directive?.loc?.start;
6496
+ if (typeof start !== 'number' || !loc_start) return node;
6497
+ // Confirm the authored spelling before claiming those bytes: the same
6498
+ // lowering also runs for constructs the author never wrote as a directive.
6499
+ if (transform_context.source?.startsWith(keyword, start) !== true) return node;
6500
+
6501
+ node.start = start;
6502
+ node.end = start + keyword.length;
6503
+ node.loc = {
6504
+ start: { ...loc_start },
6505
+ end: { ...loc_start, column: loc_start.column + keyword.length },
6506
+ };
6507
+ return node;
6508
+ }
6509
+
6286
6510
  export const MAP_ITERABLE_INTERNAL_NAME = '__map_iterable';
6287
6511
  export const ITERATION_VALUE_INTERNAL_NAME = '__IterationValue';
6288
6512