@tsrx/react 0.0.2 → 0.0.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/index.js +3 -53
- package/src/transform.js +351 -208
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
|
-
/** @import {
|
|
2
|
+
/** @import { ParseOptions } from '@tsrx/core/types' */
|
|
3
3
|
|
|
4
|
-
import { createVolarMappingsResult, parseModule } from '@tsrx/core';
|
|
4
|
+
import { createVolarMappingsResult, dedupeMappings, parseModule } from '@tsrx/core';
|
|
5
5
|
import { transform } from './transform.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -51,56 +51,6 @@ export function compile_to_volar_mappings(source, filename, options) {
|
|
|
51
51
|
|
|
52
52
|
return {
|
|
53
53
|
...result,
|
|
54
|
-
mappings:
|
|
54
|
+
mappings: dedupeMappings(result.mappings),
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Remove byte-for-byte duplicate mappings. React helper extraction can emit
|
|
60
|
-
* identical mapping entries for the same source and generated span, which
|
|
61
|
-
* causes Volar to merge duplicate hover/navigation results.
|
|
62
|
-
*
|
|
63
|
-
* @param {CodeMapping[]} mappings
|
|
64
|
-
* @returns {CodeMapping[]}
|
|
65
|
-
*/
|
|
66
|
-
function dedupe_mappings(mappings) {
|
|
67
|
-
const deduped = [];
|
|
68
|
-
const seen = new Set();
|
|
69
|
-
|
|
70
|
-
for (const mapping of mappings) {
|
|
71
|
-
const key = JSON.stringify(serialize_mapping_value(mapping));
|
|
72
|
-
|
|
73
|
-
if (seen.has(key)) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
seen.add(key);
|
|
78
|
-
deduped.push(mapping);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return deduped;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @param {unknown} value
|
|
86
|
-
* @returns {unknown}
|
|
87
|
-
*/
|
|
88
|
-
function serialize_mapping_value(value) {
|
|
89
|
-
if (typeof value === 'function') {
|
|
90
|
-
return value.toString();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (Array.isArray(value)) {
|
|
94
|
-
return value.map(serialize_mapping_value);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (value && typeof value === 'object') {
|
|
98
|
-
return Object.fromEntries(
|
|
99
|
-
Object.entries(value)
|
|
100
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
101
|
-
.map(([key, nested_value]) => [key, serialize_mapping_value(nested_value)]),
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return value;
|
|
106
|
-
}
|
package/src/transform.js
CHANGED
|
@@ -4,16 +4,33 @@
|
|
|
4
4
|
import { walk } from 'zimmerframe';
|
|
5
5
|
import { print } from 'esrap';
|
|
6
6
|
import tsx from 'esrap/languages/tsx';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
renderStylesheets,
|
|
9
|
+
setLocation,
|
|
10
|
+
applyLazyTransforms as apply_lazy_transforms,
|
|
11
|
+
collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
|
|
12
|
+
preallocateLazyIds as preallocate_lazy_ids,
|
|
13
|
+
replaceLazyParams as replace_lazy_params,
|
|
14
|
+
prepareStylesheetForRender as prepare_stylesheet_for_render,
|
|
15
|
+
annotateComponentWithHash as annotate_component_with_hash,
|
|
16
|
+
} from '@tsrx/core';
|
|
8
17
|
|
|
9
18
|
/**
|
|
10
19
|
* @typedef {{
|
|
11
20
|
* local_statement_component_index: number,
|
|
12
21
|
* needs_error_boundary: boolean,
|
|
13
22
|
* needs_suspense: boolean,
|
|
23
|
+
* helper_state: { base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] } | null,
|
|
24
|
+
* available_bindings: Map<string, AST.Identifier>,
|
|
25
|
+
* lazy_next_id: number,
|
|
26
|
+
* current_css_hash: string | null,
|
|
14
27
|
* }} TransformContext
|
|
15
28
|
*/
|
|
16
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {{ source_name: string, read: () => any }} LazyBinding
|
|
32
|
+
*/
|
|
33
|
+
|
|
17
34
|
/**
|
|
18
35
|
* Transform a parsed tsrx-react AST into a TSX/JSX module.
|
|
19
36
|
*
|
|
@@ -39,8 +56,14 @@ export function transform(ast, source, filename) {
|
|
|
39
56
|
local_statement_component_index: 0,
|
|
40
57
|
needs_error_boundary: false,
|
|
41
58
|
needs_suspense: false,
|
|
59
|
+
helper_state: null,
|
|
60
|
+
available_bindings: new Map(),
|
|
61
|
+
lazy_next_id: 0,
|
|
62
|
+
current_css_hash: null,
|
|
42
63
|
};
|
|
43
64
|
|
|
65
|
+
preallocate_lazy_ids(/** @type {any} */ (ast), transform_context);
|
|
66
|
+
|
|
44
67
|
walk(/** @type {any} */ (ast), transform_context, {
|
|
45
68
|
Component(node, { next, state }) {
|
|
46
69
|
const as_any = /** @type {any} */ (node);
|
|
@@ -56,8 +79,42 @@ export function transform(ast, source, filename) {
|
|
|
56
79
|
|
|
57
80
|
const transformed = walk(/** @type {any} */ (ast), transform_context, {
|
|
58
81
|
Component(node, { next, state }) {
|
|
82
|
+
const as_any = /** @type {any} */ (node);
|
|
83
|
+
|
|
84
|
+
// Set up helper_state and bindings BEFORE next() so that nested
|
|
85
|
+
// hook_safe_* calls (inside Element children) can register helpers
|
|
86
|
+
// and access available bindings during the bottom-up walk.
|
|
87
|
+
const helper_state = create_helper_state(as_any.id?.name || 'Component');
|
|
88
|
+
const saved_helper_state = state.helper_state;
|
|
89
|
+
const saved_bindings = state.available_bindings;
|
|
90
|
+
const saved_css_hash = state.current_css_hash;
|
|
91
|
+
state.helper_state = helper_state;
|
|
92
|
+
state.current_css_hash = as_any.css ? as_any.css.hash : null;
|
|
93
|
+
|
|
94
|
+
// Pre-collect component body bindings (params + top-level statements)
|
|
95
|
+
// so that Element children processed during the bottom-up walk can see
|
|
96
|
+
// the full scope. Without this, hoisted helpers would miss body-level
|
|
97
|
+
// variables like `const [x] = useState(...)` and produce ReferenceErrors.
|
|
98
|
+
// Only collect up to the split point — bindings declared after a
|
|
99
|
+
// hook-safe split aren't in scope at the return statement and would
|
|
100
|
+
// cause ReferenceErrors if passed as helper props.
|
|
101
|
+
const body_bindings = collect_param_bindings(as_any.params || []);
|
|
102
|
+
const body = as_any.body || [];
|
|
103
|
+
const split_index = find_hook_safe_split_index(body);
|
|
104
|
+
const collect_end = split_index === -1 ? body.length : split_index;
|
|
105
|
+
for (let i = 0; i < collect_end; i += 1) {
|
|
106
|
+
collect_statement_bindings(body[i], body_bindings);
|
|
107
|
+
}
|
|
108
|
+
state.available_bindings = body_bindings;
|
|
109
|
+
|
|
59
110
|
const inner = /** @type {any} */ (next() ?? node);
|
|
60
|
-
|
|
111
|
+
|
|
112
|
+
// Restore context
|
|
113
|
+
state.helper_state = saved_helper_state;
|
|
114
|
+
state.available_bindings = saved_bindings;
|
|
115
|
+
state.current_css_hash = saved_css_hash;
|
|
116
|
+
|
|
117
|
+
return /** @type {any} */ (component_to_function_declaration(inner, state, helper_state));
|
|
61
118
|
},
|
|
62
119
|
|
|
63
120
|
Tsx(node, { next }) {
|
|
@@ -86,12 +143,30 @@ export function transform(ast, source, filename) {
|
|
|
86
143
|
const inner = /** @type {any} */ (next() ?? node);
|
|
87
144
|
return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
|
|
88
145
|
},
|
|
146
|
+
|
|
147
|
+
MemberExpression(node, { next, state }) {
|
|
148
|
+
const as_any = /** @type {any} */ (node);
|
|
149
|
+
if (as_any.object && as_any.object.type === 'StyleIdentifier' && state.current_css_hash) {
|
|
150
|
+
const class_name = as_any.computed ? as_any.property.value : as_any.property.name;
|
|
151
|
+
const value = `${state.current_css_hash} ${class_name}`;
|
|
152
|
+
return /** @type {any} */ ({ type: 'Literal', value, raw: JSON.stringify(value) });
|
|
153
|
+
}
|
|
154
|
+
return next();
|
|
155
|
+
},
|
|
89
156
|
});
|
|
90
157
|
|
|
91
158
|
const expanded = expand_component_helpers(/** @type {AST.Program} */ (transformed));
|
|
92
159
|
inject_try_imports(expanded, transform_context);
|
|
93
160
|
|
|
94
|
-
|
|
161
|
+
// Apply lazy destructuring transforms to module-level code (top-level function
|
|
162
|
+
// declarations, arrow functions, etc.). Component bodies have already been
|
|
163
|
+
// transformed inside component_to_function_declaration; this catches plain
|
|
164
|
+
// functions outside components and any lazy patterns in module scope.
|
|
165
|
+
const final_program = /** @type {any} */ (
|
|
166
|
+
apply_lazy_transforms(/** @type {any} */ (expanded), new Map())
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const result = print(/** @type {any} */ (final_program), tsx(), {
|
|
95
170
|
sourceMapSource: filename,
|
|
96
171
|
sourceMapContent: source,
|
|
97
172
|
});
|
|
@@ -106,30 +181,60 @@ export function transform(ast, source, filename) {
|
|
|
106
181
|
}
|
|
107
182
|
: null;
|
|
108
183
|
|
|
109
|
-
return { ast:
|
|
184
|
+
return { ast: final_program, code: result.code, map: result.map, css };
|
|
110
185
|
}
|
|
111
186
|
|
|
112
187
|
/**
|
|
113
188
|
* @param {any} component
|
|
114
189
|
* @param {TransformContext} transform_context
|
|
190
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} [walk_helper_state]
|
|
115
191
|
* @returns {AST.FunctionDeclaration}
|
|
116
192
|
*/
|
|
117
|
-
function component_to_function_declaration(component, transform_context) {
|
|
118
|
-
const helper_state = create_helper_state(component.id?.name || 'Component');
|
|
193
|
+
function component_to_function_declaration(component, transform_context, walk_helper_state) {
|
|
194
|
+
const helper_state = walk_helper_state || create_helper_state(component.id?.name || 'Component');
|
|
195
|
+
const params = component.params || [];
|
|
196
|
+
const body = /** @type {any[]} */ (component.body || []);
|
|
197
|
+
|
|
198
|
+
// Collect param bindings from original patterns (lazy patterns still intact).
|
|
199
|
+
const param_bindings = collect_param_bindings(params);
|
|
200
|
+
|
|
201
|
+
// Collect lazy binding info WITHOUT mutating patterns. Stores lazy_id on metadata
|
|
202
|
+
// for later replacement. Body bindings (count, setCount, etc.) are still in the
|
|
203
|
+
// original patterns, so collect_statement_bindings during build will find them.
|
|
204
|
+
const lazy_bindings = collect_lazy_bindings_from_component(params, body, transform_context);
|
|
205
|
+
|
|
206
|
+
// Save and set context for this component scope
|
|
207
|
+
const saved_helper_state = transform_context.helper_state;
|
|
208
|
+
const saved_bindings = transform_context.available_bindings;
|
|
209
|
+
transform_context.helper_state = helper_state;
|
|
210
|
+
transform_context.available_bindings = new Map(param_bindings);
|
|
211
|
+
|
|
212
|
+
const body_statements = build_component_statements(
|
|
213
|
+
body,
|
|
214
|
+
helper_state,
|
|
215
|
+
param_bindings,
|
|
216
|
+
transform_context,
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Replace lazy param patterns with generated identifiers
|
|
220
|
+
const final_params = lazy_bindings.size > 0 ? replace_lazy_params(params) : params;
|
|
221
|
+
|
|
222
|
+
// Wrap body_statements in a BlockStatement so that apply_lazy_transforms
|
|
223
|
+
// runs collect_block_shadowed_names and detects body-level declarations
|
|
224
|
+
// (e.g. `const name = ...`) that shadow lazy binding names.
|
|
225
|
+
const body_block = /** @type {any} */ ({
|
|
226
|
+
type: 'BlockStatement',
|
|
227
|
+
body: body_statements,
|
|
228
|
+
metadata: { path: [] },
|
|
229
|
+
});
|
|
230
|
+
const final_body =
|
|
231
|
+
lazy_bindings.size > 0 ? apply_lazy_transforms(body_block, lazy_bindings) : body_block;
|
|
232
|
+
|
|
119
233
|
const fn = /** @type {any} */ ({
|
|
120
234
|
type: 'FunctionDeclaration',
|
|
121
235
|
id: component.id,
|
|
122
|
-
params:
|
|
123
|
-
body:
|
|
124
|
-
type: 'BlockStatement',
|
|
125
|
-
body: build_component_statements(
|
|
126
|
-
/** @type {any[]} */ (component.body),
|
|
127
|
-
helper_state,
|
|
128
|
-
collect_param_bindings(component.params || []),
|
|
129
|
-
transform_context,
|
|
130
|
-
),
|
|
131
|
-
metadata: { path: [] },
|
|
132
|
-
},
|
|
236
|
+
params: final_params,
|
|
237
|
+
body: final_body,
|
|
133
238
|
async: false,
|
|
134
239
|
generator: false,
|
|
135
240
|
metadata: {
|
|
@@ -138,7 +243,12 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
138
243
|
},
|
|
139
244
|
});
|
|
140
245
|
|
|
246
|
+
// Restore context
|
|
247
|
+
transform_context.helper_state = saved_helper_state;
|
|
248
|
+
transform_context.available_bindings = saved_bindings;
|
|
249
|
+
|
|
141
250
|
fn.metadata.generated_helpers = helper_state.helpers;
|
|
251
|
+
fn.metadata.generated_statics = helper_state.statics;
|
|
142
252
|
|
|
143
253
|
if (fn.id) {
|
|
144
254
|
fn.id.metadata = /** @type {AST.Identifier['metadata']} */ ({
|
|
@@ -153,7 +263,7 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
153
263
|
|
|
154
264
|
/**
|
|
155
265
|
* @param {any[]} body_nodes
|
|
156
|
-
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
266
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
157
267
|
* @param {Map<string, AST.Identifier>} available_bindings
|
|
158
268
|
* @param {TransformContext} transform_context
|
|
159
269
|
* @returns {any[]}
|
|
@@ -191,9 +301,12 @@ function build_component_statements(
|
|
|
191
301
|
} else {
|
|
192
302
|
statements.push(child);
|
|
193
303
|
collect_statement_bindings(child, bindings);
|
|
304
|
+
transform_context.available_bindings = bindings;
|
|
194
305
|
}
|
|
195
306
|
}
|
|
196
307
|
|
|
308
|
+
hoist_static_render_nodes(render_nodes, transform_context);
|
|
309
|
+
|
|
197
310
|
const split_node = body_nodes[split_index];
|
|
198
311
|
const consequent_body =
|
|
199
312
|
split_node.consequent.type === 'BlockStatement'
|
|
@@ -250,9 +363,15 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
250
363
|
const statements = [];
|
|
251
364
|
const render_nodes = [];
|
|
252
365
|
|
|
366
|
+
// Create a new bindings map so inner-scope bindings from
|
|
367
|
+
// collect_statement_bindings don't leak to the caller's scope.
|
|
368
|
+
const saved_bindings = transform_context.available_bindings;
|
|
369
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
370
|
+
|
|
253
371
|
for (const child of body_nodes) {
|
|
254
372
|
if (is_bare_return_statement(child)) {
|
|
255
373
|
statements.push(create_component_return_statement(render_nodes, child));
|
|
374
|
+
transform_context.available_bindings = saved_bindings;
|
|
256
375
|
return statements;
|
|
257
376
|
}
|
|
258
377
|
|
|
@@ -265,9 +384,12 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
265
384
|
render_nodes.push(to_jsx_child(child, transform_context));
|
|
266
385
|
} else {
|
|
267
386
|
statements.push(child);
|
|
387
|
+
collect_statement_bindings(child, transform_context.available_bindings);
|
|
268
388
|
}
|
|
269
389
|
}
|
|
270
390
|
|
|
391
|
+
hoist_static_render_nodes(render_nodes, transform_context);
|
|
392
|
+
|
|
271
393
|
const return_arg = build_return_expression(render_nodes);
|
|
272
394
|
if (return_arg || return_null_when_empty) {
|
|
273
395
|
statements.push({
|
|
@@ -276,6 +398,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
276
398
|
});
|
|
277
399
|
}
|
|
278
400
|
|
|
401
|
+
transform_context.available_bindings = saved_bindings;
|
|
279
402
|
return statements;
|
|
280
403
|
}
|
|
281
404
|
|
|
@@ -393,7 +516,7 @@ function is_hook_callee(callee) {
|
|
|
393
516
|
|
|
394
517
|
/**
|
|
395
518
|
* @param {any[]} body_nodes
|
|
396
|
-
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
519
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
397
520
|
* @param {Map<string, AST.Identifier>} available_bindings
|
|
398
521
|
* @param {any} source_node
|
|
399
522
|
* @param {string} suffix
|
|
@@ -433,7 +556,7 @@ function create_helper_component_expression(
|
|
|
433
556
|
/**
|
|
434
557
|
* @param {AST.Identifier} helper_id
|
|
435
558
|
* @param {any[]} body_nodes
|
|
436
|
-
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
559
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
437
560
|
* @param {Map<string, AST.Identifier>} available_bindings
|
|
438
561
|
* @param {AST.Identifier[]} helper_bindings
|
|
439
562
|
* @param {any} source_node
|
|
@@ -552,7 +675,7 @@ function create_helper_component_element(helper_id, bindings, source_node) {
|
|
|
552
675
|
}
|
|
553
676
|
|
|
554
677
|
/**
|
|
555
|
-
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
678
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
556
679
|
* @param {string} suffix
|
|
557
680
|
* @returns {string}
|
|
558
681
|
*/
|
|
@@ -563,13 +686,14 @@ function create_helper_name(helper_state, suffix) {
|
|
|
563
686
|
|
|
564
687
|
/**
|
|
565
688
|
* @param {string} base_name
|
|
566
|
-
* @returns {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }}
|
|
689
|
+
* @returns {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }}
|
|
567
690
|
*/
|
|
568
691
|
function create_helper_state(base_name) {
|
|
569
692
|
return {
|
|
570
693
|
base_name,
|
|
571
694
|
next_id: 0,
|
|
572
695
|
helpers: [],
|
|
696
|
+
statics: [],
|
|
573
697
|
};
|
|
574
698
|
}
|
|
575
699
|
|
|
@@ -606,6 +730,18 @@ function collect_statement_bindings(statement, bindings) {
|
|
|
606
730
|
) {
|
|
607
731
|
bindings.set(statement.id.name, statement.id);
|
|
608
732
|
}
|
|
733
|
+
|
|
734
|
+
// Statement-level lazy assignment: `&[x] = expr;` introduces `x` as a binding.
|
|
735
|
+
if (
|
|
736
|
+
statement.type === 'ExpressionStatement' &&
|
|
737
|
+
statement.expression?.type === 'AssignmentExpression' &&
|
|
738
|
+
statement.expression.operator === '=' &&
|
|
739
|
+
(statement.expression.left?.type === 'ObjectPattern' ||
|
|
740
|
+
statement.expression.left?.type === 'ArrayPattern') &&
|
|
741
|
+
statement.expression.left.lazy
|
|
742
|
+
) {
|
|
743
|
+
collect_pattern_bindings(statement.expression.left, bindings);
|
|
744
|
+
}
|
|
609
745
|
}
|
|
610
746
|
|
|
611
747
|
/**
|
|
@@ -649,6 +785,93 @@ function collect_pattern_bindings(pattern, bindings) {
|
|
|
649
785
|
}
|
|
650
786
|
}
|
|
651
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Check if a node references any of the given scope bindings.
|
|
790
|
+
* Used to determine if a JSX element is static and can be hoisted to module level.
|
|
791
|
+
*
|
|
792
|
+
* @param {any} node
|
|
793
|
+
* @param {Map<string, AST.Identifier>} scope_bindings
|
|
794
|
+
* @returns {boolean}
|
|
795
|
+
*/
|
|
796
|
+
function references_scope_bindings(node, scope_bindings) {
|
|
797
|
+
if (!node || typeof node !== 'object') return false;
|
|
798
|
+
if (scope_bindings.size === 0) return false;
|
|
799
|
+
|
|
800
|
+
if (node.type === 'Identifier') {
|
|
801
|
+
return scope_bindings.has(node.name);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// JSXIdentifier is a variable reference when capitalized (tag name like <MyComponent />)
|
|
805
|
+
// or when it's the object of a JSXMemberExpression (e.g. ui in <ui.Button />)
|
|
806
|
+
if (node.type === 'JSXIdentifier') {
|
|
807
|
+
return scope_bindings.has(node.name);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (Array.isArray(node)) {
|
|
811
|
+
return node.some((child) => references_scope_bindings(child, scope_bindings));
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
for (const key of Object.keys(node)) {
|
|
815
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
816
|
+
|
|
817
|
+
// Skip non-computed, non-shorthand property keys (they are labels, not references)
|
|
818
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) continue;
|
|
819
|
+
|
|
820
|
+
// Skip non-computed member expression property access
|
|
821
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) continue;
|
|
822
|
+
|
|
823
|
+
// Skip JSXMemberExpression property (e.g. Button in <Icons.Button /> is a label, not a reference)
|
|
824
|
+
if (key === 'property' && node.type === 'JSXMemberExpression') continue;
|
|
825
|
+
|
|
826
|
+
// Skip JSXAttribute names — they are attribute labels, not variable references
|
|
827
|
+
if (key === 'name' && node.type === 'JSXAttribute') continue;
|
|
828
|
+
|
|
829
|
+
if (references_scope_bindings(node[key], scope_bindings)) return true;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Hoist static JSX elements from render_nodes to module level.
|
|
837
|
+
* A JSX element is static if it doesn't reference any component-scope bindings.
|
|
838
|
+
* Hoisting prevents React from recreating the element on every render, allowing
|
|
839
|
+
* the reconciler to skip diffing when it sees the same element identity.
|
|
840
|
+
*
|
|
841
|
+
* @param {any[]} render_nodes
|
|
842
|
+
* @param {TransformContext} transform_context
|
|
843
|
+
*/
|
|
844
|
+
function hoist_static_render_nodes(render_nodes, transform_context) {
|
|
845
|
+
if (!transform_context.helper_state) return;
|
|
846
|
+
|
|
847
|
+
for (let i = 0; i < render_nodes.length; i++) {
|
|
848
|
+
const node = render_nodes[i];
|
|
849
|
+
if (node.type !== 'JSXElement') continue;
|
|
850
|
+
if (references_scope_bindings(node, transform_context.available_bindings)) continue;
|
|
851
|
+
|
|
852
|
+
const name = create_helper_name(transform_context.helper_state, 'static');
|
|
853
|
+
const id = create_generated_identifier(name);
|
|
854
|
+
|
|
855
|
+
transform_context.helper_state.statics.push(
|
|
856
|
+
/** @type {any} */ ({
|
|
857
|
+
type: 'VariableDeclaration',
|
|
858
|
+
kind: 'const',
|
|
859
|
+
declarations: [
|
|
860
|
+
{
|
|
861
|
+
type: 'VariableDeclarator',
|
|
862
|
+
id,
|
|
863
|
+
init: node,
|
|
864
|
+
metadata: { path: [] },
|
|
865
|
+
},
|
|
866
|
+
],
|
|
867
|
+
metadata: { path: [] },
|
|
868
|
+
}),
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
render_nodes[i] = to_jsx_expression_container(clone_identifier(id), node);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
652
875
|
/**
|
|
653
876
|
* @param {AST.Identifier} identifier
|
|
654
877
|
* @returns {AST.Identifier}
|
|
@@ -683,9 +906,11 @@ function create_null_literal() {
|
|
|
683
906
|
function expand_component_helpers(program) {
|
|
684
907
|
program.body = program.body.flatMap((statement) => {
|
|
685
908
|
if (statement.type === 'FunctionDeclaration') {
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
909
|
+
const meta = /** @type {any} */ (statement.metadata);
|
|
910
|
+
const statics = meta?.generated_statics || [];
|
|
911
|
+
const helpers = meta?.generated_helpers || [];
|
|
912
|
+
if (statics.length || helpers.length) {
|
|
913
|
+
return [...statics, ...helpers, statement];
|
|
689
914
|
}
|
|
690
915
|
}
|
|
691
916
|
|
|
@@ -694,9 +919,11 @@ function expand_component_helpers(program) {
|
|
|
694
919
|
statement.type === 'ExportDefaultDeclaration') &&
|
|
695
920
|
statement.declaration?.type === 'FunctionDeclaration'
|
|
696
921
|
) {
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
922
|
+
const meta = /** @type {any} */ (statement.declaration.metadata);
|
|
923
|
+
const statics = meta?.generated_statics || [];
|
|
924
|
+
const helpers = meta?.generated_helpers || [];
|
|
925
|
+
if (statics.length || helpers.length) {
|
|
926
|
+
return [...statics, ...helpers, statement];
|
|
700
927
|
}
|
|
701
928
|
}
|
|
702
929
|
|
|
@@ -775,176 +1002,6 @@ function create_component_lone_return_if_statement(node, render_nodes) {
|
|
|
775
1002
|
);
|
|
776
1003
|
}
|
|
777
1004
|
|
|
778
|
-
/**
|
|
779
|
-
* Mark every selector inside the stylesheet as "used" so `renderStylesheets`
|
|
780
|
-
* does not comment it out. We skip Ripple's selector-pruning pass because
|
|
781
|
-
* React component boundaries are dynamic — any selector authored inside the
|
|
782
|
-
* component's `<style>` block is considered intentional.
|
|
783
|
-
*
|
|
784
|
-
* @param {any} stylesheet
|
|
785
|
-
* @returns {any}
|
|
786
|
-
*/
|
|
787
|
-
function prepare_stylesheet_for_render(stylesheet) {
|
|
788
|
-
walk(stylesheet, null, {
|
|
789
|
-
_(node, { next }) {
|
|
790
|
-
if (node && node.metadata && typeof node.metadata === 'object') {
|
|
791
|
-
node.metadata.used = true;
|
|
792
|
-
if (node.type === 'RelativeSelector' && !node.metadata.is_global) {
|
|
793
|
-
node.metadata.scoped = true;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
return next();
|
|
797
|
-
},
|
|
798
|
-
});
|
|
799
|
-
return stylesheet;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* @param {any} node
|
|
804
|
-
* @returns {boolean}
|
|
805
|
-
*/
|
|
806
|
-
function is_style_element(node) {
|
|
807
|
-
return (
|
|
808
|
-
node &&
|
|
809
|
-
node.type === 'Element' &&
|
|
810
|
-
node.id &&
|
|
811
|
-
node.id.type === 'Identifier' &&
|
|
812
|
-
node.id.name === 'style'
|
|
813
|
-
);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* @param {any} node
|
|
818
|
-
* @returns {boolean}
|
|
819
|
-
*/
|
|
820
|
-
function is_composite_element(node) {
|
|
821
|
-
if (!node || node.type !== 'Element' || !node.id) {
|
|
822
|
-
return false;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
if (node.id.type === 'Identifier') {
|
|
826
|
-
return /^[A-Z]/.test(node.id.name);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
return node.id.type === 'MemberExpression';
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
/**
|
|
833
|
-
* Recursively walk Element nodes within a component body and add the hash
|
|
834
|
-
* class name so scope-qualified selectors (e.g. `.foo.hash`) match.
|
|
835
|
-
*
|
|
836
|
-
* @param {any} node
|
|
837
|
-
* @param {string} hash
|
|
838
|
-
* @returns {any}
|
|
839
|
-
*/
|
|
840
|
-
function annotate_with_hash(node, hash) {
|
|
841
|
-
if (!node || typeof node !== 'object') return node;
|
|
842
|
-
if (
|
|
843
|
-
node.type === 'Component' ||
|
|
844
|
-
node.type === 'FunctionDeclaration' ||
|
|
845
|
-
node.type === 'FunctionExpression' ||
|
|
846
|
-
node.type === 'ArrowFunctionExpression'
|
|
847
|
-
) {
|
|
848
|
-
return node;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
if (node.type === 'Element') {
|
|
852
|
-
if (!is_style_element(node) && !is_composite_element(node)) {
|
|
853
|
-
add_hash_class(node, hash);
|
|
854
|
-
}
|
|
855
|
-
if (Array.isArray(node.children)) {
|
|
856
|
-
node.children = node.children
|
|
857
|
-
.filter((/** @type {any} */ child) => !is_style_element(child))
|
|
858
|
-
.map((/** @type {any} */ child) => annotate_with_hash(child, hash));
|
|
859
|
-
}
|
|
860
|
-
return node;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
for (const key of Object.keys(node)) {
|
|
864
|
-
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
|
|
865
|
-
continue;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
const value = node[key];
|
|
869
|
-
if (Array.isArray(value)) {
|
|
870
|
-
node[key] = value.map((/** @type {any} */ child) => annotate_with_hash(child, hash));
|
|
871
|
-
} else if (value && typeof value === 'object') {
|
|
872
|
-
node[key] = annotate_with_hash(value, hash);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
return node;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
/**
|
|
880
|
-
* @param {any} component
|
|
881
|
-
* @param {string} hash
|
|
882
|
-
* @returns {void}
|
|
883
|
-
*/
|
|
884
|
-
function annotate_component_with_hash(component, hash) {
|
|
885
|
-
/** @type {any[]} */
|
|
886
|
-
const body = component.body;
|
|
887
|
-
component.body = body
|
|
888
|
-
.filter((/** @type {any} */ child) => !is_style_element(child))
|
|
889
|
-
.map((/** @type {any} */ child) => annotate_with_hash(child, hash));
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* Ensure the element carries a `class` attribute containing the scoping hash.
|
|
894
|
-
* @param {any} element
|
|
895
|
-
* @param {string} hash
|
|
896
|
-
*/
|
|
897
|
-
function add_hash_class(element, hash) {
|
|
898
|
-
const attrs = element.attributes || (element.attributes = []);
|
|
899
|
-
const existing = attrs.find(
|
|
900
|
-
(/** @type {any} */ a) =>
|
|
901
|
-
a.type === 'Attribute' &&
|
|
902
|
-
a.name &&
|
|
903
|
-
a.name.type === 'Identifier' &&
|
|
904
|
-
(a.name.name === 'class' || a.name.name === 'className'),
|
|
905
|
-
);
|
|
906
|
-
|
|
907
|
-
if (!existing) {
|
|
908
|
-
attrs.push({
|
|
909
|
-
type: 'Attribute',
|
|
910
|
-
name: { type: 'Identifier', name: 'class' },
|
|
911
|
-
value: { type: 'Literal', value: hash, raw: JSON.stringify(hash) },
|
|
912
|
-
});
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
const value = existing.value;
|
|
917
|
-
if (!value) {
|
|
918
|
-
existing.value = { type: 'Literal', value: hash, raw: JSON.stringify(hash) };
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
if (value.type === 'Literal' && typeof value.value === 'string') {
|
|
923
|
-
const merged = `${value.value} ${hash}`;
|
|
924
|
-
existing.value = { type: 'Literal', value: merged, raw: JSON.stringify(merged) };
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// Dynamic expression. Concatenate at runtime via template literal.
|
|
929
|
-
const expression = value.type === 'JSXExpressionContainer' ? value.expression : value;
|
|
930
|
-
existing.value = {
|
|
931
|
-
type: 'TemplateLiteral',
|
|
932
|
-
expressions: [expression],
|
|
933
|
-
quasis: [
|
|
934
|
-
{
|
|
935
|
-
type: 'TemplateElement',
|
|
936
|
-
value: { raw: '', cooked: '' },
|
|
937
|
-
tail: false,
|
|
938
|
-
},
|
|
939
|
-
{
|
|
940
|
-
type: 'TemplateElement',
|
|
941
|
-
value: { raw: ` ${hash}`, cooked: ` ${hash}` },
|
|
942
|
-
tail: true,
|
|
943
|
-
},
|
|
944
|
-
],
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
|
|
948
1005
|
/**
|
|
949
1006
|
* @param {any} node
|
|
950
1007
|
* @returns {boolean}
|
|
@@ -959,6 +1016,10 @@ function is_jsx_child(node) {
|
|
|
959
1016
|
t === 'JSXText' ||
|
|
960
1017
|
t === 'Tsx' ||
|
|
961
1018
|
t === 'TsxCompat' ||
|
|
1019
|
+
t === 'Element' ||
|
|
1020
|
+
t === 'Text' ||
|
|
1021
|
+
t === 'TSRXExpression' ||
|
|
1022
|
+
t === 'Html' ||
|
|
962
1023
|
t === 'IfStatement' ||
|
|
963
1024
|
t === 'ForOfStatement' ||
|
|
964
1025
|
t === 'SwitchStatement' ||
|
|
@@ -973,6 +1034,11 @@ function is_jsx_child(node) {
|
|
|
973
1034
|
*/
|
|
974
1035
|
function to_jsx_element(node, transform_context) {
|
|
975
1036
|
if (node.type === 'JSXElement') return node;
|
|
1037
|
+
if ((node.children || []).some((/** @type {any} */ c) => c && c.type === 'Html')) {
|
|
1038
|
+
throw new Error(
|
|
1039
|
+
'`{html ...}` is not supported on the React target. Use `dangerouslySetInnerHTML={{ __html: ... }}` as an element attribute instead.',
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
976
1042
|
if (is_dynamic_element_id(node.id)) {
|
|
977
1043
|
return dynamic_element_to_jsx_child(node, transform_context);
|
|
978
1044
|
}
|
|
@@ -1144,11 +1210,17 @@ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
|
1144
1210
|
create_generated_identifier(create_local_statement_component_name(transform_context)),
|
|
1145
1211
|
source_node,
|
|
1146
1212
|
);
|
|
1213
|
+
const helper_bindings = Array.from(transform_context.available_bindings.values());
|
|
1214
|
+
|
|
1215
|
+
// Save and isolate bindings for the helper body
|
|
1216
|
+
const saved_bindings = transform_context.available_bindings;
|
|
1217
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
1218
|
+
|
|
1147
1219
|
const helper_fn = set_loc(
|
|
1148
1220
|
/** @type {any} */ ({
|
|
1149
1221
|
type: 'FunctionDeclaration',
|
|
1150
1222
|
id: helper_id,
|
|
1151
|
-
params: [],
|
|
1223
|
+
params: helper_bindings.length > 0 ? [create_helper_props_pattern(helper_bindings)] : [],
|
|
1152
1224
|
body: {
|
|
1153
1225
|
type: 'BlockStatement',
|
|
1154
1226
|
body: build_render_statements(body_nodes, true, transform_context),
|
|
@@ -1165,6 +1237,19 @@ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
|
1165
1237
|
source_node,
|
|
1166
1238
|
);
|
|
1167
1239
|
|
|
1240
|
+
// Restore bindings
|
|
1241
|
+
transform_context.available_bindings = saved_bindings;
|
|
1242
|
+
|
|
1243
|
+
// Register helper for hoisting to module level
|
|
1244
|
+
if (transform_context.helper_state) {
|
|
1245
|
+
transform_context.helper_state.helpers.push(helper_fn);
|
|
1246
|
+
|
|
1247
|
+
return to_jsx_expression_container(
|
|
1248
|
+
/** @type {any} */ (create_helper_component_element(helper_id, helper_bindings, source_node)),
|
|
1249
|
+
source_node,
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1168
1253
|
return to_jsx_expression_container(
|
|
1169
1254
|
/** @type {any} */ ({
|
|
1170
1255
|
type: 'CallExpression',
|
|
@@ -1177,7 +1262,7 @@ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
|
1177
1262
|
helper_fn,
|
|
1178
1263
|
{
|
|
1179
1264
|
type: 'ReturnStatement',
|
|
1180
|
-
argument: create_helper_component_element(helper_id,
|
|
1265
|
+
argument: create_helper_component_element(helper_id, helper_bindings, source_node),
|
|
1181
1266
|
metadata: { path: [] },
|
|
1182
1267
|
},
|
|
1183
1268
|
],
|
|
@@ -1206,8 +1291,10 @@ function create_local_statement_component_name(transform_context) {
|
|
|
1206
1291
|
}
|
|
1207
1292
|
|
|
1208
1293
|
/**
|
|
1209
|
-
* Wraps a list of body nodes into a
|
|
1210
|
-
* statements that
|
|
1294
|
+
* Wraps a list of body nodes into a component and returns
|
|
1295
|
+
* statements that return `<ComponentName prop1={prop1} ... />`.
|
|
1296
|
+
* The component is hoisted to module level via helper_state to avoid
|
|
1297
|
+
* recreating the component identity on every render.
|
|
1211
1298
|
* Used when a control flow branch contains hook calls that must be moved
|
|
1212
1299
|
* into their own component boundary to satisfy the Rules of Hooks.
|
|
1213
1300
|
*
|
|
@@ -1222,12 +1309,17 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
|
|
|
1222
1309
|
create_generated_identifier(create_local_statement_component_name(transform_context)),
|
|
1223
1310
|
source_node,
|
|
1224
1311
|
);
|
|
1312
|
+
const helper_bindings = Array.from(transform_context.available_bindings.values());
|
|
1313
|
+
|
|
1314
|
+
// Save and isolate bindings for the helper body
|
|
1315
|
+
const saved_bindings = transform_context.available_bindings;
|
|
1316
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
1225
1317
|
|
|
1226
1318
|
const helper_fn = set_loc(
|
|
1227
1319
|
/** @type {any} */ ({
|
|
1228
1320
|
type: 'FunctionDeclaration',
|
|
1229
1321
|
id: helper_id,
|
|
1230
|
-
params: [],
|
|
1322
|
+
params: helper_bindings.length > 0 ? [create_helper_props_pattern(helper_bindings)] : [],
|
|
1231
1323
|
body: {
|
|
1232
1324
|
type: 'BlockStatement',
|
|
1233
1325
|
body: build_render_statements(body_nodes, true, transform_context),
|
|
@@ -1244,7 +1336,19 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
|
|
|
1244
1336
|
source_node,
|
|
1245
1337
|
);
|
|
1246
1338
|
|
|
1247
|
-
|
|
1339
|
+
// Restore bindings
|
|
1340
|
+
transform_context.available_bindings = saved_bindings;
|
|
1341
|
+
|
|
1342
|
+
// Register helper for hoisting to module level
|
|
1343
|
+
if (transform_context.helper_state) {
|
|
1344
|
+
transform_context.helper_state.helpers.push(helper_fn);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
const component_element = create_helper_component_element(
|
|
1348
|
+
helper_id,
|
|
1349
|
+
helper_bindings,
|
|
1350
|
+
source_node,
|
|
1351
|
+
);
|
|
1248
1352
|
|
|
1249
1353
|
if (key_expression) {
|
|
1250
1354
|
component_element.openingElement.attributes.push(
|
|
@@ -1257,8 +1361,20 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
|
|
|
1257
1361
|
);
|
|
1258
1362
|
}
|
|
1259
1363
|
|
|
1364
|
+
// When helper_state is null (no enclosing component context), inline the
|
|
1365
|
+
// helper via an IIFE so the function declaration isn't silently dropped.
|
|
1366
|
+
if (!transform_context.helper_state) {
|
|
1367
|
+
return [
|
|
1368
|
+
helper_fn,
|
|
1369
|
+
{
|
|
1370
|
+
type: 'ReturnStatement',
|
|
1371
|
+
argument: component_element,
|
|
1372
|
+
metadata: { path: [] },
|
|
1373
|
+
},
|
|
1374
|
+
];
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1260
1377
|
return [
|
|
1261
|
-
helper_fn,
|
|
1262
1378
|
{
|
|
1263
1379
|
type: 'ReturnStatement',
|
|
1264
1380
|
argument: component_element,
|
|
@@ -1307,6 +1423,10 @@ function to_jsx_child(node, transform_context) {
|
|
|
1307
1423
|
return to_jsx_expression_container(to_text_expression(node.expression, node), node);
|
|
1308
1424
|
case 'TSRXExpression':
|
|
1309
1425
|
return to_jsx_expression_container(node.expression, node);
|
|
1426
|
+
case 'Html':
|
|
1427
|
+
throw new Error(
|
|
1428
|
+
'`{html ...}` is not supported on the React target. Use `dangerouslySetInnerHTML={{ __html: ... }}` as an element attribute instead.',
|
|
1429
|
+
);
|
|
1310
1430
|
case 'IfStatement':
|
|
1311
1431
|
return if_statement_to_jsx_child(node, transform_context);
|
|
1312
1432
|
case 'ForOfStatement':
|
|
@@ -1411,6 +1531,20 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1411
1531
|
const has_hooks = body_contains_top_level_hook_call(loop_body);
|
|
1412
1532
|
const key_expression = has_hooks ? find_key_expression_in_body(loop_body) : undefined;
|
|
1413
1533
|
|
|
1534
|
+
// Add loop params to available bindings so hoisted helpers receive them as props
|
|
1535
|
+
const saved_bindings = transform_context.available_bindings;
|
|
1536
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
1537
|
+
for (const param of loop_params) {
|
|
1538
|
+
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
const body_statements = has_hooks
|
|
1542
|
+
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
1543
|
+
: build_render_statements(loop_body, true, transform_context);
|
|
1544
|
+
|
|
1545
|
+
// Restore bindings
|
|
1546
|
+
transform_context.available_bindings = saved_bindings;
|
|
1547
|
+
|
|
1414
1548
|
return to_jsx_expression_container(
|
|
1415
1549
|
/** @type {any} */ ({
|
|
1416
1550
|
type: 'CallExpression',
|
|
@@ -1428,9 +1562,7 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1428
1562
|
params: loop_params,
|
|
1429
1563
|
body: /** @type {any} */ ({
|
|
1430
1564
|
type: 'BlockStatement',
|
|
1431
|
-
body:
|
|
1432
|
-
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
1433
|
-
: build_render_statements(loop_body, true, transform_context),
|
|
1565
|
+
body: body_statements,
|
|
1434
1566
|
metadata: { path: [] },
|
|
1435
1567
|
}),
|
|
1436
1568
|
async: false,
|
|
@@ -1572,6 +1704,15 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
1572
1704
|
}
|
|
1573
1705
|
|
|
1574
1706
|
const catch_body_nodes = handler.body.body || [];
|
|
1707
|
+
|
|
1708
|
+
// Add catch params to available_bindings so static hoisting
|
|
1709
|
+
// correctly identifies references to err/reset as non-static
|
|
1710
|
+
const saved_catch_bindings = transform_context.available_bindings;
|
|
1711
|
+
transform_context.available_bindings = new Map(saved_catch_bindings);
|
|
1712
|
+
for (const param of catch_params) {
|
|
1713
|
+
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1575
1716
|
const fallback_fn = {
|
|
1576
1717
|
type: 'ArrowFunctionExpression',
|
|
1577
1718
|
params: catch_params,
|
|
@@ -1586,6 +1727,8 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
1586
1727
|
metadata: { path: [] },
|
|
1587
1728
|
};
|
|
1588
1729
|
|
|
1730
|
+
transform_context.available_bindings = saved_catch_bindings;
|
|
1731
|
+
|
|
1589
1732
|
result = create_jsx_element(
|
|
1590
1733
|
'TsrxErrorBoundary',
|
|
1591
1734
|
[
|