@tsrx/core 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,6 +21,7 @@ import {
21
21
  get_for_of_iteration_params,
22
22
  identifier_to_jsx_name,
23
23
  is_bare_render_expression,
24
+ is_component_jsx_name,
24
25
  is_dynamic_element_id,
25
26
  is_jsx_child,
26
27
  set_loc,
@@ -57,6 +58,79 @@ import {
57
58
  } from '../jsx-interleave.js';
58
59
  import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
59
60
 
61
+ const HOOK_OUTER_ASSIGNMENT_ERROR =
62
+ 'Hook calls inside conditional or repeated TSRX scopes must keep their results local to the generated hook component.';
63
+ const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
64
+ 'Hook callbacks inside conditional or repeated TSRX scopes must not mutate bindings declared outside the generated hook component.';
65
+ const TEMPLATE_FRAGMENT_ERROR =
66
+ 'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
67
+
68
+ /**
69
+ * @param {AST.Node} node
70
+ * @param {TransformContext} transform_context
71
+ */
72
+ function report_html_template_unsupported_error(node, transform_context) {
73
+ // this should be a fatal error so we don't pass the errors collection,
74
+ // since we don't have a transform for the Html node
75
+ error(
76
+ `\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
77
+ transform_context.filename,
78
+ node,
79
+ );
80
+ }
81
+
82
+ /**
83
+ * @param {AST.Node} node
84
+ * @param {TransformContext} transform_context
85
+ */
86
+ function report_jsx_fragment_in_tsrx_error(node, transform_context) {
87
+ error(
88
+ TEMPLATE_FRAGMENT_ERROR,
89
+ transform_context.filename,
90
+ node,
91
+ transform_context.errors,
92
+ transform_context.comments,
93
+ );
94
+ }
95
+
96
+ /**
97
+ * @param {AST.Node} node
98
+ * @param {string[]} names
99
+ * @param {string} hook_name
100
+ * @param {TransformContext} transform_context
101
+ * @returns {void}
102
+ */
103
+ function report_hook_outer_assignment_error(node, names, hook_name, transform_context) {
104
+ const target =
105
+ names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
106
+ error(
107
+ `${HOOK_OUTER_ASSIGNMENT_ERROR} The ${hook_name} result is assigned to ${target}, which is declared outside that generated component. Declare the hook result inside the TSRX branch, or move the hook into an explicit child component and pass values with props.`,
108
+ transform_context.filename,
109
+ node,
110
+ transform_context.errors,
111
+ transform_context.comments,
112
+ );
113
+ }
114
+
115
+ /**
116
+ * @param {AST.Node} node
117
+ * @param {string[]} names
118
+ * @param {string} hook_name
119
+ * @param {TransformContext} transform_context
120
+ * @returns {void}
121
+ */
122
+ function report_hook_callback_outer_mutation_error(node, names, hook_name, transform_context) {
123
+ const target =
124
+ names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
125
+ error(
126
+ `${HOOK_CALLBACK_OUTER_MUTATION_ERROR} The ${hook_name} callback mutates ${target}. Read outer values through props or dependencies, and move mutable state into an explicit child component when it needs to change over time.`,
127
+ transform_context.filename,
128
+ node,
129
+ transform_context.errors,
130
+ transform_context.comments,
131
+ );
132
+ }
133
+
60
134
  /**
61
135
  * Local alias for the shared `JsxTransformContext`. Kept as a typedef so the
62
136
  * rest of this file's `@param {TransformContext}` annotations don't all have
@@ -172,6 +246,8 @@ export function createJsxTransform(platform) {
172
246
  needs_ref_prop: false,
173
247
  needs_normalize_spread_props: false,
174
248
  needs_fragment: false,
249
+ needs_for_of_iterable: false,
250
+ needs_iteration_value_type: false,
175
251
  module_scoped_hook_components:
176
252
  options?.moduleScopedHookComponents ?? !!platform.hooks?.moduleScopedHookComponents,
177
253
  helper_state: null,
@@ -442,16 +518,19 @@ export function createJsxTransform(platform) {
442
518
  const value = state.current_css_hash
443
519
  ? `${state.current_css_hash} ${class_name}`
444
520
  : class_name;
445
- return /** @type {any} */ (b.literal(value, node));
521
+ return b.literal(value, undefined, node);
446
522
  },
447
523
 
448
524
  // Default .metadata on every function-like node so downstream consumers
449
525
  // (e.g. segments.js reading node.value.metadata.is_component on class
450
526
  // methods) don't trip on an undefined metadata object. Ripple's analyze
451
527
  // phase does this via visit_function; tsrx-react has no analyze phase.
452
- FunctionDeclaration: ensure_function_metadata,
453
- FunctionExpression: ensure_function_metadata,
454
- ArrowFunctionExpression: ensure_function_metadata,
528
+ // If a plain JS function contains a hook-bearing <tsrx> expression,
529
+ // give it a temporary helper scope so extracted hook components can
530
+ // be emitted with stable identities just like component-body helpers.
531
+ FunctionDeclaration: transform_function_with_hook_helpers,
532
+ FunctionExpression: transform_function_with_hook_helpers,
533
+ ArrowFunctionExpression: transform_function_with_hook_helpers,
455
534
 
456
535
  RefExpression(node) {
457
536
  return create_ref_prop_call(node, transform_context);
@@ -590,11 +669,7 @@ export function component_to_function_declaration(component, transform_context,
590
669
  // Wrap body_statements in a BlockStatement so that apply_lazy_transforms
591
670
  // runs collect_block_shadowed_names and detects body-level declarations
592
671
  // (e.g. `const name = ...`) that shadow lazy binding names.
593
- const body_block = /** @type {any} */ ({
594
- type: 'BlockStatement',
595
- body: body_statements,
596
- metadata: { path: [] },
597
- });
672
+ const body_block = b.block(body_statements);
598
673
  const final_body =
599
674
  lazy_bindings.size > 0 ? apply_lazy_transforms(body_block, lazy_bindings) : body_block;
600
675
 
@@ -602,48 +677,19 @@ export function component_to_function_declaration(component, transform_context,
602
677
  let fn;
603
678
 
604
679
  if (component.id) {
605
- fn = /** @type {any} */ ({
606
- type: 'FunctionDeclaration',
607
- id: component.id,
608
- typeParameters: component.typeParameters,
609
- params: final_params,
610
- body: final_body,
611
- async: is_async_component,
612
- generator: false,
613
- metadata: {
614
- path: [],
615
- is_component: true,
616
- },
617
- });
680
+ fn = b.function_declaration(
681
+ component.id,
682
+ final_params,
683
+ final_body,
684
+ is_async_component,
685
+ component.typeParameters,
686
+ );
618
687
  } else if (component.metadata?.arrow) {
619
- fn = /** @type {any} */ ({
620
- type: 'ArrowFunctionExpression',
621
- typeParameters: component.typeParameters,
622
- params: final_params,
623
- body: final_body,
624
- async: is_async_component,
625
- generator: false,
626
- expression: false,
627
- metadata: {
628
- path: [],
629
- is_component: true,
630
- },
631
- });
688
+ fn = b.arrow(final_params, final_body, is_async_component, component.typeParameters);
632
689
  } else {
633
- fn = /** @type {any} */ ({
634
- type: 'FunctionExpression',
635
- id: null,
636
- typeParameters: component.typeParameters,
637
- params: final_params,
638
- body: final_body,
639
- async: is_async_component,
640
- generator: false,
641
- metadata: {
642
- path: [],
643
- is_component: true,
644
- },
645
- });
690
+ fn = b.function(null, final_params, final_body, is_async_component, component.typeParameters);
646
691
  }
692
+ /** @type {any} */ (fn.metadata).is_component = true;
647
693
 
648
694
  // Restore context
649
695
  transform_context.helper_state = saved_helper_state;
@@ -813,25 +859,16 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
813
859
  if (stmt.type === 'ReturnStatement') {
814
860
  if (stmt.argument) {
815
861
  render_nodes.push(
816
- /** @type {any} */ ({
817
- type: 'JSXExpressionContainer',
818
- expression: set_loc(
819
- /** @type {any} */ ({
820
- type: 'ConditionalExpression',
821
- test: clone_expression_node(child.test),
822
- consequent: {
823
- type: 'Literal',
824
- value: null,
825
- raw: 'null',
826
- metadata: { path: [] },
827
- },
828
- alternate: stmt.argument,
829
- metadata: { path: [] },
830
- }),
862
+ b.jsx_expression_container(
863
+ set_loc(
864
+ b.conditional(
865
+ clone_expression_node(child.test),
866
+ b.literal(null),
867
+ stmt.argument,
868
+ ),
831
869
  child,
832
870
  ),
833
- metadata: { path: [] },
834
- }),
871
+ ),
835
872
  );
836
873
  }
837
874
  } else {
@@ -852,26 +889,6 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
852
889
  continue;
853
890
  }
854
891
 
855
- if (
856
- child.type === 'IfStatement' &&
857
- !child.alternate &&
858
- !is_returning_if_statement(child) &&
859
- !transform_context.platform.hooks?.isTopLevelSetupCall &&
860
- body_contains_top_level_hook_call([child], transform_context, true) &&
861
- i + 1 < body_nodes.length
862
- ) {
863
- statements.push(
864
- ...create_continuation_lift_if_statement(
865
- child,
866
- body_nodes.slice(i + 1),
867
- render_nodes,
868
- transform_context,
869
- ),
870
- );
871
- transform_context.available_bindings = saved_bindings;
872
- return statements;
873
- }
874
-
875
892
  if (
876
893
  child.type === 'ForOfStatement' &&
877
894
  !child.await &&
@@ -883,26 +900,9 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
883
900
  true,
884
901
  )
885
902
  ) {
886
- const for_of_continuation = body_nodes.slice(i + 1);
887
- const hoisted = build_hoisted_for_of_with_hooks(
888
- child,
889
- for_of_continuation,
890
- transform_context,
891
- );
903
+ const hoisted = build_hoisted_for_of_with_hooks(child, transform_context);
892
904
  if (hoisted) {
893
905
  statements.push(...hoisted.hoist_statements);
894
- if (for_of_continuation.length > 0) {
895
- // Tail was lifted into the helper; everything after the for-of
896
- // now lives there. Combine prior render_nodes with the iteration
897
- // JSX and return.
898
- statements.push({
899
- type: 'ReturnStatement',
900
- argument: combine_render_return_argument(render_nodes, hoisted.jsx_child),
901
- metadata: { path: [] },
902
- });
903
- transform_context.available_bindings = saved_bindings;
904
- return statements;
905
- }
906
906
  if (interleaved && is_capturable_jsx_child(hoisted.jsx_child)) {
907
907
  const { declaration, reference } = captureJsxChild(hoisted.jsx_child, capture_index++);
908
908
  statements.push(declaration);
@@ -914,43 +914,6 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
914
914
  }
915
915
  }
916
916
 
917
- if (
918
- child.type === 'TryStatement' &&
919
- !child.finalizer &&
920
- !transform_context.platform.hooks?.isTopLevelSetupCall &&
921
- try_statement_contains_hooks(child, transform_context) &&
922
- i + 1 < body_nodes.length
923
- ) {
924
- statements.push(
925
- ...create_continuation_lift_try_statement(
926
- child,
927
- body_nodes.slice(i + 1),
928
- render_nodes,
929
- transform_context,
930
- ),
931
- );
932
- transform_context.available_bindings = saved_bindings;
933
- return statements;
934
- }
935
-
936
- if (
937
- child.type === 'SwitchStatement' &&
938
- !transform_context.platform.hooks?.isTopLevelSetupCall &&
939
- body_contains_top_level_hook_call([child], transform_context, true) &&
940
- i + 1 < body_nodes.length
941
- ) {
942
- statements.push(
943
- ...create_continuation_lift_switch_statement(
944
- child,
945
- body_nodes.slice(i + 1),
946
- render_nodes,
947
- transform_context,
948
- ),
949
- );
950
- transform_context.available_bindings = saved_bindings;
951
- return statements;
952
- }
953
-
954
917
  if (is_jsx_child(child)) {
955
918
  const jsx = to_jsx_child(child, transform_context);
956
919
  statements.push(...extract_jsx_setup_declarations(jsx));
@@ -975,10 +938,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
975
938
 
976
939
  const return_arg = build_return_expression(render_nodes);
977
940
  if (return_arg || (return_null_when_empty && !has_terminal_return)) {
978
- statements.push({
979
- type: 'ReturnStatement',
980
- argument: return_arg || { type: 'Literal', value: null, raw: 'null' },
981
- });
941
+ statements.push(b.return(return_arg || b.literal(null)));
982
942
  }
983
943
 
984
944
  transform_context.available_bindings = saved_bindings;
@@ -1178,16 +1138,7 @@ function create_helper_props_property(binding) {
1178
1138
  const key = create_generated_identifier(binding.name);
1179
1139
  const value = create_generated_identifier(binding.name);
1180
1140
 
1181
- return /** @type {any} */ ({
1182
- type: 'Property',
1183
- key,
1184
- value,
1185
- kind: 'init',
1186
- method: false,
1187
- shorthand: true,
1188
- computed: false,
1189
- metadata: { path: [] },
1190
- });
1141
+ return b.prop('init', key, value, false, true);
1191
1142
  }
1192
1143
 
1193
1144
  /**
@@ -1250,6 +1201,156 @@ function create_helper_state(base_name) {
1250
1201
  };
1251
1202
  }
1252
1203
 
1204
+ /**
1205
+ * @param {any} node
1206
+ * @param {{ next: () => any, state: TransformContext }} context
1207
+ * @returns {any}
1208
+ */
1209
+ function transform_function_with_hook_helpers(node, { next, state }) {
1210
+ if (state.helper_state || !function_contains_hook_bearing_tsrx(node, state)) {
1211
+ return ensure_function_metadata(node, { next });
1212
+ }
1213
+
1214
+ const helper_state = create_helper_state(get_function_helper_base_name(node));
1215
+ const saved_helper_state = state.helper_state;
1216
+ const saved_bindings = state.available_bindings;
1217
+
1218
+ state.helper_state = helper_state;
1219
+ state.available_bindings = collect_function_scope_bindings(node);
1220
+
1221
+ const inner = /** @type {any} */ (next() ?? node);
1222
+
1223
+ state.helper_state = saved_helper_state;
1224
+ state.available_bindings = saved_bindings;
1225
+
1226
+ ensure_function_metadata(inner, { next: () => inner });
1227
+ if (helper_state.helpers.length || helper_state.statics.length) {
1228
+ inner.metadata = {
1229
+ ...(inner.metadata || {}),
1230
+ generated_helpers: helper_state.helpers,
1231
+ generated_statics: helper_state.statics,
1232
+ };
1233
+ }
1234
+
1235
+ return inner;
1236
+ }
1237
+
1238
+ /**
1239
+ * @param {any} node
1240
+ * @returns {string}
1241
+ */
1242
+ function get_function_helper_base_name(node) {
1243
+ if (node.id?.type === 'Identifier') {
1244
+ return node.id.name;
1245
+ }
1246
+ return 'Tsrx';
1247
+ }
1248
+
1249
+ /**
1250
+ * @param {any} node
1251
+ * @returns {Map<string, AST.Identifier>}
1252
+ */
1253
+ function collect_function_scope_bindings(node) {
1254
+ const bindings = collect_param_bindings(node.params || []);
1255
+ collect_descendant_declaration_bindings(node.body, bindings);
1256
+ return bindings;
1257
+ }
1258
+
1259
+ /**
1260
+ * @param {any} node
1261
+ * @param {Map<string, AST.Identifier>} bindings
1262
+ * @returns {void}
1263
+ */
1264
+ function collect_descendant_declaration_bindings(node, bindings) {
1265
+ if (!node || typeof node !== 'object') {
1266
+ return;
1267
+ }
1268
+
1269
+ if (node.type === 'VariableDeclaration') {
1270
+ for (const declaration of node.declarations || []) {
1271
+ collect_pattern_bindings(declaration.id, bindings);
1272
+ }
1273
+ }
1274
+
1275
+ if (
1276
+ (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') &&
1277
+ node.id?.type === 'Identifier'
1278
+ ) {
1279
+ bindings.set(node.id.name, node.id);
1280
+ }
1281
+
1282
+ if (
1283
+ node.type === 'FunctionDeclaration' ||
1284
+ node.type === 'FunctionExpression' ||
1285
+ node.type === 'ArrowFunctionExpression' ||
1286
+ node.type === 'Component'
1287
+ ) {
1288
+ return;
1289
+ }
1290
+
1291
+ if (Array.isArray(node)) {
1292
+ for (const child of node) {
1293
+ collect_descendant_declaration_bindings(child, bindings);
1294
+ }
1295
+ return;
1296
+ }
1297
+
1298
+ for (const key of Object.keys(node)) {
1299
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1300
+ continue;
1301
+ }
1302
+ collect_descendant_declaration_bindings(node[key], bindings);
1303
+ }
1304
+ }
1305
+
1306
+ /**
1307
+ * @param {any} node
1308
+ * @param {TransformContext} transform_context
1309
+ * @returns {boolean}
1310
+ */
1311
+ function function_contains_hook_bearing_tsrx(node, transform_context) {
1312
+ return node_contains_hook_bearing_tsrx(node.body, transform_context);
1313
+ }
1314
+
1315
+ /**
1316
+ * @param {any} node
1317
+ * @param {TransformContext} transform_context
1318
+ * @returns {boolean}
1319
+ */
1320
+ function node_contains_hook_bearing_tsrx(node, transform_context) {
1321
+ if (!node || typeof node !== 'object') {
1322
+ return false;
1323
+ }
1324
+
1325
+ if (Array.isArray(node)) {
1326
+ return node.some((child) => node_contains_hook_bearing_tsrx(child, transform_context));
1327
+ }
1328
+
1329
+ if (node.type === 'Tsrx') {
1330
+ return body_contains_top_level_hook_call(node.children || [], transform_context, true);
1331
+ }
1332
+
1333
+ if (
1334
+ node.type === 'FunctionDeclaration' ||
1335
+ node.type === 'FunctionExpression' ||
1336
+ node.type === 'ArrowFunctionExpression' ||
1337
+ node.type === 'Component'
1338
+ ) {
1339
+ return false;
1340
+ }
1341
+
1342
+ for (const key of Object.keys(node)) {
1343
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1344
+ continue;
1345
+ }
1346
+ if (node_contains_hook_bearing_tsrx(node[key], transform_context)) {
1347
+ return true;
1348
+ }
1349
+ }
1350
+
1351
+ return false;
1352
+ }
1353
+
1253
1354
  /**
1254
1355
  * @param {TransformContext} transform_context
1255
1356
  * @returns {boolean}
@@ -1273,7 +1374,7 @@ function create_module_scoped_hook_component_id(helper_id, transform_context) {
1273
1374
  * @param {any[]} params
1274
1375
  * @returns {Map<string, AST.Identifier>}
1275
1376
  */
1276
- function collect_param_bindings(params) {
1377
+ export function collect_param_bindings(params) {
1277
1378
  const bindings = new Map();
1278
1379
  for (const param of params) {
1279
1380
  collect_pattern_bindings(param, bindings);
@@ -1286,7 +1387,7 @@ function collect_param_bindings(params) {
1286
1387
  * @param {Map<string, AST.Identifier>} bindings
1287
1388
  * @returns {void}
1288
1389
  */
1289
- function collect_statement_bindings(statement, bindings) {
1390
+ export function collect_statement_bindings(statement, bindings) {
1290
1391
  if (!statement) return;
1291
1392
 
1292
1393
  if (statement.type === 'VariableDeclaration') {
@@ -1420,6 +1521,16 @@ function hoist_static_render_nodes(render_nodes, transform_context) {
1420
1521
  const node = render_nodes[i];
1421
1522
  if (node.type !== 'JSXElement') continue;
1422
1523
  if (!is_hoist_safe_jsx_node(node)) continue;
1524
+ if (is_bare_component_invocation(node)) {
1525
+ // `<Helper />` with no attributes and no children is just an
1526
+ // invocation reference — most often a generated `StatementBodyHook`
1527
+ // chain element we emitted ourselves. Hoisting it would produce
1528
+ // `const App__staticN = <Helper />` aliases that bloat the output
1529
+ // without enabling React's element-identity fast path (the helper
1530
+ // isn't memoized, so the parent re-invokes it every render either
1531
+ // way). Inline the reference at the call site instead.
1532
+ continue;
1533
+ }
1423
1534
  if (
1424
1535
  transform_context.platform.hooks?.canHoistStaticNode &&
1425
1536
  !transform_context.platform.hooks.canHoistStaticNode(node, transform_context)
@@ -1437,6 +1548,23 @@ function hoist_static_render_nodes(render_nodes, transform_context) {
1437
1548
  }
1438
1549
  }
1439
1550
 
1551
+ /**
1552
+ * `<Helper />` shape with no attributes and no children. The opening element
1553
+ * name must be component-shaped (see `is_component_jsx_name`) — lowercase
1554
+ * identifiers are host DOM tags, which *do* benefit from hoisting because
1555
+ * React diffs them against the previous render.
1556
+ *
1557
+ * @param {any} node
1558
+ * @returns {boolean}
1559
+ */
1560
+ function is_bare_component_invocation(node) {
1561
+ if (!node || node.type !== 'JSXElement') return false;
1562
+ const opening = node.openingElement;
1563
+ if (!opening || opening.attributes.length > 0) return false;
1564
+ if (node.children.length > 0) return false;
1565
+ return is_component_jsx_name(opening.name);
1566
+ }
1567
+
1440
1568
  /**
1441
1569
  * Static JSX that appears before multiple early-return guards is otherwise
1442
1570
  * cloned into every generated return. Capture it once at its source position
@@ -1665,201 +1793,38 @@ function create_component_returning_if_statement(node, render_nodes, transform_c
1665
1793
  return set_loc(b.if(node.test, set_loc(b.block(branch_statements), node.consequent), null), node);
1666
1794
  }
1667
1795
 
1668
- /* ---------------------------------------------------------------------- *
1669
- * Continuation-lift primitives shared across if / switch / try / for-of *
1670
- * ---------------------------------------------------------------------- */
1671
-
1672
- /**
1673
- * Build the helper component that owns the post-control-flow continuation.
1674
- * Same shape as `create_hook_safe_helper`; named for intent at lift call sites.
1675
- *
1676
- * @param {any[]} continuation_body
1677
- * @param {any} source_node
1678
- * @param {TransformContext} transform_context
1679
- * @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
1680
- */
1681
- function build_tail_helper(continuation_body, source_node, transform_context) {
1682
- return create_hook_safe_helper(continuation_body, undefined, source_node, transform_context);
1683
- }
1684
-
1685
1796
  /**
1686
- * Clone the tail helper's component element for embedding inside another
1687
- * branch's body. Loses location info because the same element appears in
1688
- * multiple positions and downstream tooling treats AST nodes as identity-keyed.
1797
+ * Build a `return <combined-render-fragment>;` statement, prepending any
1798
+ * `render_nodes` collected before the control-flow construct so they don't
1799
+ * get dropped on the lift path.
1689
1800
  *
1690
- * @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
1801
+ * @param {any[]} render_nodes
1802
+ * @param {any} jsx_child
1691
1803
  * @returns {any}
1692
1804
  */
1693
- function clone_tail_invocation(tail_helper) {
1694
- return clone_expression_node(tail_helper.component_element, false);
1805
+ function combined_return_statement(render_nodes, jsx_child) {
1806
+ return b.return(combine_render_return_argument(render_nodes, jsx_child));
1695
1807
  }
1696
1808
 
1697
1809
  /**
1698
- * Return `[...body, <TailHelper x={x} />]` so the branch's render output
1699
- * includes the tail invocation and the post-hook locals flow forward.
1700
- * Used by if / switch / try (unconditional append). For-of uses a different
1701
- * shape gating on `_tsrx_isLast_<n>` so it constructs its own.
1810
+ * Hoist a for-of iteration source into a generated `let` and add a
1811
+ * normalization assignment via `Array.isArray(src) ? src : Array.from(src)`.
1812
+ * Always emits both even when the source is already a simple identifier —
1813
+ * so the loop-scoped TS type aliases have a stable name to reference and the
1814
+ * runtime check skips the copy when the value is already an array.
1702
1815
  *
1703
- * @param {any[]} body
1704
- * @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
1705
- * @returns {any[]}
1706
- */
1707
- function append_tail_invocation(body, tail_helper) {
1708
- return [...body, clone_tail_invocation(tail_helper)];
1709
- }
1710
-
1711
- /**
1712
- * @param {AST.Identifier} tail_synthetic_id
1713
- * @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
1714
- * @returns {any}
1715
- */
1716
- function create_loop_tail_expression(tail_synthetic_id, tail_helper) {
1717
- return b.logical('&&', clone_identifier(tail_synthetic_id), clone_tail_invocation(tail_helper));
1718
- }
1719
-
1720
- /**
1721
- * @param {AST.Identifier} tail_synthetic_id
1722
- * @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
1723
- * @returns {any}
1816
+ * @param {AST.Identifier} source_id
1817
+ * @param {any} source_expr
1818
+ * @returns {{ source_decl: any, source_normalize_decl: any }}
1724
1819
  */
1725
- function create_loop_tail_conditional(tail_synthetic_id, tail_helper) {
1726
- return b.conditional(
1727
- clone_identifier(tail_synthetic_id),
1728
- clone_tail_invocation(tail_helper),
1729
- create_null_literal(),
1730
- );
1731
- }
1820
+ function build_array_normalization_decls(source_id, source_expr) {
1821
+ const source_decl = b.let(clone_identifier(source_id), clone_expression_node(source_expr));
1822
+ const is_array_call = b.call(b.member(b.id('Array'), 'isArray'), clone_identifier(source_id));
1823
+ const from_call = b.call(b.member(b.id('Array'), 'from'), clone_identifier(source_id));
1824
+ const normalized = b.conditional(is_array_call, clone_identifier(source_id), from_call);
1825
+ const source_normalize_decl = b.stmt(b.assignment('=', clone_identifier(source_id), normalized));
1732
1826
 
1733
- /**
1734
- * @param {any[]} statements
1735
- * @param {AST.Identifier} tail_synthetic_id
1736
- * @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
1737
- * @returns {void}
1738
- */
1739
- function append_loop_tail_to_return_statements(statements, tail_synthetic_id, tail_helper) {
1740
- for (const statement of statements) {
1741
- append_loop_tail_to_return_statement(statement, tail_synthetic_id, tail_helper, false);
1742
- }
1743
- }
1744
-
1745
- /**
1746
- * @param {any} node
1747
- * @param {AST.Identifier} tail_synthetic_id
1748
- * @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
1749
- * @param {boolean} inside_nested_function
1750
- * @returns {void}
1751
- */
1752
- function append_loop_tail_to_return_statement(
1753
- node,
1754
- tail_synthetic_id,
1755
- tail_helper,
1756
- inside_nested_function,
1757
- ) {
1758
- if (!node || typeof node !== 'object') {
1759
- return;
1760
- }
1761
-
1762
- if (
1763
- node.type === 'FunctionDeclaration' ||
1764
- node.type === 'FunctionExpression' ||
1765
- node.type === 'ArrowFunctionExpression'
1766
- ) {
1767
- inside_nested_function = true;
1768
- }
1769
-
1770
- if (!inside_nested_function && node.type === 'ReturnStatement') {
1771
- if (
1772
- references_scope_bindings(
1773
- node.argument,
1774
- new Map([[tail_synthetic_id.name, tail_synthetic_id]]),
1775
- )
1776
- ) {
1777
- return;
1778
- }
1779
- node.argument = append_loop_tail_to_return_argument(
1780
- node.argument,
1781
- tail_synthetic_id,
1782
- tail_helper,
1783
- );
1784
- return;
1785
- }
1786
-
1787
- if (Array.isArray(node)) {
1788
- for (const child of node) {
1789
- append_loop_tail_to_return_statement(
1790
- child,
1791
- tail_synthetic_id,
1792
- tail_helper,
1793
- inside_nested_function,
1794
- );
1795
- }
1796
- return;
1797
- }
1798
-
1799
- for (const key of Object.keys(node)) {
1800
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1801
- continue;
1802
- }
1803
- append_loop_tail_to_return_statement(
1804
- node[key],
1805
- tail_synthetic_id,
1806
- tail_helper,
1807
- inside_nested_function,
1808
- );
1809
- }
1810
- }
1811
-
1812
- /**
1813
- * @param {any} return_argument
1814
- * @param {AST.Identifier} tail_synthetic_id
1815
- * @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
1816
- * @returns {any}
1817
- */
1818
- function append_loop_tail_to_return_argument(return_argument, tail_synthetic_id, tail_helper) {
1819
- if (return_argument == null || is_null_literal(return_argument)) {
1820
- return create_loop_tail_conditional(tail_synthetic_id, tail_helper);
1821
- }
1822
-
1823
- return (
1824
- build_return_expression([
1825
- return_argument_to_render_node(return_argument),
1826
- to_jsx_expression_container(create_loop_tail_expression(tail_synthetic_id, tail_helper)),
1827
- ]) || create_null_literal()
1828
- );
1829
- }
1830
-
1831
- /**
1832
- * Build a `return <combined-render-fragment>;` statement, prepending any
1833
- * `render_nodes` collected before the control-flow construct so they don't
1834
- * get dropped on the lift path.
1835
- *
1836
- * @param {any[]} render_nodes
1837
- * @param {any} jsx_child
1838
- * @returns {any}
1839
- */
1840
- function combined_return_statement(render_nodes, jsx_child) {
1841
- return b.return(combine_render_return_argument(render_nodes, jsx_child));
1842
- }
1843
-
1844
- /**
1845
- * Hoist a for-of iteration source into a generated `let` and add a
1846
- * normalization assignment via `Array.isArray(src) ? src : Array.from(src)`.
1847
- * Always emits both — even when the source is already a simple identifier —
1848
- * so the loop-scoped TS type aliases have a stable name to reference and the
1849
- * runtime check skips the copy when the value is already an array.
1850
- *
1851
- * @param {AST.Identifier} source_id
1852
- * @param {any} source_expr
1853
- * @returns {{ source_decl: any, source_normalize_decl: any }}
1854
- */
1855
- function build_array_normalization_decls(source_id, source_expr) {
1856
- const source_decl = b.let(clone_identifier(source_id), clone_expression_node(source_expr));
1857
- const is_array_call = b.call(b.member(b.id('Array'), 'isArray'), clone_identifier(source_id));
1858
- const from_call = b.call(b.member(b.id('Array'), 'from'), clone_identifier(source_id));
1859
- const normalized = b.conditional(is_array_call, clone_identifier(source_id), from_call);
1860
- const source_normalize_decl = b.stmt(b.assignment('=', clone_identifier(source_id), normalized));
1861
-
1862
- return { source_decl, source_normalize_decl };
1827
+ return { source_decl, source_normalize_decl };
1863
1828
  }
1864
1829
 
1865
1830
  /**
@@ -1907,256 +1872,6 @@ function create_component_helper_split_returning_if_statements(
1907
1872
  ];
1908
1873
  }
1909
1874
 
1910
- /**
1911
- * Lift a non-returning `if` whose consequent contains hook calls plus the
1912
- * statements that follow it into helper components.
1913
- *
1914
- * Without this, the consequent's hook would be wrapped into a child component
1915
- * (StatementBodyHook) but any code after the `if` that reads bindings the hook
1916
- * mutates would observe the pre-hook value, because React commits children
1917
- * after their parent has finished rendering. The fix mirrors the early-return
1918
- * splitter: emit a tail helper that owns the post-`if` statements, append a
1919
- * call to it inside the branch helper so the post-hook bindings flow forward,
1920
- * and render the tail helper directly when the `if` is false.
1921
- *
1922
- * @param {any} if_node
1923
- * @param {any[]} continuation_body
1924
- * @param {any[]} render_nodes
1925
- * @param {TransformContext} transform_context
1926
- * @returns {any[]}
1927
- */
1928
- function create_continuation_lift_if_statement(
1929
- if_node,
1930
- continuation_body,
1931
- render_nodes,
1932
- transform_context,
1933
- ) {
1934
- const consequent_body = get_if_consequent_body(if_node);
1935
- const tail_helper = build_tail_helper(continuation_body, if_node, transform_context);
1936
- const branch_helper = create_hook_safe_helper(
1937
- append_tail_invocation(consequent_body, tail_helper),
1938
- undefined,
1939
- if_node.consequent,
1940
- transform_context,
1941
- );
1942
-
1943
- const branch_block = set_loc(
1944
- b.block([
1945
- ...branch_helper.setup_statements,
1946
- combined_return_statement(render_nodes, branch_helper.component_element),
1947
- ]),
1948
- if_node.consequent,
1949
- );
1950
-
1951
- return [
1952
- ...tail_helper.setup_statements,
1953
- set_loc(b.if(if_node.test, branch_block, null), if_node),
1954
- combined_return_statement(render_nodes, tail_helper.component_element),
1955
- ];
1956
- }
1957
-
1958
- /**
1959
- * Continuation lift for `try` / `try / pending / catch` statements. Same
1960
- * shape as if/switch: build a tail helper from the post-`try` statements, and
1961
- * append a clone of its invocation to the try body and the catch body so the
1962
- * post-hook locals inside each branch flow forward into the tail. The pending
1963
- * body is left untouched — when Suspense renders the pending fallback the
1964
- * parent's render is unwound, so the tail wouldn't run in source semantics
1965
- * either. Once augmented, the existing try transform builds the
1966
- * Suspense / TsrxErrorBoundary wrapper as usual.
1967
- *
1968
- * @param {any} node - TryStatement
1969
- * @param {any[]} continuation_body
1970
- * @param {any[]} render_nodes
1971
- * @param {TransformContext} transform_context
1972
- * @returns {any[]}
1973
- */
1974
- function create_continuation_lift_try_statement(
1975
- node,
1976
- continuation_body,
1977
- render_nodes,
1978
- transform_context,
1979
- ) {
1980
- const tail_helper = build_tail_helper(continuation_body, node, transform_context);
1981
-
1982
- const augmented_block = {
1983
- ...node.block,
1984
- body: append_tail_invocation(node.block.body || [], tail_helper),
1985
- };
1986
-
1987
- let augmented_handler = node.handler;
1988
- if (node.handler) {
1989
- augmented_handler = {
1990
- ...node.handler,
1991
- body: {
1992
- ...node.handler.body,
1993
- body: append_tail_invocation(node.handler.body.body || [], tail_helper),
1994
- },
1995
- };
1996
- }
1997
-
1998
- const augmented_try = {
1999
- ...node,
2000
- block: augmented_block,
2001
- handler: augmented_handler,
2002
- };
2003
-
2004
- const try_jsx_child = (
2005
- transform_context.platform.hooks?.controlFlow?.tryStatement ?? try_statement_to_jsx_child
2006
- )(augmented_try, transform_context);
2007
-
2008
- return [...tail_helper.setup_statements, combined_return_statement(render_nodes, try_jsx_child)];
2009
- }
2010
-
2011
- /**
2012
- * @param {any} node - TryStatement
2013
- * @param {TransformContext} transform_context
2014
- * @returns {boolean}
2015
- */
2016
- function try_statement_contains_hooks(node, transform_context) {
2017
- if (body_contains_top_level_hook_call(node.block?.body || [], transform_context, true)) {
2018
- return true;
2019
- }
2020
- if (
2021
- node.handler &&
2022
- body_contains_top_level_hook_call(node.handler.body?.body || [], transform_context, true)
2023
- ) {
2024
- return true;
2025
- }
2026
- if (
2027
- node.pending &&
2028
- body_contains_top_level_hook_call(node.pending.body || [], transform_context, true)
2029
- ) {
2030
- return true;
2031
- }
2032
- return false;
2033
- }
2034
-
2035
- /**
2036
- * Continuation lift for `switch` statements. Same shape as the if-version:
2037
- * each case body is wrapped in its own helper component that ends with a
2038
- * call to a shared tail helper, so post-hook bindings inside any case flow
2039
- * forward to the statements after the switch. The fall-through return at
2040
- * the end renders the tail helper directly, covering the case where no
2041
- * `case` (and no `default`) matched.
2042
- *
2043
- * Empty fall-through cases (`case 'a':` with no body, falling through to
2044
- * the next case) are preserved as-is — they must not get their own helper
2045
- * because that would convert fall-through into early-return.
2046
- *
2047
- * @param {any} switch_node
2048
- * @param {any[]} continuation_body
2049
- * @param {any[]} render_nodes
2050
- * @param {TransformContext} transform_context
2051
- * @returns {any[]}
2052
- */
2053
- function create_continuation_lift_switch_statement(
2054
- switch_node,
2055
- continuation_body,
2056
- render_nodes,
2057
- transform_context,
2058
- ) {
2059
- const tail_helper = build_tail_helper(continuation_body, switch_node, transform_context);
2060
-
2061
- // Per-case info computed once: own body (statements before any
2062
- // terminator) and whether the case has a `break` / `return`.
2063
- const case_info = switch_node.cases.map((/** @type {any} */ c) => {
2064
- const consequent = flatten_switch_consequent(c.consequent || []);
2065
- const own_body = [];
2066
- let own_has_terminator = false;
2067
- for (const node of consequent) {
2068
- if (node.type === 'BreakStatement' || node.type === 'ReturnStatement') {
2069
- own_has_terminator = true;
2070
- break;
2071
- }
2072
- own_body.push(node);
2073
- }
2074
- return { own_body, own_has_terminator };
2075
- });
2076
-
2077
- // Allocate helper ids in source order (forward pass) so the snapshot's
2078
- // `StatementBodyHook<N>` numbering reads top-to-bottom by case position.
2079
- /** @type {Array<AST.Identifier | null>} */
2080
- const helper_ids = case_info.map(
2081
- (/** @type {{ own_body: any[], own_has_terminator: boolean }} */ info) =>
2082
- info.own_body.length === 0
2083
- ? null
2084
- : create_generated_identifier(create_local_statement_component_name(transform_context)),
2085
- );
2086
-
2087
- // Build helpers in reverse order: each fall-through case's helper body
2088
- // invokes the *next* case's helper, so the chain forwards post-mutation
2089
- // locals through the switch. Reverse iteration ensures the next helper's
2090
- // component_element is already constructed when we need to embed it.
2091
- /** @type {Array<{ setup_statements: any[], component_element: any } | null>} */
2092
- const case_helper_by_index = new Array(switch_node.cases.length).fill(null);
2093
- for (let i = switch_node.cases.length - 1; i >= 0; i--) {
2094
- const { own_body, own_has_terminator } = case_info[i];
2095
- if (own_body.length === 0) continue;
2096
-
2097
- // Determine the downstream helper this case invokes after its own body.
2098
- // - With a terminator: invoke the tail helper directly (case exits switch).
2099
- // - Otherwise (fall-through): invoke the next non-empty case's helper,
2100
- // or the tail if nothing else follows.
2101
- let downstream;
2102
- if (own_has_terminator) {
2103
- downstream = tail_helper;
2104
- } else {
2105
- let next_helper = null;
2106
- for (let j = i + 1; j < switch_node.cases.length; j++) {
2107
- if (case_helper_by_index[j]) {
2108
- next_helper = case_helper_by_index[j];
2109
- break;
2110
- }
2111
- }
2112
- downstream = next_helper ?? tail_helper;
2113
- }
2114
-
2115
- case_helper_by_index[i] = create_hook_safe_helper(
2116
- append_tail_invocation(own_body, downstream),
2117
- undefined,
2118
- switch_node.cases[i],
2119
- transform_context,
2120
- /** @type {any} */ (helper_ids[i]),
2121
- );
2122
- }
2123
-
2124
- const new_cases = switch_node.cases.map(
2125
- (/** @type {any} */ original_case, /** @type {number} */ i) => {
2126
- const helper = case_helper_by_index[i];
2127
- if (helper) {
2128
- return b.switch_case(original_case.test, [
2129
- combined_return_statement(render_nodes, helper.component_element),
2130
- ]);
2131
- }
2132
-
2133
- const { own_body, own_has_terminator } = case_info[i];
2134
- if (own_body.length === 0 && own_has_terminator) {
2135
- // `case 'a': break;` — exits the switch, then runs the tail.
2136
- return b.switch_case(original_case.test, [
2137
- combined_return_statement(render_nodes, tail_helper.component_element),
2138
- ]);
2139
- }
2140
- // Genuine empty fall-through (`case 'a': case 'b': ...`).
2141
- return b.switch_case(original_case.test, []);
2142
- },
2143
- );
2144
-
2145
- // Hoist all case helpers' setup statements above the switch in source
2146
- // order so the switch body is purely a dispatcher.
2147
- const case_helper_setup_statements = [];
2148
- for (const helper of case_helper_by_index) {
2149
- if (helper) case_helper_setup_statements.push(...helper.setup_statements);
2150
- }
2151
-
2152
- return [
2153
- ...tail_helper.setup_statements,
2154
- ...case_helper_setup_statements,
2155
- set_loc(b.switch(switch_node.discriminant, new_cases), switch_node),
2156
- combined_return_statement(render_nodes, tail_helper.component_element),
2157
- ];
2158
- }
2159
-
2160
1875
  /**
2161
1876
  * Hoist the helper for a hook-bearing for-of body out of the iteration
2162
1877
  * callback so the helper is declared once per render rather than re-bound on
@@ -2169,77 +1884,52 @@ function create_continuation_lift_switch_statement(
2169
1884
  * works while skipping the copy when the source is already an array. The
2170
1885
  * iteration itself is emitted as `source.map((item, i) => ...)`.
2171
1886
  *
2172
- * If `continuation_body` is non-empty (the for-of has a tail) we also lift
2173
- * the tail into a TailHelper and call it conditionally on the last iteration
2174
- * via an `isLast={i === source.length - 1}` prop on the loop helper. The
2175
- * loop helper's mutated locals (post-`useState`) flow into the TailHelper as
2176
- * its props. When the source is empty, `.map` returns `[]` and the TailHelper
2177
- * never renders — we add a sibling fallback so the source's tail still runs
2178
- * with the original outer values in that case.
2179
- *
2180
1887
  * Bails out (returns null) when the loop pattern is destructured — deriving
2181
1888
  * element types from a tuple/object pattern is more involved and deferred.
2182
1889
  *
2183
1890
  * @param {any} node - ForOfStatement
2184
- * @param {any[]} continuation_body
2185
1891
  * @param {TransformContext} transform_context
2186
1892
  * @returns {{ hoist_statements: any[], jsx_child: any } | null}
2187
1893
  */
2188
- function build_hoisted_for_of_with_hooks(node, continuation_body, transform_context) {
1894
+ function build_hoisted_for_of_with_hooks(node, transform_context) {
2189
1895
  const loop_params = get_for_of_iteration_params(node.left, node.index);
2190
1896
  for (const param of loop_params) {
2191
1897
  if (param.type !== 'Identifier') return null;
2192
1898
  }
2193
1899
 
2194
- const has_tail = continuation_body.length > 0;
2195
1900
  const original_loop_body = /** @type {any[]} */ (
2196
1901
  rewrite_loop_continues_to_bare_returns(
2197
1902
  node.body.type === 'BlockStatement' ? node.body.body : [node.body],
2198
1903
  )
2199
1904
  );
2200
1905
 
2201
- // When there's a tail, build TailHelper first so its component_element can
2202
- // be embedded inside the loop helper's body (gated on isLast). The
2203
- // synthetic isLast prop uses the loop helper's index (which will be the
2204
- // next one assigned, since `create_hook_safe_helper` for the tail just
2205
- // consumed one) so it lines up with `StatementBodyHook<N>` in the output.
2206
- let tail_helper = null;
2207
- /** @type {AST.Identifier} */ let tail_synthetic_id;
2208
- if (has_tail) {
2209
- tail_helper = build_tail_helper(continuation_body, node, transform_context);
2210
- tail_synthetic_id = create_generated_identifier(
2211
- `_tsrx_isLast_${transform_context.local_statement_component_index + 1}`,
2212
- );
2213
- } else {
2214
- tail_synthetic_id = /** @type {any} */ (null);
2215
- }
2216
- const loop_tail_expression = has_tail
2217
- ? create_loop_tail_expression(tail_synthetic_id, /** @type {any} */ (tail_helper))
2218
- : null;
2219
- const loop_body =
2220
- has_tail && loop_tail_expression
2221
- ? [...original_loop_body, b.jsx_expression_container(loop_tail_expression)]
2222
- : original_loop_body;
2223
-
2224
1906
  const source_id = create_generated_identifier(
2225
1907
  `_tsrx_iteration_items_${transform_context.local_statement_component_index + 1}`,
2226
1908
  );
2227
- const { source_decl, source_normalize_decl } = build_array_normalization_decls(
2228
- source_id,
2229
- node.right,
2230
- );
1909
+ const use_iterable_helper = !!transform_context.platform.imports.forOfIterableHelper;
1910
+ const { source_decl, source_normalize_decl } = use_iterable_helper
1911
+ ? {
1912
+ source_decl: b.let(clone_identifier(source_id), clone_expression_node(node.right)),
1913
+ source_normalize_decl: null,
1914
+ }
1915
+ : build_array_normalization_decls(source_id, node.right);
2231
1916
 
2232
1917
  const saved_bindings = transform_context.available_bindings;
2233
1918
  transform_context.available_bindings = new Map(saved_bindings);
1919
+ const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
2234
1920
  for (const param of loop_params) {
2235
1921
  collect_pattern_bindings(param, transform_context.available_bindings);
2236
1922
  }
1923
+ validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
1924
+ original_loop_body,
1925
+ transform_context,
1926
+ loop_scoped_names,
1927
+ );
2237
1928
 
2238
1929
  const all_helper_bindings = get_referenced_helper_bindings(
2239
- loop_body,
1930
+ original_loop_body,
2240
1931
  transform_context.available_bindings,
2241
1932
  );
2242
- const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
2243
1933
  const outer_bindings = all_helper_bindings.filter((b) => !loop_scoped_names.has(b.name));
2244
1934
  const loop_bindings = all_helper_bindings.filter((b) => loop_scoped_names.has(b.name));
2245
1935
 
@@ -2257,69 +1947,42 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
2257
1947
  const loop_aliases = use_module_scoped_component
2258
1948
  ? []
2259
1949
  : loop_bindings.map((binding) =>
2260
- create_loop_scoped_type_alias_declaration(helper_id, binding, source_id, loop_params),
1950
+ create_loop_scoped_type_alias_declaration(
1951
+ helper_id,
1952
+ binding,
1953
+ source_id,
1954
+ loop_params,
1955
+ transform_context,
1956
+ ),
2261
1957
  );
2262
1958
 
2263
- // Synthetic `isLast` prop on the loop helper when there's a tail. It's
2264
- // passed from the .map callback as `i === source.length - 1` so every
2265
- // loop-helper return can append the tail helper on the last iteration.
2266
- const tail_isLast_alias = has_tail
2267
- ? use_module_scoped_component
2268
- ? null
2269
- : {
2270
- id: create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
2271
- declaration: b.ts_type_alias(
2272
- create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
2273
- b.ts_keyword_type('boolean'),
2274
- ),
2275
- }
2276
- : null;
2277
-
2278
1959
  const ordered_bindings = [...outer_bindings, ...loop_bindings];
2279
1960
  const ordered_aliases = [...outer_aliases, ...loop_aliases];
2280
1961
  const ordered_use_typeof = [...outer_bindings.map(() => true), ...loop_bindings.map(() => false)];
2281
1962
 
2282
- const signature_bindings = has_tail ? [...ordered_bindings, tail_synthetic_id] : ordered_bindings;
2283
- const signature_aliases = has_tail
2284
- ? [...ordered_aliases, /** @type {any} */ (tail_isLast_alias)]
2285
- : ordered_aliases;
2286
- const signature_use_typeof = has_tail ? [...ordered_use_typeof, false] : ordered_use_typeof;
2287
-
2288
1963
  const props_type =
2289
- signature_bindings.length > 0 && !use_module_scoped_component
1964
+ ordered_bindings.length > 0 && !use_module_scoped_component
2290
1965
  ? create_helper_props_type_literal_with_typeof_flags(
2291
- signature_bindings,
2292
- signature_aliases,
2293
- signature_use_typeof,
1966
+ ordered_bindings,
1967
+ ordered_aliases,
1968
+ ordered_use_typeof,
2294
1969
  )
2295
1970
  : null;
2296
1971
  const params =
2297
- signature_bindings.length > 0
1972
+ ordered_bindings.length > 0
2298
1973
  ? [
2299
1974
  props_type !== null
2300
- ? create_typed_helper_props_pattern(signature_bindings, props_type)
2301
- : create_helper_props_pattern(signature_bindings),
1975
+ ? create_typed_helper_props_pattern(ordered_bindings, props_type)
1976
+ : create_helper_props_pattern(ordered_bindings),
2302
1977
  ]
2303
1978
  : [];
2304
1979
 
2305
1980
  const fn_saved_bindings = transform_context.available_bindings;
2306
1981
  transform_context.available_bindings = new Map(fn_saved_bindings);
2307
- if (has_tail) {
2308
- transform_context.available_bindings.set(tail_synthetic_id.name, tail_synthetic_id);
2309
- }
2310
- const fn_body_statements = build_render_statements(loop_body, true, transform_context);
2311
- if (has_tail) {
2312
- append_loop_tail_to_return_statements(
2313
- fn_body_statements,
2314
- tail_synthetic_id,
2315
- /** @type {any} */ (tail_helper),
2316
- );
2317
- }
1982
+ const fn_body_statements = build_render_statements(original_loop_body, true, transform_context);
2318
1983
  transform_context.available_bindings = fn_saved_bindings;
2319
1984
 
2320
- const helper_fn = /** @type {any} */ (
2321
- b.function(clone_identifier(component_id), params, b.block(fn_body_statements))
2322
- );
1985
+ const helper_fn = b.function(clone_identifier(component_id), params, b.block(fn_body_statements));
2323
1986
  helper_fn.metadata = { path: [], is_component: true, is_method: false };
2324
1987
 
2325
1988
  let helper_decl;
@@ -2351,18 +2014,6 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
2351
2014
  { mapWrapper: false, mapBindingNames: false, mapBindingValues: false },
2352
2015
  );
2353
2016
 
2354
- // When there's a tail, the .map callback always needs an index to compute
2355
- // `isLast`. If the user didn't write `index i`, synthesize one. The same
2356
- // identifier is also used as the implicit key fallback below.
2357
- let index_identifier;
2358
- if (loop_params.length >= 2) {
2359
- index_identifier = clone_identifier(loop_params[1]);
2360
- } else if (has_tail) {
2361
- index_identifier = create_generated_identifier('i');
2362
- } else {
2363
- index_identifier = null;
2364
- }
2365
-
2366
2017
  const body_key_expression = find_key_expression_in_body(original_loop_body);
2367
2018
  const explicit_key_expression =
2368
2019
  body_key_expression ?? (node.key ? clone_expression_node(node.key) : undefined);
@@ -2375,57 +2026,24 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
2375
2026
  );
2376
2027
  }
2377
2028
 
2378
- if (has_tail && index_identifier) {
2379
- const length_minus_one = b.binary(
2380
- '-',
2381
- b.member(clone_identifier(source_id), 'length'),
2382
- b.literal(1),
2383
- );
2384
- callback_invocation_element.openingElement.attributes.push(
2385
- b.jsx_attribute(
2386
- b.jsx_id(tail_synthetic_id.name),
2387
- to_jsx_expression_container(
2388
- b.binary('===', clone_identifier(index_identifier), length_minus_one),
2389
- ),
2390
- ),
2391
- );
2392
- }
2393
-
2394
- const callback_params =
2395
- has_tail && loop_params.length < 2 && index_identifier
2396
- ? [
2397
- ...loop_params.map((/** @type {any} */ p) => clone_identifier(p)),
2398
- clone_identifier(index_identifier),
2399
- ]
2400
- : loop_params.map((/** @type {any} */ p) => clone_identifier(p));
2029
+ const callback_params = loop_params.map((/** @type {any} */ p) => clone_identifier(p));
2401
2030
 
2402
2031
  const iter_callback = b.arrow(callback_params, callback_invocation_element);
2403
2032
 
2404
- const map_call = b.call(b.member(clone_identifier(source_id), 'map'), iter_callback);
2405
-
2406
- // jsx_child for the iteration. When there's a tail, also render the tail
2407
- // helper directly when the source is empty (no iterations means the loop
2408
- // helper never fires, so the tail wouldn't run otherwise).
2409
- const jsx_child = has_tail
2410
- ? to_jsx_expression_container(
2411
- b.conditional(
2412
- b.binary('===', b.member(clone_identifier(source_id), 'length'), b.literal(0)),
2413
- clone_tail_invocation(/** @type {any} */ (tail_helper)),
2414
- map_call,
2415
- ),
2416
- node,
2417
- )
2418
- : to_jsx_expression_container(map_call, node);
2419
-
2420
- const hoist_statements = [source_decl, source_normalize_decl];
2421
- if (has_tail) {
2422
- // TailHelper's setup statements (its alias consts and cache decl).
2423
- hoist_statements.push(.../** @type {any} */ (tail_helper).setup_statements);
2033
+ let map_call;
2034
+ if (use_iterable_helper) {
2035
+ transform_context.needs_for_of_iterable = true;
2036
+ map_call = b.call(b.id(MAP_ITERABLE_INTERNAL_NAME), clone_identifier(source_id), iter_callback);
2037
+ } else {
2038
+ map_call = b.call(b.member(clone_identifier(source_id), 'map'), iter_callback);
2424
2039
  }
2040
+
2041
+ const jsx_child = to_jsx_expression_container(map_call, node);
2042
+
2043
+ const hoist_statements = source_normalize_decl
2044
+ ? [source_decl, source_normalize_decl]
2045
+ : [source_decl];
2425
2046
  for (const alias of ordered_aliases) hoist_statements.push(alias.declaration);
2426
- if (has_tail && tail_isLast_alias) {
2427
- hoist_statements.push(tail_isLast_alias.declaration);
2428
- }
2429
2047
  if (helper_decl) {
2430
2048
  hoist_statements.push(helper_decl);
2431
2049
  }
@@ -2438,28 +2056,49 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
2438
2056
 
2439
2057
  /**
2440
2058
  * Build a TS `type` alias for a loop-scoped binding, deriving the type
2441
- * from the iteration source. For the value param we use
2442
- * `(typeof source)[number]`, which gives the right element type for arrays
2443
- * and tuples (the common case in JSX templates). For the index param,
2444
- * the type is always `number`.
2059
+ * from the iteration source. For the index param the type is always
2060
+ * `number`. For the value param the shape depends on whether the platform
2061
+ * uses the `map_iterable` runtime helper:
2062
+ *
2063
+ * - With the helper (React, Preact): `IterationValue<typeof source>` — any
2064
+ * `Iterable<T>` is accepted, so the element type is derived through the
2065
+ * runtime's exported helper type.
2066
+ * - Without the helper: `(typeof source)[number]` — arrays/tuples only,
2067
+ * matching the inline `.map()` lowering.
2445
2068
  *
2446
2069
  * @param {AST.Identifier} helper_id
2447
2070
  * @param {AST.Identifier} binding
2448
2071
  * @param {AST.Identifier} source_id
2449
2072
  * @param {any[]} loop_params
2073
+ * @param {TransformContext} transform_context
2450
2074
  * @returns {{ id: AST.Identifier, declaration: any }}
2451
2075
  */
2452
- function create_loop_scoped_type_alias_declaration(helper_id, binding, source_id, loop_params) {
2453
- const alias_id = create_generated_identifier(`_tsrx_${helper_id.name}_${binding.name}`);
2076
+ function create_loop_scoped_type_alias_declaration(
2077
+ helper_id,
2078
+ binding,
2079
+ source_id,
2080
+ loop_params,
2081
+ transform_context,
2082
+ ) {
2083
+ const alias_id = create_generated_identifier(`_tsrx_${helper_id.name}_${binding.name}`);
2454
2084
  const is_index = loop_params.length > 1 && binding.name === loop_params[1].name;
2085
+ const use_iterable_helper = !!transform_context.platform.imports.forOfIterableHelper;
2455
2086
  const type_annotation = is_index
2456
2087
  ? b.ts_keyword_type('number')
2457
- : /** @type {any} */ ({
2458
- type: 'TSIndexedAccessType',
2459
- objectType: b.ts_type_query(clone_identifier(source_id)),
2460
- indexType: b.ts_keyword_type('number'),
2461
- metadata: { path: [] },
2462
- });
2088
+ : use_iterable_helper
2089
+ ? (() => {
2090
+ transform_context.needs_iteration_value_type = true;
2091
+ return b.ts_type_reference(
2092
+ b.id(ITERATION_VALUE_INTERNAL_NAME),
2093
+ b.ts_type_parameter_instantiation([b.ts_type_query(clone_identifier(source_id))]),
2094
+ );
2095
+ })()
2096
+ : /** @type {any} */ ({
2097
+ type: 'TSIndexedAccessType',
2098
+ objectType: b.ts_type_query(clone_identifier(source_id)),
2099
+ indexType: b.ts_keyword_type('number'),
2100
+ metadata: { path: [] },
2101
+ });
2463
2102
 
2464
2103
  return {
2465
2104
  id: alias_id,
@@ -2519,23 +2158,19 @@ function create_setup_once_helper_split_returning_if_statements(
2519
2158
  return [
2520
2159
  ...branch_helper.setup_statements,
2521
2160
  ...continuation_helper.setup_statements,
2522
- {
2523
- type: 'ReturnStatement',
2524
- argument: combine_render_return_argument(
2161
+ b.return(
2162
+ combine_render_return_argument(
2525
2163
  render_nodes,
2526
2164
  set_loc(
2527
- /** @type {any} */ ({
2528
- type: 'ConditionalExpression',
2529
- test: node.test,
2530
- consequent: branch_helper.component_element,
2531
- alternate: continuation_helper.component_element,
2532
- metadata: { path: [] },
2533
- }),
2165
+ b.conditional(
2166
+ node.test,
2167
+ branch_helper.component_element,
2168
+ continuation_helper.component_element,
2169
+ ),
2534
2170
  node,
2535
2171
  ),
2536
2172
  ),
2537
- metadata: { path: [] },
2538
- },
2173
+ ),
2539
2174
  ];
2540
2175
  }
2541
2176
 
@@ -2632,9 +2267,6 @@ function is_null_literal(node) {
2632
2267
  return node?.type === 'Literal' && node.value == null;
2633
2268
  }
2634
2269
 
2635
- const TEMPLATE_FRAGMENT_ERROR =
2636
- 'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
2637
-
2638
2270
  /**
2639
2271
  * @param {any} node
2640
2272
  * @param {TransformContext} transform_context
@@ -2643,13 +2275,7 @@ const TEMPLATE_FRAGMENT_ERROR =
2643
2275
  function to_jsx_element(node, transform_context, raw_children = node.children || []) {
2644
2276
  if (node.type === 'JSXElement') return node;
2645
2277
  if (!node.id) {
2646
- error(
2647
- TEMPLATE_FRAGMENT_ERROR,
2648
- transform_context.filename,
2649
- node,
2650
- transform_context.errors,
2651
- transform_context.comments,
2652
- );
2278
+ report_jsx_fragment_in_tsrx_error(node, transform_context);
2653
2279
  return set_loc(
2654
2280
  /** @type {any} */ ({
2655
2281
  type: 'JSXFragment',
@@ -2688,9 +2314,7 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
2688
2314
  }
2689
2315
  } else {
2690
2316
  if (walked_children.some((/** @type {any} */ c) => c && c.type === 'Html')) {
2691
- throw new Error(
2692
- `\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
2693
- );
2317
+ return report_html_template_unsupported_error(node, transform_context);
2694
2318
  }
2695
2319
  children = create_element_children(walked_children, transform_context);
2696
2320
  }
