@tsrx/core 0.1.15 → 0.1.17

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,17 @@ 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_ref_setup_statements,
49
+ } from '../style-ref.js';
50
+ import { is_function_or_component_node } from '../../utils/ast.js';
55
51
  import {
56
52
  is_interleaved_body as is_interleaved_body_core,
57
53
  is_capturable_jsx_child,
@@ -66,43 +62,6 @@ const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
66
62
  const TEMPLATE_FRAGMENT_ERROR =
67
63
  '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
64
 
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
65
  /**
107
66
  * @param {AST.Node} node
108
67
  * @param {TransformContext} transform_context
@@ -181,60 +140,16 @@ function is_function_or_class_boundary(node) {
181
140
  );
182
141
  }
183
142
 
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
143
  /**
228
144
  * 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.
145
+ * Solid). Given a `JsxPlatform` descriptor, returns a transform that lowers
146
+ * native TSRX template nodes into a plain TSX module for that platform.
232
147
  *
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.
148
+ * Any `<style>` element declared inside a TSRX fragment is collected, rendered
149
+ * via `@tsrx/core`'s stylesheet renderer, and returned alongside the JS output
150
+ * so a downstream plugin can inject it. The compiler also augments every
151
+ * non-style Element in that fragment with the stylesheet's hash class so scoped
152
+ * selectors match correctly.
238
153
  *
239
154
  * @param {JsxPlatform} platform
240
155
  * @returns {(ast: AST.Program, source: string, filename?: string, options?: JsxTransformOptions) => JsxTransformResult}
@@ -249,13 +164,6 @@ export function createJsxTransform(platform) {
249
164
  */
250
165
  function transform(ast, source, filename, options) {
251
166
  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
167
  const collect = !!(options?.collect || options?.loose);
260
168
  /** @type {any[]} */
261
169
  const stylesheets = [];
@@ -267,18 +175,20 @@ export function createJsxTransform(platform) {
267
175
  needs_error_boundary: false,
268
176
  needs_suspense: false,
269
177
  needs_merge_refs: false,
270
- needs_ref_prop: false,
271
178
  needs_normalize_spread_props: false,
179
+ needs_normalize_spread_props_for_ref_attr: false,
272
180
  needs_fragment: false,
273
181
  needs_for_of_iterable: false,
274
182
  needs_iteration_value_type: false,
183
+ stylesheets,
275
184
  module_scoped_hook_components:
276
185
  options?.moduleScopedHookComponents ?? !!platform.hooks?.moduleScopedHookComponents,
277
186
  helper_state: null,
187
+ hook_helpers_enabled: false,
278
188
  available_bindings: new Map(),
279
189
  lazy_next_id: 0,
280
- current_css_hash: null,
281
190
  filename: filename ?? null,
191
+ source,
282
192
  collect,
283
193
  errors: collect ? options?.errors : undefined,
284
194
  comments: options?.comments,
@@ -293,197 +203,12 @@ export function createJsxTransform(platform) {
293
203
  }
294
204
 
295
205
  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
- },
206
+ FunctionDeclaration: collect_native_function_tsrx_metadata,
207
+ FunctionExpression: collect_native_function_tsrx_metadata,
208
+ ArrowFunctionExpression: collect_native_function_tsrx_metadata,
447
209
  });
448
210
 
449
211
  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
