@tsrx/core 0.1.24 → 0.1.25

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.
@@ -49,7 +49,6 @@ import {
49
49
  create_style_ref_setup_statements,
50
50
  get_style_element_stylesheet,
51
51
  } from '../style-ref.js';
52
- import { is_function_or_component_node } from '../../utils/ast.js';
53
52
  import {
54
53
  is_interleaved_body as is_interleaved_body_core,
55
54
  is_capturable_jsx_child,
@@ -57,10 +56,6 @@ import {
57
56
  } from '../jsx-interleave.js';
58
57
  import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
59
58
 
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
59
  const TEMPLATE_FRAGMENT_ERROR =
65
60
  'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only in expression position.';
66
61
  const TSRX_FOR_RETURN_ERROR =
@@ -89,44 +84,6 @@ function report_jsx_fragment_in_tsrx_error(node, transform_context) {
89
84
  );
90
85
  }
91
86
 
92
- /**
93
- * @param {AST.Node} node
94
- * @param {string[]} names
95
- * @param {string} hook_name
96
- * @param {TransformContext} transform_context
97
- * @returns {void}
98
- */
99
- function report_hook_outer_assignment_error(node, names, hook_name, transform_context) {
100
- const target =
101
- names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
102
- error(
103
- `${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.`,
104
- transform_context.filename,
105
- node,
106
- transform_context.errors,
107
- transform_context.comments,
108
- );
109
- }
110
-
111
- /**
112
- * @param {AST.Node} node
113
- * @param {string[]} names
114
- * @param {string} hook_name
115
- * @param {TransformContext} transform_context
116
- * @returns {void}
117
- */
118
- function report_hook_callback_outer_mutation_error(node, names, hook_name, transform_context) {
119
- const target =
120
- names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
121
- error(
122
- `${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.`,
123
- transform_context.filename,
124
- node,
125
- transform_context.errors,
126
- transform_context.comments,
127
- );
128
- }
129
-
130
87
  /**
131
88
  * Local alias for the shared `JsxTransformContext`. Kept as a typedef so the
132
89
  * rest of this file's `@param {TransformContext}` annotations don't all have
@@ -1038,1273 +995,898 @@ function is_interleaved_body(body_nodes) {
1038
995
  }
1039
996
 
1040
997
  /**
1041
- * @param {any} node
998
+ * @param {any[]} body_nodes
1042
999
  * @param {TransformContext} transform_context
1000
+ * @param {boolean} include_platform_setup
1043
1001
  * @returns {boolean}
1044
1002
  */