@@ -2751,27 +2375,639 @@ function create_element_children(children, transform_context) {
2751
2375
  }
2752
2376
 
2753
2377
  /**
2754
- * @param {any[]} children
2378
+ * @param {any[]} children
2379
+ * @returns {boolean}
2380
+ */
2381
+ function children_contain_return_semantics(children) {
2382
+ return children.some(child_contains_return_semantics);
2383
+ }
2384
+
2385
+ /**
2386
+ * @param {any} node
2387
+ * @returns {boolean}
2388
+ */
2389
+ function child_contains_return_semantics(node) {
2390
+ if (!node || typeof node !== 'object') {
2391
+ return false;
2392
+ }
2393
+
2394
+ if (
2395
+ (node.type === 'ReturnStatement' && node.argument == null) ||
2396
+ is_lone_return_if_statement(node)
2397
+ ) {
2398
+ return true;
2399
+ }
2400
+
2401
+ if (
2402
+ node.type === 'FunctionDeclaration' ||
2403
+ node.type === 'FunctionExpression' ||
2404
+ node.type === 'ArrowFunctionExpression' ||
2405
+ node.type === 'Component'
2406
+ ) {
2407
+ return false;
2408
+ }
2409
+
2410
+ if (Array.isArray(node)) {
2411
+ return node.some(child_contains_return_semantics);
2412
+ }
2413
+
2414
+ for (const key of Object.keys(node)) {
2415
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2416
+ continue;
2417
+ }
2418
+ if (child_contains_return_semantics(node[key])) {
2419
+ return true;
2420
+ }
2421
+ }
2422
+
2423
+ return false;
2424
+ }
2425
+
2426
+ /**
2427
+ * @param {any} node
2428
+ * @returns {boolean}
2429
+ */
2430
+ function is_inline_element_child(node) {
2431
+ return node && is_jsx_child(node);
2432
+ }
2433
+
2434
+ /**
2435
+ * @param {any[]} body_nodes
2436
+ * @param {TransformContext} transform_context
2437
+ * @returns {ESTreeJSX.JSXExpressionContainer}
2438
+ */
2439
+ function statement_body_to_jsx_child(body_nodes, transform_context) {
2440
+ if (body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
2441
+ return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
2442
+ }
2443
+
2444
+ return to_jsx_expression_container(
2445
+ b.call(b.arrow([], b.block(build_render_statements(body_nodes, true, transform_context)))),
2446
+ );
2447
+ }
2448
+
2449
+ /**
2450
+ * @param {any[]} body_nodes
2451
+ * @param {TransformContext} transform_context
2452
+ * @returns {ESTreeJSX.JSXExpressionContainer}
2453
+ */
2454
+ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
2455
+ const source_node = get_body_source_node(body_nodes);
2456
+ const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
2457
+
2458
+ return to_jsx_expression_container(
2459
+ create_hook_safe_helper_iife(helper.setup_statements, helper.component_element),
2460
+ source_node,
2461
+ );
2462
+ }
2463
+
2464
+ /**
2465
+ * @param {TransformContext} transform_context
2466
+ * @returns {string}
2467
+ */
2468
+ function create_local_statement_component_name(transform_context) {
2469
+ transform_context.local_statement_component_index += 1;
2470
+ return `StatementBodyHook${transform_context.local_statement_component_index}`;
2471
+ }
2472
+
2473
+ /**
2474
+ * Wraps a list of body nodes into a component and returns
2475
+ * statements that return `<ComponentName prop1={prop1} ... />`.
2476
+ * Targets can either emit the helper component at module scope or cache the
2477
+ * component identity in module state while initializing it from the parent.
2478
+ * Used when a control flow branch contains hook calls that must be moved
2479
+ * into their own component boundary to satisfy the Rules of Hooks.
2480
+ *
2481
+ * @param {any[]} body_nodes
2482
+ * @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
2483
+ * @param {TransformContext} transform_context
2484
+ * @returns {any[]}
2485
+ */
2486
+ function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
2487
+ const source_node = get_body_source_node(body_nodes);
2488
+ const helper = create_hook_safe_helper(
2489
+ body_nodes,
2490
+ key_expression,
2491
+ source_node,
2492
+ transform_context,
2493
+ );
2494
+ const statements = [...helper.setup_statements];
2495
+
2496
+ statements.push(b.return(helper.component_element));
2497
+
2498
+ return statements;
2499
+ }
2500
+
2501
+ /**
2502
+ * @param {any[]} body_nodes
2503
+ * @param {Map<string, AST.Identifier>} available_bindings
2504
+ * @returns {AST.Identifier[]}
2505
+ */
2506
+ function get_referenced_helper_bindings(body_nodes, available_bindings) {
2507
+ const helper_bindings = [];
2508
+ const local_bindings = new Map();
2509
+
2510
+ for (const node of body_nodes) {
2511
+ collect_statement_bindings(node, local_bindings);
2512
+ }
2513
+
2514
+ for (const [name, binding] of available_bindings) {
2515
+ if (local_bindings.has(name)) continue;
2516
+
2517
+ if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
2518
+ helper_bindings.push(binding);
2519
+ }
2520
+ }
2521
+
2522
+ return helper_bindings;
2523
+ }
2524
+
2525
+ /**
2526
+ * @param {any[]} body_nodes
2527
+ * @param {TransformContext} transform_context
2528
+ * @param {Set<string>} [local_binding_names]
2529
+ * @returns {void}
2530
+ */
2531
+ function validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
2532
+ body_nodes,
2533
+ transform_context,
2534
+ local_binding_names,
2535
+ ) {
2536
+ if (!is_react_like_hook_platform(transform_context)) {
2537
+ return;
2538
+ }
2539
+ if (!body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
2540
+ return;
2541
+ }
2542
+ if (!transform_context.available_bindings || transform_context.available_bindings.size === 0) {
2543
+ return;
2544
+ }
2545
+
2546
+ const shadowed_names = collect_block_binding_names(body_nodes);
2547
+ for (const name of local_binding_names || []) {
2548
+ shadowed_names.add(name);
2549
+ }
2550
+ validate_hook_outer_assignments_in_node(body_nodes, shadowed_names, transform_context, new Set());
2551
+ }
2552
+
2553
+ /**
2554
+ * @param {TransformContext} transform_context
2555
+ * @returns {boolean}
2556
+ */
2557
+ function is_react_like_hook_platform(transform_context) {
2558
+ return (
2559
+ transform_context.platform.name === 'React' || transform_context.platform.name === 'Preact'
2560
+ );
2561
+ }
2562
+
2563
+ /**
2564
+ * @param {any[]} statements
2565
+ * @returns {Set<string>}
2566
+ */
2567
+ function collect_block_binding_names(statements) {
2568
+ const names = new Set();
2569
+ for (const statement of statements || []) {
2570
+ collect_block_binding_names_from_statement(statement, names);
2571
+ }
2572
+ return names;
2573
+ }
2574
+
2575
+ /**
2576
+ * @param {any} statement
2577
+ * @param {Set<string>} names
2578
+ * @returns {void}
2579
+ */
2580
+ function collect_block_binding_names_from_statement(statement, names) {
2581
+ if (!statement || typeof statement !== 'object') {
2582
+ return;
2583
+ }
2584
+
2585
+ if (statement.type === 'VariableDeclaration') {
2586
+ for (const declaration of statement.declarations || []) {
2587
+ collect_pattern_names(declaration.id, names);
2588
+ }
2589
+ return;
2590
+ }
2591
+
2592
+ if (
2593
+ (statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
2594
+ statement.id?.type === 'Identifier'
2595
+ ) {
2596
+ names.add(statement.id.name);
2597
+ return;
2598
+ }
2599
+
2600
+ if (statement.type === 'ForOfStatement' || statement.type === 'ForInStatement') {
2601
+ if (statement.left?.type === 'VariableDeclaration' && statement.left.kind === 'var') {
2602
+ for (const declaration of statement.left.declarations || []) {
2603
+ collect_pattern_names(declaration.id, names);
2604
+ }
2605
+ }
2606
+ return;
2607
+ }
2608
+
2609
+ if (
2610
+ statement.type === 'ForStatement' &&
2611
+ statement.init?.type === 'VariableDeclaration' &&
2612
+ statement.init.kind === 'var'
2613
+ ) {
2614
+ for (const declaration of statement.init.declarations || []) {
2615
+ collect_pattern_names(declaration.id, names);
2616
+ }
2617
+ }
2618
+ }
2619
+
2620
+ /**
2621
+ * @param {any} pattern
2622
+ * @param {Set<string>} names
2623
+ * @returns {void}
2624
+ */
2625
+ function collect_pattern_names(pattern, names) {
2626
+ if (!pattern || typeof pattern !== 'object') {
2627
+ return;
2628
+ }
2629
+
2630
+ if (pattern.type === 'Identifier') {
2631
+ names.add(pattern.name);
2632
+ return;
2633
+ }
2634
+
2635
+ if (pattern.type === 'RestElement') {
2636
+ collect_pattern_names(pattern.argument, names);
2637
+ return;
2638
+ }
2639
+
2640
+ if (pattern.type === 'AssignmentPattern') {
2641
+ collect_pattern_names(pattern.left, names);
2642
+ return;
2643
+ }
2644
+
2645
+ if (pattern.type === 'ArrayPattern') {
2646
+ for (const element of pattern.elements || []) {
2647
+ collect_pattern_names(element, names);
2648
+ }
2649
+ return;
2650
+ }
2651
+
2652
+ if (pattern.type === 'ObjectPattern') {
2653
+ for (const property of pattern.properties || []) {
2654
+ if (property.type === 'RestElement') {
2655
+ collect_pattern_names(property.argument, names);
2656
+ } else {
2657
+ collect_pattern_names(property.value, names);
2658
+ }
2659
+ }
2660
+ }
2661
+ }
2662
+
2663
+ /**
2664
+ * @param {any} node
2665
+ * @param {Set<string>} shadowed_names
2666
+ * @param {TransformContext} transform_context
2667
+ * @param {Set<string>} hook_result_names
2668
+ * @returns {void}
2669
+ */
2670
+ function validate_hook_outer_assignments_in_node(
2671
+ node,
2672
+ shadowed_names,
2673
+ transform_context,
2674
+ hook_result_names,
2675
+ ) {
2676
+ if (!node || typeof node !== 'object') {
2677
+ return;
2678
+ }
2679
+
2680
+ if (Array.isArray(node)) {
2681
+ for (const child of node) {
2682
+ validate_hook_outer_assignments_in_node(
2683
+ child,
2684
+ shadowed_names,
2685
+ transform_context,
2686
+ hook_result_names,
2687
+ );
2688
+ }
2689
+ return;
2690
+ }
2691
+
2692
+ if (is_function_like_node(node)) {
2693
+ return;
2694
+ }
2695
+
2696
+ if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
2697
+ validate_hook_callback_outer_mutations(node, shadowed_names, transform_context);
2698
+ }
2699
+
2700
+ if (node.type === 'BlockStatement') {
2701
+ const next_shadowed = new Set(shadowed_names);
2702
+ const next_hook_result_names = new Set(hook_result_names);
2703
+ for (const name of collect_block_binding_names(node.body || [])) {
2704
+ next_shadowed.add(name);
2705
+ }
2706
+ for (const child of node.body || []) {
2707
+ validate_hook_outer_assignments_in_node(
2708
+ child,
2709
+ next_shadowed,
2710
+ transform_context,
2711
+ next_hook_result_names,
2712
+ );
2713
+ }
2714
+ return;
2715
+ }
2716
+
2717
+ if (node.type === 'VariableDeclaration') {
2718
+ for (const declaration of node.declarations || []) {
2719
+ if (
2720
+ declaration.init &&
2721
+ expression_contains_hook_derived_value(
2722
+ declaration.init,
2723
+ transform_context,
2724
+ hook_result_names,
2725
+ )
2726
+ ) {
2727
+ collect_pattern_names(declaration.id, hook_result_names);
2728
+ }
2729
+ validate_hook_outer_assignments_in_node(
2730
+ declaration.init,
2731
+ shadowed_names,
2732
+ transform_context,
2733
+ hook_result_names,
2734
+ );
2735
+ }
2736
+ return;
2737
+ }
2738
+
2739
+ if (
2740
+ node.type === 'AssignmentExpression' &&
2741
+ expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
2742
+ ) {
2743
+ const outer_names = get_referenced_outer_binding_names(
2744
+ node.left,
2745
+ transform_context.available_bindings,
2746
+ shadowed_names,
2747
+ );
2748
+ if (outer_names.length > 0) {
2749
+ report_hook_outer_assignment_error(
2750
+ node.left,
2751
+ outer_names,
2752
+ find_first_hook_call_name(node.right) || 'hook',
2753
+ transform_context,
2754
+ );
2755
+ }
2756
+ for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
2757
+ hook_result_names.add(name);
2758
+ }
2759
+ }
2760
+
2761
+ if (node.type === 'ForOfStatement') {
2762
+ if (
2763
+ node.left &&
2764
+ node.left.type !== 'VariableDeclaration' &&
2765
+ expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
2766
+ ) {
2767
+ const outer_names = get_referenced_outer_binding_names(
2768
+ node.left,
2769
+ transform_context.available_bindings,
2770
+ shadowed_names,
2771
+ );
2772
+ if (outer_names.length > 0) {
2773
+ report_hook_outer_assignment_error(
2774
+ node.left,
2775
+ outer_names,
2776
+ find_first_hook_call_name(node.right) || 'hook',
2777
+ transform_context,
2778
+ );
2779
+ }
2780
+ for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
2781
+ hook_result_names.add(name);
2782
+ }
2783
+ }
2784
+
2785
+ validate_hook_outer_assignments_in_node(
2786
+ node.right,
2787
+ shadowed_names,
2788
+ transform_context,
2789
+ hook_result_names,
2790
+ );
2791
+
2792
+ // Loop-declared bindings (`for (const x of …)`, `for (let x of …)`) live
2793
+ // only in the body. They are deliberately NOT in the enclosing block's
2794
+ // shadowed set (see collect_block_binding_names_from_statement), so add
2795
+ // them just for the body recursion to keep references to the loop var
2796
+ // from being flagged as outer-binding mutations.
2797
+ const body_shadowed = new Set(shadowed_names);
2798
+ if (node.left && node.left.type === 'VariableDeclaration') {
2799
+ for (const declaration of node.left.declarations || []) {
2800
+ collect_pattern_names(declaration.id, body_shadowed);
2801
+ }
2802
+ }
2803
+ validate_hook_outer_assignments_in_node(
2804
+ node.body,
2805
+ body_shadowed,
2806
+ transform_context,
2807
+ hook_result_names,
2808
+ );
2809
+ return;
2810
+ }
2811
+
2812
+ for (const key of Object.keys(node)) {
2813
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2814
+ continue;
2815
+ }
2816
+ validate_hook_outer_assignments_in_node(
2817
+ node[key],
2818
+ shadowed_names,
2819
+ transform_context,
2820
+ hook_result_names,
2821
+ );
2822
+ }
2823
+ }
2824
+
2825
+ /**
2826
+ * @param {any} call_node
2827
+ * @param {Set<string>} shadowed_names
2828
+ * @param {TransformContext} transform_context
2829
+ * @returns {void}
2830
+ */
2831
+ function validate_hook_callback_outer_mutations(call_node, shadowed_names, transform_context) {
2832
+ const hook_name = get_hook_callee_name(call_node.callee);
2833
+ for (const argument of call_node.arguments || []) {
2834
+ if (!is_function_like_node(argument)) {
2835
+ continue;
2836
+ }
2837
+ const callback_shadowed_names = create_function_like_shadowed_names(argument, shadowed_names);
2838
+ validate_hook_callback_outer_mutations_in_node(
2839
+ argument.body,
2840
+ callback_shadowed_names,
2841
+ transform_context,
2842
+ hook_name,
2843
+ );
2844
+ }
2845
+ }
2846
+
2847
+ /**
2848
+ * @param {any} node
2849
+ * @returns {boolean}
2850
+ */
2851
+ function is_function_like_node(node) {
2852
+ return (
2853
+ node.type === 'FunctionDeclaration' ||
2854
+ node.type === 'FunctionExpression' ||
2855
+ node.type === 'ArrowFunctionExpression' ||
2856
+ // this is just in case but we should already
2857
+ // have a component replaced with a function node
2858
+ node.type === 'Component'
2859
+ );
2860
+ }
2861
+
2862
+ /**
2863
+ * @param {any} node
2864
+ * @param {Set<string>} shadowed_names
2865
+ * @returns {Set<string>}
2866
+ */
2867
+ function create_function_like_shadowed_names(node, shadowed_names) {
2868
+ const next_shadowed_names = new Set(shadowed_names);
2869
+ for (const param of node.params || []) {
2870
+ collect_pattern_names(param, next_shadowed_names);
2871
+ }
2872
+ if (node.body?.type === 'BlockStatement') {
2873
+ for (const name of collect_block_binding_names(node.body.body || [])) {
2874
+ next_shadowed_names.add(name);
2875
+ }
2876
+ }
2877
+ return next_shadowed_names;
2878
+ }
2879
+
2880
+ /**
2881
+ * @param {any} node
2882
+ * @param {Set<string>} shadowed_names
2883
+ * @param {TransformContext} transform_context
2884
+ * @param {string} hook_name
2885
+ * @returns {void}
2886
+ */
2887
+ function validate_hook_callback_outer_mutations_in_node(
2888
+ node,
2889
+ shadowed_names,
2890
+ transform_context,
2891
+ hook_name,
2892
+ ) {
2893
+ if (!node || typeof node !== 'object') {
2894
+ return;
2895
+ }
2896
+
2897
+ if (Array.isArray(node)) {
2898
+ for (const child of node) {
2899
+ validate_hook_callback_outer_mutations_in_node(
2900
+ child,
2901
+ shadowed_names,
2902
+ transform_context,
2903
+ hook_name,
2904
+ );
2905
+ }
2906
+ return;
2907
+ }
2908
+
2909
+ if (is_function_like_node(node)) {
2910
+ validate_hook_callback_outer_mutations_in_node(
2911
+ node.body,
2912
+ create_function_like_shadowed_names(node, shadowed_names),
2913
+ transform_context,
2914
+ hook_name,
2915
+ );
2916
+ return;
2917
+ }
2918
+
2919
+ if (node.type === 'BlockStatement') {
2920
+ const next_shadowed_names = new Set(shadowed_names);
2921
+ for (const name of collect_block_binding_names(node.body || [])) {
2922
+ next_shadowed_names.add(name);
2923
+ }
2924
+ for (const child of node.body || []) {
2925
+ validate_hook_callback_outer_mutations_in_node(
2926
+ child,
2927
+ next_shadowed_names,
2928
+ transform_context,
2929
+ hook_name,
2930
+ );
2931
+ }
2932
+ return;
2933
+ }
2934
+
2935
+ if (node.type === 'AssignmentExpression') {
2936
+ const outer_names = get_referenced_outer_binding_names(
2937
+ node.left,
2938
+ transform_context.available_bindings,
2939
+ shadowed_names,
2940
+ );
2941
+ if (outer_names.length > 0) {
2942
+ report_hook_callback_outer_mutation_error(
2943
+ node.left,
2944
+ outer_names,
2945
+ hook_name,
2946
+ transform_context,
2947
+ );
2948
+ }
2949
+ }
2950
+
2951
+ if (node.type === 'UpdateExpression') {
2952
+ const outer_names = get_referenced_outer_binding_names(
2953
+ node.argument,
2954
+ transform_context.available_bindings,
2955
+ shadowed_names,
2956
+ );
2957
+ if (outer_names.length > 0) {
2958
+ report_hook_callback_outer_mutation_error(
2959
+ node.argument,
2960
+ outer_names,
2961
+ hook_name,
2962
+ transform_context,
2963
+ );
2964
+ }
2965
+ }
2966
+
2967
+ for (const key of Object.keys(node)) {
2968
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2969
+ continue;
2970
+ }
2971
+ if (key === 'left' && node.type === 'AssignmentExpression') {
2972
+ continue;
2973
+ }
2974
+ if (key === 'argument' && node.type === 'UpdateExpression') {
2975
+ continue;
2976
+ }
2977
+ validate_hook_callback_outer_mutations_in_node(
2978
+ node[key],
2979
+ shadowed_names,
2980
+ transform_context,
2981
+ hook_name,
2982
+ );
2983
+ }
2984
+ }
2985
+
2986
+ /**
2987
+ * @param {any} node
2988
+ * @param {TransformContext} transform_context
2989
+ * @param {Set<string>} hook_result_names
2755
2990
  * @returns {boolean}
2756
2991
  */
