@tsrx/core 0.1.16 → 0.1.18

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.
@@ -37,21 +37,19 @@ import {
37
37
  } from '../../utils/builders.js';
38
38
  import * as b from '../../utils/builders.js';
39
39
  import { apply_lazy_transforms, preallocate_lazy_ids } from '../lazy.js';
40
- import { find_first_top_level_await_in_component_body } from '../await.js';
41
40
  import {
42
- prepare_stylesheet_for_render,
43
- annotate_component_with_hash,
44
- is_style_element,
45
- } from '../scoping.js';
41
+ find_first_top_level_await,
42
+ find_first_top_level_await_in_tsrx_function_body,
43
+ } from '../await.js';
44
+ import { prepare_stylesheet_for_render, annotate_with_hash, is_style_element } from '../scoping.js';
46
45
  import {
47
- validate_class_component_declarations,
48
- validate_component_loop_break_statement,
49
- validate_component_loop_return_statement,
50
- validate_component_params,
51
- validate_component_return_statement,
52
- validate_component_unsupported_loop_statement,
53
- } from '../../analyze/validation.js';
54
- import { get_component_from_path, is_function_or_component_node } from '../../utils/ast.js';
46
+ collect_style_ref_attributes,
47
+ create_style_class_map,
48
+ create_style_class_map_from_stylesheet,
49
+ create_style_ref_setup_statements,
50
+ get_style_element_stylesheet,
51
+ } from '../style-ref.js';
52
+ import { is_function_or_component_node } from '../../utils/ast.js';
55
53
  import {
56
54
  is_interleaved_body as is_interleaved_body_core,
57
55
  is_capturable_jsx_child,
@@ -66,43 +64,6 @@ const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
66
64
  const TEMPLATE_FRAGMENT_ERROR =
67
65
  'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
68
66
 
69
- /**
70
- * @param {TransformContext} transform_context
71
- * @returns {string}
72
- */
73
- export function get_invalid_html_child_error_message(transform_context) {
74
- return `\`{html ...}\` is only supported as the sole child of an element in ${transform_context.platform.name}.`;
75
- }
76
-
77
- /**
78
- * @param {AST.Node} node
79
- * @param {TransformContext} transform_context
80
- */
81
- function report_invalid_html_child_error(node, transform_context) {
82
- error(
83
- get_invalid_html_child_error_message(transform_context),
84
- transform_context.filename,
85
- node,
86
- transform_context.errors,
87
- transform_context.comments,
88
- );
89
- }
90
-
91
- /**
92
- * In loose/editor mode `error(...)` records the diagnostic and continues, so an
93
- * invalid standalone `{html ...}` child still needs a valid expression node for
94
- * the virtual TSX output.
95
- *
96
- * @param {any} node
97
- * @param {TransformContext} transform_context
98
- * @returns {ESTreeJSX.JSXExpressionContainer}
99
- */
100
- export function recover_invalid_html_child(node, transform_context) {
101
- report_invalid_html_child_error(node, transform_context);
102
- const expression = set_loc(clone_expression_node(node.expression), node);
103
- return to_jsx_expression_container(expression, node);
104
- }
105
-
106
67
  /**
107
68
  * @param {AST.Node} node
108
69
  * @param {TransformContext} transform_context
@@ -181,60 +142,16 @@ function is_function_or_class_boundary(node) {
181
142
  );
182
143
  }
183
144
 
184
- /**
185
- * @param {any[]} path
186
- * @returns {boolean}
187
- */
188
- function is_inside_component_for_of(path) {
189
- for (let i = path.length - 1; i >= 0; i -= 1) {
190
- const node = path[i];
191
- if (is_function_or_class_boundary(node) || node?.type === 'Component') {
192
- return false;
193
- }
194
- if (node?.type === 'ForOfStatement') {
195
- return true;
196
- }
197
- }
198
- return false;
199
- }
200
-
201
- /**
202
- * @param {any[]} path
203
- * @returns {boolean}
204
- */
205
- function break_targets_component_loop(path) {
206
- for (let i = path.length - 1; i >= 0; i -= 1) {
207
- const node = path[i];
208
- if (is_function_or_class_boundary(node) || node?.type === 'Component') {
209
- return false;
210
- }
211
- if (node?.type === 'SwitchStatement') {
212
- return false;
213
- }
214
- if (
215
- node?.type === 'ForOfStatement' ||
216
- node?.type === 'ForStatement' ||
217
- node?.type === 'ForInStatement' ||
218
- node?.type === 'WhileStatement' ||
219
- node?.type === 'DoWhileStatement'
220
- ) {
221
- return true;
222
- }
223
- }
224
- return false;
225
- }
226
-
227
145
  /**
228
146
  * Build a `transform()` function for a specific JSX platform (React, Preact,
229
- * Solid). Given a `JsxPlatform` descriptor, returns a transform that parses
230
- * Ripple's `Component`/`Element`/`Text`/`TSRXExpression` AST into a plain
231
- * TSX module for that platform.
147
+ * Solid). Given a `JsxPlatform` descriptor, returns a transform that lowers
148
+ * native TSRX template nodes into a plain TSX module for that platform.
232
149
  *
233
- * Any `<style>` element declared inside a component is collected, rendered
234
- * via `@tsrx/core`'s stylesheet renderer, and returned alongside the JS
235
- * output so a downstream plugin can inject it. The compiler also augments
236
- * every non-style Element in a scoped component with the stylesheet's hash
237
- * class so scoped selectors match correctly.
150
+ * Any `<style>` element declared inside a TSRX fragment is collected, rendered
151
+ * via `@tsrx/core`'s stylesheet renderer, and returned alongside the JS output
152
+ * so a downstream plugin can inject it. The compiler also augments every
153
+ * non-style Element in that fragment with the stylesheet's hash class so scoped
154
+ * selectors match correctly.
238
155
  *
239
156
  * @param {JsxPlatform} platform
240
157
  * @returns {(ast: AST.Program, source: string, filename?: string, options?: JsxTransformOptions) => JsxTransformResult}
@@ -249,13 +166,6 @@ export function createJsxTransform(platform) {
249
166
  */
250
167
  function transform(ast, source, filename, options) {
251
168
  const suspense_source = options?.suspenseSource ?? platform.imports.suspense;
252
- const should_scan_use_server_directive =
253
- platform.validation.requireUseServerForAwait &&
254
- (!platform.hooks?.validateComponentAwait ||
255
- platform.validation.scanUseServerDirectiveForAwaitWithCustomValidator !== false);
256
- const module_uses_server_directive = should_scan_use_server_directive
257
- ? has_use_server_directive(ast)
258
- : true;
259
169
  const collect = !!(options?.collect || options?.loose);
260
170
  /** @type {any[]} */
261
171
  const stylesheets = [];
@@ -267,18 +177,20 @@ export function createJsxTransform(platform) {
267
177
  needs_error_boundary: false,
268
178
  needs_suspense: false,
269
179
  needs_merge_refs: false,
270
- needs_ref_prop: false,
271
180
  needs_normalize_spread_props: false,
181
+ needs_normalize_spread_props_for_ref_attr: false,
272
182
  needs_fragment: false,
273
183
  needs_for_of_iterable: false,
274
184
  needs_iteration_value_type: false,
185
+ stylesheets,
275
186
  module_scoped_hook_components:
276
187
  options?.moduleScopedHookComponents ?? !!platform.hooks?.moduleScopedHookComponents,
277
188
  helper_state: null,
189
+ hook_helpers_enabled: false,
278
190
  available_bindings: new Map(),
279
191
  lazy_next_id: 0,
280
- current_css_hash: null,
281
192
  filename: filename ?? null,
193
+ source,
282
194
  collect,
283
195
  errors: collect ? options?.errors : undefined,
284
196
  comments: options?.comments,
@@ -293,197 +205,12 @@ export function createJsxTransform(platform) {
293
205
  }
294
206
 
295
207
  walk(/** @type {any} */ (ast), transform_context, {
296
- ReturnStatement(node, { next, path }) {
297
- if (get_component_from_path(path)) {
298
- if (is_inside_component_for_of(path)) {
299
- validate_component_loop_return_statement(
300
- node,
301
- filename,
302
- transform_context.errors,
303
- transform_context.comments,
304
- );
305
- } else {
306
- validate_component_return_statement(
307
- node,
308
- filename,
309
- transform_context.errors,
310
- transform_context.comments,
311
- );
312
- }
313
- }
314
-
315
- return next();
316
- },
317
-
318
- BreakStatement(node, { next, path }) {
319
- if (get_component_from_path(path) && break_targets_component_loop(path)) {
320
- validate_component_loop_break_statement(
321
- node,
322
- filename,
323
- transform_context.errors,
324
- transform_context.comments,
325
- );
326
- }
327
-
328
- return next();
329
- },
330
-
331
- ForStatement(node, { next, path }) {
332
- if (get_component_from_path(path)) {
333
- validate_component_unsupported_loop_statement(
334
- node,
335
- filename,
336
- transform_context.errors,
337
- transform_context.comments,
338
- );
339
- }
340
-
341
- return next();
342
- },
343
-
344
- ForInStatement(node, { next, path }) {
345
- if (get_component_from_path(path)) {
346
- validate_component_unsupported_loop_statement(
347
- node,
348
- filename,
349
- transform_context.errors,
350
- transform_context.comments,
351
- );
352
- }
353
-
354
- return next();
355
- },
356
-
357
- WhileStatement(node, { next, path }) {
358
- if (get_component_from_path(path)) {
359
- validate_component_unsupported_loop_statement(
360
- node,
361
- filename,
362
- transform_context.errors,
363
- transform_context.comments,
364
- );
365
- }
366
-
367
- return next();
368
- },
369
-
370
- DoWhileStatement(node, { next, path }) {
371
- if (get_component_from_path(path)) {
372
- validate_component_unsupported_loop_statement(
373
- node,
374
- filename,
375
- transform_context.errors,
376
- transform_context.comments,
377
- );
378
- }
379
-
380
- return next();
381
- },
382
-
383
- ClassBody(node, { next }) {
384
- validate_class_component_declarations(
385
- /** @type {any} */ (node),
386
- filename,
387
- transform_context.errors,
388
- transform_context.comments,
389
- );
390
- return next();
391
- },
392
-
393
- Component(node, { next, state }) {
394
- const as_any = /** @type {any} */ (node);
395
-
396
- validate_component_params(
397
- as_any,
398
- filename,
399
- transform_context.errors,
400
- transform_context.comments,
401
- );
402
-
403
- const await_expression = find_first_top_level_await_in_component_body(as_any.body || []);
404
-
405
- if (await_expression) {
406
- // Let a platform reject component-level await entirely (solid)
407
- // or customize the error. Otherwise fall back to the default
408
- // `requireUseServerForAwait` check.
409
- if (platform.hooks?.validateComponentAwait) {
410
- platform.hooks.validateComponentAwait(
411
- await_expression,
412
- as_any,
413
- state,
414
- module_uses_server_directive,
415
- source,
416
- );
417
- } else if (!module_uses_server_directive) {
418
- error(
419
- `${platform.name} components can only use \`await\` when the module has a top-level "use server" directive.`,
420
- state.filename,
421
- await_expression,
422
- state.errors,
423
- state.comments,
424
- );
425
- }
426
-
427
- as_any.metadata = /** @type {any} */ ({
428
- ...(as_any.metadata || {}),
429
- contains_top_level_await: true,
430
- });
431
- }
432
-
433
- const css = as_any.css;
434
- if (css) {
435
- apply_css_definition_metadata(as_any, css);
436
- stylesheets.push(css);
437
- const hash = css.hash;
438
- annotate_component_with_hash(
439
- as_any,
440
- hash,
441
- platform.jsx.rewriteClassAttr ? 'className' : 'class',
442
- transform_context.typeOnly,
443
- );
444
- }
445
- return next(state);
446
- },
208
+ FunctionDeclaration: collect_native_function_tsrx_metadata,
209
+ FunctionExpression: collect_native_function_tsrx_metadata,
210
+ ArrowFunctionExpression: collect_native_function_tsrx_metadata,
447
211
  });
448
212
 
449
213
  const transformed = walk(/** @type {any} */ (ast), transform_context, {
450
- Component(node, { next, state }) {
451
- const as_any = /** @type {any} */ (node);
452
-
453
- // Set up helper_state and bindings BEFORE next() so that nested
454
- // hook_safe_* calls (inside Element children) can register helpers
455
- // and access available bindings during the bottom-up walk.
456
- const helper_state = create_helper_state(as_any.id?.name || 'Component');
457
- const saved_helper_state = state.helper_state;
458
- const saved_bindings = state.available_bindings;
459
- const saved_css_hash = state.current_css_hash;
460
- state.helper_state = helper_state;
461
- state.current_css_hash = as_any.css ? as_any.css.hash : null;
462
-
463
- // Pre-collect component body bindings (params + top-level statements)
464
- // so Element children processed during the bottom-up walk can see
465
- // component-scope names. Hook-safe helpers filter this set down to
466
- // the names their body actually references before generating props.
467
- const body_bindings = collect_param_bindings(as_any.params || []);
468
- const body = as_any.body || [];
469
- const split_index = find_hook_safe_split_index(body, state);
470
- const collect_end = split_index === -1 ? body.length : split_index;
471
- for (let i = 0; i < collect_end; i += 1) {
472
- collect_statement_bindings(body[i], body_bindings);
473
- }
474
- state.available_bindings = body_bindings;
475
-
476
- const inner = /** @type {any} */ (next() ?? node);
477
-
478
- // Restore context
479
- state.helper_state = saved_helper_state;
480
- state.available_bindings = saved_bindings;
481
- state.current_css_hash = saved_css_hash;
482
-
483
- const convert = platform.hooks?.componentToFunction ?? component_to_function_declaration;
484
- return /** @type {any} */ (convert(inner, state, helper_state));
485
- },
486
-
487
214
  Tsx(node, { next, path }) {
488
215
  const inner = /** @type {any} */ (next() ?? node);
489
216
  const in_jsx_child = in_jsx_child_context(path);
@@ -493,7 +220,19 @@ export function createJsxTransform(platform) {
493
220
  },
494
221
 
495
222
  Tsrx(node, { next, path, state }) {
496
- const inner = /** @type {any} */ (next() ?? node);
223
+ /** @type {{ css: any, style_refs: any[] } | null} */
224
+ let style_context = null;
225
+ const inner = with_tsrx_fragment_styles(node, state, (context) => {
226
+ style_context = context;
227
+ return next() ?? node;
228
+ });
229
+ for (const statement of create_tsrx_style_ref_setup_statements(
230
+ node,
231
+ style_context,
232
+ state,
233
+ )) {
234
+ add_jsx_setup_declaration(inner, statement);
235
+ }
497
236
  const in_jsx_child = in_jsx_child_context(path);
498
237
  return /** @type {any} */ (
499
238
  wrap_jsx_setup_declarations(
@@ -514,12 +253,23 @@ export function createJsxTransform(platform) {
514
253
  );
515
254
  },
516
255
 
517
- Element(node, { next, state }) {
256
+ Element(node, { next, path, state }) {
257
+ if (is_style_element(node) && is_style_expression_position(path)) {
258
+ const stylesheet = get_style_element_stylesheet(node);
259
+ if (stylesheet) {
260
+ analyze_css(stylesheet);
261
+ state.stylesheets.push(stylesheet);
262
+ return /** @type {any} */ (create_style_class_map_from_stylesheet(stylesheet));
263
+ }
264
+ }
265
+
518
266
  // Capture raw children BEFORE the walker transforms them so a
519
267
  // platform hook (e.g. Solid's textContent optimization) can
520
268
  // inspect the original Text / TSRXExpression nodes rather than
521
269
  // their walker-lowered JSXExpressionContainer equivalents.
522
- const raw_children = /** @type {any} */ (node).children || [];
270
+ const raw_children = /** @type {any} */ (node.children || []).map(
271
+ (/** @type {any} */ child) => (child && typeof child === 'object' ? { ...child } : child),
272
+ );
523
273
  const inner = /** @type {any} */ (next() ?? node);
524
274
  const hook = platform.hooks?.transformElement;
525
275
  if (hook) return /** @type {any} */ (hook(inner, state, raw_children));
@@ -538,48 +288,25 @@ export function createJsxTransform(platform) {
538
288
  return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
539
289
  },
540
290
 
541
- Style(node, { state, path }) {
542
- validate_style_directive(node, state, path);
543
- const class_name = typeof node.value.value === 'string' ? node.value.value : '';
544
- const value = state.current_css_hash
545
- ? `${state.current_css_hash} ${class_name}`
546
- : class_name;
547
- return b.literal(value, undefined, node);
548
- },
549
-
550
291
  // Default .metadata on every function-like node so downstream consumers
551
- // (e.g. segments.js reading node.value.metadata.is_component on class
552
- // methods) don't trip on an undefined metadata object. Ripple's analyze
553
- // phase does this via visit_function; tsrx-react has no analyze phase.
554
- // If a plain JS function contains a hook-bearing <tsrx> expression,
555
- // give it a temporary helper scope so extracted hook components can
556
- // be emitted with stable identities just like component-body helpers.
557
- FunctionDeclaration: transform_function_with_hook_helpers,
558
- FunctionExpression: transform_function_with_hook_helpers,
559
- ArrowFunctionExpression: transform_function_with_hook_helpers,
560
-
561
- RefExpression(node) {
562
- return create_ref_prop_call(node, transform_context);
563
- },
292
+ // do not trip on an undefined metadata object. Ripple's analyze phase
293
+ // does this via visit_function; tsrx-react has no analyze phase.
294
+ // If an uppercase JS function contains hook-bearing TSRX, give it a
295
+ // temporary helper scope so extracted hook helpers get stable identities.
296
+ FunctionDeclaration: transform_function,
297
+ FunctionExpression: transform_function,
298
+ ArrowFunctionExpression: transform_function,
564
299
 
565
300
  JSXOpeningElement(node, { next }) {
566
- const visited = next() || node;
567
- const is_component = is_component_like_jsx_name(visited.name);
568
- const attrs = normalize_named_ref_attributes(
569
- visited.attributes || [],
570
- !is_component,
571
- transform_context,
572
- );
573
- if (transform_context.typeOnly) {
574
- add_ref_target_type_to_ref_prop_attributes(
575
- attrs,
576
- !is_component ? create_element_ref_target_type(visited) : null,
577
- );
301
+ const visited = /** @type {any} */ (next() || node);
302
+ if (visited.metadata?.native_tsrx_pretransformed) {
303
+ return visited;
578
304
  }
305
+ const is_component = is_component_like_jsx_name(visited.name);
579
306
  return {
580
307
  ...visited,
581
308
  attributes: merge_duplicate_refs(
582
- normalize_host_ref_spreads(attrs, !is_component, transform_context),
309
+ normalize_host_ref_spreads(visited.attributes || [], !is_component, transform_context),
583
310
  transform_context,
584
311
  ),
585
312
  };
@@ -594,9 +321,7 @@ export function createJsxTransform(platform) {
594
321
  }
595
322
 
596
323
  // Apply lazy destructuring transforms to module-level code (top-level function
597
- // declarations, arrow functions, etc.). Component bodies have already been
598
- // transformed inside component_to_function_declaration; this catches plain
599
- // functions outside components and any lazy patterns in module scope.
324
+ // declarations, arrow functions, etc.).
600
325
  // In type-only mode, the lazy patterns survive untouched: esrap ignores the
601
326
  // non-standard `lazy` flag, so `&{ a, b }` prints as `{ a, b }`, `let &[a]
602
327
  // = expr` prints as `let [a] = expr`, and the bare statement-level form
@@ -629,18 +354,30 @@ export function createJsxTransform(platform) {
629
354
  *
630
355
  * @param {any} component
631
356
  * @param {any} css
357
+ * @param {boolean} [export_top_scoped_classes]
632
358
  * @returns {void}
633
359
  */
634
- function apply_css_definition_metadata(component, css) {
360
+ function apply_css_definition_metadata(component, css, export_top_scoped_classes = false) {
635
361
  analyze_css(css);
636
362
 
637
363
  const metadata = component.metadata || (component.metadata = { path: [] });
638
364
  const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
639
365
  const top_scoped_classes = metadata.topScopedClasses || new Map();
640
- const elements = collect_css_prunable_elements(component.body || []);
366
+ const elements = collect_css_prunable_elements(component.body || component.children || []);
367
+
368
+ const prune = () => {
369
+ for (const element of elements) {
370
+ prune_css(css, element, style_classes, top_scoped_classes);
371
+ }
372
+ };
641
373
 
642
- for (const element of elements) {
643
- prune_css(css, element, style_classes, top_scoped_classes);
374
+ prune();
375
+
376
+ if (export_top_scoped_classes) {
377
+ for (const [class_name, class_info] of top_scoped_classes) {
378
+ style_classes.set(class_name, class_info.selector ?? class_info);
379
+ }
380
+ prune();
644
381
  }
645
382
 
646
383
  if (top_scoped_classes.size > 0) {
@@ -668,8 +405,7 @@ function collect_css_prunable_elements(value, elements = []) {
668
405
  if (
669
406
  value.type === 'FunctionDeclaration' ||
670
407
  value.type === 'FunctionExpression' ||
671
- value.type === 'ArrowFunctionExpression' ||
672
- value.type === 'Component'
408
+ value.type === 'ArrowFunctionExpression'
673
409
  ) {
674
410
  return elements;
675
411
  }
@@ -690,127 +426,6 @@ function collect_css_prunable_elements(value, elements = []) {
690
426
  return elements;
691
427
  }
692
428
 
693
- /**
694
- * Detect a top-level `"use server"` directive. Used by platforms whose
695
- * validation rule requires the directive to enable top-level `await`
696
- * in components (currently: Preact).
697
- *
698
- * @param {AST.Program} program
699
- * @returns {boolean}
700
- */
701
- function has_use_server_directive(program) {
702
- for (const statement of program.body || []) {
703
- const directive = /** @type {any} */ (statement).directive;
704
-
705
- if (directive === 'use server') {
706
- return true;
707
- }
708
-
709
- if (
710
- statement.type === 'ExpressionStatement' &&
711
- statement.expression?.type === 'Literal' &&
712
- statement.expression.value === 'use server'
713
- ) {
714
- return true;
715
- }
716
-
717
- if (directive == null) {
718
- break;
719
- }
720
- }
721
-
722
- return false;
723
- }
724
-
725
- /**
726
- * Lower a TSRX `Component` node into the shared function-declaration form used
727
- * by the default JSX targets. Platform hooks can reuse this helper and wrap the
728
- * resulting function in another declaration shape without reimplementing
729
- * component body lowering, lazy destructuring, helper generation, or top-level
730
- * await handling.
731
- *
732
- * @param {any} component
733
- * @param {TransformContext} transform_context
734
- * @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} [walk_helper_state]
735
- * @returns {AST.FunctionDeclaration | AST.FunctionExpression | AST.ArrowFunctionExpression}
736
- */
737
- export function component_to_function_declaration(component, transform_context, walk_helper_state) {
738
- const helper_state = walk_helper_state || create_helper_state(component.id?.name || 'Component');
739
- const params = component.params || [];
740
- const body = /** @type {any[]} */ (component.body || []);
741
- const is_async_component =
742
- !!component?.metadata?.contains_top_level_await ||
743
- find_first_top_level_await_in_component_body(body) !== null;
744
-
745
- // Collect param bindings from original patterns (lazy patterns still intact).
746
- const param_bindings = collect_param_bindings(params);
747
-
748
- // Save and set context for this component scope
749
- const saved_helper_state = transform_context.helper_state;
750
- const saved_bindings = transform_context.available_bindings;
751
- transform_context.helper_state = helper_state;
752
- transform_context.available_bindings = new Map(param_bindings);
753
-
754
- const body_statements = build_component_statements(body, transform_context);
755
- const body_block = b.block(body_statements);
756
-
757
- /** @type {AST.FunctionDeclaration | AST.FunctionExpression | AST.ArrowFunctionExpression} */
758
- let fn;
759
-
760
- if (component.id) {
761
- fn = b.function_declaration(
762
- component.id,
763
- params,
764
- body_block,
765
- is_async_component,
766
- component.typeParameters,
767
- );
768
- } else if (component.metadata?.arrow) {
769
- fn = b.arrow(params, body_block, is_async_component, component.typeParameters);
770
- } else {
771
- fn = b.function(null, params, body_block, is_async_component, component.typeParameters);
772
- }
773
- /** @type {any} */ (fn.metadata).is_component = true;
774
-
775
- // `preallocate_lazy_ids` stamped `has_lazy_descendants` on the source
776
- // `Component` node; the freshly-built `fn` shares the same params/body
777
- // subtree, so the flag is equally applicable. Propagating it lets
778
- // `apply_lazy_transforms` honor its constant-time early-return path.
779
- if (/** @type {any} */ (component).metadata?.has_lazy_descendants) {
780
- /** @type {any} */ (fn.metadata).has_lazy_descendants = true;
781
- }
782
-
783
- // Apply lazy `&{}` / `&[]` rewrites end-to-end: the function-handler in
784
- // `apply_lazy_transforms` collects param bindings, merges with body bindings
785
- // discovered by the BlockStatement handler, replaces lazy params with their
786
- // `__lazyN` ids, and rewrites every reference. Constant-time fast-path for
787
- // functions whose subtrees contain no lazy patterns (flagged ahead of time
788
- // by `preallocate_lazy_ids`). In type-only mode the rewrite is skipped so
789
- // destructuring patterns survive into the virtual TSX and TypeScript can
790
- // flow real types.
791
- if (!transform_context.typeOnly) {
792
- fn = /** @type {typeof fn} */ (apply_lazy_transforms(fn, new Map()));
793
- }
794
-
795
- // Restore context
796
- transform_context.helper_state = saved_helper_state;
797
- transform_context.available_bindings = saved_bindings;
798
-
799
- const fn_metadata = /** @type {any} */ (fn.metadata);
800
- fn_metadata.generated_helpers = helper_state.helpers;
801
- fn_metadata.generated_statics = helper_state.statics;
802
-
803
- if (fn.type === 'FunctionDeclaration' && fn.id) {
804
- fn.id.metadata = /** @type {AST.Identifier['metadata']} */ ({
805
- ...fn.id.metadata,
806
- is_component: true,
807
- });
808
- }
809
-
810
- setLocation(fn, /** @type {any} */ (component), true);
811
- return fn;
812
- }
813
-
814
429
  /**
815
430
  * @param {any[]} body_nodes
816
431
  * @param {TransformContext} transform_context
@@ -842,16 +457,12 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
842
457
  // any JSX is constructed, and every JSX child would observe the final
843
458
  // state of mutable variables.
844
459
  const interleaved = is_interleaved_body(body_nodes);
845
- const capture_static_early_return_nodes =
846
- !interleaved &&
847
- !transform_context.platform.hooks?.isTopLevelSetupCall &&
848
- body_nodes.filter(is_returning_if_statement).length > 1;
849
460
  let capture_index = 0;
850
461
 
851
462
  for (let i = 0; i < body_nodes.length; i += 1) {
852
463
  const child = body_nodes[i];
853
464
 
854
- if (is_bare_return_statement(child)) {
465
+ if (is_loop_skip_return_statement(child)) {
855
466
  statements.push(create_component_return_statement(render_nodes, child));
856
467
  render_nodes.length = 0;
857
468
  has_terminal_return = true;
@@ -864,92 +475,17 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
864
475
  continue;
865
476
  }
866
477
 
867
- if (is_returning_if_statement(child)) {
868
- const branch_has_hooks = body_contains_top_level_hook_call(
869
- get_if_consequent_body(child),
870
- transform_context,
871
- true,
872
- );
873
- const continuation_has_hooks = body_contains_top_level_hook_call(
874
- body_nodes.slice(i + 1),
875
- transform_context,
876
- true,
877
- );
878
-
879
- if (capture_static_early_return_nodes) {
880
- capture_index = capture_static_early_return_render_nodes(
881
- render_nodes,
882
- statements,
883
- capture_index,
884
- transform_context,
885
- );
886
- }
887
-
888
- if (branch_has_hooks || continuation_has_hooks) {
889
- if (transform_context.platform.hooks?.isTopLevelSetupCall) {
890
- statements.push(
891
- ...create_setup_once_helper_split_returning_if_statements(
892
- child,
893
- body_nodes.slice(i + 1),
894
- render_nodes,
895
- transform_context,
896
- ),
897
- );
898
- transform_context.available_bindings = saved_bindings;
899
- return statements;
900
- }
901
-
902
- statements.push(
903
- ...create_component_helper_split_returning_if_statements(
904
- child,
905
- body_nodes.slice(i + 1),
906
- render_nodes,
907
- transform_context,
908
- ),
478
+ if (is_loop_skip_if_statement(child)) {
479
+ if (transform_context.platform.hooks?.isTopLevelSetupCall) {
480
+ const continuation_body = body_nodes.slice(i + 1);
481
+ const continuation_has_setup_statements = continuation_body.some(
482
+ (node) =>
483
+ !is_loop_skip_return_statement(node) &&
484
+ !is_loop_skip_if_statement(node) &&
485
+ !is_jsx_child(node),
909
486
  );
910
- transform_context.available_bindings = saved_bindings;
911
- return statements;
912
- }
913
-
914
- if (is_lone_return_if_statement(child)) {
915
- // On platforms where setup runs once (Vue Vapor), an early
916
- // `if (cond) return;` placed at setup level is non-reactive:
917
- // `cond` is evaluated only when setup runs and never again.
918
- // Inline the rest of the body as a render-time ternary so the
919
- // conditional re-evaluates when `cond` changes after mount.
920
- // React/Preact/Solid re-run the component body on every render,
921
- // so the old setup-time early return is already reactive there
922
- // and we keep it to avoid gratuitous output changes.
923
- if (transform_context.platform.hooks?.isTopLevelSetupCall) {
924
- const continuation_body = body_nodes.slice(i + 1);
925
-
926
- // Render-time inlining unconditionally lifts continuation
927
- // statements (provide/watch/declarations/etc.) into the
928
- // parent setup, which would run them regardless of the
929
- // early-return condition — wrong when the user wrote them
930
- // after `if (cond) return;`. Fall back to helper-split if
931
- // the continuation has any non-render statements so they
932
- // stay scoped to the helper's lifecycle.
933
- const continuation_has_setup_statements = continuation_body.some(
934
- (node) =>
935
- !is_bare_return_statement(node) &&
936
- !is_returning_if_statement(node) &&
937
- !is_jsx_child(node),
938
- );
939
-
940
- if (continuation_has_setup_statements) {
941
- statements.push(
942
- ...create_setup_once_helper_split_returning_if_statements(
943
- child,
944
- continuation_body,
945
- render_nodes,
946
- transform_context,
947
- ),
948
- );
949
- transform_context.available_bindings = saved_bindings;
950
- return statements;
951
- }
952
487
 
488
+ if (!continuation_has_setup_statements) {
953
489
  const continuation_statements = build_render_statements(
954
490
  continuation_body,
955
491
  false,
@@ -979,13 +515,10 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
979
515
 
980
516
  break;
981
517
  }
982
-
983
- statements.push(create_component_lone_return_if_statement(child, render_nodes));
984
- continue;
985
518
  }
986
519
 
987
520
  statements.push(
988
- create_component_returning_if_statement(child, render_nodes, transform_context),
521
+ create_component_loop_skip_if_statement(child, render_nodes, transform_context),
989
522
  );
990
523
  continue;
991
524
  }
@@ -993,6 +526,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
993
526
  if (
994
527
  child.type === 'ForOfStatement' &&
995
528
  !child.await &&
529
+ should_extract_hook_helpers(transform_context) &&
996
530
  !transform_context.platform.hooks?.isTopLevelSetupCall &&
997
531
  !transform_context.platform.hooks?.controlFlow?.forOf &&
998
532
  body_contains_top_level_hook_call(
@@ -1047,19 +581,60 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
1047
581
  }
1048
582
 
1049
583
  /**
1050
- * React-specific wrapper around the core `isInterleavedBody` helper that
1051
- * ignores bare `return` / lone return-if statements. Those are rewriting
1052
- * signals rather than user-visible side effects, so JSX children around
1053
- * them don't need capturing.
1054
- *
1055
584
  * @param {any[]} body_nodes
1056
585
  * @returns {boolean}
1057
586
  */
1058
587
  function is_interleaved_body(body_nodes) {
1059
- const filtered = body_nodes.filter(
1060
- (child) => !is_bare_return_statement(child) && !is_lone_return_if_statement(child),
588
+ return is_interleaved_body_core(body_nodes, is_jsx_child);
589
+ }
590
+
591
+ /**
592
+ * @param {any} node
593
+ * @param {TransformContext} transform_context
594
+ * @returns {boolean}
595
+ */
596
+ function function_needs_component_body_hook_split(node, transform_context) {
597
+ return (
598
+ transform_context.platform.hooks?.componentBodyHookHelpers === true &&
599
+ node.body?.type === 'BlockStatement' &&
600
+ find_component_body_hook_split_index(node.body.body || [], transform_context) !== -1
601
+ );
602
+ }
603
+
604
+ /**
605
+ * @param {any} node
606
+ * @param {TransformContext} transform_context
607
+ * @returns {void}
608
+ */
609
+ function rewrite_component_body_conditional_hook_splits(node, transform_context) {
610
+ if (
611
+ transform_context.platform.hooks?.componentBodyHookHelpers !== true ||
612
+ !should_extract_hook_helpers(transform_context) ||
613
+ node.body?.type !== 'BlockStatement'
614
+ ) {
615
+ return;
616
+ }
617
+
618
+ const body = node.body.body || [];
619
+ const split_index = find_component_body_hook_split_index(body, transform_context);
620
+ if (split_index === -1) {
621
+ return;
622
+ }
623
+
624
+ const split_statement = body[split_index];
625
+ const continuation_body = body.slice(split_index + 1);
626
+ const helper = create_hook_safe_helper(
627
+ continuation_body,
628
+ undefined,
629
+ get_body_source_node(continuation_body) || split_statement,
630
+ transform_context,
1061
631
  );
1062
- return is_interleaved_body_core(filtered, is_jsx_child);
632
+
633
+ node.body.body = [
634
+ ...body.slice(0, split_index + 1),
635
+ ...helper.setup_statements,
636
+ set_loc(b.return(helper.component_element), split_statement),
637
+ ];
1063
638
  }
1064
639
 
1065
640
  /**
@@ -1067,9 +642,9 @@ function is_interleaved_body(body_nodes) {
1067
642
  * @param {TransformContext} transform_context
1068
643
  * @returns {number}
1069
644
  */
1070
- function find_hook_safe_split_index(body_nodes, transform_context) {
645
+ function find_component_body_hook_split_index(body_nodes, transform_context) {
1071
646
  for (let i = 0; i < body_nodes.length; i += 1) {
1072
- if (!is_lone_return_if_statement(body_nodes[i])) {
647
+ if (!is_component_body_conditional_return_statement(body_nodes[i])) {
1073
648
  continue;
1074
649
  }
1075
650
 
@@ -1081,53 +656,117 @@ function find_hook_safe_split_index(body_nodes, transform_context) {
1081
656
  return -1;
1082
657
  }
1083
658
 
1084
- /**
1085
- * @param {any[]} body_nodes
1086
- * @param {TransformContext} transform_context
1087
- * @param {boolean} include_platform_setup
1088
- * @returns {boolean}
1089
- */
1090
- function body_contains_top_level_hook_call(
1091
- body_nodes,
1092
- transform_context,
1093
- include_platform_setup = false,
1094
- ) {
1095
- return body_nodes.some((node) =>
1096
- statement_contains_top_level_hook_call(node, transform_context, include_platform_setup),
1097
- );
1098
- }
1099
-
1100
659
  /**
1101
660
  * @param {any} node
1102
- * @param {TransformContext} transform_context
1103
- * @param {boolean} include_platform_setup
1104
661
  * @returns {boolean}
1105
662
  */
1106
- function statement_contains_top_level_hook_call(node, transform_context, include_platform_setup) {
1107
- return node_contains_top_level_hook_call(node, false, transform_context, include_platform_setup);
663
+ function is_component_body_conditional_return_statement(node) {
664
+ if (node?.type !== 'IfStatement') {
665
+ return false;
666
+ }
667
+
668
+ return (
669
+ statement_contains_component_body_return(node.consequent) ||
670
+ statement_contains_component_body_return(node.alternate)
671
+ );
1108
672
  }
1109
673
 
1110
674
  /**
1111
675
  * @param {any} node
1112
- * @param {boolean} inside_nested_function
1113
- * @param {TransformContext} transform_context
1114
- * @param {boolean} include_platform_setup
1115
676
  * @returns {boolean}
1116
677
  */
1117
- function node_contains_top_level_hook_call(
1118
- node,
1119
- inside_nested_function,
1120
- transform_context,
1121
- include_platform_setup,
1122
- ) {
678
+ function statement_contains_component_body_return(node) {
1123
679
  if (!node || typeof node !== 'object') {
1124
680
  return false;
1125
681
  }
1126
682
 
1127
- if (
1128
- inside_nested_function &&
1129
- (node.type === 'FunctionDeclaration' ||
1130
- node.type === 'FunctionExpression' ||
683
+ if (node.type === 'ReturnStatement') {
684
+ return true;
685
+ }
686
+
687
+ if (is_function_or_class_boundary(node)) {
688
+ return false;
689
+ }
690
+
691
+ if (Array.isArray(node)) {
692
+ return node.some(statement_contains_component_body_return);
693
+ }
694
+
695
+ if (node.type === 'BlockStatement') {
696
+ return (node.body || []).some(statement_contains_component_body_return);
697
+ }
698
+
699
+ if (node.type === 'IfStatement') {
700
+ return (
701
+ statement_contains_component_body_return(node.consequent) ||
702
+ statement_contains_component_body_return(node.alternate)
703
+ );
704
+ }
705
+
706
+ if (node.type === 'SwitchStatement') {
707
+ return (node.cases || []).some((/** @type {any} */ switch_case) =>
708
+ statement_contains_component_body_return(switch_case.consequent || []),
709
+ );
710
+ }
711
+
712
+ if (node.type === 'TryStatement') {
713
+ return (
714
+ statement_contains_component_body_return(node.block) ||
715
+ statement_contains_component_body_return(node.handler?.body) ||
716
+ statement_contains_component_body_return(node.finalizer)
717
+ );
718
+ }
719
+
720
+ return false;
721
+ }
722
+
723
+ /**
724
+ * @param {any[]} body_nodes
725
+ * @param {TransformContext} transform_context
726
+ * @param {boolean} include_platform_setup
727
+ * @returns {boolean}
728
+ */
729
+ function body_contains_top_level_hook_call(
730
+ body_nodes,
731
+ transform_context,
732
+ include_platform_setup = false,
733
+ ) {
734
+ return body_nodes.some((node) =>
735
+ statement_contains_top_level_hook_call(node, transform_context, include_platform_setup),
736
+ );
737
+ }
738
+
739
+ /**
740
+ * @param {any} node
741
+ * @param {TransformContext} transform_context
742
+ * @param {boolean} include_platform_setup
743
+ * @returns {boolean}
744
+ */
745
+ function statement_contains_top_level_hook_call(node, transform_context, include_platform_setup) {
746
+ return node_contains_top_level_hook_call(node, false, transform_context, include_platform_setup);
747
+ }
748
+
749
+ /**
750
+ * @param {any} node
751
+ * @param {boolean} inside_nested_function
752
+ * @param {TransformContext} transform_context
753
+ * @param {boolean} include_platform_setup
754
+ * @returns {boolean}
755
+ */
756
+ function node_contains_top_level_hook_call(
757
+ node,
758
+ inside_nested_function,
759
+ transform_context,
760
+ include_platform_setup,
761
+ ) {
762
+ if (!node || typeof node !== 'object') {
763
+ return false;
764
+ }
765
+
766
+ if (
767
+ inside_nested_function &&
768
+ (node.type === 'FunctionDeclaration' ||
769
+ node.type === 'FunctionExpression' ||
1131
770
  node.type === 'ArrowFunctionExpression')
1132
771
  ) {
1133
772
  return false;
@@ -1271,94 +910,897 @@ function create_helper_component_element(helper_id, bindings, source_node, mappi
1271
910
  ),
1272
911
  );
1273
912
 
1274
- const opening_element = b.jsx_opening_element(
1275
- identifier_to_jsx_name(clone_identifier(helper_id)),
1276
- attributes,
1277
- true,
1278
- );
1279
- const element = b.jsx_element_fresh(
1280
- mapWrapper ? set_loc(opening_element, source_node) : opening_element,
913
+ const opening_element = b.jsx_opening_element(
914
+ identifier_to_jsx_name(clone_identifier(helper_id)),
915
+ attributes,
916
+ true,
917
+ );
918
+ const element = b.jsx_element_fresh(
919
+ mapWrapper ? set_loc(opening_element, source_node) : opening_element,
920
+ );
921
+
922
+ return mapWrapper ? set_loc(element, source_node) : element;
923
+ }
924
+
925
+ /**
926
+ * @param {{ base_name: string, next_id: number, helpers: any[], statics: any[] }} helper_state
927
+ * @param {string} suffix
928
+ * @returns {string}
929
+ */
930
+ function create_helper_name(helper_state, suffix) {
931
+ helper_state.next_id += 1;
932
+ return `${helper_state.base_name}__${suffix}${helper_state.next_id}`;
933
+ }
934
+
935
+ /**
936
+ * @param {string} base_name
937
+ * @returns {{ base_name: string, next_id: number, helpers: any[], statics: any[] }}
938
+ */
939
+ function create_helper_state(base_name) {
940
+ return {
941
+ base_name,
942
+ next_id: 0,
943
+ helpers: [],
944
+ statics: [],
945
+ };
946
+ }
947
+
948
+ /**
949
+ * @param {any} node
950
+ * @param {{ next: (state?: TransformContext) => any, state: TransformContext }} context
951
+ * @returns {any}
952
+ */
953
+ function collect_native_function_tsrx_metadata(node, { next, state }) {
954
+ if (!function_has_native_tsrx_return(node)) {
955
+ return next(state);
956
+ }
957
+
958
+ node.metadata = {
959
+ ...(node.metadata || {}),
960
+ native_tsrx_function: true,
961
+ };
962
+
963
+ return next(state);
964
+ }
965
+
966
+ /**
967
+ * @param {any} node
968
+ * @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
969
+ * @returns {any}
970
+ */
971
+ function transform_function(node, context) {
972
+ if (node.metadata?.native_tsrx_function || function_has_native_tsrx_return(node)) {
973
+ return transform_native_tsrx_function(node, context);
974
+ }
975
+
976
+ return transform_function_with_hook_helpers(node, context);
977
+ }
978
+
979
+ /**
980
+ * @param {any} node
981
+ * @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
982
+ * @returns {any}
983
+ */
984
+ function transform_native_tsrx_function(node, { next, state, path }) {
985
+ const helper_state =
986
+ state.helper_state || create_helper_state(get_function_helper_base_name(node, path));
987
+ const saved_helper_state = state.helper_state;
988
+ const saved_bindings = state.available_bindings;
989
+ const saved_hook_helpers_enabled = state.hook_helpers_enabled;
990
+
991
+ state.helper_state = helper_state;
992
+ state.hook_helpers_enabled = is_uppercase_function_like(node, path);
993
+ state.available_bindings = merge_binding_maps(
994
+ saved_bindings,
995
+ collect_function_scope_bindings(node),
996
+ );
997
+
998
+ validate_native_tsrx_function_await(node, state);
999
+ expand_native_tsrx_function_returns(node, state);
1000
+ rewrite_component_body_conditional_hook_splits(node, state);
1001
+
1002
+ const inner = /** @type {any} */ (next() ?? node);
1003
+
1004
+ state.helper_state = saved_helper_state;
1005
+ state.available_bindings = saved_bindings;
1006
+ state.hook_helpers_enabled = saved_hook_helpers_enabled;
1007
+
1008
+ ensure_function_metadata(inner, { next: () => inner });
1009
+ inner.metadata = {
1010
+ ...(inner.metadata || {}),
1011
+ native_tsrx_function: true,
1012
+ };
1013
+ if (!saved_helper_state && (helper_state.helpers.length || helper_state.statics.length)) {
1014
+ inner.metadata.generated_helpers = helper_state.helpers;
1015
+ inner.metadata.generated_statics = helper_state.statics;
1016
+ }
1017
+
1018
+ const wrapped = state.platform.hooks?.wrapNativeFunctionComponent?.(inner, state, path);
1019
+ if (wrapped) {
1020
+ return wrapped;
1021
+ }
1022
+
1023
+ return inner;
1024
+ }
1025
+
1026
+ /**
1027
+ * @param {any} node
1028
+ * @param {TransformContext} transform_context
1029
+ * @returns {void}
1030
+ */
1031
+ function validate_native_tsrx_function_await(node, transform_context) {
1032
+ const await_node = find_first_top_level_await_in_native_tsrx_function(node);
1033
+ if (!await_node) {
1034
+ return;
1035
+ }
1036
+
1037
+ const validator = transform_context.platform.hooks?.validateComponentAwait;
1038
+ if (validator) {
1039
+ validator(await_node, node, transform_context, false, transform_context.source || '');
1040
+ return;
1041
+ }
1042
+
1043
+ if (transform_context.platform.validation.requireUseServerForAwait) {
1044
+ error(
1045
+ 'Top-level `await` in TSRX functions requires a module-level `"use server"` directive.',
1046
+ transform_context.filename,
1047
+ await_node,
1048
+ transform_context.errors,
1049
+ transform_context.comments,
1050
+ );
1051
+ }
1052
+ }
1053
+
1054
+ /**
1055
+ * @param {any} node
1056
+ * @returns {any | null}
1057
+ */
1058
+ function find_first_top_level_await_in_native_tsrx_function(node) {
1059
+ if (
1060
+ node.type === 'ArrowFunctionExpression' &&
1061
+ node.body?.type !== 'BlockStatement' &&
1062
+ node_contains_native_tsrx_template(node.body)
1063
+ ) {
1064
+ return find_first_top_level_await(node.body, false);
1065
+ }
1066
+
1067
+ const body = node.body?.type === 'BlockStatement' ? node.body.body || [] : [];
1068
+ return find_first_top_level_await_in_native_tsrx_statements(body);
1069
+ }
1070
+
1071
+ /**
1072
+ * @param {any[]} statements
1073
+ * @returns {any | null}
1074
+ */
1075
+ function find_first_top_level_await_in_native_tsrx_statements(statements) {
1076
+ for (const statement of statements) {
1077
+ const found = find_first_top_level_await_in_native_tsrx_statement(statement);
1078
+ if (found) return found;
1079
+ }
1080
+ return null;
1081
+ }
1082
+
1083
+ /**
1084
+ * @param {any} statement
1085
+ * @returns {any | null}
1086
+ */
1087
+ function find_first_top_level_await_in_native_tsrx_statement(statement) {
1088
+ if (!statement || typeof statement !== 'object') return null;
1089
+
1090
+ if (statement.type === 'ReturnStatement' && statement.argument?.type === 'Tsrx') {
1091
+ return find_first_top_level_await_in_tsrx_function_body(statement.argument.children || []);
1092
+ }
1093
+
1094
+ if (
1095
+ statement.type === 'ReturnStatement' &&
1096
+ node_contains_native_tsrx_template(statement.argument)
1097
+ ) {
1098
+ return find_first_top_level_await(statement.argument, false);
1099
+ }
1100
+
1101
+ if (is_function_or_class_boundary(statement)) {
1102
+ return null;
1103
+ }
1104
+
1105
+ if (statement.type === 'BlockStatement') {
1106
+ return find_first_top_level_await_in_native_tsrx_statements(statement.body || []);
1107
+ }
1108
+
1109
+ if (statement.type === 'IfStatement') {
1110
+ return (
1111
+ find_first_top_level_await_in_native_tsrx_statement(statement.consequent) ||
1112
+ find_first_top_level_await_in_native_tsrx_statement(statement.alternate)
1113
+ );
1114
+ }
1115
+
1116
+ if (statement.type === 'SwitchStatement') {
1117
+ for (const switch_case of statement.cases || []) {
1118
+ const found = find_first_top_level_await_in_native_tsrx_statements(
1119
+ switch_case.consequent || [],
1120
+ );
1121
+ if (found) return found;
1122
+ }
1123
+ return null;
1124
+ }
1125
+
1126
+ if (statement.type === 'TryStatement') {
1127
+ return (
1128
+ find_first_top_level_await_in_native_tsrx_statement(statement.block) ||
1129
+ find_first_top_level_await_in_native_tsrx_statement(statement.handler?.body) ||
1130
+ find_first_top_level_await_in_native_tsrx_statement(statement.finalizer)
1131
+ );
1132
+ }
1133
+
1134
+ return null;
1135
+ }
1136
+
1137
+ /**
1138
+ * @param {any} node
1139
+ * @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
1140
+ * @returns {any}
1141
+ */
1142
+ function transform_function_with_hook_helpers(node, { next, state, path }) {
1143
+ const has_hook_bearing_tsrx = function_contains_hook_bearing_tsrx(node, state);
1144
+ const has_component_body_hook_split = function_needs_component_body_hook_split(node, state);
1145
+ if (
1146
+ state.helper_state ||
1147
+ !is_uppercase_function_like(node, path) ||
1148
+ (!has_hook_bearing_tsrx && !has_component_body_hook_split)
1149
+ ) {
1150
+ return ensure_function_metadata(node, { next });
1151
+ }
1152
+
1153
+ const helper_state = create_helper_state(get_function_helper_base_name(node, path));
1154
+ const saved_helper_state = state.helper_state;
1155
+ const saved_bindings = state.available_bindings;
1156
+ const saved_hook_helpers_enabled = state.hook_helpers_enabled;
1157
+
1158
+ state.helper_state = helper_state;
1159
+ state.hook_helpers_enabled = true;
1160
+ state.available_bindings = collect_function_scope_bindings(node);
1161
+
1162
+ if (has_component_body_hook_split) {
1163
+ rewrite_component_body_conditional_hook_splits(node, state);
1164
+ }
1165
+
1166
+ const inner = /** @type {any} */ (next() ?? node);
1167
+
1168
+ state.helper_state = saved_helper_state;
1169
+ state.available_bindings = saved_bindings;
1170
+ state.hook_helpers_enabled = saved_hook_helpers_enabled;
1171
+
1172
+ ensure_function_metadata(inner, { next: () => inner });
1173
+ if (helper_state.helpers.length || helper_state.statics.length) {
1174
+ inner.metadata = {
1175
+ ...(inner.metadata || {}),
1176
+ generated_helpers: helper_state.helpers,
1177
+ generated_statics: helper_state.statics,
1178
+ };
1179
+ }
1180
+
1181
+ return inner;
1182
+ }
1183
+
1184
+ /**
1185
+ * @param {any} node
1186
+ * @param {AST.Node[]} [path]
1187
+ * @returns {string}
1188
+ */
1189
+ function get_function_helper_base_name(node, path = []) {
1190
+ return get_function_like_name(node, path) || 'Tsrx';
1191
+ }
1192
+
1193
+ /**
1194
+ * @param {any} node
1195
+ * @param {AST.Node[]} path
1196
+ * @returns {boolean}
1197
+ */
1198
+ function is_uppercase_function_like(node, path) {
1199
+ const name = get_function_like_name(node, path);
1200
+ return !!(name && /^[A-Z]/.test(name));
1201
+ }
1202
+
1203
+ /**
1204
+ * @param {any} node
1205
+ * @param {AST.Node[]} path
1206
+ * @returns {string | null}
1207
+ */
1208
+ function get_function_like_name(node, path) {
1209
+ if (node.id?.type === 'Identifier') {
1210
+ return node.id.name;
1211
+ }
1212
+
1213
+ const parent = /** @type {any} */ (path.at(-1));
1214
+ if (!parent) return null;
1215
+
1216
+ if (parent.type === 'VariableDeclarator' && parent.init === node) {
1217
+ return get_static_binding_name(parent.id);
1218
+ }
1219
+
1220
+ if (parent.type === 'Property' && parent.value === node) {
1221
+ return get_static_property_name(parent.key);
1222
+ }
1223
+
1224
+ if (parent.type === 'MethodDefinition' && parent.value === node) {
1225
+ return get_static_property_name(parent.key);
1226
+ }
1227
+
1228
+ if (parent.type === 'AssignmentExpression' && parent.right === node) {
1229
+ return get_static_binding_name(parent.left);
1230
+ }
1231
+
1232
+ return null;
1233
+ }
1234
+
1235
+ /**
1236
+ * @param {any} node
1237
+ * @returns {string | null}
1238
+ */
1239
+ function get_static_binding_name(node) {
1240
+ if (node?.type === 'Identifier') {
1241
+ return node.name;
1242
+ }
1243
+ if (node?.type === 'MemberExpression' && !node.computed) {
1244
+ return get_static_property_name(node.property);
1245
+ }
1246
+ return null;
1247
+ }
1248
+
1249
+ /**
1250
+ * @param {any} key
1251
+ * @returns {string | null}
1252
+ */
1253
+ function get_static_property_name(key) {
1254
+ if (key?.type === 'Identifier') {
1255
+ return key.name;
1256
+ }
1257
+ if (key?.type === 'Literal' && typeof key.value === 'string') {
1258
+ return key.value;
1259
+ }
1260
+ return null;
1261
+ }
1262
+
1263
+ /**
1264
+ * @param {any} node
1265
+ * @returns {Map<string, AST.Identifier>}
1266
+ */
1267
+ function collect_function_scope_bindings(node) {
1268
+ const bindings = collect_param_bindings(node.params || []);
1269
+ if (node.body?.type === 'BlockStatement') {
1270
+ for (const statement of node.body.body || []) {
1271
+ if (statement.type === 'ReturnStatement' && statement.argument?.type === 'Tsrx') {
1272
+ for (const child of get_tsrx_render_children(statement.argument)) {
1273
+ collect_statement_bindings(child, bindings);
1274
+ }
1275
+ } else {
1276
+ collect_statement_bindings(statement, bindings);
1277
+ }
1278
+ }
1279
+ }
1280
+ return bindings;
1281
+ }
1282
+
1283
+ /**
1284
+ * @param {Map<string, AST.Identifier>} outer
1285
+ * @param {Map<string, AST.Identifier>} inner
1286
+ * @returns {Map<string, AST.Identifier>}
1287
+ */
1288
+ function merge_binding_maps(outer, inner) {
1289
+ const merged = new Map(outer);
1290
+ for (const [name, binding] of inner) {
1291
+ merged.set(name, binding);
1292
+ }
1293
+ return merged;
1294
+ }
1295
+
1296
+ /**
1297
+ * @param {any} node
1298
+ * @returns {boolean}
1299
+ */
1300
+ function function_has_native_tsrx_return(node) {
1301
+ if (!node) return false;
1302
+
1303
+ if (node.type === 'ArrowFunctionExpression' && node.body?.type !== 'BlockStatement') {
1304
+ return node_contains_native_tsrx_template(node.body);
1305
+ }
1306
+
1307
+ const body = node.body?.type === 'BlockStatement' ? node.body.body : [];
1308
+ return statements_contain_native_tsrx_return(body);
1309
+ }
1310
+
1311
+ /**
1312
+ * @param {any[]} statements
1313
+ * @returns {boolean}
1314
+ */
1315
+ function statements_contain_native_tsrx_return(statements) {
1316
+ return statements.some((statement) => statement_contains_native_tsrx_return(statement));
1317
+ }
1318
+
1319
+ /**
1320
+ * @param {any} statement
1321
+ * @returns {boolean}
1322
+ */
1323
+ function statement_contains_native_tsrx_return(statement) {
1324
+ if (!statement || typeof statement !== 'object') return false;
1325
+
1326
+ if (statement.type === 'ReturnStatement') {
1327
+ return node_contains_native_tsrx_template(statement.argument);
1328
+ }
1329
+
1330
+ if (is_function_or_class_boundary(statement)) {
1331
+ return false;
1332
+ }
1333
+
1334
+ if (statement.type === 'BlockStatement') {
1335
+ return statements_contain_native_tsrx_return(statement.body || []);
1336
+ }
1337
+
1338
+ if (statement.type === 'IfStatement') {
1339
+ return (
1340
+ statement_contains_native_tsrx_return(statement.consequent) ||
1341
+ statement_contains_native_tsrx_return(statement.alternate)
1342
+ );
1343
+ }
1344
+
1345
+ if (statement.type === 'SwitchStatement') {
1346
+ return (statement.cases || []).some((/** @type {any} */ c) =>
1347
+ statements_contain_native_tsrx_return(c.consequent || []),
1348
+ );
1349
+ }
1350
+
1351
+ if (statement.type === 'TryStatement') {
1352
+ return (
1353
+ statement_contains_native_tsrx_return(statement.block) ||
1354
+ statement_contains_native_tsrx_return(statement.handler?.body) ||
1355
+ statement_contains_native_tsrx_return(statement.finalizer)
1356
+ );
1357
+ }
1358
+
1359
+ for (const key of Object.keys(statement)) {
1360
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1361
+ continue;
1362
+ }
1363
+ const value = statement[key];
1364
+ if (Array.isArray(value)) {
1365
+ if (statements_contain_native_tsrx_return(value)) return true;
1366
+ } else if (statement_contains_native_tsrx_return(value)) {
1367
+ return true;
1368
+ }
1369
+ }
1370
+
1371
+ return false;
1372
+ }
1373
+
1374
+ /**
1375
+ * @param {any} node
1376
+ * @returns {boolean}
1377
+ */
1378
+ function node_contains_native_tsrx_template(node) {
1379
+ if (!node || typeof node !== 'object') return false;
1380
+ if (node.type === 'Element' || node.type === 'Tsrx') return true;
1381
+
1382
+ if (is_function_or_class_boundary(node)) {
1383
+ return false;
1384
+ }
1385
+
1386
+ if (Array.isArray(node)) {
1387
+ return node.some(node_contains_native_tsrx_template);
1388
+ }
1389
+
1390
+ for (const key of Object.keys(node)) {
1391
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1392
+ continue;
1393
+ }
1394
+ if (node_contains_native_tsrx_template(node[key])) {
1395
+ return true;
1396
+ }
1397
+ }
1398
+
1399
+ return false;
1400
+ }
1401
+
1402
+ /**
1403
+ * @param {any} node
1404
+ * @returns {any}
1405
+ */
1406
+ function collect_tsrx_stylesheet(node) {
1407
+ /** @type {any[]} */
1408
+ const styles = [];
1409
+ collect_style_elements(node.children || [], styles);
1410
+
1411
+ if (styles.length === 0) return null;
1412
+ if (styles.length > 1) {
1413
+ throw new Error('TSRX fragments can only have one style tag');
1414
+ }
1415
+
1416
+ return styles[0];
1417
+ }
1418
+
1419
+ /**
1420
+ * @param {any} node
1421
+ * @param {TransformContext} transform_context
1422
+ * @returns {{ css: any, style_refs: any[] } | null}
1423
+ */
1424
+ function prepare_tsrx_fragment_styles(node, transform_context) {
1425
+ const css = collect_tsrx_stylesheet(node);
1426
+ if (!css) return null;
1427
+
1428
+ const style_refs = collect_style_ref_attributes(node);
1429
+ apply_css_definition_metadata(node, css, style_refs.length > 0);
1430
+ transform_context.stylesheets.push(css);
1431
+ annotate_tsrx_with_hash(
1432
+ node,
1433
+ css.hash,
1434
+ transform_context.platform.jsx.classAttrName ??
1435
+ (transform_context.platform.jsx.rewriteClassAttr ? 'className' : 'class'),
1436
+ transform_context.typeOnly,
1437
+ );
1438
+ return { css, style_refs };
1439
+ }
1440
+
1441
+ /**
1442
+ * @template T
1443
+ * @param {any} node
1444
+ * @param {TransformContext} transform_context
1445
+ * @param {(style_context: { css: any, style_refs: any[] } | null) => T} callback
1446
+ * @returns {T}
1447
+ */
1448
+ function with_tsrx_fragment_styles(node, transform_context, callback) {
1449
+ const style_context = prepare_tsrx_fragment_styles(node, transform_context);
1450
+ return callback(style_context);
1451
+ }
1452
+
1453
+ /**
1454
+ * @param {any} fragment
1455
+ * @param {{ css: any, style_refs: any[] } | null} style_context
1456
+ * @param {TransformContext} transform_context
1457
+ * @returns {AST.Statement[]}
1458
+ */
1459
+ function create_tsrx_style_ref_setup_statements(fragment, style_context, transform_context) {
1460
+ if (!style_context || style_context.style_refs.length === 0) {
1461
+ return [];
1462
+ }
1463
+
1464
+ return create_style_ref_setup_statements(
1465
+ style_context.style_refs,
1466
+ create_style_class_map(fragment, style_context.css),
1467
+ {
1468
+ allowMutableRefTarget: transform_context.platform.jsx.multiRefStrategy === 'array',
1469
+ createTempIdentifier: () =>
1470
+ create_generated_identifier(create_style_ref_temp_name(transform_context)),
1471
+ },
1472
+ );
1473
+ }
1474
+
1475
+ /**
1476
+ * @param {TransformContext} transform_context
1477
+ * @returns {string}
1478
+ */
1479
+ function create_style_ref_temp_name(transform_context) {
1480
+ if (transform_context.helper_state) {
1481
+ return create_helper_name(transform_context.helper_state, 'style_ref');
1482
+ }
1483
+
1484
+ transform_context.local_statement_component_index += 1;
1485
+ return `_tsrx_style_ref_${transform_context.local_statement_component_index}`;
1486
+ }
1487
+
1488
+ /**
1489
+ * @param {any} node
1490
+ * @param {any[]} styles
1491
+ * @returns {void}
1492
+ */
1493
+ function collect_style_elements(node, styles) {
1494
+ if (!node || typeof node !== 'object') return;
1495
+
1496
+ if (Array.isArray(node)) {
1497
+ for (const child of node) {
1498
+ collect_style_elements(child, styles);
1499
+ }
1500
+ return;
1501
+ }
1502
+
1503
+ if (is_style_element(node)) {
1504
+ const stylesheet = node.children?.find(
1505
+ (/** @type {any} */ child) => child.type === 'StyleSheet',
1506
+ );
1507
+ if (stylesheet) {
1508
+ styles.push(stylesheet);
1509
+ }
1510
+ return;
1511
+ }
1512
+
1513
+ if (is_function_or_class_boundary(node) || node.type === 'Tsrx') {
1514
+ return;
1515
+ }
1516
+
1517
+ if (node.type === 'Element') {
1518
+ collect_style_elements(node.children || [], styles);
1519
+ return;
1520
+ }
1521
+
1522
+ if (node.type === 'BlockStatement') {
1523
+ collect_style_elements(node.body || [], styles);
1524
+ return;
1525
+ }
1526
+
1527
+ if (node.type === 'IfStatement') {
1528
+ collect_style_elements(node.consequent, styles);
1529
+ collect_style_elements(node.alternate, styles);
1530
+ return;
1531
+ }
1532
+
1533
+ if (node.type === 'SwitchStatement') {
1534
+ for (const switch_case of node.cases || []) {
1535
+ collect_style_elements(switch_case.consequent || [], styles);
1536
+ }
1537
+ return;
1538
+ }
1539
+
1540
+ if (node.type === 'TryStatement') {
1541
+ collect_style_elements(node.block, styles);
1542
+ collect_style_elements(node.handler?.body, styles);
1543
+ collect_style_elements(node.finalizer, styles);
1544
+ }
1545
+ }
1546
+
1547
+ /**
1548
+ * @param {any} node
1549
+ * @param {string} hash
1550
+ * @param {'class' | 'className'} jsx_class_attr_name
1551
+ * @param {boolean} preserve_style_elements
1552
+ * @returns {void}
1553
+ */
1554
+ function annotate_tsrx_with_hash(node, hash, jsx_class_attr_name, preserve_style_elements) {
1555
+ node.children = (node.children || []).map((/** @type {any} */ statement) =>
1556
+ annotate_with_hash(statement, hash, jsx_class_attr_name, preserve_style_elements),
1557
+ );
1558
+ if (!preserve_style_elements) {
1559
+ node.children = strip_style_elements(node.children);
1560
+ }
1561
+ }
1562
+
1563
+ /**
1564
+ * @param {any} node
1565
+ * @returns {any}
1566
+ */
1567
+ function strip_style_elements(node) {
1568
+ if (!node || typeof node !== 'object') return node;
1569
+
1570
+ if (Array.isArray(node)) {
1571
+ return node
1572
+ .filter((child) => !is_style_element(child))
1573
+ .map((child) => strip_style_elements(child))
1574
+ .filter(Boolean);
1575
+ }
1576
+
1577
+ if (is_style_element(node)) {
1578
+ return null;
1579
+ }
1580
+
1581
+ if (is_function_or_class_boundary(node)) {
1582
+ return node;
1583
+ }
1584
+
1585
+ if (node.type === 'Element') {
1586
+ node.children = strip_style_elements(node.children || []);
1587
+ return node;
1588
+ }
1589
+
1590
+ if (node.type === 'BlockStatement') {
1591
+ node.body = strip_style_elements(node.body || []);
1592
+ return node;
1593
+ }
1594
+
1595
+ if (node.type === 'IfStatement') {
1596
+ node.consequent = strip_style_elements(node.consequent);
1597
+ if (node.alternate) node.alternate = strip_style_elements(node.alternate);
1598
+ return node;
1599
+ }
1600
+
1601
+ if (node.type === 'SwitchStatement') {
1602
+ for (const switch_case of node.cases || []) {
1603
+ switch_case.consequent = strip_style_elements(switch_case.consequent || []);
1604
+ }
1605
+ return node;
1606
+ }
1607
+
1608
+ if (node.type === 'TryStatement') {
1609
+ node.block = strip_style_elements(node.block);
1610
+ if (node.handler?.body) node.handler.body = strip_style_elements(node.handler.body);
1611
+ if (node.finalizer) node.finalizer = strip_style_elements(node.finalizer);
1612
+ }
1613
+
1614
+ return node;
1615
+ }
1616
+
1617
+ /**
1618
+ * @param {any[]} path
1619
+ * @returns {boolean}
1620
+ */
1621
+ function is_style_expression_position(path) {
1622
+ const parent = path.at(-1);
1623
+ return !(
1624
+ parent?.type === 'Element' ||
1625
+ parent?.type === 'Tsrx' ||
1626
+ parent?.type === 'Tsx' ||
1627
+ parent?.type === 'TsxCompat' ||
1628
+ parent?.type === 'BlockStatement' ||
1629
+ parent?.type === 'Program' ||
1630
+ parent?.type === 'SwitchCase'
1281
1631
  );
1282
-
1283
- return mapWrapper ? set_loc(element, source_node) : element;
1284
1632
  }
1285
1633
 
1286
1634
  /**
1287
- * @param {{ base_name: string, next_id: number, helpers: any[], statics: any[] }} helper_state
1288
- * @param {string} suffix
1289
- * @returns {string}
1635
+ * @param {any} node
1636
+ * @param {TransformContext} transform_context
1637
+ * @returns {void}
1290
1638
  */
1291
- function create_helper_name(helper_state, suffix) {
1292
- helper_state.next_id += 1;
1293
- return `${helper_state.base_name}__${suffix}${helper_state.next_id}`;
1639
+ function expand_native_tsrx_function_returns(node, transform_context) {
1640
+ if (node.type === 'ArrowFunctionExpression' && node.body?.type === 'Tsrx') {
1641
+ const body = node.body;
1642
+ const statements = with_tsrx_fragment_styles(body, transform_context, (style_context) => {
1643
+ return [
1644
+ ...create_tsrx_style_ref_setup_statements(body, style_context, transform_context),
1645
+ ...build_render_statements(get_tsrx_render_children(body), true, transform_context),
1646
+ ];
1647
+ });
1648
+ node.body = b.block(mark_native_pretransformed_jsx(statements), body);
1649
+ node.expression = false;
1650
+ return;
1651
+ }
1652
+
1653
+ if (node.body?.type !== 'BlockStatement') {
1654
+ return;
1655
+ }
1656
+
1657
+ node.body.body = expand_native_tsrx_return_statement_list(
1658
+ node.body.body || [],
1659
+ transform_context,
1660
+ );
1294
1661
  }
1295
1662
 
1296
1663
  /**
1297
- * @param {string} base_name
1298
- * @returns {{ base_name: string, next_id: number, helpers: any[], statics: any[] }}
1664
+ * @param {any[]} statements
1665
+ * @param {TransformContext} transform_context
1666
+ * @returns {any[]}
1299
1667
  */
1300
- function create_helper_state(base_name) {
1301
- return {
1302
- base_name,
1303
- next_id: 0,
1304
- helpers: [],
1305
- statics: [],
1306
- };
1668
+ function expand_native_tsrx_return_statement_list(statements, transform_context) {
1669
+ return statements.flatMap((statement) =>
1670
+ expand_native_tsrx_return_statement(statement, transform_context),
1671
+ );
1307
1672
  }
1308
1673
 
1309
1674
  /**
1310
- * @param {any} node
1311
- * @param {{ next: () => any, state: TransformContext }} context
1312
- * @returns {any}
1675
+ * @param {any} statement
1676
+ * @param {TransformContext} transform_context
1677
+ * @returns {any[]}
1313
1678
  */
1314
- function transform_function_with_hook_helpers(node, { next, state }) {
1315
- if (state.helper_state || !function_contains_hook_bearing_tsrx(node, state)) {
1316
- return ensure_function_metadata(node, { next });
1679
+ function expand_native_tsrx_return_statement(statement, transform_context) {
1680
+ if (!statement || typeof statement !== 'object') return [statement];
1681
+
1682
+ if (statement.type === 'ReturnStatement' && statement.argument?.type === 'Tsrx') {
1683
+ const fragment = statement.argument;
1684
+ return with_tsrx_fragment_styles(fragment, transform_context, (style_context) => {
1685
+ return mark_native_pretransformed_jsx([
1686
+ ...create_tsrx_style_ref_setup_statements(fragment, style_context, transform_context),
1687
+ ...build_render_statements(get_tsrx_render_children(fragment), true, transform_context),
1688
+ ]);
1689
+ });
1317
1690
  }
1318
1691
 
1319
- const helper_state = create_helper_state(get_function_helper_base_name(node));
1320
- const saved_helper_state = state.helper_state;
1321
- const saved_bindings = state.available_bindings;
1692
+ if (is_function_or_class_boundary(statement)) {
1693
+ return [statement];
1694
+ }
1322
1695
 
1323
- state.helper_state = helper_state;
1324
- state.available_bindings = collect_function_scope_bindings(node);
1696
+ if (statement.type === 'BlockStatement') {
1697
+ statement.body = expand_native_tsrx_return_statement_list(
1698
+ statement.body || [],
1699
+ transform_context,
1700
+ );
1701
+ return [statement];
1702
+ }
1325
1703
 
1326
- const inner = /** @type {any} */ (next() ?? node);
1704
+ if (statement.type === 'IfStatement') {
1705
+ statement.consequent = expand_embedded_native_return_statement(
1706
+ statement.consequent,
1707
+ transform_context,
1708
+ );
1709
+ if (statement.alternate) {
1710
+ statement.alternate = expand_embedded_native_return_statement(
1711
+ statement.alternate,
1712
+ transform_context,
1713
+ );
1714
+ }
1715
+ return [statement];
1716
+ }
1327
1717
 
1328
- state.helper_state = saved_helper_state;
1329
- state.available_bindings = saved_bindings;
1718
+ if (statement.type === 'SwitchStatement') {
1719
+ for (const switch_case of statement.cases || []) {
1720
+ switch_case.consequent = expand_native_tsrx_return_statement_list(
1721
+ switch_case.consequent || [],
1722
+ transform_context,
1723
+ );
1724
+ }
1725
+ return [statement];
1726
+ }
1330
1727
 
1331
- ensure_function_metadata(inner, { next: () => inner });
1332
- if (helper_state.helpers.length || helper_state.statics.length) {
1333
- inner.metadata = {
1334
- ...(inner.metadata || {}),
1335
- generated_helpers: helper_state.helpers,
1336
- generated_statics: helper_state.statics,
1337
- };
1728
+ if (statement.type === 'TryStatement') {
1729
+ statement.block = expand_embedded_native_return_statement(statement.block, transform_context);
1730
+ if (statement.handler?.body) {
1731
+ statement.handler.body = expand_embedded_native_return_statement(
1732
+ statement.handler.body,
1733
+ transform_context,
1734
+ );
1735
+ }
1736
+ if (statement.finalizer) {
1737
+ statement.finalizer = expand_embedded_native_return_statement(
1738
+ statement.finalizer,
1739
+ transform_context,
1740
+ );
1741
+ }
1742
+ return [statement];
1338
1743
  }
1339
1744
 
1340
- return inner;
1745
+ return [statement];
1341
1746
  }
1342
1747
 
1343
1748
  /**
1344
- * @param {any} node
1345
- * @returns {string}
1749
+ * @param {any} statement
1750
+ * @param {TransformContext} transform_context
1751
+ * @returns {any}
1346
1752
  */
1347
- function get_function_helper_base_name(node) {
1348
- if (node.id?.type === 'Identifier') {
1349
- return node.id.name;
1753
+ function expand_embedded_native_return_statement(statement, transform_context) {
1754
+ const expanded = expand_native_tsrx_return_statement(statement, transform_context);
1755
+ return expanded.length === 1 ? expanded[0] : b.block(expanded, statement);
1756
+ }
1757
+
1758
+ /**
1759
+ * @template T
1760
+ * @param {T} node
1761
+ * @param {Set<any>} [seen]
1762
+ * @returns {T}
1763
+ */
1764
+ function mark_native_pretransformed_jsx(node, seen = new Set()) {
1765
+ if (node == null || typeof node !== 'object' || seen.has(node)) {
1766
+ return node;
1767
+ }
1768
+ seen.add(node);
1769
+
1770
+ if (Array.isArray(node)) {
1771
+ for (const item of node) mark_native_pretransformed_jsx(item, seen);
1772
+ return node;
1773
+ }
1774
+
1775
+ const as_node = /** @type {any} */ (node);
1776
+ if (as_node.type === 'JSXOpeningElement') {
1777
+ as_node.metadata = {
1778
+ ...(as_node.metadata || {}),
1779
+ native_tsrx_pretransformed: true,
1780
+ };
1781
+ }
1782
+
1783
+ for (const key of Object.keys(as_node)) {
1784
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1785
+ continue;
1786
+ }
1787
+ mark_native_pretransformed_jsx(as_node[key], seen);
1350
1788
  }
1351
- return 'Tsrx';
1789
+
1790
+ return node;
1352
1791
  }
1353
1792
 
1354
1793
  /**
1355
1794
  * @param {any} node
1356
- * @returns {Map<string, AST.Identifier>}
1795
+ * @returns {any[]}
1357
1796
  */
1358
- function collect_function_scope_bindings(node) {
1359
- const bindings = collect_param_bindings(node.params || []);
1360
- collect_descendant_declaration_bindings(node.body, bindings);
1361
- return bindings;
1797
+ function get_tsrx_render_children(node) {
1798
+ return (node.children || []).filter(
1799
+ (/** @type {any} */ child) =>
1800
+ child &&
1801
+ child.type !== 'EmptyStatement' &&
1802
+ (child.type !== 'JSXText' || child.value.trim() !== ''),
1803
+ );
1362
1804
  }
1363
1805
 
1364
1806
  /**
@@ -1387,8 +1829,7 @@ function collect_descendant_declaration_bindings(node, bindings) {
1387
1829
  if (
1388
1830
  node.type === 'FunctionDeclaration' ||
1389
1831
  node.type === 'FunctionExpression' ||
1390
- node.type === 'ArrowFunctionExpression' ||
1391
- node.type === 'Component'
1832
+ node.type === 'ArrowFunctionExpression'
1392
1833
  ) {
1393
1834
  return;
1394
1835
  }
@@ -1438,8 +1879,7 @@ function node_contains_hook_bearing_tsrx(node, transform_context) {
1438
1879
  if (
1439
1880
  node.type === 'FunctionDeclaration' ||
1440
1881
  node.type === 'FunctionExpression' ||
1441
- node.type === 'ArrowFunctionExpression' ||
1442
- node.type === 'Component'
1882
+ node.type === 'ArrowFunctionExpression'
1443
1883
  ) {
1444
1884
  return false;
1445
1885
  }
@@ -1464,6 +1904,14 @@ function should_use_module_scoped_hook_components(transform_context) {
1464
1904
  return !!(transform_context.helper_state && transform_context.module_scoped_hook_components);
1465
1905
  }
1466
1906
 
1907
+ /**
1908
+ * @param {TransformContext} transform_context
1909
+ * @returns {boolean}
1910
+ */
1911
+ function should_extract_hook_helpers(transform_context) {
1912
+ return !!transform_context.hook_helpers_enabled;
1913
+ }
1914
+
1467
1915
  /**
1468
1916
  * @param {AST.Identifier} helper_id
1469
1917
  * @param {TransformContext} transform_context
@@ -1471,7 +1919,7 @@ function should_use_module_scoped_hook_components(transform_context) {
1471
1919
  */
1472
1920
  function create_module_scoped_hook_component_id(helper_id, transform_context) {
1473
1921
  return create_generated_identifier(
1474
- `${transform_context.helper_state?.base_name || 'Component'}__${helper_id.name}`,
1922
+ `${transform_context.helper_state?.base_name || 'Tsrx'}__${helper_id.name}`,
1475
1923
  );
1476
1924
  }
1477
1925
 
@@ -1670,59 +2118,6 @@ function is_bare_component_invocation(node) {
1670
2118
  return is_component_jsx_name(opening.name);
1671
2119
  }
1672
2120
 
1673
- /**
1674
- * Static JSX that appears before multiple early-return guards is otherwise
1675
- * cloned into every generated return. Capture it once at its source position
1676
- * and reuse the reference, matching the interleaved-statement capture path
1677
- * without moving dynamic render-time expressions across guards.
1678
- *
1679
- * @param {any[]} render_nodes
1680
- * @param {any[]} statements
1681
- * @param {number} capture_index
1682
- * @param {TransformContext} transform_context
1683
- * @returns {number}
1684
- */
1685
- function capture_static_early_return_render_nodes(
1686
- render_nodes,
1687
- statements,
1688
- capture_index,
1689
- transform_context,
1690
- ) {
1691
- for (let i = 0; i < render_nodes.length; i += 1) {
1692
- const node = render_nodes[i];
1693
- if (!is_static_early_return_capture_node(node, transform_context)) {
1694
- continue;
1695
- }
1696
-
1697
- const { declaration, reference } = captureJsxChild(node, capture_index++);
1698
- statements.push(declaration);
1699
- render_nodes[i] = reference;
1700
- }
1701
-
1702
- return capture_index;
1703
- }
1704
-
1705
- /**
1706
- * @param {any} node
1707
- * @param {TransformContext} transform_context
1708
- * @returns {boolean}
1709
- */
1710
- function is_static_early_return_capture_node(node, transform_context) {
1711
- if (node?.type !== 'JSXElement' && node?.type !== 'JSXFragment') {
1712
- return false;
1713
- }
1714
- if (!is_hoist_safe_jsx_node(node)) {
1715
- return false;
1716
- }
1717
- if (
1718
- transform_context.platform.hooks?.canHoistStaticNode &&
1719
- !transform_context.platform.hooks.canHoistStaticNode(node, transform_context)
1720
- ) {
1721
- return false;
1722
- }
1723
- return !references_scope_bindings(node, transform_context.available_bindings);
1724
- }
1725
-
1726
2121
  /**
1727
2122
  * @param {AST.Program} program
1728
2123
  * @returns {AST.Program}
@@ -1743,11 +2138,9 @@ function expand_component_helpers(program) {
1743
2138
  }
1744
2139
 
1745
2140
  /**
1746
- * Component hooks may replace a `Component` node with a function declaration,
1747
- * variable declaration, object literal member, or export-safe expression.
1748
- * Generated helper/statics metadata is carried on whichever replacement node
1749
- * the hook returns, so helper expansion must read metadata from that broader
1750
- * set.
2141
+ * Generated helper/statics metadata can be carried on function declarations,
2142
+ * variable declarations, object literal members, or export-safe expressions,
2143
+ * so helper expansion reads metadata from that broader set.
1751
2144
  *
1752
2145
  * @param {any} node
1753
2146
  * @returns {{ generated_helpers?: any[], generated_statics?: any[] }[]}
@@ -1803,112 +2196,164 @@ function get_generated_component_metadata_list(node) {
1803
2196
  return metas;
1804
2197
  }
1805
2198
 
2199
+ /**
2200
+ * @param {any[]} render_nodes
2201
+ * @param {any} source_node
2202
+ * @param {boolean} [map_render_node_locations]
2203
+ * @returns {any}
2204
+ */
2205
+ function create_component_return_statement(
2206
+ render_nodes,
2207
+ source_node,
2208
+ map_render_node_locations = true,
2209
+ ) {
2210
+ const cloned = render_nodes.map((node) =>
2211
+ map_render_node_locations ? clone_expression_node(node) : clone_expression_node(node, false),
2212
+ );
2213
+
2214
+ return set_loc(b.return(build_return_expression(cloned) || create_null_literal()), source_node);
2215
+ }
2216
+
1806
2217
  /**
1807
2218
  * @param {any} node
1808
2219
  * @returns {boolean}
1809
2220
  */
1810
- function is_bare_return_statement(node) {
1811
- return node?.type === 'ReturnStatement' && node.argument == null;
2221
+ function is_loop_skip_return_statement(node) {
2222
+ return (
2223
+ node?.type === 'ReturnStatement' &&
2224
+ node.argument == null &&
2225
+ node.metadata?.generated_loop_continue_return === true
2226
+ );
1812
2227
  }
1813
2228
 
1814
2229
  /**
1815
2230
  * @param {any} node
1816
2231
  * @returns {boolean}
1817
2232
  */
1818
- function is_lone_return_if_statement(node) {
2233
+ function is_loop_skip_if_statement(node) {
2234
+ return get_loop_skip_if_consequent_body(node) !== null;
2235
+ }
2236
+
2237
+ /**
2238
+ * @param {any} node
2239
+ * @returns {any[] | null}
2240
+ */
2241
+ function get_loop_skip_if_consequent_body(node) {
1819
2242
  if (node?.type !== 'IfStatement' || node.alternate) {
1820
- return false;
2243
+ return null;
1821
2244
  }
1822
2245
 
1823
- const consequent_body = get_if_consequent_body(node);
2246
+ const consequent_body =
2247
+ node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
1824
2248
 
1825
- return consequent_body.length === 1 && is_bare_return_statement(consequent_body[0]);
2249
+ return consequent_body.some(is_loop_skip_return_statement) ? consequent_body : null;
1826
2250
  }
1827
2251
 
1828
2252
  /**
1829
2253
  * @param {any} node
1830
- * @returns {boolean}
2254
+ * @param {any[]} render_nodes
2255
+ * @param {TransformContext} transform_context
2256
+ * @returns {any}
1831
2257
  */
1832
- function is_returning_if_statement(node) {
1833
- if (node?.type !== 'IfStatement' || node.alternate) {
1834
- return false;
1835
- }
2258
+ function create_component_loop_skip_if_statement(node, render_nodes, transform_context) {
2259
+ const consequent_body = /** @type {any[]} */ (get_loop_skip_if_consequent_body(node));
2260
+ const branch_statements = build_render_statements(consequent_body, true, transform_context);
2261
+ prepend_render_nodes_to_return_statements(branch_statements, render_nodes);
1836
2262
 
1837
- return get_if_consequent_body(node).some(is_bare_return_statement);
2263
+ return set_loc(b.if(node.test, set_loc(b.block(branch_statements), node.consequent), null), node);
1838
2264
  }
1839
2265
 
1840
2266
  /**
1841
- * @param {any} node
1842
- * @returns {any[]}
2267
+ * @param {any[]} statements
2268
+ * @param {any[]} render_nodes
2269
+ * @returns {void}
1843
2270
  */
1844
- function get_if_consequent_body(node) {
1845
- return node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
2271
+ function prepend_render_nodes_to_return_statements(statements, render_nodes) {
2272
+ if (render_nodes.length === 0) {
2273
+ return;
2274
+ }
2275
+
2276
+ for (const statement of statements) {
2277
+ prepend_render_nodes_to_return_statement(statement, render_nodes, false);
2278
+ }
1846
2279
  }
1847
2280
 
1848
2281
  /**
2282
+ * @param {any} node
1849
2283
  * @param {any[]} render_nodes
1850
- * @param {any} source_node
1851
- * @param {boolean} [map_render_node_locations]
1852
- * @returns {any}
2284
+ * @param {boolean} inside_nested_function
2285
+ * @returns {void}
1853
2286
  */
1854
- function create_component_return_statement(
1855
- render_nodes,
1856
- source_node,
1857
- map_render_node_locations = true,
1858
- ) {
1859
- const cloned = render_nodes.map((node) =>
1860
- map_render_node_locations ? clone_expression_node(node) : clone_expression_node(node, false),
1861
- );
2287
+ function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nested_function) {
2288
+ if (!node || typeof node !== 'object') {
2289
+ return;
2290
+ }
2291
+
2292
+ if (
2293
+ node.type === 'FunctionDeclaration' ||
2294
+ node.type === 'FunctionExpression' ||
2295
+ node.type === 'ArrowFunctionExpression'
2296
+ ) {
2297
+ inside_nested_function = true;
2298
+ }
2299
+
2300
+ if (!inside_nested_function && node.type === 'ReturnStatement') {
2301
+ node.argument = combine_render_return_argument(render_nodes, node.argument);
2302
+ return;
2303
+ }
1862
2304
 
1863
- return set_loc(b.return(build_return_expression(cloned) || create_null_literal()), source_node);
2305
+ if (Array.isArray(node)) {
2306
+ for (const child of node) {
2307
+ prepend_render_nodes_to_return_statement(child, render_nodes, inside_nested_function);
2308
+ }
2309
+ return;
2310
+ }
2311
+
2312
+ for (const key of Object.keys(node)) {
2313
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2314
+ continue;
2315
+ }
2316
+ prepend_render_nodes_to_return_statement(node[key], render_nodes, inside_nested_function);
2317
+ }
1864
2318
  }
1865
2319
 
1866
2320
  /**
1867
- * @param {any} node
1868
2321
  * @param {any[]} render_nodes
2322
+ * @param {any} return_argument
1869
2323
  * @returns {any}
1870
2324
  */
1871
- function create_component_lone_return_if_statement(node, render_nodes) {
1872
- const consequent_body = get_if_consequent_body(node);
2325
+ function combine_render_return_argument(render_nodes, return_argument) {
2326
+ const combined = render_nodes.map((node) => clone_expression_node(node, false));
1873
2327
 
1874
- return set_loc(
1875
- b.if(
1876
- node.test,
1877
- set_loc(
1878
- b.block([create_component_return_statement(render_nodes, consequent_body[0], false)]),
1879
- node.consequent,
1880
- ),
1881
- null,
1882
- ),
1883
- node,
1884
- );
2328
+ if (return_argument != null && !is_null_literal(return_argument)) {
2329
+ combined.push(return_argument_to_render_node(return_argument));
2330
+ }
2331
+
2332
+ return build_return_expression(combined) || create_null_literal();
1885
2333
  }
1886
2334
 
1887
2335
  /**
1888
- * @param {any} node
1889
- * @param {any[]} render_nodes
1890
- * @param {TransformContext} transform_context
2336
+ * @param {any} argument
1891
2337
  * @returns {any}
1892
2338
  */
1893
- function create_component_returning_if_statement(node, render_nodes, transform_context) {
1894
- const consequent_body = get_if_consequent_body(node);
1895
- const branch_statements = build_render_statements(consequent_body, true, transform_context);
1896
- prepend_render_nodes_to_return_statements(branch_statements, render_nodes);
2339
+ function return_argument_to_render_node(argument) {
2340
+ if (
2341
+ argument?.type === 'JSXElement' ||
2342
+ argument?.type === 'JSXFragment' ||
2343
+ argument?.type === 'JSXExpressionContainer'
2344
+ ) {
2345
+ return argument;
2346
+ }
1897
2347
 
1898
- return set_loc(b.if(node.test, set_loc(b.block(branch_statements), node.consequent), null), node);
2348
+ return to_jsx_expression_container(argument);
1899
2349
  }
1900
2350
 
1901
2351
  /**
1902
- * Build a `return <combined-render-fragment>;` statement, prepending any
1903
- * `render_nodes` collected before the control-flow construct so they don't
1904
- * get dropped on the lift path.
1905
- *
1906
- * @param {any[]} render_nodes
1907
- * @param {any} jsx_child
1908
- * @returns {any}
2352
+ * @param {any} node
2353
+ * @returns {boolean}
1909
2354
  */
1910
- function combined_return_statement(render_nodes, jsx_child) {
1911
- return b.return(combine_render_return_argument(render_nodes, jsx_child));
2355
+ function is_null_literal(node) {
2356
+ return node?.type === 'Literal' && node.value == null;
1912
2357
  }
1913
2358
 
1914
2359
  /**
@@ -1932,51 +2377,6 @@ function build_array_normalization_decls(source_id, source_expr) {
1932
2377
  return { source_decl, source_normalize_decl };
1933
2378
  }
1934
2379
 
1935
- /**
1936
- * @param {any} node
1937
- * @param {any[]} continuation_body
1938
- * @param {any[]} render_nodes
1939
- * @param {TransformContext} transform_context
1940
- * @returns {any[]}
1941
- */
1942
- function create_component_helper_split_returning_if_statements(
1943
- node,
1944
- continuation_body,
1945
- render_nodes,
1946
- transform_context,
1947
- ) {
1948
- const consequent_body = get_if_consequent_body(node);
1949
- const return_index = consequent_body.findIndex(is_bare_return_statement);
1950
- const branch_body =
1951
- return_index === -1 ? consequent_body : consequent_body.slice(0, return_index);
1952
- const branch_helper = create_hook_safe_helper(
1953
- branch_body,
1954
- undefined,
1955
- node.consequent,
1956
- transform_context,
1957
- );
1958
- const continuation_helper = create_hook_safe_helper(
1959
- continuation_body,
1960
- undefined,
1961
- node,
1962
- transform_context,
1963
- );
1964
-
1965
- const branch_block = set_loc(
1966
- b.block([
1967
- ...branch_helper.setup_statements,
1968
- combined_return_statement(render_nodes, branch_helper.component_element),
1969
- ]),
1970
- node.consequent,
1971
- );
1972
-
1973
- return [
1974
- set_loc(b.if(node.test, branch_block, null), node),
1975
- ...continuation_helper.setup_statements,
1976
- combined_return_statement(render_nodes, continuation_helper.component_element),
1977
- ];
1978
- }
1979
-
1980
2380
  /**
1981
2381
  * Hoist the helper for a hook-bearing for-of body out of the iteration
1982
2382
  * callback so the helper is declared once per render rather than re-bound on
@@ -2088,7 +2488,7 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
2088
2488
  transform_context.available_bindings = fn_saved_bindings;
2089
2489
 
2090
2490
  const helper_fn = b.function(clone_identifier(component_id), params, b.block(fn_body_statements));
2091
- helper_fn.metadata = { path: [], is_component: true, is_method: false };
2491
+ helper_fn.metadata = { path: [], is_method: false };
2092
2492
 
2093
2493
  let helper_decl;
2094
2494
  if (transform_context.helper_state && use_module_scoped_component) {
@@ -2236,142 +2636,6 @@ function create_helper_props_type_literal_with_typeof_flags(bindings, aliases, u
2236
2636
  );
2237
2637
  }
2238
2638
 
2239
- /**
2240
- * @param {any} node
2241
- * @param {any[]} continuation_body
2242
- * @param {any[]} render_nodes
2243
- * @param {TransformContext} transform_context
2244
- * @returns {any[]}
2245
- */
2246
- function create_setup_once_helper_split_returning_if_statements(
2247
- node,
2248
- continuation_body,
2249
- render_nodes,
2250
- transform_context,
2251
- ) {
2252
- const consequent_body = get_if_consequent_body(node);
2253
- const return_index = consequent_body.findIndex(is_bare_return_statement);
2254
- const branch_body =
2255
- return_index === -1 ? consequent_body : consequent_body.slice(0, return_index);
2256
- const branch_helper = branch_body.length
2257
- ? create_hook_safe_helper(branch_body, undefined, node.consequent, transform_context)
2258
- : { setup_statements: [], component_element: create_null_literal() };
2259
- const continuation_helper = continuation_body.length
2260
- ? create_hook_safe_helper(continuation_body, undefined, node, transform_context)
2261
- : { setup_statements: [], component_element: create_null_literal() };
2262
-
2263
- return [
2264
- ...branch_helper.setup_statements,
2265
- ...continuation_helper.setup_statements,
2266
- b.return(
2267
- combine_render_return_argument(
2268
- render_nodes,
2269
- set_loc(
2270
- b.conditional(
2271
- node.test,
2272
- branch_helper.component_element,
2273
- continuation_helper.component_element,
2274
- ),
2275
- node,
2276
- ),
2277
- ),
2278
- ),
2279
- ];
2280
- }
2281
-
2282
- /**
2283
- * @param {any[]} statements
2284
- * @param {any[]} render_nodes
2285
- * @returns {void}
2286
- */
2287
- function prepend_render_nodes_to_return_statements(statements, render_nodes) {
2288
- if (render_nodes.length === 0) {
2289
- return;
2290
- }
2291
-
2292
- for (const statement of statements) {
2293
- prepend_render_nodes_to_return_statement(statement, render_nodes, false);
2294
- }
2295
- }
2296
-
2297
- /**
2298
- * @param {any} node
2299
- * @param {any[]} render_nodes
2300
- * @param {boolean} inside_nested_function
2301
- * @returns {void}
2302
- */
2303
- function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nested_function) {
2304
- if (!node || typeof node !== 'object') {
2305
- return;
2306
- }
2307
-
2308
- if (
2309
- node.type === 'FunctionDeclaration' ||
2310
- node.type === 'FunctionExpression' ||
2311
- node.type === 'ArrowFunctionExpression'
2312
- ) {
2313
- inside_nested_function = true;
2314
- }
2315
-
2316
- if (!inside_nested_function && node.type === 'ReturnStatement') {
2317
- node.argument = combine_render_return_argument(render_nodes, node.argument);
2318
- return;
2319
- }
2320
-
2321
- if (Array.isArray(node)) {
2322
- for (const child of node) {
2323
- prepend_render_nodes_to_return_statement(child, render_nodes, inside_nested_function);
2324
- }
2325
- return;
2326
- }
2327
-
2328
- for (const key of Object.keys(node)) {
2329
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2330
- continue;
2331
- }
2332
- prepend_render_nodes_to_return_statement(node[key], render_nodes, inside_nested_function);
2333
- }
2334
- }
2335
-
2336
- /**
2337
- * @param {any[]} render_nodes
2338
- * @param {any} return_argument
2339
- * @returns {any}
2340
- */
2341
- function combine_render_return_argument(render_nodes, return_argument) {
2342
- const combined = render_nodes.map((node) => clone_expression_node(node, false));
2343
-
2344
- if (return_argument != null && !is_null_literal(return_argument)) {
2345
- combined.push(return_argument_to_render_node(return_argument));
2346
- }
2347
-
2348
- return build_return_expression(combined) || create_null_literal();
2349
- }
2350
-
2351
- /**
2352
- * @param {any} argument
2353
- * @returns {any}
2354
- */
2355
- function return_argument_to_render_node(argument) {
2356
- if (
2357
- argument?.type === 'JSXElement' ||
2358
- argument?.type === 'JSXFragment' ||
2359
- argument?.type === 'JSXExpressionContainer'
2360
- ) {
2361
- return argument;
2362
- }
2363
-
2364
- return to_jsx_expression_container(argument);
2365
- }
2366
-
2367
- /**
2368
- * @param {any} node
2369
- * @returns {boolean}
2370
- */
2371
- function is_null_literal(node) {
2372
- return node?.type === 'Literal' && node.value == null;
2373
- }
2374
-
2375
2639
  /**
2376
2640
  * @param {any} node
2377
2641
  * @param {TransformContext} transform_context
@@ -2414,159 +2678,36 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
2414
2678
 
2415
2679
  if (child_transform) {
2416
2680
  children = child_transform.children;
2417
- if (typeof child_transform.selfClosing === 'boolean') {
2418
- selfClosing = child_transform.selfClosing;
2419
- }
2420
- } else {
2421
- const html_child_transform = rewrite_host_html_children(
2422
- node,
2423
- walked_children,
2424
- raw_children,
2425
- attributes,
2426
- transform_context,
2427
- );
2428
- if (html_child_transform) {
2429
- children = html_child_transform.children;
2430
- selfClosing = html_child_transform.selfClosing;
2431
- } else {
2432
- children = create_element_children(walked_children, transform_context);
2433
- }
2434
- }
2435
- const has_unmappable_attribute = attributes.some(
2436
- (/** @type {any} */ attribute) => attribute?.metadata?.has_unmappable_value,
2437
- );
2438
-
2439
- const opening_element_node = b.jsx_opening_element(
2440
- name,
2441
- attributes,
2442
- selfClosing,
2443
- node.openingElement?.typeArguments,
2444
- );
2445
- const openingElement = has_unmappable_attribute
2446
- ? opening_element_node
2447
- : set_loc(opening_element_node, node.openingElement || node);
2448
-
2449
- const closingElement = selfClosing
2450
- ? null
2451
- : set_loc(
2452
- b.jsx_closing_element(
2453
- clone_jsx_name(name, node.closingElement?.name || node.closingElement || node),
2454
- ),
2455
- node.closingElement || node,
2456
- );
2457
-
2458
- return set_loc(b.jsx_element_fresh(openingElement, closingElement, children), node);
2459
- }
2460
-
2461
- /**
2462
- * @param {any} node
2463
- * @param {any[]} walked_children
2464
- * @param {any[]} raw_children
2465
- * @param {any[]} attributes
2466
- * @param {TransformContext} transform_context
2467
- * @returns {{ children: any[]; selfClosing: boolean } | null}
2468
- */
2469
- export function rewrite_host_html_children(
2470
- node,
2471
- walked_children,
2472
- raw_children,
2473
- attributes,
2474
- transform_context,
2475
- ) {
2476
- const source_children = raw_children || walked_children;
2477
- const source_html_index = source_children.findIndex((child) => child?.type === 'Html');
2478
- if (source_html_index === -1) {
2479
- return null;
2480
- }
2481
- const source_html = source_children[source_html_index];
2482
- const walked_html =
2483
- walked_children[source_html_index]?.type === 'Html'
2484
- ? walked_children[source_html_index]
2485
- : source_html;
2486
-
2487
- if (is_component_like_element(node) || source_children.length !== 1) {
2488
- report_invalid_html_child_error(source_html, transform_context);
2489
- }
2490
-
2491
- const conflicting_attribute = get_host_html_conflicting_attribute(attributes, transform_context);
2492
- if (conflicting_attribute !== null) {
2493
- error(
2494
- create_host_html_conflict_error(conflicting_attribute, transform_context),
2495
- transform_context.filename,
2496
- source_html,
2497
- transform_context.errors,
2498
- transform_context.comments,
2499
- );
2500
- }
2501
-
2502
- attributes.push(create_host_html_attribute(walked_html, source_html, transform_context));
2503
-
2504
- return { children: [], selfClosing: true };
2505
- }
2506
-
2507
- /**
2508
- * @param {any[]} attributes
2509
- * @param {TransformContext} transform_context
2510
- * @returns {{ kind: 'attribute'; name: string } | null}
2511
- */
2512
- export function get_host_html_conflicting_attribute(attributes, transform_context) {
2513
- const conflicting_attributes = get_host_html_conflicting_attribute_names(transform_context);
2514
- for (const name of conflicting_attributes) {
2515
- if (has_jsx_attribute(attributes, name)) {
2516
- return { kind: 'attribute', name };
2681
+ if (typeof child_transform.selfClosing === 'boolean') {
2682
+ selfClosing = child_transform.selfClosing;
2517
2683
  }
2684
+ } else {
2685
+ children = create_element_children(walked_children, transform_context);
2518
2686
  }
2687
+ const has_unmappable_attribute = attributes.some(
2688
+ (/** @type {any} */ attribute) => attribute?.metadata?.has_unmappable_value,
2689
+ );
2519
2690
 
2520
- return null;
2521
- }
2522
-
2523
- /**
2524
- * @param {{ kind: 'attribute'; name: string }} conflicting_attribute
2525
- * @param {TransformContext} transform_context
2526
- * @returns {string}
2527
- */
2528
- export function create_host_html_conflict_error(conflicting_attribute, transform_context) {
2529
- const html_attribute = get_host_html_attribute_name(transform_context);
2530
- return `\`{html ...}\` lowers to \`${html_attribute}\` on the ${transform_context.platform.name} target and cannot be combined with an existing \`${conflicting_attribute.name}\` attribute.`;
2531
- }
2532
-
2533
- /**
2534
- * @param {TransformContext} transform_context
2535
- * @returns {string[]}
2536
- */
2537
- function get_host_html_conflicting_attribute_names(transform_context) {
2538
- switch (transform_context.platform.name) {
2539
- case 'Solid':
2540
- return ['innerHTML', 'textContent'];
2541
- case 'Vue':
2542
- return ['innerHTML'];
2543
- default:
2544
- return [get_host_html_attribute_name(transform_context)];
2545
- }
2546
- }
2691
+ const opening_element_node = b.jsx_opening_element(
2692
+ name,
2693
+ attributes,
2694
+ selfClosing,
2695
+ node.openingElement?.typeArguments,
2696
+ );
2697
+ const openingElement = has_unmappable_attribute
2698
+ ? opening_element_node
2699
+ : set_loc(opening_element_node, node.openingElement || node);
2547
2700
 
2548
- /**
2549
- * @param {TransformContext} transform_context
2550
- * @returns {'dangerouslySetInnerHTML' | 'innerHTML'}
2551
- */
2552
- function get_host_html_attribute_name(transform_context) {
2553
- return transform_context.platform.jsx?.htmlProp === 'dangerouslySetInnerHTML'
2554
- ? 'dangerouslySetInnerHTML'
2555
- : 'innerHTML';
2556
- }
2701
+ const closingElement = selfClosing
2702
+ ? null
2703
+ : set_loc(
2704
+ b.jsx_closing_element(
2705
+ clone_jsx_name(name, node.closingElement?.name || node.closingElement || node),
2706
+ ),
2707
+ node.closingElement || node,
2708
+ );
2557
2709
 
2558
- /**
2559
- * @param {any[]} attributes
2560
- * @param {string} name
2561
- * @returns {boolean}
2562
- */
2563
- function has_jsx_attribute(attributes, name) {
2564
- return attributes.some(
2565
- (attr) =>
2566
- attr?.type === 'JSXAttribute' &&
2567
- attr.name?.type === 'JSXIdentifier' &&
2568
- attr.name.name === name,
2569
- );
2710
+ return set_loc(b.jsx_element_fresh(openingElement, closingElement, children), node);
2570
2711
  }
2571
2712
 
2572
2713
  /**
@@ -2616,18 +2757,14 @@ function child_contains_return_semantics(node) {
2616
2757
  return false;
2617
2758
  }
2618
2759
 
2619
- if (
2620
- (node.type === 'ReturnStatement' && node.argument == null) ||
2621
- is_lone_return_if_statement(node)
2622
- ) {
2760
+ if (node.type === 'ReturnStatement') {
2623
2761
  return true;
2624
2762
  }
2625
2763
 
2626
2764
  if (
2627
2765
  node.type === 'FunctionDeclaration' ||
2628
2766
  node.type === 'FunctionExpression' ||
2629
- node.type === 'ArrowFunctionExpression' ||
2630
- node.type === 'Component'
2767
+ node.type === 'ArrowFunctionExpression'
2631
2768
  ) {
2632
2769
  return false;
2633
2770
  }
@@ -2662,7 +2799,10 @@ function is_inline_element_child(node) {
2662
2799
  * @returns {ESTreeJSX.JSXExpressionContainer}
2663
2800
  */
2664
2801
  function statement_body_to_jsx_child(body_nodes, transform_context) {
2665
- if (body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
2802
+ if (
2803
+ should_extract_hook_helpers(transform_context) &&
2804
+ body_contains_top_level_hook_call(body_nodes, transform_context, true)
2805
+ ) {
2666
2806
  return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
2667
2807
  }
2668
2808
 
@@ -3223,8 +3363,7 @@ function references_name_in_set(node, names) {
3223
3363
  if (
3224
3364
  node.type === 'FunctionDeclaration' ||
3225
3365
  node.type === 'FunctionExpression' ||
3226
- node.type === 'ArrowFunctionExpression' ||
3227
- node.type === 'Component'
3366
+ node.type === 'ArrowFunctionExpression'
3228
3367
  ) {
3229
3368
  return false;
3230
3369
  }
@@ -3369,8 +3508,7 @@ function find_first_hook_call_name(node) {
3369
3508
  if (
3370
3509
  node.type === 'FunctionDeclaration' ||
3371
3510
  node.type === 'FunctionExpression' ||
3372
- node.type === 'ArrowFunctionExpression' ||
3373
- node.type === 'Component'
3511
+ node.type === 'ArrowFunctionExpression'
3374
3512
  ) {
3375
3513
  return null;
3376
3514
  }
@@ -3477,7 +3615,6 @@ export function create_hook_safe_helper(
3477
3615
  params,
3478
3616
  b.block(build_render_statements(body_nodes, true, transform_context)),
3479
3617
  );
3480
- helper_fn.metadata.is_component = true;
3481
3618
  helper_fn.metadata.is_method = false;
3482
3619
 
3483
3620
  transform_context.available_bindings = saved_bindings;
@@ -3697,108 +3834,6 @@ function get_body_source_node(body_nodes) {
3697
3834
  return first;
3698
3835
  }
3699
3836
 
3700
- /**
3701
- * @param {any} node
3702
- * @param {TransformContext} transform_context
3703
- * @param {any[]} path
3704
- */
3705
- function validate_style_directive(node, transform_context, path) {
3706
- const { attribute, element } = get_style_attribute_context(node, path);
3707
-
3708
- if (!attribute) {
3709
- error(
3710
- '`{style "class_name"}` can only be used as an element attribute value.',
3711
- transform_context.filename,
3712
- node,
3713
- transform_context.errors,
3714
- transform_context.comments,
3715
- );
3716
- }
3717
-
3718
- if (element && is_dom_style_target(element)) {
3719
- error(
3720
- '`{style "class_name"}` cannot be used directly on DOM elements. Pass the class to a child component instead.',
3721
- transform_context.filename,
3722
- node,
3723
- transform_context.errors,
3724
- transform_context.comments,
3725
- );
3726
- }
3727
-
3728
- if (!transform_context.current_css_hash) {
3729
- error(
3730
- '`{style "class_name"}` requires a <style> block in the current component.',
3731
- transform_context.filename,
3732
- node,
3733
- transform_context.errors,
3734
- transform_context.comments,
3735
- );
3736
- }
3737
- }
3738
-
3739
- /**
3740
- * @param {any} node
3741
- * @param {any[]} path
3742
- * @returns {{ attribute: any, element: any }}
3743
- */
3744
- function get_style_attribute_context(node, path) {
3745
- const parent = path.at(-1);
3746
- const attribute =
3747
- parent?.type === 'Attribute' && parent.value === node
3748
- ? parent
3749
- : path
3750
- .findLast((ancestor) => ancestor?.type === 'Element')
3751
- ?.attributes?.find(
3752
- (/** @type {any} */ attr) =>
3753
- attr?.type === 'Attribute' &&
3754
- (attr.value === node || node_contains(attr.value, node)),
3755
- );
3756
- const element = path.findLast(
3757
- (ancestor) =>
3758
- ancestor?.type === 'Element' &&
3759
- (!attribute || ancestor.attributes?.some((/** @type {any} */ attr) => attr === attribute)),
3760
- );
3761
-
3762
- return { attribute: attribute ?? null, element: element ?? null };
3763
- }
3764
-
3765
- /**
3766
- * @param {any} root
3767
- * @param {any} target
3768
- * @returns {boolean}
3769
- */
3770
- function node_contains(root, target) {
3771
- if (!root || typeof root !== 'object') {
3772
- return false;
3773
- }
3774
- if (root === target) {
3775
- return true;
3776
- }
3777
- if (Array.isArray(root)) {
3778
- return root.some((child) => node_contains(child, target));
3779
- }
3780
- for (const key of Object.keys(root)) {
3781
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3782
- continue;
3783
- }
3784
- if (node_contains(root[key], target)) {
3785
- return true;
3786
- }
3787
- }
3788
- return false;
3789
- }
3790
-
3791
- /**
3792
- * @param {any} element
3793
- * @returns {boolean}
3794
- */
3795
- function is_dom_style_target(element) {
3796
- if (!element?.id || is_dynamic_element_id(element.id)) {
3797
- return false;
3798
- }
3799
- return element.id.type === 'Identifier' && /^[a-z]/.test(element.id.name);
3800
- }
3801
-
3802
3837
  /**
3803
3838
  * @param {any} node
3804
3839
  * @param {TransformContext} transform_context
@@ -3821,8 +3856,6 @@ function to_jsx_child(node, transform_context) {
3821
3856
  return to_jsx_expression_container(to_text_expression(node.expression, node), node);
3822
3857
  case 'TSRXExpression':
3823
3858
  return to_jsx_expression_container(node.expression, node);
3824
- case 'Html':
3825
- return recover_invalid_html_child(node, transform_context);
3826
3859
  case 'IfStatement':
3827
3860
  return (
3828
3861
  transform_context.platform.hooks?.controlFlow?.ifStatement ?? if_statement_to_jsx_child
@@ -3846,7 +3879,7 @@ function to_jsx_child(node, transform_context) {
3846
3879
  }
3847
3880
 
3848
3881
  /**
3849
- * Lower a `<tsrx>` node's native TSRX template body to a JSX expression.
3882
+ * Lower a native TSRX fragment body to a JSX expression.
3850
3883
  * Unlike `<tsx>`, children have already been parsed and transformed through
3851
3884
  * the normal TSRX Element/Text/control-flow visitors.
3852
3885
  *
@@ -3902,7 +3935,7 @@ function tsrx_node_to_jsx_expression(node, transform_context, in_jsx_child = fal
3902
3935
  }
3903
3936
 
3904
3937
  /**
3905
- * Explicit return values inside expression-position `<tsrx>` templates are JavaScript
3938
+ * Explicit return values inside expression-position native templates are JavaScript
3906
3939
  * values, so keep them out of platform render control flow.
3907
3940
  *
3908
3941
  * @param {any[]} body_nodes
@@ -3958,8 +3991,7 @@ function body_contains_top_level_return_value(node) {
3958
3991
  node.type === 'FunctionExpression' ||
3959
3992
  node.type === 'ArrowFunctionExpression' ||
3960
3993
  node.type === 'ClassDeclaration' ||
3961
- node.type === 'ClassExpression' ||
3962
- node.type === 'Component'
3994
+ node.type === 'ClassExpression'
3963
3995
  ) {
3964
3996
  return false;
3965
3997
  }
@@ -4153,7 +4185,12 @@ function find_key_expression_in_body(body_nodes) {
4153
4185
  * @returns {any}
4154
4186
  */
4155
4187
  function continue_to_bare_return(source_node) {
4156
- return set_loc(b.return(null), source_node);
4188
+ const node = set_loc(b.return(null), source_node);
4189
+ node.metadata = {
4190
+ ...(node.metadata || {}),
4191
+ generated_loop_continue_return: true,
4192
+ };
4193
+ return node;
4157
4194
  }
4158
4195
 
4159
4196
  /**
@@ -4214,7 +4251,7 @@ function is_loop_statement(node) {
4214
4251
  function for_of_statement_to_jsx_child(node, transform_context) {
4215
4252
  if (node.await) {
4216
4253
  error(
4217
- `${transform_context.platform.name} TSRX does not support \`for await...of\` in component templates.`,
4254
+ `${transform_context.platform.name} TSRX does not support \`for await...of\` in TSRX templates.`,
4218
4255
  transform_context.filename,
4219
4256
  node,
4220
4257
  transform_context.errors,
@@ -4228,7 +4265,9 @@ function for_of_statement_to_jsx_child(node, transform_context) {
4228
4265
  node.body.type === 'BlockStatement' ? node.body.body : [node.body],
4229
4266
  )
4230
4267
  );
4231
- const has_hooks = body_contains_top_level_hook_call(loop_body, transform_context, true);
4268
+ const has_hooks =
4269
+ should_extract_hook_helpers(transform_context) &&
4270
+ body_contains_top_level_hook_call(loop_body, transform_context, true);
4232
4271
  const body_key_expression = find_key_expression_in_body(loop_body);
4233
4272
  const explicit_key_expression =
4234
4273
  body_key_expression ?? (node.key ? clone_expression_node(node.key) : undefined);
@@ -4457,7 +4496,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4457
4496
 
4458
4497
  if (finalizer) {
4459
4498
  error(
4460
- `${transform_context.platform.name} TSRX does not support JavaScript \`try/finally\` in component templates. \`finally\` is not part of TSRX control flow; move the try/finally into a function if you need cleanup logic.`,
4499
+ `${transform_context.platform.name} TSRX does not support JavaScript \`try/finally\` in TSRX templates. \`finally\` is not part of TSRX control flow; move the try/finally into a function if you need cleanup logic.`,
4461
4500
  transform_context.filename,
4462
4501
  finalizer,
4463
4502
  transform_context.errors,
@@ -4467,7 +4506,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4467
4506
 
4468
4507
  if (!pending && !handler) {
4469
4508
  error(
4470
- 'Component try statements must have a `pending` or `catch` block.',
4509
+ 'TSRX try statements must have a `pending` or `catch` block.',
4471
4510
  transform_context.filename,
4472
4511
  node,
4473
4512
  transform_context.errors,
@@ -4491,7 +4530,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4491
4530
  const try_body = node.block.body || [];
4492
4531
  if (!try_body.some(is_jsx_child)) {
4493
4532
  error(
4494
- 'Component try statements must contain a template in their main body. Move the try statement into a function if it does not render anything.',
4533
+ 'TSRX try statements must contain a template in their main body. Move the try statement into a function if it does not render anything.',
4495
4534
  transform_context.filename,
4496
4535
  node.block,
4497
4536
  transform_context.errors,
@@ -4501,7 +4540,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4501
4540
  const pending_body = pending.body || [];
4502
4541
  if (pending_body.length > 0 && !pending_body.some(is_jsx_child)) {
4503
4542
  error(
4504
- 'Component try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
4543
+ 'TSRX try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
4505
4544
  transform_context.filename,
4506
4545
  pending,
4507
4546
  transform_context.errors,
@@ -4714,12 +4753,14 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
4714
4753
  transform_context.needs_merge_refs && platform.imports.mergeRefs
4715
4754
  ? platform.imports.mergeRefs
4716
4755
  : null;
4717
- const ref_prop_source =
4718
- transform_context.needs_ref_prop && platform.imports.refProp ? platform.imports.refProp : null;
4719
4756
  const normalize_spread_props_source =
4720
4757
  transform_context.needs_normalize_spread_props && platform.imports.refProp
4721
4758
  ? platform.imports.refProp
4722
4759
  : null;
4760
+ const normalize_spread_props_for_ref_attr_source =
4761
+ transform_context.needs_normalize_spread_props_for_ref_attr && platform.imports.refProp
4762
+ ? platform.imports.refProp
4763
+ : null;
4723
4764
 
4724
4765
  /** @type {Map<string, any[]>} */
4725
4766
  const ref_imports = new Map();
@@ -4732,19 +4773,22 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
4732
4773
  );
4733
4774
  }
4734
4775
 
4735
- if (ref_prop_source !== null) {
4776
+ if (normalize_spread_props_source !== null) {
4736
4777
  add_ref_import_specifier(
4737
4778
  ref_imports,
4738
- ref_prop_source,
4739
- b.import_specifier('create_ref_prop', CREATE_REF_PROP_INTERNAL_NAME),
4779
+ normalize_spread_props_source,
4780
+ b.import_specifier('normalize_spread_props', NORMALIZE_SPREAD_PROPS_INTERNAL_NAME),
4740
4781
  );
4741
4782
  }
4742
4783
 
4743
- if (normalize_spread_props_source !== null) {
4784
+ if (normalize_spread_props_for_ref_attr_source !== null) {
4744
4785
  add_ref_import_specifier(
4745
4786
  ref_imports,
4746
- normalize_spread_props_source,
4747
- b.import_specifier('normalize_spread_props', NORMALIZE_SPREAD_PROPS_INTERNAL_NAME),
4787
+ normalize_spread_props_for_ref_attr_source,
4788
+ b.import_specifier(
4789
+ 'normalize_spread_props_for_ref_attr',
4790
+ NORMALIZE_SPREAD_PROPS_FOR_REF_ATTR_INTERNAL_NAME,
4791
+ ),
4748
4792
  );
4749
4793
  }
4750
4794
 
@@ -4779,11 +4823,9 @@ function add_ref_import_specifier(imports, source, specifier) {
4779
4823
  function create_render_if_statement(node, transform_context) {
4780
4824
  const consequent_body =
4781
4825
  node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
4782
- const consequent_has_hooks = body_contains_top_level_hook_call(
4783
- consequent_body,
4784
- transform_context,
4785
- true,
4786
- );
4826
+ const consequent_has_hooks =
4827
+ should_extract_hook_helpers(transform_context) &&
4828
+ body_contains_top_level_hook_call(consequent_body, transform_context, true);
4787
4829
 
4788
4830
  let alternate = null;
4789
4831
  if (node.alternate) {
@@ -4791,11 +4833,9 @@ function create_render_if_statement(node, transform_context) {
4791
4833
  alternate = create_render_if_statement(node.alternate, transform_context);
4792
4834
  } else {
4793
4835
  const alternate_body = node.alternate.body || [node.alternate];
4794
- const alternate_has_hooks = body_contains_top_level_hook_call(
4795
- alternate_body,
4796
- transform_context,
4797
- true,
4798
- );
4836
+ const alternate_has_hooks =
4837
+ should_extract_hook_helpers(transform_context) &&
4838
+ body_contains_top_level_hook_call(alternate_body, transform_context, true);
4799
4839
  alternate = set_loc(
4800
4840
  b.block(
4801
4841
  alternate_has_hooks
@@ -4912,7 +4952,10 @@ export function plan_switch_lift(switch_node, transform_context) {
4912
4952
  const needs_helper = case_info.map(
4913
4953
  (/** @type {{ own_body: any[], has_terminator: boolean }} */ info, /** @type {number} */ k) => {
4914
4954
  if (info.own_body.length === 0) return false;
4915
- if (body_contains_top_level_hook_call(info.own_body, transform_context, true)) {
4955
+ if (
4956
+ should_extract_hook_helpers(transform_context) &&
4957
+ body_contains_top_level_hook_call(info.own_body, transform_context, true)
4958
+ ) {
4916
4959
  return true;
4917
4960
  }
4918
4961
  if (k === 0) return false;
@@ -5052,7 +5095,7 @@ function build_switch_with_lift(switch_node, transform_context) {
5052
5095
  let has_terminal = false;
5053
5096
 
5054
5097
  for (const child of own_body) {
5055
- if (is_bare_return_statement(child)) {
5098
+ if (is_loop_skip_return_statement(child)) {
5056
5099
  case_body.push(create_component_return_statement(render_nodes, child));
5057
5100
  has_terminal = true;
5058
5101
  break;
@@ -5155,13 +5198,13 @@ function to_jsx_expression_container(expression, source_node = expression) {
5155
5198
  * the default "map over `to_jsx_attribute`" via
5156
5199
  * `hooks.transformElementAttributes`. Whether or not the hook is used,
5157
5200
  * the result is run through `merge_duplicate_refs` so platforms with a
5158
- * `multiRefStrategy` get duplicate-`ref` handling for free.
5201
+ * `multiRefStrategy` can compose an explicit `ref={...}` with compiler-
5202
+ * synthesized refs created for host spreads.
5159
5203
  *
5160
5204
  * Before lowering, the raw attribute list is validated to reject elements
5161
5205
  * with more than one TSX-style `ref={...}` attribute — that shape produces
5162
5206
  * duplicate JSX props which the JSX runtime collapses to last-wins (and
5163
- * which TypeScript can't type cleanly). Multiple Ripple `{ref expr}`
5164
- * keyword-form refs remain valid and merge into a single ref attribute.
5207
+ * which TypeScript can't type cleanly).
5165
5208
  *
5166
5209
  * @param {any[]} attrs
5167
5210
  * @param {TransformContext} transform_context
@@ -5171,7 +5214,6 @@ function to_jsx_expression_container(expression, source_node = expression) {
5171
5214
  function transform_element_attributes_dispatch(attrs, transform_context, element) {
5172
5215
  validate_at_most_one_ref_attribute(attrs, transform_context);
5173
5216
  const is_component = is_component_like_element(element);
5174
- attrs = normalize_named_ref_attributes(attrs, !is_component, transform_context);
5175
5217
  const preprocess = transform_context.platform.hooks?.preprocessElementAttributes;
5176
5218
  if (preprocess) {
5177
5219
  attrs = preprocess(attrs, transform_context, element);
@@ -5180,43 +5222,12 @@ function transform_element_attributes_dispatch(attrs, transform_context, element
5180
5222
  const result = hook
5181
5223
  ? hook(attrs, transform_context, element)
5182
5224
  : attrs.map((/** @type {any} */ a) => to_jsx_attribute(a, transform_context));
5183
- if (transform_context.typeOnly) {
5184
- add_ref_target_type_to_ref_prop_attributes(
5185
- result,
5186
- !is_component ? create_element_ref_target_type(element) : null,
5187
- );
5188
- }
5189
5225
  return merge_duplicate_refs(
5190
5226
  normalize_host_ref_spreads(result, !is_component, transform_context),
5191
5227
  transform_context,
5192
5228
  );
5193
5229
  }
5194
5230
 
5195
- /**
5196
- * @param {any[]} attrs
5197
- * @param {AST.TypeNode | null} ref_target_type
5198
- * @returns {void}
5199
- */
5200
- export function add_ref_target_type_to_ref_prop_attributes(attrs, ref_target_type) {
5201
- if (!ref_target_type) return;
5202
- for (const attr of attrs) {
5203
- const expression =
5204
- attr?.type === 'JSXAttribute' &&
5205
- attr.value?.type === 'JSXExpressionContainer' &&
5206
- attr.value.expression?.type !== 'JSXEmptyExpression'
5207
- ? attr.value.expression
5208
- : null;
5209
- if (
5210
- expression?.type === 'CallExpression' &&
5211
- expression.callee?.type === 'Identifier' &&
5212
- expression.callee.name === CREATE_REF_PROP_INTERNAL_NAME &&
5213
- !expression.typeArguments
5214
- ) {
5215
- expression.typeArguments = b.ts_type_parameter_instantiation([ref_target_type]);
5216
- }
5217
- }
5218
- }
5219
-
5220
5231
  /**
5221
5232
  * @param {any} element
5222
5233
  * @returns {boolean}
@@ -5242,48 +5253,6 @@ function is_component_like_jsx_name(name) {
5242
5253
  return false;
5243
5254
  }
5244
5255
 
5245
- /**
5246
- * @param {any[]} attrs
5247
- * @param {boolean} is_host
5248
- * @param {TransformContext} transform_context
5249
- * @returns {any[]}
5250
- */
5251
- function normalize_named_ref_attributes(attrs, is_host, transform_context) {
5252
- if (!is_host) return attrs;
5253
-
5254
- return attrs.map((attr) => {
5255
- if (!is_named_ref_attribute(attr)) {
5256
- return attr;
5257
- }
5258
-
5259
- if (transform_context.typeOnly) {
5260
- return mark_type_only_named_ref_attribute(attr);
5261
- }
5262
-
5263
- return {
5264
- ...attr,
5265
- metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
5266
- name: attr.name?.type === 'JSXIdentifier' ? { ...attr.name, name: 'ref' } : b.id('ref'),
5267
- };
5268
- });
5269
- }
5270
-
5271
- /**
5272
- * @param {any} attr
5273
- * @returns {any}
5274
- */
5275
- function mark_type_only_named_ref_attribute(attr) {
5276
- return {
5277
- ...attr,
5278
- name: attr.name
5279
- ? {
5280
- ...attr.name,
5281
- metadata: { ...(attr.name.metadata || {}), disable_verification: true },
5282
- }
5283
- : attr.name,
5284
- };
5285
- }
5286
-
5287
5256
  /**
5288
5257
  * @param {any[]} attrs
5289
5258
  * @param {boolean} is_host
@@ -5305,8 +5274,15 @@ function normalize_host_ref_spreads(attrs, is_host, transform_context) {
5305
5274
  return [attr];
5306
5275
  }
5307
5276
 
5308
- transform_context.needs_normalize_spread_props = true;
5309
- const normalized = b.call(NORMALIZE_SPREAD_PROPS_INTERNAL_NAME, attr.argument);
5277
+ const normalize_helper = needs_synthetic_spread_ref
5278
+ ? NORMALIZE_SPREAD_PROPS_FOR_REF_ATTR_INTERNAL_NAME
5279
+ : NORMALIZE_SPREAD_PROPS_INTERNAL_NAME;
5280
+ if (needs_synthetic_spread_ref) {
5281
+ transform_context.needs_normalize_spread_props_for_ref_attr = true;
5282
+ } else {
5283
+ transform_context.needs_normalize_spread_props = true;
5284
+ }
5285
+ const normalized = b.call(normalize_helper, attr.argument);
5310
5286
 
5311
5287
  if (needs_synthetic_spread_ref) {
5312
5288
  const normalized_id = create_generated_identifier(
@@ -5323,7 +5299,7 @@ function normalize_host_ref_spreads(attrs, is_host, transform_context) {
5323
5299
  attr,
5324
5300
  );
5325
5301
  ref_attr.metadata = { ...(ref_attr.metadata || {}) };
5326
- /** @type {any} */ (ref_attr.metadata).from_ref_keyword = true;
5302
+ /** @type {any} */ (ref_attr.metadata).synthetic_ref = true;
5327
5303
  add_jsx_setup_declaration(spread, b.let(clone_identifier(normalized_id), normalized));
5328
5304
 
5329
5305
  return [spread, ref_attr];
@@ -5412,69 +5388,10 @@ function wrap_jsx_setup_declarations(expression, in_jsx_child) {
5412
5388
  return in_jsx_child ? to_jsx_expression_container(call, expression) : call;
5413
5389
  }
5414
5390
 
5415
- /**
5416
- * @param {any} attr
5417
- * @returns {boolean}
5418
- */
5419
- function is_named_ref_attribute(attr) {
5420
- return !!(
5421
- attr &&
5422
- (attr.type === 'Attribute' || attr.type === 'JSXAttribute') &&
5423
- attr.name &&
5424
- ((attr.name.type === 'Identifier' && attr.name.name !== 'ref') ||
5425
- (attr.name.type === 'JSXIdentifier' && attr.name.name !== 'ref')) &&
5426
- (attr.value?.type === 'RefExpression' ||
5427
- is_ref_prop_expression(attr.value) ||
5428
- (attr.value?.type === 'JSXExpressionContainer' &&
5429
- is_ref_prop_expression(attr.value.expression)))
5430
- );
5431
- }
5432
-
5433
- /**
5434
- * @param {any} html_expression
5435
- * @param {any} source_attr
5436
- * @param {TransformContext} transform_context
5437
- * @returns {any}
5438
- */
5439
- export function create_host_html_attribute(html_expression, source_attr, transform_context) {
5440
- const expression =
5441
- html_expression?.type === 'Html' ? html_expression.expression : html_expression;
5442
- const name = get_host_html_attribute_name(transform_context);
5443
- const value =
5444
- name === 'dangerouslySetInnerHTML'
5445
- ? set_loc(b.object([b.prop('init', b.id('__html'), expression)]), source_attr)
5446
- : expression;
5447
- const value_container = to_jsx_expression_container(value, source_attr);
5448
- if (name !== 'dangerouslySetInnerHTML') {
5449
- setLocation(value_container, source_attr, true);
5450
- }
5451
-
5452
- return set_loc(
5453
- build_jsx_attribute(b.jsx_id(name), value_container, false, source_attr),
5454
- source_attr,
5455
- );
5456
- }
5457
-
5458
- /**
5459
- * @param {any} expression
5460
- * @returns {boolean}
5461
- */
5462
- export function is_ref_prop_expression(expression) {
5463
- return (
5464
- expression?.type === 'RefExpression' ||
5465
- (expression?.type === 'CallExpression' &&
5466
- expression.callee?.type === 'Identifier' &&
5467
- expression.callee.name === CREATE_REF_PROP_INTERNAL_NAME)
5468
- );
5469
- }
5470
-
5471
5391
  /**
5472
5392
  * Reject elements with more than one TSX-style `ref={...}` attribute.
5473
- * Ripple's `{ref expr}` keyword form is parsed as a `RefAttribute` node
5474
- * and is excluded from the count multiple keyword-form refs are a Ripple
5475
- * feature that compose via the merge pass. This validator runs over the
5476
- * raw, pre-lowering attribute list so each shape is still distinguishable
5477
- * by `type`. Ripple `Element` attributes have type `Attribute` with an
5393
+ * This validator runs over the raw, pre-lowering attribute list so each
5394
+ * shape is still distinguishable by `type`. Ripple `Element` attributes have type `Attribute` with an
5478
5395
  * `Identifier` name (the parser normalizes `JSXAttribute`/`JSXIdentifier`
5479
5396
  * for non-Tsx elements); inside `<tsx:react>` compat blocks they retain
5480
5397
  * the original `JSXAttribute`/`JSXIdentifier` shape, so we accept both.
@@ -5510,7 +5427,7 @@ export function validate_at_most_one_ref_attribute(raw_attrs, transform_context)
5510
5427
  }
5511
5428
  error(
5512
5429
  'Element has multiple `ref={...}` attributes; an element may have at most one. ' +
5513
- "Use Ripple's `{ref expr}` keyword form to combine multiple refs on one element.",
5430
+ 'Use a single array-valued ref such as `ref={[a, b]}` where the target framework supports multiple refs.',
5514
5431
  transform_context?.filename ?? null,
5515
5432
  node,
5516
5433
  transform_context?.errors,
@@ -5520,11 +5437,9 @@ export function validate_at_most_one_ref_attribute(raw_attrs, transform_context)
5520
5437
  }
5521
5438
 
5522
5439
  /**
5523
- * Collapse multiple `ref` JSXAttributes on a single element into one. Both
5524
- * Ripple's `{ref expr}` keyword form and TSX-style `ref={expr}` are handled
5525
- * because they have already been normalized to `JSXAttribute` named `ref`
5526
- * by `to_jsx_attribute` (Ripple) or the parser (TSX-style). The shape of
5527
- * the merged value depends on `platform.jsx.multiRefStrategy`:
5440
+ * Collapse an explicit `ref={...}` plus compiler-synthesized spread refs into
5441
+ * one attribute. The shape of the merged value depends on
5442
+ * `platform.jsx.multiRefStrategy`:
5528
5443
  *
5529
5444
  * - `'merge-refs'` — emit `ref={__mergeRefs(a, b, ...)}` and flag
5530
5445
  * `needs_merge_refs` so an import is injected later. React and Preact
@@ -5550,7 +5465,7 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
5550
5465
  for (const attr of jsx_attrs) {
5551
5466
  if (!is_jsx_ref_attribute(attr)) continue;
5552
5467
  count += 1;
5553
- if (!attr.metadata?.from_ref_keyword) tsx_form_count += 1;
5468
+ if (!attr.metadata?.synthetic_ref) tsx_form_count += 1;
5554
5469
  }
5555
5470
  if (count <= 1) return jsx_attrs;
5556
5471
  // Two or more genuine `ref={...}` (TSX-form) attributes are already a
@@ -5571,7 +5486,7 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
5571
5486
  // Inherit loc from the (at most one) `ref={expr}`-form attribute so
5572
5487
  // the kept `ref` keyword in the generated `ref={__mergeRefs(...)}`
5573
5488
  // retains a source mapping back to its original `ref=` keyword.
5574
- if (!source_attr && !attr.metadata?.from_ref_keyword) {
5489
+ if (!source_attr && !attr.metadata?.synthetic_ref) {
5575
5490
  source_attr = attr;
5576
5491
  }
5577
5492
  } else {
@@ -5629,8 +5544,9 @@ function is_jsx_ref_attribute(attr) {
5629
5544
  * identifiers and avoids shadowing user-declared `mergeRefs` symbols.
5630
5545
  */
5631
5546
  export const MERGE_REFS_INTERNAL_NAME = '__mergeRefs';
5632
- export const CREATE_REF_PROP_INTERNAL_NAME = '__create_ref_prop';
5633
5547
  export const NORMALIZE_SPREAD_PROPS_INTERNAL_NAME = '__normalize_spread_props';
5548
+ export const NORMALIZE_SPREAD_PROPS_FOR_REF_ATTR_INTERNAL_NAME =
5549
+ '__normalize_spread_props_for_ref_attr';
5634
5550
  export const MAP_ITERABLE_INTERNAL_NAME = '__map_iterable';
5635
5551
  export const ITERATION_VALUE_INTERNAL_NAME = '__IterationValue';
5636
5552
 
@@ -5652,17 +5568,6 @@ const MATHML_REF_TAG_NAMES = new Set(
5652
5568
  ),
5653
5569
  );
5654
5570
 
5655
- /**
5656
- * @param {any} value
5657
- * @returns {boolean}
5658
- */
5659
- export function is_ref_expression_attribute_value(value) {
5660
- return (
5661
- value?.type === 'RefExpression' ||
5662
- (value?.type === 'JSXExpressionContainer' && value.expression?.type === 'RefExpression')
5663
- );
5664
- }
5665
-
5666
5571
  /**
5667
5572
  * @param {any} element
5668
5573
  * @param {'html' | 'svg' | 'mathml'} [namespace]
@@ -5751,27 +5656,6 @@ function create_tag_name_map_ref_type(map_name, tag_name) {
5751
5656
  export function to_jsx_attribute(attr, transform_context) {
5752
5657
  if (!attr) return attr;
5753
5658
  if (attr.type === 'JSXAttribute') {
5754
- if (
5755
- attr.value?.type === 'JSXExpressionContainer' &&
5756
- attr.value.expression?.type === 'RefExpression'
5757
- ) {
5758
- return {
5759
- ...attr,
5760
- value: to_jsx_expression_container(
5761
- create_ref_prop_call(attr.value.expression, transform_context),
5762
- ),
5763
- metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
5764
- };
5765
- }
5766
- if (
5767
- attr.value?.type === 'JSXExpressionContainer' &&
5768
- is_ref_prop_expression(attr.value.expression)
5769
- ) {
5770
- return {
5771
- ...attr,
5772
- metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
5773
- };
5774
- }
5775
5659
  return attr;
5776
5660
  }
5777
5661
  if (attr.type === 'JSXSpreadAttribute') {
@@ -5786,28 +5670,8 @@ export function to_jsx_attribute(attr, transform_context) {
5786
5670
  attr,
5787
5671
  );
5788
5672
  }
5789
- if (attr.type === 'RefAttribute') {
5790
- // `{ref expr}` and the generated `ref={expr}` have different shapes,
5791
- // so the source-to-generated mapping is imprecise — but pointing
5792
- // editors at the `{ref expr}` span is still useful for hover/jump,
5793
- // matching how shorthand `{name}` → `name={name}` carries loc.
5794
- // `from_ref_keyword` lets `merge_duplicate_refs` tell this form apart
5795
- // from genuine `ref={...}` attributes without inferring it from
5796
- // whether `name.loc` happens to be present.
5797
- return set_loc(
5798
- /** @type {any} */ ({
5799
- type: 'JSXAttribute',
5800
- name: { type: 'JSXIdentifier', name: 'ref', metadata: { path: [] } },
5801
- value: to_jsx_expression_container(attr.argument),
5802
- shorthand: false,
5803
- metadata: { path: [], from_ref_keyword: true },
5804
- }),
5805
- attr,
5806
- );
5807
- }
5808
-
5809
- // Platforms that expect React-style DOM attrs (React) rewrite `class` to
5810
- // `className`; Preact and Solid accept `class` natively and keep it.
5673
+ // Keep this legacy hook for targets that need React-style DOM attrs. The
5674
+ // current first-party targets preserve authored `class`.
5811
5675
  let attr_name = attr.name;
5812
5676
  if (
5813
5677
  transform_context.platform.jsx.rewriteClassAttr &&
@@ -5824,28 +5688,15 @@ export function to_jsx_attribute(attr, transform_context) {
5824
5688
  attr_name && attr_name.type === 'Identifier' ? identifier_to_jsx_name(attr_name) : attr_name;
5825
5689
 
5826
5690
  let value = attr.value;
5827
- const is_ref_expression_value =
5828
- value?.type === 'RefExpression' ||
5829
- is_ref_prop_expression(value) ||
5830
- (value?.type === 'JSXExpressionContainer' && is_ref_prop_expression(value.expression));
5831
5691
  if (value) {
5832
5692
  if (value.type === 'Literal' && typeof value.value === 'string') {
5833
5693
  // Keep string literal as attribute string.
5834
- } else if (value.type === 'RefExpression') {
5835
- value = to_jsx_expression_container(create_ref_prop_call(value, transform_context));
5836
5694
  } else if (value.type !== 'JSXExpressionContainer') {
5837
5695
  value = to_jsx_expression_container(value);
5838
- } else if (value.expression?.type === 'RefExpression') {
5839
- value = to_jsx_expression_container(
5840
- create_ref_prop_call(value.expression, transform_context),
5841
- );
5842
5696
  }
5843
5697
  }
5844
5698
 
5845
5699
  const jsx_attribute = build_jsx_attribute(name, value || null, attr.shorthand === true);
5846
- if (is_ref_expression_value) {
5847
- /** @type {any} */ (jsx_attribute.metadata).from_ref_keyword = true;
5848
- }
5849
5700
 
5850
5701
  if (value_has_unmappable_jsx_loc(value)) {
5851
5702
  /** @type {any} */ (jsx_attribute.metadata).has_unmappable_value = true;
@@ -5867,26 +5718,6 @@ function value_has_unmappable_jsx_loc(value) {
5867
5718
  );
5868
5719
  }
5869
5720
 
5870
- /**
5871
- * @param {any} node
5872
- * @param {TransformContext} transform_context
5873
- * @returns {any}
5874
- */
5875
- function create_ref_prop_call(node, transform_context) {
5876
- transform_context.needs_ref_prop = true;
5877
-
5878
- const argument = node.argument;
5879
- const args = [b.thunk(argument)];
5880
-
5881
- if (argument.type === 'Identifier' || argument.type === 'MemberExpression') {
5882
- args.push(
5883
- b.arrow([b.id('v')], b.assignment('=', clone_expression_node(argument, false), b.id('v'))),
5884
- );
5885
- }
5886
-
5887
- return b.call(CREATE_REF_PROP_INTERNAL_NAME, ...args);
5888
- }
5889
-
5890
5721
  /**
5891
5722
  * @param {any} node
5892
5723
  * @param {TransformContext} transform_context