@tsrx/core 0.1.3 → 0.1.6
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.
- package/package.json +23 -5
- package/src/diagnostics.js +1 -0
- package/src/index.js +7 -0
- package/src/plugin.js +294 -230
- package/src/runtime/index.js +110 -0
- package/src/source-map-utils.js +19 -2
- package/src/transform/jsx/ast-builders.js +120 -0
- package/src/transform/jsx/index.js +1575 -1440
- package/src/transform/lazy.js +19 -60
- package/src/transform/scoping.js +9 -45
- package/src/transform/segments.js +164 -11
- package/src/utils/builders.js +51 -13
- package/types/jsx-platform.d.ts +10 -0
- package/types/runtime/index.d.ts +13 -0
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
get_for_of_iteration_params,
|
|
22
22
|
identifier_to_jsx_name,
|
|
23
23
|
is_bare_render_expression,
|
|
24
|
+
is_component_jsx_name,
|
|
24
25
|
is_dynamic_element_id,
|
|
25
26
|
is_jsx_child,
|
|
26
27
|
set_loc,
|
|
@@ -57,6 +58,79 @@ import {
|
|
|
57
58
|
} from '../jsx-interleave.js';
|
|
58
59
|
import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
|
|
59
60
|
|
|
61
|
+
const HOOK_OUTER_ASSIGNMENT_ERROR =
|
|
62
|
+
'Hook calls inside conditional or repeated TSRX scopes must keep their results local to the generated hook component.';
|
|
63
|
+
const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
|
|
64
|
+
'Hook callbacks inside conditional or repeated TSRX scopes must not mutate bindings declared outside the generated hook component.';
|
|
65
|
+
const TEMPLATE_FRAGMENT_ERROR =
|
|
66
|
+
'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {AST.Node} node
|
|
70
|
+
* @param {TransformContext} transform_context
|
|
71
|
+
*/
|
|
72
|
+
function report_html_template_unsupported_error(node, transform_context) {
|
|
73
|
+
// this should be a fatal error so we don't pass the errors collection,
|
|
74
|
+
// since we don't have a transform for the Html node
|
|
75
|
+
error(
|
|
76
|
+
`\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
|
|
77
|
+
transform_context.filename,
|
|
78
|
+
node,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {AST.Node} node
|
|
84
|
+
* @param {TransformContext} transform_context
|
|
85
|
+
*/
|
|
86
|
+
function report_jsx_fragment_in_tsrx_error(node, transform_context) {
|
|
87
|
+
error(
|
|
88
|
+
TEMPLATE_FRAGMENT_ERROR,
|
|
89
|
+
transform_context.filename,
|
|
90
|
+
node,
|
|
91
|
+
transform_context.errors,
|
|
92
|
+
transform_context.comments,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {AST.Node} node
|
|
98
|
+
* @param {string[]} names
|
|
99
|
+
* @param {string} hook_name
|
|
100
|
+
* @param {TransformContext} transform_context
|
|
101
|
+
* @returns {void}
|
|
102
|
+
*/
|
|
103
|
+
function report_hook_outer_assignment_error(node, names, hook_name, transform_context) {
|
|
104
|
+
const target =
|
|
105
|
+
names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
|
|
106
|
+
error(
|
|
107
|
+
`${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.`,
|
|
108
|
+
transform_context.filename,
|
|
109
|
+
node,
|
|
110
|
+
transform_context.errors,
|
|
111
|
+
transform_context.comments,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {AST.Node} node
|
|
117
|
+
* @param {string[]} names
|
|
118
|
+
* @param {string} hook_name
|
|
119
|
+
* @param {TransformContext} transform_context
|
|
120
|
+
* @returns {void}
|
|
121
|
+
*/
|
|
122
|
+
function report_hook_callback_outer_mutation_error(node, names, hook_name, transform_context) {
|
|
123
|
+
const target =
|
|
124
|
+
names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
|
|
125
|
+
error(
|
|
126
|
+
`${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.`,
|
|
127
|
+
transform_context.filename,
|
|
128
|
+
node,
|
|
129
|
+
transform_context.errors,
|
|
130
|
+
transform_context.comments,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
60
134
|
/**
|
|
61
135
|
* Local alias for the shared `JsxTransformContext`. Kept as a typedef so the
|
|
62
136
|
* rest of this file's `@param {TransformContext}` annotations don't all have
|
|
@@ -172,6 +246,8 @@ export function createJsxTransform(platform) {
|
|
|
172
246
|
needs_ref_prop: false,
|
|
173
247
|
needs_normalize_spread_props: false,
|
|
174
248
|
needs_fragment: false,
|
|
249
|
+
needs_for_of_iterable: false,
|
|
250
|
+
needs_iteration_value_type: false,
|
|
175
251
|
module_scoped_hook_components:
|
|
176
252
|
options?.moduleScopedHookComponents ?? !!platform.hooks?.moduleScopedHookComponents,
|
|
177
253
|
helper_state: null,
|
|
@@ -442,16 +518,19 @@ export function createJsxTransform(platform) {
|
|
|
442
518
|
const value = state.current_css_hash
|
|
443
519
|
? `${state.current_css_hash} ${class_name}`
|
|
444
520
|
: class_name;
|
|
445
|
-
return
|
|
521
|
+
return b.literal(value, undefined, node);
|
|
446
522
|
},
|
|
447
523
|
|
|
448
524
|
// Default .metadata on every function-like node so downstream consumers
|
|
449
525
|
// (e.g. segments.js reading node.value.metadata.is_component on class
|
|
450
526
|
// methods) don't trip on an undefined metadata object. Ripple's analyze
|
|
451
527
|
// phase does this via visit_function; tsrx-react has no analyze phase.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
528
|
+
// If a plain JS function contains a hook-bearing <tsrx> expression,
|
|
529
|
+
// give it a temporary helper scope so extracted hook components can
|
|
530
|
+
// be emitted with stable identities just like component-body helpers.
|
|
531
|
+
FunctionDeclaration: transform_function_with_hook_helpers,
|
|
532
|
+
FunctionExpression: transform_function_with_hook_helpers,
|
|
533
|
+
ArrowFunctionExpression: transform_function_with_hook_helpers,
|
|
455
534
|
|
|
456
535
|
RefExpression(node) {
|
|
457
536
|
return create_ref_prop_call(node, transform_context);
|
|
@@ -590,11 +669,7 @@ export function component_to_function_declaration(component, transform_context,
|
|
|
590
669
|
// Wrap body_statements in a BlockStatement so that apply_lazy_transforms
|
|
591
670
|
// runs collect_block_shadowed_names and detects body-level declarations
|
|
592
671
|
// (e.g. `const name = ...`) that shadow lazy binding names.
|
|
593
|
-
const body_block =
|
|
594
|
-
type: 'BlockStatement',
|
|
595
|
-
body: body_statements,
|
|
596
|
-
metadata: { path: [] },
|
|
597
|
-
});
|
|
672
|
+
const body_block = b.block(body_statements);
|
|
598
673
|
const final_body =
|
|
599
674
|
lazy_bindings.size > 0 ? apply_lazy_transforms(body_block, lazy_bindings) : body_block;
|
|
600
675
|
|
|
@@ -602,48 +677,19 @@ export function component_to_function_declaration(component, transform_context,
|
|
|
602
677
|
let fn;
|
|
603
678
|
|
|
604
679
|
if (component.id) {
|
|
605
|
-
fn =
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
generator: false,
|
|
613
|
-
metadata: {
|
|
614
|
-
path: [],
|
|
615
|
-
is_component: true,
|
|
616
|
-
},
|
|
617
|
-
});
|
|
680
|
+
fn = b.function_declaration(
|
|
681
|
+
component.id,
|
|
682
|
+
final_params,
|
|
683
|
+
final_body,
|
|
684
|
+
is_async_component,
|
|
685
|
+
component.typeParameters,
|
|
686
|
+
);
|
|
618
687
|
} else if (component.metadata?.arrow) {
|
|
619
|
-
fn =
|
|
620
|
-
type: 'ArrowFunctionExpression',
|
|
621
|
-
typeParameters: component.typeParameters,
|
|
622
|
-
params: final_params,
|
|
623
|
-
body: final_body,
|
|
624
|
-
async: is_async_component,
|
|
625
|
-
generator: false,
|
|
626
|
-
expression: false,
|
|
627
|
-
metadata: {
|
|
628
|
-
path: [],
|
|
629
|
-
is_component: true,
|
|
630
|
-
},
|
|
631
|
-
});
|
|
688
|
+
fn = b.arrow(final_params, final_body, is_async_component, component.typeParameters);
|
|
632
689
|
} else {
|
|
633
|
-
fn =
|
|
634
|
-
type: 'FunctionExpression',
|
|
635
|
-
id: null,
|
|
636
|
-
typeParameters: component.typeParameters,
|
|
637
|
-
params: final_params,
|
|
638
|
-
body: final_body,
|
|
639
|
-
async: is_async_component,
|
|
640
|
-
generator: false,
|
|
641
|
-
metadata: {
|
|
642
|
-
path: [],
|
|
643
|
-
is_component: true,
|
|
644
|
-
},
|
|
645
|
-
});
|
|
690
|
+
fn = b.function(null, final_params, final_body, is_async_component, component.typeParameters);
|
|
646
691
|
}
|
|
692
|
+
/** @type {any} */ (fn.metadata).is_component = true;
|
|
647
693
|
|
|
648
694
|
// Restore context
|
|
649
695
|
transform_context.helper_state = saved_helper_state;
|
|
@@ -813,25 +859,16 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
813
859
|
if (stmt.type === 'ReturnStatement') {
|
|
814
860
|
if (stmt.argument) {
|
|
815
861
|
render_nodes.push(
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
type: 'Literal',
|
|
824
|
-
value: null,
|
|
825
|
-
raw: 'null',
|
|
826
|
-
metadata: { path: [] },
|
|
827
|
-
},
|
|
828
|
-
alternate: stmt.argument,
|
|
829
|
-
metadata: { path: [] },
|
|
830
|
-
}),
|
|
862
|
+
b.jsx_expression_container(
|
|
863
|
+
set_loc(
|
|
864
|
+
b.conditional(
|
|
865
|
+
clone_expression_node(child.test),
|
|
866
|
+
b.literal(null),
|
|
867
|
+
stmt.argument,
|
|
868
|
+
),
|
|
831
869
|
child,
|
|
832
870
|
),
|
|
833
|
-
|
|
834
|
-
}),
|
|
871
|
+
),
|
|
835
872
|
);
|
|
836
873
|
}
|
|
837
874
|
} else {
|
|
@@ -852,26 +889,6 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
852
889
|
continue;
|
|
853
890
|
}
|
|
854
891
|
|
|
855
|
-
if (
|
|
856
|
-
child.type === 'IfStatement' &&
|
|
857
|
-
!child.alternate &&
|
|
858
|
-
!is_returning_if_statement(child) &&
|
|
859
|
-
!transform_context.platform.hooks?.isTopLevelSetupCall &&
|
|
860
|
-
body_contains_top_level_hook_call([child], transform_context, true) &&
|
|
861
|
-
i + 1 < body_nodes.length
|
|
862
|
-
) {
|
|
863
|
-
statements.push(
|
|
864
|
-
...create_continuation_lift_if_statement(
|
|
865
|
-
child,
|
|
866
|
-
body_nodes.slice(i + 1),
|
|
867
|
-
render_nodes,
|
|
868
|
-
transform_context,
|
|
869
|
-
),
|
|
870
|
-
);
|
|
871
|
-
transform_context.available_bindings = saved_bindings;
|
|
872
|
-
return statements;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
892
|
if (
|
|
876
893
|
child.type === 'ForOfStatement' &&
|
|
877
894
|
!child.await &&
|
|
@@ -883,26 +900,9 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
883
900
|
true,
|
|
884
901
|
)
|
|
885
902
|
) {
|
|
886
|
-
const
|
|
887
|
-
const hoisted = build_hoisted_for_of_with_hooks(
|
|
888
|
-
child,
|
|
889
|
-
for_of_continuation,
|
|
890
|
-
transform_context,
|
|
891
|
-
);
|
|
903
|
+
const hoisted = build_hoisted_for_of_with_hooks(child, transform_context);
|
|
892
904
|
if (hoisted) {
|
|
893
905
|
statements.push(...hoisted.hoist_statements);
|
|
894
|
-
if (for_of_continuation.length > 0) {
|
|
895
|
-
// Tail was lifted into the helper; everything after the for-of
|
|
896
|
-
// now lives there. Combine prior render_nodes with the iteration
|
|
897
|
-
// JSX and return.
|
|
898
|
-
statements.push({
|
|
899
|
-
type: 'ReturnStatement',
|
|
900
|
-
argument: combine_render_return_argument(render_nodes, hoisted.jsx_child),
|
|
901
|
-
metadata: { path: [] },
|
|
902
|
-
});
|
|
903
|
-
transform_context.available_bindings = saved_bindings;
|
|
904
|
-
return statements;
|
|
905
|
-
}
|
|
906
906
|
if (interleaved && is_capturable_jsx_child(hoisted.jsx_child)) {
|
|
907
907
|
const { declaration, reference } = captureJsxChild(hoisted.jsx_child, capture_index++);
|
|
908
908
|
statements.push(declaration);
|
|
@@ -914,43 +914,6 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
914
914
|
}
|
|
915
915
|
}
|
|
916
916
|
|
|
917
|
-
if (
|
|
918
|
-
child.type === 'TryStatement' &&
|
|
919
|
-
!child.finalizer &&
|
|
920
|
-
!transform_context.platform.hooks?.isTopLevelSetupCall &&
|
|
921
|
-
try_statement_contains_hooks(child, transform_context) &&
|
|
922
|
-
i + 1 < body_nodes.length
|
|
923
|
-
) {
|
|
924
|
-
statements.push(
|
|
925
|
-
...create_continuation_lift_try_statement(
|
|
926
|
-
child,
|
|
927
|
-
body_nodes.slice(i + 1),
|
|
928
|
-
render_nodes,
|
|
929
|
-
transform_context,
|
|
930
|
-
),
|
|
931
|
-
);
|
|
932
|
-
transform_context.available_bindings = saved_bindings;
|
|
933
|
-
return statements;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
if (
|
|
937
|
-
child.type === 'SwitchStatement' &&
|
|
938
|
-
!transform_context.platform.hooks?.isTopLevelSetupCall &&
|
|
939
|
-
body_contains_top_level_hook_call([child], transform_context, true) &&
|
|
940
|
-
i + 1 < body_nodes.length
|
|
941
|
-
) {
|
|
942
|
-
statements.push(
|
|
943
|
-
...create_continuation_lift_switch_statement(
|
|
944
|
-
child,
|
|
945
|
-
body_nodes.slice(i + 1),
|
|
946
|
-
render_nodes,
|
|
947
|
-
transform_context,
|
|
948
|
-
),
|
|
949
|
-
);
|
|
950
|
-
transform_context.available_bindings = saved_bindings;
|
|
951
|
-
return statements;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
917
|
if (is_jsx_child(child)) {
|
|
955
918
|
const jsx = to_jsx_child(child, transform_context);
|
|
956
919
|
statements.push(...extract_jsx_setup_declarations(jsx));
|
|
@@ -975,10 +938,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
975
938
|
|
|
976
939
|
const return_arg = build_return_expression(render_nodes);
|
|
977
940
|
if (return_arg || (return_null_when_empty && !has_terminal_return)) {
|
|
978
|
-
statements.push(
|
|
979
|
-
type: 'ReturnStatement',
|
|
980
|
-
argument: return_arg || { type: 'Literal', value: null, raw: 'null' },
|
|
981
|
-
});
|
|
941
|
+
statements.push(b.return(return_arg || b.literal(null)));
|
|
982
942
|
}
|
|
983
943
|
|
|
984
944
|
transform_context.available_bindings = saved_bindings;
|
|
@@ -1178,16 +1138,7 @@ function create_helper_props_property(binding) {
|
|
|
1178
1138
|
const key = create_generated_identifier(binding.name);
|
|
1179
1139
|
const value = create_generated_identifier(binding.name);
|
|
1180
1140
|
|
|
1181
|
-
return
|
|
1182
|
-
type: 'Property',
|
|
1183
|
-
key,
|
|
1184
|
-
value,
|
|
1185
|
-
kind: 'init',
|
|
1186
|
-
method: false,
|
|
1187
|
-
shorthand: true,
|
|
1188
|
-
computed: false,
|
|
1189
|
-
metadata: { path: [] },
|
|
1190
|
-
});
|
|
1141
|
+
return b.prop('init', key, value, false, true);
|
|
1191
1142
|
}
|
|
1192
1143
|
|
|
1193
1144
|
/**
|
|
@@ -1250,6 +1201,156 @@ function create_helper_state(base_name) {
|
|
|
1250
1201
|
};
|
|
1251
1202
|
}
|
|
1252
1203
|
|
|
1204
|
+
/**
|
|
1205
|
+
* @param {any} node
|
|
1206
|
+
* @param {{ next: () => any, state: TransformContext }} context
|
|
1207
|
+
* @returns {any}
|
|
1208
|
+
*/
|
|
1209
|
+
function transform_function_with_hook_helpers(node, { next, state }) {
|
|
1210
|
+
if (state.helper_state || !function_contains_hook_bearing_tsrx(node, state)) {
|
|
1211
|
+
return ensure_function_metadata(node, { next });
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const helper_state = create_helper_state(get_function_helper_base_name(node));
|
|
1215
|
+
const saved_helper_state = state.helper_state;
|
|
1216
|
+
const saved_bindings = state.available_bindings;
|
|
1217
|
+
|
|
1218
|
+
state.helper_state = helper_state;
|
|
1219
|
+
state.available_bindings = collect_function_scope_bindings(node);
|
|
1220
|
+
|
|
1221
|
+
const inner = /** @type {any} */ (next() ?? node);
|
|
1222
|
+
|
|
1223
|
+
state.helper_state = saved_helper_state;
|
|
1224
|
+
state.available_bindings = saved_bindings;
|
|
1225
|
+
|
|
1226
|
+
ensure_function_metadata(inner, { next: () => inner });
|
|
1227
|
+
if (helper_state.helpers.length || helper_state.statics.length) {
|
|
1228
|
+
inner.metadata = {
|
|
1229
|
+
...(inner.metadata || {}),
|
|
1230
|
+
generated_helpers: helper_state.helpers,
|
|
1231
|
+
generated_statics: helper_state.statics,
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
return inner;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* @param {any} node
|
|
1240
|
+
* @returns {string}
|
|
1241
|
+
*/
|
|
1242
|
+
function get_function_helper_base_name(node) {
|
|
1243
|
+
if (node.id?.type === 'Identifier') {
|
|
1244
|
+
return node.id.name;
|
|
1245
|
+
}
|
|
1246
|
+
return 'Tsrx';
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* @param {any} node
|
|
1251
|
+
* @returns {Map<string, AST.Identifier>}
|
|
1252
|
+
*/
|
|
1253
|
+
function collect_function_scope_bindings(node) {
|
|
1254
|
+
const bindings = collect_param_bindings(node.params || []);
|
|
1255
|
+
collect_descendant_declaration_bindings(node.body, bindings);
|
|
1256
|
+
return bindings;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* @param {any} node
|
|
1261
|
+
* @param {Map<string, AST.Identifier>} bindings
|
|
1262
|
+
* @returns {void}
|
|
1263
|
+
*/
|
|
1264
|
+
function collect_descendant_declaration_bindings(node, bindings) {
|
|
1265
|
+
if (!node || typeof node !== 'object') {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (node.type === 'VariableDeclaration') {
|
|
1270
|
+
for (const declaration of node.declarations || []) {
|
|
1271
|
+
collect_pattern_bindings(declaration.id, bindings);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (
|
|
1276
|
+
(node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') &&
|
|
1277
|
+
node.id?.type === 'Identifier'
|
|
1278
|
+
) {
|
|
1279
|
+
bindings.set(node.id.name, node.id);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if (
|
|
1283
|
+
node.type === 'FunctionDeclaration' ||
|
|
1284
|
+
node.type === 'FunctionExpression' ||
|
|
1285
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
1286
|
+
node.type === 'Component'
|
|
1287
|
+
) {
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
if (Array.isArray(node)) {
|
|
1292
|
+
for (const child of node) {
|
|
1293
|
+
collect_descendant_declaration_bindings(child, bindings);
|
|
1294
|
+
}
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
for (const key of Object.keys(node)) {
|
|
1299
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
collect_descendant_declaration_bindings(node[key], bindings);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* @param {any} node
|
|
1308
|
+
* @param {TransformContext} transform_context
|
|
1309
|
+
* @returns {boolean}
|
|
1310
|
+
*/
|
|
1311
|
+
function function_contains_hook_bearing_tsrx(node, transform_context) {
|
|
1312
|
+
return node_contains_hook_bearing_tsrx(node.body, transform_context);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* @param {any} node
|
|
1317
|
+
* @param {TransformContext} transform_context
|
|
1318
|
+
* @returns {boolean}
|
|
1319
|
+
*/
|
|
1320
|
+
function node_contains_hook_bearing_tsrx(node, transform_context) {
|
|
1321
|
+
if (!node || typeof node !== 'object') {
|
|
1322
|
+
return false;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
if (Array.isArray(node)) {
|
|
1326
|
+
return node.some((child) => node_contains_hook_bearing_tsrx(child, transform_context));
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
if (node.type === 'Tsrx') {
|
|
1330
|
+
return body_contains_top_level_hook_call(node.children || [], transform_context, true);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
if (
|
|
1334
|
+
node.type === 'FunctionDeclaration' ||
|
|
1335
|
+
node.type === 'FunctionExpression' ||
|
|
1336
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
1337
|
+
node.type === 'Component'
|
|
1338
|
+
) {
|
|
1339
|
+
return false;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
for (const key of Object.keys(node)) {
|
|
1343
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (node_contains_hook_bearing_tsrx(node[key], transform_context)) {
|
|
1347
|
+
return true;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1253
1354
|
/**
|
|
1254
1355
|
* @param {TransformContext} transform_context
|
|
1255
1356
|
* @returns {boolean}
|
|
@@ -1273,7 +1374,7 @@ function create_module_scoped_hook_component_id(helper_id, transform_context) {
|
|
|
1273
1374
|
* @param {any[]} params
|
|
1274
1375
|
* @returns {Map<string, AST.Identifier>}
|
|
1275
1376
|
*/
|
|
1276
|
-
function collect_param_bindings(params) {
|
|
1377
|
+
export function collect_param_bindings(params) {
|
|
1277
1378
|
const bindings = new Map();
|
|
1278
1379
|
for (const param of params) {
|
|
1279
1380
|
collect_pattern_bindings(param, bindings);
|
|
@@ -1286,7 +1387,7 @@ function collect_param_bindings(params) {
|
|
|
1286
1387
|
* @param {Map<string, AST.Identifier>} bindings
|
|
1287
1388
|
* @returns {void}
|
|
1288
1389
|
*/
|
|
1289
|
-
function collect_statement_bindings(statement, bindings) {
|
|
1390
|
+
export function collect_statement_bindings(statement, bindings) {
|
|
1290
1391
|
if (!statement) return;
|
|
1291
1392
|
|
|
1292
1393
|
if (statement.type === 'VariableDeclaration') {
|
|
@@ -1420,6 +1521,16 @@ function hoist_static_render_nodes(render_nodes, transform_context) {
|
|
|
1420
1521
|
const node = render_nodes[i];
|
|
1421
1522
|
if (node.type !== 'JSXElement') continue;
|
|
1422
1523
|
if (!is_hoist_safe_jsx_node(node)) continue;
|
|
1524
|
+
if (is_bare_component_invocation(node)) {
|
|
1525
|
+
// `<Helper />` with no attributes and no children is just an
|
|
1526
|
+
// invocation reference — most often a generated `StatementBodyHook`
|
|
1527
|
+
// chain element we emitted ourselves. Hoisting it would produce
|
|
1528
|
+
// `const App__staticN = <Helper />` aliases that bloat the output
|
|
1529
|
+
// without enabling React's element-identity fast path (the helper
|
|
1530
|
+
// isn't memoized, so the parent re-invokes it every render either
|
|
1531
|
+
// way). Inline the reference at the call site instead.
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1423
1534
|
if (
|
|
1424
1535
|
transform_context.platform.hooks?.canHoistStaticNode &&
|
|
1425
1536
|
!transform_context.platform.hooks.canHoistStaticNode(node, transform_context)
|
|
@@ -1437,6 +1548,23 @@ function hoist_static_render_nodes(render_nodes, transform_context) {
|
|
|
1437
1548
|
}
|
|
1438
1549
|
}
|
|
1439
1550
|
|
|
1551
|
+
/**
|
|
1552
|
+
* `<Helper />` shape with no attributes and no children. The opening element
|
|
1553
|
+
* name must be component-shaped (see `is_component_jsx_name`) — lowercase
|
|
1554
|
+
* identifiers are host DOM tags, which *do* benefit from hoisting because
|
|
1555
|
+
* React diffs them against the previous render.
|
|
1556
|
+
*
|
|
1557
|
+
* @param {any} node
|
|
1558
|
+
* @returns {boolean}
|
|
1559
|
+
*/
|
|
1560
|
+
function is_bare_component_invocation(node) {
|
|
1561
|
+
if (!node || node.type !== 'JSXElement') return false;
|
|
1562
|
+
const opening = node.openingElement;
|
|
1563
|
+
if (!opening || opening.attributes.length > 0) return false;
|
|
1564
|
+
if (node.children.length > 0) return false;
|
|
1565
|
+
return is_component_jsx_name(opening.name);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1440
1568
|
/**
|
|
1441
1569
|
* Static JSX that appears before multiple early-return guards is otherwise
|
|
1442
1570
|
* cloned into every generated return. Capture it once at its source position
|
|
@@ -1665,201 +1793,38 @@ function create_component_returning_if_statement(node, render_nodes, transform_c
|
|
|
1665
1793
|
return set_loc(b.if(node.test, set_loc(b.block(branch_statements), node.consequent), null), node);
|
|
1666
1794
|
}
|
|
1667
1795
|
|
|
1668
|
-
/* ---------------------------------------------------------------------- *
|
|
1669
|
-
* Continuation-lift primitives shared across if / switch / try / for-of *
|
|
1670
|
-
* ---------------------------------------------------------------------- */
|
|
1671
|
-
|
|
1672
|
-
/**
|
|
1673
|
-
* Build the helper component that owns the post-control-flow continuation.
|
|
1674
|
-
* Same shape as `create_hook_safe_helper`; named for intent at lift call sites.
|
|
1675
|
-
*
|
|
1676
|
-
* @param {any[]} continuation_body
|
|
1677
|
-
* @param {any} source_node
|
|
1678
|
-
* @param {TransformContext} transform_context
|
|
1679
|
-
* @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
|
|
1680
|
-
*/
|
|
1681
|
-
function build_tail_helper(continuation_body, source_node, transform_context) {
|
|
1682
|
-
return create_hook_safe_helper(continuation_body, undefined, source_node, transform_context);
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
1796
|
/**
|
|
1686
|
-
*
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1797
|
+
* Build a `return <combined-render-fragment>;` statement, prepending any
|
|
1798
|
+
* `render_nodes` collected before the control-flow construct so they don't
|
|
1799
|
+
* get dropped on the lift path.
|
|
1689
1800
|
*
|
|
1690
|
-
* @param {
|
|
1801
|
+
* @param {any[]} render_nodes
|
|
1802
|
+
* @param {any} jsx_child
|
|
1691
1803
|
* @returns {any}
|
|
1692
1804
|
*/
|
|
1693
|
-
function
|
|
1694
|
-
return
|
|
1805
|
+
function combined_return_statement(render_nodes, jsx_child) {
|
|
1806
|
+
return b.return(combine_render_return_argument(render_nodes, jsx_child));
|
|
1695
1807
|
}
|
|
1696
1808
|
|
|
1697
1809
|
/**
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
*
|
|
1810
|
+
* Hoist a for-of iteration source into a generated `let` and add a
|
|
1811
|
+
* normalization assignment via `Array.isArray(src) ? src : Array.from(src)`.
|
|
1812
|
+
* Always emits both — even when the source is already a simple identifier —
|
|
1813
|
+
* so the loop-scoped TS type aliases have a stable name to reference and the
|
|
1814
|
+
* runtime check skips the copy when the value is already an array.
|
|
1702
1815
|
*
|
|
1703
|
-
* @param {
|
|
1704
|
-
* @param {
|
|
1705
|
-
* @returns {any
|
|
1706
|
-
*/
|
|
1707
|
-
function append_tail_invocation(body, tail_helper) {
|
|
1708
|
-
return [...body, clone_tail_invocation(tail_helper)];
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
/**
|
|
1712
|
-
* @param {AST.Identifier} tail_synthetic_id
|
|
1713
|
-
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1714
|
-
* @returns {any}
|
|
1715
|
-
*/
|
|
1716
|
-
function create_loop_tail_expression(tail_synthetic_id, tail_helper) {
|
|
1717
|
-
return b.logical('&&', clone_identifier(tail_synthetic_id), clone_tail_invocation(tail_helper));
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
/**
|
|
1721
|
-
* @param {AST.Identifier} tail_synthetic_id
|
|
1722
|
-
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1723
|
-
* @returns {any}
|
|
1816
|
+
* @param {AST.Identifier} source_id
|
|
1817
|
+
* @param {any} source_expr
|
|
1818
|
+
* @returns {{ source_decl: any, source_normalize_decl: any }}
|
|
1724
1819
|
*/
|
|
1725
|
-
function
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
);
|
|
1731
|
-
}
|
|
1820
|
+
function build_array_normalization_decls(source_id, source_expr) {
|
|
1821
|
+
const source_decl = b.let(clone_identifier(source_id), clone_expression_node(source_expr));
|
|
1822
|
+
const is_array_call = b.call(b.member(b.id('Array'), 'isArray'), clone_identifier(source_id));
|
|
1823
|
+
const from_call = b.call(b.member(b.id('Array'), 'from'), clone_identifier(source_id));
|
|
1824
|
+
const normalized = b.conditional(is_array_call, clone_identifier(source_id), from_call);
|
|
1825
|
+
const source_normalize_decl = b.stmt(b.assignment('=', clone_identifier(source_id), normalized));
|
|
1732
1826
|
|
|
1733
|
-
|
|
1734
|
-
* @param {any[]} statements
|
|
1735
|
-
* @param {AST.Identifier} tail_synthetic_id
|
|
1736
|
-
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1737
|
-
* @returns {void}
|
|
1738
|
-
*/
|
|
1739
|
-
function append_loop_tail_to_return_statements(statements, tail_synthetic_id, tail_helper) {
|
|
1740
|
-
for (const statement of statements) {
|
|
1741
|
-
append_loop_tail_to_return_statement(statement, tail_synthetic_id, tail_helper, false);
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
/**
|
|
1746
|
-
* @param {any} node
|
|
1747
|
-
* @param {AST.Identifier} tail_synthetic_id
|
|
1748
|
-
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1749
|
-
* @param {boolean} inside_nested_function
|
|
1750
|
-
* @returns {void}
|
|
1751
|
-
*/
|
|
1752
|
-
function append_loop_tail_to_return_statement(
|
|
1753
|
-
node,
|
|
1754
|
-
tail_synthetic_id,
|
|
1755
|
-
tail_helper,
|
|
1756
|
-
inside_nested_function,
|
|
1757
|
-
) {
|
|
1758
|
-
if (!node || typeof node !== 'object') {
|
|
1759
|
-
return;
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
if (
|
|
1763
|
-
node.type === 'FunctionDeclaration' ||
|
|
1764
|
-
node.type === 'FunctionExpression' ||
|
|
1765
|
-
node.type === 'ArrowFunctionExpression'
|
|
1766
|
-
) {
|
|
1767
|
-
inside_nested_function = true;
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
if (!inside_nested_function && node.type === 'ReturnStatement') {
|
|
1771
|
-
if (
|
|
1772
|
-
references_scope_bindings(
|
|
1773
|
-
node.argument,
|
|
1774
|
-
new Map([[tail_synthetic_id.name, tail_synthetic_id]]),
|
|
1775
|
-
)
|
|
1776
|
-
) {
|
|
1777
|
-
return;
|
|
1778
|
-
}
|
|
1779
|
-
node.argument = append_loop_tail_to_return_argument(
|
|
1780
|
-
node.argument,
|
|
1781
|
-
tail_synthetic_id,
|
|
1782
|
-
tail_helper,
|
|
1783
|
-
);
|
|
1784
|
-
return;
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
if (Array.isArray(node)) {
|
|
1788
|
-
for (const child of node) {
|
|
1789
|
-
append_loop_tail_to_return_statement(
|
|
1790
|
-
child,
|
|
1791
|
-
tail_synthetic_id,
|
|
1792
|
-
tail_helper,
|
|
1793
|
-
inside_nested_function,
|
|
1794
|
-
);
|
|
1795
|
-
}
|
|
1796
|
-
return;
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
for (const key of Object.keys(node)) {
|
|
1800
|
-
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
1801
|
-
continue;
|
|
1802
|
-
}
|
|
1803
|
-
append_loop_tail_to_return_statement(
|
|
1804
|
-
node[key],
|
|
1805
|
-
tail_synthetic_id,
|
|
1806
|
-
tail_helper,
|
|
1807
|
-
inside_nested_function,
|
|
1808
|
-
);
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
/**
|
|
1813
|
-
* @param {any} return_argument
|
|
1814
|
-
* @param {AST.Identifier} tail_synthetic_id
|
|
1815
|
-
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1816
|
-
* @returns {any}
|
|
1817
|
-
*/
|
|
1818
|
-
function append_loop_tail_to_return_argument(return_argument, tail_synthetic_id, tail_helper) {
|
|
1819
|
-
if (return_argument == null || is_null_literal(return_argument)) {
|
|
1820
|
-
return create_loop_tail_conditional(tail_synthetic_id, tail_helper);
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
return (
|
|
1824
|
-
build_return_expression([
|
|
1825
|
-
return_argument_to_render_node(return_argument),
|
|
1826
|
-
to_jsx_expression_container(create_loop_tail_expression(tail_synthetic_id, tail_helper)),
|
|
1827
|
-
]) || create_null_literal()
|
|
1828
|
-
);
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
/**
|
|
1832
|
-
* Build a `return <combined-render-fragment>;` statement, prepending any
|
|
1833
|
-
* `render_nodes` collected before the control-flow construct so they don't
|
|
1834
|
-
* get dropped on the lift path.
|
|
1835
|
-
*
|
|
1836
|
-
* @param {any[]} render_nodes
|
|
1837
|
-
* @param {any} jsx_child
|
|
1838
|
-
* @returns {any}
|
|
1839
|
-
*/
|
|
1840
|
-
function combined_return_statement(render_nodes, jsx_child) {
|
|
1841
|
-
return b.return(combine_render_return_argument(render_nodes, jsx_child));
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
/**
|
|
1845
|
-
* Hoist a for-of iteration source into a generated `let` and add a
|
|
1846
|
-
* normalization assignment via `Array.isArray(src) ? src : Array.from(src)`.
|
|
1847
|
-
* Always emits both — even when the source is already a simple identifier —
|
|
1848
|
-
* so the loop-scoped TS type aliases have a stable name to reference and the
|
|
1849
|
-
* runtime check skips the copy when the value is already an array.
|
|
1850
|
-
*
|
|
1851
|
-
* @param {AST.Identifier} source_id
|
|
1852
|
-
* @param {any} source_expr
|
|
1853
|
-
* @returns {{ source_decl: any, source_normalize_decl: any }}
|
|
1854
|
-
*/
|
|
1855
|
-
function build_array_normalization_decls(source_id, source_expr) {
|
|
1856
|
-
const source_decl = b.let(clone_identifier(source_id), clone_expression_node(source_expr));
|
|
1857
|
-
const is_array_call = b.call(b.member(b.id('Array'), 'isArray'), clone_identifier(source_id));
|
|
1858
|
-
const from_call = b.call(b.member(b.id('Array'), 'from'), clone_identifier(source_id));
|
|
1859
|
-
const normalized = b.conditional(is_array_call, clone_identifier(source_id), from_call);
|
|
1860
|
-
const source_normalize_decl = b.stmt(b.assignment('=', clone_identifier(source_id), normalized));
|
|
1861
|
-
|
|
1862
|
-
return { source_decl, source_normalize_decl };
|
|
1827
|
+
return { source_decl, source_normalize_decl };
|
|
1863
1828
|
}
|
|
1864
1829
|
|
|
1865
1830
|
/**
|
|
@@ -1907,256 +1872,6 @@ function create_component_helper_split_returning_if_statements(
|
|
|
1907
1872
|
];
|
|
1908
1873
|
}
|
|
1909
1874
|
|
|
1910
|
-
/**
|
|
1911
|
-
* Lift a non-returning `if` whose consequent contains hook calls plus the
|
|
1912
|
-
* statements that follow it into helper components.
|
|
1913
|
-
*
|
|
1914
|
-
* Without this, the consequent's hook would be wrapped into a child component
|
|
1915
|
-
* (StatementBodyHook) but any code after the `if` that reads bindings the hook
|
|
1916
|
-
* mutates would observe the pre-hook value, because React commits children
|
|
1917
|
-
* after their parent has finished rendering. The fix mirrors the early-return
|
|
1918
|
-
* splitter: emit a tail helper that owns the post-`if` statements, append a
|
|
1919
|
-
* call to it inside the branch helper so the post-hook bindings flow forward,
|
|
1920
|
-
* and render the tail helper directly when the `if` is false.
|
|
1921
|
-
*
|
|
1922
|
-
* @param {any} if_node
|
|
1923
|
-
* @param {any[]} continuation_body
|
|
1924
|
-
* @param {any[]} render_nodes
|
|
1925
|
-
* @param {TransformContext} transform_context
|
|
1926
|
-
* @returns {any[]}
|
|
1927
|
-
*/
|
|
1928
|
-
function create_continuation_lift_if_statement(
|
|
1929
|
-
if_node,
|
|
1930
|
-
continuation_body,
|
|
1931
|
-
render_nodes,
|
|
1932
|
-
transform_context,
|
|
1933
|
-
) {
|
|
1934
|
-
const consequent_body = get_if_consequent_body(if_node);
|
|
1935
|
-
const tail_helper = build_tail_helper(continuation_body, if_node, transform_context);
|
|
1936
|
-
const branch_helper = create_hook_safe_helper(
|
|
1937
|
-
append_tail_invocation(consequent_body, tail_helper),
|
|
1938
|
-
undefined,
|
|
1939
|
-
if_node.consequent,
|
|
1940
|
-
transform_context,
|
|
1941
|
-
);
|
|
1942
|
-
|
|
1943
|
-
const branch_block = set_loc(
|
|
1944
|
-
b.block([
|
|
1945
|
-
...branch_helper.setup_statements,
|
|
1946
|
-
combined_return_statement(render_nodes, branch_helper.component_element),
|
|
1947
|
-
]),
|
|
1948
|
-
if_node.consequent,
|
|
1949
|
-
);
|
|
1950
|
-
|
|
1951
|
-
return [
|
|
1952
|
-
...tail_helper.setup_statements,
|
|
1953
|
-
set_loc(b.if(if_node.test, branch_block, null), if_node),
|
|
1954
|
-
combined_return_statement(render_nodes, tail_helper.component_element),
|
|
1955
|
-
];
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
/**
|
|
1959
|
-
* Continuation lift for `try` / `try / pending / catch` statements. Same
|
|
1960
|
-
* shape as if/switch: build a tail helper from the post-`try` statements, and
|
|
1961
|
-
* append a clone of its invocation to the try body and the catch body so the
|
|
1962
|
-
* post-hook locals inside each branch flow forward into the tail. The pending
|
|
1963
|
-
* body is left untouched — when Suspense renders the pending fallback the
|
|
1964
|
-
* parent's render is unwound, so the tail wouldn't run in source semantics
|
|
1965
|
-
* either. Once augmented, the existing try transform builds the
|
|
1966
|
-
* Suspense / TsrxErrorBoundary wrapper as usual.
|
|
1967
|
-
*
|
|
1968
|
-
* @param {any} node - TryStatement
|
|
1969
|
-
* @param {any[]} continuation_body
|
|
1970
|
-
* @param {any[]} render_nodes
|
|
1971
|
-
* @param {TransformContext} transform_context
|
|
1972
|
-
* @returns {any[]}
|
|
1973
|
-
*/
|
|
1974
|
-
function create_continuation_lift_try_statement(
|
|
1975
|
-
node,
|
|
1976
|
-
continuation_body,
|
|
1977
|
-
render_nodes,
|
|
1978
|
-
transform_context,
|
|
1979
|
-
) {
|
|
1980
|
-
const tail_helper = build_tail_helper(continuation_body, node, transform_context);
|
|
1981
|
-
|
|
1982
|
-
const augmented_block = {
|
|
1983
|
-
...node.block,
|
|
1984
|
-
body: append_tail_invocation(node.block.body || [], tail_helper),
|
|
1985
|
-
};
|
|
1986
|
-
|
|
1987
|
-
let augmented_handler = node.handler;
|
|
1988
|
-
if (node.handler) {
|
|
1989
|
-
augmented_handler = {
|
|
1990
|
-
...node.handler,
|
|
1991
|
-
body: {
|
|
1992
|
-
...node.handler.body,
|
|
1993
|
-
body: append_tail_invocation(node.handler.body.body || [], tail_helper),
|
|
1994
|
-
},
|
|
1995
|
-
};
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
const augmented_try = {
|
|
1999
|
-
...node,
|
|
2000
|
-
block: augmented_block,
|
|
2001
|
-
handler: augmented_handler,
|
|
2002
|
-
};
|
|
2003
|
-
|
|
2004
|
-
const try_jsx_child = (
|
|
2005
|
-
transform_context.platform.hooks?.controlFlow?.tryStatement ?? try_statement_to_jsx_child
|
|
2006
|
-
)(augmented_try, transform_context);
|
|
2007
|
-
|
|
2008
|
-
return [...tail_helper.setup_statements, combined_return_statement(render_nodes, try_jsx_child)];
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
/**
|
|
2012
|
-
* @param {any} node - TryStatement
|
|
2013
|
-
* @param {TransformContext} transform_context
|
|
2014
|
-
* @returns {boolean}
|
|
2015
|
-
*/
|
|
2016
|
-
function try_statement_contains_hooks(node, transform_context) {
|
|
2017
|
-
if (body_contains_top_level_hook_call(node.block?.body || [], transform_context, true)) {
|
|
2018
|
-
return true;
|
|
2019
|
-
}
|
|
2020
|
-
if (
|
|
2021
|
-
node.handler &&
|
|
2022
|
-
body_contains_top_level_hook_call(node.handler.body?.body || [], transform_context, true)
|
|
2023
|
-
) {
|
|
2024
|
-
return true;
|
|
2025
|
-
}
|
|
2026
|
-
if (
|
|
2027
|
-
node.pending &&
|
|
2028
|
-
body_contains_top_level_hook_call(node.pending.body || [], transform_context, true)
|
|
2029
|
-
) {
|
|
2030
|
-
return true;
|
|
2031
|
-
}
|
|
2032
|
-
return false;
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
/**
|
|
2036
|
-
* Continuation lift for `switch` statements. Same shape as the if-version:
|
|
2037
|
-
* each case body is wrapped in its own helper component that ends with a
|
|
2038
|
-
* call to a shared tail helper, so post-hook bindings inside any case flow
|
|
2039
|
-
* forward to the statements after the switch. The fall-through return at
|
|
2040
|
-
* the end renders the tail helper directly, covering the case where no
|
|
2041
|
-
* `case` (and no `default`) matched.
|
|
2042
|
-
*
|
|
2043
|
-
* Empty fall-through cases (`case 'a':` with no body, falling through to
|
|
2044
|
-
* the next case) are preserved as-is — they must not get their own helper
|
|
2045
|
-
* because that would convert fall-through into early-return.
|
|
2046
|
-
*
|
|
2047
|
-
* @param {any} switch_node
|
|
2048
|
-
* @param {any[]} continuation_body
|
|
2049
|
-
* @param {any[]} render_nodes
|
|
2050
|
-
* @param {TransformContext} transform_context
|
|
2051
|
-
* @returns {any[]}
|
|
2052
|
-
*/
|
|
2053
|
-
function create_continuation_lift_switch_statement(
|
|
2054
|
-
switch_node,
|
|
2055
|
-
continuation_body,
|
|
2056
|
-
render_nodes,
|
|
2057
|
-
transform_context,
|
|
2058
|
-
) {
|
|
2059
|
-
const tail_helper = build_tail_helper(continuation_body, switch_node, transform_context);
|
|
2060
|
-
|
|
2061
|
-
// Per-case info computed once: own body (statements before any
|
|
2062
|
-
// terminator) and whether the case has a `break` / `return`.
|
|
2063
|
-
const case_info = switch_node.cases.map((/** @type {any} */ c) => {
|
|
2064
|
-
const consequent = flatten_switch_consequent(c.consequent || []);
|
|
2065
|
-
const own_body = [];
|
|
2066
|
-
let own_has_terminator = false;
|
|
2067
|
-
for (const node of consequent) {
|
|
2068
|
-
if (node.type === 'BreakStatement' || node.type === 'ReturnStatement') {
|
|
2069
|
-
own_has_terminator = true;
|
|
2070
|
-
break;
|
|
2071
|
-
}
|
|
2072
|
-
own_body.push(node);
|
|
2073
|
-
}
|
|
2074
|
-
return { own_body, own_has_terminator };
|
|
2075
|
-
});
|
|
2076
|
-
|
|
2077
|
-
// Allocate helper ids in source order (forward pass) so the snapshot's
|
|
2078
|
-
// `StatementBodyHook<N>` numbering reads top-to-bottom by case position.
|
|
2079
|
-
/** @type {Array<AST.Identifier | null>} */
|
|
2080
|
-
const helper_ids = case_info.map(
|
|
2081
|
-
(/** @type {{ own_body: any[], own_has_terminator: boolean }} */ info) =>
|
|
2082
|
-
info.own_body.length === 0
|
|
2083
|
-
? null
|
|
2084
|
-
: create_generated_identifier(create_local_statement_component_name(transform_context)),
|
|
2085
|
-
);
|
|
2086
|
-
|
|
2087
|
-
// Build helpers in reverse order: each fall-through case's helper body
|
|
2088
|
-
// invokes the *next* case's helper, so the chain forwards post-mutation
|
|
2089
|
-
// locals through the switch. Reverse iteration ensures the next helper's
|
|
2090
|
-
// component_element is already constructed when we need to embed it.
|
|
2091
|
-
/** @type {Array<{ setup_statements: any[], component_element: any } | null>} */
|
|
2092
|
-
const case_helper_by_index = new Array(switch_node.cases.length).fill(null);
|
|
2093
|
-
for (let i = switch_node.cases.length - 1; i >= 0; i--) {
|
|
2094
|
-
const { own_body, own_has_terminator } = case_info[i];
|
|
2095
|
-
if (own_body.length === 0) continue;
|
|
2096
|
-
|
|
2097
|
-
// Determine the downstream helper this case invokes after its own body.
|
|
2098
|
-
// - With a terminator: invoke the tail helper directly (case exits switch).
|
|
2099
|
-
// - Otherwise (fall-through): invoke the next non-empty case's helper,
|
|
2100
|
-
// or the tail if nothing else follows.
|
|
2101
|
-
let downstream;
|
|
2102
|
-
if (own_has_terminator) {
|
|
2103
|
-
downstream = tail_helper;
|
|
2104
|
-
} else {
|
|
2105
|
-
let next_helper = null;
|
|
2106
|
-
for (let j = i + 1; j < switch_node.cases.length; j++) {
|
|
2107
|
-
if (case_helper_by_index[j]) {
|
|
2108
|
-
next_helper = case_helper_by_index[j];
|
|
2109
|
-
break;
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
downstream = next_helper ?? tail_helper;
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
case_helper_by_index[i] = create_hook_safe_helper(
|
|
2116
|
-
append_tail_invocation(own_body, downstream),
|
|
2117
|
-
undefined,
|
|
2118
|
-
switch_node.cases[i],
|
|
2119
|
-
transform_context,
|
|
2120
|
-
/** @type {any} */ (helper_ids[i]),
|
|
2121
|
-
);
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
const new_cases = switch_node.cases.map(
|
|
2125
|
-
(/** @type {any} */ original_case, /** @type {number} */ i) => {
|
|
2126
|
-
const helper = case_helper_by_index[i];
|
|
2127
|
-
if (helper) {
|
|
2128
|
-
return b.switch_case(original_case.test, [
|
|
2129
|
-
combined_return_statement(render_nodes, helper.component_element),
|
|
2130
|
-
]);
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
const { own_body, own_has_terminator } = case_info[i];
|
|
2134
|
-
if (own_body.length === 0 && own_has_terminator) {
|
|
2135
|
-
// `case 'a': break;` — exits the switch, then runs the tail.
|
|
2136
|
-
return b.switch_case(original_case.test, [
|
|
2137
|
-
combined_return_statement(render_nodes, tail_helper.component_element),
|
|
2138
|
-
]);
|
|
2139
|
-
}
|
|
2140
|
-
// Genuine empty fall-through (`case 'a': case 'b': ...`).
|
|
2141
|
-
return b.switch_case(original_case.test, []);
|
|
2142
|
-
},
|
|
2143
|
-
);
|
|
2144
|
-
|
|
2145
|
-
// Hoist all case helpers' setup statements above the switch in source
|
|
2146
|
-
// order so the switch body is purely a dispatcher.
|
|
2147
|
-
const case_helper_setup_statements = [];
|
|
2148
|
-
for (const helper of case_helper_by_index) {
|
|
2149
|
-
if (helper) case_helper_setup_statements.push(...helper.setup_statements);
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
return [
|
|
2153
|
-
...tail_helper.setup_statements,
|
|
2154
|
-
...case_helper_setup_statements,
|
|
2155
|
-
set_loc(b.switch(switch_node.discriminant, new_cases), switch_node),
|
|
2156
|
-
combined_return_statement(render_nodes, tail_helper.component_element),
|
|
2157
|
-
];
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
1875
|
/**
|
|
2161
1876
|
* Hoist the helper for a hook-bearing for-of body out of the iteration
|
|
2162
1877
|
* callback so the helper is declared once per render rather than re-bound on
|
|
@@ -2169,77 +1884,52 @@ function create_continuation_lift_switch_statement(
|
|
|
2169
1884
|
* works while skipping the copy when the source is already an array. The
|
|
2170
1885
|
* iteration itself is emitted as `source.map((item, i) => ...)`.
|
|
2171
1886
|
*
|
|
2172
|
-
* If `continuation_body` is non-empty (the for-of has a tail) we also lift
|
|
2173
|
-
* the tail into a TailHelper and call it conditionally on the last iteration
|
|
2174
|
-
* via an `isLast={i === source.length - 1}` prop on the loop helper. The
|
|
2175
|
-
* loop helper's mutated locals (post-`useState`) flow into the TailHelper as
|
|
2176
|
-
* its props. When the source is empty, `.map` returns `[]` and the TailHelper
|
|
2177
|
-
* never renders — we add a sibling fallback so the source's tail still runs
|
|
2178
|
-
* with the original outer values in that case.
|
|
2179
|
-
*
|
|
2180
1887
|
* Bails out (returns null) when the loop pattern is destructured — deriving
|
|
2181
1888
|
* element types from a tuple/object pattern is more involved and deferred.
|
|
2182
1889
|
*
|
|
2183
1890
|
* @param {any} node - ForOfStatement
|
|
2184
|
-
* @param {any[]} continuation_body
|
|
2185
1891
|
* @param {TransformContext} transform_context
|
|
2186
1892
|
* @returns {{ hoist_statements: any[], jsx_child: any } | null}
|
|
2187
1893
|
*/
|
|
2188
|
-
function build_hoisted_for_of_with_hooks(node,
|
|
1894
|
+
function build_hoisted_for_of_with_hooks(node, transform_context) {
|
|
2189
1895
|
const loop_params = get_for_of_iteration_params(node.left, node.index);
|
|
2190
1896
|
for (const param of loop_params) {
|
|
2191
1897
|
if (param.type !== 'Identifier') return null;
|
|
2192
1898
|
}
|
|
2193
1899
|
|
|
2194
|
-
const has_tail = continuation_body.length > 0;
|
|
2195
1900
|
const original_loop_body = /** @type {any[]} */ (
|
|
2196
1901
|
rewrite_loop_continues_to_bare_returns(
|
|
2197
1902
|
node.body.type === 'BlockStatement' ? node.body.body : [node.body],
|
|
2198
1903
|
)
|
|
2199
1904
|
);
|
|
2200
1905
|
|
|
2201
|
-
// When there's a tail, build TailHelper first so its component_element can
|
|
2202
|
-
// be embedded inside the loop helper's body (gated on isLast). The
|
|
2203
|
-
// synthetic isLast prop uses the loop helper's index (which will be the
|
|
2204
|
-
// next one assigned, since `create_hook_safe_helper` for the tail just
|
|
2205
|
-
// consumed one) so it lines up with `StatementBodyHook<N>` in the output.
|
|
2206
|
-
let tail_helper = null;
|
|
2207
|
-
/** @type {AST.Identifier} */ let tail_synthetic_id;
|
|
2208
|
-
if (has_tail) {
|
|
2209
|
-
tail_helper = build_tail_helper(continuation_body, node, transform_context);
|
|
2210
|
-
tail_synthetic_id = create_generated_identifier(
|
|
2211
|
-
`_tsrx_isLast_${transform_context.local_statement_component_index + 1}`,
|
|
2212
|
-
);
|
|
2213
|
-
} else {
|
|
2214
|
-
tail_synthetic_id = /** @type {any} */ (null);
|
|
2215
|
-
}
|
|
2216
|
-
const loop_tail_expression = has_tail
|
|
2217
|
-
? create_loop_tail_expression(tail_synthetic_id, /** @type {any} */ (tail_helper))
|
|
2218
|
-
: null;
|
|
2219
|
-
const loop_body =
|
|
2220
|
-
has_tail && loop_tail_expression
|
|
2221
|
-
? [...original_loop_body, b.jsx_expression_container(loop_tail_expression)]
|
|
2222
|
-
: original_loop_body;
|
|
2223
|
-
|
|
2224
1906
|
const source_id = create_generated_identifier(
|
|
2225
1907
|
`_tsrx_iteration_items_${transform_context.local_statement_component_index + 1}`,
|
|
2226
1908
|
);
|
|
2227
|
-
const
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
1909
|
+
const use_iterable_helper = !!transform_context.platform.imports.forOfIterableHelper;
|
|
1910
|
+
const { source_decl, source_normalize_decl } = use_iterable_helper
|
|
1911
|
+
? {
|
|
1912
|
+
source_decl: b.let(clone_identifier(source_id), clone_expression_node(node.right)),
|
|
1913
|
+
source_normalize_decl: null,
|
|
1914
|
+
}
|
|
1915
|
+
: build_array_normalization_decls(source_id, node.right);
|
|
2231
1916
|
|
|
2232
1917
|
const saved_bindings = transform_context.available_bindings;
|
|
2233
1918
|
transform_context.available_bindings = new Map(saved_bindings);
|
|
1919
|
+
const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
|
|
2234
1920
|
for (const param of loop_params) {
|
|
2235
1921
|
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
2236
1922
|
}
|
|
1923
|
+
validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
|
|
1924
|
+
original_loop_body,
|
|
1925
|
+
transform_context,
|
|
1926
|
+
loop_scoped_names,
|
|
1927
|
+
);
|
|
2237
1928
|
|
|
2238
1929
|
const all_helper_bindings = get_referenced_helper_bindings(
|
|
2239
|
-
|
|
1930
|
+
original_loop_body,
|
|
2240
1931
|
transform_context.available_bindings,
|
|
2241
1932
|
);
|
|
2242
|
-
const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
|
|
2243
1933
|
const outer_bindings = all_helper_bindings.filter((b) => !loop_scoped_names.has(b.name));
|
|
2244
1934
|
const loop_bindings = all_helper_bindings.filter((b) => loop_scoped_names.has(b.name));
|
|
2245
1935
|
|
|
@@ -2257,69 +1947,42 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2257
1947
|
const loop_aliases = use_module_scoped_component
|
|
2258
1948
|
? []
|
|
2259
1949
|
: loop_bindings.map((binding) =>
|
|
2260
|
-
create_loop_scoped_type_alias_declaration(
|
|
1950
|
+
create_loop_scoped_type_alias_declaration(
|
|
1951
|
+
helper_id,
|
|
1952
|
+
binding,
|
|
1953
|
+
source_id,
|
|
1954
|
+
loop_params,
|
|
1955
|
+
transform_context,
|
|
1956
|
+
),
|
|
2261
1957
|
);
|
|
2262
1958
|
|
|
2263
|
-
// Synthetic `isLast` prop on the loop helper when there's a tail. It's
|
|
2264
|
-
// passed from the .map callback as `i === source.length - 1` so every
|
|
2265
|
-
// loop-helper return can append the tail helper on the last iteration.
|
|
2266
|
-
const tail_isLast_alias = has_tail
|
|
2267
|
-
? use_module_scoped_component
|
|
2268
|
-
? null
|
|
2269
|
-
: {
|
|
2270
|
-
id: create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
|
|
2271
|
-
declaration: b.ts_type_alias(
|
|
2272
|
-
create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
|
|
2273
|
-
b.ts_keyword_type('boolean'),
|
|
2274
|
-
),
|
|
2275
|
-
}
|
|
2276
|
-
: null;
|
|
2277
|
-
|
|
2278
1959
|
const ordered_bindings = [...outer_bindings, ...loop_bindings];
|
|
2279
1960
|
const ordered_aliases = [...outer_aliases, ...loop_aliases];
|
|
2280
1961
|
const ordered_use_typeof = [...outer_bindings.map(() => true), ...loop_bindings.map(() => false)];
|
|
2281
1962
|
|
|
2282
|
-
const signature_bindings = has_tail ? [...ordered_bindings, tail_synthetic_id] : ordered_bindings;
|
|
2283
|
-
const signature_aliases = has_tail
|
|
2284
|
-
? [...ordered_aliases, /** @type {any} */ (tail_isLast_alias)]
|
|
2285
|
-
: ordered_aliases;
|
|
2286
|
-
const signature_use_typeof = has_tail ? [...ordered_use_typeof, false] : ordered_use_typeof;
|
|
2287
|
-
|
|
2288
1963
|
const props_type =
|
|
2289
|
-
|
|
1964
|
+
ordered_bindings.length > 0 && !use_module_scoped_component
|
|
2290
1965
|
? create_helper_props_type_literal_with_typeof_flags(
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
1966
|
+
ordered_bindings,
|
|
1967
|
+
ordered_aliases,
|
|
1968
|
+
ordered_use_typeof,
|
|
2294
1969
|
)
|
|
2295
1970
|
: null;
|
|
2296
1971
|
const params =
|
|
2297
|
-
|
|
1972
|
+
ordered_bindings.length > 0
|
|
2298
1973
|
? [
|
|
2299
1974
|
props_type !== null
|
|
2300
|
-
? create_typed_helper_props_pattern(
|
|
2301
|
-
: create_helper_props_pattern(
|
|
1975
|
+
? create_typed_helper_props_pattern(ordered_bindings, props_type)
|
|
1976
|
+
: create_helper_props_pattern(ordered_bindings),
|
|
2302
1977
|
]
|
|
2303
1978
|
: [];
|
|
2304
1979
|
|
|
2305
1980
|
const fn_saved_bindings = transform_context.available_bindings;
|
|
2306
1981
|
transform_context.available_bindings = new Map(fn_saved_bindings);
|
|
2307
|
-
|
|
2308
|
-
transform_context.available_bindings.set(tail_synthetic_id.name, tail_synthetic_id);
|
|
2309
|
-
}
|
|
2310
|
-
const fn_body_statements = build_render_statements(loop_body, true, transform_context);
|
|
2311
|
-
if (has_tail) {
|
|
2312
|
-
append_loop_tail_to_return_statements(
|
|
2313
|
-
fn_body_statements,
|
|
2314
|
-
tail_synthetic_id,
|
|
2315
|
-
/** @type {any} */ (tail_helper),
|
|
2316
|
-
);
|
|
2317
|
-
}
|
|
1982
|
+
const fn_body_statements = build_render_statements(original_loop_body, true, transform_context);
|
|
2318
1983
|
transform_context.available_bindings = fn_saved_bindings;
|
|
2319
1984
|
|
|
2320
|
-
const helper_fn =
|
|
2321
|
-
b.function(clone_identifier(component_id), params, b.block(fn_body_statements))
|
|
2322
|
-
);
|
|
1985
|
+
const helper_fn = b.function(clone_identifier(component_id), params, b.block(fn_body_statements));
|
|
2323
1986
|
helper_fn.metadata = { path: [], is_component: true, is_method: false };
|
|
2324
1987
|
|
|
2325
1988
|
let helper_decl;
|
|
@@ -2351,18 +2014,6 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2351
2014
|
{ mapWrapper: false, mapBindingNames: false, mapBindingValues: false },
|
|
2352
2015
|
);
|
|
2353
2016
|
|
|
2354
|
-
// When there's a tail, the .map callback always needs an index to compute
|
|
2355
|
-
// `isLast`. If the user didn't write `index i`, synthesize one. The same
|
|
2356
|
-
// identifier is also used as the implicit key fallback below.
|
|
2357
|
-
let index_identifier;
|
|
2358
|
-
if (loop_params.length >= 2) {
|
|
2359
|
-
index_identifier = clone_identifier(loop_params[1]);
|
|
2360
|
-
} else if (has_tail) {
|
|
2361
|
-
index_identifier = create_generated_identifier('i');
|
|
2362
|
-
} else {
|
|
2363
|
-
index_identifier = null;
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
2017
|
const body_key_expression = find_key_expression_in_body(original_loop_body);
|
|
2367
2018
|
const explicit_key_expression =
|
|
2368
2019
|
body_key_expression ?? (node.key ? clone_expression_node(node.key) : undefined);
|
|
@@ -2375,57 +2026,24 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2375
2026
|
);
|
|
2376
2027
|
}
|
|
2377
2028
|
|
|
2378
|
-
|
|
2379
|
-
const length_minus_one = b.binary(
|
|
2380
|
-
'-',
|
|
2381
|
-
b.member(clone_identifier(source_id), 'length'),
|
|
2382
|
-
b.literal(1),
|
|
2383
|
-
);
|
|
2384
|
-
callback_invocation_element.openingElement.attributes.push(
|
|
2385
|
-
b.jsx_attribute(
|
|
2386
|
-
b.jsx_id(tail_synthetic_id.name),
|
|
2387
|
-
to_jsx_expression_container(
|
|
2388
|
-
b.binary('===', clone_identifier(index_identifier), length_minus_one),
|
|
2389
|
-
),
|
|
2390
|
-
),
|
|
2391
|
-
);
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
const callback_params =
|
|
2395
|
-
has_tail && loop_params.length < 2 && index_identifier
|
|
2396
|
-
? [
|
|
2397
|
-
...loop_params.map((/** @type {any} */ p) => clone_identifier(p)),
|
|
2398
|
-
clone_identifier(index_identifier),
|
|
2399
|
-
]
|
|
2400
|
-
: loop_params.map((/** @type {any} */ p) => clone_identifier(p));
|
|
2029
|
+
const callback_params = loop_params.map((/** @type {any} */ p) => clone_identifier(p));
|
|
2401
2030
|
|
|
2402
2031
|
const iter_callback = b.arrow(callback_params, callback_invocation_element);
|
|
2403
2032
|
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
? to_jsx_expression_container(
|
|
2411
|
-
b.conditional(
|
|
2412
|
-
b.binary('===', b.member(clone_identifier(source_id), 'length'), b.literal(0)),
|
|
2413
|
-
clone_tail_invocation(/** @type {any} */ (tail_helper)),
|
|
2414
|
-
map_call,
|
|
2415
|
-
),
|
|
2416
|
-
node,
|
|
2417
|
-
)
|
|
2418
|
-
: to_jsx_expression_container(map_call, node);
|
|
2419
|
-
|
|
2420
|
-
const hoist_statements = [source_decl, source_normalize_decl];
|
|
2421
|
-
if (has_tail) {
|
|
2422
|
-
// TailHelper's setup statements (its alias consts and cache decl).
|
|
2423
|
-
hoist_statements.push(.../** @type {any} */ (tail_helper).setup_statements);
|
|
2033
|
+
let map_call;
|
|
2034
|
+
if (use_iterable_helper) {
|
|
2035
|
+
transform_context.needs_for_of_iterable = true;
|
|
2036
|
+
map_call = b.call(b.id(MAP_ITERABLE_INTERNAL_NAME), clone_identifier(source_id), iter_callback);
|
|
2037
|
+
} else {
|
|
2038
|
+
map_call = b.call(b.member(clone_identifier(source_id), 'map'), iter_callback);
|
|
2424
2039
|
}
|
|
2040
|
+
|
|
2041
|
+
const jsx_child = to_jsx_expression_container(map_call, node);
|
|
2042
|
+
|
|
2043
|
+
const hoist_statements = source_normalize_decl
|
|
2044
|
+
? [source_decl, source_normalize_decl]
|
|
2045
|
+
: [source_decl];
|
|
2425
2046
|
for (const alias of ordered_aliases) hoist_statements.push(alias.declaration);
|
|
2426
|
-
if (has_tail && tail_isLast_alias) {
|
|
2427
|
-
hoist_statements.push(tail_isLast_alias.declaration);
|
|
2428
|
-
}
|
|
2429
2047
|
if (helper_decl) {
|
|
2430
2048
|
hoist_statements.push(helper_decl);
|
|
2431
2049
|
}
|
|
@@ -2438,28 +2056,49 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2438
2056
|
|
|
2439
2057
|
/**
|
|
2440
2058
|
* Build a TS `type` alias for a loop-scoped binding, deriving the type
|
|
2441
|
-
* from the iteration source. For the
|
|
2442
|
-
* `
|
|
2443
|
-
*
|
|
2444
|
-
*
|
|
2059
|
+
* from the iteration source. For the index param the type is always
|
|
2060
|
+
* `number`. For the value param the shape depends on whether the platform
|
|
2061
|
+
* uses the `map_iterable` runtime helper:
|
|
2062
|
+
*
|
|
2063
|
+
* - With the helper (React, Preact): `IterationValue<typeof source>` — any
|
|
2064
|
+
* `Iterable<T>` is accepted, so the element type is derived through the
|
|
2065
|
+
* runtime's exported helper type.
|
|
2066
|
+
* - Without the helper: `(typeof source)[number]` — arrays/tuples only,
|
|
2067
|
+
* matching the inline `.map()` lowering.
|
|
2445
2068
|
*
|
|
2446
2069
|
* @param {AST.Identifier} helper_id
|
|
2447
2070
|
* @param {AST.Identifier} binding
|
|
2448
2071
|
* @param {AST.Identifier} source_id
|
|
2449
2072
|
* @param {any[]} loop_params
|
|
2073
|
+
* @param {TransformContext} transform_context
|
|
2450
2074
|
* @returns {{ id: AST.Identifier, declaration: any }}
|
|
2451
2075
|
*/
|
|
2452
|
-
function create_loop_scoped_type_alias_declaration(
|
|
2453
|
-
|
|
2076
|
+
function create_loop_scoped_type_alias_declaration(
|
|
2077
|
+
helper_id,
|
|
2078
|
+
binding,
|
|
2079
|
+
source_id,
|
|
2080
|
+
loop_params,
|
|
2081
|
+
transform_context,
|
|
2082
|
+
) {
|
|
2083
|
+
const alias_id = create_generated_identifier(`_tsrx_${helper_id.name}_${binding.name}`);
|
|
2454
2084
|
const is_index = loop_params.length > 1 && binding.name === loop_params[1].name;
|
|
2085
|
+
const use_iterable_helper = !!transform_context.platform.imports.forOfIterableHelper;
|
|
2455
2086
|
const type_annotation = is_index
|
|
2456
2087
|
? b.ts_keyword_type('number')
|
|
2457
|
-
:
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2088
|
+
: use_iterable_helper
|
|
2089
|
+
? (() => {
|
|
2090
|
+
transform_context.needs_iteration_value_type = true;
|
|
2091
|
+
return b.ts_type_reference(
|
|
2092
|
+
b.id(ITERATION_VALUE_INTERNAL_NAME),
|
|
2093
|
+
b.ts_type_parameter_instantiation([b.ts_type_query(clone_identifier(source_id))]),
|
|
2094
|
+
);
|
|
2095
|
+
})()
|
|
2096
|
+
: /** @type {any} */ ({
|
|
2097
|
+
type: 'TSIndexedAccessType',
|
|
2098
|
+
objectType: b.ts_type_query(clone_identifier(source_id)),
|
|
2099
|
+
indexType: b.ts_keyword_type('number'),
|
|
2100
|
+
metadata: { path: [] },
|
|
2101
|
+
});
|
|
2463
2102
|
|
|
2464
2103
|
return {
|
|
2465
2104
|
id: alias_id,
|
|
@@ -2519,23 +2158,19 @@ function create_setup_once_helper_split_returning_if_statements(
|
|
|
2519
2158
|
return [
|
|
2520
2159
|
...branch_helper.setup_statements,
|
|
2521
2160
|
...continuation_helper.setup_statements,
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
argument: combine_render_return_argument(
|
|
2161
|
+
b.return(
|
|
2162
|
+
combine_render_return_argument(
|
|
2525
2163
|
render_nodes,
|
|
2526
2164
|
set_loc(
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
metadata: { path: [] },
|
|
2533
|
-
}),
|
|
2165
|
+
b.conditional(
|
|
2166
|
+
node.test,
|
|
2167
|
+
branch_helper.component_element,
|
|
2168
|
+
continuation_helper.component_element,
|
|
2169
|
+
),
|
|
2534
2170
|
node,
|
|
2535
2171
|
),
|
|
2536
2172
|
),
|
|
2537
|
-
|
|
2538
|
-
},
|
|
2173
|
+
),
|
|
2539
2174
|
];
|
|
2540
2175
|
}
|
|
2541
2176
|
|
|
@@ -2632,9 +2267,6 @@ function is_null_literal(node) {
|
|
|
2632
2267
|
return node?.type === 'Literal' && node.value == null;
|
|
2633
2268
|
}
|
|
2634
2269
|
|
|
2635
|
-
const TEMPLATE_FRAGMENT_ERROR =
|
|
2636
|
-
'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
|
|
2637
|
-
|
|
2638
2270
|
/**
|
|
2639
2271
|
* @param {any} node
|
|
2640
2272
|
* @param {TransformContext} transform_context
|
|
@@ -2643,13 +2275,7 @@ const TEMPLATE_FRAGMENT_ERROR =
|
|
|
2643
2275
|
function to_jsx_element(node, transform_context, raw_children = node.children || []) {
|
|
2644
2276
|
if (node.type === 'JSXElement') return node;
|
|
2645
2277
|
if (!node.id) {
|
|
2646
|
-
|
|
2647
|
-
TEMPLATE_FRAGMENT_ERROR,
|
|
2648
|
-
transform_context.filename,
|
|
2649
|
-
node,
|
|
2650
|
-
transform_context.errors,
|
|
2651
|
-
transform_context.comments,
|
|
2652
|
-
);
|
|
2278
|
+
report_jsx_fragment_in_tsrx_error(node, transform_context);
|
|
2653
2279
|
return set_loc(
|
|
2654
2280
|
/** @type {any} */ ({
|
|
2655
2281
|
type: 'JSXFragment',
|
|
@@ -2688,9 +2314,7 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
|
|
|
2688
2314
|
}
|
|
2689
2315
|
} else {
|
|
2690
2316
|
if (walked_children.some((/** @type {any} */ c) => c && c.type === 'Html')) {
|
|
2691
|
-
|
|
2692
|
-
`\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
|
|
2693
|
-
);
|
|
2317
|
+
return report_html_template_unsupported_error(node, transform_context);
|
|
2694
2318
|
}
|
|
2695
2319
|
children = create_element_children(walked_children, transform_context);
|
|
2696
2320
|
}
|
|
@@ -2751,27 +2375,639 @@ function create_element_children(children, transform_context) {
|
|
|
2751
2375
|
}
|
|
2752
2376
|
|
|
2753
2377
|
/**
|
|
2754
|
-
* @param {any[]} children
|
|
2378
|
+
* @param {any[]} children
|
|
2379
|
+
* @returns {boolean}
|
|
2380
|
+
*/
|
|
2381
|
+
function children_contain_return_semantics(children) {
|
|
2382
|
+
return children.some(child_contains_return_semantics);
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
/**
|
|
2386
|
+
* @param {any} node
|
|
2387
|
+
* @returns {boolean}
|
|
2388
|
+
*/
|
|
2389
|
+
function child_contains_return_semantics(node) {
|
|
2390
|
+
if (!node || typeof node !== 'object') {
|
|
2391
|
+
return false;
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
if (
|
|
2395
|
+
(node.type === 'ReturnStatement' && node.argument == null) ||
|
|
2396
|
+
is_lone_return_if_statement(node)
|
|
2397
|
+
) {
|
|
2398
|
+
return true;
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
if (
|
|
2402
|
+
node.type === 'FunctionDeclaration' ||
|
|
2403
|
+
node.type === 'FunctionExpression' ||
|
|
2404
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
2405
|
+
node.type === 'Component'
|
|
2406
|
+
) {
|
|
2407
|
+
return false;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
if (Array.isArray(node)) {
|
|
2411
|
+
return node.some(child_contains_return_semantics);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
for (const key of Object.keys(node)) {
|
|
2415
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
2416
|
+
continue;
|
|
2417
|
+
}
|
|
2418
|
+
if (child_contains_return_semantics(node[key])) {
|
|
2419
|
+
return true;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
return false;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
/**
|
|
2427
|
+
* @param {any} node
|
|
2428
|
+
* @returns {boolean}
|
|
2429
|
+
*/
|
|
2430
|
+
function is_inline_element_child(node) {
|
|
2431
|
+
return node && is_jsx_child(node);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
/**
|
|
2435
|
+
* @param {any[]} body_nodes
|
|
2436
|
+
* @param {TransformContext} transform_context
|
|
2437
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
2438
|
+
*/
|
|
2439
|
+
function statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
2440
|
+
if (body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
|
|
2441
|
+
return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
return to_jsx_expression_container(
|
|
2445
|
+
b.call(b.arrow([], b.block(build_render_statements(body_nodes, true, transform_context)))),
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
/**
|
|
2450
|
+
* @param {any[]} body_nodes
|
|
2451
|
+
* @param {TransformContext} transform_context
|
|
2452
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
2453
|
+
*/
|
|
2454
|
+
function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
2455
|
+
const source_node = get_body_source_node(body_nodes);
|
|
2456
|
+
const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
|
|
2457
|
+
|
|
2458
|
+
return to_jsx_expression_container(
|
|
2459
|
+
create_hook_safe_helper_iife(helper.setup_statements, helper.component_element),
|
|
2460
|
+
source_node,
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
/**
|
|
2465
|
+
* @param {TransformContext} transform_context
|
|
2466
|
+
* @returns {string}
|
|
2467
|
+
*/
|
|
2468
|
+
function create_local_statement_component_name(transform_context) {
|
|
2469
|
+
transform_context.local_statement_component_index += 1;
|
|
2470
|
+
return `StatementBodyHook${transform_context.local_statement_component_index}`;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
/**
|
|
2474
|
+
* Wraps a list of body nodes into a component and returns
|
|
2475
|
+
* statements that return `<ComponentName prop1={prop1} ... />`.
|
|
2476
|
+
* Targets can either emit the helper component at module scope or cache the
|
|
2477
|
+
* component identity in module state while initializing it from the parent.
|
|
2478
|
+
* Used when a control flow branch contains hook calls that must be moved
|
|
2479
|
+
* into their own component boundary to satisfy the Rules of Hooks.
|
|
2480
|
+
*
|
|
2481
|
+
* @param {any[]} body_nodes
|
|
2482
|
+
* @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
|
|
2483
|
+
* @param {TransformContext} transform_context
|
|
2484
|
+
* @returns {any[]}
|
|
2485
|
+
*/
|
|
2486
|
+
function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
|
|
2487
|
+
const source_node = get_body_source_node(body_nodes);
|
|
2488
|
+
const helper = create_hook_safe_helper(
|
|
2489
|
+
body_nodes,
|
|
2490
|
+
key_expression,
|
|
2491
|
+
source_node,
|
|
2492
|
+
transform_context,
|
|
2493
|
+
);
|
|
2494
|
+
const statements = [...helper.setup_statements];
|
|
2495
|
+
|
|
2496
|
+
statements.push(b.return(helper.component_element));
|
|
2497
|
+
|
|
2498
|
+
return statements;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
/**
|
|
2502
|
+
* @param {any[]} body_nodes
|
|
2503
|
+
* @param {Map<string, AST.Identifier>} available_bindings
|
|
2504
|
+
* @returns {AST.Identifier[]}
|
|
2505
|
+
*/
|
|
2506
|
+
function get_referenced_helper_bindings(body_nodes, available_bindings) {
|
|
2507
|
+
const helper_bindings = [];
|
|
2508
|
+
const local_bindings = new Map();
|
|
2509
|
+
|
|
2510
|
+
for (const node of body_nodes) {
|
|
2511
|
+
collect_statement_bindings(node, local_bindings);
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
for (const [name, binding] of available_bindings) {
|
|
2515
|
+
if (local_bindings.has(name)) continue;
|
|
2516
|
+
|
|
2517
|
+
if (references_scope_bindings(body_nodes, new Map([[name, binding]]))) {
|
|
2518
|
+
helper_bindings.push(binding);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
return helper_bindings;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
/**
|
|
2526
|
+
* @param {any[]} body_nodes
|
|
2527
|
+
* @param {TransformContext} transform_context
|
|
2528
|
+
* @param {Set<string>} [local_binding_names]
|
|
2529
|
+
* @returns {void}
|
|
2530
|
+
*/
|
|
2531
|
+
function validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
|
|
2532
|
+
body_nodes,
|
|
2533
|
+
transform_context,
|
|
2534
|
+
local_binding_names,
|
|
2535
|
+
) {
|
|
2536
|
+
if (!is_react_like_hook_platform(transform_context)) {
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
if (!body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
if (!transform_context.available_bindings || transform_context.available_bindings.size === 0) {
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
const shadowed_names = collect_block_binding_names(body_nodes);
|
|
2547
|
+
for (const name of local_binding_names || []) {
|
|
2548
|
+
shadowed_names.add(name);
|
|
2549
|
+
}
|
|
2550
|
+
validate_hook_outer_assignments_in_node(body_nodes, shadowed_names, transform_context, new Set());
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
/**
|
|
2554
|
+
* @param {TransformContext} transform_context
|
|
2555
|
+
* @returns {boolean}
|
|
2556
|
+
*/
|
|
2557
|
+
function is_react_like_hook_platform(transform_context) {
|
|
2558
|
+
return (
|
|
2559
|
+
transform_context.platform.name === 'React' || transform_context.platform.name === 'Preact'
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
/**
|
|
2564
|
+
* @param {any[]} statements
|
|
2565
|
+
* @returns {Set<string>}
|
|
2566
|
+
*/
|
|
2567
|
+
function collect_block_binding_names(statements) {
|
|
2568
|
+
const names = new Set();
|
|
2569
|
+
for (const statement of statements || []) {
|
|
2570
|
+
collect_block_binding_names_from_statement(statement, names);
|
|
2571
|
+
}
|
|
2572
|
+
return names;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
/**
|
|
2576
|
+
* @param {any} statement
|
|
2577
|
+
* @param {Set<string>} names
|
|
2578
|
+
* @returns {void}
|
|
2579
|
+
*/
|
|
2580
|
+
function collect_block_binding_names_from_statement(statement, names) {
|
|
2581
|
+
if (!statement || typeof statement !== 'object') {
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
if (statement.type === 'VariableDeclaration') {
|
|
2586
|
+
for (const declaration of statement.declarations || []) {
|
|
2587
|
+
collect_pattern_names(declaration.id, names);
|
|
2588
|
+
}
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
if (
|
|
2593
|
+
(statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
|
|
2594
|
+
statement.id?.type === 'Identifier'
|
|
2595
|
+
) {
|
|
2596
|
+
names.add(statement.id.name);
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
if (statement.type === 'ForOfStatement' || statement.type === 'ForInStatement') {
|
|
2601
|
+
if (statement.left?.type === 'VariableDeclaration' && statement.left.kind === 'var') {
|
|
2602
|
+
for (const declaration of statement.left.declarations || []) {
|
|
2603
|
+
collect_pattern_names(declaration.id, names);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
if (
|
|
2610
|
+
statement.type === 'ForStatement' &&
|
|
2611
|
+
statement.init?.type === 'VariableDeclaration' &&
|
|
2612
|
+
statement.init.kind === 'var'
|
|
2613
|
+
) {
|
|
2614
|
+
for (const declaration of statement.init.declarations || []) {
|
|
2615
|
+
collect_pattern_names(declaration.id, names);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
/**
|
|
2621
|
+
* @param {any} pattern
|
|
2622
|
+
* @param {Set<string>} names
|
|
2623
|
+
* @returns {void}
|
|
2624
|
+
*/
|
|
2625
|
+
function collect_pattern_names(pattern, names) {
|
|
2626
|
+
if (!pattern || typeof pattern !== 'object') {
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
if (pattern.type === 'Identifier') {
|
|
2631
|
+
names.add(pattern.name);
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
if (pattern.type === 'RestElement') {
|
|
2636
|
+
collect_pattern_names(pattern.argument, names);
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
2641
|
+
collect_pattern_names(pattern.left, names);
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
if (pattern.type === 'ArrayPattern') {
|
|
2646
|
+
for (const element of pattern.elements || []) {
|
|
2647
|
+
collect_pattern_names(element, names);
|
|
2648
|
+
}
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
if (pattern.type === 'ObjectPattern') {
|
|
2653
|
+
for (const property of pattern.properties || []) {
|
|
2654
|
+
if (property.type === 'RestElement') {
|
|
2655
|
+
collect_pattern_names(property.argument, names);
|
|
2656
|
+
} else {
|
|
2657
|
+
collect_pattern_names(property.value, names);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
/**
|
|
2664
|
+
* @param {any} node
|
|
2665
|
+
* @param {Set<string>} shadowed_names
|
|
2666
|
+
* @param {TransformContext} transform_context
|
|
2667
|
+
* @param {Set<string>} hook_result_names
|
|
2668
|
+
* @returns {void}
|
|
2669
|
+
*/
|
|
2670
|
+
function validate_hook_outer_assignments_in_node(
|
|
2671
|
+
node,
|
|
2672
|
+
shadowed_names,
|
|
2673
|
+
transform_context,
|
|
2674
|
+
hook_result_names,
|
|
2675
|
+
) {
|
|
2676
|
+
if (!node || typeof node !== 'object') {
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
if (Array.isArray(node)) {
|
|
2681
|
+
for (const child of node) {
|
|
2682
|
+
validate_hook_outer_assignments_in_node(
|
|
2683
|
+
child,
|
|
2684
|
+
shadowed_names,
|
|
2685
|
+
transform_context,
|
|
2686
|
+
hook_result_names,
|
|
2687
|
+
);
|
|
2688
|
+
}
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
if (is_function_like_node(node)) {
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
|
|
2697
|
+
validate_hook_callback_outer_mutations(node, shadowed_names, transform_context);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
if (node.type === 'BlockStatement') {
|
|
2701
|
+
const next_shadowed = new Set(shadowed_names);
|
|
2702
|
+
const next_hook_result_names = new Set(hook_result_names);
|
|
2703
|
+
for (const name of collect_block_binding_names(node.body || [])) {
|
|
2704
|
+
next_shadowed.add(name);
|
|
2705
|
+
}
|
|
2706
|
+
for (const child of node.body || []) {
|
|
2707
|
+
validate_hook_outer_assignments_in_node(
|
|
2708
|
+
child,
|
|
2709
|
+
next_shadowed,
|
|
2710
|
+
transform_context,
|
|
2711
|
+
next_hook_result_names,
|
|
2712
|
+
);
|
|
2713
|
+
}
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
if (node.type === 'VariableDeclaration') {
|
|
2718
|
+
for (const declaration of node.declarations || []) {
|
|
2719
|
+
if (
|
|
2720
|
+
declaration.init &&
|
|
2721
|
+
expression_contains_hook_derived_value(
|
|
2722
|
+
declaration.init,
|
|
2723
|
+
transform_context,
|
|
2724
|
+
hook_result_names,
|
|
2725
|
+
)
|
|
2726
|
+
) {
|
|
2727
|
+
collect_pattern_names(declaration.id, hook_result_names);
|
|
2728
|
+
}
|
|
2729
|
+
validate_hook_outer_assignments_in_node(
|
|
2730
|
+
declaration.init,
|
|
2731
|
+
shadowed_names,
|
|
2732
|
+
transform_context,
|
|
2733
|
+
hook_result_names,
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
if (
|
|
2740
|
+
node.type === 'AssignmentExpression' &&
|
|
2741
|
+
expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
|
|
2742
|
+
) {
|
|
2743
|
+
const outer_names = get_referenced_outer_binding_names(
|
|
2744
|
+
node.left,
|
|
2745
|
+
transform_context.available_bindings,
|
|
2746
|
+
shadowed_names,
|
|
2747
|
+
);
|
|
2748
|
+
if (outer_names.length > 0) {
|
|
2749
|
+
report_hook_outer_assignment_error(
|
|
2750
|
+
node.left,
|
|
2751
|
+
outer_names,
|
|
2752
|
+
find_first_hook_call_name(node.right) || 'hook',
|
|
2753
|
+
transform_context,
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
|
|
2757
|
+
hook_result_names.add(name);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
if (node.type === 'ForOfStatement') {
|
|
2762
|
+
if (
|
|
2763
|
+
node.left &&
|
|
2764
|
+
node.left.type !== 'VariableDeclaration' &&
|
|
2765
|
+
expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
|
|
2766
|
+
) {
|
|
2767
|
+
const outer_names = get_referenced_outer_binding_names(
|
|
2768
|
+
node.left,
|
|
2769
|
+
transform_context.available_bindings,
|
|
2770
|
+
shadowed_names,
|
|
2771
|
+
);
|
|
2772
|
+
if (outer_names.length > 0) {
|
|
2773
|
+
report_hook_outer_assignment_error(
|
|
2774
|
+
node.left,
|
|
2775
|
+
outer_names,
|
|
2776
|
+
find_first_hook_call_name(node.right) || 'hook',
|
|
2777
|
+
transform_context,
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
|
|
2781
|
+
hook_result_names.add(name);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
validate_hook_outer_assignments_in_node(
|
|
2786
|
+
node.right,
|
|
2787
|
+
shadowed_names,
|
|
2788
|
+
transform_context,
|
|
2789
|
+
hook_result_names,
|
|
2790
|
+
);
|
|
2791
|
+
|
|
2792
|
+
// Loop-declared bindings (`for (const x of …)`, `for (let x of …)`) live
|
|
2793
|
+
// only in the body. They are deliberately NOT in the enclosing block's
|
|
2794
|
+
// shadowed set (see collect_block_binding_names_from_statement), so add
|
|
2795
|
+
// them just for the body recursion to keep references to the loop var
|
|
2796
|
+
// from being flagged as outer-binding mutations.
|
|
2797
|
+
const body_shadowed = new Set(shadowed_names);
|
|
2798
|
+
if (node.left && node.left.type === 'VariableDeclaration') {
|
|
2799
|
+
for (const declaration of node.left.declarations || []) {
|
|
2800
|
+
collect_pattern_names(declaration.id, body_shadowed);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
validate_hook_outer_assignments_in_node(
|
|
2804
|
+
node.body,
|
|
2805
|
+
body_shadowed,
|
|
2806
|
+
transform_context,
|
|
2807
|
+
hook_result_names,
|
|
2808
|
+
);
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
for (const key of Object.keys(node)) {
|
|
2813
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
validate_hook_outer_assignments_in_node(
|
|
2817
|
+
node[key],
|
|
2818
|
+
shadowed_names,
|
|
2819
|
+
transform_context,
|
|
2820
|
+
hook_result_names,
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
/**
|
|
2826
|
+
* @param {any} call_node
|
|
2827
|
+
* @param {Set<string>} shadowed_names
|
|
2828
|
+
* @param {TransformContext} transform_context
|
|
2829
|
+
* @returns {void}
|
|
2830
|
+
*/
|
|
2831
|
+
function validate_hook_callback_outer_mutations(call_node, shadowed_names, transform_context) {
|
|
2832
|
+
const hook_name = get_hook_callee_name(call_node.callee);
|
|
2833
|
+
for (const argument of call_node.arguments || []) {
|
|
2834
|
+
if (!is_function_like_node(argument)) {
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
2837
|
+
const callback_shadowed_names = create_function_like_shadowed_names(argument, shadowed_names);
|
|
2838
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
2839
|
+
argument.body,
|
|
2840
|
+
callback_shadowed_names,
|
|
2841
|
+
transform_context,
|
|
2842
|
+
hook_name,
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
/**
|
|
2848
|
+
* @param {any} node
|
|
2849
|
+
* @returns {boolean}
|
|
2850
|
+
*/
|
|
2851
|
+
function is_function_like_node(node) {
|
|
2852
|
+
return (
|
|
2853
|
+
node.type === 'FunctionDeclaration' ||
|
|
2854
|
+
node.type === 'FunctionExpression' ||
|
|
2855
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
2856
|
+
// this is just in case but we should already
|
|
2857
|
+
// have a component replaced with a function node
|
|
2858
|
+
node.type === 'Component'
|
|
2859
|
+
);
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
/**
|
|
2863
|
+
* @param {any} node
|
|
2864
|
+
* @param {Set<string>} shadowed_names
|
|
2865
|
+
* @returns {Set<string>}
|
|
2866
|
+
*/
|
|
2867
|
+
function create_function_like_shadowed_names(node, shadowed_names) {
|
|
2868
|
+
const next_shadowed_names = new Set(shadowed_names);
|
|
2869
|
+
for (const param of node.params || []) {
|
|
2870
|
+
collect_pattern_names(param, next_shadowed_names);
|
|
2871
|
+
}
|
|
2872
|
+
if (node.body?.type === 'BlockStatement') {
|
|
2873
|
+
for (const name of collect_block_binding_names(node.body.body || [])) {
|
|
2874
|
+
next_shadowed_names.add(name);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
return next_shadowed_names;
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
/**
|
|
2881
|
+
* @param {any} node
|
|
2882
|
+
* @param {Set<string>} shadowed_names
|
|
2883
|
+
* @param {TransformContext} transform_context
|
|
2884
|
+
* @param {string} hook_name
|
|
2885
|
+
* @returns {void}
|
|
2886
|
+
*/
|
|
2887
|
+
function validate_hook_callback_outer_mutations_in_node(
|
|
2888
|
+
node,
|
|
2889
|
+
shadowed_names,
|
|
2890
|
+
transform_context,
|
|
2891
|
+
hook_name,
|
|
2892
|
+
) {
|
|
2893
|
+
if (!node || typeof node !== 'object') {
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
if (Array.isArray(node)) {
|
|
2898
|
+
for (const child of node) {
|
|
2899
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
2900
|
+
child,
|
|
2901
|
+
shadowed_names,
|
|
2902
|
+
transform_context,
|
|
2903
|
+
hook_name,
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
if (is_function_like_node(node)) {
|
|
2910
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
2911
|
+
node.body,
|
|
2912
|
+
create_function_like_shadowed_names(node, shadowed_names),
|
|
2913
|
+
transform_context,
|
|
2914
|
+
hook_name,
|
|
2915
|
+
);
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
if (node.type === 'BlockStatement') {
|
|
2920
|
+
const next_shadowed_names = new Set(shadowed_names);
|
|
2921
|
+
for (const name of collect_block_binding_names(node.body || [])) {
|
|
2922
|
+
next_shadowed_names.add(name);
|
|
2923
|
+
}
|
|
2924
|
+
for (const child of node.body || []) {
|
|
2925
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
2926
|
+
child,
|
|
2927
|
+
next_shadowed_names,
|
|
2928
|
+
transform_context,
|
|
2929
|
+
hook_name,
|
|
2930
|
+
);
|
|
2931
|
+
}
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
if (node.type === 'AssignmentExpression') {
|
|
2936
|
+
const outer_names = get_referenced_outer_binding_names(
|
|
2937
|
+
node.left,
|
|
2938
|
+
transform_context.available_bindings,
|
|
2939
|
+
shadowed_names,
|
|
2940
|
+
);
|
|
2941
|
+
if (outer_names.length > 0) {
|
|
2942
|
+
report_hook_callback_outer_mutation_error(
|
|
2943
|
+
node.left,
|
|
2944
|
+
outer_names,
|
|
2945
|
+
hook_name,
|
|
2946
|
+
transform_context,
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
if (node.type === 'UpdateExpression') {
|
|
2952
|
+
const outer_names = get_referenced_outer_binding_names(
|
|
2953
|
+
node.argument,
|
|
2954
|
+
transform_context.available_bindings,
|
|
2955
|
+
shadowed_names,
|
|
2956
|
+
);
|
|
2957
|
+
if (outer_names.length > 0) {
|
|
2958
|
+
report_hook_callback_outer_mutation_error(
|
|
2959
|
+
node.argument,
|
|
2960
|
+
outer_names,
|
|
2961
|
+
hook_name,
|
|
2962
|
+
transform_context,
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
for (const key of Object.keys(node)) {
|
|
2968
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
2969
|
+
continue;
|
|
2970
|
+
}
|
|
2971
|
+
if (key === 'left' && node.type === 'AssignmentExpression') {
|
|
2972
|
+
continue;
|
|
2973
|
+
}
|
|
2974
|
+
if (key === 'argument' && node.type === 'UpdateExpression') {
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
2978
|
+
node[key],
|
|
2979
|
+
shadowed_names,
|
|
2980
|
+
transform_context,
|
|
2981
|
+
hook_name,
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
/**
|
|
2987
|
+
* @param {any} node
|
|
2988
|
+
* @param {TransformContext} transform_context
|
|
2989
|
+
* @param {Set<string>} hook_result_names
|
|
2755
2990
|
* @returns {boolean}
|
|
2756
2991
|
*/
|
|
2757
|
-
function
|
|
2758
|
-
return
|
|
2992
|
+
function expression_contains_hook_derived_value(node, transform_context, hook_result_names) {
|
|
2993
|
+
return (
|
|
2994
|
+
node_contains_top_level_hook_call(node, false, transform_context, true) ||
|
|
2995
|
+
references_name_in_set(node, hook_result_names)
|
|
2996
|
+
);
|
|
2759
2997
|
}
|
|
2760
2998
|
|
|
2761
2999
|
/**
|
|
2762
3000
|
* @param {any} node
|
|
3001
|
+
* @param {Set<string>} names
|
|
2763
3002
|
* @returns {boolean}
|
|
2764
3003
|
*/
|
|
2765
|
-
function
|
|
2766
|
-
if (!node || typeof node !== 'object') {
|
|
3004
|
+
function references_name_in_set(node, names) {
|
|
3005
|
+
if (!node || typeof node !== 'object' || names.size === 0) {
|
|
2767
3006
|
return false;
|
|
2768
3007
|
}
|
|
2769
3008
|
|
|
2770
|
-
if (
|
|
2771
|
-
(node.
|
|
2772
|
-
is_lone_return_if_statement(node)
|
|
2773
|
-
) {
|
|
2774
|
-
return true;
|
|
3009
|
+
if (node.type === 'Identifier') {
|
|
3010
|
+
return names.has(node.name);
|
|
2775
3011
|
}
|
|
2776
3012
|
|
|
2777
3013
|
if (
|
|
@@ -2784,14 +3020,20 @@ function child_contains_return_semantics(node) {
|
|
|
2784
3020
|
}
|
|
2785
3021
|
|
|
2786
3022
|
if (Array.isArray(node)) {
|
|
2787
|
-
return node.some(
|
|
3023
|
+
return node.some((child) => references_name_in_set(child, names));
|
|
2788
3024
|
}
|
|
2789
3025
|
|
|
2790
3026
|
for (const key of Object.keys(node)) {
|
|
2791
3027
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
2792
3028
|
continue;
|
|
2793
3029
|
}
|
|
2794
|
-
if (
|
|
3030
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
3033
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
|
|
3034
|
+
continue;
|
|
3035
|
+
}
|
|
3036
|
+
if (references_name_in_set(node[key], names)) {
|
|
2795
3037
|
return true;
|
|
2796
3038
|
}
|
|
2797
3039
|
}
|
|
@@ -2801,123 +3043,163 @@ function child_contains_return_semantics(node) {
|
|
|
2801
3043
|
|
|
2802
3044
|
/**
|
|
2803
3045
|
* @param {any} node
|
|
2804
|
-
* @
|
|
3046
|
+
* @param {Set<string>} shadowed_names
|
|
3047
|
+
* @returns {string[]}
|
|
2805
3048
|
*/
|
|
2806
|
-
function
|
|
2807
|
-
|
|
3049
|
+
function get_referenced_local_binding_names(node, shadowed_names) {
|
|
3050
|
+
const names = new Set();
|
|
3051
|
+
collect_referenced_local_binding_names(node, shadowed_names, names);
|
|
3052
|
+
return [...names];
|
|
2808
3053
|
}
|
|
2809
3054
|
|
|
2810
3055
|
/**
|
|
2811
|
-
* @param {any
|
|
2812
|
-
* @param {
|
|
2813
|
-
* @
|
|
3056
|
+
* @param {any} node
|
|
3057
|
+
* @param {Set<string>} shadowed_names
|
|
3058
|
+
* @param {Set<string>} names
|
|
3059
|
+
* @returns {void}
|
|
2814
3060
|
*/
|
|
2815
|
-
function
|
|
2816
|
-
if (
|
|
2817
|
-
return
|
|
3061
|
+
function collect_referenced_local_binding_names(node, shadowed_names, names) {
|
|
3062
|
+
if (!node || typeof node !== 'object') {
|
|
3063
|
+
return;
|
|
2818
3064
|
}
|
|
2819
3065
|
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
body: /** @type {any} */ ({
|
|
2827
|
-
type: 'BlockStatement',
|
|
2828
|
-
body: build_render_statements(body_nodes, true, transform_context),
|
|
2829
|
-
metadata: { path: [] },
|
|
2830
|
-
}),
|
|
2831
|
-
async: false,
|
|
2832
|
-
generator: false,
|
|
2833
|
-
expression: false,
|
|
2834
|
-
metadata: { path: [] },
|
|
2835
|
-
},
|
|
2836
|
-
arguments: [],
|
|
2837
|
-
optional: false,
|
|
2838
|
-
metadata: { path: [] },
|
|
2839
|
-
}),
|
|
2840
|
-
);
|
|
2841
|
-
}
|
|
3066
|
+
if (node.type === 'Identifier') {
|
|
3067
|
+
if (shadowed_names.has(node.name)) {
|
|
3068
|
+
names.add(node.name);
|
|
3069
|
+
}
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
2842
3072
|
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
const source_node = get_body_source_node(body_nodes);
|
|
2850
|
-
const helper = create_hook_safe_helper(body_nodes, undefined, source_node, transform_context);
|
|
3073
|
+
if (Array.isArray(node)) {
|
|
3074
|
+
for (const child of node) {
|
|
3075
|
+
collect_referenced_local_binding_names(child, shadowed_names, names);
|
|
3076
|
+
}
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
2851
3079
|
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
3080
|
+
for (const key of Object.keys(node)) {
|
|
3081
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3082
|
+
continue;
|
|
3083
|
+
}
|
|
3084
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
|
|
3085
|
+
continue;
|
|
3086
|
+
}
|
|
3087
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
|
|
3088
|
+
continue;
|
|
3089
|
+
}
|
|
3090
|
+
collect_referenced_local_binding_names(node[key], shadowed_names, names);
|
|
3091
|
+
}
|
|
2856
3092
|
}
|
|
2857
3093
|
|
|
2858
3094
|
/**
|
|
2859
|
-
* @param {
|
|
2860
|
-
* @
|
|
3095
|
+
* @param {any} node
|
|
3096
|
+
* @param {Map<string, AST.Identifier>} available_bindings
|
|
3097
|
+
* @param {Set<string>} shadowed_names
|
|
3098
|
+
* @returns {string[]}
|
|
2861
3099
|
*/
|
|
2862
|
-
function
|
|
2863
|
-
|
|
2864
|
-
|
|
3100
|
+
function get_referenced_outer_binding_names(node, available_bindings, shadowed_names) {
|
|
3101
|
+
const names = new Set();
|
|
3102
|
+
collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names);
|
|
3103
|
+
return [...names];
|
|
2865
3104
|
}
|
|
2866
3105
|
|
|
2867
3106
|
/**
|
|
2868
|
-
*
|
|
2869
|
-
*
|
|
2870
|
-
*
|
|
2871
|
-
*
|
|
2872
|
-
*
|
|
2873
|
-
* into their own component boundary to satisfy the Rules of Hooks.
|
|
2874
|
-
*
|
|
2875
|
-
* @param {any[]} body_nodes
|
|
2876
|
-
* @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
|
|
2877
|
-
* @param {TransformContext} transform_context
|
|
2878
|
-
* @returns {any[]}
|
|
3107
|
+
* @param {any} node
|
|
3108
|
+
* @param {Map<string, AST.Identifier>} available_bindings
|
|
3109
|
+
* @param {Set<string>} shadowed_names
|
|
3110
|
+
* @param {Set<string>} names
|
|
3111
|
+
* @returns {void}
|
|
2879
3112
|
*/
|
|
2880
|
-
function
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
key_expression,
|
|
2885
|
-
source_node,
|
|
2886
|
-
transform_context,
|
|
2887
|
-
);
|
|
2888
|
-
const statements = [...helper.setup_statements];
|
|
3113
|
+
function collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names) {
|
|
3114
|
+
if (!node || typeof node !== 'object') {
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
2889
3117
|
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
3118
|
+
if (node.type === 'Identifier') {
|
|
3119
|
+
if (available_bindings.has(node.name) && !shadowed_names.has(node.name)) {
|
|
3120
|
+
names.add(node.name);
|
|
3121
|
+
}
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
2895
3124
|
|
|
2896
|
-
|
|
3125
|
+
if (Array.isArray(node)) {
|
|
3126
|
+
for (const child of node) {
|
|
3127
|
+
collect_referenced_outer_binding_names(child, available_bindings, shadowed_names, names);
|
|
3128
|
+
}
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
for (const key of Object.keys(node)) {
|
|
3133
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3134
|
+
continue;
|
|
3135
|
+
}
|
|
3136
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
|
|
3137
|
+
continue;
|
|
3138
|
+
}
|
|
3139
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
3142
|
+
collect_referenced_outer_binding_names(node[key], available_bindings, shadowed_names, names);
|
|
3143
|
+
}
|
|
2897
3144
|
}
|
|
2898
3145
|
|
|
2899
3146
|
/**
|
|
2900
|
-
* @param {any
|
|
2901
|
-
* @
|
|
2902
|
-
* @returns {AST.Identifier[]}
|
|
3147
|
+
* @param {any} node
|
|
3148
|
+
* @returns {string | null}
|
|
2903
3149
|
*/
|
|
2904
|
-
function
|
|
2905
|
-
|
|
2906
|
-
|
|
3150
|
+
function find_first_hook_call_name(node) {
|
|
3151
|
+
if (!node || typeof node !== 'object') {
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
2907
3154
|
|
|
2908
|
-
|
|
2909
|
-
|
|
3155
|
+
if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
|
|
3156
|
+
return get_hook_callee_name(node.callee);
|
|
2910
3157
|
}
|
|
2911
3158
|
|
|
2912
|
-
|
|
2913
|
-
|
|
3159
|
+
if (
|
|
3160
|
+
node.type === 'FunctionDeclaration' ||
|
|
3161
|
+
node.type === 'FunctionExpression' ||
|
|
3162
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
3163
|
+
node.type === 'Component'
|
|
3164
|
+
) {
|
|
3165
|
+
return null;
|
|
3166
|
+
}
|
|
2914
3167
|
|
|
2915
|
-
|
|
2916
|
-
|
|
3168
|
+
if (Array.isArray(node)) {
|
|
3169
|
+
for (const child of node) {
|
|
3170
|
+
const name = find_first_hook_call_name(child);
|
|
3171
|
+
if (name) return name;
|
|
2917
3172
|
}
|
|
3173
|
+
return null;
|
|
2918
3174
|
}
|
|
2919
3175
|
|
|
2920
|
-
|
|
3176
|
+
for (const key of Object.keys(node)) {
|
|
3177
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3178
|
+
continue;
|
|
3179
|
+
}
|
|
3180
|
+
const name = find_first_hook_call_name(node[key]);
|
|
3181
|
+
if (name) return name;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
return null;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
/**
|
|
3188
|
+
* @param {any} callee
|
|
3189
|
+
* @returns {string}
|
|
3190
|
+
*/
|
|
3191
|
+
function get_hook_callee_name(callee) {
|
|
3192
|
+
if (callee?.type === 'Identifier') {
|
|
3193
|
+
return callee.name;
|
|
3194
|
+
}
|
|
3195
|
+
if (
|
|
3196
|
+
callee?.type === 'MemberExpression' &&
|
|
3197
|
+
!callee.computed &&
|
|
3198
|
+
callee.property?.type === 'Identifier'
|
|
3199
|
+
) {
|
|
3200
|
+
return callee.property.name;
|
|
3201
|
+
}
|
|
3202
|
+
return 'hook';
|
|
2921
3203
|
}
|
|
2922
3204
|
|
|
2923
3205
|
/**
|
|
@@ -2938,6 +3220,11 @@ function create_hook_safe_helper(
|
|
|
2938
3220
|
transform_context,
|
|
2939
3221
|
preallocated_helper_id,
|
|
2940
3222
|
) {
|
|
3223
|
+
validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
|
|
3224
|
+
body_nodes,
|
|
3225
|
+
transform_context,
|
|
3226
|
+
);
|
|
3227
|
+
|
|
2941
3228
|
const helper_id =
|
|
2942
3229
|
preallocated_helper_id ??
|
|
2943
3230
|
create_generated_identifier(create_local_statement_component_name(transform_context));
|
|
@@ -2968,23 +3255,13 @@ function create_hook_safe_helper(
|
|
|
2968
3255
|
const saved_bindings = transform_context.available_bindings;
|
|
2969
3256
|
transform_context.available_bindings = new Map(saved_bindings);
|
|
2970
3257
|
|
|
2971
|
-
const helper_fn =
|
|
2972
|
-
|
|
2973
|
-
id: clone_identifier(component_id),
|
|
3258
|
+
const helper_fn = b.function(
|
|
3259
|
+
clone_identifier(component_id),
|
|
2974
3260
|
params,
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
},
|
|
2980
|
-
async: false,
|
|
2981
|
-
generator: false,
|
|
2982
|
-
metadata: {
|
|
2983
|
-
path: [],
|
|
2984
|
-
is_component: true,
|
|
2985
|
-
is_method: false,
|
|
2986
|
-
},
|
|
2987
|
-
});
|
|
3261
|
+
b.block(build_render_statements(body_nodes, true, transform_context)),
|
|
3262
|
+
);
|
|
3263
|
+
helper_fn.metadata.is_component = true;
|
|
3264
|
+
helper_fn.metadata.is_method = false;
|
|
2988
3265
|
|
|
2989
3266
|
transform_context.available_bindings = saved_bindings;
|
|
2990
3267
|
|
|
@@ -3050,7 +3327,7 @@ function create_hook_safe_helper(
|
|
|
3050
3327
|
|
|
3051
3328
|
/**
|
|
3052
3329
|
* @param {AST.Identifier} helper_id
|
|
3053
|
-
* @param {
|
|
3330
|
+
* @param {AST.FunctionExpression} helper_fn
|
|
3054
3331
|
* @param {any} source_node
|
|
3055
3332
|
* @param {TransformContext} transform_context
|
|
3056
3333
|
* @returns {any}
|
|
@@ -3063,7 +3340,7 @@ function create_helper_declaration(helper_id, helper_fn, source_node, transform_
|
|
|
3063
3340
|
|
|
3064
3341
|
/**
|
|
3065
3342
|
* @param {AST.Identifier} helper_id
|
|
3066
|
-
* @param {
|
|
3343
|
+
* @param {AST.FunctionExpression} helper_fn
|
|
3067
3344
|
* @param {any} source_node
|
|
3068
3345
|
* @param {TransformContext} transform_context
|
|
3069
3346
|
* @returns {any}
|
|
@@ -3092,32 +3369,7 @@ function create_helper_init_expression(helper_id, helper_fn, source_node, transf
|
|
|
3092
3369
|
* @returns {any}
|
|
3093
3370
|
*/
|
|
3094
3371
|
function create_hook_safe_helper_iife(setup_statements, component_element) {
|
|
3095
|
-
return
|
|
3096
|
-
type: 'CallExpression',
|
|
3097
|
-
callee: {
|
|
3098
|
-
type: 'ArrowFunctionExpression',
|
|
3099
|
-
params: [],
|
|
3100
|
-
body: /** @type {any} */ ({
|
|
3101
|
-
type: 'BlockStatement',
|
|
3102
|
-
body: [
|
|
3103
|
-
...setup_statements,
|
|
3104
|
-
{
|
|
3105
|
-
type: 'ReturnStatement',
|
|
3106
|
-
argument: component_element,
|
|
3107
|
-
metadata: { path: [] },
|
|
3108
|
-
},
|
|
3109
|
-
],
|
|
3110
|
-
metadata: { path: [] },
|
|
3111
|
-
}),
|
|
3112
|
-
async: false,
|
|
3113
|
-
generator: false,
|
|
3114
|
-
expression: false,
|
|
3115
|
-
metadata: { path: [] },
|
|
3116
|
-
},
|
|
3117
|
-
arguments: [],
|
|
3118
|
-
optional: false,
|
|
3119
|
-
metadata: { path: [] },
|
|
3120
|
-
});
|
|
3372
|
+
return b.call(b.arrow([], b.block([...setup_statements, b.return(component_element)])));
|
|
3121
3373
|
}
|
|
3122
3374
|
|
|
3123
3375
|
/**
|
|
@@ -3130,19 +3382,7 @@ function create_helper_type_alias_declaration(helper_id, binding) {
|
|
|
3130
3382
|
|
|
3131
3383
|
return {
|
|
3132
3384
|
id: alias_id,
|
|
3133
|
-
declaration:
|
|
3134
|
-
type: 'VariableDeclaration',
|
|
3135
|
-
kind: 'const',
|
|
3136
|
-
declarations: [
|
|
3137
|
-
{
|
|
3138
|
-
type: 'VariableDeclarator',
|
|
3139
|
-
id: clone_identifier(alias_id),
|
|
3140
|
-
init: create_generated_identifier(binding.name),
|
|
3141
|
-
metadata: { path: [] },
|
|
3142
|
-
},
|
|
3143
|
-
],
|
|
3144
|
-
metadata: { path: [] },
|
|
3145
|
-
}),
|
|
3385
|
+
declaration: b.const(clone_identifier(alias_id), create_generated_identifier(binding.name)),
|
|
3146
3386
|
};
|
|
3147
3387
|
}
|
|
3148
3388
|
|
|
@@ -3152,33 +3392,14 @@ function create_helper_type_alias_declaration(helper_id, binding) {
|
|
|
3152
3392
|
* @returns {any}
|
|
3153
3393
|
*/
|
|
3154
3394
|
function create_helper_props_type_literal(bindings, aliases) {
|
|
3155
|
-
return
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
key: create_generated_identifier(binding.name),
|
|
3162
|
-
computed: false,
|
|
3163
|
-
optional: false,
|
|
3164
|
-
readonly: false,
|
|
3165
|
-
static: false,
|
|
3166
|
-
kind: 'init',
|
|
3167
|
-
typeAnnotation: {
|
|
3168
|
-
type: 'TSTypeAnnotation',
|
|
3169
|
-
typeAnnotation: {
|
|
3170
|
-
type: 'TSTypeQuery',
|
|
3171
|
-
exprName: clone_identifier(aliases[i].id),
|
|
3172
|
-
typeArguments: null,
|
|
3173
|
-
metadata: { path: [] },
|
|
3174
|
-
},
|
|
3175
|
-
metadata: { path: [] },
|
|
3176
|
-
},
|
|
3177
|
-
metadata: { path: [] },
|
|
3178
|
-
}),
|
|
3395
|
+
return b.ts_type_literal(
|
|
3396
|
+
bindings.map((binding, i) =>
|
|
3397
|
+
b.ts_property_signature(
|
|
3398
|
+
create_generated_identifier(binding.name),
|
|
3399
|
+
b.ts_type_annotation(b.ts_type_query(clone_identifier(aliases[i].id))),
|
|
3400
|
+
),
|
|
3179
3401
|
),
|
|
3180
|
-
|
|
3181
|
-
});
|
|
3402
|
+
);
|
|
3182
3403
|
}
|
|
3183
3404
|
|
|
3184
3405
|
/**
|
|
@@ -3188,11 +3409,7 @@ function create_helper_props_type_literal(bindings, aliases) {
|
|
|
3188
3409
|
*/
|
|
3189
3410
|
function create_typed_helper_props_pattern(bindings, props_type) {
|
|
3190
3411
|
const pattern = create_helper_props_pattern(bindings);
|
|
3191
|
-
/** @type {any} */ (pattern).typeAnnotation =
|
|
3192
|
-
type: 'TSTypeAnnotation',
|
|
3193
|
-
typeAnnotation: props_type,
|
|
3194
|
-
metadata: { path: [] },
|
|
3195
|
-
};
|
|
3412
|
+
/** @type {any} */ (pattern).typeAnnotation = b.ts_type_annotation(props_type);
|
|
3196
3413
|
return pattern;
|
|
3197
3414
|
}
|
|
3198
3415
|
|
|
@@ -3201,19 +3418,7 @@ function create_typed_helper_props_pattern(bindings, props_type) {
|
|
|
3201
3418
|
* @returns {any}
|
|
3202
3419
|
*/
|
|
3203
3420
|
function create_helper_cache_declaration(cache_id) {
|
|
3204
|
-
return
|
|
3205
|
-
type: 'VariableDeclaration',
|
|
3206
|
-
kind: 'let',
|
|
3207
|
-
declarations: [
|
|
3208
|
-
{
|
|
3209
|
-
type: 'VariableDeclarator',
|
|
3210
|
-
id: clone_identifier(cache_id),
|
|
3211
|
-
init: null,
|
|
3212
|
-
metadata: { path: [] },
|
|
3213
|
-
},
|
|
3214
|
-
],
|
|
3215
|
-
metadata: { path: [] },
|
|
3216
|
-
});
|
|
3421
|
+
return b.let(clone_identifier(cache_id));
|
|
3217
3422
|
}
|
|
3218
3423
|
|
|
3219
3424
|
/**
|
|
@@ -3223,44 +3428,27 @@ function create_helper_cache_declaration(cache_id) {
|
|
|
3223
3428
|
* @returns {any}
|
|
3224
3429
|
*/
|
|
3225
3430
|
function create_cached_helper_declaration(helper_id, cache_id, helper_init) {
|
|
3226
|
-
return
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
type: 'LogicalExpression',
|
|
3235
|
-
operator: '??',
|
|
3236
|
-
left: clone_identifier(cache_id),
|
|
3237
|
-
right: {
|
|
3238
|
-
type: 'AssignmentExpression',
|
|
3239
|
-
operator: '=',
|
|
3240
|
-
left: clone_identifier(cache_id),
|
|
3241
|
-
right: helper_init,
|
|
3242
|
-
metadata: { path: [] },
|
|
3243
|
-
},
|
|
3244
|
-
metadata: { path: [] },
|
|
3245
|
-
},
|
|
3246
|
-
metadata: { path: [] },
|
|
3247
|
-
},
|
|
3248
|
-
],
|
|
3249
|
-
metadata: { path: [] },
|
|
3250
|
-
});
|
|
3431
|
+
return b.const(
|
|
3432
|
+
clone_identifier(helper_id),
|
|
3433
|
+
b.logical(
|
|
3434
|
+
'??',
|
|
3435
|
+
clone_identifier(cache_id),
|
|
3436
|
+
b.assignment('=', clone_identifier(cache_id), helper_init),
|
|
3437
|
+
),
|
|
3438
|
+
);
|
|
3251
3439
|
}
|
|
3252
3440
|
|
|
3253
3441
|
/**
|
|
3254
3442
|
* @param {AST.Identifier} helper_id
|
|
3255
|
-
* @param {
|
|
3443
|
+
* @param {AST.FunctionExpression} helper_fn
|
|
3256
3444
|
* @returns {AST.FunctionDeclaration}
|
|
3257
3445
|
*/
|
|
3258
3446
|
function create_helper_function_declaration_from_expression(helper_id, helper_fn) {
|
|
3259
|
-
return
|
|
3447
|
+
return {
|
|
3260
3448
|
...helper_fn,
|
|
3261
3449
|
type: 'FunctionDeclaration',
|
|
3262
3450
|
id: clone_identifier(helper_id),
|
|
3263
|
-
}
|
|
3451
|
+
};
|
|
3264
3452
|
}
|
|
3265
3453
|
|
|
3266
3454
|
/**
|
|
@@ -3410,9 +3598,7 @@ function to_jsx_child(node, transform_context) {
|
|
|
3410
3598
|
case 'TSRXExpression':
|
|
3411
3599
|
return to_jsx_expression_container(node.expression, node);
|
|
3412
3600
|
case 'Html':
|
|
3413
|
-
|
|
3414
|
-
`\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
|
|
3415
|
-
);
|
|
3601
|
+
return report_html_template_unsupported_error(node, transform_context);
|
|
3416
3602
|
case 'IfStatement':
|
|
3417
3603
|
return (
|
|
3418
3604
|
transform_context.platform.hooks?.controlFlow?.ifStatement ?? if_statement_to_jsx_child
|
|
@@ -3647,25 +3833,7 @@ function if_statement_to_jsx_child(node, transform_context) {
|
|
|
3647
3833
|
}
|
|
3648
3834
|
|
|
3649
3835
|
return to_jsx_expression_container(
|
|
3650
|
-
|
|
3651
|
-
type: 'CallExpression',
|
|
3652
|
-
callee: {
|
|
3653
|
-
type: 'ArrowFunctionExpression',
|
|
3654
|
-
params: [],
|
|
3655
|
-
body: /** @type {any} */ ({
|
|
3656
|
-
type: 'BlockStatement',
|
|
3657
|
-
body: [render_if_statement, create_null_return_statement()],
|
|
3658
|
-
metadata: { path: [] },
|
|
3659
|
-
}),
|
|
3660
|
-
async: false,
|
|
3661
|
-
generator: false,
|
|
3662
|
-
expression: false,
|
|
3663
|
-
metadata: { path: [] },
|
|
3664
|
-
},
|
|
3665
|
-
arguments: [],
|
|
3666
|
-
optional: false,
|
|
3667
|
-
metadata: { path: [] },
|
|
3668
|
-
}),
|
|
3836
|
+
b.call(b.arrow([], b.block([render_if_statement, create_null_return_statement()]))),
|
|
3669
3837
|
);
|
|
3670
3838
|
}
|
|
3671
3839
|
|
|
@@ -3690,16 +3858,7 @@ function render_if_statement_to_conditional_expression(node) {
|
|
|
3690
3858
|
}
|
|
3691
3859
|
}
|
|
3692
3860
|
|
|
3693
|
-
return set_loc(
|
|
3694
|
-
/** @type {any} */ ({
|
|
3695
|
-
type: 'ConditionalExpression',
|
|
3696
|
-
test: node.test,
|
|
3697
|
-
consequent,
|
|
3698
|
-
alternate,
|
|
3699
|
-
metadata: { path: [] },
|
|
3700
|
-
}),
|
|
3701
|
-
node,
|
|
3702
|
-
);
|
|
3861
|
+
return set_loc(b.conditional(node.test, consequent, alternate), node);
|
|
3703
3862
|
}
|
|
3704
3863
|
|
|
3705
3864
|
/**
|
|
@@ -3769,15 +3928,8 @@ function find_key_expression_in_body(body_nodes) {
|
|
|
3769
3928
|
* @param {any} source_node
|
|
3770
3929
|
* @returns {any}
|
|
3771
3930
|
*/
|
|
3772
|
-
function continue_to_bare_return(source_node) {
|
|
3773
|
-
return set_loc(
|
|
3774
|
-
/** @type {any} */ ({
|
|
3775
|
-
type: 'ReturnStatement',
|
|
3776
|
-
argument: null,
|
|
3777
|
-
metadata: { path: [] },
|
|
3778
|
-
}),
|
|
3779
|
-
source_node,
|
|
3780
|
-
);
|
|
3931
|
+
function continue_to_bare_return(source_node) {
|
|
3932
|
+
return set_loc(b.return(null), source_node);
|
|
3781
3933
|
}
|
|
3782
3934
|
|
|
3783
3935
|
/**
|
|
@@ -3903,36 +4055,17 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
3903
4055
|
// Restore bindings
|
|
3904
4056
|
transform_context.available_bindings = saved_bindings;
|
|
3905
4057
|
|
|
4058
|
+
const iter_callback = b.arrow(loop_params, b.block(body_statements));
|
|
4059
|
+
|
|
4060
|
+
if (transform_context.platform.imports.forOfIterableHelper) {
|
|
4061
|
+
transform_context.needs_for_of_iterable = true;
|
|
4062
|
+
return to_jsx_expression_container(
|
|
4063
|
+
b.call(b.id(MAP_ITERABLE_INTERNAL_NAME), node.right, iter_callback),
|
|
4064
|
+
);
|
|
4065
|
+
}
|
|
4066
|
+
|
|
3906
4067
|
return to_jsx_expression_container(
|
|
3907
|
-
|
|
3908
|
-
type: 'CallExpression',
|
|
3909
|
-
callee: {
|
|
3910
|
-
type: 'MemberExpression',
|
|
3911
|
-
object: node.right,
|
|
3912
|
-
property: create_generated_identifier('map'),
|
|
3913
|
-
computed: false,
|
|
3914
|
-
optional: false,
|
|
3915
|
-
metadata: { path: [] },
|
|
3916
|
-
},
|
|
3917
|
-
arguments: [
|
|
3918
|
-
{
|
|
3919
|
-
type: 'ArrowFunctionExpression',
|
|
3920
|
-
params: loop_params,
|
|
3921
|
-
body: /** @type {any} */ ({
|
|
3922
|
-
type: 'BlockStatement',
|
|
3923
|
-
body: body_statements,
|
|
3924
|
-
metadata: { path: [] },
|
|
3925
|
-
}),
|
|
3926
|
-
async: false,
|
|
3927
|
-
generator: false,
|
|
3928
|
-
expression: false,
|
|
3929
|
-
metadata: { path: [] },
|
|
3930
|
-
},
|
|
3931
|
-
],
|
|
3932
|
-
async: false,
|
|
3933
|
-
optional: false,
|
|
3934
|
-
metadata: { path: [] },
|
|
3935
|
-
}),
|
|
4068
|
+
b.call(b.member(node.right, create_generated_identifier('map')), iter_callback),
|
|
3936
4069
|
);
|
|
3937
4070
|
}
|
|
3938
4071
|
|
|
@@ -3953,7 +4086,7 @@ function apply_key_to_loop_body(body_nodes, key_expression) {
|
|
|
3953
4086
|
if (!has_key) {
|
|
3954
4087
|
attributes.push({
|
|
3955
4088
|
type: 'Attribute',
|
|
3956
|
-
name:
|
|
4089
|
+
name: b.id('key'),
|
|
3957
4090
|
value: clone_expression_node(key_expression),
|
|
3958
4091
|
shorthand: false,
|
|
3959
4092
|
metadata: { path: [] },
|
|
@@ -3973,15 +4106,10 @@ function apply_key_to_loop_body(body_nodes, key_expression) {
|
|
|
3973
4106
|
|
|
3974
4107
|
if (!has_key) {
|
|
3975
4108
|
attributes.push(
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
clone_expression_node(key_expression),
|
|
3981
|
-
key_expression,
|
|
3982
|
-
),
|
|
3983
|
-
metadata: { path: [] },
|
|
3984
|
-
}),
|
|
4109
|
+
b.jsx_attribute(
|
|
4110
|
+
b.jsx_id('key'),
|
|
4111
|
+
to_jsx_expression_container(clone_expression_node(key_expression), key_expression),
|
|
4112
|
+
),
|
|
3985
4113
|
);
|
|
3986
4114
|
}
|
|
3987
4115
|
return;
|
|
@@ -4076,29 +4204,12 @@ function keyed_fragment_to_jsx_element(fragment, key_expression) {
|
|
|
4076
4204
|
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
4077
4205
|
*/
|
|
4078
4206
|
function switch_statement_to_jsx_child(node, transform_context) {
|
|
4207
|
+
const { setup_statements, switch_statement } = build_switch_with_lift(node, transform_context);
|
|
4208
|
+
|
|
4079
4209
|
return to_jsx_expression_container(
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
type: 'ArrowFunctionExpression',
|
|
4084
|
-
params: [],
|
|
4085
|
-
body: /** @type {any} */ ({
|
|
4086
|
-
type: 'BlockStatement',
|
|
4087
|
-
body: [
|
|
4088
|
-
create_render_switch_statement(node, transform_context),
|
|
4089
|
-
create_null_return_statement(),
|
|
4090
|
-
],
|
|
4091
|
-
metadata: { path: [] },
|
|
4092
|
-
}),
|
|
4093
|
-
async: false,
|
|
4094
|
-
generator: false,
|
|
4095
|
-
expression: false,
|
|
4096
|
-
metadata: { path: [] },
|
|
4097
|
-
},
|
|
4098
|
-
arguments: [],
|
|
4099
|
-
optional: false,
|
|
4100
|
-
metadata: { path: [] },
|
|
4101
|
-
}),
|
|
4210
|
+
b.call(
|
|
4211
|
+
b.arrow([], b.block([...setup_statements, switch_statement, create_null_return_statement()])),
|
|
4212
|
+
),
|
|
4102
4213
|
);
|
|
4103
4214
|
}
|
|
4104
4215
|
|
|
@@ -4227,23 +4338,21 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
4227
4338
|
// correctly identifies references to err/reset as non-static
|
|
4228
4339
|
const saved_catch_bindings = transform_context.available_bindings;
|
|
4229
4340
|
transform_context.available_bindings = new Map(saved_catch_bindings);
|
|
4341
|
+
const catch_scoped_names = new Set();
|
|
4230
4342
|
for (const param of catch_params) {
|
|
4231
4343
|
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
4344
|
+
collect_pattern_names(param, catch_scoped_names);
|
|
4232
4345
|
}
|
|
4346
|
+
validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
|
|
4347
|
+
catch_body_nodes,
|
|
4348
|
+
transform_context,
|
|
4349
|
+
catch_scoped_names,
|
|
4350
|
+
);
|
|
4233
4351
|
|
|
4234
|
-
const fallback_fn =
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
type: 'BlockStatement',
|
|
4239
|
-
body: build_render_statements(catch_body_nodes, true, transform_context),
|
|
4240
|
-
metadata: { path: [] },
|
|
4241
|
-
}),
|
|
4242
|
-
async: false,
|
|
4243
|
-
generator: false,
|
|
4244
|
-
expression: false,
|
|
4245
|
-
metadata: { path: [] },
|
|
4246
|
-
};
|
|
4352
|
+
const fallback_fn = b.arrow(
|
|
4353
|
+
catch_params,
|
|
4354
|
+
b.block(build_render_statements(catch_body_nodes, true, transform_context)),
|
|
4355
|
+
);
|
|
4247
4356
|
|
|
4248
4357
|
transform_context.available_bindings = saved_catch_bindings;
|
|
4249
4358
|
|
|
@@ -4256,40 +4365,10 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
4256
4365
|
|
|
4257
4366
|
if (boundary_content && transform_context.inside_element_child) {
|
|
4258
4367
|
result = to_jsx_expression_container(
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
{
|
|
4264
|
-
type: 'ObjectExpression',
|
|
4265
|
-
properties: [
|
|
4266
|
-
{
|
|
4267
|
-
type: 'Property',
|
|
4268
|
-
key: { type: 'Identifier', name: 'fallback', metadata: { path: [] } },
|
|
4269
|
-
value: fallback_fn,
|
|
4270
|
-
kind: 'init',
|
|
4271
|
-
method: false,
|
|
4272
|
-
shorthand: false,
|
|
4273
|
-
computed: false,
|
|
4274
|
-
metadata: { path: [] },
|
|
4275
|
-
},
|
|
4276
|
-
{
|
|
4277
|
-
type: 'Property',
|
|
4278
|
-
key: { type: 'Identifier', name: 'content', metadata: { path: [] } },
|
|
4279
|
-
value: boundary_content,
|
|
4280
|
-
kind: 'init',
|
|
4281
|
-
method: false,
|
|
4282
|
-
shorthand: false,
|
|
4283
|
-
computed: false,
|
|
4284
|
-
metadata: { path: [] },
|
|
4285
|
-
},
|
|
4286
|
-
],
|
|
4287
|
-
metadata: { path: [] },
|
|
4288
|
-
},
|
|
4289
|
-
],
|
|
4290
|
-
optional: false,
|
|
4291
|
-
metadata: { path: [] },
|
|
4292
|
-
}),
|
|
4368
|
+
b.call(
|
|
4369
|
+
'TsrxErrorBoundary',
|
|
4370
|
+
b.object([b.init('fallback', fallback_fn), b.init('content', boundary_content)]),
|
|
4371
|
+
),
|
|
4293
4372
|
);
|
|
4294
4373
|
|
|
4295
4374
|
return result;
|
|
@@ -4298,21 +4377,12 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
4298
4377
|
result = create_jsx_element(
|
|
4299
4378
|
'TsrxErrorBoundary',
|
|
4300
4379
|
[
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
metadata: { path: [] },
|
|
4306
|
-
},
|
|
4380
|
+
b.jsx_attribute(
|
|
4381
|
+
b.jsx_id('fallback'),
|
|
4382
|
+
to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
|
|
4383
|
+
),
|
|
4307
4384
|
...(boundary_content
|
|
4308
|
-
? [
|
|
4309
|
-
{
|
|
4310
|
-
type: 'JSXAttribute',
|
|
4311
|
-
name: { type: 'JSXIdentifier', name: 'content', metadata: { path: [] } },
|
|
4312
|
-
value: to_jsx_expression_container(boundary_content),
|
|
4313
|
-
metadata: { path: [] },
|
|
4314
|
-
},
|
|
4315
|
-
]
|
|
4385
|
+
? [b.jsx_attribute(b.jsx_id('content'), to_jsx_expression_container(boundary_content))]
|
|
4316
4386
|
: []),
|
|
4317
4387
|
],
|
|
4318
4388
|
boundary_content ? [] : [result],
|
|
@@ -4361,73 +4431,29 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
|
|
|
4361
4431
|
const imports = [];
|
|
4362
4432
|
|
|
4363
4433
|
if (transform_context.needs_fragment && platform.imports.fragment) {
|
|
4364
|
-
|
|
4365
|
-
imports.push({
|
|
4366
|
-
type: 'ImportDeclaration',
|
|
4367
|
-
specifiers: [
|
|
4368
|
-
{
|
|
4369
|
-
type: 'ImportSpecifier',
|
|
4370
|
-
imported: { type: 'Identifier', name: 'Fragment', metadata: { path: [] } },
|
|
4371
|
-
local: { type: 'Identifier', name: 'Fragment', metadata: { path: [] } },
|
|
4372
|
-
metadata: { path: [] },
|
|
4373
|
-
},
|
|
4374
|
-
],
|
|
4375
|
-
source: {
|
|
4376
|
-
type: 'Literal',
|
|
4377
|
-
value: fragment_source,
|
|
4378
|
-
raw: `'${fragment_source}'`,
|
|
4379
|
-
},
|
|
4380
|
-
metadata: { path: [] },
|
|
4381
|
-
});
|
|
4434
|
+
imports.push(b.imports([['Fragment', 'Fragment']], platform.imports.fragment));
|
|
4382
4435
|
}
|
|
4383
4436
|
|
|
4384
4437
|
if (transform_context.needs_suspense) {
|
|
4385
|
-
imports.push(
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
raw: `'${suspense_source}'`,
|
|
4399
|
-
},
|
|
4400
|
-
metadata: { path: [] },
|
|
4401
|
-
});
|
|
4438
|
+
imports.push(b.imports([['Suspense', 'Suspense']], suspense_source));
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
if (transform_context.needs_for_of_iterable && platform.imports.forOfIterableHelper) {
|
|
4442
|
+
const specifiers = [b.import_specifier('map_iterable', MAP_ITERABLE_INTERNAL_NAME)];
|
|
4443
|
+
// The loop-scoped type alias `IterationValue<typeof source>` only
|
|
4444
|
+
// appears in the output when at least one hook-bearing for-of body
|
|
4445
|
+
// was lowered with non-module-scoped helpers (editor tooling sets
|
|
4446
|
+
// this for typeOnly virtual modules).
|
|
4447
|
+
if (transform_context.needs_iteration_value_type) {
|
|
4448
|
+
specifiers.push(b.import_specifier('IterationValue', ITERATION_VALUE_INTERNAL_NAME, 'type'));
|
|
4449
|
+
}
|
|
4450
|
+
imports.push(b.import_declaration(specifiers, platform.imports.forOfIterableHelper));
|
|
4402
4451
|
}
|
|
4403
4452
|
|
|
4404
4453
|
if (transform_context.needs_error_boundary) {
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
specifiers: [
|
|
4409
|
-
{
|
|
4410
|
-
type: 'ImportSpecifier',
|
|
4411
|
-
imported: {
|
|
4412
|
-
type: 'Identifier',
|
|
4413
|
-
name: 'TsrxErrorBoundary',
|
|
4414
|
-
metadata: { path: [] },
|
|
4415
|
-
},
|
|
4416
|
-
local: {
|
|
4417
|
-
type: 'Identifier',
|
|
4418
|
-
name: 'TsrxErrorBoundary',
|
|
4419
|
-
metadata: { path: [] },
|
|
4420
|
-
},
|
|
4421
|
-
metadata: { path: [] },
|
|
4422
|
-
},
|
|
4423
|
-
],
|
|
4424
|
-
source: {
|
|
4425
|
-
type: 'Literal',
|
|
4426
|
-
value: error_boundary_source,
|
|
4427
|
-
raw: `'${error_boundary_source}'`,
|
|
4428
|
-
},
|
|
4429
|
-
metadata: { path: [] },
|
|
4430
|
-
});
|
|
4454
|
+
imports.push(
|
|
4455
|
+
b.imports([['TsrxErrorBoundary', 'TsrxErrorBoundary']], platform.imports.errorBoundary),
|
|
4456
|
+
);
|
|
4431
4457
|
}
|
|
4432
4458
|
|
|
4433
4459
|
const merge_refs_source =
|
|
@@ -4445,67 +4471,31 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
|
|
|
4445
4471
|
const ref_imports = new Map();
|
|
4446
4472
|
|
|
4447
4473
|
if (merge_refs_source !== null) {
|
|
4448
|
-
add_ref_import_specifier(
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
metadata: { path: [] },
|
|
4454
|
-
},
|
|
4455
|
-
local: {
|
|
4456
|
-
type: 'Identifier',
|
|
4457
|
-
name: MERGE_REFS_INTERNAL_NAME,
|
|
4458
|
-
metadata: { path: [] },
|
|
4459
|
-
},
|
|
4460
|
-
metadata: { path: [] },
|
|
4461
|
-
});
|
|
4474
|
+
add_ref_import_specifier(
|
|
4475
|
+
ref_imports,
|
|
4476
|
+
merge_refs_source,
|
|
4477
|
+
b.import_specifier('mergeRefs', MERGE_REFS_INTERNAL_NAME),
|
|
4478
|
+
);
|
|
4462
4479
|
}
|
|
4463
4480
|
|
|
4464
4481
|
if (ref_prop_source !== null) {
|
|
4465
|
-
add_ref_import_specifier(
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
metadata: { path: [] },
|
|
4471
|
-
},
|
|
4472
|
-
local: {
|
|
4473
|
-
type: 'Identifier',
|
|
4474
|
-
name: CREATE_REF_PROP_INTERNAL_NAME,
|
|
4475
|
-
metadata: { path: [] },
|
|
4476
|
-
},
|
|
4477
|
-
metadata: { path: [] },
|
|
4478
|
-
});
|
|
4482
|
+
add_ref_import_specifier(
|
|
4483
|
+
ref_imports,
|
|
4484
|
+
ref_prop_source,
|
|
4485
|
+
b.import_specifier('create_ref_prop', CREATE_REF_PROP_INTERNAL_NAME),
|
|
4486
|
+
);
|
|
4479
4487
|
}
|
|
4480
4488
|
|
|
4481
4489
|
if (normalize_spread_props_source !== null) {
|
|
4482
|
-
add_ref_import_specifier(
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
metadata: { path: [] },
|
|
4488
|
-
},
|
|
4489
|
-
local: {
|
|
4490
|
-
type: 'Identifier',
|
|
4491
|
-
name: NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
4492
|
-
metadata: { path: [] },
|
|
4493
|
-
},
|
|
4494
|
-
metadata: { path: [] },
|
|
4495
|
-
});
|
|
4490
|
+
add_ref_import_specifier(
|
|
4491
|
+
ref_imports,
|
|
4492
|
+
normalize_spread_props_source,
|
|
4493
|
+
b.import_specifier('normalize_spread_props', NORMALIZE_SPREAD_PROPS_INTERNAL_NAME),
|
|
4494
|
+
);
|
|
4496
4495
|
}
|
|
4497
4496
|
|
|
4498
4497
|
for (const [source, ref_specifiers] of ref_imports) {
|
|
4499
|
-
imports.push(
|
|
4500
|
-
type: 'ImportDeclaration',
|
|
4501
|
-
specifiers: ref_specifiers,
|
|
4502
|
-
source: {
|
|
4503
|
-
type: 'Literal',
|
|
4504
|
-
value: source,
|
|
4505
|
-
raw: `'${source}'`,
|
|
4506
|
-
},
|
|
4507
|
-
metadata: { path: [] },
|
|
4508
|
-
});
|
|
4498
|
+
imports.push(b.import_declaration(ref_specifiers, source));
|
|
4509
4499
|
}
|
|
4510
4500
|
|
|
4511
4501
|
if (imports.length > 0) {
|
|
@@ -4553,130 +4543,343 @@ function create_render_if_statement(node, transform_context) {
|
|
|
4553
4543
|
true,
|
|
4554
4544
|
);
|
|
4555
4545
|
alternate = set_loc(
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
body: alternate_has_hooks
|
|
4546
|
+
b.block(
|
|
4547
|
+
alternate_has_hooks
|
|
4559
4548
|
? hook_safe_render_statements(alternate_body, undefined, transform_context)
|
|
4560
4549
|
: build_render_statements(alternate_body, true, transform_context),
|
|
4561
|
-
|
|
4562
|
-
}),
|
|
4550
|
+
),
|
|
4563
4551
|
node.alternate,
|
|
4564
4552
|
);
|
|
4565
4553
|
}
|
|
4566
4554
|
}
|
|
4567
4555
|
|
|
4568
4556
|
return set_loc(
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
type: 'BlockStatement',
|
|
4575
|
-
body: consequent_has_hooks
|
|
4557
|
+
b.if(
|
|
4558
|
+
node.test,
|
|
4559
|
+
set_loc(
|
|
4560
|
+
b.block(
|
|
4561
|
+
consequent_has_hooks
|
|
4576
4562
|
? hook_safe_render_statements(consequent_body, undefined, transform_context)
|
|
4577
4563
|
: build_render_statements(consequent_body, true, transform_context),
|
|
4578
|
-
|
|
4579
|
-
}),
|
|
4564
|
+
),
|
|
4580
4565
|
node.consequent,
|
|
4581
4566
|
),
|
|
4582
4567
|
alternate,
|
|
4583
|
-
|
|
4568
|
+
),
|
|
4584
4569
|
node,
|
|
4585
4570
|
);
|
|
4586
4571
|
}
|
|
4587
4572
|
|
|
4588
4573
|
/**
|
|
4589
|
-
*
|
|
4590
|
-
*
|
|
4591
|
-
*
|
|
4574
|
+
* Per-source-case information used by the switch lift to decide whether each
|
|
4575
|
+
* case body needs to be hoisted into its own helper component or can stay
|
|
4576
|
+
* inline.
|
|
4577
|
+
*
|
|
4578
|
+
* `own_body` is everything in the case's `consequent` up to (and including for
|
|
4579
|
+
* `return <expr>`, excluding for `break` / bare `return;`) the first
|
|
4580
|
+
* terminator. `has_terminator` records whether such a terminator was seen.
|
|
4581
|
+
*
|
|
4582
|
+
* @param {any[]} consequent
|
|
4583
|
+
* @returns {{ own_body: any[], has_terminator: boolean }}
|
|
4592
4584
|
*/
|
|
4593
|
-
function
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4585
|
+
function summarize_switch_case_body(consequent) {
|
|
4586
|
+
const own_body = [];
|
|
4587
|
+
let has_terminator = false;
|
|
4588
|
+
for (const child of consequent) {
|
|
4589
|
+
if (child.type === 'BreakStatement') {
|
|
4590
|
+
has_terminator = true;
|
|
4591
|
+
break;
|
|
4592
|
+
}
|
|
4593
|
+
if (child.type === 'ReturnStatement' && child.argument == null) {
|
|
4594
|
+
has_terminator = true;
|
|
4595
|
+
break;
|
|
4596
|
+
}
|
|
4597
|
+
own_body.push(child);
|
|
4598
|
+
if (child.type === 'ReturnStatement') {
|
|
4599
|
+
// `return <expr>;` — keep it in own_body so build_render_statements
|
|
4600
|
+
// can emit it as the terminal return for this case, then stop
|
|
4601
|
+
// collecting further nodes.
|
|
4602
|
+
has_terminator = true;
|
|
4603
|
+
break;
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
return { own_body, has_terminator };
|
|
4602
4607
|
}
|
|
4603
4608
|
|
|
4604
4609
|
/**
|
|
4605
|
-
*
|
|
4606
|
-
*
|
|
4610
|
+
* Clone a helper's `component_element` for embedding in another case arm or
|
|
4611
|
+
* inside another helper's body. Locations are stripped because the same
|
|
4612
|
+
* element appears in multiple positions; only the helper's *definition* (the
|
|
4613
|
+
* lifted function) keeps the source position so editor IntelliSense doesn't
|
|
4614
|
+
* see double/triple hits per source range.
|
|
4615
|
+
*
|
|
4616
|
+
* @param {{ component_element: ESTreeJSX.JSXElement }} helper
|
|
4607
4617
|
* @returns {any}
|
|
4608
4618
|
*/
|
|
4609
|
-
function
|
|
4610
|
-
|
|
4619
|
+
export function clone_switch_helper_invocation(helper) {
|
|
4620
|
+
return clone_expression_node(helper.component_element, false);
|
|
4621
|
+
}
|
|
4611
4622
|
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4623
|
+
/**
|
|
4624
|
+
* Plan the switch lift: decide which case bodies to hoist into their own
|
|
4625
|
+
* helper components, build them in reverse so each helper can chain into the
|
|
4626
|
+
* next, and return everything callers need to construct a target-specific
|
|
4627
|
+
* switch shape (a JS `switch` for React/Preact/Vue or `<Switch>/<Match>` for
|
|
4628
|
+
* Solid). Centralizes the lift bookkeeping so both consumers see the same
|
|
4629
|
+
* hook-detection rules, duplication analysis, and helper-id numbering.
|
|
4630
|
+
*
|
|
4631
|
+
* Returned helpers — when non-null — are already constructed via
|
|
4632
|
+
* `create_hook_safe_helper`, which is the same path hook-bearing case bodies
|
|
4633
|
+
* have always used. Locally-scoped helpers have their declarations in
|
|
4634
|
+
* `setup_statements`; module-scoped helpers (the client transform default on
|
|
4635
|
+
* React, Vue, and Solid) already pushed their declarations into
|
|
4636
|
+
* `transform_context.helper_state.helpers`, so `setup_statements` is empty.
|
|
4637
|
+
*
|
|
4638
|
+
* @param {any} switch_node
|
|
4639
|
+
* @param {TransformContext} transform_context
|
|
4640
|
+
* @returns {{
|
|
4641
|
+
* case_info: Array<{ own_body: any[], has_terminator: boolean }>,
|
|
4642
|
+
* case_helpers: Array<{ setup_statements: any[], component_element: ESTreeJSX.JSXElement } | null>,
|
|
4643
|
+
* find_next_helper_after: (from_index: number) => { component_element: ESTreeJSX.JSXElement } | null,
|
|
4644
|
+
* setup_statements: any[],
|
|
4645
|
+
* }}
|
|
4646
|
+
*/
|
|
4647
|
+
export function plan_switch_lift(switch_node, transform_context) {
|
|
4648
|
+
const case_info = switch_node.cases.map((/** @type {any} */ c) => {
|
|
4649
|
+
const consequent = flatten_switch_consequent(c.consequent || []);
|
|
4650
|
+
return summarize_switch_case_body(consequent);
|
|
4651
|
+
});
|
|
4618
4652
|
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
})
|
|
4626
|
-
|
|
4653
|
+
// A case body needs to be lifted iff (a) it would render in more than one
|
|
4654
|
+
// arm after fall-through expansion, or (b) it contains hooks (which always
|
|
4655
|
+
// went through the lift pipeline before this change). Duplication happens
|
|
4656
|
+
// exactly when the previous case has no terminator — that's the only way
|
|
4657
|
+
// an earlier arm can reach this body via JS fall-through semantics.
|
|
4658
|
+
const needs_helper = case_info.map(
|
|
4659
|
+
(/** @type {{ own_body: any[], has_terminator: boolean }} */ info, /** @type {number} */ k) => {
|
|
4660
|
+
if (info.own_body.length === 0) return false;
|
|
4661
|
+
if (body_contains_top_level_hook_call(info.own_body, transform_context, true)) {
|
|
4662
|
+
return true;
|
|
4663
|
+
}
|
|
4664
|
+
if (k === 0) return false;
|
|
4665
|
+
return !case_info[k - 1].has_terminator;
|
|
4666
|
+
},
|
|
4667
|
+
);
|
|
4627
4668
|
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4669
|
+
// Pre-allocate helper ids in source order so the snapshot's
|
|
4670
|
+
// `StatementBodyHook<N>` numbering reads top-to-bottom by case position
|
|
4671
|
+
// even though we build helpers in reverse below.
|
|
4672
|
+
/** @type {Array<AST.Identifier | null>} */
|
|
4673
|
+
const helper_ids = needs_helper.map((/** @type {boolean} */ needs) =>
|
|
4674
|
+
needs
|
|
4675
|
+
? create_generated_identifier(create_local_statement_component_name(transform_context))
|
|
4676
|
+
: null,
|
|
4677
|
+
);
|
|
4631
4678
|
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
if (render_nodes.length > 0 && !has_terminal) {
|
|
4635
|
-
case_body.push(create_component_return_statement(render_nodes, switch_case));
|
|
4636
|
-
} else if (!has_terminal) {
|
|
4637
|
-
case_body.push(child);
|
|
4638
|
-
}
|
|
4639
|
-
has_terminal = true;
|
|
4640
|
-
break;
|
|
4641
|
-
}
|
|
4679
|
+
/** @type {Array<{ setup_statements: any[], component_element: ESTreeJSX.JSXElement } | null>} */
|
|
4680
|
+
const case_helpers = new Array(switch_node.cases.length).fill(null);
|
|
4642
4681
|
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4682
|
+
/**
|
|
4683
|
+
* Find the next downstream helper this arm chains into when it has no
|
|
4684
|
+
* terminator: scan forward past any empty cases until we hit either a
|
|
4685
|
+
* helper-bearing case or a case whose body has a terminator (which stops
|
|
4686
|
+
* the chain — JS would have `break`/`return`ed out at that point).
|
|
4687
|
+
*
|
|
4688
|
+
* @param {number} from_index
|
|
4689
|
+
* @returns {{ component_element: ESTreeJSX.JSXElement } | null}
|
|
4690
|
+
*/
|
|
4691
|
+
function find_next_helper_after(from_index) {
|
|
4692
|
+
for (let j = from_index + 1; j < switch_node.cases.length; j++) {
|
|
4693
|
+
if (case_helpers[j]) return case_helpers[j];
|
|
4694
|
+
if (case_info[j].has_terminator) return null;
|
|
4647
4695
|
}
|
|
4696
|
+
return null;
|
|
4697
|
+
}
|
|
4648
4698
|
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4699
|
+
for (let i = switch_node.cases.length - 1; i >= 0; i--) {
|
|
4700
|
+
if (!needs_helper[i]) continue;
|
|
4701
|
+
const { own_body, has_terminator } = case_info[i];
|
|
4702
|
+
|
|
4703
|
+
let helper_body = own_body;
|
|
4704
|
+
if (!has_terminator) {
|
|
4705
|
+
const next_helper = find_next_helper_after(i);
|
|
4706
|
+
if (next_helper) {
|
|
4707
|
+
helper_body = [...own_body, clone_switch_helper_invocation(next_helper)];
|
|
4708
|
+
}
|
|
4655
4709
|
}
|
|
4710
|
+
|
|
4711
|
+
case_helpers[i] = create_hook_safe_helper(
|
|
4712
|
+
helper_body,
|
|
4713
|
+
undefined,
|
|
4714
|
+
switch_node.cases[i],
|
|
4715
|
+
transform_context,
|
|
4716
|
+
/** @type {any} */ (helper_ids[i]),
|
|
4717
|
+
);
|
|
4656
4718
|
}
|
|
4657
4719
|
|
|
4658
|
-
|
|
4659
|
-
|
|
4720
|
+
// Hoist all helpers' setup statements above the switch in source order so
|
|
4721
|
+
// the switch body stays a pure dispatcher.
|
|
4722
|
+
const setup_statements = [];
|
|
4723
|
+
for (const helper of case_helpers) {
|
|
4724
|
+
if (helper) setup_statements.push(...helper.setup_statements);
|
|
4660
4725
|
}
|
|
4661
4726
|
|
|
4662
|
-
return
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
}
|
|
4727
|
+
return {
|
|
4728
|
+
case_info,
|
|
4729
|
+
case_helpers,
|
|
4730
|
+
find_next_helper_after,
|
|
4731
|
+
setup_statements,
|
|
4732
|
+
};
|
|
4668
4733
|
}
|
|
4669
4734
|
|
|
4670
4735
|
/**
|
|
4671
|
-
*
|
|
4736
|
+
* Switch lift for fall-through deduplication. Reuses the same `create_hook_safe_helper`
|
|
4737
|
+
* pipeline as hook-bearing case bodies: every case whose body would otherwise
|
|
4738
|
+
* appear in 2+ arms (because the previous case had no `break` / `return`) is
|
|
4739
|
+
* hoisted into its own helper component, and each upstream arm references the
|
|
4740
|
+
* next helper at the end of its own body to materialize JS fall-through at
|
|
4741
|
+
* render time. Cases whose bodies live in exactly one arm stay inline so the
|
|
4742
|
+
* common (break-terminated) shape compiles to the same simple switch as before
|
|
4743
|
+
* the lift was introduced.
|
|
4744
|
+
*
|
|
4745
|
+
* The chain pattern:
|
|
4746
|
+
* helper_idle = () => <><Online/><Helper_active/></>
|
|
4747
|
+
* helper_active = () => <><Away/><Helper_offline/></>
|
|
4748
|
+
* helper_offline = () => <Offline/>
|
|
4749
|
+
*
|
|
4750
|
+
* case "idle": return <Helper_idle/>
|
|
4751
|
+
* case "active": return <Helper_active/>
|
|
4752
|
+
* case "offline": return <Helper_offline/>
|
|
4753
|
+
*
|
|
4754
|
+
* Each case body appears exactly once in the generated module — matching how
|
|
4755
|
+
* we already handle hook-bearing case bodies — which keeps the bundle from
|
|
4756
|
+
* growing quadratically in case count and means editor mappings are 1:1.
|
|
4757
|
+
*
|
|
4758
|
+
* @param {any} switch_node
|
|
4759
|
+
* @param {TransformContext} transform_context
|
|
4760
|
+
* @returns {{ setup_statements: any[], switch_statement: any }}
|
|
4672
4761
|
*/
|
|
4673
|
-
function
|
|
4762
|
+
function build_switch_with_lift(switch_node, transform_context) {
|
|
4763
|
+
const { case_info, case_helpers, find_next_helper_after, setup_statements } = plan_switch_lift(
|
|
4764
|
+
switch_node,
|
|
4765
|
+
transform_context,
|
|
4766
|
+
);
|
|
4767
|
+
|
|
4768
|
+
const new_cases = switch_node.cases.map(
|
|
4769
|
+
(/** @type {any} */ original_case, /** @type {number} */ i) => {
|
|
4770
|
+
const helper = case_helpers[i];
|
|
4771
|
+
if (helper) {
|
|
4772
|
+
return /** @type {any} */ ({
|
|
4773
|
+
type: 'SwitchCase',
|
|
4774
|
+
test: original_case.test,
|
|
4775
|
+
consequent: [
|
|
4776
|
+
create_component_return_statement([helper.component_element], original_case),
|
|
4777
|
+
],
|
|
4778
|
+
metadata: { path: [] },
|
|
4779
|
+
});
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4782
|
+
const { own_body, has_terminator } = case_info[i];
|
|
4783
|
+
|
|
4784
|
+
if (own_body.length === 0 && !has_terminator) {
|
|
4785
|
+
// Alias-pattern empty case (`case 'a': case 'b': ...`) — keep
|
|
4786
|
+
// the arm body empty so JS falls through to the next case at
|
|
4787
|
+
// runtime, where the helper invocation actually lives.
|
|
4788
|
+
return /** @type {any} */ ({
|
|
4789
|
+
type: 'SwitchCase',
|
|
4790
|
+
test: original_case.test,
|
|
4791
|
+
consequent: [],
|
|
4792
|
+
metadata: { path: [] },
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
|
|
4796
|
+
const case_body = [];
|
|
4797
|
+
const render_nodes = [];
|
|
4798
|
+
let has_terminal = false;
|
|
4799
|
+
|
|
4800
|
+
for (const child of own_body) {
|
|
4801
|
+
if (is_bare_return_statement(child)) {
|
|
4802
|
+
case_body.push(create_component_return_statement(render_nodes, child));
|
|
4803
|
+
has_terminal = true;
|
|
4804
|
+
break;
|
|
4805
|
+
}
|
|
4806
|
+
if (child.type === 'ReturnStatement') {
|
|
4807
|
+
case_body.push(child);
|
|
4808
|
+
has_terminal = true;
|
|
4809
|
+
break;
|
|
4810
|
+
}
|
|
4811
|
+
if (is_jsx_child(child)) {
|
|
4812
|
+
render_nodes.push(to_jsx_child(child, transform_context));
|
|
4813
|
+
} else if (is_bare_render_expression(child)) {
|
|
4814
|
+
render_nodes.push(to_jsx_expression_container(child, child));
|
|
4815
|
+
} else {
|
|
4816
|
+
case_body.push(child);
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
|
|
4820
|
+
if (!has_terminal && !has_terminator) {
|
|
4821
|
+
const next_helper = find_next_helper_after(i);
|
|
4822
|
+
if (next_helper) {
|
|
4823
|
+
render_nodes.push(clone_switch_helper_invocation(next_helper));
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
|
|
4827
|
+
if (!has_terminal) {
|
|
4828
|
+
if (render_nodes.length > 0) {
|
|
4829
|
+
case_body.push(create_component_return_statement(render_nodes, original_case));
|
|
4830
|
+
} else if (has_terminator) {
|
|
4831
|
+
// Empty body with explicit `break;` / bare `return;` — keep
|
|
4832
|
+
// a `break` so JS doesn't fall through into the next case
|
|
4833
|
+
// (which may now hold the lifted helper invocation).
|
|
4834
|
+
case_body.push(
|
|
4835
|
+
/** @type {any} */ ({
|
|
4836
|
+
type: 'BreakStatement',
|
|
4837
|
+
label: null,
|
|
4838
|
+
metadata: { path: [] },
|
|
4839
|
+
}),
|
|
4840
|
+
);
|
|
4841
|
+
} else if (case_body.length > 0) {
|
|
4842
|
+
// Statements-only inline case without terminator. We've
|
|
4843
|
+
// already inlined the downstream chain via the helper
|
|
4844
|
+
// reference above, so emit a `break` to stop the runtime
|
|
4845
|
+
// from re-running downstream statements via JS fall-through.
|
|
4846
|
+
case_body.push(
|
|
4847
|
+
/** @type {any} */ ({
|
|
4848
|
+
type: 'BreakStatement',
|
|
4849
|
+
label: null,
|
|
4850
|
+
metadata: { path: [] },
|
|
4851
|
+
}),
|
|
4852
|
+
);
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4856
|
+
return /** @type {any} */ ({
|
|
4857
|
+
type: 'SwitchCase',
|
|
4858
|
+
test: original_case.test,
|
|
4859
|
+
consequent: case_body,
|
|
4860
|
+
metadata: { path: [] },
|
|
4861
|
+
});
|
|
4862
|
+
},
|
|
4863
|
+
);
|
|
4864
|
+
|
|
4674
4865
|
return {
|
|
4675
|
-
|
|
4676
|
-
|
|
4866
|
+
setup_statements,
|
|
4867
|
+
switch_statement: /** @type {any} */ ({
|
|
4868
|
+
type: 'SwitchStatement',
|
|
4869
|
+
discriminant: switch_node.discriminant,
|
|
4870
|
+
cases: new_cases,
|
|
4871
|
+
metadata: { path: [] },
|
|
4872
|
+
}),
|
|
4677
4873
|
};
|
|
4678
4874
|
}
|
|
4679
4875
|
|
|
4876
|
+
/**
|
|
4877
|
+
* @returns {any}
|
|
4878
|
+
*/
|
|
4879
|
+
function create_null_return_statement() {
|
|
4880
|
+
return b.return(b.literal(null));
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4680
4883
|
/**
|
|
4681
4884
|
* @param {AST.Expression} expression
|
|
4682
4885
|
* @param {any} [source_node]
|
|
@@ -4775,10 +4978,7 @@ function normalize_named_ref_attributes(attrs, is_host, transform_context) {
|
|
|
4775
4978
|
return {
|
|
4776
4979
|
...attr,
|
|
4777
4980
|
metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
|
|
4778
|
-
name:
|
|
4779
|
-
attr.name?.type === 'JSXIdentifier'
|
|
4780
|
-
? { ...attr.name, name: 'ref' }
|
|
4781
|
-
: { type: 'Identifier', name: 'ref', metadata: { path: [] } },
|
|
4981
|
+
name: attr.name?.type === 'JSXIdentifier' ? { ...attr.name, name: 'ref' } : b.id('ref'),
|
|
4782
4982
|
};
|
|
4783
4983
|
});
|
|
4784
4984
|
}
|
|
@@ -4919,6 +5119,7 @@ function wrap_jsx_setup_declarations(expression, in_jsx_child) {
|
|
|
4919
5119
|
[],
|
|
4920
5120
|
b.block([...declarations, b.return(return_expression)], expression),
|
|
4921
5121
|
false,
|
|
5122
|
+
undefined,
|
|
4922
5123
|
expression,
|
|
4923
5124
|
),
|
|
4924
5125
|
);
|
|
@@ -5070,22 +5271,8 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
|
|
|
5070
5271
|
|
|
5071
5272
|
const merged_value =
|
|
5072
5273
|
strategy === 'merge-refs'
|
|
5073
|
-
?
|
|
5074
|
-
|
|
5075
|
-
callee: {
|
|
5076
|
-
type: 'Identifier',
|
|
5077
|
-
name: MERGE_REFS_INTERNAL_NAME,
|
|
5078
|
-
metadata: { path: [] },
|
|
5079
|
-
},
|
|
5080
|
-
arguments: ref_exprs,
|
|
5081
|
-
optional: false,
|
|
5082
|
-
metadata: { path: [] },
|
|
5083
|
-
})
|
|
5084
|
-
: /** @type {any} */ ({
|
|
5085
|
-
type: 'ArrayExpression',
|
|
5086
|
-
elements: ref_exprs,
|
|
5087
|
-
metadata: { path: [] },
|
|
5088
|
-
});
|
|
5274
|
+
? b.call(b.id(MERGE_REFS_INTERNAL_NAME), ...ref_exprs)
|
|
5275
|
+
: b.array(ref_exprs);
|
|
5089
5276
|
|
|
5090
5277
|
if (strategy === 'merge-refs') {
|
|
5091
5278
|
transform_context.needs_merge_refs = true;
|
|
@@ -5099,11 +5286,7 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
|
|
|
5099
5286
|
const merged_name = build_jsx_id('ref', source_attr?.name);
|
|
5100
5287
|
const merged_attr = build_jsx_attribute(
|
|
5101
5288
|
merged_name,
|
|
5102
|
-
|
|
5103
|
-
type: 'JSXExpressionContainer',
|
|
5104
|
-
expression: merged_value,
|
|
5105
|
-
metadata: { path: [] },
|
|
5106
|
-
}),
|
|
5289
|
+
b.jsx_expression_container(merged_value),
|
|
5107
5290
|
false,
|
|
5108
5291
|
source_attr,
|
|
5109
5292
|
);
|
|
@@ -5138,6 +5321,8 @@ function is_jsx_ref_attribute(attr) {
|
|
|
5138
5321
|
export const MERGE_REFS_INTERNAL_NAME = '__mergeRefs';
|
|
5139
5322
|
export const CREATE_REF_PROP_INTERNAL_NAME = '__create_ref_prop';
|
|
5140
5323
|
export const NORMALIZE_SPREAD_PROPS_INTERNAL_NAME = '__normalize_spread_props';
|
|
5324
|
+
export const MAP_ITERABLE_INTERNAL_NAME = '__map_iterable';
|
|
5325
|
+
export const ITERATION_VALUE_INTERNAL_NAME = '__IterationValue';
|
|
5141
5326
|
|
|
5142
5327
|
/**
|
|
5143
5328
|
* @param {any} attr
|
|
@@ -5211,10 +5396,7 @@ export function to_jsx_attribute(attr, transform_context) {
|
|
|
5211
5396
|
attr_name.type === 'Identifier' &&
|
|
5212
5397
|
attr_name.name === 'class'
|
|
5213
5398
|
) {
|
|
5214
|
-
attr_name = set_loc(
|
|
5215
|
-
/** @type {any} */ ({ type: 'Identifier', name: 'className', metadata: { path: [] } }),
|
|
5216
|
-
attr.name,
|
|
5217
|
-
);
|
|
5399
|
+
attr_name = set_loc(b.id('className'), attr.name);
|
|
5218
5400
|
}
|
|
5219
5401
|
|
|
5220
5402
|
const name =
|
|
@@ -5277,16 +5459,7 @@ function create_ref_prop_call(node, transform_context) {
|
|
|
5277
5459
|
|
|
5278
5460
|
if (argument.type === 'Identifier' || argument.type === 'MemberExpression') {
|
|
5279
5461
|
args.push(
|
|
5280
|
-
b.arrow(
|
|
5281
|
-
[b.id('v')],
|
|
5282
|
-
/** @type {any} */ ({
|
|
5283
|
-
type: 'AssignmentExpression',
|
|
5284
|
-
operator: '=',
|
|
5285
|
-
left: clone_expression_node(argument, false),
|
|
5286
|
-
right: b.id('v'),
|
|
5287
|
-
metadata: { path: [] },
|
|
5288
|
-
}),
|
|
5289
|
-
),
|
|
5462
|
+
b.arrow([b.id('v')], b.assignment('=', clone_expression_node(argument, false), b.id('v'))),
|
|
5290
5463
|
);
|
|
5291
5464
|
}
|
|
5292
5465
|
|
|
@@ -5300,57 +5473,19 @@ function create_ref_prop_call(node, transform_context) {
|
|
|
5300
5473
|
*/
|
|
5301
5474
|
function dynamic_element_to_jsx_child(node, transform_context) {
|
|
5302
5475
|
const dynamic_id = set_loc(create_generated_identifier('DynamicElement'), node.id);
|
|
5303
|
-
const alias_declaration = set_loc(
|
|
5304
|
-
/** @type {any} */ ({
|
|
5305
|
-
type: 'VariableDeclaration',
|
|
5306
|
-
kind: 'const',
|
|
5307
|
-
declarations: [
|
|
5308
|
-
{
|
|
5309
|
-
type: 'VariableDeclarator',
|
|
5310
|
-
id: dynamic_id,
|
|
5311
|
-
init: clone_expression_node(node.id),
|
|
5312
|
-
metadata: { path: [] },
|
|
5313
|
-
},
|
|
5314
|
-
],
|
|
5315
|
-
metadata: { path: [] },
|
|
5316
|
-
}),
|
|
5317
|
-
node,
|
|
5318
|
-
);
|
|
5476
|
+
const alias_declaration = set_loc(b.const(dynamic_id, clone_expression_node(node.id)), node);
|
|
5319
5477
|
const jsx_element = create_dynamic_jsx_element(dynamic_id, node, transform_context);
|
|
5320
5478
|
|
|
5321
5479
|
return to_jsx_expression_container(
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
{
|
|
5332
|
-
type: 'ReturnStatement',
|
|
5333
|
-
argument: {
|
|
5334
|
-
type: 'ConditionalExpression',
|
|
5335
|
-
test: clone_identifier(dynamic_id),
|
|
5336
|
-
consequent: jsx_element,
|
|
5337
|
-
alternate: create_null_literal(),
|
|
5338
|
-
metadata: { path: [] },
|
|
5339
|
-
},
|
|
5340
|
-
metadata: { path: [] },
|
|
5341
|
-
},
|
|
5342
|
-
],
|
|
5343
|
-
metadata: { path: [] },
|
|
5344
|
-
}),
|
|
5345
|
-
async: false,
|
|
5346
|
-
generator: false,
|
|
5347
|
-
expression: false,
|
|
5348
|
-
metadata: { path: [] },
|
|
5349
|
-
},
|
|
5350
|
-
arguments: [],
|
|
5351
|
-
optional: false,
|
|
5352
|
-
metadata: { path: [] },
|
|
5353
|
-
}),
|
|
5480
|
+
b.call(
|
|
5481
|
+
b.arrow(
|
|
5482
|
+
[],
|
|
5483
|
+
b.block([
|
|
5484
|
+
alias_declaration,
|
|
5485
|
+
b.return(b.conditional(clone_identifier(dynamic_id), jsx_element, create_null_literal())),
|
|
5486
|
+
]),
|
|
5487
|
+
),
|
|
5488
|
+
),
|
|
5354
5489
|
node,
|
|
5355
5490
|
);
|
|
5356
5491
|
}
|