2757
- function children_contain_return_semantics(children) {
2758
- return children.some(child_contains_return_semantics);
2992
+ function expression_contains_hook_derived_value(node, transform_context, hook_result_names) {
2993
+ return (
2994
+ node_contains_top_level_hook_call(node, false, transform_context, true) ||
2995
+ references_name_in_set(node, hook_result_names)
2996
+ );
2759
2997
  }
2760
2998
 
2761
2999
  /**
2762
3000
  * @param {any} node
3001
+ * @param {Set<string>} names
2763
3002
  * @returns {boolean}
2764
3003
  */
2765
- function child_contains_return_semantics(node) {
2766
- if (!node || typeof node !== 'object') {
3004
+ function references_name_in_set(node, names) {
3005
+ if (!node || typeof node !== 'object' || names.size === 0) {
2767
3006
  return false;
2768
3007
  }
2769
3008
 
2770
- if (
2771
- (node.type === 'ReturnStatement' && node.argument == null) ||
2772
- is_lone_return_if_statement(node)
2773
- ) {
2774
- return true;
3009
+ if (node.type === 'Identifier') {
3010
+ return names.has(node.name);
2775
3011
  }
2776
3012
 
2777
3013
  if (
@@ -2784,14 +3020,20 @@ function child_contains_return_semantics(node) {
2784
3020
  }
2785
3021
 
2786
3022
  if (Array.isArray(node)) {
2787
- return node.some(child_contains_return_semantics);
3023
+ return node.some((child) => references_name_in_set(child, names));
2788
3024
  }
2789
3025
 
2790
3026
  for (const key of Object.keys(node)) {
2791
3027
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2792
3028
  continue;
2793
3029
  }
2794
- if (child_contains_return_semantics(node[key])) {
3030
+ if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
3031
+ continue;
3032
+ }
3033
+ if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
3034
+ continue;
3035
+ }
3036
+ if (references_name_in_set(node[key], names)) {
2795
3037
  return true;
2796
3038
  }
2797
3039
  }
@@ -2801,123 +3043,163 @@ function child_contains_return_semantics(node) {
2801
3043
 
2802
3044
  /**
2803
3045
  * @param {any} node
2804
- * @returns {boolean}
3046
+ * @param {Set<string>} shadowed_names
3047
+ * @returns {string[]}
2805
3048
  */
2806
- function is_inline_element_child(node) {
2807
- return node && is_jsx_child(node);
3049
+ function get_referenced_local_binding_names(node, shadowed_names) {
3050
+ const names = new Set();
3051
+ collect_referenced_local_binding_names(node, shadowed_names, names);
3052
+ return [...names];
2808
3053
  }
2809
3054
 
2810
3055
  /**
2811
- * @param {any[]} body_nodes
2812
- * @param {TransformContext} transform_context
2813
- * @returns {ESTreeJSX.JSXExpressionContainer}
3056
+ * @param {any} node
3057
+ * @param {Set<string>} shadowed_names
3058
+ * @param {Set<string>} names
3059
+ * @returns {void}
2814
3060
  */
2815
- function statement_body_to_jsx_child(body_nodes, transform_context) {
2816
- if (body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
2817
- return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
3061
+ function collect_referenced_local_binding_names(node, shadowed_names, names) {
3062
+ if (!node || typeof node !== 'object') {
3063
+ return;
2818
3064
  }
2819
3065
 
2820
- return to_jsx_expression_container(
2821
- /** @type {any} */ ({
2822
- type: 'CallExpression',
2823
- callee: {
2824
- type: 'ArrowFunctionExpression',
2825
- params: [],
2826
- body: /** @type {any} */ ({
2827
- type: 'BlockStatement',
2828
- body: build_render_statements(body_nodes, true, transform_context),
2829
- metadata: { path: [] },
2830
- }),
2831
- async: false,
2832
- generator: false,
2833
- expression: false,
2834
- metadata: { path: [] },
2835
- },
2836
- arguments: [],
2837
- optional: false,
2838
- metadata: { path: [] },
2839
- }),
2840
- );
2841
- }
3066
+ if (node.type === 'Identifier') {
3067
+ if (shadowed_names.has(node.name)) {
3068
+ names.add(node.name);
3069
+ }
3070
+ return;
3071
+ }
2842
3072
 
2843
- /**
2844
- * @param {any[]} body_nodes
2845
- * @param {TransformContext} transform_context
2846
- * @returns {ESTreeJSX.JSXExpressionContainer}
2847
- */
2848
- function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
2849
- const source_node = get_body_source_node(body_nodes);
2850
- const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
3073
+ if (Array.isArray(node)) {
3074
+ for (const child of node) {
3075
+ collect_referenced_local_binding_names(child, shadowed_names, names);
3076
+ }
3077
+ return;
3078
+ }
2851
3079
 
2852
- return to_jsx_expression_container(
2853
- create_hook_safe_helper_iife(helper.setup_statements, helper.component_element),
2854
- source_node,
2855
- );
3080
+ for (const key of Object.keys(node)) {
3081
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3082
+ continue;
3083
+ }
3084
+ if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
3085
+ continue;
3086
+ }
3087
+ if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
3088
+ continue;
3089
+ }
3090
+ collect_referenced_local_binding_names(node[key], shadowed_names, names);
3091
+ }
2856
3092
  }
2857
3093
 
2858
3094
  /**
2859
- * @param {TransformContext} transform_context
2860
- * @returns {string}
3095
+ * @param {any} node
3096
+ * @param {Map<string, AST.Identifier>} available_bindings
3097
+ * @param {Set<string>} shadowed_names
3098
+ * @returns {string[]}
2861
3099
  */
2862
- function create_local_statement_component_name(transform_context) {
2863
- transform_context.local_statement_component_index += 1;
2864
- return `StatementBodyHook${transform_context.local_statement_component_index}`;
3100
+ function get_referenced_outer_binding_names(node, available_bindings, shadowed_names) {
3101
+ const names = new Set();
3102
+ collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names);
3103
+ return [...names];
2865
3104
  }
2866
3105
 
2867
3106
  /**
2868
- * Wraps a list of body nodes into a component and returns
2869
- * statements that return `<ComponentName prop1={prop1} ... />`.
2870
- * Targets can either emit the helper component at module scope or cache the
2871
- * component identity in module state while initializing it from the parent.
2872
- * Used when a control flow branch contains hook calls that must be moved
2873
- * into their own component boundary to satisfy the Rules of Hooks.
2874
- *
2875
- * @param {any[]} body_nodes
2876
- * @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
2877
- * @param {TransformContext} transform_context
2878
- * @returns {any[]}
3107
+ * @param {any} node
3108
+ * @param {Map<string, AST.Identifier>} available_bindings
3109
+ * @param {Set<string>} shadowed_names
3110
+ * @param {Set<string>} names
3111
+ * @returns {void}
2879
3112
  */
2880
- function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
2881
- const source_node = get_body_source_node(body_nodes);
2882
- const helper = create_hook_safe_helper(
2883
- body_nodes,
2884
- key_expression,
2885
- source_node,
2886
- transform_context,
2887
- );
2888
- const statements = [...helper.setup_statements];
3113
+ function collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names) {
3114
+ if (!node || typeof node !== 'object') {
3115
+ return;
3116
+ }
2889
3117
 
2890
- statements.push({
2891
- type: 'ReturnStatement',
2892
- argument: helper.component_element,
2893
- metadata: { path: [] },
2894
- });
3118
+ if (node.type === 'Identifier') {
3119
+ if (available_bindings.has(node.name) && !shadowed_names.has(node.name)) {
3120
+ names.add(node.name);
3121
+ }
3122
+ return;
3123
+ }
2895
3124
 
2896
- return statements;
3125
+ if (Array.isArray(node)) {
3126
+ for (const child of node) {
3127
+ collect_referenced_outer_binding_names(child, available_bindings, shadowed_names, names);
3128
+ }
3129
+ return;
3130
+ }
3131
+
3132
+ for (const key of Object.keys(node)) {
3133
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3134
+ continue;
3135
+ }
3136
+ if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
3137
+ continue;
3138
+ }
3139
+ if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
3140
+ continue;
3141
+ }
3142
+ collect_referenced_outer_binding_names(node[key], available_bindings, shadowed_names, names);
3143
+ }
2897
3144
  }
