@tsrx/core 0.1.24 → 0.1.26

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,7 +7,6 @@ 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';
11
10
  import {
12
11
  in_jsx_child_context,
13
12
  set_node_path_metadata,
@@ -15,6 +14,7 @@ import {
15
14
  tsx_with_ts_locations,
16
15
  } from './helpers.js';
17
16
  import {
17
+ add_extra_source_mappings_from_matching_expression,
18
18
  clone_expression_node,
19
19
  clone_identifier,
20
20
  clone_jsx_name,
@@ -49,7 +49,6 @@ import {
49
49
  create_style_ref_setup_statements,
50
50
  get_style_element_stylesheet,
51
51
  } from '../style-ref.js';
52
- import { is_function_or_component_node } from '../../utils/ast.js';
53
52
  import {
54
53
  is_interleaved_body as is_interleaved_body_core,
55
54
  is_capturable_jsx_child,
@@ -57,10 +56,6 @@ import {
57
56
  } from '../jsx-interleave.js';
58
57
  import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
59
58
 
60
- const HOOK_OUTER_ASSIGNMENT_ERROR =
61
- 'Hook calls inside conditional or repeated TSRX scopes must keep their results local to the generated hook component.';
62
- const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
63
- 'Hook callbacks inside conditional or repeated TSRX scopes must not mutate bindings declared outside the generated hook component.';
64
59
  const TEMPLATE_FRAGMENT_ERROR =
65
60
  'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only in expression position.';
66
61
  const TSRX_FOR_RETURN_ERROR =
@@ -74,6 +69,8 @@ const TSRX_IF_RETURN_ERROR =
74
69
  const TSRX_IF_BREAK_ERROR = 'Break statements are not allowed inside TSRX template @if blocks.';
75
70
  const TSRX_IF_CONTINUE_ERROR =
76
71
  'Continue statements are not allowed inside TSRX template @if blocks. Filter before rendering or use conditional output instead.';
72
+ const DYNAMIC_IMPORT_LOCAL = 'TsrxDynamic';
73
+ const DYNAMIC_FACTORY_LOCAL = '_tsrx_dynamic';
77
74
 
78
75
  /**
79
76
  * @param {AST.Node} node
@@ -89,44 +86,6 @@ function report_jsx_fragment_in_tsrx_error(node, transform_context) {
89
86
  );
90
87
  }
91
88
 
92
- /**
93
- * @param {AST.Node} node
94
- * @param {string[]} names
95
- * @param {string} hook_name
96
- * @param {TransformContext} transform_context
97
- * @returns {void}
98
- */
99
- function report_hook_outer_assignment_error(node, names, hook_name, transform_context) {
100
- const target =
101
- names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
102
- error(
103
- `${HOOK_OUTER_ASSIGNMENT_ERROR} The ${hook_name} result is assigned to ${target}, which is declared outside that generated component. Declare the hook result inside the TSRX branch, or move the hook into an explicit child component and pass values with props.`,
104
- transform_context.filename,
105
- node,
106
- transform_context.errors,
107
- transform_context.comments,
108
- );
109
- }
110
-
111
- /**
112
- * @param {AST.Node} node
113
- * @param {string[]} names
114
- * @param {string} hook_name
115
- * @param {TransformContext} transform_context
116
- * @returns {void}
117
- */
118
- function report_hook_callback_outer_mutation_error(node, names, hook_name, transform_context) {
119
- const target =
120
- names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
121
- error(
122
- `${HOOK_CALLBACK_OUTER_MUTATION_ERROR} The ${hook_name} callback mutates ${target}. Read outer values through props or dependencies, and move mutable state into an explicit child component when it needs to change over time.`,
123
- transform_context.filename,
124
- node,
125
- transform_context.errors,
126
- transform_context.comments,
127
- );
128
- }
129
-
130
89
  /**
131
90
  * Local alias for the shared `JsxTransformContext`. Kept as a typedef so the
132
91
  * rest of this file's `@param {TransformContext}` annotations don't all have
@@ -259,15 +218,32 @@ function wrap_in_native_tsrx_fragment(node) {
259
218
  * (`const x = @switch (…) { … }`, `x = @switch (…) { … }`), or a call/`new`
260
219
  * argument (`render(@if (…) { … })`) — in a native TSRX fragment.
261
220
  * @param {any} node
221
+ * @param {TransformContext | null} lower_dynamic_context
262
222
  * @param {Set<any>} [seen]
263
223
  * @returns {void}
264
224
  */
265
- function wrap_control_flow_expression_values(node, seen = new Set()) {
225
+ function wrap_control_flow_expression_values(node, lower_dynamic_context, seen = new Set()) {
266
226
  if (!node || typeof node !== 'object' || seen.has(node)) return;
267
227
  seen.add(node);
268
228
 
229
+ // Dynamic tags on factory platforms must lower before control-flow
230
+ // conversion and static hoisting run. Production output needs the scoped
231
+ // `const TsrxDynamic_N = ...` binding declared in the scope that owns the
232
+ // tag expression (e.g. a `@for` loop variable); type-only output needs
233
+ // `<TsrxDynamic is={expr}>` in place before a reference-free tree (e.g.
234
+ // `<{'div'}>`) is hoisted to a module-level static const while still
235
+ // carrying the raw dynamic tag. Alias lowerings return a replacement
236
+ // fragment, which is swapped into the child's position here.
237
+ const lower_child = (/** @type {any} */ child) => {
238
+ if (!lower_dynamic_context || child?.type !== 'JSXElement') return child;
239
+ return lower_dynamic_jsx_element(child, lower_dynamic_context) ?? child;
240
+ };
241
+
269
242
  if (Array.isArray(node)) {
270
- for (const item of node) wrap_control_flow_expression_values(item, seen);
243
+ for (let i = 0; i < node.length; i++) {
244
+ node[i] = lower_child(node[i]);
245
+ wrap_control_flow_expression_values(node[i], lower_dynamic_context, seen);
246
+ }
271
247
  return;
272
248
  }
273
249
 
@@ -299,7 +275,8 @@ function wrap_control_flow_expression_values(node, seen = new Set()) {
299
275
 
300
276
  for (const key of Object.keys(node)) {
301
277
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
302
- wrap_control_flow_expression_values(node[key], seen);
278
+ node[key] = lower_child(node[key]);
279
+ wrap_control_flow_expression_values(node[key], lower_dynamic_context, seen);
303
280
  }
304
281
  }
305
282
 
@@ -343,6 +320,8 @@ export function createJsxTransform(platform) {
343
320
  needs_normalize_spread_props: false,
344
321
  needs_normalize_spread_props_for_ref_attr: false,
345
322
  needs_fragment: false,
323
+ needs_dynamic_element: false,
324
+ needs_dynamic_factory: false,
346
325
  needs_for_of_iterable: false,
347
326
  needs_iteration_value_type: false,
348
327
  stylesheets,
@@ -353,7 +332,6 @@ export function createJsxTransform(platform) {
353
332
  hook_helpers_enabled: false,
354
333
  available_bindings: new Map(),
355
334
  lazy_next_id: 0,
356
- runtime_dynamic_scopes: null,
357
335
  filename: filename ?? null,
358
336
  source,
359
337
  collect,
@@ -366,10 +344,9 @@ export function createJsxTransform(platform) {
366
344
  };
367
345
 
368
346
  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(
347
+ wrap_control_flow_expression_values(
371
348
  /** @type {any} */ (ast),
372
- transform_context,
349
+ platform.imports.dynamicFactory ? transform_context : null,
373
350
  );
374
351
 
375
352
  if (!transform_context.typeOnly) {
@@ -406,7 +383,15 @@ export function createJsxTransform(platform) {
406
383
  return /** @type {any} */ (wrap_jsx_setup_declarations(expression, in_jsx_child));
407
384
  },
408
385
 
409
- JSXElement(node, { next, path, state }) {
386
+ JSXElement(node, { next, path, state, visit }) {
387
+ const lowered = lower_dynamic_jsx_element(node, state);
388
+ if (lowered) {
389
+ // Alias lowerings replace the element with a fragment; factory
390
+ // platforms normally lower in the pre-walk pass, so this only
391
+ // covers elements introduced after it.
392
+ return /** @type {any} */ (visit(lowered, state));
393
+ }
394
+
410
395
  if (!node.metadata?.native_tsrx) {
411
396
  return next() ?? node;
412
397
  }
@@ -505,6 +490,7 @@ export function createJsxTransform(platform) {
505
490
  transformed_program.body.unshift(...type_only_style_anchors);
506
491
  }
507
492
  const expanded = expand_component_helpers(transformed_program);
493
+ inject_dynamic_import(expanded, transform_context);
508
494
  if (platform.hooks?.injectImports) {
509
495
  platform.hooks.injectImports(expanded, transform_context, suspense_source);
510
496
  } else {
@@ -540,6 +526,179 @@ export function createJsxTransform(platform) {
540
526
  return transform;
541
527
  }
542
528
 
529
+ /**
530
+ * Lower a single parser-native dynamic tag (`<{expr}>`) into the target runtime
531
+ * `<Dynamic is={expr}>` helper shape while the existing JSXElement walker is
532
+ * already visiting it. The dynamic name container travels by reference through
533
+ * element rebuilds, so checking it covers rebuilt elements too; once lowered,
534
+ * the name is a plain `JSXIdentifier` and the element is skipped on re-visits.
535
+ *
536
+ * The parsed element is never mutated: every lowering builds a fresh
537
+ * replacement node (an element, or a fragment for the alias lowering) that
538
+ * the caller must put in the original element's position.
539
+ *
540
+ * @param {any} node
541
+ * @param {TransformContext} transform_context
542
+ * @returns {ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment | undefined}
543
+ */
544
+ function lower_dynamic_jsx_element(node, transform_context) {
545
+ const dynamic_name = node.openingElement?.name;
546
+ if (dynamic_name?.type !== 'JSXExpressionContainer' || dynamic_name.isDynamic !== true) return;
547
+
548
+ // Type-only output always uses the `<TsrxDynamic is={expr}>` component
549
+ // shape; production output prefers the platform's runtime factory when one
550
+ // is configured (e.g. Solid's `dynamic`).
551
+ const factory = transform_context.typeOnly
552
+ ? undefined
553
+ : transform_context.platform.imports.dynamicFactory;
554
+ if (!factory && !transform_context.platform.imports.dynamic) return;
555
+
556
+ const dynamic_expression = dynamic_name.expression;
557
+ if (!dynamic_expression) return;
558
+ const generated_expression = clone_expression_node(dynamic_expression);
559
+ if (node.closingElement?.name?.expression) {
560
+ // One generated expression stands in for both tags; record the closing
561
+ // tag's positions so editor features keep working on `</{expr}>`.
562
+ add_extra_source_mappings_from_matching_expression(
563
+ generated_expression,
564
+ clone_expression_node(node.closingElement.name.expression),
565
+ );
566
+ }
567
+
568
+ /**
569
+ * Rebuild the element as an ordinary component reference named `name_id`,
570
+ * carrying the original attributes (after any `extra_attributes`) and
571
+ * children over by reference.
572
+ *
573
+ * @param {ESTreeJSX.JSXIdentifier} name_id
574
+ * @param {ESTreeJSX.JSXAttribute[]} [extra_attributes]
575
+ * @returns {ESTreeJSX.JSXElement}
576
+ */
577
+ const rebuild_element = (name_id, extra_attributes = []) => {
578
+ const element = b.jsx_element_fresh(
579
+ b.jsx_opening_element(
580
+ name_id,
581
+ [...extra_attributes, ...(node.openingElement.attributes || [])],
582
+ node.openingElement.selfClosing,
583
+ node.openingElement.typeArguments,
584
+ node.openingElement,
585
+ ),
586
+ node.closingElement
587
+ ? b.jsx_closing_element(b.jsx_id(name_id.name), node.closingElement)
588
+ : null,
589
+ node.children,
590
+ node,
591
+ );
592
+ element.metadata = { ...(node.metadata || {}), path: [] };
593
+ return element;
594
+ };
595
+
596
+ /**
597
+ * Scoped-CSS passes treat lowered dynamic tags like the imported `Dynamic`
598
+ * helper: type selectors survive pruning and the scope hash lands on the
599
+ * element's class.
600
+ *
601
+ * @param {ESTreeJSX.JSXElement} element
602
+ * @returns {ESTreeJSX.JSXElement}
603
+ */
604
+ const mark_dynamic_element = (element) => {
605
+ element.metadata.dynamicElement = true;
606
+ return element;
607
+ };
608
+
609
+ if (factory) {
610
+ // Bind the tag expression to a scoped component const and reference it
611
+ // like an ordinary component.
612
+ transform_context.local_statement_component_index += 1;
613
+ const local = `${DYNAMIC_IMPORT_LOCAL}_${transform_context.local_statement_component_index}`;
614
+ const local_id = b.jsx_id(local);
615
+ transform_context.needs_dynamic_factory = true;
616
+
617
+ if (factory.renderBlock) {
618
+ // Import-free alias inside a reactive render block (Vue): the const
619
+ // is a plain snapshot and Vapor never re-runs setup, so the whole
620
+ // element is rebuilt in a native fragment whose expression-container
621
+ // child holds
622
+ // `(() => { const TsrxDynamic_1 = expr; return <TsrxDynamic_1 ...>; })()`
623
+ // — vue-jsx-vapor compiles expression children into `createNodes(...)`
624
+ // render blocks, which re-run the IIFE when the tag expression
625
+ // changes. The container is marked so downstream lone-child
626
+ // collapsing keeps it in expression-child position instead of
627
+ // unwrapping to a bare call.
628
+ const element = mark_dynamic_element(rebuild_element(local_id));
629
+ const wrapper = b.arrow(
630
+ [],
631
+ b.block([b.const(b.id(local), generated_expression), b.return(element)], node),
632
+ );
633
+ // Lets scoped-CSS collection descend into this generated closure;
634
+ // user function boundaries are otherwise skipped.
635
+ wrapper.metadata = /** @type {any} */ ({
636
+ ...(wrapper.metadata || { path: [] }),
637
+ tsrx_dynamic_wrapper: true,
638
+ });
639
+ const container = to_jsx_expression_container(b.call(wrapper), element);
640
+ container.metadata = /** @type {any} */ ({
641
+ ...(container.metadata || { path: [] }),
642
+ tsrx_reactive_block: true,
643
+ });
644
+
645
+ return set_loc(wrap_in_native_tsrx_fragment(container), node);
646
+ }
647
+
648
+ // Statement placement: `const TsrxDynamic_1 = ...;` next to the
649
+ // template. With a factory, the thunk keeps the tag reactive (Solid:
650
+ // `_tsrx_dynamic(() => expr)`); without one, the plain alias is
651
+ // re-evaluated by the host's render cycle (React/Preact re-run the
652
+ // component body). The declaration rides on the name node's metadata:
653
+ // element rebuilds clone names with a shared metadata reference, so
654
+ // setup extraction still finds it afterwards.
655
+ add_jsx_setup_declaration(
656
+ local_id,
657
+ b.const(
658
+ b.id(local),
659
+ factory.name
660
+ ? b.call(b.id(DYNAMIC_FACTORY_LOCAL), b.arrow([], generated_expression))
661
+ : generated_expression,
662
+ ),
663
+ );
664
+ return mark_dynamic_element(rebuild_element(local_id));
665
+ }
666
+
667
+ transform_context.needs_dynamic_element = true;
668
+ return mark_dynamic_element(
669
+ rebuild_element(b.jsx_id(DYNAMIC_IMPORT_LOCAL), [
670
+ b.jsx_attribute(
671
+ b.jsx_id('is'),
672
+ b.jsx_expression_container(generated_expression, dynamic_name),
673
+ false,
674
+ dynamic_name,
675
+ ),
676
+ ]),
677
+ );
678
+ }
679
+
680
+ /**
681
+ * @param {AST.Program} program
682
+ * @param {TransformContext} transform_context
683
+ * @returns {void}
684
+ */
685
+ function inject_dynamic_import(program, transform_context) {
686
+ const factory = transform_context.platform.imports.dynamicFactory;
687
+ if (transform_context.needs_dynamic_factory && factory?.name && factory.source) {
688
+ program.body.unshift(
689
+ b.import_declaration(
690
+ [b.import_specifier(factory.name, DYNAMIC_FACTORY_LOCAL)],
691
+ factory.source,
692
+ ),
693
+ );
694
+ }
695
+ const source = transform_context.platform.imports.dynamic;
696
+ if (!transform_context.needs_dynamic_element || !source) return;
697
+ program.body.unshift(
698
+ b.import_declaration([b.import_specifier('Dynamic', DYNAMIC_IMPORT_LOCAL)], source),
699
+ );
700
+ }
701
+
543
702
  /**
544
703
  * Attach selector-location metadata used by editor definitions/hover before
545
704
  * the shared scoping pass mutates class attributes with the component hash.
@@ -606,15 +765,18 @@ function collect_css_prunable_elements(value, elements = [], transform_context =
606
765
  }
607
766
 
608
767
  if (
609
- value.type === 'FunctionDeclaration' ||
610
- value.type === 'FunctionExpression' ||
611
- value.type === 'ArrowFunctionExpression'
768
+ (value.type === 'FunctionDeclaration' ||
769
+ value.type === 'FunctionExpression' ||
770
+ value.type === 'ArrowFunctionExpression') &&
771
+ // Generated dynamic-tag wrappers are render-block closures, not user
772
+ // component boundaries — the element inside still belongs to this
773
+ // component's scoped CSS.
774
+ value.metadata?.tsrx_dynamic_wrapper !== true
612
775
  ) {
613
776
  return elements;
614
777
  }
615
778
 
616
779
  if (value.type === 'JSXElement' && value.metadata?.native_tsrx) {
617
- mark_runtime_dynamic_element(value, transform_context);
618
780
  if (!is_style_element(value)) {
619
781
  elements.push(value);
620
782
  }
@@ -630,241 +792,6 @@ function collect_css_prunable_elements(value, elements = [], transform_context =
630
792
  return elements;
631
793
  }
632
794
 
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
-
868
795
  /**
869
796
  * @param {any[]} body_nodes
870
797
  * @param {TransformContext} transform_context
@@ -1038,424 +965,54 @@ function is_interleaved_body(body_nodes) {
1038
965
  }
1039
966
 
1040
967
  /**
1041
- * @param {any} node
968
+ * @param {any[]} body_nodes
1042
969
  * @param {TransformContext} transform_context
970
+ * @param {boolean} include_platform_setup
1043
971
  * @returns {boolean}
1044
972
  */
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))
973
+ function body_contains_top_level_hook_call(
974
+ body_nodes,
975
+ transform_context,
976
+ include_platform_setup = false,
977
+ ) {
978
+ return body_nodes.some((node) =>
979
+ statement_contains_top_level_hook_call(node, transform_context, include_platform_setup),
1052
980
  );
1053
981
  }
1054
982
 
1055
983
  /**
1056
984
  * @param {any} node
1057
985
  * @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
986
+ * @param {boolean} include_platform_setup
1108
987
  * @returns {boolean}
1109
988
  */
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
- );
989
+ function statement_contains_top_level_hook_call(node, transform_context, include_platform_setup) {
990
+ return node_contains_top_level_hook_call(node, false, transform_context, include_platform_setup);
1114
991
  }
1115
992
 
1116
993
  /**
1117
994
  * @param {any} node
995
+ * @param {boolean} inside_nested_function
1118
996
  * @param {TransformContext} transform_context
997
+ * @param {boolean} include_platform_setup
1119
998
  * @returns {boolean}
1120
999
  */
1121
- function statement_contains_component_body_branch_hook_return(node, transform_context) {
1000
+ function node_contains_top_level_hook_call(
1001
+ node,
1002
+ inside_nested_function,
1003
+ transform_context,
1004
+ include_platform_setup,
1005
+ ) {
1122
1006
  if (!node || typeof node !== 'object') {
1123
1007
  return false;
1124
1008
  }
1125
1009
 
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;
1259
- }
1260
- const body = branch.type === 'BlockStatement' ? branch.body || [] : [branch];
1261
- return branch_needs_component_body_hook_helper_body(body, transform_context);
1262
- }
1263
-
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
- );
1274
- }
1275
-
1276
- /**
1277
- * @param {any[]} body
1278
- * @returns {boolean}
1279
- */
1280
- function body_has_top_level_component_body_return(body) {
1281
- return body.some((node) => node?.type === 'ReturnStatement');
1282
- }
1283
-
1284
- /**
1285
- * @param {any[]} body
1286
- * @param {TransformContext} transform_context
1287
- * @param {boolean} include_platform_setup
1288
- * @returns {boolean}
1289
- */
1290
- function body_contains_direct_top_level_hook_call(body, transform_context, include_platform_setup) {
1291
- return body.some((node) =>
1292
- statement_contains_direct_top_level_hook_call(node, transform_context, include_platform_setup),
1293
- );
1294
- }
1295
-
1296
- /**
1297
- * @param {any} node
1298
- * @param {TransformContext} transform_context
1299
- * @param {boolean} include_platform_setup
1300
- * @returns {boolean}
1301
- */
1302
- function statement_contains_direct_top_level_hook_call(
1303
- node,
1304
- transform_context,
1305
- include_platform_setup,
1306
- ) {
1307
- if (!node || typeof node !== 'object') {
1308
- return false;
1309
- }
1310
-
1311
- if (is_function_or_class_boundary(node)) {
1312
- return false;
1313
- }
1314
-
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
- }
1323
-
1324
- return statement_contains_top_level_hook_call(node, transform_context, include_platform_setup);
1325
- }
1326
-
1327
- /**
1328
- * @param {any[]} body_nodes
1329
- * @param {TransformContext} transform_context
1330
- * @returns {number}
1331
- */
1332
- function find_hook_split_index(body_nodes, transform_context) {
1333
- for (let i = 0; i < body_nodes.length; i += 1) {
1334
- if (!is_component_body_conditional_return_statement(body_nodes[i])) {
1335
- continue;
1336
- }
1337
-
1338
- if (body_contains_top_level_hook_call(body_nodes.slice(i + 1), transform_context, true)) {
1339
- return i;
1340
- }
1341
- }
1342
-
1343
- return -1;
1344
- }
1345
-
1346
- /**
1347
- * @param {any} node
1348
- * @returns {boolean}
1349
- */
1350
- function is_component_body_conditional_return_statement(node) {
1351
- if (!is_if_control_node(node)) {
1352
- return false;
1353
- }
1354
-
1355
- return (
1356
- statement_contains_component_body_return(node.consequent) ||
1357
- statement_contains_component_body_return(node.alternate)
1358
- );
1359
- }
1360
-
1361
- /**
1362
- * @param {any} node
1363
- * @returns {boolean}
1364
- */
1365
- function statement_contains_component_body_return(node) {
1366
- if (!node || typeof node !== 'object') {
1367
- return false;
1368
- }
1369
-
1370
- if (node.type === 'ReturnStatement') {
1371
- return true;
1372
- }
1373
-
1374
- if (is_function_or_class_boundary(node)) {
1375
- return false;
1376
- }
1377
-
1378
- if (Array.isArray(node)) {
1379
- return node.some(statement_contains_component_body_return);
1380
- }
1381
-
1382
- if (node.type === 'BlockStatement') {
1383
- return (node.body || []).some(statement_contains_component_body_return);
1384
- }
1385
-
1386
- if (is_if_control_node(node)) {
1387
- return (
1388
- statement_contains_component_body_return(node.consequent) ||
1389
- statement_contains_component_body_return(node.alternate)
1390
- );
1391
- }
1392
-
1393
- if (is_switch_control_node(node)) {
1394
- return (node.cases || []).some((/** @type {any} */ switch_case) =>
1395
- statement_contains_component_body_return(switch_case.consequent || []),
1396
- );
1397
- }
1398
-
1399
- if (is_try_control_node(node)) {
1400
- return (
1401
- statement_contains_component_body_return(node.block) ||
1402
- statement_contains_component_body_return(node.handler?.body) ||
1403
- statement_contains_component_body_return(node.finalizer)
1404
- );
1405
- }
1406
-
1407
- return false;
1408
- }
1409
-
1410
- /**
1411
- * @param {any[]} body_nodes
1412
- * @param {TransformContext} transform_context
1413
- * @param {boolean} include_platform_setup
1414
- * @returns {boolean}
1415
- */
1416
- function body_contains_top_level_hook_call(
1417
- body_nodes,
1418
- transform_context,
1419
- include_platform_setup = false,
1420
- ) {
1421
- return body_nodes.some((node) =>
1422
- statement_contains_top_level_hook_call(node, transform_context, include_platform_setup),
1423
- );
1424
- }
1425
-
1426
- /**
1427
- * @param {any} node
1428
- * @param {TransformContext} transform_context
1429
- * @param {boolean} include_platform_setup
1430
- * @returns {boolean}
1431
- */
1432
- function statement_contains_top_level_hook_call(node, transform_context, include_platform_setup) {
1433
- return node_contains_top_level_hook_call(node, false, transform_context, include_platform_setup);
1434
- }
1435
-
1436
- /**
1437
- * @param {any} node
1438
- * @param {boolean} inside_nested_function
1439
- * @param {TransformContext} transform_context
1440
- * @param {boolean} include_platform_setup
1441
- * @returns {boolean}
1442
- */
1443
- function node_contains_top_level_hook_call(
1444
- node,
1445
- inside_nested_function,
1446
- transform_context,
1447
- include_platform_setup,
1448
- ) {
1449
- if (!node || typeof node !== 'object') {
1450
- return false;
1451
- }
1452
-
1453
- if (
1454
- inside_nested_function &&
1455
- (node.type === 'FunctionDeclaration' ||
1456
- node.type === 'FunctionExpression' ||
1457
- node.type === 'ArrowFunctionExpression')
1458
- ) {
1010
+ if (
1011
+ inside_nested_function &&
1012
+ (node.type === 'FunctionDeclaration' ||
1013
+ node.type === 'FunctionExpression' ||
1014
+ node.type === 'ArrowFunctionExpression')
1015
+ ) {
1459
1016
  return false;
1460
1017
  }
1461
1018
 
@@ -1651,7 +1208,7 @@ function create_generated_helper_metadata(helper_state) {
1651
1208
  * @returns {any}
1652
1209
  */
1653
1210
  function strip_function_transform_metadata(metadata) {
1654
- const { native_tsrx, hook_split, ...next_metadata } = metadata || {};
1211
+ const { native_tsrx, ...next_metadata } = metadata || {};
1655
1212
  return next_metadata;
1656
1213
  }
1657
1214
 
@@ -1661,18 +1218,10 @@ function strip_function_transform_metadata(metadata) {
1661
1218
  * @returns {any}
1662
1219
  */
1663
1220
  function transform_block_statement(node, { next, visit, state, path }) {
1664
- if (node.metadata?.hook_split_block || node.metadata?.native_return_block) {
1221
+ if (node.metadata?.native_return_block) {
1665
1222
  return next() ?? node;
1666
1223
  }
1667
1224
 
1668
- const parent = /** @type {any} */ (path.at(-1));
1669
- if (parent?.metadata?.hook_split && parent.body === node) {
1670
- const block = create_hook_split_block(parent, state);
1671
- if (block) {
1672
- return visit(block, state);
1673
- }
1674
- }
1675
-
1676
1225
  if (get_active_native_tsrx_function(path)?.metadata?.native_tsrx_body) {
1677
1226
  const block = create_native_tsrx_statement_list_block(node, state);
1678
1227
  if (block) {
@@ -1827,7 +1376,6 @@ function transform_native_tsrx_function(node, { next, state }, { nativeBody = fa
1827
1376
  ...(node.metadata || {}),
1828
1377
  native_tsrx: true,
1829
1378
  ...(nativeBody ? { native_tsrx_body: true } : {}),
1830
- ...(nativeBody && needs_hook_split(node, state) ? { hook_split: true } : {}),
1831
1379
  };
1832
1380
  state.available_bindings = merge_binding_maps(
1833
1381
  saved_bindings,
@@ -1979,6 +1527,10 @@ function find_native_await_in_statement(statement) {
1979
1527
  * @returns {any}
1980
1528
  */
1981
1529
  function transform_function_with_hook_helpers(node, { next, state }) {
1530
+ if (!state.platform.hooks?.moduleScopedHookComponents) {
1531
+ return next() ?? node;
1532
+ }
1533
+
1982
1534
  const has_hook_bearing_tsrx = function_contains_hook_bearing_tsrx(node, state);
1983
1535
  if (state.helper_state || !is_uppercase_function_like(node) || !has_hook_bearing_tsrx) {
1984
1536
  return next() ?? node;
@@ -2844,7 +2396,10 @@ function should_use_module_scoped_hook_components(transform_context) {
2844
2396
  * @returns {boolean}
2845
2397
  */
2846
2398
  function should_extract_hook_helpers(transform_context) {
2847
- return !!transform_context.hook_helpers_enabled;
2399
+ return !!(
2400
+ transform_context.hook_helpers_enabled &&
2401
+ transform_context.platform.hooks?.moduleScopedHookComponents
2402
+ );
2848
2403
  }
2849
2404
 
2850
2405
  /**
@@ -3406,11 +2961,6 @@ function build_hoisted_for_of_with_hooks(node, transform_context) {
3406
2961
  for (const param of loop_params) {
3407
2962
  collect_pattern_bindings(param, transform_context.available_bindings);
3408
2963
  }
3409
- validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
3410
- original_loop_body,
3411
- transform_context,
3412
- loop_scoped_names,
3413
- );
3414
2964
 
3415
2965
  const all_helper_bindings = get_referenced_helper_bindings(
3416
2966
  original_loop_body,
@@ -3678,676 +3228,82 @@ function to_jsx_element(
3678
3228
  ? opening_element_node
3679
3229
  : set_loc(opening_element_node, node.openingElement || node);
3680
3230
 
3681
- const closingElement = selfClosing
3682
- ? null
3683
- : set_loc(
3684
- b.jsx_closing_element(
3685
- clone_jsx_name(name, node.closingElement?.name || node.closingElement || node),
3686
- ),
3687
- node.closingElement || node,
3688
- );
3689
-
3690
- const element = set_loc(b.jsx_element_fresh(openingElement, closingElement, children), node);
3691
- if (transform_context.typeOnly && is_style_element(node)) {
3692
- disable_style_anchor_verification(element);
3693
- }
3694
- return element;
3695
- }
3696
-
3697
- /**
3698
- * @param {any[]} children
3699
- * @param {TransformContext} transform_context
3700
- * @returns {any[]}
3701
- */
3702
-
3703
- function create_element_children(children, transform_context) {
3704
- if (children.length === 0) {
3705
- return [];
3706
- }
3707
-
3708
- if (children.every(is_inline_element_child) && !children_contain_return_semantics(children)) {
3709
- const saved_inside_element_child = transform_context.inside_element_child;
3710
- transform_context.inside_element_child = true;
3711
- try {
3712
- return children.map((/** @type {any} */ child) => to_jsx_child(child, transform_context));
3713
- } finally {
3714
- transform_context.inside_element_child = saved_inside_element_child;
3715
- }
3716
- }
3717
-
3718
- const saved_inside_element_child = transform_context.inside_element_child;
3719
- transform_context.inside_element_child = true;
3720
- try {
3721
- return [statement_body_to_jsx_child(children, transform_context)];
3722
- } finally {
3723
- transform_context.inside_element_child = saved_inside_element_child;
3724
- }
3725
- }
3726
-
3727
- /**
3728
- * @param {any[]} children
3729
- * @returns {boolean}
3730
- */
3731
- function children_contain_return_semantics(children) {
3732
- return children.some(child_contains_return_semantics);
3733
- }
3734
-
3735
- /**
3736
- * @param {any} node
3737
- * @returns {boolean}
3738
- */
3739
- function child_contains_return_semantics(node) {
3740
- if (!node || typeof node !== 'object') {
3741
- return false;
3742
- }
3743
-
3744
- if (node.type === 'ReturnStatement') {
3745
- return true;
3746
- }
3747
-
3748
- if (
3749
- node.type === 'FunctionDeclaration' ||
3750
- node.type === 'FunctionExpression' ||
3751
- node.type === 'ArrowFunctionExpression'
3752
- ) {
3753
- return false;
3754
- }
3755
-
3756
- if (Array.isArray(node)) {
3757
- return node.some(child_contains_return_semantics);
3758
- }
3759
-
3760
- for (const key of Object.keys(node)) {
3761
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
3762
- continue;
3763
- }
3764
- if (child_contains_return_semantics(node[key])) {
3765
- return true;
3766
- }
3767
- }
3768
-
3769
- return false;
3770
- }
3771
-
3772
- /**
3773
- * @param {any} node
3774
- * @returns {boolean}
3775
- */
3776
- function is_inline_element_child(node) {
3777
- return node && is_render_child_node(node);
3778
- }
3779
-
3780
- /**
3781
- * @param {any[]} body_nodes
3782
- * @param {TransformContext} transform_context
3783
- * @returns {ESTreeJSX.JSXExpressionContainer}
3784
- */
3785
- function statement_body_to_jsx_child(body_nodes, transform_context) {
3786
- if (
3787
- should_extract_hook_helpers(transform_context) &&
3788
- body_contains_top_level_hook_call(body_nodes, transform_context, true)
3789
- ) {
3790
- return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
3791
- }
3792
-
3793
- return to_jsx_expression_container(
3794
- b.call(b.arrow([], b.block(build_render_statements(body_nodes, true, transform_context)))),
3795
- );
3796
- }
3797
-
3798
- /**
3799
- * @param {any[]} body_nodes
3800
- * @param {TransformContext} transform_context
3801
- * @returns {ESTreeJSX.JSXExpressionContainer}
3802
- */
3803
- function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
3804
- const source_node = get_body_source_node(body_nodes);
3805
- const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
3806
-
3807
- return to_jsx_expression_container(
3808
- create_hook_safe_helper_iife(helper.setup_statements, helper.component_element),
3809
- source_node,
3810
- );
3811
- }
3812
-
3813
- /**
3814
- * @param {TransformContext} transform_context
3815
- * @returns {string}
3816
- */
3817
- function create_local_statement_component_name(transform_context) {
3818
- transform_context.local_statement_component_index += 1;
3819
- return `StatementBodyHook${transform_context.local_statement_component_index}`;
3820
- }
3821
-
3822
- /**
3823
- * Wraps a list of body nodes into a component and returns
3824
- * statements that return `<ComponentName prop1={prop1} ... />`.
3825
- * Targets can either emit the helper component at module scope or cache the
3826
- * component identity in module state while initializing it from the parent.
3827
- * Used when a control flow branch contains hook calls that must be moved
3828
- * into their own component boundary to satisfy the Rules of Hooks.
3829
- *
3830
- * @param {any[]} body_nodes
3831
- * @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
3832
- * @param {TransformContext} transform_context
3833
- * @returns {any[]}
3834
- */
3835
- function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
3836
- const source_node = get_body_source_node(body_nodes);
3837
- const helper = create_hook_safe_helper(
3838
- body_nodes,
3839
- key_expression,
3840
- source_node,
3841
- transform_context,
3842
- );
3843
- const statements = [...helper.setup_statements];
3844
-
3845
- statements.push(b.return(helper.component_element));
3846
-
3847
- return statements;
3848
- }
3849
-
3850
- /**
3851
- * @param {any[]} body_nodes
3852
- * @param {Map<string, AST.Identifier>} available_bindings
3853
- * @returns {AST.Identifier[]}
3854
- */
3855
- function get_referenced_helper_bindings(body_nodes, available_bindings) {
3856
- const helper_bindings = [];
3857
- const local_bindings = new Map();
3858
-
3859
- for (const node of body_nodes) {
3860
- collect_statement_bindings(node, local_bindings);
3861
- }
3862
-
3863
- for (const [name, binding] of available_bindings) {
3864
- if (local_bindings.has(name)) continue;
3865
-
3866
- if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
3867
- helper_bindings.push(binding);
3868
- }
3869
- }
3870
-
3871
- return helper_bindings;
3872
- }
3873
-
3874
- /**
3875
- * @param {any[]} body_nodes
3876
- * @param {TransformContext} transform_context
3877
- * @param {Set<string>} [local_binding_names]
3878
- * @returns {void}
3879
- */
3880
- function validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
3881
- body_nodes,
3882
- transform_context,
3883
- local_binding_names,
3884
- ) {
3885
- if (!is_react_like_hook_platform(transform_context)) {
3886
- return;
3887
- }
3888
- if (!body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
3889
- return;
3890
- }
3891
- if (!transform_context.available_bindings || transform_context.available_bindings.size === 0) {
3892
- return;
3893
- }
3894
-
3895
- const shadowed_names = collect_block_binding_names(body_nodes);
3896
- for (const name of local_binding_names || []) {
3897
- shadowed_names.add(name);
3898
- }
3899
- validate_hook_outer_assignments_in_node(body_nodes, shadowed_names, transform_context, new Set());
3900
- }
3901
-
3902
- /**
3903
- * @param {TransformContext} transform_context
3904
- * @returns {boolean}
3905
- */
3906
- function is_react_like_hook_platform(transform_context) {
3907
- return (
3908
- transform_context.platform.name === 'React' || transform_context.platform.name === 'Preact'
3909
- );
3910
- }
3911
-
3912
- /**
3913
- * @param {any[]} statements
3914
- * @returns {Set<string>}
3915
- */
3916
- function collect_block_binding_names(statements) {
3917
- const names = new Set();
3918
- for (const statement of statements || []) {
3919
- collect_block_binding_names_from_statement(statement, names);
3920
- }
3921
- return names;
3922
- }
3923
-
3924
- /**
3925
- * @param {any} statement
3926
- * @param {Set<string>} names
3927
- * @returns {void}
3928
- */
3929
- function collect_block_binding_names_from_statement(statement, names) {
3930
- if (!statement || typeof statement !== 'object') {
3931
- return;
3932
- }
3933
-
3934
- if (statement.type === 'VariableDeclaration') {
3935
- for (const declaration of statement.declarations || []) {
3936
- collect_pattern_names(declaration.id, names);
3937
- }
3938
- return;
3939
- }
3940
-
3941
- if (
3942
- (statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
3943
- statement.id?.type === 'Identifier'
3944
- ) {
3945
- names.add(statement.id.name);
3946
- return;
3947
- }
3948
-
3949
- if (
3950
- statement.type === 'ForOfStatement' ||
3951
- statement.type === 'ForInStatement' ||
3952
- (statement.type === 'JSXForExpression' &&
3953
- (statement.statementType === 'ForOfStatement' ||
3954
- statement.statementType === 'ForInStatement'))
3955
- ) {
3956
- if (statement.left?.type === 'VariableDeclaration' && statement.left.kind === 'var') {
3957
- for (const declaration of statement.left.declarations || []) {
3958
- collect_pattern_names(declaration.id, names);
3959
- }
3960
- }
3961
- return;
3962
- }
3963
-
3964
- if (
3965
- statement.type === 'ForStatement' &&
3966
- statement.init?.type === 'VariableDeclaration' &&
3967
- statement.init.kind === 'var'
3968
- ) {
3969
- for (const declaration of statement.init.declarations || []) {
3970
- collect_pattern_names(declaration.id, names);
3971
- }
3972
- }
3973
- }
3974
-
3975
- /**
3976
- * @param {any} pattern
3977
- * @param {Set<string>} names
3978
- * @returns {void}
3979
- */
3980
- function collect_pattern_names(pattern, names) {
3981
- if (!pattern || typeof pattern !== 'object') {
3982
- return;
3983
- }
3984
-
3985
- if (pattern.type === 'Identifier') {
3986
- names.add(pattern.name);
3987
- return;
3988
- }
3989
-
3990
- if (pattern.type === 'RestElement') {
3991
- collect_pattern_names(pattern.argument, names);
3992
- return;
3993
- }
3994
-
3995
- if (pattern.type === 'AssignmentPattern') {
3996
- collect_pattern_names(pattern.left, names);
3997
- return;
3998
- }
3999
-
4000
- if (pattern.type === 'ArrayPattern') {
4001
- for (const element of pattern.elements || []) {
4002
- collect_pattern_names(element, names);
4003
- }
4004
- return;
4005
- }
4006
-
4007
- if (pattern.type === 'ObjectPattern') {
4008
- for (const property of pattern.properties || []) {
4009
- if (property.type === 'RestElement') {
4010
- collect_pattern_names(property.argument, names);
4011
- } else {
4012
- collect_pattern_names(property.value, names);
4013
- }
4014
- }
4015
- }
4016
- }
4017
-
4018
- /**
4019
- * @param {any} node
4020
- * @param {Set<string>} shadowed_names
4021
- * @param {TransformContext} transform_context
4022
- * @param {Set<string>} hook_result_names
4023
- * @returns {void}
4024
- */
4025
- function validate_hook_outer_assignments_in_node(
4026
- node,
4027
- shadowed_names,
4028
- transform_context,
4029
- hook_result_names,
4030
- ) {
4031
- if (!node || typeof node !== 'object') {
4032
- return;
4033
- }
4034
-
4035
- if (Array.isArray(node)) {
4036
- for (const child of node) {
4037
- validate_hook_outer_assignments_in_node(
4038
- child,
4039
- shadowed_names,
4040
- transform_context,
4041
- hook_result_names,
4042
- );
4043
- }
4044
- return;
4045
- }
4046
-
4047
- if (is_function_or_component_node(node)) {
4048
- return;
4049
- }
4050
-
4051
- if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
4052
- validate_hook_callback_outer_mutations(node, shadowed_names, transform_context);
4053
- }
4054
-
4055
- if (node.type === 'BlockStatement') {
4056
- const next_shadowed = new Set(shadowed_names);
4057
- const next_hook_result_names = new Set(hook_result_names);
4058
- for (const name of collect_block_binding_names(node.body || [])) {
4059
- next_shadowed.add(name);
4060
- }
4061
- for (const child of node.body || []) {
4062
- validate_hook_outer_assignments_in_node(
4063
- child,
4064
- next_shadowed,
4065
- transform_context,
4066
- next_hook_result_names,
4067
- );
4068
- }
4069
- return;
4070
- }
4071
-
4072
- if (node.type === 'VariableDeclaration') {
4073
- for (const declaration of node.declarations || []) {
4074
- if (
4075
- declaration.init &&
4076
- expression_contains_hook_derived_value(
4077
- declaration.init,
4078
- transform_context,
4079
- hook_result_names,
4080
- )
4081
- ) {
4082
- collect_pattern_names(declaration.id, hook_result_names);
4083
- }
4084
- validate_hook_outer_assignments_in_node(
4085
- declaration.init,
4086
- shadowed_names,
4087
- transform_context,
4088
- hook_result_names,
4089
- );
4090
- }
4091
- return;
4092
- }
4093
-
4094
- if (
4095
- node.type === 'AssignmentExpression' &&
4096
- expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
4097
- ) {
4098
- const outer_names = get_referenced_outer_binding_names(
4099
- node.left,
4100
- transform_context.available_bindings,
4101
- shadowed_names,
4102
- );
4103
- if (outer_names.length > 0) {
4104
- report_hook_outer_assignment_error(
4105
- node.left,
4106
- outer_names,
4107
- find_first_hook_call_name(node.right) || 'hook',
4108
- transform_context,
4109
- );
4110
- }
4111
- for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
4112
- hook_result_names.add(name);
4113
- }
4114
- }
4115
-
4116
- if (is_for_of_control_node(node)) {
4117
- if (
4118
- node.left &&
4119
- node.left.type !== 'VariableDeclaration' &&
4120
- expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
4121
- ) {
4122
- const outer_names = get_referenced_outer_binding_names(
4123
- node.left,
4124
- transform_context.available_bindings,
4125
- shadowed_names,
4126
- );
4127
- if (outer_names.length > 0) {
4128
- report_hook_outer_assignment_error(
4129
- node.left,
4130
- outer_names,
4131
- find_first_hook_call_name(node.right) || 'hook',
4132
- transform_context,
4133
- );
4134
- }
4135
- for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
4136
- hook_result_names.add(name);
4137
- }
4138
- }
4139
-
4140
- validate_hook_outer_assignments_in_node(
4141
- node.right,
4142
- shadowed_names,
4143
- transform_context,
4144
- hook_result_names,
4145
- );
4146
-
4147
- // Loop-declared bindings (`for (const x of …)`, `for (let x of …)`) live
4148
- // only in the body. They are deliberately NOT in the enclosing block's
4149
- // shadowed set (see collect_block_binding_names_from_statement), so add
4150
- // them just for the body recursion to keep references to the loop var
4151
- // from being flagged as outer-binding mutations.
4152
- const body_shadowed = new Set(shadowed_names);
4153
- if (node.left && node.left.type === 'VariableDeclaration') {
4154
- for (const declaration of node.left.declarations || []) {
4155
- collect_pattern_names(declaration.id, body_shadowed);
4156
- }
4157
- }
4158
- validate_hook_outer_assignments_in_node(
4159
- node.body,
4160
- body_shadowed,
4161
- transform_context,
4162
- hook_result_names,
4163
- );
4164
- return;
4165
- }
4166
-
4167
- for (const key of Object.keys(node)) {
4168
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4169
- continue;
4170
- }
4171
- validate_hook_outer_assignments_in_node(
4172
- node[key],
4173
- shadowed_names,
4174
- transform_context,
4175
- hook_result_names,
4176
- );
4177
- }
4178
- }
4179
-
4180
- /**
4181
- * @param {any} call_node
4182
- * @param {Set<string>} shadowed_names
4183
- * @param {TransformContext} transform_context
4184
- * @returns {void}
4185
- */
4186
- function validate_hook_callback_outer_mutations(call_node, shadowed_names, transform_context) {
4187
- const hook_name = get_hook_callee_name(call_node.callee);
4188
- for (const argument of call_node.arguments || []) {
4189
- if (!is_function_or_component_node(argument)) {
4190
- continue;
4191
- }
4192
- const callback_shadowed_names = create_function_like_shadowed_names(argument, shadowed_names);
4193
- validate_hook_callback_outer_mutations_in_node(
4194
- argument.body,
4195
- callback_shadowed_names,
4196
- transform_context,
4197
- hook_name,
4198
- );
4199
- }
4200
- }
4201
-
4202
- /**
4203
- * @param {any} node
4204
- * @param {Set<string>} shadowed_names
4205
- * @returns {Set<string>}
4206
- */
4207
- function create_function_like_shadowed_names(node, shadowed_names) {
4208
- const next_shadowed_names = new Set(shadowed_names);
4209
- for (const param of node.params || []) {
4210
- collect_pattern_names(param, next_shadowed_names);
4211
- }
4212
- if (node.body?.type === 'BlockStatement') {
4213
- for (const name of collect_block_binding_names(node.body.body || [])) {
4214
- next_shadowed_names.add(name);
4215
- }
4216
- }
4217
- return next_shadowed_names;
4218
- }
4219
-
4220
- /**
4221
- * @param {any} node
4222
- * @param {Set<string>} shadowed_names
4223
- * @param {TransformContext} transform_context
4224
- * @param {string} hook_name
4225
- * @returns {void}
4226
- */
4227
- function validate_hook_callback_outer_mutations_in_node(
4228
- node,
4229
- shadowed_names,
4230
- transform_context,
4231
- hook_name,
4232
- ) {
4233
- if (!node || typeof node !== 'object') {
4234
- return;
4235
- }
4236
-
4237
- if (Array.isArray(node)) {
4238
- for (const child of node) {
4239
- validate_hook_callback_outer_mutations_in_node(
4240
- child,
4241
- shadowed_names,
4242
- transform_context,
4243
- hook_name,
3231
+ const closingElement = selfClosing
3232
+ ? null
3233
+ : set_loc(
3234
+ b.jsx_closing_element(
3235
+ // Clone from the actual closing name when there is one: a dynamic
3236
+ // tag's closing expression (`</{Tag}>`) has its own source positions,
3237
+ // which editor mappings need.
3238
+ clone_jsx_name(
3239
+ node.closingElement?.name ?? name,
3240
+ node.closingElement?.name || node.closingElement || node,
3241
+ ),
3242
+ ),
3243
+ node.closingElement || node,
4244
3244
  );
4245
- }
4246
- return;
4247
- }
4248
3245
 
4249
- if (is_function_or_component_node(node)) {
4250
- validate_hook_callback_outer_mutations_in_node(
4251
- node.body,
4252
- create_function_like_shadowed_names(node, shadowed_names),
4253
- transform_context,
4254
- hook_name,
4255
- );
4256
- return;
3246
+ const element = set_loc(b.jsx_element_fresh(openingElement, closingElement, children), node);
3247
+ if (node.metadata?.dynamicElement === true) {
3248
+ // Keep lowered dynamic tags recognizable to scoped-CSS passes and the
3249
+ // static-hoist veto after the rebuild.
3250
+ element.metadata.dynamicElement = true;
4257
3251
  }
4258
-
4259
- if (node.type === 'BlockStatement') {
4260
- const next_shadowed_names = new Set(shadowed_names);
4261
- for (const name of collect_block_binding_names(node.body || [])) {
4262
- next_shadowed_names.add(name);
4263
- }
4264
- for (const child of node.body || []) {
4265
- validate_hook_callback_outer_mutations_in_node(
4266
- child,
4267
- next_shadowed_names,
4268
- transform_context,
4269
- hook_name,
4270
- );
4271
- }
4272
- return;
3252
+ if (transform_context.typeOnly && is_style_element(node)) {
3253
+ disable_style_anchor_verification(element);
4273
3254
  }
3255
+ return element;
3256
+ }
4274
3257
 
4275
- if (node.type === 'AssignmentExpression') {
4276
- const outer_names = get_referenced_outer_binding_names(
4277
- node.left,
4278
- transform_context.available_bindings,
4279
- shadowed_names,
4280
- );
4281
- if (outer_names.length > 0) {
4282
- report_hook_callback_outer_mutation_error(
4283
- node.left,
4284
- outer_names,
4285
- hook_name,
4286
- transform_context,
4287
- );
4288
- }
3258
+ /**
3259
+ * @param {any[]} children
3260
+ * @param {TransformContext} transform_context
3261
+ * @returns {any[]}
3262
+ */
3263
+
3264
+ function create_element_children(children, transform_context) {
3265
+ if (children.length === 0) {
3266
+ return [];
4289
3267
  }
4290
3268
 
4291
- if (node.type === 'UpdateExpression') {
4292
- const outer_names = get_referenced_outer_binding_names(
4293
- node.argument,
4294
- transform_context.available_bindings,
4295
- shadowed_names,
4296
- );
4297
- if (outer_names.length > 0) {
4298
- report_hook_callback_outer_mutation_error(
4299
- node.argument,
4300
- outer_names,
4301
- hook_name,
4302
- transform_context,
4303
- );
3269
+ if (children.every(is_inline_element_child) && !children_contain_return_semantics(children)) {
3270
+ const saved_inside_element_child = transform_context.inside_element_child;
3271
+ transform_context.inside_element_child = true;
3272
+ try {
3273
+ return children.map((/** @type {any} */ child) => to_jsx_child(child, transform_context));
3274
+ } finally {
3275
+ transform_context.inside_element_child = saved_inside_element_child;
4304
3276
  }
4305
3277
  }
4306
3278
 
4307
- for (const key of Object.keys(node)) {
4308
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4309
- continue;
4310
- }
4311
- if (key === 'left' && node.type === 'AssignmentExpression') {
4312
- continue;
4313
- }
4314
- if (key === 'argument' && node.type === 'UpdateExpression') {
4315
- continue;
4316
- }
4317
- validate_hook_callback_outer_mutations_in_node(
4318
- node[key],
4319
- shadowed_names,
4320
- transform_context,
4321
- hook_name,
4322
- );
3279
+ const saved_inside_element_child = transform_context.inside_element_child;
3280
+ transform_context.inside_element_child = true;
3281
+ try {
3282
+ return [statement_body_to_jsx_child(children, transform_context)];
3283
+ } finally {
3284
+ transform_context.inside_element_child = saved_inside_element_child;
4323
3285
  }
4324
3286
  }
4325
3287
 
4326
3288
  /**
4327
- * @param {any} node
4328
- * @param {TransformContext} transform_context
4329
- * @param {Set<string>} hook_result_names
3289
+ * @param {any[]} children
4330
3290
  * @returns {boolean}
4331
3291
  */
4332
- function expression_contains_hook_derived_value(node, transform_context, hook_result_names) {
4333
- return (
4334
- node_contains_top_level_hook_call(node, false, transform_context, true) ||
4335
- references_name_in_set(node, hook_result_names)
4336
- );
3292
+ function children_contain_return_semantics(children) {
3293
+ return children.some(child_contains_return_semantics);
4337
3294
  }
4338
3295
 
4339
3296
  /**
4340
3297
  * @param {any} node
4341
- * @param {Set<string>} names
4342
3298
  * @returns {boolean}
4343
3299
  */
4344
- function references_name_in_set(node, names) {
4345
- if (!node || typeof node !== 'object' || names.size === 0) {
3300
+ function child_contains_return_semantics(node) {
3301
+ if (!node || typeof node !== 'object') {
4346
3302
  return false;
4347
3303
  }
4348
3304
 
4349
- if (node.type === 'Identifier') {
4350
- return names.has(node.name);
3305
+ if (node.type === 'ReturnStatement') {
3306
+ return true;
4351
3307
  }
4352
3308
 
4353
3309
  if (
@@ -4359,20 +3315,14 @@ function references_name_in_set(node, names) {
4359
3315
  }
4360
3316
 
4361
3317
  if (Array.isArray(node)) {
4362
- return node.some((child) => references_name_in_set(child, names));
3318
+ return node.some(child_contains_return_semantics);
4363
3319
  }
4364
3320
 
4365
3321
  for (const key of Object.keys(node)) {
4366
3322
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4367
3323
  continue;
4368
3324
  }
4369
- if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
4370
- continue;
4371
- }
4372
- if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
4373
- continue;
4374
- }
4375
- if (references_name_in_set(node[key], names)) {
3325
+ if (child_contains_return_semantics(node[key])) {
4376
3326
  return true;
4377
3327
  }
4378
3328
  }
@@ -4382,162 +3332,104 @@ function references_name_in_set(node, names) {
4382
3332
 
4383
3333
  /**
4384
3334
  * @param {any} node
4385
- * @param {Set<string>} shadowed_names
4386
- * @returns {string[]}
3335
+ * @returns {boolean}
4387
3336
  */
4388
- function get_referenced_local_binding_names(node, shadowed_names) {
4389
- const names = new Set();
4390
- collect_referenced_local_binding_names(node, shadowed_names, names);
4391
- return [...names];
3337
+ function is_inline_element_child(node) {
3338
+ return node && is_render_child_node(node);
4392
3339
  }
4393
3340
 
4394
3341
  /**
4395
- * @param {any} node
4396
- * @param {Set<string>} shadowed_names
4397
- * @param {Set<string>} names
4398
- * @returns {void}
3342
+ * @param {any[]} body_nodes
3343
+ * @param {TransformContext} transform_context
3344
+ * @returns {ESTreeJSX.JSXExpressionContainer}
4399
3345
  */
4400
- function collect_referenced_local_binding_names(node, shadowed_names, names) {
4401
- if (!node || typeof node !== 'object') {
4402
- return;
3346
+ function statement_body_to_jsx_child(body_nodes, transform_context) {
3347
+ if (
3348
+ should_extract_hook_helpers(transform_context) &&
3349
+ body_contains_top_level_hook_call(body_nodes, transform_context, true)
3350
+ ) {
3351
+ return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
4403
3352
  }
4404
3353
 
4405
- if (node.type === 'Identifier') {
4406
- if (shadowed_names.has(node.name)) {
4407
- names.add(node.name);
4408
- }
4409
- return;
4410
- }
3354
+ return to_jsx_expression_container(
3355
+ b.call(b.arrow([], b.block(build_render_statements(body_nodes, true, transform_context)))),
3356
+ );
3357
+ }
4411
3358
 
4412
- if (Array.isArray(node)) {
4413
- for (const child of node) {
4414
- collect_referenced_local_binding_names(child, shadowed_names, names);
4415
- }
4416
- return;
4417
- }
3359
+ /**
3360
+ * @param {any[]} body_nodes
3361
+ * @param {TransformContext} transform_context
3362
+ * @returns {ESTreeJSX.JSXExpressionContainer}
3363
+ */
3364
+ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
3365
+ const source_node = get_body_source_node(body_nodes);
3366
+ const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
4418
3367
 
4419
- for (const key of Object.keys(node)) {
4420
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4421
- continue;
4422
- }
4423
- if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
4424
- continue;
4425
- }
4426
- if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
4427
- continue;
4428
- }
4429
- collect_referenced_local_binding_names(node[key], shadowed_names, names);
4430
- }
3368
+ return to_jsx_expression_container(
3369
+ create_hook_safe_helper_iife(helper.setup_statements, helper.component_element),
3370
+ source_node,
3371
+ );
4431
3372
  }
4432
3373
 
4433
3374
  /**
4434
- * @param {any} node
4435
- * @param {Map<string, AST.Identifier>} available_bindings
4436
- * @param {Set<string>} shadowed_names
4437
- * @returns {string[]}
3375
+ * @param {TransformContext} transform_context
3376
+ * @returns {string}
4438
3377
  */
4439
- function get_referenced_outer_binding_names(node, available_bindings, shadowed_names) {
4440
- const names = new Set();
4441
- collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names);
4442
- return [...names];
3378
+ function create_local_statement_component_name(transform_context) {
3379
+ transform_context.local_statement_component_index += 1;
3380
+ return `StatementBodyHook${transform_context.local_statement_component_index}`;
4443
3381
  }
4444
3382
 
4445
3383
  /**
4446
- * @param {any} node
4447
- * @param {Map<string, AST.Identifier>} available_bindings
4448
- * @param {Set<string>} shadowed_names
4449
- * @param {Set<string>} names
4450
- * @returns {void}
3384
+ * Wraps a list of body nodes into a component and returns
3385
+ * statements that return `<ComponentName prop1={prop1} ... />`.
3386
+ * Targets can either emit the helper component at module scope or cache the
3387
+ * component identity in module state while initializing it from the parent.
3388
+ * Used when a control flow branch contains hook calls that must be moved
3389
+ * into their own component boundary to satisfy the Rules of Hooks.
3390
+ *
3391
+ * @param {any[]} body_nodes
3392
+ * @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
3393
+ * @param {TransformContext} transform_context
3394
+ * @returns {any[]}
4451
3395
  */
4452
- function collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names) {
4453
- if (!node || typeof node !== 'object') {
4454
- return;
4455
- }
4456
-
4457
- if (node.type === 'Identifier') {
4458
- if (available_bindings.has(node.name) && !shadowed_names.has(node.name)) {
4459
- names.add(node.name);
4460
- }
4461
- return;
4462
- }
3396
+ function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
3397
+ const source_node = get_body_source_node(body_nodes);
3398
+ const helper = create_hook_safe_helper(
3399
+ body_nodes,
3400
+ key_expression,
3401
+ source_node,
3402
+ transform_context,
3403
+ );
3404
+ const statements = [...helper.setup_statements];
4463
3405
 
4464
- if (Array.isArray(node)) {
4465
- for (const child of node) {
4466
- collect_referenced_outer_binding_names(child, available_bindings, shadowed_names, names);
4467
- }
4468
- return;
4469
- }
3406
+ statements.push(b.return(helper.component_element));
4470
3407
 
4471
- for (const key of Object.keys(node)) {
4472
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4473
- continue;
4474
- }
4475
- if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
4476
- continue;
4477
- }
4478
- if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
4479
- continue;
4480
- }
4481
- collect_referenced_outer_binding_names(node[key], available_bindings, shadowed_names, names);
4482
- }
3408
+ return statements;
4483
3409
  }
4484
3410
 
4485
3411
  /**
4486
- * @param {any} node
4487
- * @returns {string | null}
3412
+ * @param {any[]} body_nodes
3413
+ * @param {Map<string, AST.Identifier>} available_bindings
3414
+ * @returns {AST.Identifier[]}
4488
3415
  */
4489
- function find_first_hook_call_name(node) {
4490
- if (!node || typeof node !== 'object') {
4491
- return null;
4492
- }
4493
-
4494
- if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
4495
- return get_hook_callee_name(node.callee);
4496
- }
3416
+ function get_referenced_helper_bindings(body_nodes, available_bindings) {
3417
+ const helper_bindings = [];
3418
+ const local_bindings = new Map();
4497
3419
 
4498
- if (
4499
- node.type === 'FunctionDeclaration' ||
4500
- node.type === 'FunctionExpression' ||
4501
- node.type === 'ArrowFunctionExpression'
4502
- ) {
4503
- return null;
3420
+ for (const node of body_nodes) {
3421
+ collect_statement_bindings(node, local_bindings);
4504
3422
  }
4505
3423
 
4506
- if (Array.isArray(node)) {
4507
- for (const child of node) {
4508
- const name = find_first_hook_call_name(child);
4509
- if (name) return name;
4510
- }
4511
- return null;
4512
- }
3424
+ for (const [name, binding] of available_bindings) {
3425
+ if (local_bindings.has(name)) continue;
4513
3426
 
4514
- for (const key of Object.keys(node)) {
4515
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4516
- continue;
3427
+ if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
3428
+ helper_bindings.push(binding);
4517
3429
  }
4518
- const name = find_first_hook_call_name(node[key]);
4519
- if (name) return name;
4520
3430
  }
4521
3431
 
4522
- return null;
4523
- }
4524
-
4525
- /**
4526
- * @param {any} callee
4527
- * @returns {string}
4528
- */
4529
- function get_hook_callee_name(callee) {
4530
- if (callee?.type === 'Identifier') {
4531
- return callee.name;
4532
- }
4533
- if (
4534
- callee?.type === 'MemberExpression' &&
4535
- !callee.computed &&
4536
- callee.property?.type === 'Identifier'
4537
- ) {
4538
- return callee.property.name;
4539
- }
4540
- return 'hook';
3432
+ return helper_bindings;
4541
3433
  }
4542
3434
 
4543
3435
  /**
@@ -4558,11 +3450,6 @@ export function create_hook_safe_helper(
4558
3450
  preallocated_helper_id,
4559
3451
  options = {},
4560
3452
  ) {
4561
- validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
4562
- body_nodes,
4563
- transform_context,
4564
- );
4565
-
4566
3453
  const helper_id =
4567
3454
  preallocated_helper_id ??
4568
3455
  create_generated_identifier(create_local_statement_component_name(transform_context));
@@ -4887,14 +3774,6 @@ function is_if_control_node(node) {
4887
3774
  return node?.type === 'IfStatement' || node?.type === 'JSXIfExpression';
4888
3775
  }
4889
3776
 
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
3777
  /**
4899
3778
  * @param {any} node
4900
3779
  * @returns {boolean}
@@ -4941,17 +3820,6 @@ function is_try_control_node(node) {
4941
3820
  return node?.type === 'TryStatement' || node?.type === 'JSXTryExpression';
4942
3821
  }
4943
3822
 
4944
- /**
4945
- * @param {any} node
4946
- * @returns {boolean}
4947
- */
4948
- function is_for_of_control_node(node) {
4949
- return (
4950
- node?.type === 'ForOfStatement' ||
4951
- (node?.type === 'JSXForExpression' && node.statementType === 'ForOfStatement')
4952
- );
4953
- }
4954
-
4955
3823
  /**
4956
3824
  * @param {any} node
4957
3825
  * @param {TransformContext} transform_context
@@ -5902,16 +4770,9 @@ function try_statement_to_jsx_child(node, transform_context) {
5902
4770
  // correctly identifies references to err/reset as non-static
5903
4771
  const saved_catch_bindings = transform_context.available_bindings;
5904
4772
  transform_context.available_bindings = new Map(saved_catch_bindings);
5905
- const catch_scoped_names = new Set();
5906
4773
  for (const param of catch_params) {
5907
4774
  collect_pattern_bindings(param, transform_context.available_bindings);
5908
- collect_pattern_names(param, catch_scoped_names);
5909
4775
  }
5910
- validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
5911
- catch_body_nodes,
5912
- transform_context,
5913
- catch_scoped_names,
5914
- );
5915
4776
 
5916
4777
  const fallback_fn = b.arrow(
5917
4778
  catch_params,
@@ -6904,6 +5765,12 @@ function build_return_expression(render_nodes) {
6904
5765
  if (render_nodes.length === 1) {
6905
5766
  const only = render_nodes[0];
6906
5767
  if (only.type === 'JSXExpressionContainer') {
5768
+ // Reactive-block containers (dynamic tags) must stay expression
5769
+ // children so the host JSX compiler wraps them in a render block;
5770
+ // returning the bare call would evaluate them once.
5771
+ if (only.metadata?.tsrx_reactive_block === true) {
5772
+ return set_loc(b.jsx_fragment([only]), only.loc ? only : undefined);
5773
+ }
6907
5774
  return only.expression;
6908
5775
  }
6909
5776
  if (only.type === 'JSXText') {