@tsrx/core 0.1.20 → 0.1.24

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.
@@ -7,6 +7,7 @@ import { print } from 'esrap';
7
7
  import { error } from '../../errors.js';
8
8
  import { analyze_css } from '../../analyze/css-analyze.js';
9
9
  import { prune_css } from '../../analyze/prune.js';
10
+ import { create_scopes, ScopeRoot } from '../../scope.js';
10
11
  import {
11
12
  in_jsx_child_context,
12
13
  set_node_path_metadata,
@@ -24,7 +25,6 @@ import {
24
25
  identifier_to_jsx_name,
25
26
  is_bare_render_expression,
26
27
  is_component_jsx_name,
27
- is_dynamic_element_id,
28
28
  is_jsx_child,
29
29
  set_loc,
30
30
  to_text_expression,
@@ -63,6 +63,17 @@ const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
63
63
  'Hook callbacks inside conditional or repeated TSRX scopes must not mutate bindings declared outside the generated hook component.';
64
64
  const TEMPLATE_FRAGMENT_ERROR =
65
65
  'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only in expression position.';
66
+ const TSRX_FOR_RETURN_ERROR =
67
+ 'Return statements are not allowed inside TSRX template for...of loops. Filter the iterable before rendering or use an @empty fallback for empty lists.';
68
+ const TSRX_FOR_BREAK_ERROR =
69
+ 'Break statements are not allowed inside TSRX template for...of loops.';
70
+ const TSRX_FOR_CONTINUE_ERROR =
71
+ 'Continue statements are not allowed inside TSRX template for...of loops. Filter the iterable before rendering.';
72
+ const TSRX_IF_RETURN_ERROR =
73
+ 'Return statements are not allowed inside TSRX template @if blocks. Move the return before the template output or render conditionally instead.';
74
+ const TSRX_IF_BREAK_ERROR = 'Break statements are not allowed inside TSRX template @if blocks.';
75
+ const TSRX_IF_CONTINUE_ERROR =
76
+ 'Continue statements are not allowed inside TSRX template @if blocks. Filter before rendering or use conditional output instead.';
66
77
 
67
78
  /**
68
79
  * @param {AST.Node} node
@@ -142,6 +153,156 @@ function is_function_or_class_boundary(node) {
142
153
  );
143
154
  }
144
155
 
156
+ /**
157
+ * @param {any} node
158
+ * @param {boolean} [inside_function]
159
+ * @param {Set<any>} [seen]
160
+ * @returns {void}
161
+ */
162
+ function mark_nested_function_return_jsx(node, inside_function = false, seen = new Set()) {
163
+ if (!node || typeof node !== 'object' || seen.has(node)) return;
164
+ seen.add(node);
165
+
166
+ if (Array.isArray(node)) {
167
+ for (const item of node) mark_nested_function_return_jsx(item, inside_function, seen);
168
+ return;
169
+ }
170
+
171
+ const now_inside = inside_function || is_function_or_class_boundary(node);
172
+
173
+ if (
174
+ now_inside &&
175
+ node.type === 'ReturnStatement' &&
176
+ (node.argument?.type === 'JSXFragment' ||
177
+ node.argument?.type === 'JSXElement' ||
178
+ node.argument?.type === 'JSXStyleElement')
179
+ ) {
180
+ node.argument.metadata = { ...(node.argument.metadata || {}), native_tsrx: true };
181
+ }
182
+
183
+ for (const key of Object.keys(node)) {
184
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
185
+ mark_nested_function_return_jsx(node[key], now_inside, seen);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Flatten a `@{ … }` code block that appears as an element/fragment child into
191
+ * the element's children list: its setup statements followed by its single
192
+ * render output. The render pipeline already handles interleaved setup
193
+ * statements and JSX children. This is the element-scoped equivalent of
194
+ * `transform_function`'s body lowering — function and arrow bodies are never
195
+ * element children, so they are untouched here.
196
+ * @param {any} node
197
+ * @param {Set<any>} [seen]
198
+ * @returns {void}
199
+ */
200
+ function expand_child_code_blocks(node, seen = new Set()) {
201
+ if (!node || typeof node !== 'object' || seen.has(node)) return;
202
+ seen.add(node);
203
+
204
+ if (Array.isArray(node)) {
205
+ for (const item of node) expand_child_code_blocks(item, seen);
206
+ return;
207
+ }
208
+
209
+ if (
210
+ Array.isArray(node.children) &&
211
+ node.children.some((/** @type {any} */ c) => c?.type === 'JSXCodeBlock')
212
+ ) {
213
+ node.children = node.children.flatMap((/** @type {any} */ child) =>
214
+ child?.type === 'JSXCodeBlock'
215
+ ? [...child.body, ...(child.render != null ? [child.render] : [])]
216
+ : [child],
217
+ );
218
+ }
219
+
220
+ for (const key of Object.keys(node)) {
221
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
222
+ expand_child_code_blocks(node[key], seen);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * A `@`-prefixed JSX control-flow expression (`@if`/`@for`/`@switch`/`@try`).
228
+ * These are the only control-flow nodes that can appear in expression position;
229
+ * the plain statement forms (`IfStatement`, `SwitchStatement`, …) never do.
230
+ * @param {any} node
231
+ * @returns {boolean}
232
+ */
233
+ function is_jsx_control_flow_expression(node) {
234
+ return (
235
+ node?.type === 'JSXIfExpression' ||
236
+ node?.type === 'JSXForExpression' ||
237
+ node?.type === 'JSXSwitchExpression' ||
238
+ node?.type === 'JSXTryExpression'
239
+ );
240
+ }
241
+
242
+ /**
243
+ * Wrap a render-output node in a native TSRX fragment so it flows through the
244
+ * same single-child render path as a `<> … </>` output.
245
+ * @param {any} node
246
+ * @returns {any}
247
+ */
248
+ function wrap_in_native_tsrx_fragment(node) {
249
+ const fragment = b.jsx_fragment([node]);
250
+ fragment.metadata = { ...(fragment.metadata || {}), native_tsrx: true };
251
+ return fragment;
252
+ }
253
+
254
+ /**
255
+ * Wrap a bare JSX control-flow directive that sits directly in an expression
256
+ * position — an expression-bodied arrow (`() => @switch (…) { … }`), a
257
+ * `return @switch (…) { … }`, an unused expression statement,
258
+ * assignment to a variable
259
+ * (`const x = @switch (…) { … }`, `x = @switch (…) { … }`), or a call/`new`
260
+ * argument (`render(@if (…) { … })`) — in a native TSRX fragment.
261
+ * @param {any} node
262
+ * @param {Set<any>} [seen]
263
+ * @returns {void}
264
+ */
265
+ function wrap_control_flow_expression_values(node, seen = new Set()) {
266
+ if (!node || typeof node !== 'object' || seen.has(node)) return;
267
+ seen.add(node);
268
+
269
+ if (Array.isArray(node)) {
270
+ for (const item of node) wrap_control_flow_expression_values(item, seen);
271
+ return;
272
+ }
273
+
274
+ if (
275
+ node.type === 'ArrowFunctionExpression' &&
276
+ node.body?.type !== 'BlockStatement' &&
277
+ is_jsx_control_flow_expression(node.body)
278
+ ) {
279
+ node.body = wrap_in_native_tsrx_fragment(node.body);
280
+ } else if (node.type === 'ReturnStatement' && is_jsx_control_flow_expression(node.argument)) {
281
+ node.argument = wrap_in_native_tsrx_fragment(node.argument);
282
+ } else if (
283
+ node.type === 'ExpressionStatement' &&
284
+ is_jsx_control_flow_expression(node.expression)
285
+ ) {
286
+ node.expression = wrap_in_native_tsrx_fragment(node.expression);
287
+ } else if (node.type === 'VariableDeclarator' && is_jsx_control_flow_expression(node.init)) {
288
+ node.init = wrap_in_native_tsrx_fragment(node.init);
289
+ } else if (node.type === 'AssignmentExpression' && is_jsx_control_flow_expression(node.right)) {
290
+ node.right = wrap_in_native_tsrx_fragment(node.right);
291
+ } else if (
292
+ (node.type === 'CallExpression' || node.type === 'NewExpression') &&
293
+ Array.isArray(node.arguments)
294
+ ) {
295
+ node.arguments = node.arguments.map((/** @type {any} */ arg) =>
296
+ is_jsx_control_flow_expression(arg) ? wrap_in_native_tsrx_fragment(arg) : arg,
297
+ );
298
+ }
299
+
300
+ for (const key of Object.keys(node)) {
301
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
302
+ wrap_control_flow_expression_values(node[key], seen);
303
+ }
304
+ }
305
+
145
306
  /**
146
307
  * Build a `transform()` function for a specific JSX platform (React, Preact,
147
308
  * Solid). Given a `JsxPlatform` descriptor, returns a transform that lowers
@@ -150,7 +311,7 @@ function is_function_or_class_boundary(node) {
150
311
  * Any `<style>` element declared inside a TSRX fragment is collected, rendered
151
312
  * via `@tsrx/core`'s stylesheet renderer, and returned alongside the JS output
152
313
  * so a downstream plugin can inject it. The compiler also augments every
153
- * non-style Element in that fragment with the stylesheet's hash class so scoped
314
+ * non-style JSX element in that fragment with the stylesheet's hash class so scoped
154
315
  * selectors match correctly.
155
316
  *
156
317
  * @param {JsxPlatform} platform
@@ -192,6 +353,7 @@ export function createJsxTransform(platform) {
192
353
  hook_helpers_enabled: false,
193
354
  available_bindings: new Map(),
194
355
  lazy_next_id: 0,
356
+ runtime_dynamic_scopes: null,
195
357
  filename: filename ?? null,
196
358
  source,
197
359
  collect,
@@ -203,6 +365,13 @@ export function createJsxTransform(platform) {
203
365
  ...(platform.hooks?.initialState?.() ?? {}),
204
366
  };
205
367
 
368
+ expand_child_code_blocks(/** @type {any} */ (ast));
369
+ wrap_control_flow_expression_values(/** @type {any} */ (ast));
370
+ transform_context.runtime_dynamic_scopes = create_runtime_dynamic_scopes(
371
+ /** @type {any} */ (ast),
372
+ transform_context,
373
+ );
374
+
206
375
  if (!transform_context.typeOnly) {
207
376
  preallocate_lazy_ids(/** @type {any} */ (ast), transform_context);
208
377
  }
@@ -213,7 +382,11 @@ export function createJsxTransform(platform) {
213
382
  return next();
214
383
  },
215
384
 
216
- TsrxFragment(node, { next, path, state, visit }) {
385
+ JSXFragment(node, { next, path, state, visit }) {
386
+ if (!node.metadata?.native_tsrx) {
387
+ return next() ?? node;
388
+ }
389
+
217
390
  const parent = /** @type {AST.ArrowFunctionExpression} */ (path.at(-1));
218
391
  if (parent?.metadata?.native_tsrx && parent.body === node) {
219
392
  return /** @type {any} */ (visit(create_native_tsrx_render_block(node, state), state));
@@ -233,18 +406,11 @@ export function createJsxTransform(platform) {
233
406
  return /** @type {any} */ (wrap_jsx_setup_declarations(expression, in_jsx_child));
234
407
  },
235
408
 
236
- TsxCompat(node, { next, path, state }) {
237
- const inner = /** @type {any} */ (next() ?? node);
238
- const in_jsx_child = in_jsx_child_context(path);
239
- return /** @type {any} */ (
240
- wrap_jsx_setup_declarations(
241
- tsx_compat_node_to_jsx_expression(inner, state, in_jsx_child),
242
- in_jsx_child,
243
- )
244
- );
245
- },
409
+ JSXElement(node, { next, path, state }) {
410
+ if (!node.metadata?.native_tsrx) {
411
+ return next() ?? node;
412
+ }
246
413
 
247
- Element(node, { next, path, state }) {
248
414
  if (is_style_element(node) && is_style_expression_position(path)) {
249
415
  const stylesheet = get_style_element_stylesheet(node);
250
416
  if (stylesheet) {
@@ -254,30 +420,57 @@ export function createJsxTransform(platform) {
254
420
  }
255
421
  }
256
422
 
257
- // Capture raw children BEFORE the walker transforms them so a
258
- // platform hook (e.g. Solid's textContent optimization) can
259
- // inspect the original Text / TSRXExpression nodes rather than
260
- // their walker-lowered JSXExpressionContainer equivalents.
423
+ // Capture raw children BEFORE the walker transforms them so platform
424
+ // hooks can inspect the original JSX child shape.
261
425
  const raw_children = /** @type {any} */ (node.children || []).map(
262
426
  (/** @type {any} */ child) => (child && typeof child === 'object' ? { ...child } : child),
263
427
  );
264
428
  const inner = /** @type {any} */ (next() ?? node);
265
429
  const hook = platform.hooks?.transformElement;
266
430
  if (hook) return /** @type {any} */ (hook(inner, state, raw_children));
267
- return /** @type {any} */ (to_jsx_element(inner, state, raw_children));
431
+ return /** @type {any} */ (
432
+ to_jsx_element(inner, state, raw_children, in_jsx_child_context(path))
433
+ );
268
434
  },
269
435
 
270
- Text(node, { next }) {
271
- const inner = /** @type {any} */ (next() ?? node);
436
+ JSXExpressionContainer(node, { next, state }) {
437
+ const result = /** @type {any} */ (next() ?? node);
438
+ const expression = result.expression;
439
+ // `@if`/`@for`/`@switch`/`@try` used as an expression value (e.g. an
440
+ // attribute value `content={@if (…) { … }}` or a `{ … }` child) leaks a
441
+ // JSX*Expression node straight to the printer. Lower it with the same
442
+ // control-flow machinery used for render children and unwrap the value.
443
+ if (
444
+ is_if_control_node(expression) ||
445
+ is_switch_control_node(expression) ||
446
+ is_try_control_node(expression) ||
447
+ expression?.type === 'JSXForExpression'
448
+ ) {
449
+ const lowered = /** @type {any} */ (to_jsx_child(expression, state));
450
+ return { ...result, expression: lowered?.expression ?? lowered };
451
+ }
452
+ return result;
453
+ },
454
+
455
+ JSXStyleElement(node, { path, state }) {
456
+ if (is_style_expression_position(path)) {
457
+ const stylesheet = get_style_element_stylesheet(node);
458
+ if (stylesheet) {
459
+ analyze_css(stylesheet);
460
+ state.stylesheets.push(stylesheet);
461
+ return /** @type {any} */ (create_style_expression_value(node, stylesheet, state));
462
+ }
463
+ }
272
464
  return /** @type {any} */ (
273
- to_jsx_expression_container(to_text_expression(inner.expression, inner), inner)
465
+ b.jsx_element(
466
+ /** @type {ESTreeJSX.JSXElement} */ ({ ...node, type: 'JSXElement', children: [] }),
467
+ node.openingElement?.attributes ?? [],
468
+ [],
469
+ )
274
470
  );
275
471
  },
276
472
 
277
- TSRXExpression(node, { next }) {
278
- const inner = /** @type {any} */ (next() ?? node);
279
- return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
280
- },
473
+ JSXCodeBlock: transform_jsx_code_block,
281
474
 
282
475
  BlockStatement: transform_block_statement,
283
476
  ReturnStatement: transform_return_statement,
@@ -330,6 +523,7 @@ export function createJsxTransform(platform) {
330
523
  ? expanded
331
524
  : apply_lazy_transforms(/** @type {any} */ (expanded), new Map())
332
525
  );
526
+ lower_remaining_jsx_code_blocks(final_program, transform_context);
333
527
 
334
528
  const result = print(/** @type {any} */ (final_program), tsx_with_ts_locations(), {
335
529
  sourceMapSource: filename,
@@ -352,16 +546,26 @@ export function createJsxTransform(platform) {
352
546
  *
353
547
  * @param {any} component
354
548
  * @param {any} css
549
+ * @param {TransformContext} transform_context
355
550
  * @param {boolean} [export_top_scoped_classes]
356
551
  * @returns {void}
357
552
  */
358
- function apply_css_definition_metadata(component, css, export_top_scoped_classes = false) {
553
+ function apply_css_definition_metadata(
554
+ component,
555
+ css,
556
+ transform_context,
557
+ export_top_scoped_classes = false,
558
+ ) {
359
559
  analyze_css(css);
360
560
 
361
561
  const metadata = component.metadata || (component.metadata = { path: [] });
362
562
  const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
363
563
  const top_scoped_classes = metadata.topScopedClasses || new Map();
364
- const elements = collect_css_prunable_elements(component.body || component.children || []);
564
+ const elements = collect_css_prunable_elements(
565
+ component.body || component.children || [],
566
+ [],
567
+ transform_context,
568
+ );
365
569
 
366
570
  const prune = () => {
367
571
  for (const element of elements) {
@@ -386,16 +590,17 @@ function apply_css_definition_metadata(component, css, export_top_scoped_classes
386
590
  /**
387
591
  * @param {any} value
388
592
  * @param {any[]} [elements]
593
+ * @param {TransformContext | null} [transform_context]
389
594
  * @returns {any[]}
390
595
  */
391
- function collect_css_prunable_elements(value, elements = []) {
596
+ function collect_css_prunable_elements(value, elements = [], transform_context = null) {
392
597
  if (!value || typeof value !== 'object') {
393
598
  return elements;
394
599
  }
395
600
 
396
601
  if (Array.isArray(value)) {
397
602
  for (const child of value) {
398
- collect_css_prunable_elements(child, elements);
603
+ collect_css_prunable_elements(child, elements, transform_context);
399
604
  }
400
605
  return elements;
401
606
  }
@@ -408,7 +613,8 @@ function collect_css_prunable_elements(value, elements = []) {
408
613
  return elements;
409
614
  }
410
615
 
411
- if (value.type === 'Element') {
616
+ if (value.type === 'JSXElement' && value.metadata?.native_tsrx) {
617
+ mark_runtime_dynamic_element(value, transform_context);
412
618
  if (!is_style_element(value)) {
413
619
  elements.push(value);
414
620
  }
@@ -418,12 +624,247 @@ function collect_css_prunable_elements(value, elements = []) {
418
624
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
419
625
  continue;
420
626
  }
421
- collect_css_prunable_elements(value[key], elements);
627
+ collect_css_prunable_elements(value[key], elements, transform_context);
422
628
  }
423
629
 
424
630
  return elements;
425
631
  }
426
632
 
633
+ /**
634
+ * @param {AST.Program} ast
635
+ * @param {TransformContext} transform_context
636
+ * @returns {Map<any, any> | null}
637
+ */
638
+ function create_runtime_dynamic_scopes(ast, transform_context) {
639
+ const dynamic_source = transform_context.platform.imports.dynamic;
640
+ if (!dynamic_source) {
641
+ return null;
642
+ }
643
+ if (!has_runtime_dynamic_import(ast, dynamic_source)) {
644
+ return null;
645
+ }
646
+
647
+ const { scopes } = create_scopes(ast, new ScopeRoot(), null, {
648
+ collect: true,
649
+ errors: [],
650
+ filename: transform_context.filename ?? '',
651
+ comments: transform_context.comments,
652
+ });
653
+
654
+ return scopes;
655
+ }
656
+
657
+ /**
658
+ * @param {any} node
659
+ * @param {TransformContext | null} transform_context
660
+ * @returns {void}
661
+ */
662
+ function mark_runtime_dynamic_element(node, transform_context) {
663
+ const dynamic_source = transform_context?.platform.imports.dynamic;
664
+ const scopes = transform_context?.runtime_dynamic_scopes;
665
+ if (
666
+ !dynamic_source ||
667
+ !scopes ||
668
+ node.metadata?.runtime_dynamic_element === true ||
669
+ !has_jsx_attribute(node, 'is') ||
670
+ !is_runtime_dynamic_jsx_name(node.openingElement?.name, scopes.get(node), dynamic_source)
671
+ ) {
672
+ return;
673
+ }
674
+
675
+ node.metadata.runtime_dynamic_element = true;
676
+ }
677
+
678
+ /**
679
+ * @param {AST.Program} ast
680
+ * @param {string} dynamic_source
681
+ * @returns {boolean}
682
+ */
683
+ function has_runtime_dynamic_import(ast, dynamic_source) {
684
+ return ast.body.some(
685
+ (/** @type {any} */ node) =>
686
+ node.type === 'ImportDeclaration' &&
687
+ node.importKind !== 'type' &&
688
+ node.source?.type === 'Literal' &&
689
+ node.source.value === dynamic_source &&
690
+ node.specifiers.some(
691
+ (/** @type {any} */ specifier) =>
692
+ is_runtime_dynamic_import_specifier(specifier, 'component') ||
693
+ is_runtime_dynamic_import_specifier(specifier, 'namespace'),
694
+ ),
695
+ );
696
+ }
697
+
698
+ /**
699
+ * @param {any} node
700
+ * @param {string} name
701
+ * @returns {boolean}
702
+ */
703
+ function has_jsx_attribute(node, name) {
704
+ return (node.openingElement?.attributes ?? []).some(
705
+ (/** @type {any} */ attr) =>
706
+ attr.type === 'JSXAttribute' &&
707
+ attr.name?.type === 'JSXIdentifier' &&
708
+ attr.name.name === name,
709
+ );
710
+ }
711
+
712
+ /**
713
+ * @param {any} name
714
+ * @param {any} scope
715
+ * @param {string} dynamic_source
716
+ * @returns {boolean}
717
+ */
718
+ function is_runtime_dynamic_jsx_name(name, scope, dynamic_source) {
719
+ if (!scope || !name) {
720
+ return false;
721
+ }
722
+
723
+ if (name.type === 'JSXIdentifier') {
724
+ return is_runtime_dynamic_binding(scope.get(name.name), dynamic_source, 'component', new Set());
725
+ }
726
+
727
+ if (
728
+ name.type === 'JSXMemberExpression' &&
729
+ name.object?.type === 'JSXIdentifier' &&
730
+ name.property?.type === 'JSXIdentifier' &&
731
+ name.property.name === 'Dynamic'
732
+ ) {
733
+ return is_runtime_dynamic_binding(
734
+ scope.get(name.object.name),
735
+ dynamic_source,
736
+ 'namespace',
737
+ new Set(),
738
+ );
739
+ }
740
+
741
+ return false;
742
+ }
743
+
744
+ /**
745
+ * @param {any} binding
746
+ * @param {string} dynamic_source
747
+ * @param {'component' | 'namespace'} kind
748
+ * @param {Set<any>} seen
749
+ * @returns {boolean}
750
+ */
751
+ function is_runtime_dynamic_binding(binding, dynamic_source, kind, seen) {
752
+ if (!binding || seen.has(binding)) {
753
+ return false;
754
+ }
755
+ seen.add(binding);
756
+
757
+ if (is_runtime_dynamic_import_binding(binding, dynamic_source, kind)) {
758
+ return true;
759
+ }
760
+
761
+ if (binding.reassigned) {
762
+ return false;
763
+ }
764
+
765
+ const initial = unwrap_reference_expression(binding.initial);
766
+ if (!initial) {
767
+ return false;
768
+ }
769
+
770
+ if (initial.type === 'Identifier') {
771
+ return is_runtime_dynamic_binding(binding.scope.get(initial.name), dynamic_source, kind, seen);
772
+ }
773
+
774
+ if (
775
+ kind === 'component' &&
776
+ initial.type === 'MemberExpression' &&
777
+ !initial.computed &&
778
+ initial.object?.type === 'Identifier' &&
779
+ initial.property?.type === 'Identifier' &&
780
+ initial.property.name === 'Dynamic'
781
+ ) {
782
+ return is_runtime_dynamic_binding(
783
+ binding.scope.get(initial.object.name),
784
+ dynamic_source,
785
+ 'namespace',
786
+ new Set(),
787
+ );
788
+ }
789
+
790
+ return false;
791
+ }
792
+
793
+ /**
794
+ * @param {any} binding
795
+ * @param {string} dynamic_source
796
+ * @param {'component' | 'namespace'} kind
797
+ * @returns {boolean}
798
+ */
799
+ function is_runtime_dynamic_import_binding(binding, dynamic_source, kind) {
800
+ const declaration = binding?.initial;
801
+ if (
802
+ binding?.declaration_kind !== 'import' ||
803
+ declaration?.type !== 'ImportDeclaration' ||
804
+ declaration.importKind === 'type' ||
805
+ declaration.source?.type !== 'Literal' ||
806
+ declaration.source.value !== dynamic_source
807
+ ) {
808
+ return false;
809
+ }
810
+
811
+ return declaration.specifiers.some(
812
+ (/** @type {any} */ specifier) =>
813
+ specifier.local?.name === binding.node?.name &&
814
+ is_runtime_dynamic_import_specifier(specifier, kind),
815
+ );
816
+ }
817
+
818
+ /**
819
+ * @param {any} specifier
820
+ * @param {'component' | 'namespace'} kind
821
+ * @returns {boolean}
822
+ */
823
+ function is_runtime_dynamic_import_specifier(specifier, kind) {
824
+ if (kind === 'namespace') {
825
+ return specifier.type === 'ImportNamespaceSpecifier';
826
+ }
827
+ return (
828
+ specifier.type === 'ImportSpecifier' &&
829
+ specifier.importKind !== 'type' &&
830
+ get_imported_name(specifier) === 'Dynamic'
831
+ );
832
+ }
833
+
834
+ /**
835
+ * @param {any} specifier
836
+ * @returns {string | null}
837
+ */
838
+ function get_imported_name(specifier) {
839
+ const imported = specifier.imported;
840
+ if (imported?.type === 'Identifier') {
841
+ return imported.name;
842
+ }
843
+ if (imported?.type === 'Literal') {
844
+ return String(imported.value);
845
+ }
846
+ return null;
847
+ }
848
+
849
+ /**
850
+ * @param {any} expression
851
+ * @returns {any}
852
+ */
853
+ function unwrap_reference_expression(expression) {
854
+ let node = expression;
855
+ while (
856
+ node &&
857
+ (node.type === 'TSAsExpression' ||
858
+ node.type === 'TSTypeAssertion' ||
859
+ node.type === 'TSNonNullExpression' ||
860
+ node.type === 'ParenthesizedExpression' ||
861
+ node.type === 'ChainExpression')
862
+ ) {
863
+ node = node.expression;
864
+ }
865
+ return node;
866
+ }
867
+
427
868
  /**
428
869
  * @param {any[]} body_nodes
429
870
  * @param {TransformContext} transform_context
@@ -440,6 +881,12 @@ function build_component_statements(body_nodes, transform_context) {
440
881
  * @returns {any[]}
441
882
  */
442
883
  function build_render_statements(body_nodes, return_null_when_empty, transform_context) {
884
+ body_nodes = body_nodes.flatMap((node) =>
885
+ node?.type === 'JSXCodeBlock'
886
+ ? [...node.body, ...(node.render != null ? [node.render] : [])]
887
+ : [node],
888
+ );
889
+
443
890
  const statements = [];
444
891
  const render_nodes = [];
445
892
  let has_terminal_return = false;
@@ -480,7 +927,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
480
927
  (node) =>
481
928
  !is_loop_skip_return_statement(node) &&
482
929
  !is_loop_skip_if_statement(node) &&
483
- !is_jsx_child(node),
930
+ !is_render_child_node(node),
484
931
  );
485
932
 
486
933
  if (!continuation_has_setup_statements) {
@@ -522,7 +969,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
522
969
  }
523
970
 
524
971
  if (
525
- child.type === 'ForOfStatement' &&
972
+ is_template_for_of_node(child) &&
526
973
  !child.await &&
527
974
  should_extract_hook_helpers(transform_context) &&
528
975
  !transform_context.platform.hooks?.isTopLevelSetupCall &&
@@ -533,7 +980,10 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
533
980
  true,
534
981
  )
535
982
  ) {
536
- const hoisted = build_hoisted_for_of_with_hooks(child, transform_context);
983
+ const hoisted = build_hoisted_for_of_with_hooks(
984
+ jsx_control_expression_to_statement(child),
985
+ transform_context,
986
+ );
537
987
  if (hoisted) {
538
988
  statements.push(...hoisted.hoist_statements);
539
989
  if (interleaved && is_capturable_jsx_child(hoisted.jsx_child)) {
@@ -547,7 +997,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
547
997
  }
548
998
  }
549
999
 
550
- if (is_jsx_child(child)) {
1000
+ if (is_render_child_node(child)) {
551
1001
  const jsx = to_jsx_child(child, transform_context);
552
1002
  statements.push(...extract_jsx_setup_declarations(jsx));
553
1003
  if (interleaved && is_capturable_jsx_child(jsx)) {
@@ -560,6 +1010,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
560
1010
  } else if (is_bare_render_expression(child)) {
561
1011
  render_nodes.push(to_jsx_expression_container(child, child));
562
1012
  } else {
1013
+ mark_nested_function_return_jsx(child);
563
1014
  statements.push(child);
564
1015
  collect_statement_bindings(child, transform_context.available_bindings);
565
1016
  }
@@ -569,78 +1020,308 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
569
1020
  hoist_static_render_nodes(render_nodes, transform_context);
570
1021
  }
571
1022
 
572
- const return_arg = build_return_expression(render_nodes);
573
- if (return_arg || (return_null_when_empty && !has_terminal_return)) {
574
- statements.push(b.return(return_arg || b.literal(null)));
1023
+ const return_arg = build_return_expression(render_nodes);
1024
+ if (return_arg || (return_null_when_empty && !has_terminal_return)) {
1025
+ statements.push(b.return(return_arg || b.literal(null)));
1026
+ }
1027
+
1028
+ transform_context.available_bindings = saved_bindings;
1029
+ return statements;
1030
+ }
1031
+
1032
+ /**
1033
+ * @param {any[]} body_nodes
1034
+ * @returns {boolean}
1035
+ */
1036
+ function is_interleaved_body(body_nodes) {
1037
+ return is_interleaved_body_core(body_nodes, is_render_child_node);
1038
+ }
1039
+
1040
+ /**
1041
+ * @param {any} node
1042
+ * @param {TransformContext} transform_context
1043
+ * @returns {boolean}
1044
+ */
1045
+ function needs_hook_split(node, transform_context) {
1046
+ const body = node.body?.body || [];
1047
+ return (
1048
+ transform_context.platform.hooks?.componentBodyHookHelpers === true &&
1049
+ node.body?.type === 'BlockStatement' &&
1050
+ (find_hook_split_index(body, transform_context) !== -1 ||
1051
+ body_contains_component_body_branch_hook_return(body, transform_context))
1052
+ );
1053
+ }
1054
+
1055
+ /**
1056
+ * @param {any} node
1057
+ * @param {TransformContext} transform_context
1058
+ * @returns {any}
1059
+ */
1060
+ function create_hook_split_block(node, transform_context) {
1061
+ if (
1062
+ transform_context.platform.hooks?.componentBodyHookHelpers !== true ||
1063
+ !should_extract_hook_helpers(transform_context) ||
1064
+ node.body?.type !== 'BlockStatement'
1065
+ ) {
1066
+ return null;
1067
+ }
1068
+
1069
+ const source_body = node.body.body || [];
1070
+ const branch_rewrite = rewrite_component_body_branch_hook_returns(source_body, transform_context);
1071
+ const body = branch_rewrite.body;
1072
+ const split_index = find_hook_split_index(body, transform_context);
1073
+ if (split_index === -1 && !branch_rewrite.changed) {
1074
+ return null;
1075
+ }
1076
+
1077
+ let block_body;
1078
+ if (split_index === -1) {
1079
+ block_body = expand_native_tsrx_return_statement_list(body, transform_context);
1080
+ } else {
1081
+ const split_statement = body[split_index];
1082
+ const continuation_body = body.slice(split_index + 1);
1083
+ const helper = create_hook_safe_helper(
1084
+ expand_native_tsrx_return_statement_list(continuation_body, transform_context),
1085
+ undefined,
1086
+ get_body_source_node(continuation_body) || split_statement,
1087
+ transform_context,
1088
+ );
1089
+
1090
+ block_body = [
1091
+ ...body.slice(0, split_index + 1),
1092
+ ...helper.setup_statements,
1093
+ set_loc(b.return(helper.component_element), split_statement),
1094
+ ];
1095
+ }
1096
+
1097
+ const block = b.block(block_body, node.body);
1098
+ block.metadata = {
1099
+ ...(block.metadata || {}),
1100
+ hook_split_block: true,
1101
+ };
1102
+ return block;
1103
+ }
1104
+
1105
+ /**
1106
+ * @param {any[]} body_nodes
1107
+ * @param {TransformContext} transform_context
1108
+ * @returns {boolean}
1109
+ */
1110
+ function body_contains_component_body_branch_hook_return(body_nodes, transform_context) {
1111
+ return body_nodes.some((node) =>
1112
+ statement_contains_component_body_branch_hook_return(node, transform_context),
1113
+ );
1114
+ }
1115
+
1116
+ /**
1117
+ * @param {any} node
1118
+ * @param {TransformContext} transform_context
1119
+ * @returns {boolean}
1120
+ */
1121
+ function statement_contains_component_body_branch_hook_return(node, transform_context) {
1122
+ if (!node || typeof node !== 'object') {
1123
+ return false;
1124
+ }
1125
+
1126
+ if (Array.isArray(node)) {
1127
+ return body_contains_component_body_branch_hook_return(node, transform_context);
1128
+ }
1129
+
1130
+ if (is_function_or_class_boundary(node)) {
1131
+ return false;
1132
+ }
1133
+
1134
+ if (is_plain_if_statement(node)) {
1135
+ return (
1136
+ branch_needs_component_body_hook_helper(node.consequent, transform_context) ||
1137
+ statement_contains_component_body_branch_hook_return(node.consequent, transform_context) ||
1138
+ branch_needs_component_body_hook_helper(node.alternate, transform_context) ||
1139
+ statement_contains_component_body_branch_hook_return(node.alternate, transform_context)
1140
+ );
1141
+ }
1142
+
1143
+ if (node.type === 'BlockStatement') {
1144
+ return body_contains_component_body_branch_hook_return(node.body || [], transform_context);
1145
+ }
1146
+
1147
+ return false;
1148
+ }
1149
+
1150
+ /**
1151
+ * @param {any[]} body_nodes
1152
+ * @param {TransformContext} transform_context
1153
+ * @returns {{ body: any[], changed: boolean }}
1154
+ */
1155
+ function rewrite_component_body_branch_hook_returns(body_nodes, transform_context) {
1156
+ let changed = false;
1157
+ const body = body_nodes.map((node) => {
1158
+ const next_node = rewrite_component_body_branch_hook_return_statement(node, transform_context);
1159
+ if (next_node !== node) {
1160
+ changed = true;
1161
+ }
1162
+ return next_node;
1163
+ });
1164
+ return changed ? { body, changed } : { body: body_nodes, changed: false };
1165
+ }
1166
+
1167
+ /**
1168
+ * @param {any} node
1169
+ * @param {TransformContext} transform_context
1170
+ * @returns {any}
1171
+ */
1172
+ function rewrite_component_body_branch_hook_return_statement(node, transform_context) {
1173
+ if (!node || typeof node !== 'object' || is_function_or_class_boundary(node)) {
1174
+ return node;
1175
+ }
1176
+
1177
+ if (is_plain_if_statement(node)) {
1178
+ const consequent = rewrite_component_body_hook_return_branch(
1179
+ node.consequent,
1180
+ transform_context,
1181
+ );
1182
+ const alternate = node.alternate
1183
+ ? rewrite_component_body_hook_return_branch(node.alternate, transform_context)
1184
+ : { node: node.alternate, changed: false };
1185
+
1186
+ if (!consequent.changed && !alternate.changed) {
1187
+ return node;
1188
+ }
1189
+ return set_loc(b.if(node.test, consequent.node, alternate.node), node);
1190
+ }
1191
+
1192
+ if (node.type === 'BlockStatement') {
1193
+ const rewritten = rewrite_component_body_branch_hook_returns(
1194
+ node.body || [],
1195
+ transform_context,
1196
+ );
1197
+ return rewritten.changed ? set_loc(b.block(rewritten.body, node), node) : node;
1198
+ }
1199
+
1200
+ return node;
1201
+ }
1202
+
1203
+ /**
1204
+ * @param {any} branch
1205
+ * @param {TransformContext} transform_context
1206
+ * @returns {{ node: any, changed: boolean }}
1207
+ */
1208
+ function rewrite_component_body_hook_return_branch(branch, transform_context) {
1209
+ if (!branch || typeof branch !== 'object') {
1210
+ return { node: branch, changed: false };
1211
+ }
1212
+
1213
+ if (is_plain_if_statement(branch)) {
1214
+ const next_node = rewrite_component_body_branch_hook_return_statement(
1215
+ branch,
1216
+ transform_context,
1217
+ );
1218
+ return { node: next_node, changed: next_node !== branch };
1219
+ }
1220
+
1221
+ const branch_body = branch.type === 'BlockStatement' ? branch.body || [] : [branch];
1222
+ const rewritten = rewrite_component_body_branch_hook_returns(branch_body, transform_context);
1223
+ const body = rewritten.body;
1224
+ const needs_helper = branch_needs_component_body_hook_helper_body(body, transform_context);
1225
+
1226
+ if (!needs_helper) {
1227
+ if (!rewritten.changed) {
1228
+ return { node: branch, changed: false };
1229
+ }
1230
+ const node =
1231
+ branch.type === 'BlockStatement'
1232
+ ? set_loc(b.block(body, branch), branch)
1233
+ : (body[0] ?? branch);
1234
+ return { node, changed: true };
1235
+ }
1236
+
1237
+ const helper_body = expand_native_tsrx_return_statement_list(body, transform_context);
1238
+ const helper = create_hook_safe_helper(
1239
+ helper_body,
1240
+ undefined,
1241
+ get_body_source_node(body) || branch,
1242
+ transform_context,
1243
+ );
1244
+ const node = set_loc(
1245
+ b.block([...helper.setup_statements, set_loc(b.return(helper.component_element), branch)]),
1246
+ branch,
1247
+ );
1248
+ return { node, changed: true };
1249
+ }
1250
+
1251
+ /**
1252
+ * @param {any} branch
1253
+ * @param {TransformContext} transform_context
1254
+ * @returns {boolean}
1255
+ */
1256
+ function branch_needs_component_body_hook_helper(branch, transform_context) {
1257
+ if (!branch || typeof branch !== 'object' || is_plain_if_statement(branch)) {
1258
+ return false;
575
1259
  }
1260
+ const body = branch.type === 'BlockStatement' ? branch.body || [] : [branch];
1261
+ return branch_needs_component_body_hook_helper_body(body, transform_context);
1262
+ }
576
1263
 
577
- transform_context.available_bindings = saved_bindings;
578
- return statements;
1264
+ /**
1265
+ * @param {any[]} body
1266
+ * @param {TransformContext} transform_context
1267
+ * @returns {boolean}
1268
+ */
1269
+ function branch_needs_component_body_hook_helper_body(body, transform_context) {
1270
+ return (
1271
+ body_has_top_level_component_body_return(body) &&
1272
+ body_contains_direct_top_level_hook_call(body, transform_context, true)
1273
+ );
579
1274
  }
580
1275
 
581
1276
  /**
582
- * @param {any[]} body_nodes
1277
+ * @param {any[]} body
583
1278
  * @returns {boolean}
584
1279
  */
585
- function is_interleaved_body(body_nodes) {
586
- return is_interleaved_body_core(body_nodes, is_jsx_child);
1280
+ function body_has_top_level_component_body_return(body) {
1281
+ return body.some((node) => node?.type === 'ReturnStatement');
587
1282
  }
588
1283
 
589
1284
  /**
590
- * @param {any} node
1285
+ * @param {any[]} body
591
1286
  * @param {TransformContext} transform_context
1287
+ * @param {boolean} include_platform_setup
592
1288
  * @returns {boolean}
593
1289
  */
594
- function needs_hook_split(node, transform_context) {
595
- return (
596
- transform_context.platform.hooks?.componentBodyHookHelpers === true &&
597
- node.body?.type === 'BlockStatement' &&
598
- find_hook_split_index(node.body.body || [], transform_context) !== -1
1290
+ function body_contains_direct_top_level_hook_call(body, transform_context, include_platform_setup) {
1291
+ return body.some((node) =>
1292
+ statement_contains_direct_top_level_hook_call(node, transform_context, include_platform_setup),
599
1293
  );
600
1294
  }
601
1295
 
602
1296
  /**
603
1297
  * @param {any} node
604
1298
  * @param {TransformContext} transform_context
605
- * @returns {any}
1299
+ * @param {boolean} include_platform_setup
1300
+ * @returns {boolean}
606
1301
  */
607
- function create_hook_split_block(node, transform_context) {
608
- if (
609
- transform_context.platform.hooks?.componentBodyHookHelpers !== true ||
610
- !should_extract_hook_helpers(transform_context) ||
611
- node.body?.type !== 'BlockStatement'
612
- ) {
613
- return null;
1302
+ function statement_contains_direct_top_level_hook_call(
1303
+ node,
1304
+ transform_context,
1305
+ include_platform_setup,
1306
+ ) {
1307
+ if (!node || typeof node !== 'object') {
1308
+ return false;
614
1309
  }
615
1310
 
616
- const body = node.body.body || [];
617
- const split_index = find_hook_split_index(body, transform_context);
618
- if (split_index === -1) {
619
- return null;
1311
+ if (is_function_or_class_boundary(node)) {
1312
+ return false;
620
1313
  }
621
1314
 
622
- const split_statement = body[split_index];
623
- const continuation_body = body.slice(split_index + 1);
624
- const helper = create_hook_safe_helper(
625
- expand_native_tsrx_return_statement_list(continuation_body, transform_context),
626
- undefined,
627
- get_body_source_node(continuation_body) || split_statement,
628
- transform_context,
629
- );
1315
+ if (
1316
+ is_plain_if_statement(node) ||
1317
+ is_switch_control_node(node) ||
1318
+ is_try_control_node(node) ||
1319
+ is_for_of_control_node(node)
1320
+ ) {
1321
+ return false;
1322
+ }
630
1323
 
631
- const block = b.block(
632
- [
633
- ...body.slice(0, split_index + 1),
634
- ...helper.setup_statements,
635
- set_loc(b.return(helper.component_element), split_statement),
636
- ],
637
- node.body,
638
- );
639
- block.metadata = {
640
- ...(block.metadata || {}),
641
- hook_split_block: true,
642
- };
643
- return block;
1324
+ return statement_contains_top_level_hook_call(node, transform_context, include_platform_setup);
644
1325
  }
645
1326
 
646
1327
  /**
@@ -667,7 +1348,7 @@ function find_hook_split_index(body_nodes, transform_context) {
667
1348
  * @returns {boolean}
668
1349
  */
669
1350
  function is_component_body_conditional_return_statement(node) {
670
- if (node?.type !== 'IfStatement') {
1351
+ if (!is_if_control_node(node)) {
671
1352
  return false;
672
1353
  }
673
1354
 
@@ -702,20 +1383,20 @@ function statement_contains_component_body_return(node) {
702
1383
  return (node.body || []).some(statement_contains_component_body_return);
703
1384
  }
704
1385
 
705
- if (node.type === 'IfStatement') {
1386
+ if (is_if_control_node(node)) {
706
1387
  return (
707
1388
  statement_contains_component_body_return(node.consequent) ||
708
1389
  statement_contains_component_body_return(node.alternate)
709
1390
  );
710
1391
  }
711
1392
 
712
- if (node.type === 'SwitchStatement') {
1393
+ if (is_switch_control_node(node)) {
713
1394
  return (node.cases || []).some((/** @type {any} */ switch_case) =>
714
1395
  statement_contains_component_body_return(switch_case.consequent || []),
715
1396
  );
716
1397
  }
717
1398
 
718
- if (node.type === 'TryStatement') {
1399
+ if (is_try_control_node(node)) {
719
1400
  return (
720
1401
  statement_contains_component_body_return(node.block) ||
721
1402
  statement_contains_component_body_return(node.handler?.body) ||
@@ -992,7 +1673,7 @@ function transform_block_statement(node, { next, visit, state, path }) {
992
1673
  }
993
1674
  }
994
1675
 
995
- if (get_active_native_tsrx_function(path)) {
1676
+ if (get_active_native_tsrx_function(path)?.metadata?.native_tsrx_body) {
996
1677
  const block = create_native_tsrx_statement_list_block(node, state);
997
1678
  if (block) {
998
1679
  return visit(block, state);
@@ -1008,13 +1689,56 @@ function transform_block_statement(node, { next, visit, state, path }) {
1008
1689
  * @returns {any}
1009
1690
  */
1010
1691
  function transform_return_statement(node, { next, visit, state, path }) {
1011
- if (get_active_native_tsrx_function(path) && node.argument?.type === 'TsrxFragment') {
1692
+ const active_native_tsrx_function = get_active_native_tsrx_function(path);
1693
+ if (active_native_tsrx_function && is_native_tsrx_node(node.argument)) {
1694
+ if (!active_native_tsrx_function.metadata?.native_tsrx_body) {
1695
+ const statements = mark_native_pretransformed_jsx(
1696
+ create_native_tsrx_render_statements(node.argument, state),
1697
+ );
1698
+ if (statements.length === 1) {
1699
+ return visit(statements[0], state);
1700
+ }
1701
+ const block = b.block(statements, node.argument);
1702
+ block.metadata = {
1703
+ ...(block.metadata || {}),
1704
+ native_return_block: true,
1705
+ };
1706
+ return visit(block, state);
1707
+ }
1012
1708
  return visit(create_native_tsrx_render_block(node.argument, state), state);
1013
1709
  }
1014
1710
 
1015
1711
  return next() ?? node;
1016
1712
  }
1017
1713
 
1714
+ /**
1715
+ * @param {any} node
1716
+ * @param {{ state: TransformContext, path: AST.Node[] }} context
1717
+ * @returns {any}
1718
+ */
1719
+ function transform_jsx_code_block(node, { state, path }) {
1720
+ const body_nodes = get_jsx_code_block_body_nodes(node, state);
1721
+ const parent = /** @type {any} */ (path.at(-1));
1722
+
1723
+ if (parent && parent.body === node && is_function_or_class_boundary(parent)) {
1724
+ const block = b.block(
1725
+ mark_native_pretransformed_jsx(build_render_statements(body_nodes, true, state)),
1726
+ node,
1727
+ );
1728
+ block.metadata = {
1729
+ ...(block.metadata || {}),
1730
+ native_return_block: true,
1731
+ };
1732
+ return block;
1733
+ }
1734
+
1735
+ const expression = b.call(
1736
+ b.arrow([], b.block(build_render_statements(body_nodes, true, state), node)),
1737
+ );
1738
+
1739
+ return in_jsx_child_context(path) ? to_jsx_expression_container(expression, node) : expression;
1740
+ }
1741
+
1018
1742
  /**
1019
1743
  * @param {AST.Node[]} path
1020
1744
  * @returns {any | null}
@@ -1035,19 +1759,62 @@ function get_active_native_tsrx_function(path) {
1035
1759
  * @returns {any}
1036
1760
  */
1037
1761
  function transform_function(node, context) {
1038
- if (node.metadata?.native_tsrx_function || function_has_native_tsrx_return(node)) {
1039
- return transform_native_tsrx_function(node, context);
1762
+ // Lower a `@{ … }` function body (JSXCodeBlock) to an ordinary block: the
1763
+ // setup statements followed by `return <render>` when the block produces a
1764
+ // render output. The parser already marks the render JSX as native_tsrx, so
1765
+ // from here it flows through the existing native-component machinery exactly
1766
+ // like the older fenced `{ return <> … </> }` shape.
1767
+ const has_jsx_code_block_body = node.body?.type === 'JSXCodeBlock';
1768
+ lower_jsx_code_block_function_body(node);
1769
+
1770
+ if (
1771
+ has_jsx_code_block_body ||
1772
+ node.metadata?.native_tsrx_function ||
1773
+ function_has_native_tsrx_return(node)
1774
+ ) {
1775
+ return transform_native_tsrx_function(node, context, {
1776
+ nativeBody: has_jsx_code_block_body || !!node.metadata?.native_tsrx_function,
1777
+ });
1040
1778
  }
1041
1779
 
1042
1780
  return transform_function_with_hook_helpers(node, context);
1043
1781
  }
1044
1782
 
1783
+ /**
1784
+ * @param {any} node
1785
+ * @returns {void}
1786
+ */
1787
+ function lower_jsx_code_block_function_body(node) {
1788
+ if (node.body?.type !== 'JSXCodeBlock') return;
1789
+
1790
+ const code_block = node.body;
1791
+ const statements = [...code_block.body];
1792
+ if (code_block.render != null) {
1793
+ let render = code_block.render;
1794
+ if (!is_native_tsrx_node(render)) {
1795
+ // A control-flow output (@if/@for/@switch/@try) isn't itself a native
1796
+ // template node, so `return @if (…) { … }` wouldn't be recognized as a
1797
+ // component render output. Wrap it in a native fragment so it flows
1798
+ // through the same children-rendering path as a `<> … </>` render.
1799
+ const fragment = b.jsx_fragment([render]);
1800
+ fragment.metadata = { ...fragment.metadata, native_tsrx: true };
1801
+ render = fragment;
1802
+ }
1803
+ statements.push(b.return(render, code_block.render));
1804
+ }
1805
+ node.body = b.block(statements, code_block);
1806
+ if (node.type === 'ArrowFunctionExpression') {
1807
+ node.expression = false;
1808
+ }
1809
+ }
1810
+
1045
1811
  /**
1046
1812
  * @param {any} node
1047
1813
  * @param {{ next: () => any, state: TransformContext }} context
1814
+ * @param {{ nativeBody?: boolean }} [options]
1048
1815
  * @returns {any}
1049
1816
  */
1050
- function transform_native_tsrx_function(node, { next, state }) {
1817
+ function transform_native_tsrx_function(node, { next, state }, { nativeBody = false } = {}) {
1051
1818
  const helper_state =
1052
1819
  state.helper_state || create_helper_state(get_function_helper_base_name(node));
1053
1820
  const saved_helper_state = state.helper_state;
@@ -1059,7 +1826,8 @@ function transform_native_tsrx_function(node, { next, state }) {
1059
1826
  node.metadata = {
1060
1827
  ...(node.metadata || {}),
1061
1828
  native_tsrx: true,
1062
- ...(needs_hook_split(node, state) ? { hook_split: true } : {}),
1829
+ ...(nativeBody ? { native_tsrx_body: true } : {}),
1830
+ ...(nativeBody && needs_hook_split(node, state) ? { hook_split: true } : {}),
1063
1831
  };
1064
1832
  state.available_bindings = merge_binding_maps(
1065
1833
  saved_bindings,
@@ -1072,7 +1840,7 @@ function transform_native_tsrx_function(node, { next, state }) {
1072
1840
  if (
1073
1841
  inner !== node &&
1074
1842
  node.type === 'ArrowFunctionExpression' &&
1075
- node.body?.type === 'TsrxFragment' &&
1843
+ is_native_tsrx_node(node.body) &&
1076
1844
  inner.body?.type === 'BlockStatement'
1077
1845
  ) {
1078
1846
  inner.expression = false;
@@ -1085,6 +1853,7 @@ function transform_native_tsrx_function(node, { next, state }) {
1085
1853
  inner.metadata = {
1086
1854
  ...strip_function_transform_metadata(inner.metadata),
1087
1855
  native_tsrx_function: true,
1856
+ ...(nativeBody ? { native_tsrx_body: true } : {}),
1088
1857
  ...(!saved_helper_state ? create_generated_helper_metadata(helper_state) || {} : {}),
1089
1858
  };
1090
1859
 
@@ -1132,6 +1901,10 @@ function find_native_await(node) {
1132
1901
  return find_first_top_level_await(node.body, false);
1133
1902
  }
1134
1903
 
1904
+ if (node.body?.type === 'JSXCodeBlock') {
1905
+ return find_native_await_in_list(get_raw_jsx_code_block_body_nodes(node.body));
1906
+ }
1907
+
1135
1908
  const body = node.body?.type === 'BlockStatement' ? node.body.body || [] : [];
1136
1909
  return find_native_await_in_list(body);
1137
1910
  }
@@ -1155,7 +1928,7 @@ function find_native_await_in_list(statements) {
1155
1928
  function find_native_await_in_statement(statement) {
1156
1929
  if (!statement || typeof statement !== 'object') return null;
1157
1930
 
1158
- if (statement.type === 'ReturnStatement' && statement.argument?.type === 'TsrxFragment') {
1931
+ if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
1159
1932
  return find_first_top_level_await_in_tsrx_function_body(statement.argument.children || []);
1160
1933
  }
1161
1934
 
@@ -1174,14 +1947,14 @@ function find_native_await_in_statement(statement) {
1174
1947
  return find_native_await_in_list(statement.body || []);
1175
1948
  }
1176
1949
 
1177
- if (statement.type === 'IfStatement') {
1950
+ if (is_if_control_node(statement)) {
1178
1951
  return (
1179
1952
  find_native_await_in_statement(statement.consequent) ||
1180
1953
  find_native_await_in_statement(statement.alternate)
1181
1954
  );
1182
1955
  }
1183
1956
 
1184
- if (statement.type === 'SwitchStatement') {
1957
+ if (is_switch_control_node(statement)) {
1185
1958
  for (const switch_case of statement.cases || []) {
1186
1959
  const found = find_native_await_in_list(switch_case.consequent || []);
1187
1960
  if (found) return found;
@@ -1189,7 +1962,7 @@ function find_native_await_in_statement(statement) {
1189
1962
  return null;
1190
1963
  }
1191
1964
 
1192
- if (statement.type === 'TryStatement') {
1965
+ if (is_try_control_node(statement)) {
1193
1966
  return (
1194
1967
  find_native_await_in_statement(statement.block) ||
1195
1968
  find_native_await_in_statement(statement.handler?.body) ||
@@ -1197,7 +1970,7 @@ function find_native_await_in_statement(statement) {
1197
1970
  );
1198
1971
  }
1199
1972
 
1200
- return null;
1973
+ return find_first_top_level_await(statement, false);
1201
1974
  }
1202
1975
 
1203
1976
  /**
@@ -1207,12 +1980,7 @@ function find_native_await_in_statement(statement) {
1207
1980
  */
1208
1981
  function transform_function_with_hook_helpers(node, { next, state }) {
1209
1982
  const has_hook_bearing_tsrx = function_contains_hook_bearing_tsrx(node, state);
1210
- const has_hook_split = needs_hook_split(node, state);
1211
- if (
1212
- state.helper_state ||
1213
- !is_uppercase_function_like(node) ||
1214
- (!has_hook_bearing_tsrx && !has_hook_split)
1215
- ) {
1983
+ if (state.helper_state || !is_uppercase_function_like(node) || !has_hook_bearing_tsrx) {
1216
1984
  return next() ?? node;
1217
1985
  }
1218
1986
 
@@ -1223,12 +1991,6 @@ function transform_function_with_hook_helpers(node, { next, state }) {
1223
1991
 
1224
1992
  state.helper_state = helper_state;
1225
1993
  state.hook_helpers_enabled = true;
1226
- if (has_hook_split) {
1227
- node.metadata = {
1228
- ...(node.metadata || {}),
1229
- hook_split: true,
1230
- };
1231
- }
1232
1994
  state.available_bindings = collect_function_scope_bindings(node);
1233
1995
 
1234
1996
  const inner = /** @type {any} */ (next() ?? node);
@@ -1250,7 +2012,7 @@ function transform_function_with_hook_helpers(node, { next, state }) {
1250
2012
  * @returns {string}
1251
2013
  */
1252
2014
  function get_function_helper_base_name(node) {
1253
- return get_function_like_name(node) || 'TsrxFragment';
2015
+ return get_function_like_name(node) || 'TSRXTemplate';
1254
2016
  }
1255
2017
 
1256
2018
  /**
@@ -1329,7 +2091,7 @@ function collect_function_scope_bindings(node) {
1329
2091
  const bindings = collect_param_bindings(node.params || []);
1330
2092
  if (node.body?.type === 'BlockStatement') {
1331
2093
  for (const statement of node.body.body || []) {
1332
- if (statement.type === 'ReturnStatement' && statement.argument?.type === 'TsrxFragment') {
2094
+ if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
1333
2095
  for (const child of get_tsrx_render_children(statement.argument)) {
1334
2096
  collect_statement_bindings(child, bindings);
1335
2097
  }
@@ -1361,6 +2123,10 @@ function merge_binding_maps(outer, inner) {
1361
2123
  function function_has_native_tsrx_return(node) {
1362
2124
  if (!node) return false;
1363
2125
 
2126
+ if (node.body?.type === 'JSXCodeBlock') {
2127
+ return true;
2128
+ }
2129
+
1364
2130
  if (node.type === 'ArrowFunctionExpression' && node.body?.type !== 'BlockStatement') {
1365
2131
  return node_contains_native_tsrx_template(node.body);
1366
2132
  }
@@ -1396,22 +2162,23 @@ function statement_contains_native_tsrx_return(statement) {
1396
2162
  return statements_contain_native_tsrx_return(statement.body || []);
1397
2163
  }
1398
2164
 
1399
- if (statement.type === 'IfStatement') {
2165
+ if (is_if_control_node(statement)) {
1400
2166
  return (
1401
2167
  statement_contains_native_tsrx_return(statement.consequent) ||
1402
2168
  statement_contains_native_tsrx_return(statement.alternate)
1403
2169
  );
1404
2170
  }
1405
2171
 
1406
- if (statement.type === 'SwitchStatement') {
2172
+ if (is_switch_control_node(statement)) {
1407
2173
  return (statement.cases || []).some((/** @type {any} */ c) =>
1408
2174
  statements_contain_native_tsrx_return(c.consequent || []),
1409
2175
  );
1410
2176
  }
1411
2177
 
1412
- if (statement.type === 'TryStatement') {
2178
+ if (is_try_control_node(statement)) {
1413
2179
  return (
1414
2180
  statement_contains_native_tsrx_return(statement.block) ||
2181
+ statement_contains_native_tsrx_return(statement.pending) ||
1415
2182
  statement_contains_native_tsrx_return(statement.handler?.body) ||
1416
2183
  statement_contains_native_tsrx_return(statement.finalizer)
1417
2184
  );
@@ -1438,7 +2205,7 @@ function statement_contains_native_tsrx_return(statement) {
1438
2205
  */
1439
2206
  function node_contains_native_tsrx_template(node) {
1440
2207
  if (!node || typeof node !== 'object') return false;
1441
- if (node.type === 'Element' || node.type === 'TsrxFragment') return true;
2208
+ if (is_native_tsrx_node(node)) return true;
1442
2209
 
1443
2210
  if (is_function_or_class_boundary(node)) {
1444
2211
  return false;
@@ -1487,7 +2254,7 @@ function prepare_tsrx_fragment_styles(node, transform_context) {
1487
2254
  if (!css) return null;
1488
2255
 
1489
2256
  const style_refs = collect_style_ref_attributes(node);
1490
- apply_css_definition_metadata(node, css, style_refs.length > 0);
2257
+ apply_css_definition_metadata(node, css, transform_context, style_refs.length > 0);
1491
2258
  transform_context.stylesheets.push(css);
1492
2259
  const fragment = annotate_tsrx_with_hash(
1493
2260
  node,
@@ -1629,11 +2396,11 @@ function collect_style_elements(node, styles) {
1629
2396
  return;
1630
2397
  }
1631
2398
 
1632
- if (is_function_or_class_boundary(node) || node.type === 'TsrxFragment') {
2399
+ if (is_function_or_class_boundary(node)) {
1633
2400
  return;
1634
2401
  }
1635
2402
 
1636
- if (node.type === 'Element') {
2403
+ if ((node.type === 'JSXElement' || node.type === 'JSXFragment') && node.metadata?.native_tsrx) {
1637
2404
  collect_style_elements(node.children || [], styles);
1638
2405
  return;
1639
2406
  }
@@ -1643,20 +2410,20 @@ function collect_style_elements(node, styles) {
1643
2410
  return;
1644
2411
  }
1645
2412
 
1646
- if (node.type === 'IfStatement') {
2413
+ if (is_if_control_node(node)) {
1647
2414
  collect_style_elements(node.consequent, styles);
1648
2415
  collect_style_elements(node.alternate, styles);
1649
2416
  return;
1650
2417
  }
1651
2418
 
1652
- if (node.type === 'SwitchStatement') {
2419
+ if (is_switch_control_node(node)) {
1653
2420
  for (const switch_case of node.cases || []) {
1654
2421
  collect_style_elements(switch_case.consequent || [], styles);
1655
2422
  }
1656
2423
  return;
1657
2424
  }
1658
2425
 
1659
- if (node.type === 'TryStatement') {
2426
+ if (is_try_control_node(node)) {
1660
2427
  collect_style_elements(node.block, styles);
1661
2428
  collect_style_elements(node.handler?.body, styles);
1662
2429
  collect_style_elements(node.finalizer, styles);
@@ -1708,7 +2475,7 @@ function strip_style_elements(node) {
1708
2475
  return node;
1709
2476
  }
1710
2477
 
1711
- if (node.type === 'Element') {
2478
+ if ((node.type === 'JSXElement' || node.type === 'JSXFragment') && node.metadata?.native_tsrx) {
1712
2479
  node.children = strip_style_elements(node.children || []);
1713
2480
  return node;
1714
2481
  }
@@ -1718,20 +2485,20 @@ function strip_style_elements(node) {
1718
2485
  return node;
1719
2486
  }
1720
2487
 
1721
- if (node.type === 'IfStatement') {
2488
+ if (is_if_control_node(node)) {
1722
2489
  node.consequent = strip_style_elements(node.consequent);
1723
2490
  if (node.alternate) node.alternate = strip_style_elements(node.alternate);
1724
2491
  return node;
1725
2492
  }
1726
2493
 
1727
- if (node.type === 'SwitchStatement') {
2494
+ if (is_switch_control_node(node)) {
1728
2495
  for (const switch_case of node.cases || []) {
1729
2496
  switch_case.consequent = strip_style_elements(switch_case.consequent || []);
1730
2497
  }
1731
2498
  return node;
1732
2499
  }
1733
2500
 
1734
- if (node.type === 'TryStatement') {
2501
+ if (is_try_control_node(node)) {
1735
2502
  node.block = strip_style_elements(node.block);
1736
2503
  if (node.handler?.body) node.handler.body = strip_style_elements(node.handler.body);
1737
2504
  if (node.finalizer) node.finalizer = strip_style_elements(node.finalizer);
@@ -1747,9 +2514,7 @@ function strip_style_elements(node) {
1747
2514
  function is_style_expression_position(path) {
1748
2515
  const parent = path.at(-1);
1749
2516
  return !(
1750
- parent?.type === 'Element' ||
1751
- parent?.type === 'TsrxFragment' ||
1752
- parent?.type === 'TsxCompat' ||
2517
+ is_native_tsrx_node(parent) ||
1753
2518
  parent?.type === 'BlockStatement' ||
1754
2519
  parent?.type === 'Program' ||
1755
2520
  parent?.type === 'SwitchCase'
@@ -1804,9 +2569,11 @@ function create_native_tsrx_statement_list_block(block, transform_context) {
1804
2569
  function create_native_tsrx_render_statements(fragment, transform_context) {
1805
2570
  return with_tsrx_fragment_styles(fragment, transform_context, (style_context) => {
1806
2571
  const target = style_context?.fragment ?? fragment;
2572
+ const render_nodes =
2573
+ target.type === 'JSXFragment' ? get_tsrx_render_children(target) : [target];
1807
2574
  return [
1808
2575
  ...create_tsrx_style_ref_setup_statements(target, style_context, transform_context),
1809
- ...build_render_statements(get_tsrx_render_children(target), true, transform_context),
2576
+ ...build_render_statements(render_nodes, true, transform_context),
1810
2577
  ];
1811
2578
  });
1812
2579
  }
@@ -1836,7 +2603,7 @@ function expand_native_tsrx_return_statement_list(statements, transform_context)
1836
2603
  function expand_native_tsrx_return_statement(statement, transform_context) {
1837
2604
  if (!statement || typeof statement !== 'object') return [statement];
1838
2605
 
1839
- if (statement.type === 'ReturnStatement' && statement.argument?.type === 'TsrxFragment') {
2606
+ if (statement.type === 'ReturnStatement' && is_native_tsrx_node(statement.argument)) {
1840
2607
  return create_native_tsrx_render_statements(statement.argument, transform_context);
1841
2608
  }
1842
2609
 
@@ -1849,7 +2616,7 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
1849
2616
  return body === statement.body ? [statement] : [b.block(body, statement)];
1850
2617
  }
1851
2618
 
1852
- if (statement.type === 'IfStatement') {
2619
+ if (is_if_control_node(statement)) {
1853
2620
  const consequent = expand_embedded_native_return_statement(
1854
2621
  statement.consequent,
1855
2622
  transform_context,
@@ -1863,7 +2630,7 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
1863
2630
  return [set_loc(b.if(statement.test, consequent, alternate), statement)];
1864
2631
  }
1865
2632
 
1866
- if (statement.type === 'SwitchStatement') {
2633
+ if (is_switch_control_node(statement)) {
1867
2634
  let changed = false;
1868
2635
  const cases = (statement.cases || []).map((/** @type {any} */ switch_case) => {
1869
2636
  const consequent = expand_native_tsrx_return_statement_list(
@@ -1879,8 +2646,11 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
1879
2646
  return changed ? [set_loc(b.switch(statement.discriminant, cases), statement)] : [statement];
1880
2647
  }
1881
2648
 
1882
- if (statement.type === 'TryStatement') {
2649
+ if (is_try_control_node(statement)) {
1883
2650
  const block = expand_embedded_native_return_statement(statement.block, transform_context);
2651
+ const pending = statement.pending
2652
+ ? expand_embedded_native_return_statement(statement.pending, transform_context)
2653
+ : statement.pending;
1884
2654
  const handler_body = statement.handler?.body
1885
2655
  ? expand_embedded_native_return_statement(statement.handler.body, transform_context)
1886
2656
  : statement.handler?.body;
@@ -1889,6 +2659,7 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
1889
2659
  : statement.finalizer;
1890
2660
  if (
1891
2661
  block === statement.block &&
2662
+ pending === statement.pending &&
1892
2663
  handler_body === statement.handler?.body &&
1893
2664
  finalizer === statement.finalizer
1894
2665
  ) {
@@ -1903,7 +2674,7 @@ function expand_native_tsrx_return_statement(statement, transform_context) {
1903
2674
  statement.handler,
1904
2675
  )
1905
2676
  : statement.handler;
1906
- return [set_loc(b.try(block, handler, finalizer, statement.pending ?? null), statement)];
2677
+ return [set_loc(b.try(block, handler, finalizer, pending ?? null), statement)];
1907
2678
  }
1908
2679
 
1909
2680
  return [statement];
@@ -2036,7 +2807,7 @@ function node_contains_hook_bearing_tsrx(node, transform_context) {
2036
2807
  return node.some((child) => node_contains_hook_bearing_tsrx(child, transform_context));
2037
2808
  }
2038
2809
 
2039
- if (node.type === 'TsrxFragment') {
2810
+ if (is_native_tsrx_node(node)) {
2040
2811
  return body_contains_top_level_hook_call(node.children || [], transform_context, true);
2041
2812
  }
2042
2813
 
@@ -2083,7 +2854,7 @@ function should_extract_hook_helpers(transform_context) {
2083
2854
  */
2084
2855
  function create_module_scoped_hook_component_id(helper_id, transform_context) {
2085
2856
  return create_generated_identifier(
2086
- `${transform_context.helper_state?.base_name || 'TsrxFragment'}__${helper_id.name}`,
2857
+ `${transform_context.helper_state?.base_name || 'TSRXTemplate'}__${helper_id.name}`,
2087
2858
  );
2088
2859
  }
2089
2860
 
@@ -2301,6 +3072,48 @@ function expand_component_helpers(program) {
2301
3072
  return program;
2302
3073
  }
2303
3074
 
3075
+ /**
3076
+ * Generated helper metadata can be appended after the main transformer walk.
3077
+ * If one of those helpers contains a statement-container body, lower it before
3078
+ * the printer sees the helper subtree.
3079
+ *
3080
+ * @param {any} node
3081
+ * @param {TransformContext} transform_context
3082
+ * @param {Set<any>} [seen]
3083
+ * @returns {void}
3084
+ */
3085
+ function lower_remaining_jsx_code_blocks(node, transform_context, seen = new Set()) {
3086
+ if (!node || typeof node !== 'object' || seen.has(node)) return;
3087
+ seen.add(node);
3088
+
3089
+ if (is_function_or_class_boundary(node)) {
3090
+ lower_jsx_code_block_function_body(node);
3091
+ }
3092
+
3093
+ for (const key of Object.keys(node)) {
3094
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
3095
+ let value = node[key];
3096
+ if (!value || typeof value !== 'object') continue;
3097
+
3098
+ if (Array.isArray(value)) {
3099
+ if (key === 'body') {
3100
+ value = node[key] = value.flatMap((child) => {
3101
+ if (child?.type !== 'JSXCodeBlock') return [child];
3102
+ const body_nodes = get_jsx_code_block_body_nodes(child, transform_context);
3103
+ return mark_native_pretransformed_jsx(
3104
+ build_render_statements(body_nodes, true, transform_context),
3105
+ );
3106
+ });
3107
+ }
3108
+ for (const child of value) {
3109
+ lower_remaining_jsx_code_blocks(child, transform_context, seen);
3110
+ }
3111
+ } else {
3112
+ lower_remaining_jsx_code_blocks(value, transform_context, seen);
3113
+ }
3114
+ }
3115
+ }
3116
+
2304
3117
  /**
2305
3118
  * Generated helper/statics metadata can be carried on function declarations,
2306
3119
  * variable declarations, object literal members, or export-safe expressions,
@@ -2383,11 +3196,7 @@ function create_component_return_statement(
2383
3196
  * @returns {boolean}
2384
3197
  */
2385
3198
  function is_loop_skip_return_statement(node) {
2386
- return (
2387
- node?.type === 'ReturnStatement' &&
2388
- node.argument == null &&
2389
- node.metadata?.generated_loop_continue_return === true
2390
- );
3199
+ return node?.type === 'ReturnStatement' && node.metadata?.generated_loop_continue_return === true;
2391
3200
  }
2392
3201
 
2393
3202
  /**
@@ -2403,7 +3212,7 @@ function is_loop_skip_if_statement(node) {
2403
3212
  * @returns {any[] | null}
2404
3213
  */
2405
3214
  function get_loop_skip_if_consequent_body(node) {
2406
- if (node?.type !== 'IfStatement' || node.alternate) {
3215
+ if (!is_if_control_node(node) || node.alternate) {
2407
3216
  return null;
2408
3217
  }
2409
3218
 
@@ -2424,7 +3233,15 @@ function create_component_loop_skip_if_statement(node, render_nodes, transform_c
2424
3233
  const branch_statements = build_render_statements(consequent_body, true, transform_context);
2425
3234
  prepend_render_nodes_to_return_statements(branch_statements, render_nodes);
2426
3235
 
2427
- return set_loc(b.if(node.test, set_loc(b.block(branch_statements), node.consequent), null), node);
3236
+ const statement = set_loc(
3237
+ b.if(node.test, set_loc(b.block(branch_statements), node.consequent), null),
3238
+ node,
3239
+ );
3240
+ statement.metadata = {
3241
+ ...(statement.metadata || {}),
3242
+ generated_loop_skip_if: true,
3243
+ };
3244
+ return statement;
2428
3245
  }
2429
3246
 
2430
3247
  /**
@@ -2803,26 +3620,33 @@ function create_helper_props_type_literal_with_typeof_flags(bindings, aliases, u
2803
3620
  /**
2804
3621
  * @param {any} node
2805
3622
  * @param {TransformContext} transform_context
3623
+ * @param {boolean} [in_jsx_child]
2806
3624
  * @returns {any}
2807
3625
  */
2808
- function to_jsx_element(node, transform_context, raw_children = node.children || []) {
2809
- if (node.type === 'JSXElement') return node;
2810
- if (!node.id) {
3626
+ function to_jsx_element(
3627
+ node,
3628
+ transform_context,
3629
+ raw_children = node.children || [],
3630
+ in_jsx_child = false,
3631
+ ) {
3632
+ if (node.type === 'JSXElement' && !node.metadata?.native_tsrx) {
3633
+ return node;
3634
+ }
3635
+
3636
+ const source_opening = node.openingElement;
3637
+ const source_name = source_opening?.name;
3638
+ if (!source_name) {
2811
3639
  report_jsx_fragment_in_tsrx_error(node, transform_context);
2812
3640
  return set_loc(b.jsx_fragment(), node);
2813
3641
  }
2814
- if (is_dynamic_element_id(node.id)) {
2815
- return dynamic_element_to_jsx_child(node, transform_context);
2816
- }
2817
-
2818
- const name = identifier_to_jsx_name(node.id);
3642
+ const name = clone_jsx_name(source_name);
2819
3643
  const attributes = transform_element_attributes_dispatch(
2820
- node.attributes || [],
3644
+ source_opening.attributes || [],
2821
3645
  transform_context,
2822
3646
  node,
2823
3647
  );
2824
3648
  const walked_children = node.children || [];
2825
- let selfClosing = !!node.selfClosing;
3649
+ let selfClosing = !!source_opening.selfClosing;
2826
3650
  let children;
2827
3651
  const child_transform = transform_context.platform.hooks?.transformElementChildren?.(
2828
3652
  node,
@@ -2848,7 +3672,7 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
2848
3672
  name,
2849
3673
  attributes,
2850
3674
  selfClosing,
2851
- node.openingElement?.typeArguments,
3675
+ source_opening.typeArguments,
2852
3676
  );
2853
3677
  const openingElement = has_unmappable_attribute
2854
3678
  ? opening_element_node
@@ -2950,7 +3774,7 @@ function child_contains_return_semantics(node) {
2950
3774
  * @returns {boolean}
2951
3775
  */
2952
3776
  function is_inline_element_child(node) {
2953
- return node && is_jsx_child(node);
3777
+ return node && is_render_child_node(node);
2954
3778
  }
2955
3779
 
2956
3780
  /**
@@ -3122,7 +3946,13 @@ function collect_block_binding_names_from_statement(statement, names) {
3122
3946
  return;
3123
3947
  }
3124
3948
 
3125
- if (statement.type === 'ForOfStatement' || statement.type === 'ForInStatement') {
3949
+ if (
3950
+ statement.type === 'ForOfStatement' ||
3951
+ statement.type === 'ForInStatement' ||
3952
+ (statement.type === 'JSXForExpression' &&
3953
+ (statement.statementType === 'ForOfStatement' ||
3954
+ statement.statementType === 'ForInStatement'))
3955
+ ) {
3126
3956
  if (statement.left?.type === 'VariableDeclaration' && statement.left.kind === 'var') {
3127
3957
  for (const declaration of statement.left.declarations || []) {
3128
3958
  collect_pattern_names(declaration.id, names);
@@ -3283,7 +4113,7 @@ function validate_hook_outer_assignments_in_node(
3283
4113
  }
3284
4114
  }
3285
4115
 
3286
- if (node.type === 'ForOfStatement') {
4116
+ if (is_for_of_control_node(node)) {
3287
4117
  if (
3288
4118
  node.left &&
3289
4119
  node.left.type !== 'VariableDeclaration' &&
@@ -3716,9 +4546,7 @@ function get_hook_callee_name(callee) {
3716
4546
  * @param {any} source_node
3717
4547
  * @param {TransformContext} transform_context
3718
4548
  * @param {AST.Identifier} [preallocated_helper_id] - Optional pre-allocated id.
3719
- * Used by the switch lift's chained-call build, which allocates ids in
3720
- * source order in a forward pass and then constructs helpers in reverse so
3721
- * each fall-through case can reference the next case's component element.
4549
+ * Used by switch lifting to keep generated helper ids stable in source order.
3722
4550
  * @param {{ transientBindings?: Set<string> }} [options]
3723
4551
  * @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
3724
4552
  */
@@ -3997,6 +4825,133 @@ function get_body_source_node(body_nodes) {
3997
4825
  return first;
3998
4826
  }
3999
4827
 
4828
+ /**
4829
+ * @param {any} node
4830
+ * @returns {any}
4831
+ */
4832
+ function jsx_control_expression_to_statement(node) {
4833
+ if (!node?.statementType) return node;
4834
+ return { ...node, type: node.statementType };
4835
+ }
4836
+
4837
+ /**
4838
+ * @param {any} node
4839
+ * @param {TransformContext} transform_context
4840
+ * @returns {any[]}
4841
+ */
4842
+ function get_jsx_code_block_body_nodes(node, transform_context) {
4843
+ if (!node.render) {
4844
+ return node.body || [];
4845
+ }
4846
+
4847
+ if (is_native_tsrx_node(node.render)) {
4848
+ const style_context = prepare_tsrx_fragment_styles(node.render, transform_context);
4849
+ const render = style_context?.fragment ?? node.render;
4850
+ return [
4851
+ ...(node.body || []),
4852
+ ...create_tsrx_style_ref_setup_statements(render, style_context, transform_context),
4853
+ render,
4854
+ ];
4855
+ }
4856
+
4857
+ return [...(node.body || []), node.render];
4858
+ }
4859
+
4860
+ /**
4861
+ * @param {any} node
4862
+ * @returns {any[]}
4863
+ */
4864
+ function get_raw_jsx_code_block_body_nodes(node) {
4865
+ return [...(node.body || []), ...(node.render ? [node.render] : [])];
4866
+ }
4867
+
4868
+ /**
4869
+ * @param {any} node
4870
+ * @returns {boolean}
4871
+ */
4872
+ function is_native_tsrx_node(node) {
4873
+ return (
4874
+ node?.type === 'JSXCodeBlock' ||
4875
+ ((node?.type === 'JSXElement' ||
4876
+ node?.type === 'JSXFragment' ||
4877
+ node?.type === 'JSXStyleElement') &&
4878
+ node.metadata?.native_tsrx)
4879
+ );
4880
+ }
4881
+
4882
+ /**
4883
+ * @param {any} node
4884
+ * @returns {boolean}
4885
+ */
4886
+ function is_if_control_node(node) {
4887
+ return node?.type === 'IfStatement' || node?.type === 'JSXIfExpression';
4888
+ }
4889
+
4890
+ /**
4891
+ * @param {any} node
4892
+ * @returns {boolean}
4893
+ */
4894
+ function is_plain_if_statement(node) {
4895
+ return node?.type === 'IfStatement' && !is_template_if_node(node);
4896
+ }
4897
+
4898
+ /**
4899
+ * @param {any} node
4900
+ * @returns {boolean}
4901
+ */
4902
+ function is_render_child_node(node) {
4903
+ if (!node) return false;
4904
+
4905
+ switch (node.type) {
4906
+ case 'JSXElement':
4907
+ case 'JSXFragment':
4908
+ case 'JSXExpressionContainer':
4909
+ case 'JSXText':
4910
+ case 'JSXIfExpression':
4911
+ case 'JSXForExpression':
4912
+ case 'JSXSwitchExpression':
4913
+ case 'JSXTryExpression':
4914
+ return true;
4915
+ case 'IfStatement':
4916
+ return is_template_if_node(node);
4917
+ case 'ForOfStatement':
4918
+ return is_template_for_of_node(node);
4919
+ case 'SwitchStatement':
4920
+ return is_template_switch_node(node);
4921
+ case 'TryStatement':
4922
+ return is_template_try_node(node);
4923
+ default:
4924
+ return false;
4925
+ }
4926
+ }
4927
+
4928
+ /**
4929
+ * @param {any} node
4930
+ * @returns {boolean}
4931
+ */
4932
+ function is_switch_control_node(node) {
4933
+ return node?.type === 'SwitchStatement' || node?.type === 'JSXSwitchExpression';
4934
+ }
4935
+
4936
+ /**
4937
+ * @param {any} node
4938
+ * @returns {boolean}
4939
+ */
4940
+ function is_try_control_node(node) {
4941
+ return node?.type === 'TryStatement' || node?.type === 'JSXTryExpression';
4942
+ }
4943
+
4944
+ /**
4945
+ * @param {any} node
4946
+ * @returns {boolean}
4947
+ */
4948
+ function is_for_of_control_node(node) {
4949
+ return (
4950
+ node?.type === 'ForOfStatement' ||
4951
+ (node?.type === 'JSXForExpression' && node.statementType === 'ForOfStatement')
4952
+ );
4953
+ }
4954
+
4000
4955
  /**
4001
4956
  * @param {any} node
4002
4957
  * @param {TransformContext} transform_context
@@ -4005,33 +4960,65 @@ function get_body_source_node(body_nodes) {
4005
4960
  function to_jsx_child(node, transform_context) {
4006
4961
  if (!node) return node;
4007
4962
  switch (node.type) {
4008
- case 'TsrxFragment':
4009
- return tsrx_node_to_jsx_expression(node, transform_context, true);
4010
- case 'TsxCompat':
4011
- return tsx_compat_node_to_jsx_expression(node, transform_context, true);
4012
- case 'Element':
4013
- return to_jsx_element(node, transform_context);
4014
- case 'Text':
4015
- return to_jsx_expression_container(to_text_expression(node.expression, node), node);
4016
- case 'TSRXExpression':
4017
- return to_jsx_expression_container(node.expression, node);
4963
+ case 'JSXElement':
4964
+ if (is_native_tsrx_node(node)) {
4965
+ return to_jsx_element(node, transform_context, node.children || [], true);
4966
+ }
4967
+ return node;
4968
+ case 'JSXFragment':
4969
+ if (is_native_tsrx_node(node)) {
4970
+ return tsrx_node_to_jsx_expression(node, transform_context, true);
4971
+ }
4972
+ return node;
4973
+ case 'JSXIfExpression':
4018
4974
  case 'IfStatement':
4975
+ if (node.type === 'IfStatement' && !is_template_if_node(node)) {
4976
+ return node;
4977
+ }
4978
+ if (node.metadata?.generated_loop_skip_if) {
4979
+ return node;
4980
+ }
4019
4981
  return (
4020
4982
  transform_context.platform.hooks?.controlFlow?.ifStatement ?? if_statement_to_jsx_child
4021
- )(node, transform_context);
4983
+ )(jsx_control_expression_to_statement(node), transform_context);
4984
+ case 'JSXForExpression':
4985
+ if (node.statementType !== 'ForOfStatement') {
4986
+ error(
4987
+ 'TSRX `@for` currently supports `for...of` loops in template output.',
4988
+ transform_context.filename,
4989
+ node,
4990
+ transform_context.errors,
4991
+ transform_context.comments,
4992
+ );
4993
+ return to_jsx_expression_container(create_null_literal(), node);
4994
+ }
4995
+ return (
4996
+ transform_context.platform.hooks?.controlFlow?.forOf ?? for_of_statement_to_jsx_child
4997
+ )(jsx_control_expression_to_statement(node), transform_context);
4022
4998
  case 'ForOfStatement':
4999
+ if (!is_template_for_of_node(node)) {
5000
+ return node;
5001
+ }
4023
5002
  return (
4024
5003
  transform_context.platform.hooks?.controlFlow?.forOf ?? for_of_statement_to_jsx_child
4025
5004
  )(node, transform_context);
5005
+ case 'JSXSwitchExpression':
4026
5006
  case 'SwitchStatement':
5007
+ if (node.type === 'SwitchStatement' && !is_template_switch_node(node)) {
5008
+ return node;
5009
+ }
4027
5010
  return (
4028
5011
  transform_context.platform.hooks?.controlFlow?.switchStatement ??
4029
5012
  switch_statement_to_jsx_child
4030
- )(node, transform_context);
5013
+ )(jsx_control_expression_to_statement(node), transform_context);
5014
+ case 'JSXTryExpression':
4031
5015
  case 'TryStatement':
5016
+ if (node.type === 'TryStatement' && !is_template_try_node(node)) {
5017
+ return node;
5018
+ }
4032
5019
  return (
4033
5020
  transform_context.platform.hooks?.controlFlow?.tryStatement ?? try_statement_to_jsx_child
4034
- )(node, transform_context);
5021
+ )(jsx_control_expression_to_statement(node), transform_context);
4035
5022
  default:
4036
5023
  return node;
4037
5024
  }
@@ -4040,7 +5027,7 @@ function to_jsx_child(node, transform_context) {
4040
5027
  /**
4041
5028
  * Lower a native TSRX fragment body to a JSX expression.
4042
5029
  * Children have already been parsed and transformed through the normal TSRX
4043
- * Element/Text/control-flow visitors.
5030
+ * JSX element/text/control-flow visitors.
4044
5031
  *
4045
5032
  * @param {any} node
4046
5033
  * @param {TransformContext} transform_context
@@ -4123,7 +5110,7 @@ function return_value_statement_to_expression(node, transform_context) {
4123
5110
  return node.argument;
4124
5111
  }
4125
5112
 
4126
- if (node?.type === 'IfStatement') {
5113
+ if (is_if_control_node(node)) {
4127
5114
  return return_value_if_statement_to_conditional_expression(node, transform_context);
4128
5115
  }
4129
5116
 
@@ -4217,7 +5204,7 @@ function return_value_block_to_expression(node, transform_context) {
4217
5204
  * @returns {any | null}
4218
5205
  */
4219
5206
  function return_value_if_statement_to_conditional_expression(node, transform_context) {
4220
- if (!node || node.type !== 'IfStatement') return null;
5207
+ if (!is_if_control_node(node)) return null;
4221
5208
 
4222
5209
  const consequent = return_value_block_to_expression(node.consequent, transform_context);
4223
5210
  if (!consequent) return null;
@@ -4257,14 +5244,14 @@ function if_statement_to_jsx_child(node, transform_context) {
4257
5244
  * @returns {any | null}
4258
5245
  */
4259
5246
  function render_if_statement_to_conditional_expression(node) {
4260
- if (!node || node.type !== 'IfStatement') return null;
5247
+ if (!is_if_control_node(node)) return null;
4261
5248
 
4262
5249
  const consequent = block_statement_to_return_expression(node.consequent);
4263
5250
  if (!consequent) return null;
4264
5251
 
4265
5252
  let alternate = create_null_literal();
4266
5253
  if (node.alternate) {
4267
- if (node.alternate.type === 'IfStatement') {
5254
+ if (is_if_control_node(node.alternate)) {
4268
5255
  alternate = render_if_statement_to_conditional_expression(node.alternate);
4269
5256
  if (!alternate) return null;
4270
5257
  } else {
@@ -4301,25 +5288,11 @@ function block_statement_to_return_expression(block) {
4301
5288
  /**
4302
5289
  * Find the first `key` attribute expression in the top-level elements of a body.
4303
5290
  * Used to propagate keys from loop body elements to wrapper components.
4304
- * Works on both pre-transform (Ripple Element) and post-transform (JSXElement) nodes.
4305
- *
4306
5291
  * @param {any[]} body_nodes
4307
5292
  * @returns {any | undefined}
4308
5293
  */
4309
5294
  function find_key_expression_in_body(body_nodes) {
4310
5295
  for (const node of body_nodes) {
4311
- // Pre-transform: Ripple Element node
4312
- if (node.type === 'Element') {
4313
- for (const attr of node.attributes || []) {
4314
- if (attr.type === 'Attribute') {
4315
- const attr_name = typeof attr.name === 'string' ? attr.name : attr.name?.name;
4316
- if (attr_name === 'key') {
4317
- return attr.value?.expression ?? attr.value;
4318
- }
4319
- }
4320
- }
4321
- }
4322
- // Post-transform: JSXElement node
4323
5296
  if (node.type === 'JSXElement') {
4324
5297
  for (const attr of node.openingElement?.attributes || []) {
4325
5298
  if (
@@ -4344,7 +5317,7 @@ function find_key_expression_in_body(body_nodes) {
4344
5317
  * @returns {any}
4345
5318
  */
4346
5319
  function continue_to_bare_return(source_node) {
4347
- const node = set_loc(b.return(null), source_node);
5320
+ const node = set_loc(b.return(create_null_literal()), source_node);
4348
5321
  node.metadata = {
4349
5322
  ...(node.metadata || {}),
4350
5323
  generated_loop_continue_return: true,
@@ -4354,8 +5327,9 @@ function continue_to_bare_return(source_node) {
4354
5327
 
4355
5328
  /**
4356
5329
  * `continue` in a component `for...of` body means "skip this item". JSX targets
4357
- * lower `for...of` to callbacks, so a raw ContinueStatement would be invalid JS;
4358
- * a bare `return` from the callback preserves the item-skip behavior.
5330
+ * lower `for...of` to callbacks, so a raw ContinueStatement would be invalid JS.
5331
+ * Returning null from the callback preserves the item-skip behavior while still
5332
+ * producing an explicit "render nothing" value for JSX runtimes.
4359
5333
  *
4360
5334
  * @param {any[] | any} node
4361
5335
  * @param {boolean} [is_root]
@@ -4363,7 +5337,9 @@ function continue_to_bare_return(source_node) {
4363
5337
  */
4364
5338
  export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
4365
5339
  if (Array.isArray(node)) {
4366
- return node.map((child) => rewrite_loop_continues_to_bare_returns(child, false));
5340
+ return node.map((child) =>
5341
+ rewrite_loop_continues_to_bare_returns(child, is_root && !is_loop_statement(child)),
5342
+ );
4367
5343
  }
4368
5344
 
4369
5345
  if (!node || typeof node !== 'object') {
@@ -4388,6 +5364,181 @@ export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
4388
5364
  return node;
4389
5365
  }
4390
5366
 
5367
+ /**
5368
+ * @param {any[] | any} node
5369
+ * @param {TransformContext} transform_context
5370
+ * @param {boolean} [is_root]
5371
+ */
5372
+ function validate_for_body_control_flow(node, transform_context, is_root = true) {
5373
+ if (Array.isArray(node)) {
5374
+ for (const child of node) {
5375
+ validate_for_body_control_flow(
5376
+ child,
5377
+ transform_context,
5378
+ is_root && !is_loop_statement(child),
5379
+ );
5380
+ }
5381
+ return;
5382
+ }
5383
+
5384
+ if (!node || typeof node !== 'object') {
5385
+ return;
5386
+ }
5387
+
5388
+ if (is_template_if_node(node)) {
5389
+ return;
5390
+ }
5391
+
5392
+ if (node.type === 'ReturnStatement') {
5393
+ error(
5394
+ TSRX_FOR_RETURN_ERROR,
5395
+ transform_context.filename,
5396
+ node,
5397
+ transform_context.errors,
5398
+ transform_context.comments,
5399
+ );
5400
+ return;
5401
+ }
5402
+ if (node.type === 'BreakStatement') {
5403
+ error(
5404
+ TSRX_FOR_BREAK_ERROR,
5405
+ transform_context.filename,
5406
+ node,
5407
+ transform_context.errors,
5408
+ transform_context.comments,
5409
+ );
5410
+ return;
5411
+ }
5412
+ if (node.type === 'ContinueStatement') {
5413
+ error(
5414
+ TSRX_FOR_CONTINUE_ERROR,
5415
+ transform_context.filename,
5416
+ node,
5417
+ transform_context.errors,
5418
+ transform_context.comments,
5419
+ );
5420
+ return;
5421
+ }
5422
+
5423
+ if (is_function_or_class_boundary(node) || (!is_root && is_loop_statement(node))) {
5424
+ return;
5425
+ }
5426
+
5427
+ for (const key of Object.keys(node)) {
5428
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
5429
+ continue;
5430
+ }
5431
+ validate_for_body_control_flow(node[key], transform_context, false);
5432
+ }
5433
+ }
5434
+
5435
+ /**
5436
+ * @param {any[] | any} node
5437
+ * @param {TransformContext} transform_context
5438
+ */
5439
+ function validate_if_body_control_flow(node, transform_context) {
5440
+ if (Array.isArray(node)) {
5441
+ for (const child of node) {
5442
+ validate_if_body_control_flow(child, transform_context);
5443
+ }
5444
+ return;
5445
+ }
5446
+
5447
+ if (!node || typeof node !== 'object') {
5448
+ return;
5449
+ }
5450
+
5451
+ if (node.type === 'ReturnStatement') {
5452
+ error(
5453
+ TSRX_IF_RETURN_ERROR,
5454
+ transform_context.filename,
5455
+ node,
5456
+ transform_context.errors,
5457
+ transform_context.comments,
5458
+ );
5459
+ return;
5460
+ }
5461
+ if (node.type === 'BreakStatement') {
5462
+ error(
5463
+ TSRX_IF_BREAK_ERROR,
5464
+ transform_context.filename,
5465
+ node,
5466
+ transform_context.errors,
5467
+ transform_context.comments,
5468
+ );
5469
+ return;
5470
+ }
5471
+ if (node.type === 'ContinueStatement') {
5472
+ error(
5473
+ TSRX_IF_CONTINUE_ERROR,
5474
+ transform_context.filename,
5475
+ node,
5476
+ transform_context.errors,
5477
+ transform_context.comments,
5478
+ );
5479
+ return;
5480
+ }
5481
+
5482
+ if (is_function_or_class_boundary(node)) {
5483
+ return;
5484
+ }
5485
+
5486
+ for (const key of Object.keys(node)) {
5487
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
5488
+ continue;
5489
+ }
5490
+ validate_if_body_control_flow(node[key], transform_context);
5491
+ }
5492
+ }
5493
+
5494
+ /**
5495
+ * @param {any} node
5496
+ * @returns {boolean}
5497
+ */
5498
+ function is_template_if_node(node) {
5499
+ return (
5500
+ node?.type === 'JSXIfExpression' ||
5501
+ node?.metadata?.tsrxDirective === 'if' ||
5502
+ (node?.type === 'IfStatement' && node?.statementType === 'IfStatement')
5503
+ );
5504
+ }
5505
+
5506
+ /**
5507
+ * @param {any} node
5508
+ * @returns {boolean}
5509
+ */
5510
+ function is_template_for_of_node(node) {
5511
+ return (
5512
+ node?.type === 'JSXForExpression' ||
5513
+ node?.metadata?.tsrxDirective === 'for' ||
5514
+ (node?.type === 'ForOfStatement' && node?.statementType === 'ForOfStatement')
5515
+ );
5516
+ }
5517
+
5518
+ /**
5519
+ * @param {any} node
5520
+ * @returns {boolean}
5521
+ */
5522
+ function is_template_switch_node(node) {
5523
+ return (
5524
+ node?.type === 'JSXSwitchExpression' ||
5525
+ node?.metadata?.tsrxDirective === 'switch' ||
5526
+ (node?.type === 'SwitchStatement' && node?.statementType === 'SwitchStatement')
5527
+ );
5528
+ }
5529
+
5530
+ /**
5531
+ * @param {any} node
5532
+ * @returns {boolean}
5533
+ */
5534
+ function is_template_try_node(node) {
5535
+ return (
5536
+ node?.type === 'JSXTryExpression' ||
5537
+ node?.metadata?.tsrxDirective === 'try' ||
5538
+ (node?.type === 'TryStatement' && node?.statementType === 'TryStatement')
5539
+ );
5540
+ }
5541
+
4391
5542
  /**
4392
5543
  * @param {any} node
4393
5544
  * @returns {boolean}
@@ -4395,8 +5546,11 @@ export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
4395
5546
  function is_loop_statement(node) {
4396
5547
  return (
4397
5548
  node?.type === 'ForOfStatement' ||
5549
+ (node?.type === 'JSXForExpression' && node.statementType === 'ForOfStatement') ||
4398
5550
  node?.type === 'ForStatement' ||
5551
+ (node?.type === 'JSXForExpression' && node.statementType === 'ForStatement') ||
4399
5552
  node?.type === 'ForInStatement' ||
5553
+ (node?.type === 'JSXForExpression' && node.statementType === 'ForInStatement') ||
4400
5554
  node?.type === 'WhileStatement' ||
4401
5555
  node?.type === 'DoWhileStatement'
4402
5556
  );
@@ -4420,10 +5574,9 @@ function for_of_statement_to_jsx_child(node, transform_context) {
4420
5574
 
4421
5575
  const loop_params = get_for_of_iteration_params(node.left, node.index);
4422
5576
  const loop_body = /** @type {any[]} */ (
4423
- rewrite_loop_continues_to_bare_returns(
4424
- node.body.type === 'BlockStatement' ? node.body.body : [node.body],
4425
- )
5577
+ node.body.type === 'BlockStatement' ? node.body.body : [node.body]
4426
5578
  );
5579
+ validate_for_body_control_flow(loop_body, transform_context);
4427
5580
  const has_hooks =
4428
5581
  should_extract_hook_helpers(transform_context) &&
4429
5582
  body_contains_top_level_hook_call(loop_body, transform_context, true);
@@ -4478,17 +5631,49 @@ function for_of_statement_to_jsx_child(node, transform_context) {
4478
5631
  transform_context.available_bindings = saved_bindings;
4479
5632
 
4480
5633
  const iter_callback = b.arrow(loop_params, b.block(body_statements));
5634
+ const empty_fallback = node.empty
5635
+ ? b.call(
5636
+ b.arrow(
5637
+ [],
5638
+ b.block(
5639
+ build_render_statements(
5640
+ node.empty.type === 'BlockStatement' ? node.empty.body : [node.empty],
5641
+ true,
5642
+ transform_context,
5643
+ ),
5644
+ ),
5645
+ false,
5646
+ undefined,
5647
+ node.empty,
5648
+ ),
5649
+ )
5650
+ : null;
4481
5651
 
4482
5652
  if (transform_context.platform.imports.forOfIterableHelper) {
4483
5653
  transform_context.needs_for_of_iterable = true;
5654
+ const args = [node.right, iter_callback];
5655
+ if (empty_fallback) {
5656
+ args.push(b.literal(null), b.arrow([], empty_fallback));
5657
+ }
5658
+ return to_jsx_expression_container(b.call(b.id(MAP_ITERABLE_INTERNAL_NAME), ...args));
5659
+ }
5660
+
5661
+ const map_call = b.call(b.member(node.right, create_generated_identifier('map')), iter_callback);
5662
+ if (empty_fallback) {
4484
5663
  return to_jsx_expression_container(
4485
- b.call(b.id(MAP_ITERABLE_INTERNAL_NAME), node.right, iter_callback),
5664
+ b.conditional(
5665
+ b.binary(
5666
+ '===',
5667
+ b.member(clone_expression_node(node.right), create_generated_identifier('length')),
5668
+ b.literal(0),
5669
+ ),
5670
+ empty_fallback,
5671
+ map_call,
5672
+ ),
4486
5673
  );
4487
5674
  }
4488
5675
 
4489
- return to_jsx_expression_container(
4490
- b.call(b.member(node.right, create_generated_identifier('map')), iter_callback),
4491
- );
5676
+ return to_jsx_expression_container(map_call);
4492
5677
  }
4493
5678
 
4494
5679
  /**
@@ -4498,25 +5683,6 @@ function for_of_statement_to_jsx_child(node, transform_context) {
4498
5683
  */
4499
5684
  function apply_key_to_loop_body(body_nodes, key_expression) {
4500
5685
  for (const node of body_nodes) {
4501
- if (node.type === 'Element') {
4502
- const attributes = node.attributes || (node.attributes = []);
4503
- const has_key = attributes.some((/** @type {any} */ attr) => {
4504
- const attr_name = typeof attr.name === 'string' ? attr.name : attr.name?.name;
4505
- return attr_name === 'key';
4506
- });
4507
-
4508
- if (!has_key) {
4509
- attributes.push({
4510
- type: 'Attribute',
4511
- name: b.id('key'),
4512
- value: clone_expression_node(key_expression),
4513
- shorthand: false,
4514
- metadata: { path: [] },
4515
- });
4516
- }
4517
- return;
4518
- }
4519
-
4520
5686
  if (node.type === 'JSXElement') {
4521
5687
  const attributes = node.openingElement?.attributes || [];
4522
5688
  const has_key = attributes.some(
@@ -4546,7 +5712,7 @@ function apply_key_to_loop_body(body_nodes, key_expression) {
4546
5712
  function should_apply_key_to_loop_body(body_nodes) {
4547
5713
  let keyable_children = 0;
4548
5714
  for (const node of body_nodes) {
4549
- if (node.type === 'Element' || node.type === 'JSXElement') {
5715
+ if (node.type === 'JSXElement') {
4550
5716
  keyable_children += 1;
4551
5717
  }
4552
5718
  }
@@ -4636,11 +5802,11 @@ function switch_statement_to_jsx_child(node, transform_context) {
4636
5802
  }
4637
5803
 
4638
5804
  /**
4639
- * Transform a `try { ... } pending { ... } catch (err, reset) { ... }` block
5805
+ * Transform an `@try { ... } @pending { ... } @catch (err, reset) { ... }` block
4640
5806
  * into React `<TsrxErrorBoundary>` and/or `<Suspense>` JSX elements.
4641
5807
  *
4642
- * - `pending` → `<Suspense fallback={...}>`
4643
- * - `catch` → `<TsrxErrorBoundary fallback={(err, reset) => ...}>`
5808
+ * - `@pending` → `<Suspense fallback={...}>`
5809
+ * - `@catch` → `<TsrxErrorBoundary fallback={(err, reset) => ...}>`
4644
5810
  * - both → ErrorBoundary wraps Suspense
4645
5811
  * - JavaScript `try/finally` is not part of component template control flow
4646
5812
  *
@@ -4684,30 +5850,6 @@ function try_statement_to_jsx_child(node, transform_context) {
4684
5850
  );
4685
5851
  }
4686
5852
 
4687
- // Validate that try body contains JSX if pending block is present
4688
- if (pending) {
4689
- const try_body = node.block.body || [];
4690
- if (!try_body.some(is_jsx_child)) {
4691
- error(
4692
- 'TSRX try statements must contain a template in their main body. Move the try statement into a function if it does not render anything.',
4693
- transform_context.filename,
4694
- node.block,
4695
- transform_context.errors,
4696
- transform_context.comments,
4697
- );
4698
- }
4699
- const pending_body = pending.body || [];
4700
- if (pending_body.length > 0 && !pending_body.some(is_jsx_child)) {
4701
- error(
4702
- 'TSRX try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
4703
- transform_context.filename,
4704
- pending,
4705
- transform_context.errors,
4706
- transform_context.comments,
4707
- );
4708
- }
4709
- }
4710
-
4711
5853
  // Build the try body content as JSX children
4712
5854
  const try_body_nodes = node.block.body || [];
4713
5855
  const try_content = statement_body_to_jsx_child(try_body_nodes, transform_context);
@@ -4860,8 +6002,8 @@ function create_jsx_element(tag_name, attributes, children) {
4860
6002
 
4861
6003
  /**
4862
6004
  * Inject runtime-helper import declarations the transform decided it needed
4863
- * during the walk: `Suspense` for `try { ... } pending { ... }`,
4864
- * `TsrxErrorBoundary` for `try { ... } catch (...)`, and `mergeRefs` for
6005
+ * during the walk: `Suspense` for `@try { ... } @pending { ... }`,
6006
+ * `TsrxErrorBoundary` for `@try { ... } @catch (...)`, and `mergeRefs` for
4865
6007
  * elements with multiple `ref` attributes under the `'merge-refs'`
4866
6008
  * strategy. Import sources are platform-specific.
4867
6009
  *
@@ -4975,16 +6117,22 @@ function add_ref_import_specifier(imports, source, specifier) {
4975
6117
  function create_render_if_statement(node, transform_context) {
4976
6118
  const consequent_body =
4977
6119
  node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
6120
+ if (is_template_if_node(node)) {
6121
+ validate_if_body_control_flow(consequent_body, transform_context);
6122
+ }
4978
6123
  const consequent_has_hooks =
4979
6124
  should_extract_hook_helpers(transform_context) &&
4980
6125
  body_contains_top_level_hook_call(consequent_body, transform_context, true);
4981
6126
 
4982
6127
  let alternate = null;
4983
6128
  if (node.alternate) {
4984
- if (node.alternate.type === 'IfStatement') {
6129
+ if (is_if_control_node(node.alternate)) {
4985
6130
  alternate = create_render_if_statement(node.alternate, transform_context);
4986
6131
  } else {
4987
6132
  const alternate_body = node.alternate.body || [node.alternate];
6133
+ if (is_template_if_node(node)) {
6134
+ validate_if_body_control_flow(alternate_body, transform_context);
6135
+ }
4988
6136
  const alternate_has_hooks =
4989
6137
  should_extract_hook_helpers(transform_context) &&
4990
6138
  body_contains_top_level_hook_call(alternate_body, transform_context, true);
@@ -5021,9 +6169,8 @@ function create_render_if_statement(node, transform_context) {
5021
6169
  * case body needs to be hoisted into its own helper component or can stay
5022
6170
  * inline.
5023
6171
  *
5024
- * `own_body` is everything in the case's `consequent` up to (and including for
5025
- * `return <expr>`, excluding for `break` / bare `return;`) the first
5026
- * terminator. `has_terminator` records whether such a terminator was seen.
6172
+ * `own_body` is the case's isolated consequent. JSX `@switch` cases do not
6173
+ * fall through, so `break` is not part of the template switch model.
5027
6174
  *
5028
6175
  * @param {any[]} consequent
5029
6176
  * @returns {{ own_body: any[], has_terminator: boolean }}
@@ -5032,10 +6179,6 @@ function summarize_switch_case_body(consequent) {
5032
6179
  const own_body = [];
5033
6180
  let has_terminator = false;
5034
6181
  for (const child of consequent) {
5035
- if (child.type === 'BreakStatement') {
5036
- has_terminator = true;
5037
- break;
5038
- }
5039
6182
  if (child.type === 'ReturnStatement' && child.argument == null) {
5040
6183
  has_terminator = true;
5041
6184
  break;
@@ -5068,11 +6211,10 @@ export function clone_switch_helper_invocation(helper) {
5068
6211
 
5069
6212
  /**
5070
6213
  * Plan the switch lift: decide which case bodies to hoist into their own
5071
- * helper components, build them in reverse so each helper can chain into the
5072
- * next, and return everything callers need to construct a target-specific
5073
- * switch shape (a JS `switch` for React/Preact/Vue or `<Switch>/<Match>` for
5074
- * Solid). Centralizes the lift bookkeeping so both consumers see the same
5075
- * hook-detection rules, duplication analysis, and helper-id numbering.
6214
+ * helper components and return everything callers need to construct a
6215
+ * target-specific switch shape (a JS `switch` for React/Preact/Vue or
6216
+ * `<Switch>/<Match>` for Solid). JSX `@switch` cases are isolated and do not
6217
+ * fall through.
5076
6218
  *
5077
6219
  * Returned helpers — when non-null — are already constructed via
5078
6220
  * `create_hook_safe_helper`, which is the same path hook-bearing case bodies
@@ -5086,7 +6228,6 @@ export function clone_switch_helper_invocation(helper) {
5086
6228
  * @returns {{
5087
6229
  * case_info: Array<{ own_body: any[], has_terminator: boolean }>,
5088
6230
  * case_helpers: Array<{ setup_statements: any[], component_element: ESTreeJSX.JSXElement } | null>,
5089
- * find_next_helper_after: (from_index: number) => { component_element: ESTreeJSX.JSXElement } | null,
5090
6231
  * setup_statements: any[],
5091
6232
  * }}
5092
6233
  */
@@ -5096,22 +6237,15 @@ export function plan_switch_lift(switch_node, transform_context) {
5096
6237
  return summarize_switch_case_body(consequent);
5097
6238
  });
5098
6239
 
5099
- // A case body needs to be lifted iff (a) it would render in more than one
5100
- // arm after fall-through expansion, or (b) it contains hooks (which always
5101
- // went through the lift pipeline before this change). Duplication happens
5102
- // exactly when the previous case has no terminator — that's the only way
5103
- // an earlier arm can reach this body via JS fall-through semantics.
6240
+ // A case body needs to be lifted iff it contains hooks. Cases are isolated,
6241
+ // so downstream case bodies are never duplicated into earlier arms.
5104
6242
  const needs_helper = case_info.map(
5105
- (/** @type {{ own_body: any[], has_terminator: boolean }} */ info, /** @type {number} */ k) => {
6243
+ (/** @type {{ own_body: any[], has_terminator: boolean }} */ info) => {
5106
6244
  if (info.own_body.length === 0) return false;
5107
- if (
6245
+ return (
5108
6246
  should_extract_hook_helpers(transform_context) &&
5109
6247
  body_contains_top_level_hook_call(info.own_body, transform_context, true)
5110
- ) {
5111
- return true;
5112
- }
5113
- if (k === 0) return false;
5114
- return !case_info[k - 1].has_terminator;
6248
+ );
5115
6249
  },
5116
6250
  );
5117
6251
 
@@ -5128,37 +6262,12 @@ export function plan_switch_lift(switch_node, transform_context) {
5128
6262
  /** @type {Array<{ setup_statements: any[], component_element: ESTreeJSX.JSXElement } | null>} */
5129
6263
  const case_helpers = new Array(switch_node.cases.length).fill(null);
5130
6264
 
5131
- /**
5132
- * Find the next downstream helper this arm chains into when it has no
5133
- * terminator: scan forward past any empty cases until we hit either a
5134
- * helper-bearing case or a case whose body has a terminator (which stops
5135
- * the chain — JS would have `break`/`return`ed out at that point).
5136
- *
5137
- * @param {number} from_index
5138
- * @returns {{ component_element: ESTreeJSX.JSXElement } | null}
5139
- */
5140
- function find_next_helper_after(from_index) {
5141
- for (let j = from_index + 1; j < switch_node.cases.length; j++) {
5142
- if (case_helpers[j]) return case_helpers[j];
5143
- if (case_info[j].has_terminator) return null;
5144
- }
5145
- return null;
5146
- }
5147
-
5148
6265
  for (let i = switch_node.cases.length - 1; i >= 0; i--) {
5149
6266
  if (!needs_helper[i]) continue;
5150
- const { own_body, has_terminator } = case_info[i];
5151
-
5152
- let helper_body = own_body;
5153
- if (!has_terminator) {
5154
- const next_helper = find_next_helper_after(i);
5155
- if (next_helper) {
5156
- helper_body = [...own_body, clone_switch_helper_invocation(next_helper)];
5157
- }
5158
- }
6267
+ const { own_body } = case_info[i];
5159
6268
 
5160
6269
  case_helpers[i] = create_hook_safe_helper(
5161
- helper_body,
6270
+ own_body,
5162
6271
  undefined,
5163
6272
  switch_node.cases[i],
5164
6273
  transform_context,
@@ -5176,40 +6285,17 @@ export function plan_switch_lift(switch_node, transform_context) {
5176
6285
  return {
5177
6286
  case_info,
5178
6287
  case_helpers,
5179
- find_next_helper_after,
5180
6288
  setup_statements,
5181
6289
  };
5182
6290
  }
5183
6291
 
5184
6292
  /**
5185
- * Switch lift for fall-through deduplication. Reuses the same `create_hook_safe_helper`
5186
- * pipeline as hook-bearing case bodies: every case whose body would otherwise
5187
- * appear in 2+ arms (because the previous case had no `break` / `return`) is
5188
- * hoisted into its own helper component, and each upstream arm references the
5189
- * next helper at the end of its own body to materialize JS fall-through at
5190
- * render time. Cases whose bodies live in exactly one arm stay inline so the
5191
- * common (break-terminated) shape compiles to the same simple switch as before
5192
- * the lift was introduced.
5193
- *
5194
- * The chain pattern:
5195
- * helper_idle = () => <><Online/><Helper_active/></>
5196
- * helper_active = () => <><Away/><Helper_offline/></>
5197
- * helper_offline = () => <Offline/>
5198
- *
5199
- * case "idle": return <Helper_idle/>
5200
- * case "active": return <Helper_active/>
5201
- * case "offline": return <Helper_offline/>
5202
- *
5203
- * Each case body appears exactly once in the generated module — matching how
5204
- * we already handle hook-bearing case bodies — which keeps the bundle from
5205
- * growing quadratically in case count and means editor mappings are 1:1.
5206
- *
5207
6293
  * @param {any} switch_node
5208
6294
  * @param {TransformContext} transform_context
5209
6295
  * @returns {{ setup_statements: any[], switch_statement: any }}
5210
6296
  */
5211
6297
  function build_switch_with_lift(switch_node, transform_context) {
5212
- const { case_info, case_helpers, find_next_helper_after, setup_statements } = plan_switch_lift(
6298
+ const { case_info, case_helpers, setup_statements } = plan_switch_lift(
5213
6299
  switch_node,
5214
6300
  transform_context,
5215
6301
  );
@@ -5229,10 +6315,10 @@ function build_switch_with_lift(switch_node, transform_context) {
5229
6315
  const { own_body, has_terminator } = case_info[i];
5230
6316
 
5231
6317
  if (own_body.length === 0 && !has_terminator) {
5232
- // Alias-pattern empty case (`case 'a': case 'b': ...`) — keep
5233
- // the arm body empty so JS falls through to the next case at
5234
- // runtime, where the helper invocation actually lives.
5235
- return set_loc(b.switch_case(original_case.test, []), original_case);
6318
+ return set_loc(
6319
+ b.switch_case(original_case.test, [create_null_return_statement()]),
6320
+ original_case,
6321
+ );
5236
6322
  }
5237
6323
 
5238
6324
  const case_body = [];
@@ -5250,7 +6336,7 @@ function build_switch_with_lift(switch_node, transform_context) {
5250
6336
  has_terminal = true;
5251
6337
  break;
5252
6338
  }
5253
- if (is_jsx_child(child)) {
6339
+ if (is_render_child_node(child)) {
5254
6340
  render_nodes.push(to_jsx_child(child, transform_context));
5255
6341
  } else if (is_bare_render_expression(child)) {
5256
6342
  render_nodes.push(to_jsx_expression_container(child, child));
@@ -5259,27 +6345,13 @@ function build_switch_with_lift(switch_node, transform_context) {
5259
6345
  }
5260
6346
  }
5261
6347
 
5262
- if (!has_terminal && !has_terminator) {
5263
- const next_helper = find_next_helper_after(i);
5264
- if (next_helper) {
5265
- render_nodes.push(clone_switch_helper_invocation(next_helper));
5266
- }
5267
- }
5268
-
5269
6348
  if (!has_terminal) {
5270
6349
  if (render_nodes.length > 0) {
5271
6350
  case_body.push(create_component_return_statement(render_nodes, original_case));
5272
- } else if (has_terminator) {
5273
- // Empty body with explicit `break;` / bare `return;` — keep
5274
- // a `break` so JS doesn't fall through into the next case
5275
- // (which may now hold the lifted helper invocation).
5276
- case_body.push(b.break);
5277
6351
  } else if (case_body.length > 0) {
5278
- // Statements-only inline case without terminator. We've
5279
- // already inlined the downstream chain via the helper
5280
- // reference above, so emit a `break` to stop the runtime
5281
- // from re-running downstream statements via JS fall-through.
5282
- case_body.push(b.break);
6352
+ case_body.push(create_null_return_statement());
6353
+ } else if (has_terminator) {
6354
+ case_body.push(create_null_return_statement());
5283
6355
  }
5284
6356
  }
5285
6357
 
@@ -5356,12 +6428,12 @@ function transform_element_attributes_dispatch(attrs, transform_context, element
5356
6428
  * @returns {boolean}
5357
6429
  */
5358
6430
  export function is_component_like_element(element) {
5359
- const id = element?.id;
5360
- if (!id) return false;
5361
- if (id.type === 'Identifier') return /^[A-Z]/.test(id.name);
5362
- if (id.type === 'JSXIdentifier') return /^[A-Z]/.test(id.name);
5363
- if (id.type === 'MemberExpression') return true;
5364
- if (id.type === 'JSXMemberExpression') return true;
6431
+ const name = element?.openingElement?.name;
6432
+ if (!name) return false;
6433
+ if (name.type === 'Identifier') return /^[A-Z]/.test(name.name);
6434
+ if (name.type === 'JSXIdentifier') return /^[A-Z]/.test(name.name);
6435
+ if (name.type === 'MemberExpression') return true;
6436
+ if (name.type === 'JSXMemberExpression') return true;
5365
6437
  return false;
5366
6438
  }
5367
6439
 
@@ -5514,10 +6586,7 @@ function wrap_jsx_setup_declarations(expression, in_jsx_child) {
5514
6586
  /**
5515
6587
  * Reject elements with more than one TSX-style `ref={...}` attribute.
5516
6588
  * This validator runs over the raw, pre-lowering attribute list so each
5517
- * shape is still distinguishable by `type`. Ripple `Element` attributes have type `Attribute` with an
5518
- * `Identifier` name (the parser normalizes `JSXAttribute`/`JSXIdentifier`
5519
- * for native elements); inside `<tsx:react>` compat blocks they retain
5520
- * the original `JSXAttribute`/`JSXIdentifier` shape, so we accept both.
6589
+ * shape is still distinguishable by `type`.
5521
6590
  *
5522
6591
  * @param {any[]} raw_attrs
5523
6592
  * @param {TransformContext} [transform_context]
@@ -5528,14 +6597,10 @@ export function validate_at_most_one_ref_attribute(raw_attrs, transform_context)
5528
6597
  for (const attr of raw_attrs) {
5529
6598
  if (!attr) continue;
5530
6599
  const is_ref_attr =
5531
- (attr.type === 'Attribute' &&
5532
- attr.name &&
5533
- attr.name.type === 'Identifier' &&
5534
- attr.name.name === 'ref') ||
5535
- (attr.type === 'JSXAttribute' &&
5536
- attr.name &&
5537
- attr.name.type === 'JSXIdentifier' &&
5538
- attr.name.name === 'ref');
6600
+ attr.type === 'JSXAttribute' &&
6601
+ attr.name &&
6602
+ attr.name.type === 'JSXIdentifier' &&
6603
+ attr.name.name === 'ref';
5539
6604
  if (!is_ref_attr) continue;
5540
6605
  refs.push(attr.name);
5541
6606
  }
@@ -5747,8 +6812,6 @@ function infer_ref_namespace(tag_name) {
5747
6812
  * @returns {string | null}
5748
6813
  */
5749
6814
  function get_element_ref_tag_name(element) {
5750
- const id = element?.id;
5751
- if (id?.type === 'Identifier') return id.name;
5752
6815
  const name = element?.name;
5753
6816
  if (name?.type === 'JSXIdentifier') return name.name;
5754
6817
  if (element?.openingElement?.name?.type === 'JSXIdentifier') {
@@ -5784,15 +6847,6 @@ export function to_jsx_attribute(attr, transform_context) {
5784
6847
  if (attr.type === 'JSXSpreadAttribute') {
5785
6848
  return attr;
5786
6849
  }
5787
- if (attr.type === 'SpreadAttribute') {
5788
- return set_loc(
5789
- /** @type {any} */ ({
5790
- type: 'JSXSpreadAttribute',
5791
- argument: attr.argument,
5792
- }),
5793
- attr,
5794
- );
5795
- }
5796
6850
  // Keep this legacy hook for targets that need React-style DOM attrs. The
5797
6851
  // current first-party targets preserve authored `class`.
5798
6852
  let attr_name = attr.name;
@@ -5841,53 +6895,6 @@ function value_has_unmappable_jsx_loc(value) {
5841
6895
  );
5842
6896
  }
5843
6897
 
5844
- /**
5845
- * @param {any} node
5846
- * @param {TransformContext} transform_context
5847
- * @returns {ESTreeJSX.JSXExpressionContainer}
5848
- */
5849
- function dynamic_element_to_jsx_child(node, transform_context) {
5850
- const dynamic_id = set_loc(create_generated_identifier('DynamicElement'), node.id);
5851
- const alias_declaration = set_loc(b.const(dynamic_id, clone_expression_node(node.id)), node);
5852
- const jsx_element = create_dynamic_jsx_element(dynamic_id, node, transform_context);
5853
-
5854
- return to_jsx_expression_container(
5855
- b.call(
5856
- b.arrow(
5857
- [],
5858
- b.block([
5859
- alias_declaration,
5860
- b.return(b.conditional(clone_identifier(dynamic_id), jsx_element, create_null_literal())),
5861
- ]),
5862
- ),
5863
- ),
5864
- node,
5865
- );
5866
- }
5867
-
5868
- /**
5869
- * @param {AST.Identifier} dynamic_id
5870
- * @param {any} node
5871
- * @param {TransformContext} transform_context
5872
- * @returns {ESTreeJSX.JSXElement}
5873
- */
5874
- function create_dynamic_jsx_element(dynamic_id, node, transform_context) {
5875
- const attributes = transform_element_attributes_dispatch(
5876
- node.attributes || [],
5877
- transform_context,
5878
- node,
5879
- );
5880
- const selfClosing = !!node.selfClosing;
5881
- const children = create_element_children(node.children || [], transform_context);
5882
- const name = identifier_to_jsx_name(clone_identifier(dynamic_id));
5883
-
5884
- return b.jsx_element_fresh(
5885
- b.jsx_opening_element(name, attributes, selfClosing),
5886
- selfClosing ? null : b.jsx_closing_element(clone_jsx_name(name)),
5887
- children,
5888
- );
5889
- }
5890
-
5891
6898
  /**
5892
6899
  * @param {any[]} render_nodes
5893
6900
  * @returns {any}
@@ -5899,6 +6906,10 @@ function build_return_expression(render_nodes) {
5899
6906
  if (only.type === 'JSXExpressionContainer') {
5900
6907
  return only.expression;
5901
6908
  }
6909
+ if (only.type === 'JSXText') {
6910
+ const value = (only.value ?? '').trim();
6911
+ return b.literal(value, JSON.stringify(value), only);
6912
+ }
5902
6913
  return only;
5903
6914
  }
5904
6915
  const first = render_nodes[0];
@@ -5917,25 +6928,3 @@ function build_return_expression(render_nodes) {
5917
6928
  : undefined,
5918
6929
  );
5919
6930
  }
5920
-
5921
- /**
5922
- * @param {any} node
5923
- * @param {TransformContext} transform_context
5924
- * @param {boolean} [in_jsx_child]
5925
- * @returns {any}
5926
- */
5927
- function tsx_compat_node_to_jsx_expression(node, transform_context, in_jsx_child = false) {
5928
- const platform = transform_context.platform;
5929
- if (!platform.jsx.acceptedTsxKinds.includes(node.kind)) {
5930
- const accepted = platform.jsx.acceptedTsxKinds.map((k) => `<tsx:${k}>`).join(', ');
5931
- error(
5932
- `${platform.name} TSRX does not support <tsx:${node.kind}> blocks. Use one of: ${accepted}.`,
5933
- transform_context.filename,
5934
- node,
5935
- transform_context.errors,
5936
- transform_context.comments,
5937
- );
5938
- }
5939
-
5940
- return tsx_node_to_jsx_expression(node, in_jsx_child);
5941
- }