2898
3145
 
2899
3146
  /**
2900
- * @param {any[]} body_nodes
2901
- * @param {Map<string, AST.Identifier>} available_bindings
2902
- * @returns {AST.Identifier[]}
3147
+ * @param {any} node
3148
+ * @returns {string | null}
2903
3149
  */
2904
- function get_referenced_helper_bindings(body_nodes, available_bindings) {
2905
- const helper_bindings = [];
2906
- const local_bindings = new Map();
3150
+ function find_first_hook_call_name(node) {
3151
+ if (!node || typeof node !== 'object') {
3152
+ return null;
3153
+ }
2907
3154
 
2908
- for (const node of body_nodes) {
2909
- collect_statement_bindings(node, local_bindings);
3155
+ if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
3156
+ return get_hook_callee_name(node.callee);
2910
3157
  }
2911
3158
 
2912
- for (const [name, binding] of available_bindings) {
2913
- if (local_bindings.has(name)) continue;
3159
+ if (
3160
+ node.type === 'FunctionDeclaration' ||
3161
+ node.type === 'FunctionExpression' ||
3162
+ node.type === 'ArrowFunctionExpression' ||
3163
+ node.type === 'Component'
3164
+ ) {
3165
+ return null;
3166
+ }
2914
3167
 
2915
- if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
2916
- helper_bindings.push(binding);
3168
+ if (Array.isArray(node)) {
3169
+ for (const child of node) {
3170
+ const name = find_first_hook_call_name(child);
3171
+ if (name) return name;
2917
3172
  }
3173
+ return null;
2918
3174
  }
2919
3175
 
2920
- return helper_bindings;
3176
+ for (const key of Object.keys(node)) {
3177
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3178
+ continue;
3179
+ }
3180
+ const name = find_first_hook_call_name(node[key]);
3181
+ if (name) return name;
3182
+ }
3183
+
3184
+ return null;
3185
+ }
3186
+
3187
+ /**
3188
+ * @param {any} callee
3189
+ * @returns {string}
3190
+ */
3191
+ function get_hook_callee_name(callee) {
3192
+ if (callee?.type === 'Identifier') {
3193
+ return callee.name;
3194
+ }
3195
+ if (
3196
+ callee?.type === 'MemberExpression' &&
3197
+ !callee.computed &&
3198
+ callee.property?.type === 'Identifier'
3199
+ ) {
3200
+ return callee.property.name;
3201
+ }
3202
+ return 'hook';
2921
3203
  }
2922
3204
 
2923
3205
  /**
@@ -2938,6 +3220,11 @@ function create_hook_safe_helper(
2938
3220
  transform_context,
2939
3221
  preallocated_helper_id,
2940
3222
  ) {
3223
+ validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
3224
+ body_nodes,
3225
+ transform_context,
3226
+ );
3227
+
2941
3228
  const helper_id =
2942
3229
  preallocated_helper_id ??
2943
3230
  create_generated_identifier(create_local_statement_component_name(transform_context));
@@ -2968,23 +3255,13 @@ function create_hook_safe_helper(
2968
3255
  const saved_bindings = transform_context.available_bindings;
2969
3256
  transform_context.available_bindings = new Map(saved_bindings);
2970
3257
 
2971
- const helper_fn = /** @type {any} */ ({
2972
- type: 'FunctionExpression',
2973
- id: clone_identifier(component_id),
3258
+ const helper_fn = b.function(
3259
+ clone_identifier(component_id),
2974
3260
  params,
2975
- body: {
2976
- type: 'BlockStatement',
2977
- body: build_render_statements(body_nodes, true, transform_context),
2978
- metadata: { path: [] },
2979
- },
2980
- async: false,
2981
- generator: false,
2982
- metadata: {
2983
- path: [],
2984
- is_component: true,
2985
- is_method: false,
2986
- },
2987
- });
3261
+ b.block(build_render_statements(body_nodes, true, transform_context)),
3262
+ );
3263
+ helper_fn.metadata.is_component = true;
3264
+ helper_fn.metadata.is_method = false;
2988
3265
 
2989
3266
  transform_context.available_bindings = saved_bindings;
2990
3267
 
@@ -3050,7 +3327,7 @@ function create_hook_safe_helper(
3050
3327
 
3051
3328
  /**
3052
3329
  * @param {AST.Identifier} helper_id
3053
- * @param {any} helper_fn
3330
+ * @param {AST.FunctionExpression} helper_fn
3054
3331
  * @param {any} source_node
3055
3332
  * @param {TransformContext} transform_context
3056
3333
  * @returns {any}
@@ -3063,7 +3340,7 @@ function create_helper_declaration(helper_id, helper_fn, source_node, transform_
3063
3340
 
3064
3341
  /**
3065
3342
  * @param {AST.Identifier} helper_id
3066
- * @param {any} helper_fn
3343
+ * @param {AST.FunctionExpression} helper_fn
3067
3344
  * @param {any} source_node
3068
3345
  * @param {TransformContext} transform_context
3069
3346
  * @returns {any}
@@ -3092,32 +3369,7 @@ function create_helper_init_expression(helper_id, helper_fn, source_node, transf
3092
3369
  * @returns {any}
3093
3370
  */
3094
3371
  function create_hook_safe_helper_iife(setup_statements, component_element) {
3095
- return /** @type {any} */ ({
3096
- type: 'CallExpression',
3097
- callee: {
3098
- type: 'ArrowFunctionExpression',
3099
- params: [],
3100
- body: /** @type {any} */ ({
3101
- type: 'BlockStatement',
3102
- body: [
3103
- ...setup_statements,
3104
- {
3105
- type: 'ReturnStatement',
3106
- argument: component_element,
3107
- metadata: { path: [] },
3108
- },
3109
- ],
3110
- metadata: { path: [] },
3111
- }),
3112
- async: false,
3113
- generator: false,
3114
- expression: false,
3115
- metadata: { path: [] },
3116
- },
3117
- arguments: [],
3118
- optional: false,
3119
- metadata: { path: [] },
3120
- });
3372
+ return b.call(b.arrow([], b.block([...setup_statements, b.return(component_element)])));
3121
3373
  }
3122
3374
 
3123
3375
  /**
@@ -3130,19 +3382,7 @@ function create_helper_type_alias_declaration(helper_id, binding) {
3130
3382
 
3131
3383
  return {
3132
3384
  id: alias_id,
3133
- declaration: /** @type {any} */ ({
3134
- type: 'VariableDeclaration',
3135
- kind: 'const',
3136
- declarations: [
3137
- {
3138
- type: 'VariableDeclarator',
3139
- id: clone_identifier(alias_id),
3140
- init: create_generated_identifier(binding.name),
3141
- metadata: { path: [] },
3142
- },
3143
- ],
3144
- metadata: { path: [] },
3145
- }),
3385
+ declaration: b.const(clone_identifier(alias_id), create_generated_identifier(binding.name)),
3146
3386
  };
3147
3387
  }
