@tsrx/core 0.1.3 → 0.1.4
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 +1 -1
- package/src/plugin.js +264 -230
- package/src/transform/jsx/index.js +1001 -101
|
@@ -57,6 +57,79 @@ import {
|
|
|
57
57
|
} from '../jsx-interleave.js';
|
|
58
58
|
import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
|
|
59
59
|
|
|
60
|
+
const HOOK_OUTER_ASSIGNMENT_ERROR =
|
|
61
|
+
'Hook calls inside conditional or repeated TSRX scopes must keep their results local to the generated hook component.';
|
|
62
|
+
const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
|
|
63
|
+
'Hook callbacks inside conditional or repeated TSRX scopes must not mutate bindings declared outside the generated hook component.';
|
|
64
|
+
const TEMPLATE_FRAGMENT_ERROR =
|
|
65
|
+
'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {AST.Node} node
|
|
69
|
+
* @param {TransformContext} transform_context
|
|
70
|
+
*/
|
|
71
|
+
function report_html_template_unsupported_error(node, transform_context) {
|
|
72
|
+
// this should be a fatal error so we don't pass the errors collection,
|
|
73
|
+
// since we don't have a transform for the Html node
|
|
74
|
+
error(
|
|
75
|
+
`\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
|
|
76
|
+
transform_context.filename,
|
|
77
|
+
node,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {AST.Node} node
|
|
83
|
+
* @param {TransformContext} transform_context
|
|
84
|
+
*/
|
|
85
|
+
function report_jsx_fragment_in_tsrx_error(node, transform_context) {
|
|
86
|
+
error(
|
|
87
|
+
TEMPLATE_FRAGMENT_ERROR,
|
|
88
|
+
transform_context.filename,
|
|
89
|
+
node,
|
|
90
|
+
transform_context.errors,
|
|
91
|
+
transform_context.comments,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {AST.Node} node
|
|
97
|
+
* @param {string[]} names
|
|
98
|
+
* @param {string} hook_name
|
|
99
|
+
* @param {TransformContext} transform_context
|
|
100
|
+
* @returns {void}
|
|
101
|
+
*/
|
|
102
|
+
function report_hook_outer_assignment_error(node, names, hook_name, transform_context) {
|
|
103
|
+
const target =
|
|
104
|
+
names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
|
|
105
|
+
error(
|
|
106
|
+
`${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.`,
|
|
107
|
+
transform_context.filename,
|
|
108
|
+
node,
|
|
109
|
+
transform_context.errors,
|
|
110
|
+
transform_context.comments,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {AST.Node} node
|
|
116
|
+
* @param {string[]} names
|
|
117
|
+
* @param {string} hook_name
|
|
118
|
+
* @param {TransformContext} transform_context
|
|
119
|
+
* @returns {void}
|
|
120
|
+
*/
|
|
121
|
+
function report_hook_callback_outer_mutation_error(node, names, hook_name, transform_context) {
|
|
122
|
+
const target =
|
|
123
|
+
names.length === 1 ? `\`${names[0]}\`` : names.map((name) => `\`${name}\``).join(', ');
|
|
124
|
+
error(
|
|
125
|
+
`${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.`,
|
|
126
|
+
transform_context.filename,
|
|
127
|
+
node,
|
|
128
|
+
transform_context.errors,
|
|
129
|
+
transform_context.comments,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
60
133
|
/**
|
|
61
134
|
* Local alias for the shared `JsxTransformContext`. Kept as a typedef so the
|
|
62
135
|
* rest of this file's `@param {TransformContext}` annotations don't all have
|
|
@@ -449,9 +522,12 @@ export function createJsxTransform(platform) {
|
|
|
449
522
|
// (e.g. segments.js reading node.value.metadata.is_component on class
|
|
450
523
|
// methods) don't trip on an undefined metadata object. Ripple's analyze
|
|
451
524
|
// phase does this via visit_function; tsrx-react has no analyze phase.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
525
|
+
// If a plain JS function contains a hook-bearing <tsrx> expression,
|
|
526
|
+
// give it a temporary helper scope so extracted hook components can
|
|
527
|
+
// be emitted with stable identities just like component-body helpers.
|
|
528
|
+
FunctionDeclaration: transform_function_with_hook_helpers,
|
|
529
|
+
FunctionExpression: transform_function_with_hook_helpers,
|
|
530
|
+
ArrowFunctionExpression: transform_function_with_hook_helpers,
|
|
455
531
|
|
|
456
532
|
RefExpression(node) {
|
|
457
533
|
return create_ref_prop_call(node, transform_context);
|
|
@@ -1250,6 +1326,156 @@ function create_helper_state(base_name) {
|
|
|
1250
1326
|
};
|
|
1251
1327
|
}
|
|
1252
1328
|
|
|
1329
|
+
/**
|
|
1330
|
+
* @param {any} node
|
|
1331
|
+
* @param {{ next: () => any, state: TransformContext }} context
|
|
1332
|
+
* @returns {any}
|
|
1333
|
+
*/
|
|
1334
|
+
function transform_function_with_hook_helpers(node, { next, state }) {
|
|
1335
|
+
if (state.helper_state || !function_contains_hook_bearing_tsrx(node, state)) {
|
|
1336
|
+
return ensure_function_metadata(node, { next });
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
const helper_state = create_helper_state(get_function_helper_base_name(node));
|
|
1340
|
+
const saved_helper_state = state.helper_state;
|
|
1341
|
+
const saved_bindings = state.available_bindings;
|
|
1342
|
+
|
|
1343
|
+
state.helper_state = helper_state;
|
|
1344
|
+
state.available_bindings = collect_function_scope_bindings(node);
|
|
1345
|
+
|
|
1346
|
+
const inner = /** @type {any} */ (next() ?? node);
|
|
1347
|
+
|
|
1348
|
+
state.helper_state = saved_helper_state;
|
|
1349
|
+
state.available_bindings = saved_bindings;
|
|
1350
|
+
|
|
1351
|
+
ensure_function_metadata(inner, { next: () => inner });
|
|
1352
|
+
if (helper_state.helpers.length || helper_state.statics.length) {
|
|
1353
|
+
inner.metadata = {
|
|
1354
|
+
...(inner.metadata || {}),
|
|
1355
|
+
generated_helpers: helper_state.helpers,
|
|
1356
|
+
generated_statics: helper_state.statics,
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
return inner;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
/**
|
|
1364
|
+
* @param {any} node
|
|
1365
|
+
* @returns {string}
|
|
1366
|
+
*/
|
|
1367
|
+
function get_function_helper_base_name(node) {
|
|
1368
|
+
if (node.id?.type === 'Identifier') {
|
|
1369
|
+
return node.id.name;
|
|
1370
|
+
}
|
|
1371
|
+
return 'Tsrx';
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* @param {any} node
|
|
1376
|
+
* @returns {Map<string, AST.Identifier>}
|
|
1377
|
+
*/
|
|
1378
|
+
function collect_function_scope_bindings(node) {
|
|
1379
|
+
const bindings = collect_param_bindings(node.params || []);
|
|
1380
|
+
collect_descendant_declaration_bindings(node.body, bindings);
|
|
1381
|
+
return bindings;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* @param {any} node
|
|
1386
|
+
* @param {Map<string, AST.Identifier>} bindings
|
|
1387
|
+
* @returns {void}
|
|
1388
|
+
*/
|
|
1389
|
+
function collect_descendant_declaration_bindings(node, bindings) {
|
|
1390
|
+
if (!node || typeof node !== 'object') {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (node.type === 'VariableDeclaration') {
|
|
1395
|
+
for (const declaration of node.declarations || []) {
|
|
1396
|
+
collect_pattern_bindings(declaration.id, bindings);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
if (
|
|
1401
|
+
(node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') &&
|
|
1402
|
+
node.id?.type === 'Identifier'
|
|
1403
|
+
) {
|
|
1404
|
+
bindings.set(node.id.name, node.id);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
if (
|
|
1408
|
+
node.type === 'FunctionDeclaration' ||
|
|
1409
|
+
node.type === 'FunctionExpression' ||
|
|
1410
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
1411
|
+
node.type === 'Component'
|
|
1412
|
+
) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (Array.isArray(node)) {
|
|
1417
|
+
for (const child of node) {
|
|
1418
|
+
collect_descendant_declaration_bindings(child, bindings);
|
|
1419
|
+
}
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
for (const key of Object.keys(node)) {
|
|
1424
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
1425
|
+
continue;
|
|
1426
|
+
}
|
|
1427
|
+
collect_descendant_declaration_bindings(node[key], bindings);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* @param {any} node
|
|
1433
|
+
* @param {TransformContext} transform_context
|
|
1434
|
+
* @returns {boolean}
|
|
1435
|
+
*/
|
|
1436
|
+
function function_contains_hook_bearing_tsrx(node, transform_context) {
|
|
1437
|
+
return node_contains_hook_bearing_tsrx(node.body, transform_context);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* @param {any} node
|
|
1442
|
+
* @param {TransformContext} transform_context
|
|
1443
|
+
* @returns {boolean}
|
|
1444
|
+
*/
|
|
1445
|
+
function node_contains_hook_bearing_tsrx(node, transform_context) {
|
|
1446
|
+
if (!node || typeof node !== 'object') {
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
if (Array.isArray(node)) {
|
|
1451
|
+
return node.some((child) => node_contains_hook_bearing_tsrx(child, transform_context));
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
if (node.type === 'Tsrx') {
|
|
1455
|
+
return body_contains_top_level_hook_call(node.children || [], transform_context, true);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
if (
|
|
1459
|
+
node.type === 'FunctionDeclaration' ||
|
|
1460
|
+
node.type === 'FunctionExpression' ||
|
|
1461
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
1462
|
+
node.type === 'Component'
|
|
1463
|
+
) {
|
|
1464
|
+
return false;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
for (const key of Object.keys(node)) {
|
|
1468
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
if (node_contains_hook_bearing_tsrx(node[key], transform_context)) {
|
|
1472
|
+
return true;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1253
1479
|
/**
|
|
1254
1480
|
* @param {TransformContext} transform_context
|
|
1255
1481
|
* @returns {boolean}
|
|
@@ -2231,15 +2457,20 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2231
2457
|
|
|
2232
2458
|
const saved_bindings = transform_context.available_bindings;
|
|
2233
2459
|
transform_context.available_bindings = new Map(saved_bindings);
|
|
2460
|
+
const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
|
|
2234
2461
|
for (const param of loop_params) {
|
|
2235
2462
|
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
2236
2463
|
}
|
|
2464
|
+
validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
|
|
2465
|
+
original_loop_body,
|
|
2466
|
+
transform_context,
|
|
2467
|
+
loop_scoped_names,
|
|
2468
|
+
);
|
|
2237
2469
|
|
|
2238
2470
|
const all_helper_bindings = get_referenced_helper_bindings(
|
|
2239
2471
|
loop_body,
|
|
2240
2472
|
transform_context.available_bindings,
|
|
2241
2473
|
);
|
|
2242
|
-
const loop_scoped_names = new Set(loop_params.map((/** @type {any} */ p) => p.name));
|
|
2243
2474
|
const outer_bindings = all_helper_bindings.filter((b) => !loop_scoped_names.has(b.name));
|
|
2244
2475
|
const loop_bindings = all_helper_bindings.filter((b) => loop_scoped_names.has(b.name));
|
|
2245
2476
|
|
|
@@ -2632,9 +2863,6 @@ function is_null_literal(node) {
|
|
|
2632
2863
|
return node?.type === 'Literal' && node.value == null;
|
|
2633
2864
|
}
|
|
2634
2865
|
|
|
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
2866
|
/**
|
|
2639
2867
|
* @param {any} node
|
|
2640
2868
|
* @param {TransformContext} transform_context
|
|
@@ -2643,13 +2871,7 @@ const TEMPLATE_FRAGMENT_ERROR =
|
|
|
2643
2871
|
function to_jsx_element(node, transform_context, raw_children = node.children || []) {
|
|
2644
2872
|
if (node.type === 'JSXElement') return node;
|
|
2645
2873
|
if (!node.id) {
|
|
2646
|
-
|
|
2647
|
-
TEMPLATE_FRAGMENT_ERROR,
|
|
2648
|
-
transform_context.filename,
|
|
2649
|
-
node,
|
|
2650
|
-
transform_context.errors,
|
|
2651
|
-
transform_context.comments,
|
|
2652
|
-
);
|
|
2874
|
+
report_jsx_fragment_in_tsrx_error(node, transform_context);
|
|
2653
2875
|
return set_loc(
|
|
2654
2876
|
/** @type {any} */ ({
|
|
2655
2877
|
type: 'JSXFragment',
|
|
@@ -2688,9 +2910,7 @@ function to_jsx_element(node, transform_context, raw_children = node.children ||
|
|
|
2688
2910
|
}
|
|
2689
2911
|
} else {
|
|
2690
2912
|
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
|
-
);
|
|
2913
|
+
return report_html_template_unsupported_error(node, transform_context);
|
|
2694
2914
|
}
|
|
2695
2915
|
children = create_element_children(walked_children, transform_context);
|
|
2696
2916
|
}
|
|
@@ -2922,98 +3142,773 @@ function get_referenced_helper_bindings(body_nodes, available_bindings) {
|
|
|
2922
3142
|
|
|
2923
3143
|
/**
|
|
2924
3144
|
* @param {any[]} body_nodes
|
|
2925
|
-
* @param {any} key_expression
|
|
2926
|
-
* @param {any} source_node
|
|
2927
3145
|
* @param {TransformContext} transform_context
|
|
2928
|
-
* @param {
|
|
2929
|
-
*
|
|
2930
|
-
* source order in a forward pass and then constructs helpers in reverse so
|
|
2931
|
-
* each fall-through case can reference the next case's component element.
|
|
2932
|
-
* @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
|
|
3146
|
+
* @param {Set<string>} [local_binding_names]
|
|
3147
|
+
* @returns {void}
|
|
2933
3148
|
*/
|
|
2934
|
-
function
|
|
3149
|
+
function validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
|
|
2935
3150
|
body_nodes,
|
|
2936
|
-
key_expression,
|
|
2937
|
-
source_node,
|
|
2938
3151
|
transform_context,
|
|
2939
|
-
|
|
3152
|
+
local_binding_names,
|
|
2940
3153
|
) {
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
transform_context.available_bindings,
|
|
2951
|
-
);
|
|
2952
|
-
const aliases = use_module_scoped_component
|
|
2953
|
-
? []
|
|
2954
|
-
: helper_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
|
|
2955
|
-
const props_type =
|
|
2956
|
-
helper_bindings.length > 0 && !use_module_scoped_component
|
|
2957
|
-
? create_helper_props_type_literal(helper_bindings, aliases)
|
|
2958
|
-
: null;
|
|
2959
|
-
const params =
|
|
2960
|
-
helper_bindings.length > 0
|
|
2961
|
-
? [
|
|
2962
|
-
props_type !== null
|
|
2963
|
-
? create_typed_helper_props_pattern(helper_bindings, props_type)
|
|
2964
|
-
: create_helper_props_pattern(helper_bindings),
|
|
2965
|
-
]
|
|
2966
|
-
: [];
|
|
2967
|
-
|
|
2968
|
-
const saved_bindings = transform_context.available_bindings;
|
|
2969
|
-
transform_context.available_bindings = new Map(saved_bindings);
|
|
2970
|
-
|
|
2971
|
-
const helper_fn = /** @type {any} */ ({
|
|
2972
|
-
type: 'FunctionExpression',
|
|
2973
|
-
id: clone_identifier(component_id),
|
|
2974
|
-
params,
|
|
2975
|
-
body: {
|
|
2976
|
-
type: 'BlockStatement',
|
|
2977
|
-
body: build_render_statements(body_nodes, true, transform_context),
|
|
2978
|
-
metadata: { path: [] },
|
|
2979
|
-
},
|
|
2980
|
-
async: false,
|
|
2981
|
-
generator: false,
|
|
2982
|
-
metadata: {
|
|
2983
|
-
path: [],
|
|
2984
|
-
is_component: true,
|
|
2985
|
-
is_method: false,
|
|
2986
|
-
},
|
|
2987
|
-
});
|
|
3154
|
+
if (!is_react_like_hook_platform(transform_context)) {
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
if (!body_contains_top_level_hook_call(body_nodes, transform_context, true)) {
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
if (!transform_context.available_bindings || transform_context.available_bindings.size === 0) {
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
2988
3163
|
|
|
2989
|
-
|
|
3164
|
+
const shadowed_names = collect_block_binding_names(body_nodes);
|
|
3165
|
+
for (const name of local_binding_names || []) {
|
|
3166
|
+
shadowed_names.add(name);
|
|
3167
|
+
}
|
|
3168
|
+
validate_hook_outer_assignments_in_node(body_nodes, shadowed_names, transform_context, new Set());
|
|
3169
|
+
}
|
|
2990
3170
|
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
mapBindingValues: false,
|
|
2999
|
-
},
|
|
3171
|
+
/**
|
|
3172
|
+
* @param {TransformContext} transform_context
|
|
3173
|
+
* @returns {boolean}
|
|
3174
|
+
*/
|
|
3175
|
+
function is_react_like_hook_platform(transform_context) {
|
|
3176
|
+
return (
|
|
3177
|
+
transform_context.platform.name === 'React' || transform_context.platform.name === 'Preact'
|
|
3000
3178
|
);
|
|
3179
|
+
}
|
|
3001
3180
|
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
);
|
|
3181
|
+
/**
|
|
3182
|
+
* @param {any[]} statements
|
|
3183
|
+
* @returns {Set<string>}
|
|
3184
|
+
*/
|
|
3185
|
+
function collect_block_binding_names(statements) {
|
|
3186
|
+
const names = new Set();
|
|
3187
|
+
for (const statement of statements || []) {
|
|
3188
|
+
collect_block_binding_names_from_statement(statement, names);
|
|
3011
3189
|
}
|
|
3190
|
+
return names;
|
|
3191
|
+
}
|
|
3012
3192
|
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3193
|
+
/**
|
|
3194
|
+
* @param {any} statement
|
|
3195
|
+
* @param {Set<string>} names
|
|
3196
|
+
* @returns {void}
|
|
3197
|
+
*/
|
|
3198
|
+
function collect_block_binding_names_from_statement(statement, names) {
|
|
3199
|
+
if (!statement || typeof statement !== 'object') {
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
if (statement.type === 'VariableDeclaration') {
|
|
3204
|
+
for (const declaration of statement.declarations || []) {
|
|
3205
|
+
collect_pattern_names(declaration.id, names);
|
|
3206
|
+
}
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
if (
|
|
3211
|
+
(statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
|
|
3212
|
+
statement.id?.type === 'Identifier'
|
|
3213
|
+
) {
|
|
3214
|
+
names.add(statement.id.name);
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
if (statement.type === 'ForOfStatement' || statement.type === 'ForInStatement') {
|
|
3219
|
+
if (statement.left?.type === 'VariableDeclaration' && statement.left.kind === 'var') {
|
|
3220
|
+
for (const declaration of statement.left.declarations || []) {
|
|
3221
|
+
collect_pattern_names(declaration.id, names);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
if (
|
|
3228
|
+
statement.type === 'ForStatement' &&
|
|
3229
|
+
statement.init?.type === 'VariableDeclaration' &&
|
|
3230
|
+
statement.init.kind === 'var'
|
|
3231
|
+
) {
|
|
3232
|
+
for (const declaration of statement.init.declarations || []) {
|
|
3233
|
+
collect_pattern_names(declaration.id, names);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
/**
|
|
3239
|
+
* @param {any} pattern
|
|
3240
|
+
* @param {Set<string>} names
|
|
3241
|
+
* @returns {void}
|
|
3242
|
+
*/
|
|
3243
|
+
function collect_pattern_names(pattern, names) {
|
|
3244
|
+
if (!pattern || typeof pattern !== 'object') {
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
if (pattern.type === 'Identifier') {
|
|
3249
|
+
names.add(pattern.name);
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
if (pattern.type === 'RestElement') {
|
|
3254
|
+
collect_pattern_names(pattern.argument, names);
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
3259
|
+
collect_pattern_names(pattern.left, names);
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
if (pattern.type === 'ArrayPattern') {
|
|
3264
|
+
for (const element of pattern.elements || []) {
|
|
3265
|
+
collect_pattern_names(element, names);
|
|
3266
|
+
}
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
if (pattern.type === 'ObjectPattern') {
|
|
3271
|
+
for (const property of pattern.properties || []) {
|
|
3272
|
+
if (property.type === 'RestElement') {
|
|
3273
|
+
collect_pattern_names(property.argument, names);
|
|
3274
|
+
} else {
|
|
3275
|
+
collect_pattern_names(property.value, names);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
/**
|
|
3282
|
+
* @param {any} node
|
|
3283
|
+
* @param {Set<string>} shadowed_names
|
|
3284
|
+
* @param {TransformContext} transform_context
|
|
3285
|
+
* @param {Set<string>} hook_result_names
|
|
3286
|
+
* @returns {void}
|
|
3287
|
+
*/
|
|
3288
|
+
function validate_hook_outer_assignments_in_node(
|
|
3289
|
+
node,
|
|
3290
|
+
shadowed_names,
|
|
3291
|
+
transform_context,
|
|
3292
|
+
hook_result_names,
|
|
3293
|
+
) {
|
|
3294
|
+
if (!node || typeof node !== 'object') {
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
if (Array.isArray(node)) {
|
|
3299
|
+
for (const child of node) {
|
|
3300
|
+
validate_hook_outer_assignments_in_node(
|
|
3301
|
+
child,
|
|
3302
|
+
shadowed_names,
|
|
3303
|
+
transform_context,
|
|
3304
|
+
hook_result_names,
|
|
3305
|
+
);
|
|
3306
|
+
}
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
if (is_function_like_node(node)) {
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
|
|
3315
|
+
validate_hook_callback_outer_mutations(node, shadowed_names, transform_context);
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
if (node.type === 'BlockStatement') {
|
|
3319
|
+
const next_shadowed = new Set(shadowed_names);
|
|
3320
|
+
const next_hook_result_names = new Set(hook_result_names);
|
|
3321
|
+
for (const name of collect_block_binding_names(node.body || [])) {
|
|
3322
|
+
next_shadowed.add(name);
|
|
3323
|
+
}
|
|
3324
|
+
for (const child of node.body || []) {
|
|
3325
|
+
validate_hook_outer_assignments_in_node(
|
|
3326
|
+
child,
|
|
3327
|
+
next_shadowed,
|
|
3328
|
+
transform_context,
|
|
3329
|
+
next_hook_result_names,
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
if (node.type === 'VariableDeclaration') {
|
|
3336
|
+
for (const declaration of node.declarations || []) {
|
|
3337
|
+
if (
|
|
3338
|
+
declaration.init &&
|
|
3339
|
+
expression_contains_hook_derived_value(
|
|
3340
|
+
declaration.init,
|
|
3341
|
+
transform_context,
|
|
3342
|
+
hook_result_names,
|
|
3343
|
+
)
|
|
3344
|
+
) {
|
|
3345
|
+
collect_pattern_names(declaration.id, hook_result_names);
|
|
3346
|
+
}
|
|
3347
|
+
validate_hook_outer_assignments_in_node(
|
|
3348
|
+
declaration.init,
|
|
3349
|
+
shadowed_names,
|
|
3350
|
+
transform_context,
|
|
3351
|
+
hook_result_names,
|
|
3352
|
+
);
|
|
3353
|
+
}
|
|
3354
|
+
return;
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
if (
|
|
3358
|
+
node.type === 'AssignmentExpression' &&
|
|
3359
|
+
expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
|
|
3360
|
+
) {
|
|
3361
|
+
const outer_names = get_referenced_outer_binding_names(
|
|
3362
|
+
node.left,
|
|
3363
|
+
transform_context.available_bindings,
|
|
3364
|
+
shadowed_names,
|
|
3365
|
+
);
|
|
3366
|
+
if (outer_names.length > 0) {
|
|
3367
|
+
report_hook_outer_assignment_error(
|
|
3368
|
+
node,
|
|
3369
|
+
outer_names,
|
|
3370
|
+
find_first_hook_call_name(node.right) || 'hook',
|
|
3371
|
+
transform_context,
|
|
3372
|
+
);
|
|
3373
|
+
}
|
|
3374
|
+
for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
|
|
3375
|
+
hook_result_names.add(name);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
if (node.type === 'ForOfStatement') {
|
|
3380
|
+
if (
|
|
3381
|
+
node.left &&
|
|
3382
|
+
node.left.type !== 'VariableDeclaration' &&
|
|
3383
|
+
expression_contains_hook_derived_value(node.right, transform_context, hook_result_names)
|
|
3384
|
+
) {
|
|
3385
|
+
const outer_names = get_referenced_outer_binding_names(
|
|
3386
|
+
node.left,
|
|
3387
|
+
transform_context.available_bindings,
|
|
3388
|
+
shadowed_names,
|
|
3389
|
+
);
|
|
3390
|
+
if (outer_names.length > 0) {
|
|
3391
|
+
report_hook_outer_assignment_error(
|
|
3392
|
+
node,
|
|
3393
|
+
outer_names,
|
|
3394
|
+
find_first_hook_call_name(node.right) || 'hook',
|
|
3395
|
+
transform_context,
|
|
3396
|
+
);
|
|
3397
|
+
}
|
|
3398
|
+
for (const name of get_referenced_local_binding_names(node.left, shadowed_names)) {
|
|
3399
|
+
hook_result_names.add(name);
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
validate_hook_outer_assignments_in_node(
|
|
3404
|
+
node.right,
|
|
3405
|
+
shadowed_names,
|
|
3406
|
+
transform_context,
|
|
3407
|
+
hook_result_names,
|
|
3408
|
+
);
|
|
3409
|
+
|
|
3410
|
+
// Loop-declared bindings (`for (const x of …)`, `for (let x of …)`) live
|
|
3411
|
+
// only in the body. They are deliberately NOT in the enclosing block's
|
|
3412
|
+
// shadowed set (see collect_block_binding_names_from_statement), so add
|
|
3413
|
+
// them just for the body recursion to keep references to the loop var
|
|
3414
|
+
// from being flagged as outer-binding mutations.
|
|
3415
|
+
const body_shadowed = new Set(shadowed_names);
|
|
3416
|
+
if (node.left && node.left.type === 'VariableDeclaration') {
|
|
3417
|
+
for (const declaration of node.left.declarations || []) {
|
|
3418
|
+
collect_pattern_names(declaration.id, body_shadowed);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
validate_hook_outer_assignments_in_node(
|
|
3422
|
+
node.body,
|
|
3423
|
+
body_shadowed,
|
|
3424
|
+
transform_context,
|
|
3425
|
+
hook_result_names,
|
|
3426
|
+
);
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
for (const key of Object.keys(node)) {
|
|
3431
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3432
|
+
continue;
|
|
3433
|
+
}
|
|
3434
|
+
validate_hook_outer_assignments_in_node(
|
|
3435
|
+
node[key],
|
|
3436
|
+
shadowed_names,
|
|
3437
|
+
transform_context,
|
|
3438
|
+
hook_result_names,
|
|
3439
|
+
);
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
/**
|
|
3444
|
+
* @param {any} call_node
|
|
3445
|
+
* @param {Set<string>} shadowed_names
|
|
3446
|
+
* @param {TransformContext} transform_context
|
|
3447
|
+
* @returns {void}
|
|
3448
|
+
*/
|
|
3449
|
+
function validate_hook_callback_outer_mutations(call_node, shadowed_names, transform_context) {
|
|
3450
|
+
const hook_name = get_hook_callee_name(call_node.callee);
|
|
3451
|
+
for (const argument of call_node.arguments || []) {
|
|
3452
|
+
if (!is_function_like_node(argument)) {
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
const callback_shadowed_names = create_function_like_shadowed_names(argument, shadowed_names);
|
|
3456
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
3457
|
+
argument.body,
|
|
3458
|
+
callback_shadowed_names,
|
|
3459
|
+
transform_context,
|
|
3460
|
+
hook_name,
|
|
3461
|
+
);
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
/**
|
|
3466
|
+
* @param {any} node
|
|
3467
|
+
* @returns {boolean}
|
|
3468
|
+
*/
|
|
3469
|
+
function is_function_like_node(node) {
|
|
3470
|
+
return (
|
|
3471
|
+
node.type === 'FunctionDeclaration' ||
|
|
3472
|
+
node.type === 'FunctionExpression' ||
|
|
3473
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
3474
|
+
// this is just in case but we should already
|
|
3475
|
+
// have a component replaced with a function node
|
|
3476
|
+
node.type === 'Component'
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
/**
|
|
3481
|
+
* @param {any} node
|
|
3482
|
+
* @param {Set<string>} shadowed_names
|
|
3483
|
+
* @returns {Set<string>}
|
|
3484
|
+
*/
|
|
3485
|
+
function create_function_like_shadowed_names(node, shadowed_names) {
|
|
3486
|
+
const next_shadowed_names = new Set(shadowed_names);
|
|
3487
|
+
for (const param of node.params || []) {
|
|
3488
|
+
collect_pattern_names(param, next_shadowed_names);
|
|
3489
|
+
}
|
|
3490
|
+
if (node.body?.type === 'BlockStatement') {
|
|
3491
|
+
for (const name of collect_block_binding_names(node.body.body || [])) {
|
|
3492
|
+
next_shadowed_names.add(name);
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
return next_shadowed_names;
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
/**
|
|
3499
|
+
* @param {any} node
|
|
3500
|
+
* @param {Set<string>} shadowed_names
|
|
3501
|
+
* @param {TransformContext} transform_context
|
|
3502
|
+
* @param {string} hook_name
|
|
3503
|
+
* @returns {void}
|
|
3504
|
+
*/
|
|
3505
|
+
function validate_hook_callback_outer_mutations_in_node(
|
|
3506
|
+
node,
|
|
3507
|
+
shadowed_names,
|
|
3508
|
+
transform_context,
|
|
3509
|
+
hook_name,
|
|
3510
|
+
) {
|
|
3511
|
+
if (!node || typeof node !== 'object') {
|
|
3512
|
+
return;
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
if (Array.isArray(node)) {
|
|
3516
|
+
for (const child of node) {
|
|
3517
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
3518
|
+
child,
|
|
3519
|
+
shadowed_names,
|
|
3520
|
+
transform_context,
|
|
3521
|
+
hook_name,
|
|
3522
|
+
);
|
|
3523
|
+
}
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
if (is_function_like_node(node)) {
|
|
3528
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
3529
|
+
node.body,
|
|
3530
|
+
create_function_like_shadowed_names(node, shadowed_names),
|
|
3531
|
+
transform_context,
|
|
3532
|
+
hook_name,
|
|
3533
|
+
);
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
if (node.type === 'BlockStatement') {
|
|
3538
|
+
const next_shadowed_names = new Set(shadowed_names);
|
|
3539
|
+
for (const name of collect_block_binding_names(node.body || [])) {
|
|
3540
|
+
next_shadowed_names.add(name);
|
|
3541
|
+
}
|
|
3542
|
+
for (const child of node.body || []) {
|
|
3543
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
3544
|
+
child,
|
|
3545
|
+
next_shadowed_names,
|
|
3546
|
+
transform_context,
|
|
3547
|
+
hook_name,
|
|
3548
|
+
);
|
|
3549
|
+
}
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
if (node.type === 'AssignmentExpression') {
|
|
3554
|
+
const outer_names = get_referenced_outer_binding_names(
|
|
3555
|
+
node.left,
|
|
3556
|
+
transform_context.available_bindings,
|
|
3557
|
+
shadowed_names,
|
|
3558
|
+
);
|
|
3559
|
+
if (outer_names.length > 0) {
|
|
3560
|
+
report_hook_callback_outer_mutation_error(node, outer_names, hook_name, transform_context);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
if (node.type === 'UpdateExpression') {
|
|
3565
|
+
const outer_names = get_referenced_outer_binding_names(
|
|
3566
|
+
node.argument,
|
|
3567
|
+
transform_context.available_bindings,
|
|
3568
|
+
shadowed_names,
|
|
3569
|
+
);
|
|
3570
|
+
if (outer_names.length > 0) {
|
|
3571
|
+
report_hook_callback_outer_mutation_error(node, outer_names, hook_name, transform_context);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
for (const key of Object.keys(node)) {
|
|
3576
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
if (key === 'left' && node.type === 'AssignmentExpression') {
|
|
3580
|
+
continue;
|
|
3581
|
+
}
|
|
3582
|
+
if (key === 'argument' && node.type === 'UpdateExpression') {
|
|
3583
|
+
continue;
|
|
3584
|
+
}
|
|
3585
|
+
validate_hook_callback_outer_mutations_in_node(
|
|
3586
|
+
node[key],
|
|
3587
|
+
shadowed_names,
|
|
3588
|
+
transform_context,
|
|
3589
|
+
hook_name,
|
|
3590
|
+
);
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
/**
|
|
3595
|
+
* @param {any} node
|
|
3596
|
+
* @param {TransformContext} transform_context
|
|
3597
|
+
* @param {Set<string>} hook_result_names
|
|
3598
|
+
* @returns {boolean}
|
|
3599
|
+
*/
|
|
3600
|
+
function expression_contains_hook_derived_value(node, transform_context, hook_result_names) {
|
|
3601
|
+
return (
|
|
3602
|
+
node_contains_top_level_hook_call(node, false, transform_context, true) ||
|
|
3603
|
+
references_name_in_set(node, hook_result_names)
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
/**
|
|
3608
|
+
* @param {any} node
|
|
3609
|
+
* @param {Set<string>} names
|
|
3610
|
+
* @returns {boolean}
|
|
3611
|
+
*/
|
|
3612
|
+
function references_name_in_set(node, names) {
|
|
3613
|
+
if (!node || typeof node !== 'object' || names.size === 0) {
|
|
3614
|
+
return false;
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
if (node.type === 'Identifier') {
|
|
3618
|
+
return names.has(node.name);
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
if (
|
|
3622
|
+
node.type === 'FunctionDeclaration' ||
|
|
3623
|
+
node.type === 'FunctionExpression' ||
|
|
3624
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
3625
|
+
node.type === 'Component'
|
|
3626
|
+
) {
|
|
3627
|
+
return false;
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
if (Array.isArray(node)) {
|
|
3631
|
+
return node.some((child) => references_name_in_set(child, names));
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
for (const key of Object.keys(node)) {
|
|
3635
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3636
|
+
continue;
|
|
3637
|
+
}
|
|
3638
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
|
|
3639
|
+
continue;
|
|
3640
|
+
}
|
|
3641
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
|
|
3642
|
+
continue;
|
|
3643
|
+
}
|
|
3644
|
+
if (references_name_in_set(node[key], names)) {
|
|
3645
|
+
return true;
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
return false;
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
/**
|
|
3653
|
+
* @param {any} node
|
|
3654
|
+
* @param {Set<string>} shadowed_names
|
|
3655
|
+
* @returns {string[]}
|
|
3656
|
+
*/
|
|
3657
|
+
function get_referenced_local_binding_names(node, shadowed_names) {
|
|
3658
|
+
const names = new Set();
|
|
3659
|
+
collect_referenced_local_binding_names(node, shadowed_names, names);
|
|
3660
|
+
return [...names];
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
/**
|
|
3664
|
+
* @param {any} node
|
|
3665
|
+
* @param {Set<string>} shadowed_names
|
|
3666
|
+
* @param {Set<string>} names
|
|
3667
|
+
* @returns {void}
|
|
3668
|
+
*/
|
|
3669
|
+
function collect_referenced_local_binding_names(node, shadowed_names, names) {
|
|
3670
|
+
if (!node || typeof node !== 'object') {
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
if (node.type === 'Identifier') {
|
|
3675
|
+
if (shadowed_names.has(node.name)) {
|
|
3676
|
+
names.add(node.name);
|
|
3677
|
+
}
|
|
3678
|
+
return;
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
if (Array.isArray(node)) {
|
|
3682
|
+
for (const child of node) {
|
|
3683
|
+
collect_referenced_local_binding_names(child, shadowed_names, names);
|
|
3684
|
+
}
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
for (const key of Object.keys(node)) {
|
|
3689
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3690
|
+
continue;
|
|
3691
|
+
}
|
|
3692
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
|
|
3693
|
+
continue;
|
|
3694
|
+
}
|
|
3695
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
|
|
3696
|
+
continue;
|
|
3697
|
+
}
|
|
3698
|
+
collect_referenced_local_binding_names(node[key], shadowed_names, names);
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
/**
|
|
3703
|
+
* @param {any} node
|
|
3704
|
+
* @param {Map<string, AST.Identifier>} available_bindings
|
|
3705
|
+
* @param {Set<string>} shadowed_names
|
|
3706
|
+
* @returns {string[]}
|
|
3707
|
+
*/
|
|
3708
|
+
function get_referenced_outer_binding_names(node, available_bindings, shadowed_names) {
|
|
3709
|
+
const names = new Set();
|
|
3710
|
+
collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names);
|
|
3711
|
+
return [...names];
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
/**
|
|
3715
|
+
* @param {any} node
|
|
3716
|
+
* @param {Map<string, AST.Identifier>} available_bindings
|
|
3717
|
+
* @param {Set<string>} shadowed_names
|
|
3718
|
+
* @param {Set<string>} names
|
|
3719
|
+
* @returns {void}
|
|
3720
|
+
*/
|
|
3721
|
+
function collect_referenced_outer_binding_names(node, available_bindings, shadowed_names, names) {
|
|
3722
|
+
if (!node || typeof node !== 'object') {
|
|
3723
|
+
return;
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
if (node.type === 'Identifier') {
|
|
3727
|
+
if (available_bindings.has(node.name) && !shadowed_names.has(node.name)) {
|
|
3728
|
+
names.add(node.name);
|
|
3729
|
+
}
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
if (Array.isArray(node)) {
|
|
3734
|
+
for (const child of node) {
|
|
3735
|
+
collect_referenced_outer_binding_names(child, available_bindings, shadowed_names, names);
|
|
3736
|
+
}
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
for (const key of Object.keys(node)) {
|
|
3741
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3742
|
+
continue;
|
|
3743
|
+
}
|
|
3744
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
|
|
3745
|
+
continue;
|
|
3746
|
+
}
|
|
3747
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
collect_referenced_outer_binding_names(node[key], available_bindings, shadowed_names, names);
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
/**
|
|
3755
|
+
* @param {any} node
|
|
3756
|
+
* @returns {string | null}
|
|
3757
|
+
*/
|
|
3758
|
+
function find_first_hook_call_name(node) {
|
|
3759
|
+
if (!node || typeof node !== 'object') {
|
|
3760
|
+
return null;
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3763
|
+
if (node.type === 'CallExpression' && is_hook_callee(node.callee)) {
|
|
3764
|
+
return get_hook_callee_name(node.callee);
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
if (
|
|
3768
|
+
node.type === 'FunctionDeclaration' ||
|
|
3769
|
+
node.type === 'FunctionExpression' ||
|
|
3770
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
3771
|
+
node.type === 'Component'
|
|
3772
|
+
) {
|
|
3773
|
+
return null;
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
if (Array.isArray(node)) {
|
|
3777
|
+
for (const child of node) {
|
|
3778
|
+
const name = find_first_hook_call_name(child);
|
|
3779
|
+
if (name) return name;
|
|
3780
|
+
}
|
|
3781
|
+
return null;
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
for (const key of Object.keys(node)) {
|
|
3785
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3786
|
+
continue;
|
|
3787
|
+
}
|
|
3788
|
+
const name = find_first_hook_call_name(node[key]);
|
|
3789
|
+
if (name) return name;
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
return null;
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
/**
|
|
3796
|
+
* @param {any} callee
|
|
3797
|
+
* @returns {string}
|
|
3798
|
+
*/
|
|
3799
|
+
function get_hook_callee_name(callee) {
|
|
3800
|
+
if (callee?.type === 'Identifier') {
|
|
3801
|
+
return callee.name;
|
|
3802
|
+
}
|
|
3803
|
+
if (
|
|
3804
|
+
callee?.type === 'MemberExpression' &&
|
|
3805
|
+
!callee.computed &&
|
|
3806
|
+
callee.property?.type === 'Identifier'
|
|
3807
|
+
) {
|
|
3808
|
+
return callee.property.name;
|
|
3809
|
+
}
|
|
3810
|
+
return 'hook';
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
/**
|
|
3814
|
+
* @param {any[]} body_nodes
|
|
3815
|
+
* @param {any} key_expression
|
|
3816
|
+
* @param {any} source_node
|
|
3817
|
+
* @param {TransformContext} transform_context
|
|
3818
|
+
* @param {AST.Identifier} [preallocated_helper_id] - Optional pre-allocated id.
|
|
3819
|
+
* Used by the switch lift's chained-call build, which allocates ids in
|
|
3820
|
+
* source order in a forward pass and then constructs helpers in reverse so
|
|
3821
|
+
* each fall-through case can reference the next case's component element.
|
|
3822
|
+
* @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
|
|
3823
|
+
*/
|
|
3824
|
+
function create_hook_safe_helper(
|
|
3825
|
+
body_nodes,
|
|
3826
|
+
key_expression,
|
|
3827
|
+
source_node,
|
|
3828
|
+
transform_context,
|
|
3829
|
+
preallocated_helper_id,
|
|
3830
|
+
) {
|
|
3831
|
+
validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
|
|
3832
|
+
body_nodes,
|
|
3833
|
+
transform_context,
|
|
3834
|
+
);
|
|
3835
|
+
|
|
3836
|
+
const helper_id =
|
|
3837
|
+
preallocated_helper_id ??
|
|
3838
|
+
create_generated_identifier(create_local_statement_component_name(transform_context));
|
|
3839
|
+
const use_module_scoped_component = should_use_module_scoped_hook_components(transform_context);
|
|
3840
|
+
const component_id = use_module_scoped_component
|
|
3841
|
+
? create_module_scoped_hook_component_id(helper_id, transform_context)
|
|
3842
|
+
: helper_id;
|
|
3843
|
+
const helper_bindings = get_referenced_helper_bindings(
|
|
3844
|
+
body_nodes,
|
|
3845
|
+
transform_context.available_bindings,
|
|
3846
|
+
);
|
|
3847
|
+
const aliases = use_module_scoped_component
|
|
3848
|
+
? []
|
|
3849
|
+
: helper_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
|
|
3850
|
+
const props_type =
|
|
3851
|
+
helper_bindings.length > 0 && !use_module_scoped_component
|
|
3852
|
+
? create_helper_props_type_literal(helper_bindings, aliases)
|
|
3853
|
+
: null;
|
|
3854
|
+
const params =
|
|
3855
|
+
helper_bindings.length > 0
|
|
3856
|
+
? [
|
|
3857
|
+
props_type !== null
|
|
3858
|
+
? create_typed_helper_props_pattern(helper_bindings, props_type)
|
|
3859
|
+
: create_helper_props_pattern(helper_bindings),
|
|
3860
|
+
]
|
|
3861
|
+
: [];
|
|
3862
|
+
|
|
3863
|
+
const saved_bindings = transform_context.available_bindings;
|
|
3864
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
3865
|
+
|
|
3866
|
+
const helper_fn = /** @type {any} */ ({
|
|
3867
|
+
type: 'FunctionExpression',
|
|
3868
|
+
id: clone_identifier(component_id),
|
|
3869
|
+
params,
|
|
3870
|
+
body: {
|
|
3871
|
+
type: 'BlockStatement',
|
|
3872
|
+
body: build_render_statements(body_nodes, true, transform_context),
|
|
3873
|
+
metadata: { path: [] },
|
|
3874
|
+
},
|
|
3875
|
+
async: false,
|
|
3876
|
+
generator: false,
|
|
3877
|
+
metadata: {
|
|
3878
|
+
path: [],
|
|
3879
|
+
is_component: true,
|
|
3880
|
+
is_method: false,
|
|
3881
|
+
},
|
|
3882
|
+
});
|
|
3883
|
+
|
|
3884
|
+
transform_context.available_bindings = saved_bindings;
|
|
3885
|
+
|
|
3886
|
+
const component_element = create_helper_component_element(
|
|
3887
|
+
component_id,
|
|
3888
|
+
helper_bindings,
|
|
3889
|
+
source_node,
|
|
3890
|
+
{
|
|
3891
|
+
mapWrapper: false,
|
|
3892
|
+
mapBindingNames: false,
|
|
3893
|
+
mapBindingValues: false,
|
|
3894
|
+
},
|
|
3895
|
+
);
|
|
3896
|
+
|
|
3897
|
+
if (key_expression) {
|
|
3898
|
+
component_element.openingElement.attributes.push(
|
|
3899
|
+
/** @type {any} */ ({
|
|
3900
|
+
type: 'JSXAttribute',
|
|
3901
|
+
name: { type: 'JSXIdentifier', name: 'key', metadata: { path: [] } },
|
|
3902
|
+
value: to_jsx_expression_container(key_expression, key_expression),
|
|
3903
|
+
metadata: { path: [] },
|
|
3904
|
+
}),
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
if (!transform_context.helper_state) {
|
|
3909
|
+
return {
|
|
3910
|
+
setup_statements: [
|
|
3911
|
+
...aliases.map((alias) => alias.declaration),
|
|
3017
3912
|
create_helper_declaration(helper_id, helper_fn, source_node, transform_context),
|
|
3018
3913
|
],
|
|
3019
3914
|
component_element,
|
|
@@ -3410,9 +4305,7 @@ function to_jsx_child(node, transform_context) {
|
|
|
3410
4305
|
case 'TSRXExpression':
|
|
3411
4306
|
return to_jsx_expression_container(node.expression, node);
|
|
3412
4307
|
case 'Html':
|
|
3413
|
-
|
|
3414
|
-
`\`{html ...}\` is not supported on the ${transform_context.platform.name} target. Use \`dangerouslySetInnerHTML={{ __html: ... }}\` as an element attribute instead.`,
|
|
3415
|
-
);
|
|
4308
|
+
return report_html_template_unsupported_error(node, transform_context);
|
|
3416
4309
|
case 'IfStatement':
|
|
3417
4310
|
return (
|
|
3418
4311
|
transform_context.platform.hooks?.controlFlow?.ifStatement ?? if_statement_to_jsx_child
|
|
@@ -4227,9 +5120,16 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
4227
5120
|
// correctly identifies references to err/reset as non-static
|
|
4228
5121
|
const saved_catch_bindings = transform_context.available_bindings;
|
|
4229
5122
|
transform_context.available_bindings = new Map(saved_catch_bindings);
|
|
5123
|
+
const catch_scoped_names = new Set();
|
|
4230
5124
|
for (const param of catch_params) {
|
|
4231
5125
|
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
5126
|
+
collect_pattern_names(param, catch_scoped_names);
|
|
4232
5127
|
}
|
|
5128
|
+
validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
|
|
5129
|
+
catch_body_nodes,
|
|
5130
|
+
transform_context,
|
|
5131
|
+
catch_scoped_names,
|
|
5132
|
+
);
|
|
4233
5133
|
|
|
4234
5134
|
const fallback_fn = {
|
|
4235
5135
|
type: 'ArrowFunctionExpression',
|