1045
- function needs_hook_split(node, transform_context) {
1046
- const body = node.body?.body || [];
1047
- return (
1048
- transform_context.platform.hooks?.componentBodyHookHelpers === true &&
1049
- node.body?.type === 'BlockStatement' &&
1050
- (find_hook_split_index(body, transform_context) !== -1 ||
1051
- body_contains_component_body_branch_hook_return(body, transform_context))
1003
+ function body_contains_top_level_hook_call(
1004
+ body_nodes,
1005
+ transform_context,
1006
+ include_platform_setup = false,
1007
+ ) {
1008
+ return body_nodes.some((node) =>
1009
+ statement_contains_top_level_hook_call(node, transform_context, include_platform_setup),
1052
1010
  );
1053
1011
  }
1054
1012
 
1055
1013
  /**
1056
1014
  * @param {any} node
1057
1015
  * @param {TransformContext} transform_context
1058
- * @returns {any}
1059
- */
1060
- function create_hook_split_block(node, transform_context) {
1061
- if (
1062
- transform_context.platform.hooks?.componentBodyHookHelpers !== true ||
1063
- !should_extract_hook_helpers(transform_context) ||
1064
- node.body?.type !== 'BlockStatement'
1065
- ) {
1066
- return null;
1067
- }
1068
-
1069
- const source_body = node.body.body || [];
1070
- const branch_rewrite = rewrite_component_body_branch_hook_returns(source_body, transform_context);
1071
- const body = branch_rewrite.body;
1072
- const split_index = find_hook_split_index(body, transform_context);
1073
- if (split_index === -1 && !branch_rewrite.changed) {
1074
- return null;
1075
- }
1076
-
1077
- let block_body;
1078
- if (split_index === -1) {
1079
- block_body = expand_native_tsrx_return_statement_list(body, transform_context);
1080
- } else {
1081
- const split_statement = body[split_index];
1082
- const continuation_body = body.slice(split_index + 1);
1083
- const helper = create_hook_safe_helper(
1084
- expand_native_tsrx_return_statement_list(continuation_body, transform_context),
1085
- undefined,
1086
- get_body_source_node(continuation_body) || split_statement,
1087
- transform_context,
1088
- );
1089
-
1090
- block_body = [
1091
- ...body.slice(0, split_index + 1),
1092
- ...helper.setup_statements,
1093
- set_loc(b.return(helper.component_element), split_statement),
1094
- ];
1095
- }
1096
-
1097
- const block = b.block(block_body, node.body);
1098
- block.metadata = {
1099
- ...(block.metadata || {}),
1100
- hook_split_block: true,
1101
- };
1102
- return block;
1103
- }
1104
-
1105
- /**
1106
- * @param {any[]} body_nodes
1107
- * @param {TransformContext} transform_context
1016
+ * @param {boolean} include_platform_setup
1108
1017
  * @returns {boolean}
1109
1018
  */
1110
- function body_contains_component_body_branch_hook_return(body_nodes, transform_context) {
1111
- return body_nodes.some((node) =>
1112
- statement_contains_component_body_branch_hook_return(node, transform_context),
1113
- );
1019
+ function statement_contains_top_level_hook_call(node, transform_context, include_platform_setup) {
1020
+ return node_contains_top_level_hook_call(node, false, transform_context, include_platform_setup);
1114
1021
  }
1115
1022
 
1116
1023
  /**
1117
1024
  * @param {any} node
1025
+ * @param {boolean} inside_nested_function
1118
1026
  * @param {TransformContext} transform_context
1027
+ * @param {boolean} include_platform_setup
1119
1028
  * @returns {boolean}
1120
1029
  */
1121
- function statement_contains_component_body_branch_hook_return(node, transform_context) {
1030
+ function node_contains_top_level_hook_call(
1031
+ node,
1032
+ inside_nested_function,
1033
+ transform_context,
1034
+ include_platform_setup,
1035
+ ) {
1122
1036
  if (!node || typeof node !== 'object') {
1123
1037
  return false;
1124
1038
  }
1125
1039
 
1126
- if (Array.isArray(node)) {
1127
- return body_contains_component_body_branch_hook_return(node, transform_context);
1040
+ if (
1041
+ inside_nested_function &&
1042
+ (node.type === 'FunctionDeclaration' ||
1043
+ node.type === 'FunctionExpression' ||
1044
+ node.type === 'ArrowFunctionExpression')
1045
+ ) {
1046
+ return false;
1128
1047
  }
1129
1048
 
1130
- if (is_function_or_class_boundary(node)) {
1049
+ if (
1050
+ node.type === 'FunctionDeclaration' ||
1051
+ node.type === 'FunctionExpression' ||
1052
+ node.type === 'ArrowFunctionExpression'
1053
+ ) {
1054
+ const next_inside_nested_function = true;
1055
+ for (const key of Object.keys(node)) {
1056
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1057
+ continue;
1058
+ }
1059
+ if (
1060
+ node_contains_top_level_hook_call(
1061
+ node[key],
1062
+ next_inside_nested_function,
1063
+ transform_context,
1064
+ include_platform_setup,
1065
+ )
1066
+ ) {
1067
+ return true;
1068
+ }
1069
+ }
1131
1070
  return false;
1132
1071
  }
1133
1072
 
1134
- if (is_plain_if_statement(node)) {
1135
- return (
1136
- branch_needs_component_body_hook_helper(node.consequent, transform_context) ||
1137
- statement_contains_component_body_branch_hook_return(node.consequent, transform_context) ||
1138
- branch_needs_component_body_hook_helper(node.alternate, transform_context) ||
1139
- statement_contains_component_body_branch_hook_return(node.alternate, transform_context)
1073
+ if (
1074
+ !inside_nested_function &&
1075
+ node.type === 'CallExpression' &&
1076
+ (is_hook_callee(node.callee) ||
1077
+ (include_platform_setup &&
1078
+ transform_context.platform.hooks?.isTopLevelSetupCall?.(node, transform_context) === true))
1079
+ ) {
1080
+ return true;
1081
+ }
1082
+
1083
+ if (Array.isArray(node)) {
1084
+ return node.some((child) =>
1085
+ node_contains_top_level_hook_call(
1086
+ child,
1087
+ inside_nested_function,
1088
+ transform_context,
1089
+ include_platform_setup,
1090
+ ),
1140
1091
  );
1141
1092
  }
1142
1093
 
1143
- if (node.type === 'BlockStatement') {
1144
- return body_contains_component_body_branch_hook_return(node.body || [], transform_context);
1094
+ for (const key of Object.keys(node)) {
1095
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1096
+ continue;
1097
+ }
1098
+ if (
1099
+ node_contains_top_level_hook_call(
1100
+ node[key],
1101
+ inside_nested_function,
1102
+ transform_context,
1103
+ include_platform_setup,
1104
+ )
1105
+ ) {
1106
+ return true;
1107
+ }
1145
1108
  }
1146
1109
 
1147
1110
  return false;
1148
1111
  }
1149
1112
 
1150
1113
  /**
1151
- * @param {any[]} body_nodes
1152
- * @param {TransformContext} transform_context
1153
- * @returns {{ body: any[], changed: boolean }}
1154
- */
1155
- function rewrite_component_body_branch_hook_returns(body_nodes, transform_context) {
1156
- let changed = false;
1157
- const body = body_nodes.map((node) => {
1158
- const next_node = rewrite_component_body_branch_hook_return_statement(node, transform_context);
1159
- if (next_node !== node) {
1160
- changed = true;
1161
- }
1162
- return next_node;
1163
- });
1164
- return changed ? { body, changed } : { body: body_nodes, changed: false };
1165
- }
1166
-
1167
- /**
1168
- * @param {any} node
1169
- * @param {TransformContext} transform_context
1170
- * @returns {any}
1114
+ * @param {any} callee
1115
+ * @returns {boolean}
1171
1116
  */
1172
- function rewrite_component_body_branch_hook_return_statement(node, transform_context) {
1173
- if (!node || typeof node !== 'object' || is_function_or_class_boundary(node)) {
1174
- return node;
1175
- }
1176
-
1177
- if (is_plain_if_statement(node)) {
1178
- const consequent = rewrite_component_body_hook_return_branch(
1179
- node.consequent,
1180
- transform_context,
1181
- );
1182
- const alternate = node.alternate
1183
- ? rewrite_component_body_hook_return_branch(node.alternate, transform_context)
1184
- : { node: node.alternate, changed: false };
1117
+ function is_hook_callee(callee) {
1118
+ if (!callee) return false;
1185
1119
 
1186
- if (!consequent.changed && !alternate.changed) {
1187
- return node;
1188
- }
1189
- return set_loc(b.if(node.test, consequent.node, alternate.node), node);
1120
+ if (callee.type === 'Identifier') {
1121
+ return /^use[A-Z0-9]/.test(callee.name);
1190
1122
  }
1191
1123
 
1192
- if (node.type === 'BlockStatement') {
1193
- const rewritten = rewrite_component_body_branch_hook_returns(
1194
- node.body || [],
1195
- transform_context,
1196
- );
1197
- return rewritten.changed ? set_loc(b.block(rewritten.body, node), node) : node;
1124
+ if (
1125
+ !callee.computed &&
1126
+ callee.type === 'MemberExpression' &&
1127
+ callee.property?.type === 'Identifier'
1128
+ ) {
1129
+ return /^use[A-Z0-9]/.test(callee.property.name);
1198
1130
  }
1199
1131
 
1200
- return node;
1132
+ return false;
1201
1133
  }
1202
1134
 
1203
1135
  /**
1204
- * @param {any} branch
1205
- * @param {TransformContext} transform_context
1206
- * @returns {{ node: any, changed: boolean }}
1136
+ * @param {AST.Identifier[]} bindings
1137
+ * @param {Set<string>} [mapped_bindings]
1138
+ * @returns {AST.ObjectPattern}
1207
1139
  */
1208
- function rewrite_component_body_hook_return_branch(branch, transform_context) {
1209
- if (!branch || typeof branch !== 'object') {
1210
- return { node: branch, changed: false };
1211
- }
1140
+ function create_helper_props_pattern(bindings, mapped_bindings = new Set()) {
1141
+ return /** @type {any} */ ({
1142
+ type: 'ObjectPattern',
1143
+ properties: bindings.map((binding) =>
1144
+ create_helper_props_property(binding, mapped_bindings.has(binding.name)),
1145
+ ),
1146
+ metadata: { path: [] },
1147
+ });
1148
+ }
1212
1149
 
1213
- if (is_plain_if_statement(branch)) {
1214
- const next_node = rewrite_component_body_branch_hook_return_statement(
1215
- branch,
1216
- transform_context,
1217
- );
1218
- return { node: next_node, changed: next_node !== branch };
1219
- }
1150
+ /**
1151
+ * @param {AST.Identifier} binding
1152
+ * @param {boolean} [map_binding]
1153
+ * @returns {AST.Property}
1154
+ */
1155
+ function create_helper_props_property(binding, map_binding = false) {
1156
+ const key = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1157
+ const value = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1220
1158
 
1221
- const branch_body = branch.type === 'BlockStatement' ? branch.body || [] : [branch];
1222
- const rewritten = rewrite_component_body_branch_hook_returns(branch_body, transform_context);
1223
- const body = rewritten.body;
1224
- const needs_helper = branch_needs_component_body_hook_helper_body(body, transform_context);
1159
+ return b.prop('init', key, value, false, true);
1160
+ }
1225
1161
 
1226
- if (!needs_helper) {
1227
- if (!rewritten.changed) {
1228
- return { node: branch, changed: false };
1229
- }
1230
- const node =
1231
- branch.type === 'BlockStatement'
1232
- ? set_loc(b.block(body, branch), branch)
1233
- : (body[0] ?? branch);
1234
- return { node, changed: true };
1235
- }
1162
+ /**
1163
+ * @param {AST.Identifier} helper_id
1164
+ * @param {AST.Identifier[]} bindings
1165
+ * @param {any} source_node
1166
+ * @param {{
1167
+ * mapWrapper?: boolean,
1168
+ * mapBindingNames?: boolean,
1169
+ * mapBindingValues?: boolean,
1170
+ * }} [mapping]
1171
+ * @returns {ESTreeJSX.JSXElement}
1172
+ */
1173
+ function create_helper_component_element(helper_id, bindings, source_node, mapping = {}) {
1174
+ const { mapWrapper = true, mapBindingNames = true, mapBindingValues = true } = mapping;
1175
+ const attributes = bindings.map((binding) =>
1176
+ b.jsx_attribute(
1177
+ identifier_to_jsx_name(
1178
+ mapBindingNames ? clone_identifier(binding) : create_generated_identifier(binding.name),
1179
+ ),
1180
+ to_jsx_expression_container(
1181
+ mapBindingValues ? clone_identifier(binding) : create_generated_identifier(binding.name),
1182
+ binding,
1183
+ ),
1184
+ ),
1185
+ );
1236
1186
 
1237
- const helper_body = expand_native_tsrx_return_statement_list(body, transform_context);
1238
- const helper = create_hook_safe_helper(
1239
- helper_body,
1240
- undefined,
1241
- get_body_source_node(body) || branch,
1242
- transform_context,
1187
+ const opening_element = b.jsx_opening_element(
1188
+ identifier_to_jsx_name(clone_identifier(helper_id)),
1189
+ attributes,
1190
+ true,
1243
1191
  );
1244
- const node = set_loc(
1245
- b.block([...helper.setup_statements, set_loc(b.return(helper.component_element), branch)]),
1246
- branch,
1192
+ const element = b.jsx_element_fresh(
1193
+ mapWrapper ? set_loc(opening_element, source_node) : opening_element,
1247
1194
  );
1248
- return { node, changed: true };
1195
+
1196
+ return mapWrapper ? set_loc(element, source_node) : element;
1249
1197
  }
1250
1198
 
1251
1199
  /**
1252
- * @param {any} branch
1253
- * @param {TransformContext} transform_context
1254
- * @returns {boolean}
1200
+ * @param {{ base_name: string, next_id: number, helpers: any[], statics: any[] }} helper_state
1201
+ * @param {string} suffix
1202
+ * @returns {string}
1255
1203
  */
1256
- function branch_needs_component_body_hook_helper(branch, transform_context) {
1257
- if (!branch || typeof branch !== 'object' || is_plain_if_statement(branch)) {
1258
- return false;
1259
- }
1260
- const body = branch.type === 'BlockStatement' ? branch.body || [] : [branch];
1261
- return branch_needs_component_body_hook_helper_body(body, transform_context);
1204
+ function create_helper_name(helper_state, suffix) {
1205
+ helper_state.next_id += 1;
1206
+ return `${helper_state.base_name}__${suffix}${helper_state.next_id}`;
1262
1207
  }
1263
1208
 
1264
1209
  /**
1265
- * @param {any[]} body
1266
- * @param {TransformContext} transform_context
1267
- * @returns {boolean}
1210
+ * @param {string} base_name
1211
+ * @returns {{ base_name: string, next_id: number, helpers: any[], statics: any[] }}
1268
1212
  */
1269
- function branch_needs_component_body_hook_helper_body(body, transform_context) {
1270
- return (
1271
- body_has_top_level_component_body_return(body) &&
1272
- body_contains_direct_top_level_hook_call(body, transform_context, true)
1273
- );
1213
+ function create_helper_state(base_name) {
1214
+ return {
1215
+ base_name,
1216
+ next_id: 0,
1217
+ helpers: [],
1218
+ statics: [],
1219
+ };
1274
1220
  }
1275
1221
 
1276
1222
  /**
1277
- * @param {any[]} body
1278
- * @returns {boolean}
1223
+ * @param {{ helpers: any[], statics: any[] }} helper_state
1224
+ * @returns {{ generated_helpers: any[], generated_statics: any[] } | null}
1279
1225
  */
1280
- function body_has_top_level_component_body_return(body) {
1281
- return body.some((node) => node?.type === 'ReturnStatement');
1226
+ function create_generated_helper_metadata(helper_state) {
1227
+ if (helper_state.helpers.length === 0 && helper_state.statics.length === 0) {
1228
+ return null;
1229
+ }
1230
+ return {
1231
+ generated_helpers: helper_state.helpers,
1232
+ generated_statics: helper_state.statics,
1233
+ };
1282
1234
  }
1283
1235
 
1284
1236
  /**
1285
- * @param {any[]} body
1286
- * @param {TransformContext} transform_context
1287
- * @param {boolean} include_platform_setup
1288
- * @returns {boolean}
1237
+ * @param {any} metadata
1238
+ * @returns {any}
1289
1239
  */
1290
- function body_contains_direct_top_level_hook_call(body, transform_context, include_platform_setup) {
1291
- return body.some((node) =>
1292
- statement_contains_direct_top_level_hook_call(node, transform_context, include_platform_setup),
1293
- );
1240
+ function strip_function_transform_metadata(metadata) {
1241
+ const { native_tsrx, ...next_metadata } = metadata || {};
1242
+ return next_metadata;
1294
1243
  }
1295
1244
 
1296
1245
  /**
1297
- * @param {any} node
1298
- * @param {TransformContext} transform_context
1299
- * @param {boolean} include_platform_setup
1300
- * @returns {boolean}
1246
+ * @param {AST.BlockStatement} node
1247
+ * @param {{ next: () => any, visit: (node: any, state?: TransformContext) => any, state: TransformContext, path: AST.Node[] }} context
1248
+ * @returns {any}
1301
1249
  */
1302
- function statement_contains_direct_top_level_hook_call(
1303
- node,
1304
- transform_context,
1305
- include_platform_setup,
1306
- ) {
1307
- if (!node || typeof node !== 'object') {
1308
- return false;
1309
- }
1310
-
1311
- if (is_function_or_class_boundary(node)) {
1312
- return false;
1250
+ function transform_block_statement(node, { next, visit, state, path }) {
1251
+ if (node.metadata?.native_return_block) {
1252
+ return next() ?? node;
1313
1253
  }
1314
1254
 
1315
- if (
1316
- is_plain_if_statement(node) ||
1317
- is_switch_control_node(node) ||
1318
- is_try_control_node(node) ||
1319
- is_for_of_control_node(node)
1320
- ) {
1321
- return false;
1255
+ if (get_active_native_tsrx_function(path)?.metadata?.native_tsrx_body) {
1256
+ const block = create_native_tsrx_statement_list_block(node, state);
1257
+ if (block) {
1258
+ return visit(block, state);
1259
+ }
1322
1260
  }
1323
1261
 
1324
- return statement_contains_top_level_hook_call(node, transform_context, include_platform_setup);
1262
+ return next() ?? node;
1325
1263
  }
1326
1264
 
1327
1265
  /**
1328
- * @param {any[]} body_nodes
1329
- * @param {TransformContext} transform_context
1330
- * @returns {number}
1266
+ * @param {any} node
1267
+ * @param {{ next: () => any, visit: (node: any, state?: TransformContext) => any, state: TransformContext, path: AST.Node[] }} context
1268
+ * @returns {any}
1331
1269
  */
1332
- function find_hook_split_index(body_nodes, transform_context) {
1333
- for (let i = 0; i < body_nodes.length; i += 1) {
1334
- if (!is_component_body_conditional_return_statement(body_nodes[i])) {
1335
- continue;
1336
- }
1337
-
1338
- if (body_contains_top_level_hook_call(body_nodes.slice(i + 1), transform_context, true)) {
1339
- return i;
1270
+ function transform_return_statement(node, { next, visit, state, path }) {
1271
+ const active_native_tsrx_function = get_active_native_tsrx_function(path);
1272
+ if (active_native_tsrx_function && is_native_tsrx_node(node.argument)) {
1273
+ if (!active_native_tsrx_function.metadata?.native_tsrx_body) {
1274
+ const statements = mark_native_pretransformed_jsx(
1275
+ create_native_tsrx_render_statements(node.argument, state),
1276
+ );
1277
+ if (statements.length === 1) {
1278
+ return visit(statements[0], state);
1279
+ }
1280
+ const block = b.block(statements, node.argument);
1281
+ block.metadata = {
1282
+ ...(block.metadata || {}),
1283
+ native_return_block: true,
1284
+ };
1285
+ return visit(block, state);
1340
1286
  }
1287
+ return visit(create_native_tsrx_render_block(node.argument, state), state);
1341
1288
  }
1342
1289
 
1343
- return -1;
1290
+ return next() ?? node;
1344
1291
  }
1345
1292
 
1346
1293
  /**
1347
1294
  * @param {any} node
1348
- * @returns {boolean}
1295
+ * @param {{ state: TransformContext, path: AST.Node[] }} context
1296
+ * @returns {any}
1349
1297
  */
1350
- function is_component_body_conditional_return_statement(node) {
1351
- if (!is_if_control_node(node)) {
1352
- return false;
1298
+ function transform_jsx_code_block(node, { state, path }) {
1299
+ const body_nodes = get_jsx_code_block_body_nodes(node, state);
1300
+ const parent = /** @type {any} */ (path.at(-1));
1301
+
1302
+ if (parent && parent.body === node && is_function_or_class_boundary(parent)) {
1303
+ const block = b.block(
1304
+ mark_native_pretransformed_jsx(build_render_statements(body_nodes, true, state)),
1305
+ node,
1306
+ );
1307
+ block.metadata = {
1308
+ ...(block.metadata || {}),
1309
+ native_return_block: true,
1310
+ };
1311
+ return block;
1353
1312
  }
1354
1313
 
1355
- return (
1356
- statement_contains_component_body_return(node.consequent) ||
1357
- statement_contains_component_body_return(node.alternate)
1314
+ const expression = b.call(
1315
+ b.arrow([], b.block(build_render_statements(body_nodes, true, state), node)),
1358
1316
  );
1317
+
1318
+ return in_jsx_child_context(path) ? to_jsx_expression_container(expression, node) : expression;
1359
1319
  }
1360
1320
 
1361
1321
  /**
1362
- * @param {any} node
1363
- * @returns {boolean}
1322
+ * @param {AST.Node[]} path
1323
+ * @returns {any | null}
1364
1324
  */
1365
- function statement_contains_component_body_return(node) {
1366
- if (!node || typeof node !== 'object') {
1367
- return false;
1368
- }
1369
-
1370
- if (node.type === 'ReturnStatement') {
1371
- return true;
1325
+ function get_active_native_tsrx_function(path) {
1326
+ for (let i = path.length - 1; i >= 0; i -= 1) {
1327
+ const node = /** @type {any} */ (path[i]);
1328
+ if (is_function_or_class_boundary(node)) {
1329
+ return node.metadata?.native_tsrx ? node : null;
1330
+ }
1372
1331
  }
1332
+ return null;
1333
+ }
1373
1334
 
1374
- if (is_function_or_class_boundary(node)) {
1375
- return false;
1376
- }
1335
+ /**
1336
+ * @param {any} node
1337
+ * @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
1338
+ * @returns {any}
1339
+ */
1340
+ function transform_function(node, context) {
1341
+ // Lower a `@{ … }` function body (JSXCodeBlock) to an ordinary block: the
1342
+ // setup statements followed by `return <render>` when the block produces a
1343
+ // render output. The parser already marks the render JSX as native_tsrx, so
1344
+ // from here it flows through the existing native-component machinery exactly
1345
+ // like the older fenced `{ return <> … </> }` shape.
1346
+ const has_jsx_code_block_body = node.body?.type === 'JSXCodeBlock';
1347
+ lower_jsx_code_block_function_body(node);
1377
1348
 
1378
- if (Array.isArray(node)) {
1379
- return node.some(statement_contains_component_body_return);
1349
+ if (
1350
+ has_jsx_code_block_body ||
1351
+ node.metadata?.native_tsrx_function ||
1352
+ function_has_native_tsrx_return(node)
1353
+ ) {
1354
+ return transform_native_tsrx_function(node, context, {
1355
+ nativeBody: has_jsx_code_block_body || !!node.metadata?.native_tsrx_function,
1356
+ });
1380
1357
  }
1381
1358
 
1382
- if (node.type === 'BlockStatement') {
1383
- return (node.body || []).some(statement_contains_component_body_return);
1384
- }
1359
+ return transform_function_with_hook_helpers(node, context);
1360
+ }
1385
1361
 
1386
- if (is_if_control_node(node)) {
1387
- return (
1388
- statement_contains_component_body_return(node.consequent) ||
1389
- statement_contains_component_body_return(node.alternate)
1390
- );
1391
- }
1362
+ /**
1363
+ * @param {any} node
1364
+ * @returns {void}
1365
+ */
1366
+ function lower_jsx_code_block_function_body(node) {
1367
+ if (node.body?.type !== 'JSXCodeBlock') return;
1392
1368
 
1393
- if (is_switch_control_node(node)) {
1394
- return (node.cases || []).some((/** @type {any} */ switch_case) =>
1395
- statement_contains_component_body_return(switch_case.consequent || []),
1396
- );
1369
+ const code_block = node.body;
1370
+ const statements = [...code_block.body];
1371
+ if (code_block.render != null) {
1372
+ let render = code_block.render;
1373
+ if (!is_native_tsrx_node(render)) {
1374
+ // A control-flow output (@if/@for/@switch/@try) isn't itself a native
1375
+ // template node, so `return @if (…) { … }` wouldn't be recognized as a
1376
+ // component render output. Wrap it in a native fragment so it flows
1377
+ // through the same children-rendering path as a `<> … </>` render.
1378
+ const fragment = b.jsx_fragment([render]);
1379
+ fragment.metadata = { ...fragment.metadata, native_tsrx: true };
1380
+ render = fragment;
1381
+ }
1382
+ statements.push(b.return(render, code_block.render));
1397
1383
  }
1398
-
1399
- if (is_try_control_node(node)) {
1400
- return (
1401
- statement_contains_component_body_return(node.block) ||
1402
- statement_contains_component_body_return(node.handler?.body) ||
1403
- statement_contains_component_body_return(node.finalizer)
1404
- );
1384
+ node.body = b.block(statements, code_block);
1385
+ if (node.type === 'ArrowFunctionExpression') {
1386
+ node.expression = false;
1405
1387
  }
1406
-
1407
- return false;
1408
1388
  }
1409
1389
 
1410
1390
  /**
1411
- * @param {any[]} body_nodes
1412
- * @param {TransformContext} transform_context
1413
- * @param {boolean} include_platform_setup
1414
- * @returns {boolean}
1391
+ * @param {any} node
1392
+ * @param {{ next: () => any, state: TransformContext }} context
1393
+ * @param {{ nativeBody?: boolean }} [options]
1394
+ * @returns {any}
1415
1395
  */
1416
- function body_contains_top_level_hook_call(
1417
- body_nodes,
1418
- transform_context,
1419
- include_platform_setup = false,
1420
- ) {
1421
- return body_nodes.some((node) =>
1422
- statement_contains_top_level_hook_call(node, transform_context, include_platform_setup),
1396
+ function transform_native_tsrx_function(node, { next, state }, { nativeBody = false } = {}) {
1397
+ const helper_state =
1398
+ state.helper_state || create_helper_state(get_function_helper_base_name(node));
1399
+ const saved_helper_state = state.helper_state;
1400
+ const saved_bindings = state.available_bindings;
1401
+ const saved_hook_helpers_enabled = state.hook_helpers_enabled;
1402
+
1403
+ state.helper_state = helper_state;
1404
+ state.hook_helpers_enabled = is_uppercase_function_like(node);
1405
+ node.metadata = {
1406
+ ...(node.metadata || {}),
1407
+ native_tsrx: true,
1408
+ ...(nativeBody ? { native_tsrx_body: true } : {}),
1409
+ };
1410
+ state.available_bindings = merge_binding_maps(
1411
+ saved_bindings,
1412
+ collect_function_scope_bindings(node),
1423
1413
  );
1424
- }
1425
1414
 
1426
- /**
1427
- * @param {any} node
1428
- * @param {TransformContext} transform_context
1429
- * @param {boolean} include_platform_setup
1430
- * @returns {boolean}
1431
- */
1432
- function statement_contains_top_level_hook_call(node, transform_context, include_platform_setup) {
1433
- return node_contains_top_level_hook_call(node, false, transform_context, include_platform_setup);
1415
+ validate_native_await(node, state);
1416
+
1417
+ const inner = /** @type {any} */ (next() ?? node);
1418
+ if (
1419
+ inner !== node &&
1420
+ node.type === 'ArrowFunctionExpression' &&
1421
+ is_native_tsrx_node(node.body) &&
1422
+ inner.body?.type === 'BlockStatement'
1423
+ ) {
1424
+ inner.expression = false;
1425
+ }
1426
+
1427
+ state.helper_state = saved_helper_state;
1428
+ state.available_bindings = saved_bindings;
1429
+ state.hook_helpers_enabled = saved_hook_helpers_enabled;
1430
+
1431
+ inner.metadata = {
1432
+ ...strip_function_transform_metadata(inner.metadata),
1433
+ native_tsrx_function: true,
1434
+ ...(nativeBody ? { native_tsrx_body: true } : {}),
1435
+ ...(!saved_helper_state ? create_generated_helper_metadata(helper_state) || {} : {}),
1436
+ };
1437
+
1438
+ return inner;
1434
1439
  }
1435
1440
 
1436
1441
  /**
1437
1442
  * @param {any} node
1438
- * @param {boolean} inside_nested_function
1439
1443
  * @param {TransformContext} transform_context
1440
- * @param {boolean} include_platform_setup
1441
- * @returns {boolean}
1444
+ * @returns {void}
1442
1445
  */
1443
- function node_contains_top_level_hook_call(
1444
- node,
1445
- inside_nested_function,
1446
- transform_context,
1447
- include_platform_setup,
1448
- ) {
1449
- if (!node || typeof node !== 'object') {
1450
- return false;
1446
+ function validate_native_await(node, transform_context) {
1447
+ const await_node = find_native_await(node);
1448
+ if (!await_node) {
1449
+ return;
1451
1450
  }
1452
1451
 
1453
- if (
1454
- inside_nested_function &&
1455
- (node.type === 'FunctionDeclaration' ||
1456
- node.type === 'FunctionExpression' ||
1457
- node.type === 'ArrowFunctionExpression')
1458
- ) {
1459
- return false;
1452
+ const validator = transform_context.platform.hooks?.validateComponentAwait;
1453
+ if (validator) {
1454
+ validator(await_node, node, transform_context, false, transform_context.source || '');
1455
+ return;
1460
1456
  }
1461
1457
 
1462
- if (
1463
- node.type === 'FunctionDeclaration' ||
1464
- node.type === 'FunctionExpression' ||
1465
- node.type === 'ArrowFunctionExpression'
1466
- ) {
1467
- const next_inside_nested_function = true;
1468
- for (const key of Object.keys(node)) {
1469
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1470
- continue;
1471
- }
1472
- if (
1473
- node_contains_top_level_hook_call(
1474
- node[key],
1475
- next_inside_nested_function,
1476
- transform_context,
1477
- include_platform_setup,
1478
- )
1479
- ) {
1480
- return true;
1481
- }
1482
- }
1483
- return false;
1458
+ if (transform_context.platform.validation.requireUseServerForAwait) {
1459
+ error(
1460
+ 'Top-level `await` in TSRX functions requires a module-level `"use server"` directive.',
1461
+ transform_context.filename,
1462
+ await_node,
1463
+ transform_context.errors,
1464
+ transform_context.comments,
1465
+ );
1484
1466
  }
1467
+ }
1485
1468
 
1469
+ /**
1470
+ * @param {any} node
1471
+ * @returns {any | null}
1472
+ */
1473
+ function find_native_await(node) {
1486
1474
  if (
1487
- !inside_nested_function &&
1488
- node.type === 'CallExpression' &&
1489
- (is_hook_callee(node.callee) ||
1490
- (include_platform_setup &&
1491
- transform_context.platform.hooks?.isTopLevelSetupCall?.(node, transform_context) === true))
1475
+ node.type === 'ArrowFunctionExpression' &&
1476
+ node.body?.type !== 'BlockStatement' &&
1477
+ node_contains_native_tsrx_template(node.body)
1492
1478
  ) {
1493
- return true;
1479
+ return find_first_top_level_await(node.body, false);
1494
1480
  }
1495
1481
 
1496
- if (Array.isArray(node)) {
1497
- return node.some((child) =>
1498
- node_contains_top_level_hook_call(
1499
- child,
1500
- inside_nested_function,
1501
- transform_context,
1502
- include_platform_setup,
1503
- ),
1504
- );
1482
+ if (node.body?.type === 'JSXCodeBlock') {
1483
+ return find_native_await_in_list(get_raw_jsx_code_block_body_nodes(node.body));
1505
1484
  }
1506
1485
 
1507
- for (const key of Object.keys(node)) {
1508
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1509
- continue;
1510
- }
1511
- if (
1512
- node_contains_top_level_hook_call(
1513
- node[key],
1514
- inside_nested_function,
1515
- transform_context,
1516
- include_platform_setup,
1517
- )
1518
- ) {
1519
- return true;
1520
- }
1521
- }
1486
+ const body = node.body?.type === 'BlockStatement' ? node.body.body || [] : [];
1487
+ return find_native_await_in_list(body);
1488
+ }
1522
1489
 
1523
- return false;
1490
+ /**
1491
+ * @param {any[]} statements
1492
+ * @returns {any | null}
1493
+ */
1494
+ function find_native_await_in_list(statements) {
1495
+ for (const statement of statements) {
1496
+ const found = find_native_await_in_statement(statement);
1497
+ if (found) return found;
1498
+ }
1499
+ return null;
1524
1500
  }
1525
1501
 
1526
1502
  /**
1527
- * @param {any} callee
1528
- * @returns {boolean}
1503
+ * @param {any} statement
1504
+ * @returns {any | null}
1529
1505
  */
1530
- function is_hook_callee(callee) {
1531
- if (!callee) return false;
1506
+ function find_native_await_in_statement(statement) {
1507
+ if (!statement || typeof statement !== 'object') return null;
1532
1508
 
1533
- if (callee.type === 'Identifier') {
1534
- return /^use[A-Z0-9]/.test(callee.name);
1509
+ if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
1510
+ return find_first_top_level_await_in_tsrx_function_body(statement.argument.children || []);
1535
1511
  }
1536
1512
 
1537
1513
  if (
1538
- !callee.computed &&
1539
- callee.type === 'MemberExpression' &&
1540
- callee.property?.type === 'Identifier'
1514
+ statement.type === 'ReturnStatement' &&
1515
+ node_contains_native_tsrx_template(statement.argument)
1541
1516
  ) {
1542
- return /^use[A-Z0-9]/.test(callee.property.name);
1517
+ return find_first_top_level_await(statement.argument, false);
1543
1518
  }
1544
1519
 
1545
- return false;
1546
- }
1520
+ if (is_function_or_class_boundary(statement)) {
1521
+ return null;
1522
+ }
1547
1523
 
1548
- /**
1549
- * @param {AST.Identifier[]} bindings
1550
- * @param {Set<string>} [mapped_bindings]
1551
- * @returns {AST.ObjectPattern}
1552
- */
1553
- function create_helper_props_pattern(bindings, mapped_bindings = new Set()) {
1554
- return /** @type {any} */ ({
1555
- type: 'ObjectPattern',
1556
- properties: bindings.map((binding) =>
1557
- create_helper_props_property(binding, mapped_bindings.has(binding.name)),
1558
- ),
1559
- metadata: { path: [] },
1560
- });
1561
- }
1524
+ if (statement.type === 'BlockStatement') {
1525
+ return find_native_await_in_list(statement.body || []);
1526
+ }
1562
1527
 
1563
- /**
1564
- * @param {AST.Identifier} binding
1565
- * @param {boolean} [map_binding]
1566
- * @returns {AST.Property}
1567
- */
1568
- function create_helper_props_property(binding, map_binding = false) {
1569
- const key = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1570
- const value = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1528
+ if (is_if_control_node(statement)) {
1529
+ return (
1530
+ find_native_await_in_statement(statement.consequent) ||
1531
+ find_native_await_in_statement(statement.alternate)
1532
+ );
1533
+ }
1571
1534
 
1572
- return b.prop('init', key, value, false, true);
1535
+ if (is_switch_control_node(statement)) {
1536
+ for (const switch_case of statement.cases || []) {
1537
+ const found = find_native_await_in_list(switch_case.consequent || []);
1538
+ if (found) return found;
1539
+ }
1540
+ return null;
1541
+ }
1542
+
1543
+ if (is_try_control_node(statement)) {
1544
+ return (
1545
+ find_native_await_in_statement(statement.block) ||
1546
+ find_native_await_in_statement(statement.handler?.body) ||
1547
+ find_native_await_in_statement(statement.finalizer)
1548
+ );
1549
+ }
1550
+
1551
+ return find_first_top_level_await(statement, false);
1573
1552
  }
1574
1553
 
1575
1554
  /**
1576
- * @param {AST.Identifier} helper_id
1577
- * @param {AST.Identifier[]} bindings
1578
- * @param {any} source_node
1579
- * @param {{
1580
- * mapWrapper?: boolean,
1581
- * mapBindingNames?: boolean,
1582
- * mapBindingValues?: boolean,
1583
- * }} [mapping]
1584
- * @returns {ESTreeJSX.JSXElement}
1555
+ * @param {any} node
1556
+ * @param {{ next: () => any, state: TransformContext }} context
1557
+ * @returns {any}
1585
1558
  */
1586
- function create_helper_component_element(helper_id, bindings, source_node, mapping = {}) {
1587
- const { mapWrapper = true, mapBindingNames = true, mapBindingValues = true } = mapping;
1588
- const attributes = bindings.map((binding) =>
1589
- b.jsx_attribute(
1590
- identifier_to_jsx_name(
1591
- mapBindingNames ? clone_identifier(binding) : create_generated_identifier(binding.name),
1592
- ),
1593
- to_jsx_expression_container(
1594
- mapBindingValues ? clone_identifier(binding) : create_generated_identifier(binding.name),
1595
- binding,
1596
- ),
1597
- ),
1598
- );
1559
+ function transform_function_with_hook_helpers(node, { next, state }) {
1560
+ if (!state.platform.hooks?.moduleScopedHookComponents) {
1561
+ return next() ?? node;
1562
+ }
1599
1563
 
1600
- const opening_element = b.jsx_opening_element(
1601
- identifier_to_jsx_name(clone_identifier(helper_id)),
1602
- attributes,
1603
- true,
1604
- );
1605
- const element = b.jsx_element_fresh(
1606
- mapWrapper ? set_loc(opening_element, source_node) : opening_element,
1607
- );
1564
+ const has_hook_bearing_tsrx = function_contains_hook_bearing_tsrx(node, state);
1565
+ if (state.helper_state || !is_uppercase_function_like(node) || !has_hook_bearing_tsrx) {
1566
+ return next() ?? node;
1567
+ }
1608
1568
 
1609
- return mapWrapper ? set_loc(element, source_node) : element;
1569
+ const helper_state = create_helper_state(get_function_helper_base_name(node));
1570
+ const saved_helper_state = state.helper_state;
1571
+ const saved_bindings = state.available_bindings;
1572
+ const saved_hook_helpers_enabled = state.hook_helpers_enabled;
1573
+
1574
+ state.helper_state = helper_state;
1575
+ state.hook_helpers_enabled = true;
1576
+ state.available_bindings = collect_function_scope_bindings(node);
1577
+
1578
+ const inner = /** @type {any} */ (next() ?? node);
1579
+
1580
+ state.helper_state = saved_helper_state;
1581
+ state.available_bindings = saved_bindings;
1582
+ state.hook_helpers_enabled = saved_hook_helpers_enabled;
1583
+
1584
+ inner.metadata = {
1585
+ ...strip_function_transform_metadata(inner.metadata),
1586
+ ...(create_generated_helper_metadata(helper_state) || {}),
1587
+ };
1588
+
1589
+ return inner;
1610
1590
  }
1611
1591
 
1612
1592
  /**
1613
- * @param {{ base_name: string, next_id: number, helpers: any[], statics: any[] }} helper_state
1614
- * @param {string} suffix
1593
+ * @param {any} node
1615
1594
  * @returns {string}
1616
1595
  */
1617
- function create_helper_name(helper_state, suffix) {
1618
- helper_state.next_id += 1;
1619
- return `${helper_state.base_name}__${suffix}${helper_state.next_id}`;
1596
+ function get_function_helper_base_name(node) {
1597
+ return get_function_like_name(node) || 'TSRXTemplate';
1620
1598
  }
1621
1599
 
1622
1600
  /**
1623
- * @param {string} base_name
1624
- * @returns {{ base_name: string, next_id: number, helpers: any[], statics: any[] }}
1601
+ * @param {any} node
1602
+ * @returns {boolean}
1625
1603
  */
1626
- function create_helper_state(base_name) {
1627
- return {
1628
- base_name,
1629
- next_id: 0,
1630
- helpers: [],
1631
- statics: [],
1632
- };
1604
+ function is_uppercase_function_like(node) {
1605
+ const name = get_function_like_name(node);
1606
+ return !!(name && /^[A-Z]/.test(name));
1633
1607
  }
1634
1608
 
1635
1609
  /**
1636
- * @param {{ helpers: any[], statics: any[] }} helper_state
1637
- * @returns {{ generated_helpers: any[], generated_statics: any[] } | null}
1610
+ * @param {any} node
1611
+ * @returns {string | null}
1638
1612
  */
1639
- function create_generated_helper_metadata(helper_state) {
1640
- if (helper_state.helpers.length === 0 && helper_state.statics.length === 0) {
1641
- return null;
1613
+ function get_function_like_name(node) {
1614
+ if (node.id?.type === 'Identifier') {
1615
+ return node.id.name;
1642
1616
  }
1643
- return {
1644
- generated_helpers: helper_state.helpers,
1645
- generated_statics: helper_state.statics,
1646
- };
1647
- }
1648
1617
 
1649
- /**
1650
- * @param {any} metadata
1651
- * @returns {any}
1652
- */
1653
- function strip_function_transform_metadata(metadata) {
1654
- const { native_tsrx, hook_split, ...next_metadata } = metadata || {};
1655
- return next_metadata;
1656
- }
1618
+ const parent = /** @type {any} */ (node.metadata?.path?.at(-1));
1619
+ if (!parent) return null;
1657
1620
 
1658
- /**
1659
- * @param {AST.BlockStatement} node
1660
- * @param {{ next: () => any, visit: (node: any, state?: TransformContext) => any, state: TransformContext, path: AST.Node[] }} context
1661
- * @returns {any}
1662
- */
1663
- function transform_block_statement(node, { next, visit, state, path }) {
1664
- if (node.metadata?.hook_split_block || node.metadata?.native_return_block) {
1665
- return next() ?? node;
1621
+ if (parent.type === 'VariableDeclarator' && parent.init === node) {
1622
+ return get_static_binding_name(parent.id);
1666
1623
  }
1667
1624
 
1668
- const parent = /** @type {any} */ (path.at(-1));
1669
- if (parent?.metadata?.hook_split && parent.body === node) {
1670
- const block = create_hook_split_block(parent, state);
1671
- if (block) {
1672
- return visit(block, state);
1673
- }
1625
+ if (parent.type === 'Property' && parent.value === node) {
1626
+ return get_static_property_name(parent.key);
1674
1627
  }
1675
1628
 
1676
- if (get_active_native_tsrx_function(path)?.metadata?.native_tsrx_body) {
1677
- const block = create_native_tsrx_statement_list_block(node, state);
1678
- if (block) {
1679
- return visit(block, state);
1680
- }
1629
+ if (parent.type === 'MethodDefinition' && parent.value === node) {
1630
+ return get_static_property_name(parent.key);
1681
1631
  }
1682
1632
 
1683
- return next() ?? node;
1633
+ if (parent.type === 'AssignmentExpression' && parent.right === node) {
1634
+ return get_static_binding_name(parent.left);
1635
+ }
1636
+
1637
+ return null;
1684
1638
  }
1685
1639
 
1686
1640
  /**
1687
1641
  * @param {any} node
1688
- * @param {{ next: () => any, visit: (node: any, state?: TransformContext) => any, state: TransformContext, path: AST.Node[] }} context
1689
- * @returns {any}
1642
+ * @returns {string | null}
1690
1643
  */
1691
- function transform_return_statement(node, { next, visit, state, path }) {
1692
- const active_native_tsrx_function = get_active_native_tsrx_function(path);
1693
- if (active_native_tsrx_function && is_native_tsrx_node(node.argument)) {
1694
- if (!active_native_tsrx_function.metadata?.native_tsrx_body) {
1695
- const statements = mark_native_pretransformed_jsx(
1696
- create_native_tsrx_render_statements(node.argument, state),
1697
- );
1698
- if (statements.length === 1) {
1699
- return visit(statements[0], state);
1700
- }
1701
- const block = b.block(statements, node.argument);
1702
- block.metadata = {
1703
- ...(block.metadata || {}),
1704
- native_return_block: true,
1705
- };
1706
- return visit(block, state);
1707
- }
1708
- return visit(create_native_tsrx_render_block(node.argument, state), state);
1644
+ function get_static_binding_name(node) {
1645
+ if (node?.type === 'Identifier') {
1646
+ return node.name;
1709
1647
  }
1710
-
1711
- return next() ?? node;
1648
+ if (node?.type === 'MemberExpression' && !node.computed) {
1649
+ return get_static_property_name(node.property);
1650
+ }
1651
+ return null;
1712
1652
  }
1713
1653
 
1714
1654
  /**
1715
- * @param {any} node
1716
- * @param {{ state: TransformContext, path: AST.Node[] }} context
1717
- * @returns {any}
1655
+ * @param {any} key
1656
+ * @returns {string | null}
1718
1657
  */
1719
- function transform_jsx_code_block(node, { state, path }) {
1720
- const body_nodes = get_jsx_code_block_body_nodes(node, state);
1721
- const parent = /** @type {any} */ (path.at(-1));
1722
-
1723
- if (parent && parent.body === node && is_function_or_class_boundary(parent)) {
1724
- const block = b.block(
1725
- mark_native_pretransformed_jsx(build_render_statements(body_nodes, true, state)),
1726
- node,
1727
- );
1728
- block.metadata = {
1729
- ...(block.metadata || {}),
1730
- native_return_block: true,
1731
- };
1732
- return block;
1658
+ function get_static_property_name(key) {
1659
+ if (key?.type === 'Identifier') {
1660
+ return key.name;
1733
1661
  }
1734
-
1735
- const expression = b.call(
1736
- b.arrow([], b.block(build_render_statements(body_nodes, true, state), node)),
1737
- );
1738
-
1739
- return in_jsx_child_context(path) ? to_jsx_expression_container(expression, node) : expression;
1662
+ if (key?.type === 'Literal' && typeof key.value === 'string') {
1663
+ return key.value;
1664
+ }
1665
+ return null;
1740
1666
  }
1741
1667
 
1742
1668
  /**
1743
- * @param {AST.Node[]} path
1744
- * @returns {any | null}
1669
+ * @param {any} node
1670
+ * @returns {Map<string, AST.Identifier>}
1745
1671
  */
1746
- function get_active_native_tsrx_function(path) {
1747
- for (let i = path.length - 1; i >= 0; i -= 1) {
1748
- const node = /** @type {any} */ (path[i]);
1749
- if (is_function_or_class_boundary(node)) {
1750
- return node.metadata?.native_tsrx ? node : null;
1672
+ function collect_function_scope_bindings(node) {
1673
+ const bindings = collect_param_bindings(node.params || []);
1674
+ if (node.body?.type === 'BlockStatement') {
1675
+ for (const statement of node.body.body || []) {
1676
+ if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
1677
+ for (const child of get_tsrx_render_children(statement.argument)) {
1678
+ collect_statement_bindings(child, bindings);
1679
+ }
1680
+ } else {
1681
+ collect_statement_bindings(statement, bindings);
1682
+ }
1751
1683
  }
1752
1684
  }
1753
- return null;
1685
+ return bindings;
1754
1686
  }
1755
1687
 
1756
1688
  /**
1757
- * @param {any} node
1758
- * @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
1759
- * @returns {any}
1689
+ * @param {Map<string, AST.Identifier>} outer
1690
+ * @param {Map<string, AST.Identifier>} inner
1691
+ * @returns {Map<string, AST.Identifier>}
1760
1692
  */
1761
- function transform_function(node, context) {
1762
- // Lower a `@{ … }` function body (JSXCodeBlock) to an ordinary block: the
1763
- // setup statements followed by `return <render>` when the block produces a
1764
- // render output. The parser already marks the render JSX as native_tsrx, so
1765
- // from here it flows through the existing native-component machinery exactly
1766
- // like the older fenced `{ return <> … </> }` shape.
1767
- const has_jsx_code_block_body = node.body?.type === 'JSXCodeBlock';
1768
- lower_jsx_code_block_function_body(node);
1769
-
1770
- if (
1771
- has_jsx_code_block_body ||
1772
- node.metadata?.native_tsrx_function ||
1773
- function_has_native_tsrx_return(node)
1774
- ) {
1775
- return transform_native_tsrx_function(node, context, {
1776
- nativeBody: has_jsx_code_block_body || !!node.metadata?.native_tsrx_function,
1777
- });
1693
+ function merge_binding_maps(outer, inner) {
1694
+ const merged = new Map(outer);
1695
+ for (const [name, binding] of inner) {
1696
+ merged.set(name, binding);
1778
1697
  }
1779
-
1780
- return transform_function_with_hook_helpers(node, context);
1698
+ return merged;
1781
1699
  }
1782
1700
 
1783
1701
  /**
1784
1702
  * @param {any} node
1785
- * @returns {void}
1703
+ * @returns {boolean}
1786
1704
  */
1787
- function lower_jsx_code_block_function_body(node) {
1788
- if (node.body?.type !== 'JSXCodeBlock') return;
1705
+ function function_has_native_tsrx_return(node) {
1706
+ if (!node) return false;
1789
1707
 
1790
- const code_block = node.body;
1791
- const statements = [...code_block.body];
1792
- if (code_block.render != null) {
1793
- let render = code_block.render;
1794
- if (!is_native_tsrx_node(render)) {
1795
- // A control-flow output (@if/@for/@switch/@try) isn't itself a native
1796
- // template node, so `return @if (…) { … }` wouldn't be recognized as a
1797
- // component render output. Wrap it in a native fragment so it flows
1798
- // through the same children-rendering path as a `<> … </>` render.
1799
- const fragment = b.jsx_fragment([render]);
1800
- fragment.metadata = { ...fragment.metadata, native_tsrx: true };
1801
- render = fragment;
1802
- }
1803
- statements.push(b.return(render, code_block.render));
1708
+ if (node.body?.type === 'JSXCodeBlock') {
1709
+ return true;
1804
1710
  }
1805
- node.body = b.block(statements, code_block);
1806
- if (node.type === 'ArrowFunctionExpression') {
1807
- node.expression = false;
1711
+
1712
+ if (node.type === 'ArrowFunctionExpression' && node.body?.type !== 'BlockStatement') {
1713
+ return node_contains_native_tsrx_template(node.body);
1808
1714
  }
1715
+
1716
+ const body = node.body?.type === 'BlockStatement' ? node.body.body : [];
1717
+ return statements_contain_native_tsrx_return(body);
1809
1718
  }
1810
1719
 
1811
1720
  /**
1812
- * @param {any} node
1813
- * @param {{ next: () => any, state: TransformContext }} context
1814
- * @param {{ nativeBody?: boolean }} [options]
1815
- * @returns {any}
1721
+ * @param {any[]} statements
1722
+ * @returns {boolean}
1816
1723
  */
1817
- function transform_native_tsrx_function(node, { next, state }, { nativeBody = false } = {}) {
1818
- const helper_state =
1819
- state.helper_state || create_helper_state(get_function_helper_base_name(node));
1820
- const saved_helper_state = state.helper_state;
1821
- const saved_bindings = state.available_bindings;
1822
- const saved_hook_helpers_enabled = state.hook_helpers_enabled;
1724
+ function statements_contain_native_tsrx_return(statements) {
1725
+ return statements.some((statement) => statement_contains_native_tsrx_return(statement));
1726
+ }
1823
1727
 
1824
- state.helper_state = helper_state;
1825
- state.hook_helpers_enabled = is_uppercase_function_like(node);
1826
- node.metadata = {
1827
- ...(node.metadata || {}),
1828
- native_tsrx: true,
1829
- ...(nativeBody ? { native_tsrx_body: true } : {}),
1830
- ...(nativeBody && needs_hook_split(node, state) ? { hook_split: true } : {}),
1831
- };
1832
- state.available_bindings = merge_binding_maps(
1833
- saved_bindings,
1834
- collect_function_scope_bindings(node),
1835
- );
1728
+ /**
1729
+ * @param {any} statement
1730
+ * @returns {boolean}
1731
+ */
1732
+ function statement_contains_native_tsrx_return(statement) {
1733
+ if (!statement || typeof statement !== 'object') return false;
1836
1734
 
1837
- validate_native_await(node, state);
1735
+ if (statement.type === 'ReturnStatement') {
1736
+ return node_contains_native_tsrx_template(statement.argument);
1737
+ }
1838
1738
 
1839
- const inner = /** @type {any} */ (next() ?? node);
1840
- if (
1841
- inner !== node &&
1842
- node.type === 'ArrowFunctionExpression' &&
1843
- is_native_tsrx_node(node.body) &&
1844
- inner.body?.type === 'BlockStatement'
1845
- ) {
1846
- inner.expression = false;
1739
+ if (is_function_or_class_boundary(statement)) {
1740
+ return false;
1847
1741
  }
1848
1742
 
1849
- state.helper_state = saved_helper_state;
1850
- state.available_bindings = saved_bindings;
1851
- state.hook_helpers_enabled = saved_hook_helpers_enabled;
1852
-
1853
- inner.metadata = {
1854
- ...strip_function_transform_metadata(inner.metadata),
1855
- native_tsrx_function: true,
1856
- ...(nativeBody ? { native_tsrx_body: true } : {}),
1857
- ...(!saved_helper_state ? create_generated_helper_metadata(helper_state) || {} : {}),
1858
- };
1859
-
1860
- return inner;
1861
- }
1862
-
1863
- /**
1864
- * @param {any} node
1865
- * @param {TransformContext} transform_context
1866
- * @returns {void}
1867
- */
1868
- function validate_native_await(node, transform_context) {
1869
- const await_node = find_native_await(node);
1870
- if (!await_node) {
1871
- return;
1743
+ if (statement.type === 'BlockStatement') {
1744
+ return statements_contain_native_tsrx_return(statement.body || []);
1872
1745
  }
1873
1746
 
1874
- const validator = transform_context.platform.hooks?.validateComponentAwait;
1875
- if (validator) {
1876
- validator(await_node, node, transform_context, false, transform_context.source || '');
1877
- return;
1747
+ if (is_if_control_node(statement)) {
1748
+ return (
1749
+ statement_contains_native_tsrx_return(statement.consequent) ||
1750
+ statement_contains_native_tsrx_return(statement.alternate)
1751
+ );
1878
1752
  }
1879
1753
 
1880
- if (transform_context.platform.validation.requireUseServerForAwait) {
1881
- error(
1882
- 'Top-level `await` in TSRX functions requires a module-level `"use server"` directive.',
1883
- transform_context.filename,
1884
- await_node,
1885
- transform_context.errors,
1886
- transform_context.comments,
1754
+ if (is_switch_control_node(statement)) {
1755
+ return (statement.cases || []).some((/** @type {any} */ c) =>
1756
+ statements_contain_native_tsrx_return(c.consequent || []),
1887
1757
  );
1888
1758
  }
1889
- }
1890
1759
 
1891
- /**
1892
- * @param {any} node
1893
- * @returns {any | null}
1894
- */
1895
- function find_native_await(node) {
1896
- if (
1897
- node.type === 'ArrowFunctionExpression' &&
1898
- node.body?.type !== 'BlockStatement' &&
1899
- node_contains_native_tsrx_template(node.body)
1900
- ) {
1901
- return find_first_top_level_await(node.body, false);
1760
+ if (is_try_control_node(statement)) {
1761
+ return (
1762
+ statement_contains_native_tsrx_return(statement.block) ||
1763
+ statement_contains_native_tsrx_return(statement.pending) ||
1764
+ statement_contains_native_tsrx_return(statement.handler?.body) ||
1765
+ statement_contains_native_tsrx_return(statement.finalizer)
1766
+ );
1902
1767
  }
1903
1768
 
1904
- if (node.body?.type === 'JSXCodeBlock') {
1905
- return find_native_await_in_list(get_raw_jsx_code_block_body_nodes(node.body));
1769
+ for (const key of Object.keys(statement)) {
1770
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1771
+ continue;
1772
+ }
1773
+ const value = statement[key];
1774
+ if (Array.isArray(value)) {
1775
+ if (statements_contain_native_tsrx_return(value)) return true;
1776
+ } else if (statement_contains_native_tsrx_return(value)) {
1777
+ return true;
1778
+ }
1906
1779
  }
1907
1780
 
1908
- const body = node.body?.type === 'BlockStatement' ? node.body.body || [] : [];
1909
- return find_native_await_in_list(body);
1910
- }
1911
-
1912
- /**
1913
- * @param {any[]} statements
1914
- * @returns {any | null}
1915
- */
1916
- function find_native_await_in_list(statements) {
1917
- for (const statement of statements) {
1918
- const found = find_native_await_in_statement(statement);
1919
- if (found) return found;
1920
- }
1921
- return null;
1781
+ return false;
1922
1782
  }
1923
1783
 
1924
1784
  /**
1925
- * @param {any} statement
1926
- * @returns {any | null}
1785
+ * @param {any} node
1786
+ * @returns {boolean}
1927
1787
  */
1928
- function find_native_await_in_statement(statement) {
1929
- if (!statement || typeof statement !== 'object') return null;
1930
-
1931
- if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
1932
- return find_first_top_level_await_in_tsrx_function_body(statement.argument.children || []);
1933
- }
1934
-
1935
- if (
1936
- statement.type === 'ReturnStatement' &&
1937
- node_contains_native_tsrx_template(statement.argument)
1938
- ) {
1939
- return find_first_top_level_await(statement.argument, false);
1940
- }
1941
-
1942
- if (is_function_or_class_boundary(statement)) {
1943
- return null;
1944
- }
1788
+ function node_contains_native_tsrx_template(node) {
1789
+ if (!node || typeof node !== 'object') return false;
1790
+ if (is_native_tsrx_node(node)) return true;
1945
1791
 
1946
- if (statement.type === 'BlockStatement') {
1947
- return find_native_await_in_list(statement.body || []);
1792
+ if (is_function_or_class_boundary(node)) {
1793
+ return false;
1948
1794
  }
1949
1795
 
1950
- if (is_if_control_node(statement)) {
1951
- return (
1952
- find_native_await_in_statement(statement.consequent) ||
1953
- find_native_await_in_statement(statement.alternate)
1954
- );
1796
+ if (Array.isArray(node)) {
1797
+ return node.some(node_contains_native_tsrx_template);
1955
1798
  }
1956
1799
 
1957
- if (is_switch_control_node(statement)) {
1958
- for (const switch_case of statement.cases || []) {
1959
- const found = find_native_await_in_list(switch_case.consequent || []);
1960
- if (found) return found;
1800
+ for (const key of Object.keys(node)) {
1801
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1802
+ continue;
1803
+ }
1804
+ if (node_contains_native_tsrx_template(node[key])) {
1805
+ return true;
1961
1806
  }
1962
- return null;
1963
- }
1964
-
1965
- if (is_try_control_node(statement)) {
1966
- return (
1967
- find_native_await_in_statement(statement.block) ||
1968
- find_native_await_in_statement(statement.handler?.body) ||
1969
- find_native_await_in_statement(statement.finalizer)
1970
- );
1971
1807
  }
1972
1808
 
1973
- return find_first_top_level_await(statement, false);
1809
+ return false;
1974
1810
  }
1975
1811
 
1976
1812
  /**
1977
1813
  * @param {any} node
1978
- * @param {{ next: () => any, state: TransformContext }} context
1979
1814
  * @returns {any}
1980
1815
  */
1981
- function transform_function_with_hook_helpers(node, { next, state }) {
1982
- const has_hook_bearing_tsrx = function_contains_hook_bearing_tsrx(node, state);
1983
- if (state.helper_state || !is_uppercase_function_like(node) || !has_hook_bearing_tsrx) {
1984
- return next() ?? node;
1985
- }
1986
-
1987
- const helper_state = create_helper_state(get_function_helper_base_name(node));
1988
- const saved_helper_state = state.helper_state;
1989
- const saved_bindings = state.available_bindings;
1990
- const saved_hook_helpers_enabled = state.hook_helpers_enabled;
1991
-
1992
- state.helper_state = helper_state;
1993
- state.hook_helpers_enabled = true;
1994
- state.available_bindings = collect_function_scope_bindings(node);
1995
-
1996
- const inner = /** @type {any} */ (next() ?? node);
1997
-
1998
- state.helper_state = saved_helper_state;
1999
- state.available_bindings = saved_bindings;
2000
- state.hook_helpers_enabled = saved_hook_helpers_enabled;
1816
+ function collect_tsrx_stylesheet(node) {
1817
+ /** @type {any[]} */
1818
+ const styles = [];
1819
+ collect_style_elements(node.children || [], styles);
2001
1820
 
2002
- inner.metadata = {
2003
- ...strip_function_transform_metadata(inner.metadata),
2004
- ...(create_generated_helper_metadata(helper_state) || {}),
2005
- };
1821
+ if (styles.length === 0) return null;
1822
+ if (styles.length > 1) {
1823
+ throw new Error('TSRX fragments can only have one style tag');
1824
+ }
2006
1825
 
2007
- return inner;
1826
+ return styles[0];
2008
1827
  }
2009
1828
 
2010
1829
  /**
2011
1830
  * @param {any} node
2012
- * @returns {string}
1831
+ * @param {TransformContext} transform_context
1832
+ * @returns {{ css: any, style_refs: any[], fragment: any } | null}
2013
1833
  */
2014
- function get_function_helper_base_name(node) {
2015
- return get_function_like_name(node) || 'TSRXTemplate';
1834
+ function prepare_tsrx_fragment_styles(node, transform_context) {
1835
+ const css = collect_tsrx_stylesheet(node);
1836
+ if (!css) return null;
1837
+
1838
+ const style_refs = collect_style_ref_attributes(node);
1839
+ apply_css_definition_metadata(node, css, transform_context, style_refs.length > 0);
1840
+ transform_context.stylesheets.push(css);
1841
+ const fragment = annotate_tsrx_with_hash(
1842
+ node,
1843
+ css.hash,
1844
+ transform_context.platform.jsx.classAttrName ??
1845
+ (transform_context.platform.jsx.rewriteClassAttr ? 'className' : 'class'),
1846
+ transform_context.typeOnly,
1847
+ );
1848
+ return { css, style_refs, fragment };
2016
1849
  }
2017
1850
 
2018
1851
  /**
1852
+ * @template T
2019
1853
  * @param {any} node
2020
- * @returns {boolean}
1854
+ * @param {TransformContext} transform_context
1855
+ * @param {(style_context: { css: any, style_refs: any[], fragment: any } | null) => T} callback
1856
+ * @returns {T}
2021
1857
  */
2022
- function is_uppercase_function_like(node) {
2023
- const name = get_function_like_name(node);
2024
- return !!(name && /^[A-Z]/.test(name));
1858
+ function with_tsrx_fragment_styles(node, transform_context, callback) {
1859
+ const style_context = prepare_tsrx_fragment_styles(node, transform_context);
1860
+ return callback(style_context);
2025
1861
  }
2026
1862
 
2027
1863
  /**
2028
- * @param {any} node
2029
- * @returns {string | null}
1864
+ * @param {any} fragment
1865
+ * @param {{ css: any, style_refs: any[], fragment: any } | null} style_context
1866
+ * @param {TransformContext} transform_context
1867
+ * @returns {AST.Statement[]}
2030
1868
  */
2031
- function get_function_like_name(node) {
2032
- if (node.id?.type === 'Identifier') {
2033
- return node.id.name;
2034
- }
2035
-
2036
- const parent = /** @type {any} */ (node.metadata?.path?.at(-1));
2037
- if (!parent) return null;
2038
-
2039
- if (parent.type === 'VariableDeclarator' && parent.init === node) {
2040
- return get_static_binding_name(parent.id);
2041
- }
2042
-
2043
- if (parent.type === 'Property' && parent.value === node) {
2044
- return get_static_property_name(parent.key);
2045
- }
2046
-
2047
- if (parent.type === 'MethodDefinition' && parent.value === node) {
2048
- return get_static_property_name(parent.key);
2049
- }
2050
-
2051
- if (parent.type === 'AssignmentExpression' && parent.right === node) {
2052
- return get_static_binding_name(parent.left);
1869
+ function create_tsrx_style_ref_setup_statements(fragment, style_context, transform_context) {
1870
+ if (!style_context || style_context.style_refs.length === 0) {
1871
+ return [];
2053
1872
  }
2054
1873
 
2055
- return null;
1874
+ return create_style_ref_setup_statements(
1875
+ style_context.style_refs,
1876
+ create_style_class_map(fragment, style_context.css),
1877
+ {
1878
+ allowMutableRefTarget: transform_context.platform.jsx.multiRefStrategy === 'array',
1879
+ createTempIdentifier: () =>
1880
+ create_generated_identifier(create_style_ref_temp_name(transform_context)),
1881
+ },
1882
+ );
2056
1883
  }
2057
1884
 
2058
1885
  /**
2059
1886
  * @param {any} node
2060
- * @returns {string | null}
2061
- */
2062
- function get_static_binding_name(node) {
2063
- if (node?.type === 'Identifier') {
2064
- return node.name;
2065
- }
2066
- if (node?.type === 'MemberExpression' && !node.computed) {
2067
- return get_static_property_name(node.property);
2068
- }
2069
- return null;
2070
- }
2071
-
2072
- /**
2073
- * @param {any} key
2074
- * @returns {string | null}
2075
- */
2076
- function get_static_property_name(key) {
2077
- if (key?.type === 'Identifier') {
2078
- return key.name;
2079
- }
2080
- if (key?.type === 'Literal' && typeof key.value === 'string') {
2081
- return key.value;
2082
- }
2083
- return null;
2084
- }
2085
-
2086
- /**
2087
- * @param {any} node
2088
- * @returns {Map<string, AST.Identifier>}
2089
- */
2090
- function collect_function_scope_bindings(node) {
2091
- const bindings = collect_param_bindings(node.params || []);
2092
- if (node.body?.type === 'BlockStatement') {
2093
- for (const statement of node.body.body || []) {
2094
- if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
2095
- for (const child of get_tsrx_render_children(statement.argument)) {
2096
- collect_statement_bindings(child, bindings);
2097
- }
2098
- } else {
2099
- collect_statement_bindings(statement, bindings);
2100
- }
2101
- }
2102
- }
2103
- return bindings;
2104
- }
2105
-
2106
- /**
2107
- * @param {Map<string, AST.Identifier>} outer
2108
- * @param {Map<string, AST.Identifier>} inner
2109
- * @returns {Map<string, AST.Identifier>}
2110
- */
2111
- function merge_binding_maps(outer, inner) {
2112
- const merged = new Map(outer);
2113
- for (const [name, binding] of inner) {
2114
- merged.set(name, binding);
2115
- }
2116
- return merged;
2117
- }
2118
-
2119
- /**
2120
- * @param {any} node
2121
- * @returns {boolean}
2122
- */
2123
- function function_has_native_tsrx_return(node) {
2124
- if (!node) return false;
2125
-
2126
- if (node.body?.type === 'JSXCodeBlock') {
2127
- return true;
2128
- }
2129
-
2130
- if (node.type === 'ArrowFunctionExpression' && node.body?.type !== 'BlockStatement') {
2131
- return node_contains_native_tsrx_template(node.body);
2132
- }
2133
-
2134
- const body = node.body?.type === 'BlockStatement' ? node.body.body : [];
2135
- return statements_contain_native_tsrx_return(body);
2136
- }
2137
-
2138
- /**
2139
- * @param {any[]} statements
2140
- * @returns {boolean}
2141
- */
2142
- function statements_contain_native_tsrx_return(statements) {
2143
- return statements.some((statement) => statement_contains_native_tsrx_return(statement));
2144
- }
2145
-
2146
- /**
2147
- * @param {any} statement
2148
- * @returns {boolean}
2149
- */
2150
- function statement_contains_native_tsrx_return(statement) {
2151
- if (!statement || typeof statement !== 'object') return false;
2152
-
2153
- if (statement.type === 'ReturnStatement') {
2154
- return node_contains_native_tsrx_template(statement.argument);
2155
- }
2156
-
2157
- if (is_function_or_class_boundary(statement)) {
2158
- return false;
2159
- }
2160
-
2161
- if (statement.type === 'BlockStatement') {
2162
- return statements_contain_native_tsrx_return(statement.body || []);
2163
- }
2164
-
2165
- if (is_if_control_node(statement)) {
2166
- return (
2167
- statement_contains_native_tsrx_return(statement.consequent) ||
2168
- statement_contains_native_tsrx_return(statement.alternate)
2169
- );
2170
- }
2171
-
2172
- if (is_switch_control_node(statement)) {
2173
- return (statement.cases || []).some((/** @type {any} */ c) =>
2174
- statements_contain_native_tsrx_return(c.consequent || []),
2175
- );
2176
- }
2177
-
2178
- if (is_try_control_node(statement)) {
2179
- return (
2180
- statement_contains_native_tsrx_return(statement.block) ||
2181
- statement_contains_native_tsrx_return(statement.pending) ||
2182
- statement_contains_native_tsrx_return(statement.handler?.body) ||
2183
- statement_contains_native_tsrx_return(statement.finalizer)
2184
- );
2185
- }
2186
-
2187
- for (const key of Object.keys(statement)) {
2188
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2189
- continue;
2190
- }
2191
- const value = statement[key];
2192
- if (Array.isArray(value)) {
2193
- if (statements_contain_native_tsrx_return(value)) return true;
2194
- } else if (statement_contains_native_tsrx_return(value)) {
2195
- return true;
2196
- }
2197
- }
2198
-
2199
- return false;
2200
- }
2201
-
2202
- /**
2203
- * @param {any} node
2204
- * @returns {boolean}
2205
- */
2206
- function node_contains_native_tsrx_template(node) {
2207
- if (!node || typeof node !== 'object') return false;
2208
- if (is_native_tsrx_node(node)) return true;
2209
-
2210
- if (is_function_or_class_boundary(node)) {
2211
- return false;
2212
- }
2213
-
2214
- if (Array.isArray(node)) {
2215
- return node.some(node_contains_native_tsrx_template);
2216
- }
2217
-
2218
- for (const key of Object.keys(node)) {
2219
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2220
- continue;
2221
- }
2222
- if (node_contains_native_tsrx_template(node[key])) {
2223
- return true;
2224
- }
2225
- }
2226
-
2227
- return false;
2228
- }
2229
-
2230
- /**
2231
- * @param {any} node
2232
- * @returns {any}
2233
- */
2234
- function collect_tsrx_stylesheet(node) {
2235
- /** @type {any[]} */
2236
- const styles = [];
2237
- collect_style_elements(node.children || [], styles);
2238
-
2239
- if (styles.length === 0) return null;
2240
- if (styles.length > 1) {
2241
- throw new Error('TSRX fragments can only have one style tag');
2242
- }
2243
-
2244
- return styles[0];
2245
- }
2246
-
2247
- /**
2248
- * @param {any} node
2249
- * @param {TransformContext} transform_context
2250
- * @returns {{ css: any, style_refs: any[], fragment: any } | null}
2251
- */
2252
- function prepare_tsrx_fragment_styles(node, transform_context) {
2253
- const css = collect_tsrx_stylesheet(node);
2254
- if (!css) return null;
2255
-
2256
- const style_refs = collect_style_ref_attributes(node);
2257
- apply_css_definition_metadata(node, css, transform_context, style_refs.length > 0);
2258
- transform_context.stylesheets.push(css);
2259
- const fragment = annotate_tsrx_with_hash(
2260
- node,
2261
- css.hash,
2262
- transform_context.platform.jsx.classAttrName ??
2263
- (transform_context.platform.jsx.rewriteClassAttr ? 'className' : 'class'),
2264
- transform_context.typeOnly,
2265
- );
2266
- return { css, style_refs, fragment };
2267
- }
2268
-
2269
- /**
2270
- * @template T
2271
- * @param {any} node
2272
- * @param {TransformContext} transform_context
2273
- * @param {(style_context: { css: any, style_refs: any[], fragment: any } | null) => T} callback
2274
- * @returns {T}
2275
- */
2276
- function with_tsrx_fragment_styles(node, transform_context, callback) {
2277
- const style_context = prepare_tsrx_fragment_styles(node, transform_context);
2278
- return callback(style_context);
2279
- }
2280
-
2281
- /**
2282
- * @param {any} fragment
2283
- * @param {{ css: any, style_refs: any[], fragment: any } | null} style_context
2284
- * @param {TransformContext} transform_context
2285
- * @returns {AST.Statement[]}
2286
- */
2287
- function create_tsrx_style_ref_setup_statements(fragment, style_context, transform_context) {
2288
- if (!style_context || style_context.style_refs.length === 0) {
2289
- return [];
2290
- }
2291
-
2292
- return create_style_ref_setup_statements(
2293
- style_context.style_refs,
2294
- create_style_class_map(fragment, style_context.css),
2295
- {
2296
- allowMutableRefTarget: transform_context.platform.jsx.multiRefStrategy === 'array',
2297
- createTempIdentifier: () =>
2298
- create_generated_identifier(create_style_ref_temp_name(transform_context)),
2299
- },
2300
- );
2301
- }
2302
-
2303
- /**
2304
- * @param {any} node
2305
- * @param {any} stylesheet
2306
- * @param {TransformContext} transform_context
2307
- * @returns {AST.Expression}
1887
+ * @param {any} stylesheet
1888
+ * @param {TransformContext} transform_context
1889
+ * @returns {AST.Expression}
2308
1890
  */
2309
1891
  function create_style_expression_value(node, stylesheet, transform_context) {
2310
1892
  const class_map = create_style_class_map_from_stylesheet(stylesheet);
@@ -2844,7 +2426,10 @@ function should_use_module_scoped_hook_components(transform_context) {
2844
2426
  * @returns {boolean}
2845
2427
  */
2846
2428
  function should_extract_hook_helpers(transform_context) {
2847
- return !!transform_context.hook_helpers_enabled;
2429
+ return !!(
2430
+ transform_context.hook_helpers_enabled &&
2431
+ transform_context.platform.hooks?.moduleScopedHookComponents
2432
+ );
2848
2433
  }
2849
2434
 
2850
2435
  /**
@@ -3406,11 +2991,6 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
3406
2991
  for (const param of loop_params) {
3407
2992
  collect_pattern_bindings(param, transform_context.available_bindings);
3408
2993
  }
3409
- validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
3410
- original_loop_body,
3411
- transform_context,
3412
- loop_scoped_names,
3413
- );
3414
2994
 
3415
2995
  const all_helper_bindings = get_referenced_helper_bindings(
3416
2996
  original_loop_body,
@@ -3695,659 +3275,54 @@ function to_jsx_element(
3695
3275
  }
3696
3276
 
3697
3277
  /**
3698
- * @param {any[]} children
3699
- * @param {TransformContext} transform_context
3700
- * @returns {any[]}
3701
- */
3702
-
3703
- function create_element_children(children, transform_context) {
3704
- if (children.length === 0) {
3705
- return [];
3706
- }
3707
-
3708
- if (children.every(is_inline_element_child) && !children_contain_return_semantics(children)) {
3709
- const saved_inside_element_child = transform_context.inside_element_child;
3710
- transform_context.inside_element_child = true;
3711
- try {
3712
- return children.map((/** @type {any} */ child) => to_jsx_child(child, transform_context));
3713
- } finally {
3714
- transform_context.inside_element_child = saved_inside_element_child;
3715
- }
3716
- }
3717
-
3718
- const saved_inside_element_child = transform_context.inside_element_child;
3719
- transform_context.inside_element_child = true;
3720
- try {
3721
- return [statement_body_to_jsx_child(children, transform_context)];
3722
- } finally {
3723
- transform_context.inside_element_child = saved_inside_element_child;
3724
- }
3725
- }
3726
-
3727
- /**
3728
- * @param {any[]} children
3729
- * @returns {boolean}
3730
- */
3731
- function children_contain_return_semantics(children) {
3732
- return children.some(child_contains_return_semantics);
3733
- }
3734
-
3735
- /**
3736
- * @param {any} node
3737
- * @returns {boolean}
3738
- */
3739
- function child_contains_return_semantics(node) {
3740
- if (!node || typeof node !== 'object') {
3741
- return false;
3742
- }
3743
-
3744
- if (node.type === 'ReturnStatement') {
3745
- return true;
3746
- }
3747
-
3748
- if (
3749
- node.type === 'FunctionDeclaration' ||
3750
- node.type === 'FunctionExpression' ||
3751
- node.type === 'ArrowFunctionExpression'
3752
- ) {
3753
- return false;
3754
- }
3755
-
3756
- if (Array.isArray(node)) {
3757
- return node.some(child_contains_return_semantics);
3758
- }
3759
-
3760
- for (const key of Object.keys(node)) {
3761
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3762
- continue;
3763
- }
3764
- if (child_contains_return_semantics(node[key])) {
3765
- return true;
3766
- }
3767
- }
3768
-
3769
- return false;
3770
- }
3771
-
3772
- /**
3773
- * @param {any} node
3774
- * @returns {boolean}
3775
- */
3776
- function is_inline_element_child(node) {
3777
- return node && is_render_child_node(node);
3778
- }
3779
-
3780
- /**
3781
- * @param {any[]} body_nodes
3782
- * @param {TransformContext} transform_context
3783
- * @returns {ESTreeJSX.JSXExpressionContainer}
3784
- */
3785
- function statement_body_to_jsx_child(body_nodes, transform_context) {
3786
- if (
3787
- should_extract_hook_helpers(transform_context) &&
3788
- body_contains_top_level_hook_call(body_nodes, transform_context, true)
3789
- ) {
3790
- return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
3791
- }
3792
-
3793
- return to_jsx_expression_container(
3794
- b.call(b.arrow([], b.block(build_render_statements(body_nodes, true, transform_context)))),
3795
- );
3796
- }
3797
-
3798
- /**
3799
- * @param {any[]} body_nodes
3800
- * @param {TransformContext} transform_context
3801
- * @returns {ESTreeJSX.JSXExpressionContainer}
3802
- */
3803
- function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
3804
- const source_node = get_body_source_node(body_nodes);
3805
- const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
3806
-
3807
- return to_jsx_expression_container(
3808
- create_hook_safe_helper_iife(helper.setup_statements, helper.component_element),
3809
- source_node,
3810
- );
3811
- }
3812
-
3813
- /**
3814
- * @param {TransformContext} transform_context
3815
- * @returns {string}
3816
- */
3817
- function create_local_statement_component_name(transform_context) {
3818
- transform_context.local_statement_component_index += 1;
3819
- return `StatementBodyHook${transform_context.local_statement_component_index}`;
3820
- }
3821
-
3822
- /**
3823
- * Wraps a list of body nodes into a component and returns
3824
- * statements that return `<ComponentName prop1={prop1} ... />`.
3825
- * Targets can either emit the helper component at module scope or cache the
3826
- * component identity in module state while initializing it from the parent.
3827
- * Used when a control flow branch contains hook calls that must be moved
3828
- * into their own component boundary to satisfy the Rules of Hooks.
3829
- *
3830
- * @param {any[]} body_nodes
3831
- * @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
3832
- * @param {TransformContext} transform_context
3833
- * @returns {any[]}
3834
- */
3835
- function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
3836
- const source_node = get_body_source_node(body_nodes);
3837
- const helper = create_hook_safe_helper(
3838
- body_nodes,
3839
- key_expression,
3840
- source_node,
3841
- transform_context,
3842
- );
3843
- const statements = [...helper.setup_statements];
3844
-
3845
- statements.push(b.return(helper.component_element));
3846
-
3847
- return statements;
3848
- }
3849
-
3850
- /**
3851
- * @param {any[]} body_nodes
3852
- * @param {Map<string, AST.Identifier>} available_bindings
3853
- * @returns {AST.Identifier[]}
3854
- */
3855
- function get_referenced_helper_bindings(body_nodes, available_bindings) {
3856
- const helper_bindings = [];
3857
- const local_bindings = new Map();
3858
-
3859
- for (const node of body_nodes) {
3860
- collect_statement_bindings(node, local_bindings);
3861
- }
3862
-
3863
- for (const [name, binding] of available_bindings) {
3864
- if (local_bindings.has(name)) continue;
3865
-
3866
- if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
3867
- helper_bindings.push(binding);
3868
- }
3869
- }
3870
-
3871
- return helper_bindings;
3872
- }
3873
-
3874
- /**
3875
- * @param {any[]} body_nodes
3876
- * @param {TransformContext} transform_context
3877
- * @param {Set<string>} [local_binding_names]
3878
- * @returns {void}
3879
- */
3880
- function validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
3881
- body_nodes,
3882
- transform_context,
3883
- local_binding_names,
3884
- ) {
3885
- if (!is_react_like_hook_platform(transform_context)) {
3886
- return;
3887
- }
3888
- if (!body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
3889
- return;
3890
- }
3891
- if (!transform_context.available_bindings || transform_context.available_bindings.size === 0) {
3892
- return;
3893
- }
3894
-
3895
- const shadowed_names = collect_block_binding_names(body_nodes);
3896
- for (const name of local_binding_names || []) {
3897
- shadowed_names.add(name);
3898
- }
3899
- validate_hook_outer_assignments_in_node(body_nodes, shadowed_names, transform_context, new Set());
3900
- }
3901
-
3902
- /**
3903
- * @param {TransformContext} transform_context
3904
- * @returns {boolean}
3905
- */
3906
- function is_react_like_hook_platform(transform_context) {
3907
- return (
3908
- transform_context.platform.name === 'React' || transform_context.platform.name === 'Preact'
3909
- );
3910
- }
3911
-
3912
- /**
3913
- * @param {any[]} statements
3914
- * @returns {Set<string>}
3915
- */
3916
- function collect_block_binding_names(statements) {
3917
- const names = new Set();
3918
- for (const statement of statements || []) {
3919
- collect_block_binding_names_from_statement(statement, names);
3920
- }
3921
- return names;
3922
- }
3923
-
3924
- /**
3925
- * @param {any} statement
3926
- * @param {Set<string>} names
3927
- * @returns {void}
3928
- */
3929
- function collect_block_binding_names_from_statement(statement, names) {
3930
- if (!statement || typeof statement !== 'object') {
3931
- return;
3932
- }
3933
-
3934
- if (statement.type === 'VariableDeclaration') {
3935
- for (const declaration of statement.declarations || []) {
3936
- collect_pattern_names(declaration.id, names);
3937
- }
3938
- return;
3939
- }
3940
-
3941
- if (
3942
- (statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
3943
- statement.id?.type === 'Identifier'
3944
- ) {
3945
- names.add(statement.id.name);
3946
- return;
3947
- }
3948
-
3949
- if (
3950
- statement.type === 'ForOfStatement' ||
3951
- statement.type === 'ForInStatement' ||
3952
- (statement.type === 'JSXForExpression' &&
3953
- (statement.statementType === 'ForOfStatement' ||
3954
- statement.statementType === 'ForInStatement'))
3955
- ) {
3956
- if (statement.left?.type === 'VariableDeclaration' && statement.left.kind === 'var') {
3957
- for (const declaration of statement.left.declarations || []) {
3958
- collect_pattern_names(declaration.id, names);
3959
- }
3960
- }
3961
- return;
3962
- }
3963
-
3964
- if (
3965
- statement.type === 'ForStatement' &&
3966
- statement.init?.type === 'VariableDeclaration' &&
3967
- statement.init.kind === 'var'
3968
- ) {
3969
- for (const declaration of statement.init.declarations || []) {
3970
- collect_pattern_names(declaration.id, names);
3971
- }
3972
- }
3973
- }
3974
-
3975
- /**
3976
- * @param {any} pattern
3977
- * @param {Set<string>} names
3978
- * @returns {void}
3979
- */
3980
- function collect_pattern_names(pattern, names) {
3981
- if (!pattern || typeof pattern !== 'object') {
3982
- return;
3983
- }
3984
-
3985
- if (pattern.type === 'Identifier') {
3986
- names.add(pattern.name);
3987
- return;
3988
- }
3989
-
3990
- if (pattern.type === 'RestElement') {
3991
- collect_pattern_names(pattern.argument, names);
3992
- return;
3993
- }
3994
-
3995
- if (pattern.type === 'AssignmentPattern') {
3996
- collect_pattern_names(pattern.left, names);
3997
- return;
3998
- }
3999
-
4000
- if (pattern.type === 'ArrayPattern') {
4001
- for (const element of pattern.elements || []) {
4002
- collect_pattern_names(element, names);
4003
- }
4004
- return;
4005
- }
4006
-
4007
- if (pattern.type === 'ObjectPattern') {
4008
- for (const property of pattern.properties || []) {
4009
- if (property.type === 'RestElement') {
4010
- collect_pattern_names(property.argument, names);
4011
- } else {
4012
- collect_pattern_names(property.value, names);
4013
- }
4014
- }
4015
- }
4016
- }
4017
-
4018
- /**
4019
- * @param {any} node
4020
- * @param {Set<string>} shadowed_names
4021
- * @param {TransformContext} transform_context
4022
- * @param {Set<string>} hook_result_names
4023
- * @returns {void}
4024
- */
4025
- function validate_hook_outer_assignments_in_node(
4026
- node,
4027
- shadowed_names,
4028
- transform_context,
4029
- hook_result_names,
4030
- ) {
4031
- if (!node || typeof node !== 'object') {
4032
- return;
4033
- }
4034
-
4035
- if (Array.isArray(node)) {
4036
- for (const child of node) {
4037
- validate_hook_outer_assignments_in_node(
4038
- child,
4039
- shadowed_names,
4040
- transform_context,
4041
- hook_result_names,
4042
- );
4043
- }
4044
- return;
4045
- }
4046
-
4047
- if (is_function_or_component_node(node)) {
4048
- return;
4049
- }
4050
-
4051
- if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
4052
- validate_hook_callback_outer_mutations(node, shadowed_names, transform_context);
4053
- }
4054
-
4055
- if (node.type === 'BlockStatement') {
4056
- const next_shadowed = new Set(shadowed_names);
4057
- const next_hook_result_names = new Set(hook_result_names);
4058
- for (const name of collect_block_binding_names(node.body || [])) {
4059
- next_shadowed.add(name);
4060
- }
4061
- for (const child of node.body || []) {
4062
- validate_hook_outer_assignments_in_node(
4063
- child,
4064
- next_shadowed,
4065
- transform_context,
4066
- next_hook_result_names,
4067
- );
4068
- }
4069
- return;
4070
- }
4071
-
4072
- if (node.type === 'VariableDeclaration') {
4073
- for (const declaration of node.declarations || []) {
4074
- if (
4075
- declaration.init &&
4076
- expression_contains_hook_derived_value(
4077
- declaration.init,
4078
- transform_context,
4079
- hook_result_names,
4080
- )
4081
- ) {
4082
- collect_pattern_names(declaration.id, hook_result_names);
4083
- }
4084
- validate_hook_outer_assignments_in_node(
4085
- declaration.init,
4086
- shadowed_names,
4087
- transform_context,
4088
- hook_result_names,
4089
- );
4090
- }
4091
- return;
4092
- }
4093
-
4094
- if (
4095
- node.type === 'AssignmentExpression' &&
4096
- expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
4097
- ) {
4098
- const outer_names = get_referenced_outer_binding_names(
4099
- node.left,
4100
- transform_context.available_bindings,
4101
- shadowed_names,
4102
- );
4103
- if (outer_names.length > 0) {
4104
- report_hook_outer_assignment_error(
4105
- node.left,
4106
- outer_names,
4107
- find_first_hook_call_name(node.right) || 'hook',
4108
- transform_context,
4109
- );
4110
- }
4111
- for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
4112
- hook_result_names.add(name);
4113
- }
4114
- }
4115
-
4116
- if (is_for_of_control_node(node)) {
4117
- if (
4118
- node.left &&
4119
- node.left.type !== 'VariableDeclaration' &&
4120
- expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
4121
- ) {
4122
- const outer_names = get_referenced_outer_binding_names(
4123
- node.left,
4124
- transform_context.available_bindings,
4125
- shadowed_names,
4126
- );
4127
- if (outer_names.length > 0) {
4128
- report_hook_outer_assignment_error(
4129
- node.left,
4130
- outer_names,
4131
- find_first_hook_call_name(node.right) || 'hook',
4132
- transform_context,
4133
- );
4134
- }
4135
- for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
4136
- hook_result_names.add(name);
4137
- }
4138
- }
4139
-
4140
- validate_hook_outer_assignments_in_node(
4141
- node.right,
4142
- shadowed_names,
4143
- transform_context,
4144
- hook_result_names,
4145
- );
4146
-
4147
- // Loop-declared bindings (`for (const x of …)`, `for (let x of …)`) live
4148
- // only in the body. They are deliberately NOT in the enclosing block's
4149
- // shadowed set (see collect_block_binding_names_from_statement), so add
4150
- // them just for the body recursion to keep references to the loop var
4151
- // from being flagged as outer-binding mutations.
4152
- const body_shadowed = new Set(shadowed_names);
4153
- if (node.left && node.left.type === 'VariableDeclaration') {
4154
- for (const declaration of node.left.declarations || []) {
4155
- collect_pattern_names(declaration.id, body_shadowed);
4156
- }
4157
- }
4158
- validate_hook_outer_assignments_in_node(
4159
- node.body,
4160
- body_shadowed,
4161
- transform_context,
4162
- hook_result_names,
4163
- );
4164
- return;
4165
- }
4166
-
4167
- for (const key of Object.keys(node)) {
4168
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4169
- continue;
4170
- }
4171
- validate_hook_outer_assignments_in_node(
4172
- node[key],
4173
- shadowed_names,
4174
- transform_context,
4175
- hook_result_names,
4176
- );
4177
- }
4178
- }
4179
-
4180
- /**
4181
- * @param {any} call_node
4182
- * @param {Set<string>} shadowed_names
4183
- * @param {TransformContext} transform_context
4184
- * @returns {void}
4185
- */
4186
- function validate_hook_callback_outer_mutations(call_node, shadowed_names, transform_context) {
4187
- const hook_name = get_hook_callee_name(call_node.callee);
4188
- for (const argument of call_node.arguments || []) {
4189
- if (!is_function_or_component_node(argument)) {
4190
- continue;
4191
- }
4192
- const callback_shadowed_names = create_function_like_shadowed_names(argument, shadowed_names);
4193
- validate_hook_callback_outer_mutations_in_node(
4194
- argument.body,
4195
- callback_shadowed_names,
4196
- transform_context,
4197
- hook_name,
4198
- );
4199
- }
4200
- }
4201
-
4202
- /**
4203
- * @param {any} node
4204
- * @param {Set<string>} shadowed_names
4205
- * @returns {Set<string>}
4206
- */
4207
- function create_function_like_shadowed_names(node, shadowed_names) {
4208
- const next_shadowed_names = new Set(shadowed_names);
4209
- for (const param of node.params || []) {
4210
- collect_pattern_names(param, next_shadowed_names);
4211
- }
4212
- if (node.body?.type === 'BlockStatement') {
4213
- for (const name of collect_block_binding_names(node.body.body || [])) {
4214
- next_shadowed_names.add(name);
4215
- }
4216
- }
4217
- return next_shadowed_names;
4218
- }
4219
-
4220
- /**
4221
- * @param {any} node
4222
- * @param {Set<string>} shadowed_names
3278
+ * @param {any[]} children
4223
3279
  * @param {TransformContext} transform_context
4224
- * @param {string} hook_name
4225
- * @returns {void}
3280
+ * @returns {any[]}
4226
3281
  */
4227
- function validate_hook_callback_outer_mutations_in_node(
4228
- node,
4229
- shadowed_names,
4230
- transform_context,
4231
- hook_name,
4232
- ) {
4233
- if (!node || typeof node !== 'object') {
4234
- return;
4235
- }
4236
-
4237
- if (Array.isArray(node)) {
4238
- for (const child of node) {
4239
- validate_hook_callback_outer_mutations_in_node(
4240
- child,
4241
- shadowed_names,
4242
- transform_context,
4243
- hook_name,
4244
- );
4245
- }
4246
- return;
4247
- }
4248
-
4249
- if (is_function_or_component_node(node)) {
4250
- validate_hook_callback_outer_mutations_in_node(
4251
- node.body,
4252
- create_function_like_shadowed_names(node, shadowed_names),
4253
- transform_context,
4254
- hook_name,
4255
- );
4256
- return;
4257
- }
4258
-
4259
- if (node.type === 'BlockStatement') {
4260
- const next_shadowed_names = new Set(shadowed_names);
4261
- for (const name of collect_block_binding_names(node.body || [])) {
4262
- next_shadowed_names.add(name);
4263
- }
4264
- for (const child of node.body || []) {
4265
- validate_hook_callback_outer_mutations_in_node(
4266
- child,
4267
- next_shadowed_names,
4268
- transform_context,
4269
- hook_name,
4270
- );
4271
- }
4272
- return;
4273
- }
4274
3282
 
4275
- if (node.type === 'AssignmentExpression') {
4276
- const outer_names = get_referenced_outer_binding_names(
4277
- node.left,
4278
- transform_context.available_bindings,
4279
- shadowed_names,
4280
- );
4281
- if (outer_names.length > 0) {
4282
- report_hook_callback_outer_mutation_error(
4283
- node.left,
4284
- outer_names,
4285
- hook_name,
4286
- transform_context,
4287
- );
4288
- }
3283
+ function create_element_children(children, transform_context) {
3284
+ if (children.length === 0) {
3285
+ return [];
4289
3286
  }
4290
3287
 
4291
- if (node.type === 'UpdateExpression') {
4292
- const outer_names = get_referenced_outer_binding_names(
4293
- node.argument,
4294
- transform_context.available_bindings,
4295
- shadowed_names,
4296
- );
4297
- if (outer_names.length > 0) {
4298
- report_hook_callback_outer_mutation_error(
4299
- node.argument,
4300
- outer_names,
4301
- hook_name,
4302
- transform_context,
4303
- );
3288
+ if (children.every(is_inline_element_child) && !children_contain_return_semantics(children)) {
3289
+ const saved_inside_element_child = transform_context.inside_element_child;
3290
+ transform_context.inside_element_child = true;
3291
+ try {
3292
+ return children.map((/** @type {any} */ child) => to_jsx_child(child, transform_context));
3293
+ } finally {
3294
+ transform_context.inside_element_child = saved_inside_element_child;
4304
3295
  }
4305
3296
  }
4306
3297
 
4307
- for (const key of Object.keys(node)) {
4308
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4309
- continue;
4310
- }
4311
- if (key === 'left' && node.type === 'AssignmentExpression') {
4312
- continue;
4313
- }
4314
- if (key === 'argument' && node.type === 'UpdateExpression') {
4315
- continue;
4316
- }
4317
- validate_hook_callback_outer_mutations_in_node(
4318
- node[key],
4319
- shadowed_names,
4320
- transform_context,
4321
- hook_name,
4322
- );
3298
+ const saved_inside_element_child = transform_context.inside_element_child;
3299
+ transform_context.inside_element_child = true;
3300
+ try {
3301
+ return [statement_body_to_jsx_child(children, transform_context)];
3302
+ } finally {
3303
+ transform_context.inside_element_child = saved_inside_element_child;
4323
3304
  }
4324
3305
  }
4325
3306
 
4326
3307
  /**
4327
- * @param {any} node
4328
- * @param {TransformContext} transform_context
4329
- * @param {Set<string>} hook_result_names
3308
+ * @param {any[]} children
4330
3309
  * @returns {boolean}
4331
3310
  */
4332
- function expression_contains_hook_derived_value(node, transform_context, hook_result_names) {
4333
- return (
4334
- node_contains_top_level_hook_call(node, false, transform_context, true) ||
4335
- references_name_in_set(node, hook_result_names)
4336
- );
3311
+ function children_contain_return_semantics(children) {
3312
+ return children.some(child_contains_return_semantics);
4337
3313
  }
4338
3314
 
4339
3315
  /**
4340
3316
  * @param {any} node
4341
- * @param {Set<string>} names
4342
3317
  * @returns {boolean}
4343
3318
  */
4344
- function references_name_in_set(node, names) {
4345
- if (!node || typeof node !== 'object' || names.size === 0) {
3319
+ function child_contains_return_semantics(node) {
3320
+ if (!node || typeof node !== 'object') {
4346
3321
  return false;
4347
3322
  }
4348
3323
 
4349
- if (node.type === 'Identifier') {
4350
- return names.has(node.name);
3324
+ if (node.type === 'ReturnStatement') {
3325
+ return true;
4351
3326
  }
4352
3327
 
4353
3328
  if (
@@ -4359,20 +3334,14 @@ function references_name_in_set(node, names) {
4359
3334
  }
4360
3335
 
4361
3336
  if (Array.isArray(node)) {
4362
- return node.some((child) => references_name_in_set(child, names));
3337
+ return node.some(child_contains_return_semantics);
4363
3338
  }
4364
3339
 
4365
3340
  for (const key of Object.keys(node)) {
4366
3341
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4367
3342
  continue;
4368
3343
  }
4369
- if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
4370
- continue;
4371
- }
4372
- if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
4373
- continue;
4374
- }
4375
- if (references_name_in_set(node[key], names)) {
3344
+ if (child_contains_return_semantics(node[key])) {
4376
3345
  return true;
4377
3346
  }
4378
3347
  }
@@ -4382,162 +3351,104 @@ function references_name_in_set(node, names) {
4382
3351
 
4383
3352
  /**
4384
3353
  * @param {any} node
4385
- * @param {Set<string>} shadowed_names
4386
- * @returns {string[]}
3354
+ * @returns {boolean}
4387
3355
  */
4388
- function get_referenced_local_binding_names(node, shadowed_names) {
4389
- const names = new Set();
4390
- collect_referenced_local_binding_names(node, shadowed_names, names);
4391
- return [...names];
3356
+ function is_inline_element_child(node) {
3357
+ return node && is_render_child_node(node);
4392
3358
  }
4393
3359
 
4394
3360
  /**
4395
- * @param {any} node
4396
- * @param {Set<string>} shadowed_names
4397
- * @param {Set<string>} names
4398
- * @returns {void}
3361
+ * @param {any[]} body_nodes
3362
+ * @param {TransformContext} transform_context
3363
+ * @returns {ESTreeJSX.JSXExpressionContainer}
4399
3364
  */
4400
- function collect_referenced_local_binding_names(node, shadowed_names, names) {
4401
- if (!node || typeof node !== 'object') {
4402
- return;
3365
+ function statement_body_to_jsx_child(body_nodes, transform_context) {
3366
+ if (
3367
+ should_extract_hook_helpers(transform_context) &&
3368
+ body_contains_top_level_hook_call(body_nodes, transform_context, true)
3369
+ ) {
3370
+ return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
4403
3371
  }
4404
3372
 
4405
- if (node.type === 'Identifier') {
4406
- if (shadowed_names.has(node.name)) {
4407
- names.add(node.name);
4408
- }
4409
- return;
4410
- }
3373
+ return to_jsx_expression_container(
3374
+ b.call(b.arrow([], b.block(build_render_statements(body_nodes, true, transform_context)))),
3375
+ );
3376
+ }
4411
3377
 
4412
- if (Array.isArray(node)) {
4413
- for (const child of node) {
4414
- collect_referenced_local_binding_names(child, shadowed_names, names);
4415
- }
4416
- return;
4417
- }
3378
+ /**
3379
+ * @param {any[]} body_nodes
3380
+ * @param {TransformContext} transform_context
3381
+ * @returns {ESTreeJSX.JSXExpressionContainer}
3382
+ */
3383
+ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
3384
+ const source_node = get_body_source_node(body_nodes);
3385
+ const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
4418
3386
 
4419
- for (const key of Object.keys(node)) {
4420
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4421
- continue;
4422
- }
4423
- if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
4424
- continue;
4425
- }
4426
- if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
4427
- continue;
4428
- }
4429
- collect_referenced_local_binding_names(node[key], shadowed_names, names);
4430
- }
3387
+ return to_jsx_expression_container(
3388
+ create_hook_safe_helper_iife(helper.setup_statements, helper.component_element),
3389
+ source_node,
3390
+ );
4431
3391
  }
4432
3392
 
4433
3393
  /**
4434
- * @param {any} node
4435
- * @param {Map<string, AST.Identifier>} available_bindings
4436
- * @param {Set<string>} shadowed_names
4437
- * @returns {string[]}
3394
+ * @param {TransformContext} transform_context
3395
+ * @returns {string}
4438
3396
  */
4439
- function get_referenced_outer_binding_names(node, available_bindings, shadowed_names) {
4440
- const names = new Set();
4441
- collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names);
4442
- return [...names];
3397
+ function create_local_statement_component_name(transform_context) {
3398
+ transform_context.local_statement_component_index += 1;
3399
+ return `StatementBodyHook${transform_context.local_statement_component_index}`;
4443
3400
  }
4444
3401
 
4445
3402
  /**
4446
- * @param {any} node
4447
- * @param {Map<string, AST.Identifier>} available_bindings
4448
- * @param {Set<string>} shadowed_names
4449
- * @param {Set<string>} names
4450
- * @returns {void}
3403
+ * Wraps a list of body nodes into a component and returns
3404
+ * statements that return `<ComponentName prop1={prop1} ... />`.
3405
+ * Targets can either emit the helper component at module scope or cache the
3406
+ * component identity in module state while initializing it from the parent.
3407
+ * Used when a control flow branch contains hook calls that must be moved
3408
+ * into their own component boundary to satisfy the Rules of Hooks.
3409
+ *
3410
+ * @param {any[]} body_nodes
3411
+ * @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
3412
+ * @param {TransformContext} transform_context
3413
+ * @returns {any[]}
4451
3414
  */
4452
- function collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names) {
4453
- if (!node || typeof node !== 'object') {
4454
- return;
4455
- }
4456
-
4457
- if (node.type === 'Identifier') {
4458
- if (available_bindings.has(node.name) && !shadowed_names.has(node.name)) {
4459
- names.add(node.name);
4460
- }
4461
- return;
4462
- }
3415
+ function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
3416
+ const source_node = get_body_source_node(body_nodes);
3417
+ const helper = create_hook_safe_helper(
3418
+ body_nodes,
3419
+ key_expression,
3420
+ source_node,
3421
+ transform_context,
3422
+ );
3423
+ const statements = [...helper.setup_statements];
4463
3424
 
4464
- if (Array.isArray(node)) {
4465
- for (const child of node) {
4466
- collect_referenced_outer_binding_names(child, available_bindings, shadowed_names, names);
4467
- }
4468
- return;
4469
- }
3425
+ statements.push(b.return(helper.component_element));
4470
3426
 
4471
- for (const key of Object.keys(node)) {
4472
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4473
- continue;
4474
- }
4475
- if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
4476
- continue;
4477
- }
4478
- if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
4479
- continue;
4480
- }
4481
- collect_referenced_outer_binding_names(node[key], available_bindings, shadowed_names, names);
4482
- }
3427
+ return statements;
4483
3428
  }
4484
3429
 
4485
3430
  /**
4486
- * @param {any} node
4487
- * @returns {string | null}
3431
+ * @param {any[]} body_nodes
3432
+ * @param {Map<string, AST.Identifier>} available_bindings
3433
+ * @returns {AST.Identifier[]}
4488
3434
  */
4489
- function find_first_hook_call_name(node) {
4490
- if (!node || typeof node !== 'object') {
4491
- return null;
4492
- }
4493
-
4494
- if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
4495
- return get_hook_callee_name(node.callee);
4496
- }
3435
+ function get_referenced_helper_bindings(body_nodes, available_bindings) {
3436
+ const helper_bindings = [];
3437
+ const local_bindings = new Map();
4497
3438
 
4498
- if (
4499
- node.type === 'FunctionDeclaration' ||
4500
- node.type === 'FunctionExpression' ||
4501
- node.type === 'ArrowFunctionExpression'
4502
- ) {
4503
- return null;
3439
+ for (const node of body_nodes) {
3440
+ collect_statement_bindings(node, local_bindings);
4504
3441
  }
4505
3442
 
4506
- if (Array.isArray(node)) {
4507
- for (const child of node) {
4508
- const name = find_first_hook_call_name(child);
4509
- if (name) return name;
4510
- }
4511
- return null;
4512
- }
3443
+ for (const [name, binding] of available_bindings) {
3444
+ if (local_bindings.has(name)) continue;
4513
3445
 
4514
- for (const key of Object.keys(node)) {
4515
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4516
- continue;
3446
+ if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
3447
+ helper_bindings.push(binding);
4517
3448
  }
4518
- const name = find_first_hook_call_name(node[key]);
4519
- if (name) return name;
4520
3449
  }
4521
3450
 
4522
- return null;
4523
- }
4524
-
4525
- /**
4526
- * @param {any} callee
4527
- * @returns {string}
4528
- */
4529
- function get_hook_callee_name(callee) {
4530
- if (callee?.type === 'Identifier') {
4531
- return callee.name;
4532
- }
4533
- if (
4534
- callee?.type === 'MemberExpression' &&
4535
- !callee.computed &&
4536
- callee.property?.type === 'Identifier'
4537
- ) {
4538
- return callee.property.name;
4539
- }
4540
- return 'hook';
3451
+ return helper_bindings;
4541
3452
  }
4542
3453
 
4543
3454
  /**
@@ -4558,11 +3469,6 @@ export function create_hook_safe_helper(
4558
3469
  preallocated_helper_id,
4559
3470
  options = {},
4560
3471
  ) {
4561
- validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
4562
- body_nodes,
4563
- transform_context,
4564
- );
4565
-
4566
3472
  const helper_id =
4567
3473
  preallocated_helper_id ??
4568
3474
  create_generated_identifier(create_local_statement_component_name(transform_context));
@@ -4887,14 +3793,6 @@ function is_if_control_node(node) {
4887
3793
  return node?.type === 'IfStatement' || node?.type === 'JSXIfExpression';
4888
3794
  }
4889
3795
 
4890
- /**
4891
- * @param {any} node
4892
- * @returns {boolean}
4893
- */
4894
- function is_plain_if_statement(node) {
4895
- return node?.type === 'IfStatement' && !is_template_if_node(node);
4896
- }
4897
-
4898
3796
  /**
4899
3797
  * @param {any} node
4900
3798
  * @returns {boolean}
@@ -4941,17 +3839,6 @@ function is_try_control_node(node) {
4941
3839
  return node?.type === 'TryStatement' || node?.type === 'JSXTryExpression';
4942
3840
  }
4943
3841
 
4944
- /**
4945
- * @param {any} node
4946
- * @returns {boolean}
4947
- */
4948
- function is_for_of_control_node(node) {
4949
- return (
4950
- node?.type === 'ForOfStatement' ||
4951
- (node?.type === 'JSXForExpression' && node.statementType === 'ForOfStatement')
4952
- );
4953
- }
4954
-
4955
3842
  /**
4956
3843
  * @param {any} node
4957
3844
  * @param {TransformContext} transform_context
@@ -5902,16 +4789,9 @@ function try_statement_to_jsx_child(node, transform_context) {
5902
4789
  // correctly identifies references to err/reset as non-static
5903
4790
  const saved_catch_bindings = transform_context.available_bindings;
5904
4791
  transform_context.available_bindings = new Map(saved_catch_bindings);
5905
- const catch_scoped_names = new Set();
5906
4792
  for (const param of catch_params) {
5907
4793
  collect_pattern_bindings(param, transform_context.available_bindings);
5908
- collect_pattern_names(param, catch_scoped_names);
5909
4794
  }
5910
- validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
5911
- catch_body_nodes,
5912
- transform_context,
5913
- catch_scoped_names,
5914
- );
5915
4795
 
5916
4796
  const fallback_fn = b.arrow(
5917
4797
  catch_params,