3148
3388
 
@@ -3152,33 +3392,14 @@ function create_helper_type_alias_declaration(helper_id, binding) {
3152
3392
  * @returns {any}
3153
3393
  */
3154
3394
  function create_helper_props_type_literal(bindings, aliases) {
3155
- return /** @type {any} */ ({
3156
- type: 'TSTypeLiteral',
3157
- members: bindings.map(
3158
- (binding, i) =>
3159
- /** @type {any} */ ({
3160
- type: 'TSPropertySignature',
3161
- key: create_generated_identifier(binding.name),
3162
- computed: false,
3163
- optional: false,
3164
- readonly: false,
3165
- static: false,
3166
- kind: 'init',
3167
- typeAnnotation: {
3168
- type: 'TSTypeAnnotation',
3169
- typeAnnotation: {
3170
- type: 'TSTypeQuery',
3171
- exprName: clone_identifier(aliases[i].id),
3172
- typeArguments: null,
3173
- metadata: { path: [] },
3174
- },
3175
- metadata: { path: [] },
3176
- },
3177
- metadata: { path: [] },
3178
- }),
3395
+ return b.ts_type_literal(
3396
+ bindings.map((binding, i) =>
3397
+ b.ts_property_signature(
3398
+ create_generated_identifier(binding.name),
3399
+ b.ts_type_annotation(b.ts_type_query(clone_identifier(aliases[i].id))),
3400
+ ),
3179
3401
  ),
3180
- metadata: { path: [] },
3181
- });
3402
+ );
3182
3403
  }
3183
3404
 
3184
3405
  /**
@@ -3188,11 +3409,7 @@ function create_helper_props_type_literal(bindings, aliases) {
3188
3409
  */
3189
3410
  function create_typed_helper_props_pattern(bindings, props_type) {
3190
3411
  const pattern = create_helper_props_pattern(bindings);
3191
- /** @type {any} */ (pattern).typeAnnotation = {
3192
- type: 'TSTypeAnnotation',
3193
- typeAnnotation: props_type,
3194
- metadata: { path: [] },
3195
- };
3412
+ /** @type {any} */ (pattern).typeAnnotation = b.ts_type_annotation(props_type);
3196
3413
  return pattern;
3197
3414
  }
3198
3415
 
@@ -3201,19 +3418,7 @@ function create_typed_helper_props_pattern(bindings, props_type) {
3201
3418
  * @returns {any}
3202
3419
  */
3203
3420
  function create_helper_cache_declaration(cache_id) {
3204
- return /** @type {any} */ ({
3205
- type: 'VariableDeclaration',
3206
- kind: 'let',
3207
- declarations: [
3208
- {
3209
- type: 'VariableDeclarator',
3210
- id: clone_identifier(cache_id),
3211
- init: null,
3212
- metadata: { path: [] },
3213
- },
3214
- ],
3215
- metadata: { path: [] },
3216
- });
3421
+ return b.let(clone_identifier(cache_id));
3217
3422
  }
3218
3423
 
3219
3424
  /**
@@ -3223,44 +3428,27 @@ function create_helper_cache_declaration(cache_id) {
3223
3428
  * @returns {any}
3224
3429
  */
3225
3430
  function create_cached_helper_declaration(helper_id, cache_id, helper_init) {
3226
- return /** @type {any} */ ({
3227
- type: 'VariableDeclaration',
3228
- kind: 'const',
3229
- declarations: [
3230
- {
3231
- type: 'VariableDeclarator',
3232
- id: clone_identifier(helper_id),
3233
- init: {
3234
- type: 'LogicalExpression',
3235
- operator: '??',
3236
- left: clone_identifier(cache_id),
3237
- right: {
3238
- type: 'AssignmentExpression',
3239
- operator: '=',
3240
- left: clone_identifier(cache_id),
3241
- right: helper_init,
3242
- metadata: { path: [] },
3243
- },
3244
- metadata: { path: [] },
3245
- },
3246
- metadata: { path: [] },
3247
- },
3248
- ],
3249
- metadata: { path: [] },
3250
- });
3431
+ return b.const(
3432
+ clone_identifier(helper_id),
3433
+ b.logical(
3434
+ '??',
3435
+ clone_identifier(cache_id),
3436
+ b.assignment('=', clone_identifier(cache_id), helper_init),
3437
+ ),
3438
+ );
3251
3439
  }
3252
3440
 
3253
3441
  /**
3254
3442
  * @param {AST.Identifier} helper_id
3255
- * @param {any} helper_fn
3443
+ * @param {AST.FunctionExpression} helper_fn
3256
3444
  * @returns {AST.FunctionDeclaration}
3257
3445
  */
3258
3446
  function create_helper_function_declaration_from_expression(helper_id, helper_fn) {
3259
- return /** @type {any} */ ({
3447
+ return {
3260
3448
  ...helper_fn,
3261
3449
  type: 'FunctionDeclaration',
3262
3450
  id: clone_identifier(helper_id),
3263
- });
3451
+ };
3264
3452
  }
3265
3453
 
3266
3454
  /**
@@ -3410,9 +3598,7 @@ function to_jsx_child(node, transform_context) {
3410
3598
  case 'TSRXExpression':
3411
3599
  return to_jsx_expression_container(node.expression, node);
3412
3600
  case 'Html':
3413
- throw new Error(
3414
- `\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
3415
- );
3601
+ return report_html_template_unsupported_error(node, transform_context);
3416
3602
  case 'IfStatement':
3417
3603
  return (
3418
3604
  transform_context.platform.hooks?.controlFlow?.ifStatement ?? if_statement_to_jsx_child
@@ -3647,25 +3833,7 @@ function if_statement_to_jsx_child(node, transform_context) {
3647
3833
  }
3648
3834
 
3649
3835
  return to_jsx_expression_container(
3650
- /** @type {any} */ ({
3651
- type: 'CallExpression',
3652
- callee: {
3653
- type: 'ArrowFunctionExpression',
3654
- params: [],
3655
- body: /** @type {any} */ ({
3656
- type: 'BlockStatement',
3657
- body: [render_if_statement, create_null_return_statement()],
3658
- metadata: { path: [] },
3659
- }),
3660
- async: false,
3661
- generator: false,
3662
- expression: false,
3663
- metadata: { path: [] },
3664
- },
3665
- arguments: [],
3666
- optional: false,
3667
- metadata: { path: [] },
3668
- }),
3836
+ b.call(b.arrow([], b.block([render_if_statement, create_null_return_statement()]))),
3669
3837
  );
3670
3838
  }
3671
3839
 
@@ -3690,16 +3858,7 @@ function render_if_statement_to_conditional_expression(node) {
3690
3858
  }
3691
3859
  }