212
  Tsx(node, { next, path }) {
488
213
  const inner = /** @type {any} */ (next() ?? node);
489
214
  const in_jsx_child = in_jsx_child_context(path);
@@ -493,7 +218,19 @@ export function createJsxTransform(platform) {
493
218
  },
494
219
 
495
220
  Tsrx(node, { next, path, state }) {
496
- const inner = /** @type {any} */ (next() ?? node);
221
+ /** @type {{ css: any, style_refs: any[] } | null} */
222
+ let style_context = null;
223
+ const inner = with_tsrx_fragment_styles(node, state, (context) => {
224
+ style_context = context;
225
+ return next() ?? node;
226
+ });
227
+ for (const statement of create_tsrx_style_ref_setup_statements(
228
+ node,
229
+ style_context,
230
+ state,
231
+ )) {
232
+ add_jsx_setup_declaration(inner, statement);
233
+ }
497
234
  const in_jsx_child = in_jsx_child_context(path);
498
235
  return /** @type {any} */ (
499
236
  wrap_jsx_setup_declarations(
@@ -519,7 +256,9 @@ export function createJsxTransform(platform) {
519
256
  // platform hook (e.g. Solid's textContent optimization) can
520
257
  // inspect the original Text / TSRXExpression nodes rather than
521
258
  // their walker-lowered JSXExpressionContainer equivalents.
522
- const raw_children = /** @type {any} */ (node).children || [];
259
+ const raw_children = /** @type {any} */ (node.children || []).map(
260
+ (/** @type {any} */ child) => (child && typeof child === 'object' ? { ...child } : child),
261
+ );
523
262
  const inner = /** @type {any} */ (next() ?? node);
524
263
  const hook = platform.hooks?.transformElement;
525
264
  if (hook) return /** @type {any} */ (hook(inner, state, raw_children));
@@ -538,48 +277,25 @@ export function createJsxTransform(platform) {
538
277
  return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
539
278
  },
540
279
 
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
280
  // 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
- },
281
+ // do not trip on an undefined metadata object. Ripple's analyze phase
282
+ // does this via visit_function; tsrx-react has no analyze phase.
283
+ // If an uppercase JS function contains hook-bearing TSRX, give it a
284
+ // temporary helper scope so extracted hook helpers get stable identities.
285
+ FunctionDeclaration: transform_function,
286
+ FunctionExpression: transform_function,
287
+ ArrowFunctionExpression: transform_function,
564
288
 
565
289
  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
- );
290
+ const visited = /** @type {any} */ (next() || node);
291
+ if (visited.metadata?.native_tsrx_pretransformed) {
292
+ return visited;
578
293
  }
294
+ const is_component = is_component_like_jsx_name(visited.name);
579
295
  return {
580
296
  ...visited,
581
297
  attributes: merge_duplicate_refs(
582
- normalize_host_ref_spreads(attrs, !is_component, transform_context),
298
+ normalize_host_ref_spreads(visited.attributes || [], !is_component, transform_context),
583
299
  transform_context,
584
300
  ),
585
301
  };
@@ -594,9 +310,7 @@ export function createJsxTransform(platform) {
594
310
  }
595
311
 
596
312
  // 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.
313
+ // declarations, arrow functions, etc.).
600
314
  // In type-only mode, the lazy patterns survive untouched: esrap ignores the
601
315
  // non-standard `lazy` flag, so `&{ a, b }` prints as `{ a, b }`, `let &[a]
602
316
  // = expr` prints as `let [a] = expr`, and the bare statement-level form
@@ -629,18 +343,30 @@ export function createJsxTransform(platform) {
629
343
  *
630
344
  * @param {any} component
631
345
  * @param {any} css
346
+ * @param {boolean} [export_top_scoped_classes]
632
347
  * @returns {void}
633
348
  */
634
- function apply_css_definition_metadata(component, css) {
349
+ function apply_css_definition_metadata(component, css, export_top_scoped_classes = false) {
635
350
  analyze_css(css);
636
351
 
637
352
  const metadata = component.metadata || (component.metadata = { path: [] });
638
353
  const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
639
354
  const top_scoped_classes = metadata.topScopedClasses || new Map();
640
- const elements = collect_css_prunable_elements(component.body || []);
355
+ const elements = collect_css_prunable_elements(component.body || component.children || []);
641
356
 
642
- for (const element of elements) {
643
- prune_css(css, element, style_classes, top_scoped_classes);
357
+ const prune = () => {
358
+ for (const element of elements) {
359
+ prune_css(css, element, style_classes, top_scoped_classes);
360
+ }
361
+ };
362
+
363
+ prune();
364
+
365
+ if (export_top_scoped_classes) {
366
+ for (const [class_name, class_info] of top_scoped_classes) {
367
+ style_classes.set(class_name, class_info.selector ?? class_info);
368
+ }
369
+ prune();
644
370
  }
645
371
 
646
372
  if (top_scoped_classes.size > 0) {
@@ -668,8 +394,7 @@ function collect_css_prunable_elements(value, elements = []) {
668
394
  if (
669
395
  value.type === 'FunctionDeclaration' ||
670
396
  value.type === 'FunctionExpression' ||
671
- value.type === 'ArrowFunctionExpression' ||
672
- value.type === 'Component'
397
+ value.type === 'ArrowFunctionExpression'
673
398
  ) {
674
399
  return elements;
675
400
  }
@@ -690,127 +415,6 @@ function collect_css_prunable_elements(value, elements = []) {
690
415
  return elements;
691
416
  }
692
417
 
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
418
  /**
815
419
  * @param {any[]} body_nodes
816
420
  * @param {TransformContext} transform_context
@@ -842,16 +446,12 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
842
446
  // any JSX is constructed, and every JSX child would observe the final
843
447
  // state of mutable variables.
844
448
  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
449
  let capture_index = 0;
850
450
 
851
451
  for (let i = 0; i < body_nodes.length; i += 1) {
852
452
  const child = body_nodes[i];
853
453
 
854
- if (is_bare_return_statement(child)) {
454
+ if (is_loop_skip_return_statement(child)) {
855
455
  statements.push(create_component_return_statement(render_nodes, child));
856
456
  render_nodes.length = 0;
857
457
  has_terminal_return = true;
@@ -864,92 +464,17 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
864
464
  continue;
865
465
  }
866
466
 
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
- ),
467
+ if (is_loop_skip_if_statement(child)) {
468
+ if (transform_context.platform.hooks?.isTopLevelSetupCall) {
469
+ const continuation_body = body_nodes.slice(i + 1);
470
+ const continuation_has_setup_statements = continuation_body.some(
471
+ (node) =>
472
+ !is_loop_skip_return_statement(node) &&
473
+ !is_loop_skip_if_statement(node) &&
474
+ !is_jsx_child(node),
909
475
  );
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
476
 
477
+ if (!continuation_has_setup_statements) {
953
478
  const continuation_statements = build_render_statements(
954
479
  continuation_body,
955
480
  false,
@@ -979,13 +504,10 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
979
504
 
980
505
  break;
981
506
  }
982
-
983
- statements.push(create_component_lone_return_if_statement(child, render_nodes));
984
- continue;
985
507
  }
986
508
 
987
509
  statements.push(
988
- create_component_returning_if_statement(child, render_nodes, transform_context),
510
+ create_component_loop_skip_if_statement(child, render_nodes, transform_context),
989
511
  );
990
512
  continue;
991
513
  }
@@ -993,6 +515,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
993
515
  if (
994
516
  child.type === 'ForOfStatement' &&
995
517
  !child.await &&
518
+ should_extract_hook_helpers(transform_context) &&
996
519
  !transform_context.platform.hooks?.isTopLevelSetupCall &&
997
520
  !transform_context.platform.hooks?.controlFlow?.forOf &&
998
521
  body_contains_top_level_hook_call(
@@ -1047,19 +570,60 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
1047
570
  }
1048
571
 
1049
572
  /**
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
573
  * @param {any[]} body_nodes
1056
574
  * @returns {boolean}
1057
575
  */
1058
576
  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),
577
+ return is_interleaved_body_core(body_nodes, is_jsx_child);
578
+ }
579
+
580
+ /**
581
+ * @param {any} node
582
+ * @param {TransformContext} transform_context
583
+ * @returns {boolean}
584
+ */
585
+ function function_needs_component_body_hook_split(node, transform_context) {
586
+ return (
587
+ transform_context.platform.hooks?.componentBodyHookHelpers === true &&
588
+ node.body?.type === 'BlockStatement' &&
589
+ find_component_body_hook_split_index(node.body.body || [], transform_context) !== -1
590
+ );
591
+ }
592
+
593
+ /**
594
+ * @param {any} node
595
+ * @param {TransformContext} transform_context
596
+ * @returns {void}
597
+ */
598
+ function rewrite_component_body_conditional_hook_splits(node, transform_context) {
599
+ if (
600
+ transform_context.platform.hooks?.componentBodyHookHelpers !== true ||
601
+ !should_extract_hook_helpers(transform_context) ||
602
+ node.body?.type !== 'BlockStatement'
603
+ ) {
604
+ return;
605
+ }
606
+
607
+ const body = node.body.body || [];
608
+ const split_index = find_component_body_hook_split_index(body, transform_context);
609
+ if (split_index === -1) {
610
+ return;
611
+ }
612
+
613
+ const split_statement = body[split_index];
614
+ const continuation_body = body.slice(split_index + 1);
615
+ const helper = create_hook_safe_helper(
616
+ continuation_body,
617
+ undefined,
618
+ get_body_source_node(continuation_body) || split_statement,
619
+ transform_context,
1061
620
  );
1062
- return is_interleaved_body_core(filtered, is_jsx_child);
621
+
622
+ node.body.body = [
623
+ ...body.slice(0, split_index + 1),
624
+ ...helper.setup_statements,
625
+ set_loc(b.return(helper.component_element), split_statement),
626
+ ];
1063
627
  }
1064
628
 
1065
629
  /**
@@ -1067,9 +631,9 @@ function is_interleaved_body(body_nodes) {
1067
631
  * @param {TransformContext} transform_context
1068
632
  * @returns {number}
1069
633
  */
1070
- function find_hook_safe_split_index(body_nodes, transform_context) {
634
+ function find_component_body_hook_split_index(body_nodes, transform_context) {
1071
635
  for (let i = 0; i < body_nodes.length; i += 1) {
1072
- if (!is_lone_return_if_statement(body_nodes[i])) {
636
+ if (!is_component_body_conditional_return_statement(body_nodes[i])) {
1073
637
  continue;
1074
638
  }
1075
639
 
@@ -1081,56 +645,120 @@ function find_hook_safe_split_index(body_nodes, transform_context) {
1081
645
  return -1;
1082
646
  }
1083
647
 
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
648
  /**
1101
649
  * @param {any} node
1102
- * @param {TransformContext} transform_context
1103
- * @param {boolean} include_platform_setup
1104
650
  * @returns {boolean}
1105
651
  */
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);
652
+ function is_component_body_conditional_return_statement(node) {
653
+ if (node?.type !== 'IfStatement') {
654
+ return false;
655
+ }
656
+
657
+ return (
658
+ statement_contains_component_body_return(node.consequent) ||
659
+ statement_contains_component_body_return(node.alternate)
660
+ );
1108
661
  }
1109
662
 
1110
663
  /**
1111
664
  * @param {any} node
1112
- * @param {boolean} inside_nested_function
1113
- * @param {TransformContext} transform_context
1114
- * @param {boolean} include_platform_setup
1115
665
  * @returns {boolean}
1116
666
  */
1117
- function node_contains_top_level_hook_call(
1118
- node,
1119
- inside_nested_function,
1120
- transform_context,
1121
- include_platform_setup,
1122
- ) {
667
+ function statement_contains_component_body_return(node) {
1123
668
  if (!node || typeof node !== 'object') {
1124
669
  return false;
1125
670
  }
1126
671
 
1127
- if (
1128
- inside_nested_function &&
1129
- (node.type === 'FunctionDeclaration' ||
1130
- node.type === 'FunctionExpression' ||
1131
- node.type === 'ArrowFunctionExpression')
1132
- ) {
1133
- return false;
672
+ if (node.type === 'ReturnStatement') {
673
+ return true;
674
+ }
675
+
676
+ if (is_function_or_class_boundary(node)) {
677
+ return false;
678
+ }
679
+
680
+ if (Array.isArray(node)) {
681
+ return node.some(statement_contains_component_body_return);
682
+ }
683
+
684
+ if (node.type === 'BlockStatement') {
685
+ return (node.body || []).some(statement_contains_component_body_return);
686
+ }
687
+
688
+ if (node.type === 'IfStatement') {
689
+ return (
690
+ statement_contains_component_body_return(node.consequent) ||
691
+ statement_contains_component_body_return(node.alternate)
692
+ );
693
+ }
694
+
695
+ if (node.type === 'SwitchStatement') {
696
+ return (node.cases || []).some((/** @type {any} */ switch_case) =>
697
+ statement_contains_component_body_return(switch_case.consequent || []),
698
+ );
699
+ }
700
+
701
+ if (node.type === 'TryStatement') {
702
+ return (
703
+ statement_contains_component_body_return(node.block) ||
704
+ statement_contains_component_body_return(node.handler?.body) ||
705
+ statement_contains_component_body_return(node.finalizer)
706
+ );
707
+ }
708
+
709
+ return false;
710
+ }
711
+
712
+ /**
713
+ * @param {any[]} body_nodes
714
+ * @param {TransformContext} transform_context
715
+ * @param {boolean} include_platform_setup
716
+ * @returns {boolean}
717
+ */
718
+ function body_contains_top_level_hook_call(
719
+ body_nodes,
720
+ transform_context,
721
+ include_platform_setup = false,
722
+ ) {
723
+ return body_nodes.some((node) =>
724
+ statement_contains_top_level_hook_call(node, transform_context, include_platform_setup),
725
+ );
726
+ }
727
+
728
+ /**
729
+ * @param {any} node
730
+ * @param {TransformContext} transform_context
731
+ * @param {boolean} include_platform_setup
732
+ * @returns {boolean}
733
+ */
734
+ function statement_contains_top_level_hook_call(node, transform_context, include_platform_setup) {
735
+ return node_contains_top_level_hook_call(node, false, transform_context, include_platform_setup);
736
+ }
737
+
738
+ /**
739
+ * @param {any} node
740
+ * @param {boolean} inside_nested_function
741
+ * @param {TransformContext} transform_context
742
+ * @param {boolean} include_platform_setup
743
+ * @returns {boolean}
744
+ */
745
+ function node_contains_top_level_hook_call(
746
+ node,
747
+ inside_nested_function,
748
+ transform_context,
749
+ include_platform_setup,
750
+ ) {
751
+ if (!node || typeof node !== 'object') {
752
+ return false;
753
+ }
754
+
755
+ if (
756
+ inside_nested_function &&
757
+ (node.type === 'FunctionDeclaration' ||
758
+ node.type === 'FunctionExpression' ||
759
+ node.type === 'ArrowFunctionExpression')
760
+ ) {
761
+ return false;
1134
762
  }
1135
763
 
1136
764
  if (
@@ -1280,85 +908,834 @@ function create_helper_component_element(helper_id, bindings, source_node, mappi
1280
908
  mapWrapper ? set_loc(opening_element, source_node) : opening_element,
1281
909
  );
1282
910
 
1283
- return mapWrapper ? set_loc(element, source_node) : element;
911
+ return mapWrapper ? set_loc(element, source_node) : element;
912
+ }
913
+
914
+ /**
915
+ * @param {{ base_name: string, next_id: number, helpers: any[], statics: any[] }} helper_state
916
+ * @param {string} suffix
917
+ * @returns {string}
918
+ */
919
+ function create_helper_name(helper_state, suffix) {
920
+ helper_state.next_id += 1;
921
+ return `${helper_state.base_name}__${suffix}${helper_state.next_id}`;
922
+ }
923
+
924
+ /**
925
+ * @param {string} base_name
926
+ * @returns {{ base_name: string, next_id: number, helpers: any[], statics: any[] }}
927
+ */
928
+ function create_helper_state(base_name) {
929
+ return {
930
+ base_name,
931
+ next_id: 0,
932
+ helpers: [],
933
+ statics: [],
934
+ };
935
+ }
936
+
937
+ /**
938
+ * @param {any} node
939
+ * @param {{ next: (state?: TransformContext) => any, state: TransformContext }} context
940
+ * @returns {any}
941
+ */
942
+ function collect_native_function_tsrx_metadata(node, { next, state }) {
943
+ if (!function_has_native_tsrx_return(node)) {
944
+ return next(state);
945
+ }
946
+
947
+ node.metadata = {
948
+ ...(node.metadata || {}),
949
+ native_tsrx_function: true,
950
+ };
951
+
952
+ return next(state);
953
+ }
954
+
955
+ /**
956
+ * @param {any} node
957
+ * @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
958
+ * @returns {any}
959
+ */
960
+ function transform_function(node, context) {
961
+ if (node.metadata?.native_tsrx_function || function_has_native_tsrx_return(node)) {
962
+ return transform_native_tsrx_function(node, context);
963
+ }
964
+
965
+ return transform_function_with_hook_helpers(node, context);
966
+ }
967
+
968
+ /**
969
+ * @param {any} node
970
+ * @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
971
+ * @returns {any}
972
+ */
973
+ function transform_native_tsrx_function(node, { next, state, path }) {
974
+ const helper_state =
975
+ state.helper_state || create_helper_state(get_function_helper_base_name(node, path));
976
+ const saved_helper_state = state.helper_state;
977
+ const saved_bindings = state.available_bindings;
978
+ const saved_hook_helpers_enabled = state.hook_helpers_enabled;
979
+
980
+ state.helper_state = helper_state;
981
+ state.hook_helpers_enabled = is_uppercase_function_like(node, path);
982
+ state.available_bindings = merge_binding_maps(
983
+ saved_bindings,
984
+ collect_function_scope_bindings(node),
985
+ );
986
+
987
+ validate_native_tsrx_function_await(node, state);
988
+ expand_native_tsrx_function_returns(node, state);
989
+ rewrite_component_body_conditional_hook_splits(node, state);
990
+
991
+ const inner = /** @type {any} */ (next() ?? node);
992
+
993
+ state.helper_state = saved_helper_state;
994
+ state.available_bindings = saved_bindings;
995
+ state.hook_helpers_enabled = saved_hook_helpers_enabled;
996
+
997
+ ensure_function_metadata(inner, { next: () => inner });
998
+ inner.metadata = {
999
+ ...(inner.metadata || {}),
1000
+ native_tsrx_function: true,
1001
+ };
1002
+ if (!saved_helper_state && (helper_state.helpers.length || helper_state.statics.length)) {
1003
+ inner.metadata.generated_helpers = helper_state.helpers;
1004
+ inner.metadata.generated_statics = helper_state.statics;
1005
+ }
1006
+
1007
+ const wrapped = state.platform.hooks?.wrapNativeFunctionComponent?.(inner, state, path);
1008
+ if (wrapped) {
1009
+ return wrapped;
1010
+ }
1011
+
1012
+ return inner;
1013
+ }
1014
+
1015
+ /**
1016
+ * @param {any} node
1017
+ * @param {TransformContext} transform_context
1018
+ * @returns {void}
1019
+ */
1020
+ function validate_native_tsrx_function_await(node, transform_context) {
1021
+ const await_node = find_first_top_level_await_in_native_tsrx_function(node);
1022
+ if (!await_node) {
1023
+ return;
1024
+ }
1025
+
1026
+ const validator = transform_context.platform.hooks?.validateComponentAwait;
1027
+ if (validator) {
1028
+ validator(await_node, node, transform_context, false, transform_context.source || '');
1029
+ return;
1030
+ }
1031
+
1032
+ if (transform_context.platform.validation.requireUseServerForAwait) {
1033
+ error(
1034
+ 'Top-level `await` in TSRX functions requires a module-level `"use server"` directive.',
1035
+ transform_context.filename,
1036
+ await_node,
1037
+ transform_context.errors,
1038
+ transform_context.comments,
1039
+ );
1040
+ }
1041
+ }
1042
+
1043
+ /**
1044
+ * @param {any} node
1045
+ * @returns {any | null}
1046
+ */
1047
+ function find_first_top_level_await_in_native_tsrx_function(node) {
1048
+ if (
1049
+ node.type === 'ArrowFunctionExpression' &&
1050
+ node.body?.type !== 'BlockStatement' &&
1051
+ node_contains_native_tsrx_template(node.body)
1052
+ ) {
1053
+ return find_first_top_level_await(node.body, false);
1054
+ }
1055
+
1056
+ const body = node.body?.type === 'BlockStatement' ? node.body.body || [] : [];
1057
+ return find_first_top_level_await_in_native_tsrx_statements(body);
1058
+ }
1059
+
1060
+ /**
1061
+ * @param {any[]} statements
1062
+ * @returns {any | null}
1063
+ */
1064
+ function find_first_top_level_await_in_native_tsrx_statements(statements) {
1065
+ for (const statement of statements) {
1066
+ const found = find_first_top_level_await_in_native_tsrx_statement(statement);
1067
+ if (found) return found;
1068
+ }
1069
+ return null;
1070
+ }
1071
+
1072
+ /**
1073
+ * @param {any} statement
1074
+ * @returns {any | null}
1075
+ */
1076
+ function find_first_top_level_await_in_native_tsrx_statement(statement) {
1077
+ if (!statement || typeof statement !== 'object') return null;
1078
+
1079
+ if (statement.type === 'ReturnStatement' && statement.argument?.type === 'Tsrx') {
1080
+ return find_first_top_level_await_in_tsrx_function_body(statement.argument.children || []);
1081
+ }
1082
+
1083
+ if (
1084
+ statement.type === 'ReturnStatement' &&
1085
+ node_contains_native_tsrx_template(statement.argument)
1086
+ ) {
1087
+ return find_first_top_level_await(statement.argument, false);
1088
+ }
1089
+
1090
+ if (is_function_or_class_boundary(statement)) {
1091
+ return null;
1092
+ }
1093
+
1094
+ if (statement.type === 'BlockStatement') {
1095
+ return find_first_top_level_await_in_native_tsrx_statements(statement.body || []);
1096
+ }
1097
+
1098
+ if (statement.type === 'IfStatement') {
1099
+ return (
1100
+ find_first_top_level_await_in_native_tsrx_statement(statement.consequent) ||
1101
+ find_first_top_level_await_in_native_tsrx_statement(statement.alternate)
1102
+ );
1103
+ }
1104
+
1105
+ if (statement.type === 'SwitchStatement') {
1106
+ for (const switch_case of statement.cases || []) {
1107
+ const found = find_first_top_level_await_in_native_tsrx_statements(
1108
+ switch_case.consequent || [],
1109
+ );
1110
+ if (found) return found;
1111
+ }
1112
+ return null;
1113
+ }
1114
+
1115
+ if (statement.type === 'TryStatement') {
1116
+ return (
1117
+ find_first_top_level_await_in_native_tsrx_statement(statement.block) ||
1118
+ find_first_top_level_await_in_native_tsrx_statement(statement.handler?.body) ||
1119
+ find_first_top_level_await_in_native_tsrx_statement(statement.finalizer)
1120
+ );
1121
+ }
1122
+
1123
+ return null;
1124
+ }
1125
+
1126
+ /**
1127
+ * @param {any} node
1128
+ * @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
1129
+ * @returns {any}
1130
+ */
1131
+ function transform_function_with_hook_helpers(node, { next, state, path }) {
1132
+ const has_hook_bearing_tsrx = function_contains_hook_bearing_tsrx(node, state);
1133
+ const has_component_body_hook_split = function_needs_component_body_hook_split(node, state);
1134
+ if (
1135
+ state.helper_state ||
1136
+ !is_uppercase_function_like(node, path) ||
1137
+ (!has_hook_bearing_tsrx && !has_component_body_hook_split)
1138
+ ) {
1139
+ return ensure_function_metadata(node, { next });
1140
+ }
1141
+
1142
+ const helper_state = create_helper_state(get_function_helper_base_name(node, path));
1143
+ const saved_helper_state = state.helper_state;
1144
+ const saved_bindings = state.available_bindings;
1145
+ const saved_hook_helpers_enabled = state.hook_helpers_enabled;
1146
+
1147
+ state.helper_state = helper_state;
1148
+ state.hook_helpers_enabled = true;
1149
+ state.available_bindings = collect_function_scope_bindings(node);
1150
+
1151
+ if (has_component_body_hook_split) {
1152
+ rewrite_component_body_conditional_hook_splits(node, state);
1153
+ }
1154
+
1155
+ const inner = /** @type {any} */ (next() ?? node);
1156
+
1157
+ state.helper_state = saved_helper_state;
1158
+ state.available_bindings = saved_bindings;
1159
+ state.hook_helpers_enabled = saved_hook_helpers_enabled;
1160
+
1161
+ ensure_function_metadata(inner, { next: () => inner });
1162
+ if (helper_state.helpers.length || helper_state.statics.length) {
1163
+ inner.metadata = {
1164
+ ...(inner.metadata || {}),
1165
+ generated_helpers: helper_state.helpers,
1166
+ generated_statics: helper_state.statics,
1167
+ };
1168
+ }
1169
+
1170
+ return inner;
1171
+ }
1172
+
1173
+ /**
1174
+ * @param {any} node
1175
+ * @param {AST.Node[]} [path]
1176
+ * @returns {string}
1177
+ */
1178
+ function get_function_helper_base_name(node, path = []) {
1179
+ return get_function_like_name(node, path) || 'Tsrx';
1180
+ }
1181
+
1182
+ /**
1183
+ * @param {any} node
1184
+ * @param {AST.Node[]} path
1185
+ * @returns {boolean}
1186
+ */
1187
+ function is_uppercase_function_like(node, path) {
1188
+ const name = get_function_like_name(node, path);
1189
+ return !!(name && /^[A-Z]/.test(name));
1190
+ }
1191
+
1192
+ /**
1193
+ * @param {any} node
1194
+ * @param {AST.Node[]} path
1195
+ * @returns {string | null}
1196
+ */
1197
+ function get_function_like_name(node, path) {
1198
+ if (node.id?.type === 'Identifier') {
1199
+ return node.id.name;
1200
+ }
1201
+
1202
+ const parent = /** @type {any} */ (path.at(-1));
1203
+ if (!parent) return null;
1204
+
1205
+ if (parent.type === 'VariableDeclarator' && parent.init === node) {
1206
+ return get_static_binding_name(parent.id);
1207
+ }
1208
+
1209
+ if (parent.type === 'Property' && parent.value === node) {
1210
+ return get_static_property_name(parent.key);
1211
+ }
1212
+
1213
+ if (parent.type === 'MethodDefinition' && parent.value === node) {
1214
+ return get_static_property_name(parent.key);
1215
+ }
1216
+
1217
+ if (parent.type === 'AssignmentExpression' && parent.right === node) {
1218
+ return get_static_binding_name(parent.left);
1219
+ }
1220
+
1221
+ return null;
1222
+ }
1223
+
1224
+ /**
1225
+ * @param {any} node
1226
+ * @returns {string | null}
1227
+ */
1228
+ function get_static_binding_name(node) {
1229
+ if (node?.type === 'Identifier') {
1230
+ return node.name;
1231
+ }
1232
+ if (node?.type === 'MemberExpression' && !node.computed) {
1233
+ return get_static_property_name(node.property);
1234
+ }
1235
+ return null;
1236
+ }
1237
+
1238
+ /**
1239
+ * @param {any} key
1240
+ * @returns {string | null}
1241
+ */
1242
+ function get_static_property_name(key) {
1243
+ if (key?.type === 'Identifier') {
1244
+ return key.name;
1245
+ }
1246
+ if (key?.type === 'Literal' && typeof key.value === 'string') {
1247
+ return key.value;
1248
+ }
1249
+ return null;
1250
+ }
1251
+
1252
+ /**
1253
+ * @param {any} node
1254
+ * @returns {Map<string, AST.Identifier>}
1255
+ */
1256
+ function collect_function_scope_bindings(node) {
1257
+ const bindings = collect_param_bindings(node.params || []);
1258
+ if (node.body?.type === 'BlockStatement') {
1259
+ for (const statement of node.body.body || []) {
1260
+ if (statement.type === 'ReturnStatement' && statement.argument?.type === 'Tsrx') {
1261
+ for (const child of get_tsrx_render_children(statement.argument)) {
1262
+ collect_statement_bindings(child, bindings);
1263
+ }
1264
+ } else {
1265
+ collect_statement_bindings(statement, bindings);
1266
+ }
1267
+ }
1268
+ }
1269
+ return bindings;
1270
+ }
1271
+
1272
+ /**
1273
+ * @param {Map<string, AST.Identifier>} outer
1274
+ * @param {Map<string, AST.Identifier>} inner
1275
+ * @returns {Map<string, AST.Identifier>}
1276
+ */
1277
+ function merge_binding_maps(outer, inner) {
1278
+ const merged = new Map(outer);
1279
+ for (const [name, binding] of inner) {
1280
+ merged.set(name, binding);
1281
+ }
1282
+ return merged;
1283
+ }
1284
+
1285
+ /**
1286
+ * @param {any} node
1287
+ * @returns {boolean}
1288
+ */
1289
+ function function_has_native_tsrx_return(node) {
1290
+ if (!node) return false;
1291
+
1292
+ if (node.type === 'ArrowFunctionExpression' && node.body?.type !== 'BlockStatement') {
1293
+ return node_contains_native_tsrx_template(node.body);
1294
+ }
1295
+
1296
+ const body = node.body?.type === 'BlockStatement' ? node.body.body : [];
1297
+ return statements_contain_native_tsrx_return(body);
1298
+ }
1299
+
1300
+ /**
1301
+ * @param {any[]} statements
1302
+ * @returns {boolean}
1303
+ */
1304
+ function statements_contain_native_tsrx_return(statements) {
1305
+ return statements.some((statement) => statement_contains_native_tsrx_return(statement));
1306
+ }
1307
+
1308
+ /**
1309
+ * @param {any} statement
1310
+ * @returns {boolean}
1311
+ */
1312
+ function statement_contains_native_tsrx_return(statement) {
1313
+ if (!statement || typeof statement !== 'object') return false;
1314
+
1315
+ if (statement.type === 'ReturnStatement') {
1316
+ return node_contains_native_tsrx_template(statement.argument);
1317
+ }
1318
+
1319
+ if (is_function_or_class_boundary(statement)) {
1320
+ return false;
1321
+ }
1322
+
1323
+ if (statement.type === 'BlockStatement') {
1324
+ return statements_contain_native_tsrx_return(statement.body || []);
1325
+ }
1326
+
1327
+ if (statement.type === 'IfStatement') {
1328
+ return (
1329
+ statement_contains_native_tsrx_return(statement.consequent) ||
1330
+ statement_contains_native_tsrx_return(statement.alternate)
1331
+ );
1332
+ }
1333
+
1334
+ if (statement.type === 'SwitchStatement') {
1335
+ return (statement.cases || []).some((/** @type {any} */ c) =>
1336
+ statements_contain_native_tsrx_return(c.consequent || []),
1337
+ );
1338
+ }
1339
+
1340
+ if (statement.type === 'TryStatement') {
1341
+ return (
1342
+ statement_contains_native_tsrx_return(statement.block) ||
1343
+ statement_contains_native_tsrx_return(statement.handler?.body) ||
1344
+ statement_contains_native_tsrx_return(statement.finalizer)
1345
+ );
1346
+ }
1347
+
1348
+ for (const key of Object.keys(statement)) {
1349
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1350
+ continue;
1351
+ }
1352
+ const value = statement[key];
1353
+ if (Array.isArray(value)) {
1354
+ if (statements_contain_native_tsrx_return(value)) return true;
1355
+ } else if (statement_contains_native_tsrx_return(value)) {
1356
+ return true;
1357
+ }
1358
+ }
1359
+
1360
+ return false;
1361
+ }
1362
+
1363
+ /**
1364
+ * @param {any} node
1365
+ * @returns {boolean}
1366
+ */
1367
+ function node_contains_native_tsrx_template(node) {
1368
+ if (!node || typeof node !== 'object') return false;
1369
+ if (node.type === 'Element' || node.type === 'Tsrx') return true;
1370
+
1371
+ if (is_function_or_class_boundary(node)) {
1372
+ return false;
1373
+ }
1374
+
1375
+ if (Array.isArray(node)) {
1376
+ return node.some(node_contains_native_tsrx_template);
1377
+ }
1378
+
1379
+ for (const key of Object.keys(node)) {
1380
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1381
+ continue;
1382
+ }
1383
+ if (node_contains_native_tsrx_template(node[key])) {
1384
+ return true;
1385
+ }
1386
+ }
1387
+
1388
+ return false;
1389
+ }
1390
+
1391
+ /**
1392
+ * @param {any} node
1393
+ * @returns {any}
1394
+ */
1395
+ function collect_tsrx_stylesheet(node) {
1396
+ /** @type {any[]} */
1397
+ const styles = [];
1398
+ collect_style_elements(node.children || [], styles);
1399
+
1400
+ if (styles.length === 0) return null;
1401
+ if (styles.length > 1) {
1402
+ throw new Error('TSRX fragments can only have one style tag');
1403
+ }
1404
+
1405
+ return styles[0];
1406
+ }
1407
+
1408
+ /**
1409
+ * @param {any} node
1410
+ * @param {TransformContext} transform_context
1411
+ * @returns {{ css: any, style_refs: any[] } | null}
1412
+ */
1413
+ function prepare_tsrx_fragment_styles(node, transform_context) {
1414
+ const css = collect_tsrx_stylesheet(node);
1415
+ if (!css) return null;
1416
+
1417
+ const style_refs = collect_style_ref_attributes(node);
1418
+ apply_css_definition_metadata(node, css, style_refs.length > 0);
1419
+ transform_context.stylesheets.push(css);
1420
+ annotate_tsrx_with_hash(
1421
+ node,
1422
+ css.hash,
1423
+ transform_context.platform.jsx.rewriteClassAttr ? 'className' : 'class',
1424
+ transform_context.typeOnly,
1425
+ );
1426
+ return { css, style_refs };
1427
+ }
1428
+
1429
+ /**
1430
+ * @template T
1431
+ * @param {any} node
1432
+ * @param {TransformContext} transform_context
1433
+ * @param {(style_context: { css: any, style_refs: any[] } | null) => T} callback
1434
+ * @returns {T}
1435
+ */
1436
+ function with_tsrx_fragment_styles(node, transform_context, callback) {
1437
+ const style_context = prepare_tsrx_fragment_styles(node, transform_context);
1438
+ return callback(style_context);
1439
+ }
1440
+
1441
+ /**
1442
+ * @param {any} fragment
1443
+ * @param {{ css: any, style_refs: any[] } | null} style_context
1444
+ * @param {TransformContext} transform_context
1445
+ * @returns {AST.Statement[]}
1446
+ */
1447
+ function create_tsrx_style_ref_setup_statements(fragment, style_context, transform_context) {
1448
+ if (!style_context || style_context.style_refs.length === 0) {
1449
+ return [];
1450
+ }
1451
+
1452
+ return create_style_ref_setup_statements(
1453
+ style_context.style_refs,
1454
+ create_style_class_map(fragment, style_context.css),
1455
+ {
1456
+ allowMutableRefTarget: transform_context.platform.jsx.multiRefStrategy === 'array',
1457
+ createTempIdentifier: () =>
1458
+ create_generated_identifier(create_style_ref_temp_name(transform_context)),
1459
+ },
1460
+ );
1461
+ }
1462
+
1463
+ /**
1464
+ * @param {TransformContext} transform_context
1465
+ * @returns {string}
1466
+ */
1467
+ function create_style_ref_temp_name(transform_context) {
1468
+ if (transform_context.helper_state) {
1469
+ return create_helper_name(transform_context.helper_state, 'style_ref');
1470
+ }
1471
+
1472
+ transform_context.local_statement_component_index += 1;
1473
+ return `_tsrx_style_ref_${transform_context.local_statement_component_index}`;
1474
+ }
1475
+
1476
+ /**
1477
+ * @param {any} node
1478
+ * @param {any[]} styles
1479
+ * @returns {void}
1480
+ */
1481
+ function collect_style_elements(node, styles) {
1482
+ if (!node || typeof node !== 'object') return;
1483
+
1484
+ if (Array.isArray(node)) {
1485
+ for (const child of node) {
1486
+ collect_style_elements(child, styles);
1487
+ }
1488
+ return;
1489
+ }
1490
+
1491
+ if (is_style_element(node)) {
1492
+ const stylesheet = node.children?.find(
1493
+ (/** @type {any} */ child) => child.type === 'StyleSheet',
1494
+ );
1495
+ if (stylesheet) {
1496
+ styles.push(stylesheet);
1497
+ }
1498
+ return;
1499
+ }
1500
+
1501
+ if (is_function_or_class_boundary(node) || node.type === 'Tsrx') {
1502
+ return;
1503
+ }
1504
+
1505
+ for (const key of Object.keys(node)) {
1506
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1507
+ continue;
1508
+ }
1509
+ collect_style_elements(node[key], styles);
1510
+ }
1511
+ }
1512
+
1513
+ /**
1514
+ * @param {any} node
1515
+ * @param {string} hash
1516
+ * @param {'class' | 'className'} jsx_class_attr_name
1517
+ * @param {boolean} preserve_style_elements
1518
+ * @returns {void}
1519
+ */
1520
+ function annotate_tsrx_with_hash(node, hash, jsx_class_attr_name, preserve_style_elements) {
1521
+ node.children = (node.children || []).map((/** @type {any} */ statement) =>
1522
+ annotate_with_hash(statement, hash, jsx_class_attr_name, preserve_style_elements),
1523
+ );
1524
+ if (!preserve_style_elements) {
1525
+ node.children = strip_style_elements(node.children);
1526
+ }
1527
+ }
1528
+
1529
+ /**
1530
+ * @param {any} node
1531
+ * @returns {any}
1532
+ */
1533
+ function strip_style_elements(node) {
1534
+ if (!node || typeof node !== 'object') return node;
1535
+
1536
+ if (Array.isArray(node)) {
1537
+ return node
1538
+ .filter((child) => !is_style_element(child))
1539
+ .map((child) => strip_style_elements(child))
1540
+ .filter(Boolean);
1541
+ }
1542
+
1543
+ if (is_style_element(node)) {
1544
+ return null;
1545
+ }
1546
+
1547
+ if (is_function_or_class_boundary(node)) {
1548
+ return node;
1549
+ }
1550
+
1551
+ for (const key of Object.keys(node)) {
1552
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
1553
+ continue;
1554
+ }
1555
+ const value = node[key];
1556
+ if (Array.isArray(value)) {
1557
+ node[key] = strip_style_elements(value);
1558
+ } else if (value && typeof value === 'object') {
1559
+ const stripped = strip_style_elements(value);
1560
+ if (stripped) {
1561
+ node[key] = stripped;
1562
+ }
1563
+ }
1564
+ }
1565
+
1566
+ return node;
1284
1567
  }
1285
1568
 
1286
1569
  /**
1287
- * @param {{ base_name: string, next_id: number, helpers: any[], statics: any[] }} helper_state
1288
- * @param {string} suffix
1289
- * @returns {string}
1570
+ * @param {any} node
1571
+ * @param {TransformContext} transform_context
1572
+ * @returns {void}
1290
1573
  */
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}`;
1574
+ function expand_native_tsrx_function_returns(node, transform_context) {
1575
+ if (node.type === 'ArrowFunctionExpression' && node.body?.type === 'Tsrx') {
1576
+ const body = node.body;
1577
+ const statements = with_tsrx_fragment_styles(body, transform_context, (style_context) => {
1578
+ return [
1579
+ ...create_tsrx_style_ref_setup_statements(body, style_context, transform_context),
1580
+ ...build_render_statements(get_tsrx_render_children(body), true, transform_context),
1581
+ ];
1582
+ });
1583
+ node.body = b.block(mark_native_pretransformed_jsx(statements), body);
1584
+ node.expression = false;
1585
+ return;
1586
+ }
1587
+
1588
+ if (node.body?.type !== 'BlockStatement') {
1589
+ return;
1590
+ }
1591
+
1592
+ node.body.body = expand_native_tsrx_return_statement_list(
1593
+ node.body.body || [],
1594
+ transform_context,
1595
+ );
1294
1596
  }
1295
1597
 
1296
1598
  /**
1297
- * @param {string} base_name
1298
- * @returns {{ base_name: string, next_id: number, helpers: any[], statics: any[] }}
1599
+ * @param {any[]} statements
1600
+ * @param {TransformContext} transform_context
1601
+ * @returns {any[]}
1299
1602
  */
1300
- function create_helper_state(base_name) {
1301
- return {
1302
- base_name,
1303
- next_id: 0,
1304
- helpers: [],
1305
- statics: [],
1306
- };
1603
+ function expand_native_tsrx_return_statement_list(statements, transform_context) {
1604
+ return statements.flatMap((statement) =>
1605
+ expand_native_tsrx_return_statement(statement, transform_context),
1606
+ );
1307
1607
  }
1308
1608
 
1309
1609
  /**
1310
- * @param {any} node
1311
- * @param {{ next: () => any, state: TransformContext }} context
1312
- * @returns {any}
1610
+ * @param {any} statement
1611
+ * @param {TransformContext} transform_context
1612
+ * @returns {any[]}
1313
1613
  */
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 });
1614
+ function expand_native_tsrx_return_statement(statement, transform_context) {
1615
+ if (!statement || typeof statement !== 'object') return [statement];
1616
+
1617
+ if (statement.type === 'ReturnStatement' && statement.argument?.type === 'Tsrx') {
1618
+ const fragment = statement.argument;
1619
+ return with_tsrx_fragment_styles(fragment, transform_context, (style_context) => {
1620
+ return mark_native_pretransformed_jsx([
1621
+ ...create_tsrx_style_ref_setup_statements(fragment, style_context, transform_context),
1622
+ ...build_render_statements(get_tsrx_render_children(fragment), true, transform_context),
1623
+ ]);
1624
+ });
1317
1625
  }
1318
1626
 
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;
1627
+ if (is_function_or_class_boundary(statement)) {
1628
+ return [statement];
1629
+ }
1322
1630
 
1323
- state.helper_state = helper_state;
1324
- state.available_bindings = collect_function_scope_bindings(node);
1631
+ if (statement.type === 'BlockStatement') {
1632
+ statement.body = expand_native_tsrx_return_statement_list(
1633
+ statement.body || [],
1634
+ transform_context,
1635
+ );
1636
+ return [statement];
1637
+ }
1325
1638
 
1326
- const inner = /** @type {any} */ (next() ?? node);
1639
+ if (statement.type === 'IfStatement') {
1640
+ statement.consequent = expand_embedded_native_return_statement(
1641
+ statement.consequent,
1642
+ transform_context,
1643
+ );
1644
+ if (statement.alternate) {
1645
+ statement.alternate = expand_embedded_native_return_statement(
1646
+ statement.alternate,
1647
+ transform_context,
1648
+ );
1649
+ }
1650
+ return [statement];
1651
+ }
1327
1652
 
1328
- state.helper_state = saved_helper_state;
1329
- state.available_bindings = saved_bindings;
1653
+ if (statement.type === 'SwitchStatement') {
1654
+ for (const switch_case of statement.cases || []) {
1655
+ switch_case.consequent = expand_native_tsrx_return_statement_list(
1656
+ switch_case.consequent || [],
1657
+ transform_context,
1658
+ );
1659
+ }
1660
+ return [statement];
1661
+ }
1330
1662
 
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
- };
1663
+ if (statement.type === 'TryStatement') {
1664
+ statement.block = expand_embedded_native_return_statement(statement.block, transform_context);
1665
+ if (statement.handler?.body) {
1666
+ statement.handler.body = expand_embedded_native_return_statement(
1667
+ statement.handler.body,
1668
+ transform_context,
1669
+ );
1670
+ }
1671
+ if (statement.finalizer) {
1672
+ statement.finalizer = expand_embedded_native_return_statement(
1673
+ statement.finalizer,
1674
+ transform_context,
1675
+ );
1676
+ }
1677
+ return [statement];
1338
1678
  }
1339
1679
 
1340
- return inner;
1680
+ return [statement];
1341
1681
  }
1342
1682
 
1343
1683
  /**
1344
- * @param {any} node
1345
- * @returns {string}
1684
+ * @param {any} statement
1685
+ * @param {TransformContext} transform_context
1686
+ * @returns {any}
1346
1687
  */
1347
- function get_function_helper_base_name(node) {
1348
- if (node.id?.type === 'Identifier') {
1349
- return node.id.name;
1688
+ function expand_embedded_native_return_statement(statement, transform_context) {
1689
+ const expanded = expand_native_tsrx_return_statement(statement, transform_context);
1690
+ return expanded.length === 1 ? expanded[0] : b.block(expanded, statement);
1691
+ }
1692
+
1693
+ /**
1694
+ * @template T
1695
+ * @param {T} node
1696
+ * @param {Set<any>} [seen]
1697
+ * @returns {T}
1698
+ */
1699
+ function mark_native_pretransformed_jsx(node, seen = new Set()) {
1700
+ if (node == null || typeof node !== 'object' || seen.has(node)) {
1701
+ return node;
1702
+ }
1703
+ seen.add(node);
1704
+
1705
+ if (Array.isArray(node)) {
1706
+ for (const item of node) mark_native_pretransformed_jsx(item, seen);
1707
+ return node;
1708
+ }
1709
+
1710
+ const as_node = /** @type {any} */ (node);
1711
+ if (as_node.type === 'JSXOpeningElement') {
1712
+ as_node.metadata = {
1713
+ ...(as_node.metadata || {}),
1714
+ native_tsrx_pretransformed: true,
1715
+ };
1350
1716
  }
1351
- return 'Tsrx';
1717
+
1718
+ for (const key of Object.keys(as_node)) {
1719
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
1720
+ continue;
1721
+ }
1722
+ mark_native_pretransformed_jsx(as_node[key], seen);
1723
+ }
1724
+
1725
+ return node;
1352
1726
  }
1353
1727
 
1354
1728
  /**
1355
1729
  * @param {any} node
1356
- * @returns {Map<string, AST.Identifier>}
1730
+ * @returns {any[]}
1357
1731
  */
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;
1732
+ function get_tsrx_render_children(node) {
1733
+ return (node.children || []).filter(
1734
+ (/** @type {any} */ child) =>
1735
+ child &&
1736
+ child.type !== 'EmptyStatement' &&
1737
+ (child.type !== 'JSXText' || child.value.trim() !== ''),
1738
+ );
1362
1739
  }
1363
1740
 
1364
1741
  /**
@@ -1387,8 +1764,7 @@ function collect_descendant_declaration_bindings(node, bindings) {
1387
1764
  if (
1388
1765
  node.type === 'FunctionDeclaration' ||
1389
1766
  node.type === 'FunctionExpression' ||
1390
- node.type === 'ArrowFunctionExpression' ||
1391
- node.type === 'Component'
1767
+ node.type === 'ArrowFunctionExpression'
1392
1768
  ) {
1393
1769
  return;
1394
1770
  }
@@ -1438,8 +1814,7 @@ function node_contains_hook_bearing_tsrx(node, transform_context) {
1438
1814
  if (
1439
1815
  node.type === 'FunctionDeclaration' ||
1440
1816
  node.type === 'FunctionExpression' ||
1441
- node.type === 'ArrowFunctionExpression' ||
1442
- node.type === 'Component'
1817
+ node.type === 'ArrowFunctionExpression'
1443
1818
  ) {
1444
1819
  return false;
1445
1820
  }
@@ -1464,6 +1839,14 @@ function should_use_module_scoped_hook_components(transform_context) {
1464
1839
  return !!(transform_context.helper_state && transform_context.module_scoped_hook_components);
1465
1840
  }
1466
1841
 
1842
+ /**
1843
+ * @param {TransformContext} transform_context
1844
+ * @returns {boolean}
1845
+ */
1846
+ function should_extract_hook_helpers(transform_context) {
1847
+ return !!transform_context.hook_helpers_enabled;
1848
+ }
1849
+
1467
1850
  /**
1468
1851
  * @param {AST.Identifier} helper_id
1469
1852
  * @param {TransformContext} transform_context
@@ -1471,7 +1854,7 @@ function should_use_module_scoped_hook_components(transform_context) {
1471
1854
  */
1472
1855
  function create_module_scoped_hook_component_id(helper_id, transform_context) {
1473
1856
  return create_generated_identifier(
1474
- `${transform_context.helper_state?.base_name || 'Component'}__${helper_id.name}`,
1857
+ `${transform_context.helper_state?.base_name || 'Tsrx'}__${helper_id.name}`,
1475
1858
  );
1476
1859
  }
1477
1860
 
@@ -1670,59 +2053,6 @@ function is_bare_component_invocation(node) {
1670
2053
  return is_component_jsx_name(opening.name);
1671
2054
  }
1672
2055
 
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
2056
  /**
1727
2057
  * @param {AST.Program} program
1728
2058
  * @returns {AST.Program}
@@ -1743,11 +2073,9 @@ function expand_component_helpers(program) {
1743
2073
  }
1744
2074
 
1745
2075
  /**
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.
2076
+ * Generated helper/statics metadata can be carried on function declarations,
2077
+ * variable declarations, object literal members, or export-safe expressions,
2078
+ * so helper expansion reads metadata from that broader set.
1751
2079
  *
1752
2080
  * @param {any} node
1753
2081
  * @returns {{ generated_helpers?: any[], generated_statics?: any[] }[]}
@@ -1803,112 +2131,164 @@ function get_generated_component_metadata_list(node) {
1803
2131
  return metas;
1804
2132
  }
1805
2133
 
2134
+ /**
2135
+ * @param {any[]} render_nodes
2136
+ * @param {any} source_node
2137
+ * @param {boolean} [map_render_node_locations]
2138
+ * @returns {any}
2139
+ */
2140
+ function create_component_return_statement(
2141
+ render_nodes,
2142
+ source_node,
2143
+ map_render_node_locations = true,
2144
+ ) {
2145
+ const cloned = render_nodes.map((node) =>
2146
+ map_render_node_locations ? clone_expression_node(node) : clone_expression_node(node, false),
2147
+ );
2148
+
2149
+ return set_loc(b.return(build_return_expression(cloned) || create_null_literal()), source_node);
2150
+ }
2151
+
1806
2152
  /**
1807
2153
  * @param {any} node
1808
2154
  * @returns {boolean}
1809
2155
  */
1810
- function is_bare_return_statement(node) {
1811
- return node?.type === 'ReturnStatement' && node.argument == null;
2156
+ function is_loop_skip_return_statement(node) {
2157
+ return (
2158
+ node?.type === 'ReturnStatement' &&
2159
+ node.argument == null &&
2160
+ node.metadata?.generated_loop_continue_return === true
2161
+ );
1812
2162
  }
1813
2163
 
1814
2164
  /**
1815
2165
  * @param {any} node
1816
2166
  * @returns {boolean}
1817
2167
  */
1818
- function is_lone_return_if_statement(node) {
2168
+ function is_loop_skip_if_statement(node) {
2169
+ return get_loop_skip_if_consequent_body(node) !== null;
2170
+ }
2171
+
2172
+ /**
2173
+ * @param {any} node
2174
+ * @returns {any[] | null}
2175
+ */
2176
+ function get_loop_skip_if_consequent_body(node) {
1819
2177
  if (node?.type !== 'IfStatement' || node.alternate) {
1820
- return false;
2178
+ return null;
1821
2179
  }
1822
2180
 
1823
- const consequent_body = get_if_consequent_body(node);
2181
+ const consequent_body =
2182
+ node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
1824
2183
 
1825
- return consequent_body.length === 1 && is_bare_return_statement(consequent_body[0]);
2184
+ return consequent_body.some(is_loop_skip_return_statement) ? consequent_body : null;
1826
2185
  }
1827
2186
 
1828
2187
  /**
1829
2188
  * @param {any} node
1830
- * @returns {boolean}
2189
+ * @param {any[]} render_nodes
2190
+ * @param {TransformContext} transform_context
2191
+ * @returns {any}
1831
2192
  */
1832
- function is_returning_if_statement(node) {
1833
- if (node?.type !== 'IfStatement' || node.alternate) {
1834
- return false;
1835
- }
2193
+ function create_component_loop_skip_if_statement(node, render_nodes, transform_context) {
2194
+ const consequent_body = /** @type {any[]} */ (get_loop_skip_if_consequent_body(node));
2195
+ const branch_statements = build_render_statements(consequent_body, true, transform_context);
2196
+ prepend_render_nodes_to_return_statements(branch_statements, render_nodes);
1836
2197
 
1837
- return get_if_consequent_body(node).some(is_bare_return_statement);
2198
+ return set_loc(b.if(node.test, set_loc(b.block(branch_statements), node.consequent), null), node);
1838
2199
  }
1839
2200
 
1840
2201
  /**
1841
- * @param {any} node
1842
- * @returns {any[]}
2202
+ * @param {any[]} statements
2203
+ * @param {any[]} render_nodes
2204
+ * @returns {void}
1843
2205
  */
1844
- function get_if_consequent_body(node) {
1845
- return node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
2206
+ function prepend_render_nodes_to_return_statements(statements, render_nodes) {
2207
+ if (render_nodes.length === 0) {
2208
+ return;
2209
+ }
2210
+
2211
+ for (const statement of statements) {
2212
+ prepend_render_nodes_to_return_statement(statement, render_nodes, false);
2213
+ }
1846
2214
  }
1847
2215
 
1848
2216
  /**
2217
+ * @param {any} node
1849
2218
  * @param {any[]} render_nodes
1850
- * @param {any} source_node
1851
- * @param {boolean} [map_render_node_locations]
1852
- * @returns {any}
2219
+ * @param {boolean} inside_nested_function
2220
+ * @returns {void}
1853
2221
  */
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
- );
2222
+ function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nested_function) {
2223
+ if (!node || typeof node !== 'object') {
2224
+ return;
2225
+ }
2226
+
2227
+ if (
2228
+ node.type === 'FunctionDeclaration' ||
2229
+ node.type === 'FunctionExpression' ||
2230
+ node.type === 'ArrowFunctionExpression'
2231
+ ) {
2232
+ inside_nested_function = true;
2233
+ }
2234
+
2235
+ if (!inside_nested_function && node.type === 'ReturnStatement') {
2236
+ node.argument = combine_render_return_argument(render_nodes, node.argument);
2237
+ return;
2238
+ }
2239
+
2240
+ if (Array.isArray(node)) {
2241
+ for (const child of node) {
2242
+ prepend_render_nodes_to_return_statement(child, render_nodes, inside_nested_function);
2243
+ }
2244
+ return;
2245
+ }
1862
2246
 
1863
- return set_loc(b.return(build_return_expression(cloned) || create_null_literal()), source_node);
2247
+ for (const key of Object.keys(node)) {
2248
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
2249
+ continue;
2250
+ }
2251
+ prepend_render_nodes_to_return_statement(node[key], render_nodes, inside_nested_function);
2252
+ }
1864
2253
  }
1865
2254
 
1866
2255
  /**
1867
- * @param {any} node
1868
2256
  * @param {any[]} render_nodes
2257
+ * @param {any} return_argument
1869
2258
  * @returns {any}
1870
2259
  */
1871
- function create_component_lone_return_if_statement(node, render_nodes) {
1872
- const consequent_body = get_if_consequent_body(node);
2260
+ function combine_render_return_argument(render_nodes, return_argument) {
2261
+ const combined = render_nodes.map((node) => clone_expression_node(node, false));
1873
2262
 
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
- );
2263
+ if (return_argument != null && !is_null_literal(return_argument)) {
2264
+ combined.push(return_argument_to_render_node(return_argument));
2265
+ }
2266
+
2267
+ return build_return_expression(combined) || create_null_literal();
1885
2268
  }
1886
2269
 
1887
2270
  /**
1888
- * @param {any} node
1889
- * @param {any[]} render_nodes
1890
- * @param {TransformContext} transform_context
2271
+ * @param {any} argument
1891
2272
  * @returns {any}
1892
2273
  */
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);
2274
+ function return_argument_to_render_node(argument) {
2275
+ if (
2276
+ argument?.type === 'JSXElement' ||
2277
+ argument?.type === 'JSXFragment' ||
2278
+ argument?.type === 'JSXExpressionContainer'
2279
+ ) {
2280
+ return argument;
2281
+ }
1897
2282
 
1898
- return set_loc(b.if(node.test, set_loc(b.block(branch_statements), node.consequent), null), node);
2283
+ return to_jsx_expression_container(argument);
1899
2284
  }
1900
2285
 
1901
2286
  /**
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}
2287
+ * @param {any} node
2288
+ * @returns {boolean}
1909
2289
  */
1910
- function combined_return_statement(render_nodes, jsx_child) {
1911
- return b.return(combine_render_return_argument(render_nodes, jsx_child));
2290
+ function is_null_literal(node) {
2291
+ return node?.type === 'Literal' && node.value == null;
1912
2292
  }
1913
2293
 
1914
2294
  /**
@@ -1932,51 +2312,6 @@ function build_array_normalization_decls(source_id, source_expr) {
1932
2312
  return { source_decl, source_normalize_decl };
1933
2313
  }
1934
2314
 
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
2315
  /**
1981
2316
  * Hoist the helper for a hook-bearing for-of body out of the iteration
1982
2317
  * callback so the helper is declared once per render rather than re-bound on
@@ -2088,7 +2423,7 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
2088
2423
  transform_context.available_bindings = fn_saved_bindings;
2089
2424
 
2090
2425
  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 };
2426
+ helper_fn.metadata = { path: [], is_method: false };
2092
2427
 
2093
2428
  let helper_decl;
2094
2429
  if (transform_context.helper_state && use_module_scoped_component) {
@@ -2236,142 +2571,6 @@ function create_helper_props_type_literal_with_typeof_flags(bindings, aliases, u
2236
2571
  );
2237
2572
  }
2238
2573
 
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
2574
  /**
2376
2575
  * @param {any} node
2377
2576
  * @param {TransformContext} transform_context
@@ -2410,163 +2609,40 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
2410
2609
  raw_children,
2411
2610
  attributes,
2412
2611
  transform_context,
2413
- );
2414
-
2415
- if (child_transform) {
2416
- 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 };
2612
+ );
2613
+
2614
+ if (child_transform) {
2615
+ children = child_transform.children;
2616
+ if (typeof child_transform.selfClosing === 'boolean') {
2617
+ selfClosing = child_transform.selfClosing;
2517
2618
  }
2619
+ } else {
2620
+ children = create_element_children(walked_children, transform_context);
2518
2621
  }
2622
+ const has_unmappable_attribute = attributes.some(
2623
+ (/** @type {any} */ attribute) => attribute?.metadata?.has_unmappable_value,
2624
+ );
2519
2625
 
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
- }
2626
+ const opening_element_node = b.jsx_opening_element(
2627
+ name,
2628
+ attributes,
2629
+ selfClosing,
2630
+ node.openingElement?.typeArguments,
2631
+ );
2632
+ const openingElement = has_unmappable_attribute
2633
+ ? opening_element_node
2634
+ : set_loc(opening_element_node, node.openingElement || node);
2547
2635
 
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
- }
2636
+ const closingElement = selfClosing
2637
+ ? null
2638
+ : set_loc(
2639
+ b.jsx_closing_element(
2640
+ clone_jsx_name(name, node.closingElement?.name || node.closingElement || node),
2641
+ ),
2642
+ node.closingElement || node,
2643
+ );
2557
2644
 
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
- );
2645
+ return set_loc(b.jsx_element_fresh(openingElement, closingElement, children), node);
2570
2646
  }
2571
2647
 
2572
2648
  /**
@@ -2616,18 +2692,14 @@ function child_contains_return_semantics(node) {
2616
2692
  return false;
2617
2693
  }
2618
2694
 
2619
- if (
2620
- (node.type === 'ReturnStatement' && node.argument == null) ||
2621
- is_lone_return_if_statement(node)
2622
- ) {
2695
+ if (node.type === 'ReturnStatement') {
2623
2696
  return true;
2624
2697
  }
2625
2698
 
2626
2699
  if (
2627
2700
  node.type === 'FunctionDeclaration' ||
2628
2701
  node.type === 'FunctionExpression' ||
2629
- node.type === 'ArrowFunctionExpression' ||
2630
- node.type === 'Component'
2702
+ node.type === 'ArrowFunctionExpression'
2631
2703
  ) {
2632
2704
  return false;
2633
2705
  }
@@ -2662,7 +2734,10 @@ function is_inline_element_child(node) {
2662
2734
  * @returns {ESTreeJSX.JSXExpressionContainer}
2663
2735
  */
2664
2736
  function statement_body_to_jsx_child(body_nodes, transform_context) {
2665
- if (body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
2737
+ if (
2738
+ should_extract_hook_helpers(transform_context) &&
2739
+ body_contains_top_level_hook_call(body_nodes, transform_context, true)
2740
+ ) {
2666
2741
  return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
2667
2742
  }
2668
2743
 
@@ -3223,8 +3298,7 @@ function references_name_in_set(node, names) {
3223
3298
  if (
3224
3299
  node.type === 'FunctionDeclaration' ||
3225
3300
  node.type === 'FunctionExpression' ||
3226
- node.type === 'ArrowFunctionExpression' ||
3227
- node.type === 'Component'
3301
+ node.type === 'ArrowFunctionExpression'
3228
3302
  ) {
3229
3303
  return false;
3230
3304
  }
@@ -3369,8 +3443,7 @@ function find_first_hook_call_name(node) {
3369
3443
  if (
3370
3444
  node.type === 'FunctionDeclaration' ||
3371
3445
  node.type === 'FunctionExpression' ||
3372
- node.type === 'ArrowFunctionExpression' ||
3373
- node.type === 'Component'
3446
+ node.type === 'ArrowFunctionExpression'
3374
3447
  ) {
3375
3448
  return null;
3376
3449
  }
@@ -3477,7 +3550,6 @@ export function create_hook_safe_helper(
3477
3550
  params,
3478
3551
  b.block(build_render_statements(body_nodes, true, transform_context)),
3479
3552
  );
3480
- helper_fn.metadata.is_component = true;
3481
3553
  helper_fn.metadata.is_method = false;
3482
3554
 
3483
3555
  transform_context.available_bindings = saved_bindings;
@@ -3697,108 +3769,6 @@ function get_body_source_node(body_nodes) {
3697
3769
  return first;
3698
3770
  }
3699
3771
 
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
3772
  /**
3803
3773
  * @param {any} node
3804
3774
  * @param {TransformContext} transform_context
@@ -3821,8 +3791,6 @@ function to_jsx_child(node, transform_context) {
3821
3791
  return to_jsx_expression_container(to_text_expression(node.expression, node), node);
3822
3792
  case 'TSRXExpression':
3823
3793
  return to_jsx_expression_container(node.expression, node);
3824
- case 'Html':
3825
- return recover_invalid_html_child(node, transform_context);
3826
3794
  case 'IfStatement':
3827
3795
  return (
3828
3796
  transform_context.platform.hooks?.controlFlow?.ifStatement ?? if_statement_to_jsx_child
@@ -3846,7 +3814,7 @@ function to_jsx_child(node, transform_context) {
3846
3814
  }
3847
3815
 
3848
3816
  /**
3849
- * Lower a `<tsrx>` node's native TSRX template body to a JSX expression.
3817
+ * Lower a native TSRX fragment body to a JSX expression.
3850
3818
  * Unlike `<tsx>`, children have already been parsed and transformed through
3851
3819
  * the normal TSRX Element/Text/control-flow visitors.
3852
3820
  *
@@ -3902,7 +3870,7 @@ function tsrx_node_to_jsx_expression(node, transform_context, in_jsx_child = fal
3902
3870
  }
3903
3871
 
3904
3872
  /**
3905
- * Explicit return values inside expression-position `<tsrx>` templates are JavaScript
3873
+ * Explicit return values inside expression-position native templates are JavaScript
3906
3874
  * values, so keep them out of platform render control flow.
3907
3875
  *
3908
3876
  * @param {any[]} body_nodes
@@ -3958,8 +3926,7 @@ function body_contains_top_level_return_value(node) {
3958
3926
  node.type === 'FunctionExpression' ||
3959
3927
  node.type === 'ArrowFunctionExpression' ||
3960
3928
  node.type === 'ClassDeclaration' ||
3961
- node.type === 'ClassExpression' ||
3962
- node.type === 'Component'
3929
+ node.type === 'ClassExpression'
3963
3930
  ) {
3964
3931
  return false;
3965
3932
  }
@@ -4153,7 +4120,12 @@ function find_key_expression_in_body(body_nodes) {
4153
4120
  * @returns {any}
4154
4121
  */
4155
4122
  function continue_to_bare_return(source_node) {
4156
- return set_loc(b.return(null), source_node);
4123
+ const node = set_loc(b.return(null), source_node);
4124
+ node.metadata = {
4125
+ ...(node.metadata || {}),
4126
+ generated_loop_continue_return: true,
4127
+ };
4128
+ return node;
4157
4129
  }
4158
4130
 
4159
4131
  /**
@@ -4214,7 +4186,7 @@ function is_loop_statement(node) {
4214
4186
  function for_of_statement_to_jsx_child(node, transform_context) {
4215
4187
  if (node.await) {
4216
4188
  error(
4217
- `${transform_context.platform.name} TSRX does not support \`for await...of\` in component templates.`,
4189
+ `${transform_context.platform.name} TSRX does not support \`for await...of\` in TSRX templates.`,
4218
4190
  transform_context.filename,
4219
4191
  node,
4220
4192
  transform_context.errors,
@@ -4228,7 +4200,9 @@ function for_of_statement_to_jsx_child(node, transform_context) {
4228
4200
  node.body.type === 'BlockStatement' ? node.body.body : [node.body],
4229
4201
  )
4230
4202
  );
4231
- const has_hooks = body_contains_top_level_hook_call(loop_body, transform_context, true);
4203
+ const has_hooks =
4204
+ should_extract_hook_helpers(transform_context) &&
4205
+ body_contains_top_level_hook_call(loop_body, transform_context, true);
4232
4206
  const body_key_expression = find_key_expression_in_body(loop_body);
4233
4207
  const explicit_key_expression =
4234
4208
  body_key_expression ?? (node.key ? clone_expression_node(node.key) : undefined);
@@ -4457,7 +4431,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4457
4431
 
4458
4432
  if (finalizer) {
4459
4433
  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.`,
4434
+ `${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
4435
  transform_context.filename,
4462
4436
  finalizer,
4463
4437
  transform_context.errors,
@@ -4467,7 +4441,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4467
4441
 
4468
4442
  if (!pending && !handler) {
4469
4443
  error(
4470
- 'Component try statements must have a `pending` or `catch` block.',
4444
+ 'TSRX try statements must have a `pending` or `catch` block.',
4471
4445
  transform_context.filename,
4472
4446
  node,
4473
4447
  transform_context.errors,
@@ -4491,7 +4465,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4491
4465
  const try_body = node.block.body || [];
4492
4466
  if (!try_body.some(is_jsx_child)) {
4493
4467
  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.',
4468
+ '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
4469
  transform_context.filename,
4496
4470
  node.block,
4497
4471
  transform_context.errors,
@@ -4501,7 +4475,7 @@ function try_statement_to_jsx_child(node, transform_context) {
4501
4475
  const pending_body = pending.body || [];
4502
4476
  if (pending_body.length > 0 && !pending_body.some(is_jsx_child)) {
4503
4477
  error(
4504
- 'Component try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
4478
+ 'TSRX try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
4505
4479
  transform_context.filename,
4506
4480
  pending,
4507
4481
  transform_context.errors,
@@ -4714,12 +4688,14 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
4714
4688
  transform_context.needs_merge_refs && platform.imports.mergeRefs
4715
4689
  ? platform.imports.mergeRefs
4716
4690
  : null;
4717
- const ref_prop_source =
4718
- transform_context.needs_ref_prop && platform.imports.refProp ? platform.imports.refProp : null;
4719
4691
  const normalize_spread_props_source =
4720
4692
  transform_context.needs_normalize_spread_props && platform.imports.refProp
4721
4693
  ? platform.imports.refProp
4722
4694
  : null;
4695
+ const normalize_spread_props_for_ref_attr_source =
4696
+ transform_context.needs_normalize_spread_props_for_ref_attr && platform.imports.refProp
4697
+ ? platform.imports.refProp
4698
+ : null;
4723
4699
 
4724
4700
  /** @type {Map<string, any[]>} */
4725
4701
  const ref_imports = new Map();
@@ -4732,19 +4708,22 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
4732
4708
  );
4733
4709
  }
4734
4710
 
4735
- if (ref_prop_source !== null) {
4711
+ if (normalize_spread_props_source !== null) {
4736
4712
  add_ref_import_specifier(
4737
4713
  ref_imports,
4738
- ref_prop_source,
4739
- b.import_specifier('create_ref_prop', CREATE_REF_PROP_INTERNAL_NAME),
4714
+ normalize_spread_props_source,
4715
+ b.import_specifier('normalize_spread_props', NORMALIZE_SPREAD_PROPS_INTERNAL_NAME),
4740
4716
  );
4741
4717
  }
4742
4718
 
4743
- if (normalize_spread_props_source !== null) {
4719
+ if (normalize_spread_props_for_ref_attr_source !== null) {
4744
4720
  add_ref_import_specifier(
4745
4721
  ref_imports,
4746
- normalize_spread_props_source,
4747
- b.import_specifier('normalize_spread_props', NORMALIZE_SPREAD_PROPS_INTERNAL_NAME),
4722
+ normalize_spread_props_for_ref_attr_source,
4723
+ b.import_specifier(
4724
+ 'normalize_spread_props_for_ref_attr',
4725
+ NORMALIZE_SPREAD_PROPS_FOR_REF_ATTR_INTERNAL_NAME,
4726
+ ),
4748
4727
  );
4749
4728
  }
4750
4729
 
@@ -4779,11 +4758,9 @@ function add_ref_import_specifier(imports, source, specifier) {
4779
4758
  function create_render_if_statement(node, transform_context) {
4780
4759
  const consequent_body =
4781
4760
  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
- );
4761
+ const consequent_has_hooks =
4762
+ should_extract_hook_helpers(transform_context) &&
4763
+ body_contains_top_level_hook_call(consequent_body, transform_context, true);
4787
4764
 
4788
4765
  let alternate = null;
4789
4766
  if (node.alternate) {
@@ -4791,11 +4768,9 @@ function create_render_if_statement(node, transform_context) {
4791
4768
  alternate = create_render_if_statement(node.alternate, transform_context);
4792
4769
  } else {
4793
4770
  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
- );
4771
+ const alternate_has_hooks =
4772
+ should_extract_hook_helpers(transform_context) &&
4773
+ body_contains_top_level_hook_call(alternate_body, transform_context, true);
4799
4774
  alternate = set_loc(
4800
4775
  b.block(
4801
4776
  alternate_has_hooks
@@ -4912,7 +4887,10 @@ export function plan_switch_lift(switch_node, transform_context) {
4912
4887
  const needs_helper = case_info.map(
4913
4888
  (/** @type {{ own_body: any[], has_terminator: boolean }} */ info, /** @type {number} */ k) => {
4914
4889
  if (info.own_body.length === 0) return false;
4915
- if (body_contains_top_level_hook_call(info.own_body, transform_context, true)) {
4890
+ if (
4891
+ should_extract_hook_helpers(transform_context) &&
4892
+ body_contains_top_level_hook_call(info.own_body, transform_context, true)
4893
+ ) {
4916
4894
  return true;
4917
4895
  }
4918
4896
  if (k === 0) return false;
@@ -5052,7 +5030,7 @@ function build_switch_with_lift(switch_node, transform_context) {
5052
5030
  let has_terminal = false;
5053
5031
 
5054
5032
  for (const child of own_body) {
5055
- if (is_bare_return_statement(child)) {
5033
+ if (is_loop_skip_return_statement(child)) {
5056
5034
  case_body.push(create_component_return_statement(render_nodes, child));
5057
5035
  has_terminal = true;
5058
5036
  break;
@@ -5155,13 +5133,13 @@ function to_jsx_expression_container(expression, source_node = expression) {
5155
5133
  * the default "map over `to_jsx_attribute`" via
5156
5134
  * `hooks.transformElementAttributes`. Whether or not the hook is used,
5157
5135
  * the result is run through `merge_duplicate_refs` so platforms with a
5158
- * `multiRefStrategy` get duplicate-`ref` handling for free.
5136
+ * `multiRefStrategy` can compose an explicit `ref={...}` with compiler-
5137
+ * synthesized refs created for host spreads.
5159
5138
  *
5160
5139
  * Before lowering, the raw attribute list is validated to reject elements
5161
5140
  * with more than one TSX-style `ref={...}` attribute — that shape produces
5162
5141
  * 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.
5142
+ * which TypeScript can't type cleanly).
5165
5143
  *
5166
5144
  * @param {any[]} attrs
5167
5145
  * @param {TransformContext} transform_context
@@ -5171,7 +5149,6 @@ function to_jsx_expression_container(expression, source_node = expression) {
5171
5149
  function transform_element_attributes_dispatch(attrs, transform_context, element) {
5172
5150
  validate_at_most_one_ref_attribute(attrs, transform_context);
5173
5151
  const is_component = is_component_like_element(element);
5174
- attrs = normalize_named_ref_attributes(attrs, !is_component, transform_context);
5175
5152
  const preprocess = transform_context.platform.hooks?.preprocessElementAttributes;
5176
5153
  if (preprocess) {
5177
5154
  attrs = preprocess(attrs, transform_context, element);
@@ -5180,43 +5157,12 @@ function transform_element_attributes_dispatch(attrs, transform_context, element
5180
5157
  const result = hook
5181
5158
  ? hook(attrs, transform_context, element)
5182
5159
  : 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
5160
  return merge_duplicate_refs(
5190
5161
  normalize_host_ref_spreads(result, !is_component, transform_context),
5191
5162
  transform_context,
5192
5163
  );
5193
5164
  }
5194
5165
 
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
5166
  /**
5221
5167
  * @param {any} element
5222
5168
  * @returns {boolean}
@@ -5242,48 +5188,6 @@ function is_component_like_jsx_name(name) {
5242
5188
  return false;
5243
5189
  }
5244
5190
 
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
5191
  /**
5288
5192
  * @param {any[]} attrs
5289
5193
  * @param {boolean} is_host
@@ -5305,8 +5209,15 @@ function normalize_host_ref_spreads(attrs, is_host, transform_context) {
5305
5209
  return [attr];
5306
5210
  }
5307
5211
 
5308
- transform_context.needs_normalize_spread_props = true;
5309
- const normalized = b.call(NORMALIZE_SPREAD_PROPS_INTERNAL_NAME, attr.argument);
5212
+ const normalize_helper = needs_synthetic_spread_ref
5213
+ ? NORMALIZE_SPREAD_PROPS_FOR_REF_ATTR_INTERNAL_NAME
5214
+ : NORMALIZE_SPREAD_PROPS_INTERNAL_NAME;
5215
+ if (needs_synthetic_spread_ref) {
5216
+ transform_context.needs_normalize_spread_props_for_ref_attr = true;
5217
+ } else {
5218
+ transform_context.needs_normalize_spread_props = true;
5219
+ }
5220
+ const normalized = b.call(normalize_helper, attr.argument);
5310
5221
 
5311
5222
  if (needs_synthetic_spread_ref) {
5312
5223
  const normalized_id = create_generated_identifier(
@@ -5323,7 +5234,7 @@ function normalize_host_ref_spreads(attrs, is_host, transform_context) {
5323
5234
  attr,
5324
5235
  );
5325
5236
  ref_attr.metadata = { ...(ref_attr.metadata || {}) };
5326
- /** @type {any} */ (ref_attr.metadata).from_ref_keyword = true;
5237
+ /** @type {any} */ (ref_attr.metadata).synthetic_ref = true;
5327
5238
  add_jsx_setup_declaration(spread, b.let(clone_identifier(normalized_id), normalized));
5328
5239
 
5329
5240
  return [spread, ref_attr];
@@ -5412,69 +5323,10 @@ function wrap_jsx_setup_declarations(expression, in_jsx_child) {
5412
5323
  return in_jsx_child ? to_jsx_expression_container(call, expression) : call;
5413
5324
  }
5414
5325
 
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
5326
  /**
5472
5327
  * 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
5328
+ * This validator runs over the raw, pre-lowering attribute list so each
5329
+ * shape is still distinguishable by `type`. Ripple `Element` attributes have type `Attribute` with an
5478
5330
  * `Identifier` name (the parser normalizes `JSXAttribute`/`JSXIdentifier`
5479
5331
  * for non-Tsx elements); inside `<tsx:react>` compat blocks they retain
5480
5332
  * the original `JSXAttribute`/`JSXIdentifier` shape, so we accept both.
@@ -5510,7 +5362,7 @@ export function validate_at_most_one_ref_attribute(raw_attrs, transform_context)
5510
5362
  }
5511
5363
  error(
5512
5364
  '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.",
5365
+ 'Use a single array-valued ref such as `ref={[a, b]}` where the target framework supports multiple refs.',
5514
5366
  transform_context?.filename ?? null,
5515
5367
  node,
5516
5368
  transform_context?.errors,
@@ -5520,11 +5372,9 @@ export function validate_at_most_one_ref_attribute(raw_attrs, transform_context)
5520
5372
  }
5521
5373
 
5522
5374
  /**
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`:
5375
+ * Collapse an explicit `ref={...}` plus compiler-synthesized spread refs into
5376
+ * one attribute. The shape of the merged value depends on
5377
+ * `platform.jsx.multiRefStrategy`:
5528
5378
  *
5529
5379
  * - `'merge-refs'` — emit `ref={__mergeRefs(a, b, ...)}` and flag
5530
5380
  * `needs_merge_refs` so an import is injected later. React and Preact
@@ -5550,7 +5400,7 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
5550
5400
  for (const attr of jsx_attrs) {
5551
5401
  if (!is_jsx_ref_attribute(attr)) continue;
5552
5402
  count += 1;
5553
- if (!attr.metadata?.from_ref_keyword) tsx_form_count += 1;
5403
+ if (!attr.metadata?.synthetic_ref) tsx_form_count += 1;
5554
5404
  }
5555
5405
  if (count <= 1) return jsx_attrs;
5556
5406
  // Two or more genuine `ref={...}` (TSX-form) attributes are already a
@@ -5571,7 +5421,7 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
5571
5421
  // Inherit loc from the (at most one) `ref={expr}`-form attribute so
5572
5422
  // the kept `ref` keyword in the generated `ref={__mergeRefs(...)}`
5573
5423
  // retains a source mapping back to its original `ref=` keyword.
5574
- if (!source_attr && !attr.metadata?.from_ref_keyword) {
5424
+ if (!source_attr && !attr.metadata?.synthetic_ref) {
5575
5425
  source_attr = attr;
5576
5426
  }
5577
5427
  } else {
@@ -5629,8 +5479,9 @@ function is_jsx_ref_attribute(attr) {
5629
5479
  * identifiers and avoids shadowing user-declared `mergeRefs` symbols.
5630
5480
  */
5631
5481
  export const MERGE_REFS_INTERNAL_NAME = '__mergeRefs';
5632
- export const CREATE_REF_PROP_INTERNAL_NAME = '__create_ref_prop';
5633
5482
  export const NORMALIZE_SPREAD_PROPS_INTERNAL_NAME = '__normalize_spread_props';
5483
+ export const NORMALIZE_SPREAD_PROPS_FOR_REF_ATTR_INTERNAL_NAME =
5484
+ '__normalize_spread_props_for_ref_attr';
5634
5485
  export const MAP_ITERABLE_INTERNAL_NAME = '__map_iterable';
5635
5486
  export const ITERATION_VALUE_INTERNAL_NAME = '__IterationValue';
5636
5487
 
@@ -5652,17 +5503,6 @@ const MATHML_REF_TAG_NAMES = new Set(
5652
5503
  ),
5653
5504
  );
5654
5505
 
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
5506
  /**
5667
5507
  * @param {any} element
5668
5508
  * @param {'html' | 'svg' | 'mathml'} [namespace]
@@ -5751,27 +5591,6 @@ function create_tag_name_map_ref_type(map_name, tag_name) {
5751
5591
  export function to_jsx_attribute(attr, transform_context) {
5752
5592
  if (!attr) return attr;
5753
5593
  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
5594
  return attr;
5776
5595
  }
5777
5596
  if (attr.type === 'JSXSpreadAttribute') {
@@ -5786,26 +5605,6 @@ export function to_jsx_attribute(attr, transform_context) {
5786
5605
  attr,
5787
5606
  );
5788
5607
  }
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
5608
  // Platforms that expect React-style DOM attrs (React) rewrite `class` to
5810
5609
  // `className`; Preact and Solid accept `class` natively and keep it.
5811
5610
  let attr_name = attr.name;
@@ -5824,28 +5623,15 @@ export function to_jsx_attribute(attr, transform_context) {
5824
5623
  attr_name && attr_name.type === 'Identifier' ? identifier_to_jsx_name(attr_name) : attr_name;
5825
5624
 
5826
5625
  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
5626
  if (value) {
5832
5627
  if (value.type === 'Literal' && typeof value.value === 'string') {
5833
5628
  // 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
5629
  } else if (value.type !== 'JSXExpressionContainer') {
5837
5630
  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
5631
  }
5843
5632
  }
5844
5633
 
5845
5634
  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
5635
 
5850
5636
  if (value_has_unmappable_jsx_loc(value)) {
5851
5637
  /** @type {any} */ (jsx_attribute.metadata).has_unmappable_value = true;
@@ -5867,26 +5653,6 @@ function value_has_unmappable_jsx_loc(value) {
5867
5653
  );
5868
5654
  }
5869
5655
 
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
5656
  /**
5891
5657
  * @param {any} node
5892
5658
  * @param {TransformContext} transform_context