@tsrx/core 0.0.13 → 0.0.14

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.
@@ -35,6 +35,8 @@ import {
35
35
  } from '../lazy.js';
36
36
  import { find_first_top_level_await_in_component_body } from '../await.js';
37
37
  import { prepare_stylesheet_for_render, annotate_component_with_hash } from '../scoping.js';
38
+ import { validate_component_return_statement } from '../../analyze/validation.js';
39
+ import { get_component_from_path } from '../../utils/ast.js';
38
40
  import {
39
41
  is_interleaved_body as is_interleaved_body_core,
40
42
  is_capturable_jsx_child,
@@ -48,7 +50,7 @@ import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
48
50
  * local_statement_component_index: number,
49
51
  * needs_error_boundary: boolean,
50
52
  * needs_suspense: boolean,
51
- * helper_state: { base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] } | null,
53
+ * helper_state: { base_name: string, next_id: number, helpers: any[], statics: any[] } | null,
52
54
  * available_bindings: Map<string, AST.Identifier>,
53
55
  * lazy_next_id: number,
54
56
  * current_css_hash: string | null,
@@ -112,8 +114,17 @@ export function createJsxTransform(platform) {
112
114
  preallocate_lazy_ids(/** @type {any} */ (ast), transform_context);
113
115
 
114
116
  walk(/** @type {any} */ (ast), transform_context, {
117
+ ReturnStatement(node, { next, path }) {
118
+ if (get_component_from_path(path)) {
119
+ validate_component_return_statement(node, filename);
120
+ }
121
+
122
+ return next();
123
+ },
124
+
115
125
  Component(node, { next, state }) {
116
126
  const as_any = /** @type {any} */ (node);
127
+
117
128
  const await_expression = find_first_top_level_await_in_component_body(as_any.body || []);
118
129
 
119
130
  if (await_expression) {
@@ -170,17 +181,12 @@ export function createJsxTransform(platform) {
170
181
  state.current_css_hash = as_any.css ? as_any.css.hash : null;
171
182
 
172
183
  // Pre-collect component body bindings (params + top-level statements)
173
- // so that Element children processed during the bottom-up walk can see
174
- // the full scope. Without this, hoisted helpers would miss body-level
175
- // variables like `const [x] = useState(...)` and produce ReferenceErrors.
176
- // Only collect up to the split point — bindings declared after a
177
- // hook-safe split aren't in scope at the return statement and would
178
- // cause ReferenceErrors if passed as helper props.
184
+ // so Element children processed during the bottom-up walk can see
185
+ // component-scope names. Hook-safe helpers filter this set down to
186
+ // the names their body actually references before generating props.
179
187
  const body_bindings = collect_param_bindings(as_any.params || []);
180
188
  const body = as_any.body || [];
181
- const split_index = find_hook_safe_split_index(body);
182
- const collect_end = split_index === -1 ? body.length : split_index;
183
- for (let i = 0; i < collect_end; i += 1) {
189
+ for (let i = 0; i < body.length; i += 1) {
184
190
  collect_statement_bindings(body[i], body_bindings);
185
191
  }
186
192
  state.available_bindings = body_bindings;
@@ -347,12 +353,7 @@ function component_to_function_declaration(component, transform_context, walk_he
347
353
  transform_context.helper_state = helper_state;
348
354
  transform_context.available_bindings = new Map(param_bindings);
349
355
 
350
- const body_statements = build_component_statements(
351
- body,
352
- helper_state,
353
- param_bindings,
354
- transform_context,
355
- );
356
+ const body_statements = build_component_statements(body, transform_context);
356
357
 
357
358
  // Replace lazy param patterns with generated identifiers
358
359
  const final_params = lazy_bindings.size > 0 ? replace_lazy_params(params) : params;
@@ -371,6 +372,7 @@ function component_to_function_declaration(component, transform_context, walk_he
371
372
  const fn = /** @type {any} */ ({
372
373
  type: 'FunctionDeclaration',
373
374
  id: component.id,
375
+ typeParameters: component.typeParameters,
374
376
  params: final_params,
375
377
  body: final_body,
376
378
  async: is_async_component,
@@ -401,107 +403,11 @@ function component_to_function_declaration(component, transform_context, walk_he
401
403
 
402
404
  /**
403
405
  * @param {any[]} body_nodes
404
- * @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
405
- * @param {Map<string, AST.Identifier>} available_bindings
406
406
  * @param {TransformContext} transform_context
407
407
  * @returns {any[]}
408
408
  */
409
- function build_component_statements(
410
- body_nodes,
411
- helper_state,
412
- available_bindings,
413
- transform_context,
414
- ) {
415
- const split_index = find_hook_safe_split_index(body_nodes);
416
- if (split_index === -1) {
417
- return build_render_statements(body_nodes, false, transform_context);
418
- }
419
-
420
- const statements = [];
421
- const render_nodes = [];
422
- const bindings = new Map(available_bindings);
423
-
424
- const pre_split_body = body_nodes.slice(0, split_index);
425
- const interleaved = is_interleaved_body(pre_split_body);
426
- let capture_index = 0;
427
-
428
- for (let i = 0; i < split_index; i += 1) {
429
- const child = body_nodes[i];
430
-
431
- if (is_bare_return_statement(child)) {
432
- statements.push(create_component_return_statement(render_nodes, child));
433
- return statements;
434
- }
435
-
436
- if (is_lone_return_if_statement(child)) {
437
- statements.push(create_component_lone_return_if_statement(child, render_nodes));
438
- continue;
439
- }
440
-
441
- if (is_jsx_child(child)) {
442
- const jsx = to_jsx_child(child, transform_context);
443
- if (interleaved && is_capturable_jsx_child(jsx)) {
444
- const { declaration, reference } = captureJsxChild(jsx, capture_index++);
445
- statements.push(declaration);
446
- render_nodes.push(reference);
447
- } else {
448
- render_nodes.push(jsx);
449
- }
450
- } else {
451
- statements.push(child);
452
- collect_statement_bindings(child, bindings);
453
- transform_context.available_bindings = bindings;
454
- }
455
- }
456
-
457
- if (!interleaved) {
458
- hoist_static_render_nodes(render_nodes, transform_context);
459
- }
460
-
461
- const split_node = body_nodes[split_index];
462
- const consequent_body =
463
- split_node.consequent.type === 'BlockStatement'
464
- ? split_node.consequent.body
465
- : [split_node.consequent];
466
- const short_branch_body = consequent_body.filter(
467
- (/** @type {any} */ child) => !is_bare_return_statement(child),
468
- );
469
- const continuation_body = body_nodes.slice(split_index + 1);
470
- const short_branch = create_helper_component_expression(
471
- short_branch_body,
472
- helper_state,
473
- bindings,
474
- split_node.consequent,
475
- 'Exit',
476
- transform_context,
477
- );
478
- const continuation = create_helper_component_expression(
479
- continuation_body,
480
- helper_state,
481
- bindings,
482
- split_node,
483
- 'Continue',
484
- transform_context,
485
- );
486
-
487
- render_nodes.push(
488
- to_jsx_expression_container(
489
- set_loc(
490
- /** @type {any} */ ({
491
- type: 'ConditionalExpression',
492
- test: split_node.test,
493
- consequent: short_branch,
494
- alternate: continuation,
495
- metadata: { path: [] },
496
- }),
497
- split_node,
498
- ),
499
- split_node,
500
- ),
501
- );
502
-
503
- statements.push(create_component_return_statement(render_nodes, split_node));
504
- return statements;
409
+ function build_component_statements(body_nodes, transform_context) {
410
+ return build_render_statements(body_nodes, false, transform_context);
505
411
  }
506
412
 
507
413
  /**
@@ -527,15 +433,40 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
527
433
  const interleaved = is_interleaved_body(body_nodes);
528
434
  let capture_index = 0;
529
435
 
530
- for (const child of body_nodes) {
436
+ for (let i = 0; i < body_nodes.length; i += 1) {
437
+ const child = body_nodes[i];
438
+
531
439
  if (is_bare_return_statement(child)) {
532
440
  statements.push(create_component_return_statement(render_nodes, child));
533
- transform_context.available_bindings = saved_bindings;
534
- return statements;
441
+ render_nodes.length = 0;
442
+ continue;
535
443
  }
536
444
 
537
- if (is_lone_return_if_statement(child)) {
538
- statements.push(create_component_lone_return_if_statement(child, render_nodes));
445
+ if (is_returning_if_statement(child)) {
446
+ const branch_has_hooks = body_contains_top_level_hook_call(get_if_consequent_body(child));
447
+ const continuation_has_hooks = body_contains_top_level_hook_call(body_nodes.slice(i + 1));
448
+
449
+ if (branch_has_hooks || continuation_has_hooks) {
450
+ statements.push(
451
+ ...create_component_helper_split_returning_if_statements(
452
+ child,
453
+ body_nodes.slice(i + 1),
454
+ render_nodes,
455
+ transform_context,
456
+ ),
457
+ );
458
+ transform_context.available_bindings = saved_bindings;
459
+ return statements;
460
+ }
461
+
462
+ if (is_lone_return_if_statement(child)) {
463
+ statements.push(create_component_lone_return_if_statement(child, render_nodes));
464
+ continue;
465
+ }
466
+
467
+ statements.push(
468
+ create_component_returning_if_statement(child, render_nodes, transform_context),
469
+ );
539
470
  continue;
540
471
  }
541
472
 
@@ -586,24 +517,6 @@ function is_interleaved_body(body_nodes) {
586
517
  return is_interleaved_body_core(filtered, is_jsx_child);
587
518
  }
588
519
 
589
- /**
590
- * @param {any[]} body_nodes
591
- * @returns {number}
592
- */
593
- function find_hook_safe_split_index(body_nodes) {
594
- for (let i = 0; i < body_nodes.length; i += 1) {
595
- if (!is_lone_return_if_statement(body_nodes[i])) {
596
- continue;
597
- }
598
-
599
- if (body_contains_top_level_hook_call(body_nodes.slice(i + 1))) {
600
- return i;
601
- }
602
- }
603
-
604
- return -1;
605
- }
606
-
607
520
  /**
608
521
  * @param {any[]} body_nodes
609
522
  * @returns {boolean}
@@ -698,96 +611,6 @@ function is_hook_callee(callee) {
698
611
  return false;
699
612
  }
700
613
 
701
- /**
702
- * @param {any[]} body_nodes
703
- * @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
704
- * @param {Map<string, AST.Identifier>} available_bindings
705
- * @param {any} source_node
706
- * @param {string} suffix
707
- * @param {TransformContext} transform_context
708
- * @returns {any}
709
- */
710
- function create_helper_component_expression(
711
- body_nodes,
712
- helper_state,
713
- available_bindings,
714
- source_node,
715
- suffix,
716
- transform_context,
717
- ) {
718
- if (body_nodes.length === 0) {
719
- return create_null_literal();
720
- }
721
-
722
- const helper_name = create_helper_name(helper_state, suffix);
723
- const helper_id = set_loc(create_generated_identifier(helper_name), source_node);
724
- const helper_bindings = Array.from(available_bindings.values());
725
- const helper_fn = create_helper_function_declaration(
726
- helper_id,
727
- body_nodes,
728
- helper_state,
729
- available_bindings,
730
- helper_bindings,
731
- source_node,
732
- transform_context,
733
- );
734
-
735
- helper_state.helpers.push(helper_fn);
736
-
737
- return create_helper_component_element(helper_id, helper_bindings, source_node);
738
- }
739
-
740
- /**
741
- * @param {AST.Identifier} helper_id
742
- * @param {any[]} body_nodes
743
- * @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
744
- * @param {Map<string, AST.Identifier>} available_bindings
745
- * @param {AST.Identifier[]} helper_bindings
746
- * @param {any} source_node
747
- * @param {TransformContext} transform_context
748
- * @returns {AST.FunctionDeclaration}
749
- */
750
- function create_helper_function_declaration(
751
- helper_id,
752
- body_nodes,
753
- helper_state,
754
- available_bindings,
755
- helper_bindings,
756
- source_node,
757
- transform_context,
758
- ) {
759
- const fn = /** @type {any} */ ({
760
- type: 'FunctionDeclaration',
761
- id: helper_id,
762
- params: helper_bindings.length > 0 ? [create_helper_props_pattern(helper_bindings)] : [],
763
- body: {
764
- type: 'BlockStatement',
765
- body: build_component_statements(
766
- body_nodes,
767
- helper_state,
768
- new Map(available_bindings),
769
- transform_context,
770
- ),
771
- metadata: { path: [] },
772
- },
773
- async: false,
774
- generator: false,
775
- metadata: {
776
- path: [],
777
- is_component: true,
778
- },
779
- });
780
-
781
- if (fn.id) {
782
- fn.id.metadata = /** @type {AST.Identifier['metadata']} */ ({
783
- ...fn.id.metadata,
784
- is_component: true,
785
- });
786
- }
787
-
788
- return set_loc(fn, source_node);
789
- }
790
-
791
614
  /**
792
615
  * @param {AST.Identifier[]} bindings
793
616
  * @returns {AST.ObjectPattern}
@@ -867,7 +690,7 @@ function create_helper_component_element(helper_id, bindings, source_node, mappi
867
690
  }
868
691
 
869
692
  /**
870
- * @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
693
+ * @param {{ base_name: string, next_id: number, helpers: any[], statics: any[] }} helper_state
871
694
  * @param {string} suffix
872
695
  * @returns {string}
873
696
  */
@@ -878,7 +701,7 @@ function create_helper_name(helper_state, suffix) {
878
701
 
879
702
  /**
880
703
  * @param {string} base_name
881
- * @returns {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }}
704
+ * @returns {{ base_name: string, next_id: number, helpers: any[], statics: any[] }}
882
705
  */
883
706
  function create_helper_state(base_name) {
884
707
  return {
@@ -1116,12 +939,31 @@ function is_lone_return_if_statement(node) {
1116
939
  return false;
1117
940
  }
1118
941
 
1119
- const consequent_body =
1120
- node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
942
+ const consequent_body = get_if_consequent_body(node);
1121
943
 
1122
944
  return consequent_body.length === 1 && is_bare_return_statement(consequent_body[0]);
1123
945
  }
1124
946
 
947
+ /**
948
+ * @param {any} node
949
+ * @returns {boolean}
950
+ */
951
+ function is_returning_if_statement(node) {
952
+ if (node?.type !== 'IfStatement' || node.alternate) {
953
+ return false;
954
+ }
955
+
956
+ return get_if_consequent_body(node).some(is_bare_return_statement);
957
+ }
958
+
959
+ /**
960
+ * @param {any} node
961
+ * @returns {any[]}
962
+ */
963
+ function get_if_consequent_body(node) {
964
+ return node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
965
+ }
966
+
1125
967
  /**
1126
968
  * @param {any[]} render_nodes
1127
969
  * @param {any} source_node
@@ -1133,22 +975,25 @@ function create_component_return_statement(
1133
975
  source_node,
1134
976
  map_render_node_locations = true,
1135
977
  ) {
1136
- return /** @type {any} */ ({
1137
- type: 'ReturnStatement',
1138
- argument: build_return_expression(
1139
- render_nodes.map((node) =>
1140
- map_render_node_locations
1141
- ? clone_expression_node(node)
1142
- : clone_expression_node_without_locations(node),
1143
- ),
1144
- ) || {
1145
- type: 'Literal',
1146
- value: null,
1147
- raw: 'null',
978
+ return set_loc(
979
+ /** @type {any} */ ({
980
+ type: 'ReturnStatement',
981
+ argument: build_return_expression(
982
+ render_nodes.map((node) =>
983
+ map_render_node_locations
984
+ ? clone_expression_node(node)
985
+ : clone_expression_node_without_locations(node),
986
+ ),
987
+ ) || {
988
+ type: 'Literal',
989
+ value: null,
990
+ raw: 'null',
991
+ metadata: { path: [] },
992
+ },
1148
993
  metadata: { path: [] },
1149
- },
1150
- metadata: { path: [] },
1151
- });
994
+ }),
995
+ source_node,
996
+ );
1152
997
  }
1153
998
 
1154
999
  /**
@@ -1157,8 +1002,7 @@ function create_component_return_statement(
1157
1002
  * @returns {any}
1158
1003
  */
1159
1004
  function create_component_lone_return_if_statement(node, render_nodes) {
1160
- const consequent_body =
1161
- node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
1005
+ const consequent_body = get_if_consequent_body(node);
1162
1006
 
1163
1007
  return set_loc(
1164
1008
  /** @type {any} */ ({
@@ -1179,6 +1023,195 @@ function create_component_lone_return_if_statement(node, render_nodes) {
1179
1023
  );
1180
1024
  }
1181
1025
 
1026
+ /**
1027
+ * @param {any} node
1028
+ * @param {any[]} render_nodes
1029
+ * @param {TransformContext} transform_context
1030
+ * @returns {any}
1031
+ */
1032
+ function create_component_returning_if_statement(node, render_nodes, transform_context) {
1033
+ const consequent_body = get_if_consequent_body(node);
1034
+ const branch_statements = build_render_statements(consequent_body, true, transform_context);
1035
+ prepend_render_nodes_to_return_statements(branch_statements, render_nodes);
1036
+
1037
+ return set_loc(
1038
+ /** @type {any} */ ({
1039
+ type: 'IfStatement',
1040
+ test: node.test,
1041
+ consequent: set_loc(
1042
+ /** @type {any} */ ({
1043
+ type: 'BlockStatement',
1044
+ body: branch_statements,
1045
+ metadata: { path: [] },
1046
+ }),
1047
+ node.consequent,
1048
+ ),
1049
+ alternate: null,
1050
+ metadata: { path: [] },
1051
+ }),
1052
+ node,
1053
+ );
1054
+ }
1055
+
1056
+ /**
1057
+ * @param {any} node
1058
+ * @param {any[]} continuation_body
1059
+ * @param {any[]} render_nodes
1060
+ * @param {TransformContext} transform_context
1061
+ * @returns {any[]}
1062
+ */
1063
+ function create_component_helper_split_returning_if_statements(
1064
+ node,
1065
+ continuation_body,
1066
+ render_nodes,
1067
+ transform_context,
1068
+ ) {
1069
+ const consequent_body = get_if_consequent_body(node);
1070
+ const return_index = consequent_body.findIndex(is_bare_return_statement);
1071
+ const branch_body =
1072
+ return_index === -1 ? consequent_body : consequent_body.slice(0, return_index);
1073
+ const branch_helper = create_hook_safe_helper(
1074
+ branch_body,
1075
+ undefined,
1076
+ node.consequent,
1077
+ transform_context,
1078
+ );
1079
+ const continuation_helper = create_hook_safe_helper(
1080
+ continuation_body,
1081
+ undefined,
1082
+ node,
1083
+ transform_context,
1084
+ );
1085
+ return [
1086
+ set_loc(
1087
+ /** @type {any} */ ({
1088
+ type: 'IfStatement',
1089
+ test: node.test,
1090
+ consequent: set_loc(
1091
+ /** @type {any} */ ({
1092
+ type: 'BlockStatement',
1093
+ body: [
1094
+ ...branch_helper.setup_statements,
1095
+ {
1096
+ type: 'ReturnStatement',
1097
+ argument: combine_render_return_argument(
1098
+ render_nodes,
1099
+ branch_helper.component_element,
1100
+ ),
1101
+ metadata: { path: [] },
1102
+ },
1103
+ ],
1104
+ metadata: { path: [] },
1105
+ }),
1106
+ node.consequent,
1107
+ ),
1108
+ alternate: null,
1109
+ metadata: { path: [] },
1110
+ }),
1111
+ node,
1112
+ ),
1113
+ ...continuation_helper.setup_statements,
1114
+ {
1115
+ type: 'ReturnStatement',
1116
+ argument: combine_render_return_argument(render_nodes, continuation_helper.component_element),
1117
+ metadata: { path: [] },
1118
+ },
1119
+ ];
1120
+ }
1121
+
1122
+ /**
1123
+ * @param {any[]} statements
1124
+ * @param {any[]} render_nodes
1125
+ * @returns {void}
1126
+ */
1127
+ function prepend_render_nodes_to_return_statements(statements, render_nodes) {
1128
+ if (render_nodes.length === 0) {
1129
+ return;
1130
+ }
1131
+
1132
+ for (const statement of statements) {
1133
+ prepend_render_nodes_to_return_statement(statement, render_nodes, false);
1134
+ }
1135
+ }
1136
+
1137
+ /**
1138
+ * @param {any} node
1139
+ * @param {any[]} render_nodes
1140
+ * @param {boolean} inside_nested_function
1141
+ * @returns {void}
1142
+ */
1143
+ function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nested_function) {
1144
+ if (!node || typeof node !== 'object') {
1145
+ return;
1146
+ }
1147
+
1148
+ if (
1149
+ node.type === 'FunctionDeclaration' ||
1150
+ node.type === 'FunctionExpression' ||
1151
+ node.type === 'ArrowFunctionExpression'
1152
+ ) {
1153
+ inside_nested_function = true;
1154
+ }
1155
+
1156
+ if (!inside_nested_function && node.type === 'ReturnStatement') {
1157
+ node.argument = combine_render_return_argument(render_nodes, node.argument);
1158
+ return;
1159
+ }
1160
+
1161
+ if (Array.isArray(node)) {
1162
+ for (const child of node) {
1163
+ prepend_render_nodes_to_return_statement(child, render_nodes, inside_nested_function);
1164
+ }
1165
+ return;
1166
+ }
1167
+
1168
+ for (const key of Object.keys(node)) {
1169
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1170
+ continue;
1171
+ }
1172
+ prepend_render_nodes_to_return_statement(node[key], render_nodes, inside_nested_function);
1173
+ }
1174
+ }
1175
+
1176
+ /**
1177
+ * @param {any[]} render_nodes
1178
+ * @param {any} return_argument
1179
+ * @returns {any}
1180
+ */
1181
+ function combine_render_return_argument(render_nodes, return_argument) {
1182
+ const combined = render_nodes.map((node) => clone_expression_node_without_locations(node));
1183
+
1184
+ if (!is_null_literal(return_argument)) {
1185
+ combined.push(return_argument_to_render_node(return_argument));
1186
+ }
1187
+
1188
+ return build_return_expression(combined) || create_null_literal();
1189
+ }
1190
+
1191
+ /**
1192
+ * @param {any} argument
1193
+ * @returns {any}
1194
+ */
1195
+ function return_argument_to_render_node(argument) {
1196
+ if (
1197
+ argument?.type === 'JSXElement' ||
1198
+ argument?.type === 'JSXFragment' ||
1199
+ argument?.type === 'JSXExpressionContainer'
1200
+ ) {
1201
+ return argument;
1202
+ }
1203
+
1204
+ return to_jsx_expression_container(argument);
1205
+ }
1206
+
1207
+ /**
1208
+ * @param {any} node
1209
+ * @returns {boolean}
1210
+ */
1211
+ function is_null_literal(node) {
1212
+ return node?.type === 'Literal' && node.value == null;
1213
+ }
1214
+
1182
1215
  /**
1183
1216
  * @param {any} node
1184
1217
  * @returns {any}
@@ -1392,83 +1425,10 @@ function statement_body_to_jsx_child(body_nodes, transform_context) {
1392
1425
  */
1393
1426
  function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
1394
1427
  const source_node = get_body_source_node(body_nodes);
1395
- const helper_id = create_generated_identifier(
1396
- create_local_statement_component_name(transform_context),
1397
- );
1398
- const helper_bindings = Array.from(transform_context.available_bindings.values());
1399
-
1400
- // Save and isolate bindings for the helper body
1401
- const saved_bindings = transform_context.available_bindings;
1402
- transform_context.available_bindings = new Map(saved_bindings);
1403
-
1404
- const helper_fn = /** @type {any} */ ({
1405
- type: 'FunctionDeclaration',
1406
- id: helper_id,
1407
- params: helper_bindings.length > 0 ? [create_helper_props_pattern(helper_bindings)] : [],
1408
- body: {
1409
- type: 'BlockStatement',
1410
- body: build_render_statements(body_nodes, true, transform_context),
1411
- metadata: { path: [] },
1412
- },
1413
- async: false,
1414
- generator: false,
1415
- metadata: {
1416
- path: [],
1417
- is_component: true,
1418
- is_method: false,
1419
- },
1420
- });
1421
-
1422
- // Restore bindings
1423
- transform_context.available_bindings = saved_bindings;
1424
-
1425
- // Register helper for hoisting to module level
1426
- if (transform_context.helper_state) {
1427
- transform_context.helper_state.helpers.push(helper_fn);
1428
-
1429
- return to_jsx_expression_container(
1430
- /** @type {any} */ (
1431
- create_helper_component_element(helper_id, helper_bindings, source_node, {
1432
- mapWrapper: false,
1433
- mapBindingNames: false,
1434
- mapBindingValues: false,
1435
- })
1436
- ),
1437
- source_node,
1438
- );
1439
- }
1428
+ const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
1440
1429
 
1441
1430
  return to_jsx_expression_container(
1442
- /** @type {any} */ ({
1443
- type: 'CallExpression',
1444
- callee: {
1445
- type: 'ArrowFunctionExpression',
1446
- params: [],
1447
- body: /** @type {any} */ ({
1448
- type: 'BlockStatement',
1449
- body: [
1450
- helper_fn,
1451
- {
1452
- type: 'ReturnStatement',
1453
- argument: create_helper_component_element(helper_id, helper_bindings, source_node, {
1454
- mapWrapper: false,
1455
- mapBindingNames: false,
1456
- mapBindingValues: false,
1457
- }),
1458
- metadata: { path: [] },
1459
- },
1460
- ],
1461
- metadata: { path: [] },
1462
- }),
1463
- async: false,
1464
- generator: false,
1465
- expression: false,
1466
- metadata: { path: [] },
1467
- },
1468
- arguments: [],
1469
- optional: false,
1470
- metadata: { path: [] },
1471
- }),
1431
+ create_hook_safe_helper_iife(helper.setup_statements, helper.component_element),
1472
1432
  source_node,
1473
1433
  );
1474
1434
  }
@@ -1497,19 +1457,77 @@ function create_local_statement_component_name(transform_context) {
1497
1457
  */
1498
1458
  function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
1499
1459
  const source_node = get_body_source_node(body_nodes);
1460
+ const helper = create_hook_safe_helper(
1461
+ body_nodes,
1462
+ key_expression,
1463
+ source_node,
1464
+ transform_context,
1465
+ );
1466
+ const statements = [...helper.setup_statements];
1467
+
1468
+ statements.push({
1469
+ type: 'ReturnStatement',
1470
+ argument: helper.component_element,
1471
+ metadata: { path: [] },
1472
+ });
1473
+
1474
+ return statements;
1475
+ }
1476
+
1477
+ /**
1478
+ * @param {any[]} body_nodes
1479
+ * @param {Map<string, AST.Identifier>} available_bindings
1480
+ * @returns {AST.Identifier[]}
1481
+ */
1482
+ function get_referenced_helper_bindings(body_nodes, available_bindings) {
1483
+ const helper_bindings = [];
1484
+ const local_bindings = new Map();
1485
+
1486
+ for (const node of body_nodes) {
1487
+ collect_statement_bindings(node, local_bindings);
1488
+ }
1489
+
1490
+ for (const [name, binding] of available_bindings) {
1491
+ if (local_bindings.has(name)) continue;
1492
+
1493
+ if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
1494
+ helper_bindings.push(binding);
1495
+ }
1496
+ }
1497
+
1498
+ return helper_bindings;
1499
+ }
1500
+
1501
+ /**
1502
+ * @param {any[]} body_nodes
1503
+ * @param {any} key_expression
1504
+ * @param {any} source_node
1505
+ * @param {TransformContext} transform_context
1506
+ * @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
1507
+ */
1508
+ function create_hook_safe_helper(body_nodes, key_expression, source_node, transform_context) {
1500
1509
  const helper_id = create_generated_identifier(
1501
1510
  create_local_statement_component_name(transform_context),
1502
1511
  );
1503
- const helper_bindings = Array.from(transform_context.available_bindings.values());
1512
+ const helper_bindings = get_referenced_helper_bindings(
1513
+ body_nodes,
1514
+ transform_context.available_bindings,
1515
+ );
1516
+ const aliases = helper_bindings.map((binding) =>
1517
+ create_helper_type_alias_declaration(helper_id, binding),
1518
+ );
1519
+ const props_type =
1520
+ helper_bindings.length > 0 ? create_helper_props_type_literal(helper_bindings, aliases) : null;
1521
+ const params =
1522
+ props_type !== null ? [create_typed_helper_props_pattern(helper_bindings, props_type)] : [];
1504
1523
 
1505
- // Save and isolate bindings for the helper body
1506
1524
  const saved_bindings = transform_context.available_bindings;
1507
1525
  transform_context.available_bindings = new Map(saved_bindings);
1508
1526
 
1509
1527
  const helper_fn = /** @type {any} */ ({
1510
- type: 'FunctionDeclaration',
1511
- id: helper_id,
1512
- params: helper_bindings.length > 0 ? [create_helper_props_pattern(helper_bindings)] : [],
1528
+ type: 'FunctionExpression',
1529
+ id: clone_identifier(helper_id),
1530
+ params,
1513
1531
  body: {
1514
1532
  type: 'BlockStatement',
1515
1533
  body: build_render_statements(body_nodes, true, transform_context),
@@ -1524,14 +1542,8 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
1524
1542
  },
1525
1543
  });
1526
1544
 
1527
- // Restore bindings
1528
1545
  transform_context.available_bindings = saved_bindings;
1529
1546
 
1530
- // Register helper for hoisting to module level
1531
- if (transform_context.helper_state) {
1532
- transform_context.helper_state.helpers.push(helper_fn);
1533
- }
1534
-
1535
1547
  const component_element = create_helper_component_element(
1536
1548
  helper_id,
1537
1549
  helper_bindings,
@@ -1554,26 +1566,205 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
1554
1566
  );
1555
1567
  }
1556
1568
 
1557
- // When helper_state is null (no enclosing component context), inline the
1558
- // helper via an IIFE so the function declaration isn't silently dropped.
1559
1569
  if (!transform_context.helper_state) {
1560
- return [
1561
- helper_fn,
1562
- {
1563
- type: 'ReturnStatement',
1564
- argument: component_element,
1565
- metadata: { path: [] },
1566
- },
1567
- ];
1570
+ return {
1571
+ setup_statements: [
1572
+ ...aliases.map((alias) => alias.declaration),
1573
+ create_helper_function_declaration_from_expression(helper_id, helper_fn),
1574
+ ],
1575
+ component_element,
1576
+ };
1568
1577
  }
1569
1578
 
1570
- return [
1571
- {
1572
- type: 'ReturnStatement',
1573
- argument: component_element,
1579
+ const cache_id = create_generated_identifier(
1580
+ `${transform_context.helper_state.base_name}__${helper_id.name}`,
1581
+ );
1582
+ transform_context.helper_state.helpers.push(create_helper_cache_declaration(cache_id));
1583
+
1584
+ return {
1585
+ setup_statements: [
1586
+ ...aliases.map((alias) => alias.declaration),
1587
+ create_cached_helper_declaration(helper_id, cache_id, helper_fn),
1588
+ ],
1589
+ component_element,
1590
+ };
1591
+ }
1592
+
1593
+ /**
1594
+ * @param {any[]} setup_statements
1595
+ * @param {ESTreeJSX.JSXElement} component_element
1596
+ * @returns {any}
1597
+ */
1598
+ function create_hook_safe_helper_iife(setup_statements, component_element) {
1599
+ return /** @type {any} */ ({
1600
+ type: 'CallExpression',
1601
+ callee: {
1602
+ type: 'ArrowFunctionExpression',
1603
+ params: [],
1604
+ body: /** @type {any} */ ({
1605
+ type: 'BlockStatement',
1606
+ body: [
1607
+ ...setup_statements,
1608
+ {
1609
+ type: 'ReturnStatement',
1610
+ argument: component_element,
1611
+ metadata: { path: [] },
1612
+ },
1613
+ ],
1614
+ metadata: { path: [] },
1615
+ }),
1616
+ async: false,
1617
+ generator: false,
1618
+ expression: false,
1574
1619
  metadata: { path: [] },
1575
1620
  },
1576
- ];
1621
+ arguments: [],
1622
+ optional: false,
1623
+ metadata: { path: [] },
1624
+ });
1625
+ }
1626
+
1627
+ /**
1628
+ * @param {AST.Identifier} helper_id
1629
+ * @param {AST.Identifier} binding
1630
+ * @returns {{ id: AST.Identifier, declaration: any }}
1631
+ */
1632
+ function create_helper_type_alias_declaration(helper_id, binding) {
1633
+ const alias_id = create_generated_identifier(`_tsrx_${helper_id.name}_${binding.name}`);
1634
+
1635
+ return {
1636
+ id: alias_id,
1637
+ declaration: /** @type {any} */ ({
1638
+ type: 'VariableDeclaration',
1639
+ kind: 'const',
1640
+ declarations: [
1641
+ {
1642
+ type: 'VariableDeclarator',
1643
+ id: clone_identifier(alias_id),
1644
+ init: create_generated_identifier(binding.name),
1645
+ metadata: { path: [] },
1646
+ },
1647
+ ],
1648
+ metadata: { path: [] },
1649
+ }),
1650
+ };
1651
+ }
1652
+
1653
+ /**
1654
+ * @param {AST.Identifier[]} bindings
1655
+ * @param {{ id: AST.Identifier }[]} aliases
1656
+ * @returns {any}
1657
+ */
1658
+ function create_helper_props_type_literal(bindings, aliases) {
1659
+ return /** @type {any} */ ({
1660
+ type: 'TSTypeLiteral',
1661
+ members: bindings.map(
1662
+ (binding, i) =>
1663
+ /** @type {any} */ ({
1664
+ type: 'TSPropertySignature',
1665
+ key: create_generated_identifier(binding.name),
1666
+ computed: false,
1667
+ optional: false,
1668
+ readonly: false,
1669
+ static: false,
1670
+ kind: 'init',
1671
+ typeAnnotation: {
1672
+ type: 'TSTypeAnnotation',
1673
+ typeAnnotation: {
1674
+ type: 'TSTypeQuery',
1675
+ exprName: clone_identifier(aliases[i].id),
1676
+ typeArguments: null,
1677
+ metadata: { path: [] },
1678
+ },
1679
+ metadata: { path: [] },
1680
+ },
1681
+ metadata: { path: [] },
1682
+ }),
1683
+ ),
1684
+ metadata: { path: [] },
1685
+ });
1686
+ }
1687
+
1688
+ /**
1689
+ * @param {AST.Identifier[]} bindings
1690
+ * @param {any} props_type
1691
+ * @returns {AST.ObjectPattern}
1692
+ */
1693
+ function create_typed_helper_props_pattern(bindings, props_type) {
1694
+ const pattern = create_helper_props_pattern(bindings);
1695
+ /** @type {any} */ (pattern).typeAnnotation = {
1696
+ type: 'TSTypeAnnotation',
1697
+ typeAnnotation: props_type,
1698
+ metadata: { path: [] },
1699
+ };
1700
+ return pattern;
1701
+ }
1702
+
1703
+ /**
1704
+ * @param {AST.Identifier} cache_id
1705
+ * @returns {any}
1706
+ */
1707
+ function create_helper_cache_declaration(cache_id) {
1708
+ return /** @type {any} */ ({
1709
+ type: 'VariableDeclaration',
1710
+ kind: 'let',
1711
+ declarations: [
1712
+ {
1713
+ type: 'VariableDeclarator',
1714
+ id: clone_identifier(cache_id),
1715
+ init: null,
1716
+ metadata: { path: [] },
1717
+ },
1718
+ ],
1719
+ metadata: { path: [] },
1720
+ });
1721
+ }
1722
+
1723
+ /**
1724
+ * @param {AST.Identifier} helper_id
1725
+ * @param {AST.Identifier} cache_id
1726
+ * @param {any} helper_fn
1727
+ * @returns {any}
1728
+ */
1729
+ function create_cached_helper_declaration(helper_id, cache_id, helper_fn) {
1730
+ return /** @type {any} */ ({
1731
+ type: 'VariableDeclaration',
1732
+ kind: 'const',
1733
+ declarations: [
1734
+ {
1735
+ type: 'VariableDeclarator',
1736
+ id: clone_identifier(helper_id),
1737
+ init: {
1738
+ type: 'LogicalExpression',
1739
+ operator: '??',
1740
+ left: clone_identifier(cache_id),
1741
+ right: {
1742
+ type: 'AssignmentExpression',
1743
+ operator: '=',
1744
+ left: clone_identifier(cache_id),
1745
+ right: helper_fn,
1746
+ metadata: { path: [] },
1747
+ },
1748
+ metadata: { path: [] },
1749
+ },
1750
+ metadata: { path: [] },
1751
+ },
1752
+ ],
1753
+ metadata: { path: [] },
1754
+ });
1755
+ }
1756
+
1757
+ /**
1758
+ * @param {AST.Identifier} helper_id
1759
+ * @param {any} helper_fn
1760
+ * @returns {AST.FunctionDeclaration}
1761
+ */
1762
+ function create_helper_function_declaration_from_expression(helper_id, helper_fn) {
1763
+ return /** @type {any} */ ({
1764
+ ...helper_fn,
1765
+ type: 'FunctionDeclaration',
1766
+ id: clone_identifier(helper_id),
1767
+ });
1577
1768
  }
1578
1769
 
1579
1770
  /**