3692
3860
 
3693
- return set_loc(
3694
- /** @type {any} */ ({
3695
- type: 'ConditionalExpression',
3696
- test: node.test,
3697
- consequent,
3698
- alternate,
3699
- metadata: { path: [] },
3700
- }),
3701
- node,
3702
- );
3861
+ return set_loc(b.conditional(node.test, consequent, alternate), node);
3703
3862
  }
3704
3863
 
3705
3864
  /**
@@ -3769,15 +3928,8 @@ function find_key_expression_in_body(body_nodes) {
3769
3928
  * @param {any} source_node
3770
3929
  * @returns {any}
3771
3930
  */
3772
- function continue_to_bare_return(source_node) {
3773
- return set_loc(
3774
- /** @type {any} */ ({
3775
- type: 'ReturnStatement',
3776
- argument: null,
3777
- metadata: { path: [] },
3778
- }),
3779
- source_node,
3780
- );
3931
+ function continue_to_bare_return(source_node) {
3932
+ return set_loc(b.return(null), source_node);
3781
3933
  }
3782
3934
 
3783
3935
  /**
@@ -3903,36 +4055,17 @@ function for_of_statement_to_jsx_child(node, transform_context) {
3903
4055
  // Restore bindings
3904
4056
  transform_context.available_bindings = saved_bindings;
3905
4057
 
4058
+ const iter_callback = b.arrow(loop_params, b.block(body_statements));
4059
+
4060
+ if (transform_context.platform.imports.forOfIterableHelper) {
4061
+ transform_context.needs_for_of_iterable = true;
4062
+ return to_jsx_expression_container(
4063
+ b.call(b.id(MAP_ITERABLE_INTERNAL_NAME), node.right, iter_callback),
4064
+ );
4065
+ }
4066
+
3906
4067
  return to_jsx_expression_container(
3907
- /** @type {any} */ ({
3908
- type: 'CallExpression',
3909
- callee: {
3910
- type: 'MemberExpression',
3911
- object: node.right,
3912
- property: create_generated_identifier('map'),
3913
- computed: false,
3914
- optional: false,
3915
- metadata: { path: [] },
3916
- },
3917
- arguments: [
3918
- {
3919
- type: 'ArrowFunctionExpression',
3920
- params: loop_params,
3921
- body: /** @type {any} */ ({
3922
- type: 'BlockStatement',
3923
- body: body_statements,
3924
- metadata: { path: [] },
3925
- }),
3926
- async: false,
3927
- generator: false,
3928
- expression: false,
3929
- metadata: { path: [] },
3930
- },
3931
- ],
3932
- async: false,
3933
- optional: false,
3934
- metadata: { path: [] },
3935
- }),
4068
+ b.call(b.member(node.right, create_generated_identifier('map')), iter_callback),
3936
4069
  );
3937
4070
  }
3938
4071
 
