@tsrx/core 0.1.2 → 0.1.4

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.
@@ -57,6 +57,79 @@ import {
57
57
  } from '../jsx-interleave.js';
58
58
  import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
59
59
 
60
+ const HOOK_OUTER_ASSIGNMENT_ERROR =
61
+ 'Hook calls inside conditional or repeated TSRX scopes must keep their results local to the generated hook component.';
62
+ const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
63
+ 'Hook callbacks inside conditional or repeated TSRX scopes must not mutate bindings declared outside the generated hook component.';
64
+ const TEMPLATE_FRAGMENT_ERROR =
65
+ '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>.';
66
+
67
+ /**
68
+ * @param {AST.Node} node
69
+ * @param {TransformContext} transform_context
70
+ */
71
+ function report_html_template_unsupported_error(node, transform_context) {
72
+ // this should be a fatal error so we don't pass the errors collection,
73
+ // since we don't have a transform for the Html node
74
+ error(
75
+ `\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
76
+ transform_context.filename,
77
+ node,
78
+ );
79
+ }
80
+
81
+ /**
82
+ * @param {AST.Node} node
83
+ * @param {TransformContext} transform_context
84
+ */
85
+ function report_jsx_fragment_in_tsrx_error(node, transform_context) {
86
+ error(
87
+ TEMPLATE_FRAGMENT_ERROR,
88
+ transform_context.filename,
89
+ node,
90
+ transform_context.errors,
91
+ transform_context.comments,
92
+ );
93
+ }
94
+
95
+ /**
96
+ * @param {AST.Node} node
97
+ * @param {string[]} names
98
+ * @param {string} hook_name
99
+ * @param {TransformContext} transform_context
100
+ * @returns {void}
101
+ */
102
+ function report_hook_outer_assignment_error(node, names, hook_name, transform_context) {
103
+ const target =
104
+ names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
105
+ error(
106
+ `${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.`,
107
+ transform_context.filename,
108
+ node,
109
+ transform_context.errors,
110
+ transform_context.comments,
111
+ );
112
+ }
113
+
114
+ /**
115
+ * @param {AST.Node} node
116
+ * @param {string[]} names
117
+ * @param {string} hook_name
118
+ * @param {TransformContext} transform_context
119
+ * @returns {void}
120
+ */
121
+ function report_hook_callback_outer_mutation_error(node, names, hook_name, transform_context) {
122
+ const target =
123
+ names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
124
+ error(
125
+ `${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.`,
126
+ transform_context.filename,
127
+ node,
128
+ transform_context.errors,
129
+ transform_context.comments,
130
+ );
131
+ }
132
+
60
133
  /**
61
134
  * Local alias for the shared `JsxTransformContext`. Kept as a typedef so the
62
135
  * rest of this file's `@param {TransformContext}` annotations don't all have
@@ -449,9 +522,12 @@ export function createJsxTransform(platform) {
449
522
  // (e.g. segments.js reading node.value.metadata.is_component on class
450
523
  // methods) don't trip on an undefined metadata object. Ripple's analyze
451
524
  // 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,
525
+ // If a plain JS function contains a hook-bearing <tsrx> expression,
526
+ // give it a temporary helper scope so extracted hook components can
527
+ // be emitted with stable identities just like component-body helpers.
528
+ FunctionDeclaration: transform_function_with_hook_helpers,
529
+ FunctionExpression: transform_function_with_hook_helpers,
530
+ ArrowFunctionExpression: transform_function_with_hook_helpers,
455
531
 
456
532
  RefExpression(node) {
457
533
  return create_ref_prop_call(node, transform_context);
@@ -682,7 +758,7 @@ function build_component_statements(body_nodes, transform_context) {
682
758
  function build_render_statements(body_nodes, return_null_when_empty, transform_context) {
683
759
  const statements = [];
684
760
  const render_nodes = [];
685
- let has_bare_return = false;
761
+ let has_terminal_return = false;
686
762
 
687
763
  // Create a new bindings map so inner-scope bindings from
688
764
  // collect_statement_bindings don't leak to the caller's scope.
@@ -707,7 +783,13 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
707
783
  if (is_bare_return_statement(child)) {
708
784
  statements.push(create_component_return_statement(render_nodes, child));
709
785
  render_nodes.length = 0;
710
- has_bare_return = true;
786
+ has_terminal_return = true;
787
+ continue;
788
+ }
789
+
790
+ if (child?.type === 'ReturnStatement' && child.argument != null) {
791
+ statements.push(child);
792
+ has_terminal_return = true;
711
793
  continue;
712
794
  }
713
795
 
@@ -968,7 +1050,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
968
1050
  }
969
1051
 
970
1052
  const return_arg = build_return_expression(render_nodes);
971
- if (return_arg || (return_null_when_empty && !has_bare_return)) {
1053
+ if (return_arg || (return_null_when_empty && !has_terminal_return)) {
972
1054
  statements.push({
973
1055
  type: 'ReturnStatement',
974
1056
  argument: return_arg || { type: 'Literal', value: null, raw: 'null' },
@@ -1244,6 +1326,156 @@ function create_helper_state(base_name) {
1244
1326
  };
1245
1327
  }
1246
1328
 
1329
+ /**
1330
+ * @param {any} node
1331
+ * @param {{ next: () => any, state: TransformContext }} context
1332
+ * @returns {any}
1333
+ */
1334
+ function transform_function_with_hook_helpers(node, { next, state }) {
1335
+ if (state.helper_state || !function_contains_hook_bearing_tsrx(node, state)) {
1336
+ return ensure_function_metadata(node, { next });
1337
+ }
1338
+
1339
+ const helper_state = create_helper_state(get_function_helper_base_name(node));
1340
+ const saved_helper_state = state.helper_state;
1341
+ const saved_bindings = state.available_bindings;
1342
+
1343
+ state.helper_state = helper_state;
1344
+ state.available_bindings = collect_function_scope_bindings(node);
1345
+
1346
+ const inner = /** @type {any} */ (next() ?? node);
1347
+
1348
+ state.helper_state = saved_helper_state;
1349
+ state.available_bindings = saved_bindings;
1350
+
1351
+ ensure_function_metadata(inner, { next: () => inner });
1352
+ if (helper_state.helpers.length || helper_state.statics.length) {
1353
+ inner.metadata = {
1354
+ ...(inner.metadata || {}),
1355
+ generated_helpers: helper_state.helpers,
1356
+ generated_statics: helper_state.statics,
1357
+ };
1358
+ }
1359
+
1360
+ return inner;
1361
+ }
1362
+
1363
+ /**
1364
+ * @param {any} node
1365
+ * @returns {string}
1366
+ */
1367
+ function get_function_helper_base_name(node) {
1368
+ if (node.id?.type === 'Identifier') {
1369
+ return node.id.name;
1370
+ }
1371
+ return 'Tsrx';
1372
+ }
1373
+
1374
+ /**
1375
+ * @param {any} node
1376
+ * @returns {Map<string, AST.Identifier>}
1377
+ */
1378
+ function collect_function_scope_bindings(node) {
1379
+ const bindings = collect_param_bindings(node.params || []);
1380
+ collect_descendant_declaration_bindings(node.body, bindings);
1381
+ return bindings;
1382
+ }
1383
+
1384
+ /**
1385
+ * @param {any} node
1386
+ * @param {Map<string, AST.Identifier>} bindings
1387
+ * @returns {void}
1388
+ */
1389
+ function collect_descendant_declaration_bindings(node, bindings) {
1390
+ if (!node || typeof node !== 'object') {
1391
+ return;
1392
+ }
1393
+
1394
+ if (node.type === 'VariableDeclaration') {
1395
+ for (const declaration of node.declarations || []) {
1396
+ collect_pattern_bindings(declaration.id, bindings);
1397
+ }
1398
+ }
1399
+
1400
+ if (
1401
+ (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') &&
1402
+ node.id?.type === 'Identifier'
1403
+ ) {
1404
+ bindings.set(node.id.name, node.id);
1405
+ }
1406
+
1407
+ if (
1408
+ node.type === 'FunctionDeclaration' ||
1409
+ node.type === 'FunctionExpression' ||
1410
+ node.type === 'ArrowFunctionExpression' ||
1411
+ node.type === 'Component'
1412
+ ) {
1413
+ return;
1414
+ }
1415
+
1416
+ if (Array.isArray(node)) {
1417
+ for (const child of node) {
1418
+ collect_descendant_declaration_bindings(child, bindings);
1419
+ }
1420
+ return;
1421
+ }
1422
+
1423
+ for (const key of Object.keys(node)) {
1424
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1425
+ continue;
1426
+ }
1427
+ collect_descendant_declaration_bindings(node[key], bindings);
1428
+ }
1429
+ }
1430
+
1431
+ /**
1432
+ * @param {any} node
1433
+ * @param {TransformContext} transform_context
1434
+ * @returns {boolean}
1435
+ */
1436
+ function function_contains_hook_bearing_tsrx(node, transform_context) {
1437
+ return node_contains_hook_bearing_tsrx(node.body, transform_context);
1438
+ }
1439
+
1440
+ /**
1441
+ * @param {any} node
1442
+ * @param {TransformContext} transform_context
1443
+ * @returns {boolean}
1444
+ */
1445
+ function node_contains_hook_bearing_tsrx(node, transform_context) {
1446
+ if (!node || typeof node !== 'object') {
1447
+ return false;
1448
+ }
1449
+
1450
+ if (Array.isArray(node)) {
1451
+ return node.some((child) => node_contains_hook_bearing_tsrx(child, transform_context));
1452
+ }
1453
+
1454
+ if (node.type === 'Tsrx') {
1455
+ return body_contains_top_level_hook_call(node.children || [], transform_context, true);
1456
+ }
1457
+
1458
+ if (
1459
+ node.type === 'FunctionDeclaration' ||
1460
+ node.type === 'FunctionExpression' ||
1461
+ node.type === 'ArrowFunctionExpression' ||
1462
+ node.type === 'Component'
1463
+ ) {
1464
+ return false;
1465
+ }
1466
+
1467
+ for (const key of Object.keys(node)) {
1468
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1469
+ continue;
1470
+ }
1471
+ if (node_contains_hook_bearing_tsrx(node[key], transform_context)) {
1472
+ return true;
1473
+ }
1474
+ }
1475
+
1476
+ return false;
1477
+ }
1478
+
1247
1479
  /**
1248
1480
  * @param {TransformContext} transform_context
1249
1481
  * @returns {boolean}
@@ -2225,15 +2457,20 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
2225
2457
 
2226
2458
  const saved_bindings = transform_context.available_bindings;
2227
2459
  transform_context.available_bindings = new Map(saved_bindings);
2460
+ const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
2228
2461
  for (const param of loop_params) {
2229
2462
  collect_pattern_bindings(param, transform_context.available_bindings);
2230
2463
  }
2464
+ validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
2465
+ original_loop_body,
2466
+ transform_context,
2467
+ loop_scoped_names,
2468
+ );
2231
2469
 
2232
2470
  const all_helper_bindings = get_referenced_helper_bindings(
2233
2471
  loop_body,
2234
2472
  transform_context.available_bindings,
2235
2473
  );
2236
- const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
2237
2474
  const outer_bindings = all_helper_bindings.filter((b) => !loop_scoped_names.has(b.name));
2238
2475
  const loop_bindings = all_helper_bindings.filter((b) => loop_scoped_names.has(b.name));
2239
2476
 
@@ -2626,9 +2863,6 @@ function is_null_literal(node) {
2626
2863
  return node?.type === 'Literal' && node.value == null;
2627
2864
  }
2628
2865
 
2629
- const TEMPLATE_FRAGMENT_ERROR =
2630
- '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>.';
2631
-
2632
2866
  /**
2633
2867
  * @param {any} node
2634
2868
  * @param {TransformContext} transform_context
@@ -2637,13 +2871,7 @@ const TEMPLATE_FRAGMENT_ERROR =
2637
2871
  function to_jsx_element(node, transform_context, raw_children = node.children || []) {
2638
2872
  if (node.type === 'JSXElement') return node;
2639
2873
  if (!node.id) {
2640
- error(
2641
- TEMPLATE_FRAGMENT_ERROR,
2642
- transform_context.filename,
2643
- node,
2644
- transform_context.errors,
2645
- transform_context.comments,
2646
- );
2874
+ report_jsx_fragment_in_tsrx_error(node, transform_context);
2647
2875
  return set_loc(
2648
2876
  /** @type {any} */ ({
2649
2877
  type: 'JSXFragment',
@@ -2682,9 +2910,7 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
2682
2910
  }
2683
2911
  } else {
2684
2912
  if (walked_children.some((/** @type {any} */ c) => c && c.type === 'Html')) {
2685
- throw new Error(
2686
- `\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
2687
- );
2913
+ return report_html_template_unsupported_error(node, transform_context);
2688
2914
  }
2689
2915
  children = create_element_children(walked_children, transform_context);
2690
2916
  }
@@ -2761,7 +2987,10 @@ function child_contains_return_semantics(node) {
2761
2987
  return false;
2762
2988
  }
2763
2989
 
2764
- if (node.type === 'ReturnStatement' || is_lone_return_if_statement(node)) {
2990
+ if (
2991
+ (node.type === 'ReturnStatement' && node.argument == null) ||
2992
+ is_lone_return_if_statement(node)
2993
+ ) {
2765
2994
  return true;
2766
2995
  }
2767
2996
 
@@ -2913,82 +3142,757 @@ function get_referenced_helper_bindings(body_nodes, available_bindings) {
2913
3142
 
2914
3143
  /**
2915
3144
  * @param {any[]} body_nodes
2916
- * @param {any} key_expression
2917
- * @param {any} source_node
2918
3145
  * @param {TransformContext} transform_context
2919
- * @param {AST.Identifier} [preallocated_helper_id] - Optional pre-allocated id.
2920
- * Used by the switch lift's chained-call build, which allocates ids in
2921
- * source order in a forward pass and then constructs helpers in reverse so
2922
- * each fall-through case can reference the next case's component element.
2923
- * @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
3146
+ * @param {Set<string>} [local_binding_names]
3147
+ * @returns {void}
2924
3148
  */
2925
- function create_hook_safe_helper(
3149
+ function validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
2926
3150
  body_nodes,
2927
- key_expression,
2928
- source_node,
2929
3151
  transform_context,
2930
- preallocated_helper_id,
3152
+ local_binding_names,
2931
3153
  ) {
2932
- const helper_id =
2933
- preallocated_helper_id ??
2934
- create_generated_identifier(create_local_statement_component_name(transform_context));
2935
- const use_module_scoped_component = should_use_module_scoped_hook_components(transform_context);
2936
- const component_id = use_module_scoped_component
2937
- ? create_module_scoped_hook_component_id(helper_id, transform_context)
2938
- : helper_id;
2939
- const helper_bindings = get_referenced_helper_bindings(
2940
- body_nodes,
2941
- transform_context.available_bindings,
2942
- );
2943
- const aliases = use_module_scoped_component
2944
- ? []
2945
- : helper_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
2946
- const props_type =
2947
- helper_bindings.length > 0 && !use_module_scoped_component
2948
- ? create_helper_props_type_literal(helper_bindings, aliases)
2949
- : null;
2950
- const params =
2951
- helper_bindings.length > 0
2952
- ? [
2953
- props_type !== null
2954
- ? create_typed_helper_props_pattern(helper_bindings, props_type)
2955
- : create_helper_props_pattern(helper_bindings),
2956
- ]
2957
- : [];
3154
+ if (!is_react_like_hook_platform(transform_context)) {
3155
+ return;
3156
+ }
3157
+ if (!body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
3158
+ return;
3159
+ }
3160
+ if (!transform_context.available_bindings || transform_context.available_bindings.size === 0) {
3161
+ return;
3162
+ }
2958
3163
 
2959
- const saved_bindings = transform_context.available_bindings;
2960
- transform_context.available_bindings = new Map(saved_bindings);
3164
+ const shadowed_names = collect_block_binding_names(body_nodes);
3165
+ for (const name of local_binding_names || []) {
3166
+ shadowed_names.add(name);
3167
+ }
3168
+ validate_hook_outer_assignments_in_node(body_nodes, shadowed_names, transform_context, new Set());
3169
+ }
2961
3170
 
2962
- const helper_fn = /** @type {any} */ ({
2963
- type: 'FunctionExpression',
2964
- id: clone_identifier(component_id),
2965
- params,
2966
- body: {
2967
- type: 'BlockStatement',
2968
- body: build_render_statements(body_nodes, true, transform_context),
2969
- metadata: { path: [] },
2970
- },
2971
- async: false,
2972
- generator: false,
2973
- metadata: {
2974
- path: [],
2975
- is_component: true,
2976
- is_method: false,
2977
- },
2978
- });
3171
+ /**
3172
+ * @param {TransformContext} transform_context
3173
+ * @returns {boolean}
3174
+ */
3175
+ function is_react_like_hook_platform(transform_context) {
3176
+ return (
3177
+ transform_context.platform.name === 'React' || transform_context.platform.name === 'Preact'
3178
+ );
3179
+ }
2979
3180
 
2980
- transform_context.available_bindings = saved_bindings;
3181
+ /**
3182
+ * @param {any[]} statements
3183
+ * @returns {Set<string>}
3184
+ */
3185
+ function collect_block_binding_names(statements) {
3186
+ const names = new Set();
3187
+ for (const statement of statements || []) {
3188
+ collect_block_binding_names_from_statement(statement, names);
3189
+ }
3190
+ return names;
3191
+ }
2981
3192
 
2982
- const component_element = create_helper_component_element(
2983
- component_id,
2984
- helper_bindings,
2985
- source_node,
2986
- {
2987
- mapWrapper: false,
2988
- mapBindingNames: false,
2989
- mapBindingValues: false,
2990
- },
2991
- );
3193
+ /**
3194
+ * @param {any} statement
3195
+ * @param {Set<string>} names
3196
+ * @returns {void}
3197
+ */
3198
+ function collect_block_binding_names_from_statement(statement, names) {
3199
+ if (!statement || typeof statement !== 'object') {
3200
+ return;
3201
+ }
3202
+
3203
+ if (statement.type === 'VariableDeclaration') {
3204
+ for (const declaration of statement.declarations || []) {
3205
+ collect_pattern_names(declaration.id, names);
3206
+ }
3207
+ return;
3208
+ }
3209
+
3210
+ if (
3211
+ (statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
3212
+ statement.id?.type === 'Identifier'
3213
+ ) {
3214
+ names.add(statement.id.name);
3215
+ return;
3216
+ }
3217
+
3218
+ if (statement.type === 'ForOfStatement' || statement.type === 'ForInStatement') {
3219
+ if (statement.left?.type === 'VariableDeclaration' && statement.left.kind === 'var') {
3220
+ for (const declaration of statement.left.declarations || []) {
3221
+ collect_pattern_names(declaration.id, names);
3222
+ }
3223
+ }
3224
+ return;
3225
+ }
3226
+
3227
+ if (
3228
+ statement.type === 'ForStatement' &&
3229
+ statement.init?.type === 'VariableDeclaration' &&
3230
+ statement.init.kind === 'var'
3231
+ ) {
3232
+ for (const declaration of statement.init.declarations || []) {
3233
+ collect_pattern_names(declaration.id, names);
3234
+ }
3235
+ }
3236
+ }
3237
+
3238
+ /**
3239
+ * @param {any} pattern
3240
+ * @param {Set<string>} names
3241
+ * @returns {void}
3242
+ */
3243
+ function collect_pattern_names(pattern, names) {
3244
+ if (!pattern || typeof pattern !== 'object') {
3245
+ return;
3246
+ }
3247
+
3248
+ if (pattern.type === 'Identifier') {
3249
+ names.add(pattern.name);
3250
+ return;
3251
+ }
3252
+
3253
+ if (pattern.type === 'RestElement') {
3254
+ collect_pattern_names(pattern.argument, names);
3255
+ return;
3256
+ }
3257
+
3258
+ if (pattern.type === 'AssignmentPattern') {
3259
+ collect_pattern_names(pattern.left, names);
3260
+ return;
3261
+ }
3262
+
3263
+ if (pattern.type === 'ArrayPattern') {
3264
+ for (const element of pattern.elements || []) {
3265
+ collect_pattern_names(element, names);
3266
+ }
3267
+ return;
3268
+ }
3269
+
3270
+ if (pattern.type === 'ObjectPattern') {
3271
+ for (const property of pattern.properties || []) {
3272
+ if (property.type === 'RestElement') {
3273
+ collect_pattern_names(property.argument, names);
3274
+ } else {
3275
+ collect_pattern_names(property.value, names);
3276
+ }
3277
+ }
3278
+ }
3279
+ }
3280
+
3281
+ /**
3282
+ * @param {any} node
3283
+ * @param {Set<string>} shadowed_names
3284
+ * @param {TransformContext} transform_context
3285
+ * @param {Set<string>} hook_result_names
3286
+ * @returns {void}
3287
+ */
3288
+ function validate_hook_outer_assignments_in_node(
3289
+ node,
3290
+ shadowed_names,
3291
+ transform_context,
3292
+ hook_result_names,
3293
+ ) {
3294
+ if (!node || typeof node !== 'object') {
3295
+ return;
3296
+ }
3297
+
3298
+ if (Array.isArray(node)) {
3299
+ for (const child of node) {
3300
+ validate_hook_outer_assignments_in_node(
3301
+ child,
3302
+ shadowed_names,
3303
+ transform_context,
3304
+ hook_result_names,
3305
+ );
3306
+ }
3307
+ return;
3308
+ }
3309
+
3310
+ if (is_function_like_node(node)) {
3311
+ return;
3312
+ }
3313
+
3314
+ if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
3315
+ validate_hook_callback_outer_mutations(node, shadowed_names, transform_context);
3316
+ }
3317
+
3318
+ if (node.type === 'BlockStatement') {
3319
+ const next_shadowed = new Set(shadowed_names);
3320
+ const next_hook_result_names = new Set(hook_result_names);
3321
+ for (const name of collect_block_binding_names(node.body || [])) {
3322
+ next_shadowed.add(name);
3323
+ }
3324
+ for (const child of node.body || []) {
3325
+ validate_hook_outer_assignments_in_node(
3326
+ child,
3327
+ next_shadowed,
3328
+ transform_context,
3329
+ next_hook_result_names,
3330
+ );
3331
+ }
3332
+ return;
3333
+ }
3334
+
3335
+ if (node.type === 'VariableDeclaration') {
3336
+ for (const declaration of node.declarations || []) {
3337
+ if (
3338
+ declaration.init &&
3339
+ expression_contains_hook_derived_value(
3340
+ declaration.init,
3341
+ transform_context,
3342
+ hook_result_names,
3343
+ )
3344
+ ) {
3345
+ collect_pattern_names(declaration.id, hook_result_names);
3346
+ }
3347
+ validate_hook_outer_assignments_in_node(
3348
+ declaration.init,
3349
+ shadowed_names,
3350
+ transform_context,
3351
+ hook_result_names,
3352
+ );
3353
+ }
3354
+ return;
3355
+ }
3356
+
3357
+ if (
3358
+ node.type === 'AssignmentExpression' &&
3359
+ expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
3360
+ ) {
3361
+ const outer_names = get_referenced_outer_binding_names(
3362
+ node.left,
3363
+ transform_context.available_bindings,
3364
+ shadowed_names,
3365
+ );
3366
+ if (outer_names.length > 0) {
3367
+ report_hook_outer_assignment_error(
3368
+ node,
3369
+ outer_names,
3370
+ find_first_hook_call_name(node.right) || 'hook',
3371
+ transform_context,
3372
+ );
3373
+ }
3374
+ for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
3375
+ hook_result_names.add(name);
3376
+ }
3377
+ }
3378
+
3379
+ if (node.type === 'ForOfStatement') {
3380
+ if (
3381
+ node.left &&
3382
+ node.left.type !== 'VariableDeclaration' &&
3383
+ expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
3384
+ ) {
3385
+ const outer_names = get_referenced_outer_binding_names(
3386
+ node.left,
3387
+ transform_context.available_bindings,
3388
+ shadowed_names,
3389
+ );
3390
+ if (outer_names.length > 0) {
3391
+ report_hook_outer_assignment_error(
3392
+ node,
3393
+ outer_names,
3394
+ find_first_hook_call_name(node.right) || 'hook',
3395
+ transform_context,
3396
+ );
3397
+ }
3398
+ for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
3399
+ hook_result_names.add(name);
3400
+ }
3401
+ }
3402
+
3403
+ validate_hook_outer_assignments_in_node(
3404
+ node.right,
3405
+ shadowed_names,
3406
+ transform_context,
3407
+ hook_result_names,
3408
+ );
3409
+
3410
+ // Loop-declared bindings (`for (const x of …)`, `for (let x of …)`) live
3411
+ // only in the body. They are deliberately NOT in the enclosing block's
3412
+ // shadowed set (see collect_block_binding_names_from_statement), so add
3413
+ // them just for the body recursion to keep references to the loop var
3414
+ // from being flagged as outer-binding mutations.
3415
+ const body_shadowed = new Set(shadowed_names);
3416
+ if (node.left && node.left.type === 'VariableDeclaration') {
3417
+ for (const declaration of node.left.declarations || []) {
3418
+ collect_pattern_names(declaration.id, body_shadowed);
3419
+ }
3420
+ }
3421
+ validate_hook_outer_assignments_in_node(
3422
+ node.body,
3423
+ body_shadowed,
3424
+ transform_context,
3425
+ hook_result_names,
3426
+ );
3427
+ return;
3428
+ }
3429
+
3430
+ for (const key of Object.keys(node)) {
3431
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3432
+ continue;
3433
+ }
3434
+ validate_hook_outer_assignments_in_node(
3435
+ node[key],
3436
+ shadowed_names,
3437
+ transform_context,
3438
+ hook_result_names,
3439
+ );
3440
+ }
3441
+ }
3442
+
3443
+ /**
3444
+ * @param {any} call_node
3445
+ * @param {Set<string>} shadowed_names
3446
+ * @param {TransformContext} transform_context
3447
+ * @returns {void}
3448
+ */
3449
+ function validate_hook_callback_outer_mutations(call_node, shadowed_names, transform_context) {
3450
+ const hook_name = get_hook_callee_name(call_node.callee);
3451
+ for (const argument of call_node.arguments || []) {
3452
+ if (!is_function_like_node(argument)) {
3453
+ continue;
3454
+ }
3455
+ const callback_shadowed_names = create_function_like_shadowed_names(argument, shadowed_names);
3456
+ validate_hook_callback_outer_mutations_in_node(
3457
+ argument.body,
3458
+ callback_shadowed_names,
3459
+ transform_context,
3460
+ hook_name,
3461
+ );
3462
+ }
3463
+ }
3464
+
3465
+ /**
3466
+ * @param {any} node
3467
+ * @returns {boolean}
3468
+ */
3469
+ function is_function_like_node(node) {
3470
+ return (
3471
+ node.type === 'FunctionDeclaration' ||
3472
+ node.type === 'FunctionExpression' ||
3473
+ node.type === 'ArrowFunctionExpression' ||
3474
+ // this is just in case but we should already
3475
+ // have a component replaced with a function node
3476
+ node.type === 'Component'
3477
+ );
3478
+ }
3479
+
3480
+ /**
3481
+ * @param {any} node
3482
+ * @param {Set<string>} shadowed_names
3483
+ * @returns {Set<string>}
3484
+ */
3485
+ function create_function_like_shadowed_names(node, shadowed_names) {
3486
+ const next_shadowed_names = new Set(shadowed_names);
3487
+ for (const param of node.params || []) {
3488
+ collect_pattern_names(param, next_shadowed_names);
3489
+ }
3490
+ if (node.body?.type === 'BlockStatement') {
3491
+ for (const name of collect_block_binding_names(node.body.body || [])) {
3492
+ next_shadowed_names.add(name);
3493
+ }
3494
+ }
3495
+ return next_shadowed_names;
3496
+ }
3497
+
3498
+ /**
3499
+ * @param {any} node
3500
+ * @param {Set<string>} shadowed_names
3501
+ * @param {TransformContext} transform_context
3502
+ * @param {string} hook_name
3503
+ * @returns {void}
3504
+ */
3505
+ function validate_hook_callback_outer_mutations_in_node(
3506
+ node,
3507
+ shadowed_names,
3508
+ transform_context,
3509
+ hook_name,
3510
+ ) {
3511
+ if (!node || typeof node !== 'object') {
3512
+ return;
3513
+ }
3514
+
3515
+ if (Array.isArray(node)) {
3516
+ for (const child of node) {
3517
+ validate_hook_callback_outer_mutations_in_node(
3518
+ child,
3519
+ shadowed_names,
3520
+ transform_context,
3521
+ hook_name,
3522
+ );
3523
+ }
3524
+ return;
3525
+ }
3526
+
3527
+ if (is_function_like_node(node)) {
3528
+ validate_hook_callback_outer_mutations_in_node(
3529
+ node.body,
3530
+ create_function_like_shadowed_names(node, shadowed_names),
3531
+ transform_context,
3532
+ hook_name,
3533
+ );
3534
+ return;
3535
+ }
3536
+
3537
+ if (node.type === 'BlockStatement') {
3538
+ const next_shadowed_names = new Set(shadowed_names);
3539
+ for (const name of collect_block_binding_names(node.body || [])) {
3540
+ next_shadowed_names.add(name);
3541
+ }
3542
+ for (const child of node.body || []) {
3543
+ validate_hook_callback_outer_mutations_in_node(
3544
+ child,
3545
+ next_shadowed_names,
3546
+ transform_context,
3547
+ hook_name,
3548
+ );
3549
+ }
3550
+ return;
3551
+ }
3552
+
3553
+ if (node.type === 'AssignmentExpression') {
3554
+ const outer_names = get_referenced_outer_binding_names(
3555
+ node.left,
3556
+ transform_context.available_bindings,
3557
+ shadowed_names,
3558
+ );
3559
+ if (outer_names.length > 0) {
3560
+ report_hook_callback_outer_mutation_error(node, outer_names, hook_name, transform_context);
3561
+ }
3562
+ }
3563
+
3564
+ if (node.type === 'UpdateExpression') {
3565
+ const outer_names = get_referenced_outer_binding_names(
3566
+ node.argument,
3567
+ transform_context.available_bindings,
3568
+ shadowed_names,
3569
+ );
3570
+ if (outer_names.length > 0) {
3571
+ report_hook_callback_outer_mutation_error(node, outer_names, hook_name, transform_context);
3572
+ }
3573
+ }
3574
+
3575
+ for (const key of Object.keys(node)) {
3576
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3577
+ continue;
3578
+ }
3579
+ if (key === 'left' && node.type === 'AssignmentExpression') {
3580
+ continue;
3581
+ }
3582
+ if (key === 'argument' && node.type === 'UpdateExpression') {
3583
+ continue;
3584
+ }
3585
+ validate_hook_callback_outer_mutations_in_node(
3586
+ node[key],
3587
+ shadowed_names,
3588
+ transform_context,
3589
+ hook_name,
3590
+ );
3591
+ }
3592
+ }
3593
+
3594
+ /**
3595
+ * @param {any} node
3596
+ * @param {TransformContext} transform_context
3597
+ * @param {Set<string>} hook_result_names
3598
+ * @returns {boolean}
3599
+ */
3600
+ function expression_contains_hook_derived_value(node, transform_context, hook_result_names) {
3601
+ return (
3602
+ node_contains_top_level_hook_call(node, false, transform_context, true) ||
3603
+ references_name_in_set(node, hook_result_names)
3604
+ );
3605
+ }
3606
+
3607
+ /**
3608
+ * @param {any} node
3609
+ * @param {Set<string>} names
3610
+ * @returns {boolean}
3611
+ */
3612
+ function references_name_in_set(node, names) {
3613
+ if (!node || typeof node !== 'object' || names.size === 0) {
3614
+ return false;
3615
+ }
3616
+
3617
+ if (node.type === 'Identifier') {
3618
+ return names.has(node.name);
3619
+ }
3620
+
3621
+ if (
3622
+ node.type === 'FunctionDeclaration' ||
3623
+ node.type === 'FunctionExpression' ||
3624
+ node.type === 'ArrowFunctionExpression' ||
3625
+ node.type === 'Component'
3626
+ ) {
3627
+ return false;
3628
+ }
3629
+
3630
+ if (Array.isArray(node)) {
3631
+ return node.some((child) => references_name_in_set(child, names));
3632
+ }
3633
+
3634
+ for (const key of Object.keys(node)) {
3635
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3636
+ continue;
3637
+ }
3638
+ if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
3639
+ continue;
3640
+ }
3641
+ if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
3642
+ continue;
3643
+ }
3644
+ if (references_name_in_set(node[key], names)) {
3645
+ return true;
3646
+ }
3647
+ }
3648
+
3649
+ return false;
3650
+ }
3651
+
3652
+ /**
3653
+ * @param {any} node
3654
+ * @param {Set<string>} shadowed_names
3655
+ * @returns {string[]}
3656
+ */
3657
+ function get_referenced_local_binding_names(node, shadowed_names) {
3658
+ const names = new Set();
3659
+ collect_referenced_local_binding_names(node, shadowed_names, names);
3660
+ return [...names];
3661
+ }
3662
+
3663
+ /**
3664
+ * @param {any} node
3665
+ * @param {Set<string>} shadowed_names
3666
+ * @param {Set<string>} names
3667
+ * @returns {void}
3668
+ */
3669
+ function collect_referenced_local_binding_names(node, shadowed_names, names) {
3670
+ if (!node || typeof node !== 'object') {
3671
+ return;
3672
+ }
3673
+
3674
+ if (node.type === 'Identifier') {
3675
+ if (shadowed_names.has(node.name)) {
3676
+ names.add(node.name);
3677
+ }
3678
+ return;
3679
+ }
3680
+
3681
+ if (Array.isArray(node)) {
3682
+ for (const child of node) {
3683
+ collect_referenced_local_binding_names(child, shadowed_names, names);
3684
+ }
3685
+ return;
3686
+ }
3687
+
3688
+ for (const key of Object.keys(node)) {
3689
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3690
+ continue;
3691
+ }
3692
+ if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
3693
+ continue;
3694
+ }
3695
+ if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
3696
+ continue;
3697
+ }
3698
+ collect_referenced_local_binding_names(node[key], shadowed_names, names);
3699
+ }
3700
+ }
3701
+
3702
+ /**
3703
+ * @param {any} node
3704
+ * @param {Map<string, AST.Identifier>} available_bindings
3705
+ * @param {Set<string>} shadowed_names
3706
+ * @returns {string[]}
3707
+ */
3708
+ function get_referenced_outer_binding_names(node, available_bindings, shadowed_names) {
3709
+ const names = new Set();
3710
+ collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names);
3711
+ return [...names];
3712
+ }
3713
+
3714
+ /**
3715
+ * @param {any} node
3716
+ * @param {Map<string, AST.Identifier>} available_bindings
3717
+ * @param {Set<string>} shadowed_names
3718
+ * @param {Set<string>} names
3719
+ * @returns {void}
3720
+ */
3721
+ function collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names) {
3722
+ if (!node || typeof node !== 'object') {
3723
+ return;
3724
+ }
3725
+
3726
+ if (node.type === 'Identifier') {
3727
+ if (available_bindings.has(node.name) && !shadowed_names.has(node.name)) {
3728
+ names.add(node.name);
3729
+ }
3730
+ return;
3731
+ }
3732
+
3733
+ if (Array.isArray(node)) {
3734
+ for (const child of node) {
3735
+ collect_referenced_outer_binding_names(child, available_bindings, shadowed_names, names);
3736
+ }
3737
+ return;
3738
+ }
3739
+
3740
+ for (const key of Object.keys(node)) {
3741
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3742
+ continue;
3743
+ }
3744
+ if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
3745
+ continue;
3746
+ }
3747
+ if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
3748
+ continue;
3749
+ }
3750
+ collect_referenced_outer_binding_names(node[key], available_bindings, shadowed_names, names);
3751
+ }
3752
+ }
3753
+
3754
+ /**
3755
+ * @param {any} node
3756
+ * @returns {string | null}
3757
+ */
3758
+ function find_first_hook_call_name(node) {
3759
+ if (!node || typeof node !== 'object') {
3760
+ return null;
3761
+ }
3762
+
3763
+ if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
3764
+ return get_hook_callee_name(node.callee);
3765
+ }
3766
+
3767
+ if (
3768
+ node.type === 'FunctionDeclaration' ||
3769
+ node.type === 'FunctionExpression' ||
3770
+ node.type === 'ArrowFunctionExpression' ||
3771
+ node.type === 'Component'
3772
+ ) {
3773
+ return null;
3774
+ }
3775
+
3776
+ if (Array.isArray(node)) {
3777
+ for (const child of node) {
3778
+ const name = find_first_hook_call_name(child);
3779
+ if (name) return name;
3780
+ }
3781
+ return null;
3782
+ }
3783
+
3784
+ for (const key of Object.keys(node)) {
3785
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3786
+ continue;
3787
+ }
3788
+ const name = find_first_hook_call_name(node[key]);
3789
+ if (name) return name;
3790
+ }
3791
+
3792
+ return null;
3793
+ }
3794
+
3795
+ /**
3796
+ * @param {any} callee
3797
+ * @returns {string}
3798
+ */
3799
+ function get_hook_callee_name(callee) {
3800
+ if (callee?.type === 'Identifier') {
3801
+ return callee.name;
3802
+ }
3803
+ if (
3804
+ callee?.type === 'MemberExpression' &&
3805
+ !callee.computed &&
3806
+ callee.property?.type === 'Identifier'
3807
+ ) {
3808
+ return callee.property.name;
3809
+ }
3810
+ return 'hook';
3811
+ }
3812
+
3813
+ /**
3814
+ * @param {any[]} body_nodes
3815
+ * @param {any} key_expression
3816
+ * @param {any} source_node
3817
+ * @param {TransformContext} transform_context
3818
+ * @param {AST.Identifier} [preallocated_helper_id] - Optional pre-allocated id.
3819
+ * Used by the switch lift's chained-call build, which allocates ids in
3820
+ * source order in a forward pass and then constructs helpers in reverse so
3821
+ * each fall-through case can reference the next case's component element.
3822
+ * @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
3823
+ */
3824
+ function create_hook_safe_helper(
3825
+ body_nodes,
3826
+ key_expression,
3827
+ source_node,
3828
+ transform_context,
3829
+ preallocated_helper_id,
3830
+ ) {
3831
+ validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
3832
+ body_nodes,
3833
+ transform_context,
3834
+ );
3835
+
3836
+ const helper_id =
3837
+ preallocated_helper_id ??
3838
+ create_generated_identifier(create_local_statement_component_name(transform_context));
3839
+ const use_module_scoped_component = should_use_module_scoped_hook_components(transform_context);
3840
+ const component_id = use_module_scoped_component
3841
+ ? create_module_scoped_hook_component_id(helper_id, transform_context)
3842
+ : helper_id;
3843
+ const helper_bindings = get_referenced_helper_bindings(
3844
+ body_nodes,
3845
+ transform_context.available_bindings,
3846
+ );
3847
+ const aliases = use_module_scoped_component
3848
+ ? []
3849
+ : helper_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
3850
+ const props_type =
3851
+ helper_bindings.length > 0 && !use_module_scoped_component
3852
+ ? create_helper_props_type_literal(helper_bindings, aliases)
3853
+ : null;
3854
+ const params =
3855
+ helper_bindings.length > 0
3856
+ ? [
3857
+ props_type !== null
3858
+ ? create_typed_helper_props_pattern(helper_bindings, props_type)
3859
+ : create_helper_props_pattern(helper_bindings),
3860
+ ]
3861
+ : [];
3862
+
3863
+ const saved_bindings = transform_context.available_bindings;
3864
+ transform_context.available_bindings = new Map(saved_bindings);
3865
+
3866
+ const helper_fn = /** @type {any} */ ({
3867
+ type: 'FunctionExpression',
3868
+ id: clone_identifier(component_id),
3869
+ params,
3870
+ body: {
3871
+ type: 'BlockStatement',
3872
+ body: build_render_statements(body_nodes, true, transform_context),
3873
+ metadata: { path: [] },
3874
+ },
3875
+ async: false,
3876
+ generator: false,
3877
+ metadata: {
3878
+ path: [],
3879
+ is_component: true,
3880
+ is_method: false,
3881
+ },
3882
+ });
3883
+
3884
+ transform_context.available_bindings = saved_bindings;
3885
+
3886
+ const component_element = create_helper_component_element(
3887
+ component_id,
3888
+ helper_bindings,
3889
+ source_node,
3890
+ {
3891
+ mapWrapper: false,
3892
+ mapBindingNames: false,
3893
+ mapBindingValues: false,
3894
+ },
3895
+ );
2992
3896
 