@@ -3953,7 +4086,7 @@ function apply_key_to_loop_body(body_nodes, key_expression) {
3953
4086
  if (!has_key) {
3954
4087
  attributes.push({
3955
4088
  type: 'Attribute',
3956
- name: { type: 'Identifier', name: 'key', metadata: { path: [] } },
4089
+ name: b.id('key'),
3957
4090
  value: clone_expression_node(key_expression),
3958
4091
  shorthand: false,
3959
4092
  metadata: { path: [] },
@@ -3973,15 +4106,10 @@ function apply_key_to_loop_body(body_nodes, key_expression) {
3973
4106
 
3974
4107
  if (!has_key) {
3975
4108
  attributes.push(
3976
- /** @type {any} */ ({
3977
- type: 'JSXAttribute',
3978
- name: { type: 'JSXIdentifier', name: 'key', metadata: { path: [] } },
3979
- value: to_jsx_expression_container(
3980
- clone_expression_node(key_expression),
3981
- key_expression,
3982
- ),
3983
- metadata: { path: [] },
3984
- }),
4109
+ b.jsx_attribute(
4110
+ b.jsx_id('key'),
4111
+ to_jsx_expression_container(clone_expression_node(key_expression), key_expression),
4112
+ ),
3985
4113
  );
3986
4114
  }
3987
4115
  return;
@@ -4076,29 +4204,12 @@ function keyed_fragment_to_jsx_element(fragment, key_expression) {
4076
4204
  * @returns {ESTreeJSX.JSXExpressionContainer}
4077
4205
  */
4078
4206
  function switch_statement_to_jsx_child(node, transform_context) {
4207
+ const { setup_statements, switch_statement } = build_switch_with_lift(node, transform_context);
4208
+
4079
4209
  return to_jsx_expression_container(
4080
- /** @type {any} */ ({
4081
- type: 'CallExpression',
4082
- callee: {
4083
- type: 'ArrowFunctionExpression',
4084
- params: [],
4085
- body: /** @type {any} */ ({
4086
- type: 'BlockStatement',
4087
- body: [
4088
- create_render_switch_statement(node, transform_context),
4089
- create_null_return_statement(),
4090
- ],
4091
- metadata: { path: [] },
4092
- }),
4093
- async: false,
4094
- generator: false,
4095
- expression: false,
4096
- metadata: { path: [] },
4097
- },
4098
- arguments: [],
4099
- optional: false,
4100
- metadata: { path: [] },
4101
- }),
4210
+ b.call(
4211
+ b.arrow([], b.block([...setup_statements, switch_statement, create_null_return_statement()])),
4212
+ ),
4102
4213
  );
4103
4214
  }
4104
4215
 
@@ -4227,23 +4338,21 @@ function try_statement_to_jsx_child(node, transform_context) {
4227
4338
  // correctly identifies references to err/reset as non-static
4228
4339
  const saved_catch_bindings = transform_context.available_bindings;
4229
4340
  transform_context.available_bindings = new Map(saved_catch_bindings);
4341
+ const catch_scoped_names = new Set();
4230
4342
  for (const param of catch_params) {
4231
4343
  collect_pattern_bindings(param, transform_context.available_bindings);
4344
+ collect_pattern_names(param, catch_scoped_names);
4232
4345
  }
4346
+ validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
4347
+ catch_body_nodes,
4348
+ transform_context,
4349
+ catch_scoped_names,
4350
+ );
4233
4351
 
4234
- const fallback_fn = {
4235
- type: 'ArrowFunctionExpression',
4236
- params: catch_params,
4237
- body: /** @type {any} */ ({
4238
- type: 'BlockStatement',
4239
- body: build_render_statements(catch_body_nodes, true, transform_context),
4240
- metadata: { path: [] },
4241
- }),
4242
- async: false,
4243
- generator: false,
4244
- expression: false,
4245
- metadata: { path: [] },
4246
- };
4352
+ const fallback_fn = b.arrow(
4353
+ catch_params,
4354
+ b.block(build_render_statements(catch_body_nodes, true, transform_context)),
4355
+ );
4247
4356
 
4248
4357
  transform_context.available_bindings = saved_catch_bindings;
4249
4358
 
@@ -4256,40 +4365,10 @@ function try_statement_to_jsx_child(node, transform_context) {
4256
4365
 
4257
4366
  if (boundary_content && transform_context.inside_element_child) {
4258
4367
  result = to_jsx_expression_container(
4259
- /** @type {any} */ ({
4260
- type: 'CallExpression',
4261
- callee: { type: 'Identifier', name: 'TsrxErrorBoundary', metadata: { path: [] } },
4262
- arguments: [
4263
- {
4264
- type: 'ObjectExpression',
4265
- properties: [
4266
- {
4267
- type: 'Property',
4268
- key: { type: 'Identifier', name: 'fallback', metadata: { path: [] } },
4269
- value: fallback_fn,
4270
- kind: 'init',
4271
- method: false,
4272
- shorthand: false,
4273
- computed: false,
4274
- metadata: { path: [] },
4275
- },
4276
- {
4277
- type: 'Property',
4278
- key: { type: 'Identifier', name: 'content', metadata: { path: [] } },
4279
- value: boundary_content,
4280
- kind: 'init',
4281
- method: false,
4282
- shorthand: false,
4283
- computed: false,
4284
- metadata: { path: [] },
4285
- },
4286
- ],
4287
- metadata: { path: [] },
4288
- },
4289
- ],
4290
- optional: false,
4291
- metadata: { path: [] },
4292
- }),
4368
+ b.call(
4369
+ 'TsrxErrorBoundary',
4370
+ b.object([b.init('fallback', fallback_fn), b.init('content', boundary_content)]),
4371
+ ),
4293
4372
  );
4294
4373
 
4295
4374
  return result;
@@ -4298,21 +4377,12 @@ function try_statement_to_jsx_child(node, transform_context) {
4298
4377
  result = create_jsx_element(
4299
4378
  'TsrxErrorBoundary',
4300
4379
  [
4301
- {
4302
- type: 'JSXAttribute',
4303
- name: { type: 'JSXIdentifier', name: 'fallback', metadata: { path: [] } },
4304
- value: to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
4305
- metadata: { path: [] },
4306
- },
4380
+ b.jsx_attribute(
4381
+ b.jsx_id('fallback'),
4382
+ to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
4383
+ ),
4307
4384
  ...(boundary_content
4308
- ? [
4309
- {
4310
- type: 'JSXAttribute',
4311
- name: { type: 'JSXIdentifier', name: 'content', metadata: { path: [] } },
4312
- value: to_jsx_expression_container(boundary_content),
4313
- metadata: { path: [] },
4314
- },
4315
- ]
4385
+ ? [b.jsx_attribute(b.jsx_id('content'), to_jsx_expression_container(boundary_content))]
4316
4386
  : []),
4317
4387
  ],
4318
4388
  boundary_content ? [] : [result],
@@ -4361,73 +4431,29 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
4361
4431
  const imports = [];
4362
4432
 
4363
4433
  if (transform_context.needs_fragment && platform.imports.fragment) {
4364
- const fragment_source = platform.imports.fragment;
4365
- imports.push({
4366
- type: 'ImportDeclaration',
4367
- specifiers: [
4368
- {
4369
- type: 'ImportSpecifier',
4370
- imported: { type: 'Identifier', name: 'Fragment', metadata: { path: [] } },
4371
- local: { type: 'Identifier', name: 'Fragment', metadata: { path: [] } },
4372
- metadata: { path: [] },
4373
- },
4374
- ],
4375
- source: {
4376
- type: 'Literal',
4377
- value: fragment_source,
4378
- raw: `'${fragment_source}'`,
4379
- },
4380
- metadata: { path: [] },
4381
- });
4434
+ imports.push(b.imports([['Fragment', 'Fragment']], platform.imports.fragment));
4382
4435
  }
4383
4436
 
4384
4437
  if (transform_context.needs_suspense) {
4385
- imports.push({
4386
- type: 'ImportDeclaration',
4387
- specifiers: [
4388
- {
4389
- type: 'ImportSpecifier',
4390
- imported: { type: 'Identifier', name: 'Suspense', metadata: { path: [] } },
4391
- local: { type: 'Identifier', name: 'Suspense', metadata: { path: [] } },
4392
- metadata: { path: [] },
4393
- },
4394
- ],
4395
- source: {
4396
- type: 'Literal',
4397
- value: suspense_source,
4398
- raw: `'${suspense_source}'`,
4399
- },
4400
- metadata: { path: [] },
4401
- });
4438
+ imports.push(b.imports([['Suspense', 'Suspense']], suspense_source));
4439
+ }
4440
+
4441
+ if (transform_context.needs_for_of_iterable && platform.imports.forOfIterableHelper) {
4442
+ const specifiers = [b.import_specifier('map_iterable', MAP_ITERABLE_INTERNAL_NAME)];
4443
+ // The loop-scoped type alias `IterationValue<typeof source>` only
4444
+ // appears in the output when at least one hook-bearing for-of body
4445
+ // was lowered with non-module-scoped helpers (editor tooling sets
4446
+ // this for typeOnly virtual modules).
4447
+ if (transform_context.needs_iteration_value_type) {
4448
+ specifiers.push(b.import_specifier('IterationValue', ITERATION_VALUE_INTERNAL_NAME, 'type'));
4449
+ }
4450
+ imports.push(b.import_declaration(specifiers, platform.imports.forOfIterableHelper));
4402
4451
  }
4403
4452
 
4404
4453
  if (transform_context.needs_error_boundary) {
4405
- const error_boundary_source = platform.imports.errorBoundary;
4406
- imports.push({
4407
- type: 'ImportDeclaration',
4408
- specifiers: [
4409
- {
4410
- type: 'ImportSpecifier',
4411
- imported: {
4412
- type: 'Identifier',
4413
- name: 'TsrxErrorBoundary',
4414
- metadata: { path: [] },
4415
- },
4416
- local: {
4417
- type: 'Identifier',
4418
- name: 'TsrxErrorBoundary',
4419
- metadata: { path: [] },
4420
- },
4421
- metadata: { path: [] },
4422
- },
4423
- ],
4424
- source: {
4425
- type: 'Literal',
4426
- value: error_boundary_source,
4427
- raw: `'${error_boundary_source}'`,
4428
- },
4429
- metadata: { path: [] },
4430
- });
4454
+ imports.push(
4455
+ b.imports([['TsrxErrorBoundary', 'TsrxErrorBoundary']], platform.imports.errorBoundary),
4456
+ );
4431
4457
  }
4432
4458
 
4433
4459
  const merge_refs_source =
@@ -4445,67 +4471,31 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
4445
4471
  const ref_imports = new Map();
4446
4472
 
4447
4473
  if (merge_refs_source !== null) {
4448
- add_ref_import_specifier(ref_imports, merge_refs_source, {
4449
- type: 'ImportSpecifier',
4450
- imported: {
4451
- type: 'Identifier',
4452
- name: 'mergeRefs',
4453
- metadata: { path: [] },
4454
- },
4455
- local: {
4456
- type: 'Identifier',
4457
- name: MERGE_REFS_INTERNAL_NAME,
4458
- metadata: { path: [] },
4459
- },
4460
- metadata: { path: [] },
4461
- });
4474
+ add_ref_import_specifier(
4475
+ ref_imports,
4476
+ merge_refs_source,
4477
+ b.import_specifier('mergeRefs', MERGE_REFS_INTERNAL_NAME),
4478
+ );
4462
4479
  }
4463
4480
 
4464
4481
  if (ref_prop_source !== null) {
4465
- add_ref_import_specifier(ref_imports, ref_prop_source, {
4466
- type: 'ImportSpecifier',
4467
- imported: {
4468
- type: 'Identifier',
4469
- name: 'create_ref_prop',
4470
- metadata: { path: [] },
4471
- },
4472
- local: {
4473
- type: 'Identifier',
4474
- name: CREATE_REF_PROP_INTERNAL_NAME,
4475
- metadata: { path: [] },
4476
- },
4477
- metadata: { path: [] },
4478
- });
4482
+ add_ref_import_specifier(
4483
+ ref_imports,
4484
+ ref_prop_source,
4485
+ b.import_specifier('create_ref_prop', CREATE_REF_PROP_INTERNAL_NAME),
4486
+ );
4479
4487
  }
4480
4488
 
4481
4489
  if (normalize_spread_props_source !== null) {
4482
- add_ref_import_specifier(ref_imports, normalize_spread_props_source, {
4483
- type: 'ImportSpecifier',
4484
- imported: {
4485
- type: 'Identifier',
4486
- name: 'normalize_spread_props',
4487
- metadata: { path: [] },
4488
- },
4489
- local: {
4490
- type: 'Identifier',
4491
- name: NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
4492
- metadata: { path: [] },
4493
- },
4494
- metadata: { path: [] },
4495
- });
4490
+ add_ref_import_specifier(
4491
+ ref_imports,
4492
+ normalize_spread_props_source,
4493
+ b.import_specifier('normalize_spread_props', NORMALIZE_SPREAD_PROPS_INTERNAL_NAME),
4494
+ );
4496
4495
  }
4497
4496
 
4498
4497
  for (const [source, ref_specifiers] of ref_imports) {
4499
- imports.push({
4500
- type: 'ImportDeclaration',
4501
- specifiers: ref_specifiers,
4502
- source: {
4503
- type: 'Literal',
4504
- value: source,
4505
- raw: `'${source}'`,
4506
- },
4507
- metadata: { path: [] },
4508
- });
4498
+ imports.push(b.import_declaration(ref_specifiers, source));
4509
4499
  }
4510
4500
 
4511
4501
  if (imports.length > 0) {
@@ -4553,130 +4543,343 @@ function create_render_if_statement(node, transform_context) {
4553
4543
  true,
4554
4544
  );
4555
4545
  alternate = set_loc(
4556
- /** @type {any} */ ({
4557
- type: 'BlockStatement',
4558
- body: alternate_has_hooks
4546
+ b.block(
4547
+ alternate_has_hooks
4559
4548
  ? hook_safe_render_statements(alternate_body, undefined, transform_context)
4560
4549
  : build_render_statements(alternate_body, true, transform_context),
4561
- metadata: { path: [] },
4562
- }),
4550
+ ),
4563
4551
  node.alternate,
4564
4552
  );
4565
4553
  }
4566
4554
  }
4567
4555
 
4568
4556
  return set_loc(
4569
- {
4570
- type: 'IfStatement',
4571
- test: node.test,
4572
- consequent: set_loc(
4573
- /** @type {any} */ ({
4574
- type: 'BlockStatement',
4575
- body: consequent_has_hooks
4557
+ b.if(
4558
+ node.test,
4559
+ set_loc(
4560
+ b.block(
4561
+ consequent_has_hooks
4576
4562
  ? hook_safe_render_statements(consequent_body, undefined, transform_context)
4577
4563
  : build_render_statements(consequent_body, true, transform_context),
4578
- metadata: { path: [] },
4579
- }),
4564
+ ),
4580
4565
  node.consequent,
4581
4566
  ),
4582
4567
  alternate,
4583
- },
4568
+ ),
4584
4569
  node,
4585
4570
  );
4586
4571
  }
4587
4572
 
4588
4573
  /**
4589
- * @param {any} node
4590
- * @param {TransformContext} transform_context
4591
- * @returns {any}
4574
+ * Per-source-case information used by the switch lift to decide whether each
4575
+ * case body needs to be hoisted into its own helper component or can stay
4576
+ * inline.
4577
+ *
4578
+ * `own_body` is everything in the case's `consequent` up to (and including for
4579
+ * `return <expr>`, excluding for `break` / bare `return;`) the first
4580
+ * terminator. `has_terminator` records whether such a terminator was seen.
4581
+ *
4582
+ * @param {any[]} consequent
4583
+ * @returns {{ own_body: any[], has_terminator: boolean }}
4592
4584
  */
4593
- function create_render_switch_statement(node, transform_context) {
4594
- return /** @type {any} */ ({
4595
- type: 'SwitchStatement',
4596
- discriminant: node.discriminant,
4597
- cases: node.cases.map((/** @type {any} */ c) =>
4598
- create_render_switch_case(c, transform_context),
4599
- ),
4600
- metadata: { path: [] },
4601
- });
4585
+ function summarize_switch_case_body(consequent) {
4586
+ const own_body = [];
4587
+ let has_terminator = false;
4588
+ for (const child of consequent) {
4589
+ if (child.type === 'BreakStatement') {
4590
+ has_terminator = true;
4591
+ break;
4592
+ }
4593
+ if (child.type === 'ReturnStatement' && child.argument == null) {
4594
+ has_terminator = true;
4595
+ break;
4596
+ }
4597
+ own_body.push(child);
4598
+ if (child.type === 'ReturnStatement') {
4599
+ // `return <expr>;` — keep it in own_body so build_render_statements
4600
+ // can emit it as the terminal return for this case, then stop
4601
+ // collecting further nodes.
4602
+ has_terminator = true;
4603
+ break;
4604
+ }
4605
+ }
4606
+ return { own_body, has_terminator };
4602
4607
  }
4603
4608
 
4604
4609
  /**
4605
- * @param {any} switch_case
4606
- * @param {TransformContext} transform_context
4610
+ * Clone a helper's `component_element` for embedding in another case arm or
4611
+ * inside another helper's body. Locations are stripped because the same
4612
+ * element appears in multiple positions; only the helper's *definition* (the
4613
+ * lifted function) keeps the source position so editor IntelliSense doesn't
4614
+ * see double/triple hits per source range.
4615
+ *
4616
+ * @param {{ component_element: ESTreeJSX.JSXElement }} helper
4607
4617
  * @returns {any}
4608
4618
  */
4609
- function create_render_switch_case(switch_case, transform_context) {
4610
- const consequent = flatten_switch_consequent(switch_case.consequent || []);
4619
+ export function clone_switch_helper_invocation(helper) {
4620
+ return clone_expression_node(helper.component_element, false);
4621
+ }
4611
4622
 
4612
- // Strip trailing break statements for hook analysis
4613
- const body_without_break = [];
4614
- for (const child of consequent) {
4615
- if (child.type === 'BreakStatement') break;
4616
- body_without_break.push(child);
4617
- }
4623
+ /**
4624
+ * Plan the switch lift: decide which case bodies to hoist into their own
4625
+ * helper components, build them in reverse so each helper can chain into the
4626
+ * next, and return everything callers need to construct a target-specific
4627
+ * switch shape (a JS `switch` for React/Preact/Vue or `<Switch>/<Match>` for
4628
+ * Solid). Centralizes the lift bookkeeping so both consumers see the same
4629
+ * hook-detection rules, duplication analysis, and helper-id numbering.
4630
+ *
4631
+ * Returned helpers — when non-null — are already constructed via
4632
+ * `create_hook_safe_helper`, which is the same path hook-bearing case bodies
4633
+ * have always used. Locally-scoped helpers have their declarations in
4634
+ * `setup_statements`; module-scoped helpers (the client transform default on
4635
+ * React, Vue, and Solid) already pushed their declarations into
4636
+ * `transform_context.helper_state.helpers`, so `setup_statements` is empty.
4637
+ *
4638
+ * @param {any} switch_node
4639
+ * @param {TransformContext} transform_context
4640
+ * @returns {{
4641
+ * case_info: Array<{ own_body: any[], has_terminator: boolean }>,
4642
+ * case_helpers: Array<{ setup_statements: any[], component_element: ESTreeJSX.JSXElement } | null>,
4643
+ * find_next_helper_after: (from_index: number) => { component_element: ESTreeJSX.JSXElement } | null,
4644
+ * setup_statements: any[],
4645
+ * }}
4646
+ */
4647
+ export function plan_switch_lift(switch_node, transform_context) {
4648
+ const case_info = switch_node.cases.map((/** @type {any} */ c) => {
4649
+ const consequent = flatten_switch_consequent(c.consequent || []);
4650
+ return summarize_switch_case_body(consequent);
4651
+ });
4618
4652
 
4619
- if (body_contains_top_level_hook_call(body_without_break, transform_context, true)) {
4620
- return /** @type {any} */ ({
4621
- type: 'SwitchCase',
4622
- test: switch_case.test,
4623
- consequent: hook_safe_render_statements(body_without_break, undefined, transform_context),
4624
- metadata: { path: [] },
4625
- });
4626
- }
4653
+ // A case body needs to be lifted iff (a) it would render in more than one
4654
+ // arm after fall-through expansion, or (b) it contains hooks (which always
4655
+ // went through the lift pipeline before this change). Duplication happens
4656
+ // exactly when the previous case has no terminator — that's the only way
4657
+ // an earlier arm can reach this body via JS fall-through semantics.
4658
+ const needs_helper = case_info.map(
4659
+ (/** @type {{ own_body: any[], has_terminator: boolean }} */ info, /** @type {number} */ k) => {
4660
+ if (info.own_body.length === 0) return false;
4661
+ if (body_contains_top_level_hook_call(info.own_body, transform_context, true)) {
4662
+ return true;
4663
+ }
4664
+ if (k === 0) return false;
4665
+ return !case_info[k - 1].has_terminator;
4666
+ },
4667
+ );
4627
4668
 
4628
- const case_body = [];
4629
- const render_nodes = [];
4630
- let has_terminal = false;
4669
+ // Pre-allocate helper ids in source order so the snapshot's
4670
+ // `StatementBodyHook<N>` numbering reads top-to-bottom by case position
4671
+ // even though we build helpers in reverse below.
4672
+ /** @type {Array<AST.Identifier | null>} */
4673
+ const helper_ids = needs_helper.map((/** @type {boolean} */ needs) =>
4674
+ needs
4675
+ ? create_generated_identifier(create_local_statement_component_name(transform_context))
4676
+ : null,
4677
+ );
4631
4678
 
4632
- for (const child of consequent) {
4633
- if (child.type === 'BreakStatement') {
4634
- if (render_nodes.length > 0 && !has_terminal) {
4635
- case_body.push(create_component_return_statement(render_nodes, switch_case));
4636
- } else if (!has_terminal) {
4637
- case_body.push(child);
4638
- }
4639
- has_terminal = true;
4640
- break;
4641
- }
4679
+ /** @type {Array<{ setup_statements: any[], component_element: ESTreeJSX.JSXElement } | null>} */
4680
+ const case_helpers = new Array(switch_node.cases.length).fill(null);
4642
4681
 
4643
- if (is_bare_return_statement(child)) {
4644
- case_body.push(create_component_return_statement(render_nodes, child));
4645
- has_terminal = true;
4646
- break;
4682
+ /**
4683
+ * Find the next downstream helper this arm chains into when it has no
4684
+ * terminator: scan forward past any empty cases until we hit either a
4685
+ * helper-bearing case or a case whose body has a terminator (which stops
4686
+ * the chain — JS would have `break`/`return`ed out at that point).
4687
+ *
4688
+ * @param {number} from_index
4689
+ * @returns {{ component_element: ESTreeJSX.JSXElement } | null}
4690
+ */
4691
+ function find_next_helper_after(from_index) {
4692
+ for (let j = from_index + 1; j < switch_node.cases.length; j++) {
4693
+ if (case_helpers[j]) return case_helpers[j];
4694
+ if (case_info[j].has_terminator) return null;
4647
4695
  }
4696
+ return null;
4697
+ }
4648
4698
 
4649
- if (is_jsx_child(child)) {
4650
- render_nodes.push(to_jsx_child(child, transform_context));
4651
- } else if (is_bare_render_expression(child)) {
4652
- render_nodes.push(to_jsx_expression_container(child, child));
4653
- } else {
4654
- case_body.push(child);
4699
+ for (let i = switch_node.cases.length - 1; i >= 0; i--) {
4700
+ if (!needs_helper[i]) continue;
4701
+ const { own_body, has_terminator } = case_info[i];
4702
+
4703
+ let helper_body = own_body;
4704
+ if (!has_terminator) {
4705
+ const next_helper = find_next_helper_after(i);
4706
+ if (next_helper) {
4707
+ helper_body = [...own_body, clone_switch_helper_invocation(next_helper)];
4708
+ }
4655
4709
  }
4710
+
4711
+ case_helpers[i] = create_hook_safe_helper(
4712
+ helper_body,
4713
+ undefined,
4714
+ switch_node.cases[i],
4715
+ transform_context,
4716
+ /** @type {any} */ (helper_ids[i]),
4717
+ );
4656
4718
  }
4657
4719
 
4658
- if (!has_terminal && render_nodes.length > 0) {
4659
- case_body.push(create_component_return_statement(render_nodes, switch_case));
4720
+ // Hoist all helpers' setup statements above the switch in source order so
4721
+ // the switch body stays a pure dispatcher.
4722
+ const setup_statements = [];
4723
+ for (const helper of case_helpers) {
4724
+ if (helper) setup_statements.push(...helper.setup_statements);
4660
4725
  }
4661
4726
 
4662
- return /** @type {any} */ ({
4663
- type: 'SwitchCase',
4664
- test: switch_case.test,
4665
- consequent: case_body,
4666
- metadata: { path: [] },
4667
- });
4727
+ return {
4728
+ case_info,
4729
+ case_helpers,
4730
+ find_next_helper_after,
4731
+ setup_statements,
4732
+ };
4668
4733
  }
4669
4734
 
4670
4735
  /**
4671
- * @returns {any}
4736
+ * Switch lift for fall-through deduplication. Reuses the same `create_hook_safe_helper`
4737
+ * pipeline as hook-bearing case bodies: every case whose body would otherwise
4738
+ * appear in 2+ arms (because the previous case had no `break` / `return`) is
4739
+ * hoisted into its own helper component, and each upstream arm references the
4740
+ * next helper at the end of its own body to materialize JS fall-through at
4741
+ * render time. Cases whose bodies live in exactly one arm stay inline so the
4742
+ * common (break-terminated) shape compiles to the same simple switch as before
4743
+ * the lift was introduced.
4744
+ *
4745
+ * The chain pattern:
4746
+ * helper_idle = () => <><Online/><Helper_active/></>
4747
+ * helper_active = () => <><Away/><Helper_offline/></>
4748
+ * helper_offline = () => <Offline/>
4749
+ *
4750
+ * case "idle": return <Helper_idle/>
4751
+ * case "active": return <Helper_active/>
4752
+ * case "offline": return <Helper_offline/>
4753
+ *
4754
+ * Each case body appears exactly once in the generated module — matching how
4755
+ * we already handle hook-bearing case bodies — which keeps the bundle from
4756
+ * growing quadratically in case count and means editor mappings are 1:1.
4757
+ *
4758
+ * @param {any} switch_node
4759
+ * @param {TransformContext} transform_context
4760
+ * @returns {{ setup_statements: any[], switch_statement: any }}
4672
4761
  */
4673
- function create_null_return_statement() {
4762
+ function build_switch_with_lift(switch_node, transform_context) {
4763
+ const { case_info, case_helpers, find_next_helper_after, setup_statements } = plan_switch_lift(
4764
+ switch_node,
4765
+ transform_context,
4766
+ );
4767
+
4768
+ const new_cases = switch_node.cases.map(
4769
+ (/** @type {any} */ original_case, /** @type {number} */ i) => {
4770
+ const helper = case_helpers[i];
4771
+ if (helper) {
4772
+ return /** @type {any} */ ({
4773
+ type: 'SwitchCase',
4774
+ test: original_case.test,
4775
+ consequent: [
4776
+ create_component_return_statement([helper.component_element], original_case),
4777
+ ],
4778
+ metadata: { path: [] },
4779
+ });
4780
+ }
4781
+
4782
+ const { own_body, has_terminator } = case_info[i];
4783
+
4784
+ if (own_body.length === 0 && !has_terminator) {
4785
+ // Alias-pattern empty case (`case 'a': case 'b': ...`) — keep
4786
+ // the arm body empty so JS falls through to the next case at
4787
+ // runtime, where the helper invocation actually lives.
4788
+ return /** @type {any} */ ({
4789
+ type: 'SwitchCase',
4790
+ test: original_case.test,
4791
+ consequent: [],
4792
+ metadata: { path: [] },
4793
+ });
4794
+ }
4795
+
4796
+ const case_body = [];
4797
+ const render_nodes = [];
4798
+ let has_terminal = false;
4799
+
4800
+ for (const child of own_body) {
4801
+ if (is_bare_return_statement(child)) {
4802
+ case_body.push(create_component_return_statement(render_nodes, child));
4803
+ has_terminal = true;
4804
+ break;
4805
+ }
4806
+ if (child.type === 'ReturnStatement') {
4807
+ case_body.push(child);
4808
+ has_terminal = true;
4809
+ break;
4810
+ }
4811
+ if (is_jsx_child(child)) {
4812
+ render_nodes.push(to_jsx_child(child, transform_context));
4813
+ } else if (is_bare_render_expression(child)) {
4814
+ render_nodes.push(to_jsx_expression_container(child, child));
4815
+ } else {
4816
+ case_body.push(child);
4817
+ }
4818
+ }
4819
+
4820
+ if (!has_terminal && !has_terminator) {
4821
+ const next_helper = find_next_helper_after(i);
4822
+ if (next_helper) {
4823
+ render_nodes.push(clone_switch_helper_invocation(next_helper));
4824
+ }
4825
+ }
4826
+
4827
+ if (!has_terminal) {
4828
+ if (render_nodes.length > 0) {
4829
+ case_body.push(create_component_return_statement(render_nodes, original_case));
4830
+ } else if (has_terminator) {
4831
+ // Empty body with explicit `break;` / bare `return;` — keep
4832
+ // a `break` so JS doesn't fall through into the next case
4833
+ // (which may now hold the lifted helper invocation).
4834
+ case_body.push(
4835
+ /** @type {any} */ ({
4836
+ type: 'BreakStatement',
4837
+ label: null,
4838
+ metadata: { path: [] },
4839
+ }),
4840
+ );
4841
+ } else if (case_body.length > 0) {
4842
+ // Statements-only inline case without terminator. We've
4843
+ // already inlined the downstream chain via the helper
4844
+ // reference above, so emit a `break` to stop the runtime
4845
+ // from re-running downstream statements via JS fall-through.
4846
+ case_body.push(
4847
+ /** @type {any} */ ({
4848
+ type: 'BreakStatement',
4849
+ label: null,
4850
+ metadata: { path: [] },
4851
+ }),
4852
+ );
4853
+ }
4854
+ }
4855
+
4856
+ return /** @type {any} */ ({
4857
+ type: 'SwitchCase',
4858
+ test: original_case.test,
4859
+ consequent: case_body,
4860
+ metadata: { path: [] },
4861
+ });
4862
+ },
4863
+ );
4864
+
4674
4865
  return {
4675
- type: 'ReturnStatement',
4676
- argument: { type: 'Literal', value: null, raw: 'null' },
4866
+ setup_statements,
4867
+ switch_statement: /** @type {any} */ ({
4868
+ type: 'SwitchStatement',
4869
+ discriminant: switch_node.discriminant,
4870
+ cases: new_cases,
4871
+ metadata: { path: [] },
4872
+ }),
4677
4873
  };
4678
4874
  }
4679
4875
 
4876
+ /**
4877
+ * @returns {any}
4878
+ */
4879
+ function create_null_return_statement() {
4880
+ return b.return(b.literal(null));
4881
+ }
4882
+
4680
4883
  /**
4681
4884
  * @param {AST.Expression} expression
4682
4885
  * @param {any} [source_node]
@@ -4775,10 +4978,7 @@ function normalize_named_ref_attributes(attrs, is_host, transform_context) {
4775
4978
  return {
4776
4979
  ...attr,
4777
4980
  metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
4778
- name:
4779
- attr.name?.type === 'JSXIdentifier'
4780
- ? { ...attr.name, name: 'ref' }
4781
- : { type: 'Identifier', name: 'ref', metadata: { path: [] } },
4981
+ name: attr.name?.type === 'JSXIdentifier' ? { ...attr.name, name: 'ref' } : b.id('ref'),
4782
4982
  };
4783
4983
  });
4784
4984
  }
@@ -4919,6 +5119,7 @@ function wrap_jsx_setup_declarations(expression, in_jsx_child) {
4919
5119
  [],
4920
5120
  b.block([...declarations, b.return(return_expression)], expression),
4921
5121
  false,
5122
+ undefined,
4922
5123
  expression,
4923
5124
  ),
4924
5125
  );
@@ -5070,22 +5271,8 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
5070
5271
 
5071
5272
  const merged_value =
5072
5273
  strategy === 'merge-refs'
5073
- ? /** @type {any} */ ({
5074
- type: 'CallExpression',
5075
- callee: {
5076
- type: 'Identifier',
5077
- name: MERGE_REFS_INTERNAL_NAME,
5078
- metadata: { path: [] },
5079
- },
5080
- arguments: ref_exprs,
5081
- optional: false,
5082
- metadata: { path: [] },
5083
- })
5084
- : /** @type {any} */ ({
5085
- type: 'ArrayExpression',
5086
- elements: ref_exprs,
5087
- metadata: { path: [] },
5088
- });
5274
+ ? b.call(b.id(MERGE_REFS_INTERNAL_NAME), ...ref_exprs)
5275
+ : b.array(ref_exprs);
5089
5276
 
5090
5277
  if (strategy === 'merge-refs') {
5091
5278
  transform_context.needs_merge_refs = true;
@@ -5099,11 +5286,7 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
5099
5286
  const merged_name = build_jsx_id('ref', source_attr?.name);
5100
5287
  const merged_attr = build_jsx_attribute(
5101
5288
  merged_name,
5102
- /** @type {any} */ ({
5103
- type: 'JSXExpressionContainer',
5104
- expression: merged_value,
5105
- metadata: { path: [] },
5106
- }),
5289
+ b.jsx_expression_container(merged_value),
5107
5290
  false,
5108
5291
  source_attr,
5109
5292
  );
@@ -5138,6 +5321,8 @@ function is_jsx_ref_attribute(attr) {
5138
5321
  export const MERGE_REFS_INTERNAL_NAME = '__mergeRefs';
5139
5322
  export const CREATE_REF_PROP_INTERNAL_NAME = '__create_ref_prop';
5140
5323
  export const NORMALIZE_SPREAD_PROPS_INTERNAL_NAME = '__normalize_spread_props';
5324
+ export const MAP_ITERABLE_INTERNAL_NAME = '__map_iterable';
5325
+ export const ITERATION_VALUE_INTERNAL_NAME = '__IterationValue';
5141
5326
 
5142
5327
  /**
5143
5328
  * @param {any} attr
@@ -5211,10 +5396,7 @@ export function to_jsx_attribute(attr, transform_context) {
5211
5396
  attr_name.type === 'Identifier' &&
5212
5397
  attr_name.name === 'class'
5213
5398
  ) {
5214
- attr_name = set_loc(
5215
- /** @type {any} */ ({ type: 'Identifier', name: 'className', metadata: { path: [] } }),
5216
- attr.name,
5217
- );
5399
+ attr_name = set_loc(b.id('className'), attr.name);
5218
5400
  }
5219
5401
 
5220
5402
  const name =
@@ -5277,16 +5459,7 @@ function create_ref_prop_call(node, transform_context) {
5277
5459
 
5278
5460
  if (argument.type === 'Identifier' || argument.type === 'MemberExpression') {
5279
5461
  args.push(
5280
- b.arrow(
5281
- [b.id('v')],
5282
- /** @type {any} */ ({
5283
- type: 'AssignmentExpression',
5284
- operator: '=',
5285
- left: clone_expression_node(argument, false),
5286
- right: b.id('v'),
5287
- metadata: { path: [] },
5288
- }),
5289
- ),
5462
+ b.arrow([b.id('v')], b.assignment('=', clone_expression_node(argument, false), b.id('v'))),
5290
5463
  );
5291
5464
  }
5292
5465
 
@@ -5300,57 +5473,19 @@ function create_ref_prop_call(node, transform_context) {
5300
5473
  */
5301
5474
  function dynamic_element_to_jsx_child(node, transform_context) {
5302
5475
  const dynamic_id = set_loc(create_generated_identifier('DynamicElement'), node.id);
5303
- const alias_declaration = set_loc(
5304
- /** @type {any} */ ({
5305
- type: 'VariableDeclaration',
5306
- kind: 'const',
5307
- declarations: [
5308
- {
5309
- type: 'VariableDeclarator',
5310
- id: dynamic_id,
5311
- init: clone_expression_node(node.id),
5312
- metadata: { path: [] },
5313
- },
5314
- ],
5315
- metadata: { path: [] },
5316
- }),
5317
- node,
5318
- );
5476
+ const alias_declaration = set_loc(b.const(dynamic_id, clone_expression_node(node.id)), node);
5319
5477
  const jsx_element = create_dynamic_jsx_element(dynamic_id, node, transform_context);
5320
5478
 
5321
5479
  return to_jsx_expression_container(
5322
- /** @type {any} */ ({
5323
- type: 'CallExpression',
5324
- callee: {
5325
- type: 'ArrowFunctionExpression',
5326
- params: [],
5327
- body: /** @type {any} */ ({
5328
- type: 'BlockStatement',
5329
- body: [
5330
- alias_declaration,
5331
- {
5332
- type: 'ReturnStatement',
5333
- argument: {
5334
- type: 'ConditionalExpression',
5335
- test: clone_identifier(dynamic_id),
5336
- consequent: jsx_element,
5337
- alternate: create_null_literal(),
5338
- metadata: { path: [] },
5339
- },
5340
- metadata: { path: [] },
5341
- },
5342
- ],
5343
- metadata: { path: [] },
5344
- }),
5345
- async: false,
5346
- generator: false,
5347
- expression: false,
5348
- metadata: { path: [] },
5349
- },
5350
- arguments: [],
5351
- optional: false,
5352
- metadata: { path: [] },
5353
- }),
5480
+ b.call(
5481
+ b.arrow(
5482
+ [],
5483
+ b.block([
5484
+ alias_declaration,
5485
+ b.return(b.conditional(clone_identifier(dynamic_id), jsx_element, create_null_literal())),
5486
+ ]),
5487
+ ),
5488
+ ),
5354
5489
  node,
5355
5490
  );
5356
5491
  }