2993
3897
  if (key_expression) {
2994
3898
  component_element.openingElement.attributes.push(
@@ -3401,9 +4305,7 @@ function to_jsx_child(node, transform_context) {
3401
4305
  case 'TSRXExpression':
3402
4306
  return to_jsx_expression_container(node.expression, node);
3403
4307
  case 'Html':
3404
- throw new Error(
3405
- `\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
3406
- );
4308
+ return report_html_template_unsupported_error(node, transform_context);
3407
4309
  case 'IfStatement':
3408
4310
  return (
3409
4311
  transform_context.platform.hooks?.controlFlow?.ifStatement ?? if_statement_to_jsx_child
@@ -3448,22 +4350,25 @@ function tsrx_node_to_jsx_expression(node, transform_context, in_jsx_child = fal
3448
4350
  let expression;
3449
4351
  if (children.length === 0) {
3450
4352
  expression = create_null_literal();
3451
- } else if (
3452
- children.every(is_inline_element_child) &&
3453
- !children_contain_return_semantics(children)
3454
- ) {
3455
- const saved_inside_element_child = transform_context.inside_element_child;
3456
- transform_context.inside_element_child = true;
3457
- try {
3458
- const render_nodes = children.map((/** @type {any} */ child) =>
3459
- to_jsx_child(child, transform_context),
3460
- );
3461
- expression = build_return_expression(render_nodes) || create_null_literal();
3462
- } finally {
3463
- transform_context.inside_element_child = saved_inside_element_child;
3464
- }
3465
4353
  } else {
3466
- expression = statement_body_to_jsx_child(children, transform_context).expression;
4354
+ expression = return_value_body_to_expression(children, node, transform_context);
4355
+ }
4356
+
4357
+ if (!expression) {
4358
+ if (children.every(is_inline_element_child) && !children_contain_return_semantics(children)) {
4359
+ const saved_inside_element_child = transform_context.inside_element_child;
4360
+ transform_context.inside_element_child = true;
4361
+ try {
4362
+ const render_nodes = children.map((/** @type {any} */ child) =>
4363
+ to_jsx_child(child, transform_context),
4364
+ );
4365
+ expression = build_return_expression(render_nodes) || create_null_literal();
4366
+ } finally {
4367
+ transform_context.inside_element_child = saved_inside_element_child;
4368
+ }
4369
+ } else {
4370
+ expression = statement_body_to_jsx_child(children, transform_context).expression;
4371
+ }
3467
4372
  }
3468
4373
 
3469
4374
  if (
@@ -3479,6 +4384,149 @@ function tsrx_node_to_jsx_expression(node, transform_context, in_jsx_child = fal
3479
4384
  return expression;
3480
4385
  }
3481
4386
 
4387
+ /**
4388
+ * Explicit return values inside expression-position `<tsrx>` templates are JavaScript
4389
+ * values, so keep them out of platform render control flow.
4390
+ *
4391
+ * @param {any[]} body_nodes
4392
+ * @param {any} source_node
4393
+ * @param {TransformContext} [transform_context]
4394
+ * @returns {any | null}
4395
+ */
4396
+ export function return_value_body_to_expression(body_nodes, source_node, transform_context) {
4397
+ if (!body_contains_top_level_return_value(body_nodes)) return null;
4398
+
4399
+ if (body_nodes.length === 1) {
4400
+ const expression = return_value_statement_to_expression(body_nodes[0], transform_context);
4401
+ if (expression) return expression;
4402
+ }
4403
+
4404
+ return create_statement_iife(body_nodes, source_node, transform_context);
4405
+ }
4406
+
4407
+ /**
4408
+ * @param {any} node
4409
+ * @param {TransformContext} [transform_context]
4410
+ * @returns {any | null}
4411
+ */
4412
+ function return_value_statement_to_expression(node, transform_context) {
4413
+ if (node?.type === 'ReturnStatement' && node.argument != null) {
4414
+ return node.argument;
4415
+ }
4416
+
4417
+ if (node?.type === 'IfStatement') {
4418
+ return return_value_if_statement_to_conditional_expression(node, transform_context);
4419
+ }
4420
+
4421
+ return null;
4422
+ }
4423
+
4424
+ /**
4425
+ * @param {any} node
4426
+ * @returns {boolean}
4427
+ */
4428
+ function body_contains_top_level_return_value(node) {
4429
+ if (!node || typeof node !== 'object') return false;
4430
+
4431
+ if (Array.isArray(node)) {
4432
+ return node.some(body_contains_top_level_return_value);
4433
+ }
4434
+
4435
+ if (node.type === 'ReturnStatement') {
4436
+ return node.argument != null;
4437
+ }
4438
+
4439
+ if (
4440
+ node.type === 'FunctionDeclaration' ||
4441
+ node.type === 'FunctionExpression' ||
4442
+ node.type === 'ArrowFunctionExpression' ||
4443
+ node.type === 'ClassDeclaration' ||
4444
+ node.type === 'ClassExpression' ||
4445
+ node.type === 'Component'
4446
+ ) {
4447
+ return false;
4448
+ }
4449
+
4450
+ for (const key of Object.keys(node)) {
4451
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4452
+ continue;
4453
+ }
4454
+ if (body_contains_top_level_return_value(node[key])) {
4455
+ return true;
4456
+ }
4457
+ }
4458
+
4459
+ return false;
4460
+ }
4461
+
4462
+ /**
4463
+ * @param {any[]} body_nodes
4464
+ * @param {any} source_node
4465
+ * @param {TransformContext} [transform_context]
4466
+ * @returns {any}
4467
+ */
4468
+ function create_statement_iife(body_nodes, source_node, transform_context) {
4469
+ return set_generated_expression_loc(
4470
+ b.call(b.arrow([], b.block(body_nodes))),
4471
+ source_node,
4472
+ transform_context,
4473
+ );
4474
+ }
4475
+
4476
+ /**
4477
+ * @param {any} node
4478
+ * @param {any} source_node
4479
+ * @param {TransformContext} [transform_context]
4480
+ * @returns {any}
4481
+ */
4482
+ function set_generated_expression_loc(node, source_node, transform_context) {
4483
+ if (transform_context?.typeOnly || !source_node?.loc) return node;
4484
+ return setLocation(/** @type {any} */ (node), source_node);
4485
+ }
4486
+
4487
+ /**
4488
+ * @returns {any}
4489
+ */
4490
+ function create_undefined_expression() {
4491
+ return b.unary('void', b.literal(0));
4492
+ }
4493
+
4494
+ /**
4495
+ * @param {any} node
4496
+ * @param {TransformContext} [transform_context]
4497
+ * @returns {any | null}
4498
+ */
4499
+ function return_value_block_to_expression(node, transform_context) {
4500
+ const body = node?.type === 'BlockStatement' ? node.body : node ? [node] : [];
4501
+ if (body.length !== 1) return null;
4502
+
4503
+ return return_value_statement_to_expression(body[0], transform_context);
4504
+ }
4505
+
4506
+ /**
4507
+ * @param {any} node
4508
+ * @param {TransformContext} [transform_context]
4509
+ * @returns {any | null}
4510
+ */
4511
+ function return_value_if_statement_to_conditional_expression(node, transform_context) {
4512
+ if (!node || node.type !== 'IfStatement') return null;
4513
+
4514
+ const consequent = return_value_block_to_expression(node.consequent, transform_context);
4515
+ if (!consequent) return null;
4516
+
4517
+ let alternate = create_undefined_expression();
4518
+ if (node.alternate) {
4519
+ alternate = return_value_block_to_expression(node.alternate, transform_context);
4520
+ if (!alternate) return null;
4521
+ }
4522
+
4523
+ return set_generated_expression_loc(
4524
+ b.conditional(node.test, consequent, alternate),
4525
+ node,
4526
+ transform_context,
4527
+ );
4528
+ }
4529
+
3482
4530
  /**
3483
4531
  * @param {any} node
3484
4532
  * @param {TransformContext} transform_context
@@ -4009,7 +5057,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4009
5057
  );
4010
5058
  }
4011
5059
  const pending_body = pending.body || [];
4012
- if (!pending_body.some(is_jsx_child)) {
5060
+ if (pending_body.length > 0 && !pending_body.some(is_jsx_child)) {
4013
5061
  error(
4014
5062
  'Component try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
4015
5063
  transform_context.filename,
@@ -4031,7 +5079,10 @@ function try_statement_to_jsx_child(node, transform_context) {
4031
5079
  if (pending) {
4032
5080
  transform_context.needs_suspense = true;
4033
5081
  const pending_body_nodes = pending.body || [];
4034
- const fallback_content = statement_body_to_jsx_child(pending_body_nodes, transform_context);
5082
+ const fallback_content =
5083
+ pending_body_nodes.length === 0
5084
+ ? to_jsx_expression_container(create_null_literal())
5085
+ : statement_body_to_jsx_child(pending_body_nodes, transform_context);
4035
5086
 
4036
5087
  result = create_jsx_element(
4037
5088
  'Suspense',
@@ -4069,9 +5120,16 @@ function try_statement_to_jsx_child(node, transform_context) {
4069
5120
  // correctly identifies references to err/reset as non-static
4070
5121
  const saved_catch_bindings = transform_context.available_bindings;
4071
5122
  transform_context.available_bindings = new Map(saved_catch_bindings);
5123
+ const catch_scoped_names = new Set();
4072
5124
  for (const param of catch_params) {
4073
5125
  collect_pattern_bindings(param, transform_context.available_bindings);
5126
+ collect_pattern_names(param, catch_scoped_names);
4074
5127
  }
5128
+ validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
5129
+ catch_body_nodes,
5130
+ transform_context,
5131
+ catch_scoped_names,
5132
+ );
4075
5133
 
4076
5134
  const fallback_fn = {
4077
5135
  type: 'ArrowFunctionExpression',