@tsrx/react 0.0.1
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/LICENSE +21 -0
- package/package.json +43 -0
- package/src/error-boundary.js +33 -0
- package/src/index.js +49 -0
- package/src/transform.js +2283 -0
- package/types/index.d.ts +15 -0
package/src/transform.js
ADDED
|
@@ -0,0 +1,2283 @@
|
|
|
1
|
+
/** @import * as AST from 'estree' */
|
|
2
|
+
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
3
|
+
|
|
4
|
+
import { walk } from 'zimmerframe';
|
|
5
|
+
import { print } from 'esrap';
|
|
6
|
+
import tsx from 'esrap/languages/tsx';
|
|
7
|
+
import { renderStylesheets, setLocation } from '@tsrx/core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* local_statement_component_index: number,
|
|
12
|
+
* needs_error_boundary: boolean,
|
|
13
|
+
* needs_suspense: boolean,
|
|
14
|
+
* }} TransformContext
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Transform a parsed tsrx-react AST into a TSX/JSX module.
|
|
19
|
+
*
|
|
20
|
+
* Replaces Ripple-specific `Component`/`Element`/`Text`/`TSRXExpression`
|
|
21
|
+
* nodes with their standard JSX equivalents inside a `FunctionDeclaration`.
|
|
22
|
+
* Any `<style>` element declared inside a component is collected,
|
|
23
|
+
* rendered via `@tsrx/core`'s stylesheet renderer, and returned alongside
|
|
24
|
+
* the JS output so a downstream plugin can inject it. The compiler also
|
|
25
|
+
* augments every non-style Element in a scoped component with the
|
|
26
|
+
* stylesheet's hash class so scoped selectors match correctly.
|
|
27
|
+
*
|
|
28
|
+
* @param {AST.Program} ast
|
|
29
|
+
* @param {string} source
|
|
30
|
+
* @param {string} [filename]
|
|
31
|
+
* @returns {{ ast: AST.Program, code: string, map: any, css: { code: string, hash: string } | null }}
|
|
32
|
+
*/
|
|
33
|
+
export function transform(ast, source, filename) {
|
|
34
|
+
/** @type {any[]} */
|
|
35
|
+
const stylesheets = [];
|
|
36
|
+
|
|
37
|
+
/** @type {TransformContext} */
|
|
38
|
+
const transform_context = {
|
|
39
|
+
local_statement_component_index: 0,
|
|
40
|
+
needs_error_boundary: false,
|
|
41
|
+
needs_suspense: false,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
walk(/** @type {any} */ (ast), transform_context, {
|
|
45
|
+
Component(node, { next, state }) {
|
|
46
|
+
const as_any = /** @type {any} */ (node);
|
|
47
|
+
const css = as_any.css;
|
|
48
|
+
if (css) {
|
|
49
|
+
stylesheets.push(css);
|
|
50
|
+
const hash = css.hash;
|
|
51
|
+
annotate_component_with_hash(as_any, hash);
|
|
52
|
+
}
|
|
53
|
+
return next(state);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const transformed = walk(/** @type {any} */ (ast), transform_context, {
|
|
58
|
+
Component(node, { next, state }) {
|
|
59
|
+
const inner = /** @type {any} */ (next() ?? node);
|
|
60
|
+
return /** @type {any} */ (component_to_function_declaration(inner, state));
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
Tsx(node, { next }) {
|
|
64
|
+
const inner = /** @type {any} */ (next() ?? node);
|
|
65
|
+
return /** @type {any} */ (tsx_node_to_jsx_expression(inner));
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
TsxCompat(node, { next }) {
|
|
69
|
+
const inner = /** @type {any} */ (next() ?? node);
|
|
70
|
+
return /** @type {any} */ (tsx_compat_node_to_jsx_expression(inner));
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
Element(node, { next, state }) {
|
|
74
|
+
const inner = /** @type {any} */ (next() ?? node);
|
|
75
|
+
return /** @type {any} */ (to_jsx_element(inner, state));
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
Text(node, { next }) {
|
|
79
|
+
const inner = /** @type {any} */ (next() ?? node);
|
|
80
|
+
return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
TSRXExpression(node, { next }) {
|
|
84
|
+
const inner = /** @type {any} */ (next() ?? node);
|
|
85
|
+
return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const expanded = expand_component_helpers(/** @type {AST.Program} */ (transformed));
|
|
90
|
+
inject_try_imports(expanded, transform_context);
|
|
91
|
+
|
|
92
|
+
const result = print(/** @type {any} */ (expanded), tsx(), {
|
|
93
|
+
sourceMapSource: filename,
|
|
94
|
+
sourceMapContent: source,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const css =
|
|
98
|
+
stylesheets.length > 0
|
|
99
|
+
? {
|
|
100
|
+
code: renderStylesheets(
|
|
101
|
+
/** @type {any} */ (stylesheets.map(prepare_stylesheet_for_render)),
|
|
102
|
+
),
|
|
103
|
+
hash: stylesheets.map((s) => s.hash).join(' '),
|
|
104
|
+
}
|
|
105
|
+
: null;
|
|
106
|
+
|
|
107
|
+
return { ast: expanded, code: result.code, map: result.map, css };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {any} component
|
|
112
|
+
* @param {TransformContext} transform_context
|
|
113
|
+
* @returns {AST.FunctionDeclaration}
|
|
114
|
+
*/
|
|
115
|
+
function component_to_function_declaration(component, transform_context) {
|
|
116
|
+
const helper_state = create_helper_state(component.id?.name || 'Component');
|
|
117
|
+
const fn = /** @type {any} */ ({
|
|
118
|
+
type: 'FunctionDeclaration',
|
|
119
|
+
id: component.id,
|
|
120
|
+
params: component.params || [],
|
|
121
|
+
body: {
|
|
122
|
+
type: 'BlockStatement',
|
|
123
|
+
body: build_component_statements(
|
|
124
|
+
/** @type {any[]} */ (component.body),
|
|
125
|
+
helper_state,
|
|
126
|
+
collect_param_bindings(component.params || []),
|
|
127
|
+
transform_context,
|
|
128
|
+
),
|
|
129
|
+
metadata: { path: [] },
|
|
130
|
+
},
|
|
131
|
+
async: false,
|
|
132
|
+
generator: false,
|
|
133
|
+
metadata: {
|
|
134
|
+
path: [],
|
|
135
|
+
is_component: true,
|
|
136
|
+
is_method: true,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
fn.metadata.generated_helpers = helper_state.helpers;
|
|
141
|
+
|
|
142
|
+
setLocation(fn, /** @type {any} */ (component), true);
|
|
143
|
+
return fn;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {any[]} body_nodes
|
|
148
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
149
|
+
* @param {Map<string, AST.Identifier>} available_bindings
|
|
150
|
+
* @param {TransformContext} transform_context
|
|
151
|
+
* @returns {any[]}
|
|
152
|
+
*/
|
|
153
|
+
function build_component_statements(
|
|
154
|
+
body_nodes,
|
|
155
|
+
helper_state,
|
|
156
|
+
available_bindings,
|
|
157
|
+
transform_context,
|
|
158
|
+
) {
|
|
159
|
+
const split_index = find_hook_safe_split_index(body_nodes);
|
|
160
|
+
if (split_index === -1) {
|
|
161
|
+
return build_render_statements(body_nodes, false, transform_context);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const statements = [];
|
|
165
|
+
const render_nodes = [];
|
|
166
|
+
const bindings = new Map(available_bindings);
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < split_index; i += 1) {
|
|
169
|
+
const child = body_nodes[i];
|
|
170
|
+
|
|
171
|
+
if (is_bare_return_statement(child)) {
|
|
172
|
+
statements.push(create_component_return_statement(render_nodes, child));
|
|
173
|
+
return statements;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (is_lone_return_if_statement(child)) {
|
|
177
|
+
statements.push(create_component_lone_return_if_statement(child, render_nodes));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (is_jsx_child(child)) {
|
|
182
|
+
render_nodes.push(to_jsx_child(child, transform_context));
|
|
183
|
+
} else {
|
|
184
|
+
statements.push(child);
|
|
185
|
+
collect_statement_bindings(child, bindings);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const split_node = body_nodes[split_index];
|
|
190
|
+
const consequent_body =
|
|
191
|
+
split_node.consequent.type === 'BlockStatement'
|
|
192
|
+
? split_node.consequent.body
|
|
193
|
+
: [split_node.consequent];
|
|
194
|
+
const short_branch_body = consequent_body.filter(
|
|
195
|
+
(/** @type {any} */ child) => !is_bare_return_statement(child),
|
|
196
|
+
);
|
|
197
|
+
const continuation_body = body_nodes.slice(split_index + 1);
|
|
198
|
+
const short_branch = create_helper_component_expression(
|
|
199
|
+
short_branch_body,
|
|
200
|
+
helper_state,
|
|
201
|
+
bindings,
|
|
202
|
+
split_node.consequent,
|
|
203
|
+
'Exit',
|
|
204
|
+
transform_context,
|
|
205
|
+
);
|
|
206
|
+
const continuation = create_helper_component_expression(
|
|
207
|
+
continuation_body,
|
|
208
|
+
helper_state,
|
|
209
|
+
bindings,
|
|
210
|
+
split_node,
|
|
211
|
+
'Continue',
|
|
212
|
+
transform_context,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
render_nodes.push(
|
|
216
|
+
to_jsx_expression_container(
|
|
217
|
+
set_loc(
|
|
218
|
+
/** @type {any} */ ({
|
|
219
|
+
type: 'ConditionalExpression',
|
|
220
|
+
test: split_node.test,
|
|
221
|
+
consequent: short_branch,
|
|
222
|
+
alternate: continuation,
|
|
223
|
+
metadata: { path: [] },
|
|
224
|
+
}),
|
|
225
|
+
split_node,
|
|
226
|
+
),
|
|
227
|
+
split_node,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
statements.push(create_component_return_statement(render_nodes, split_node));
|
|
232
|
+
return statements;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @param {any[]} body_nodes
|
|
237
|
+
* @param {boolean} return_null_when_empty
|
|
238
|
+
* @param {TransformContext} transform_context
|
|
239
|
+
* @returns {any[]}
|
|
240
|
+
*/
|
|
241
|
+
function build_render_statements(body_nodes, return_null_when_empty, transform_context) {
|
|
242
|
+
const statements = [];
|
|
243
|
+
const render_nodes = [];
|
|
244
|
+
|
|
245
|
+
for (const child of body_nodes) {
|
|
246
|
+
if (is_bare_return_statement(child)) {
|
|
247
|
+
statements.push(create_component_return_statement(render_nodes, child));
|
|
248
|
+
return statements;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (is_lone_return_if_statement(child)) {
|
|
252
|
+
statements.push(create_component_lone_return_if_statement(child, render_nodes));
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (is_jsx_child(child)) {
|
|
257
|
+
render_nodes.push(to_jsx_child(child, transform_context));
|
|
258
|
+
} else {
|
|
259
|
+
statements.push(child);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const return_arg = build_return_expression(render_nodes);
|
|
264
|
+
if (return_arg || return_null_when_empty) {
|
|
265
|
+
statements.push({
|
|
266
|
+
type: 'ReturnStatement',
|
|
267
|
+
argument: return_arg || { type: 'Literal', value: null, raw: 'null' },
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return statements;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @param {any[]} body_nodes
|
|
276
|
+
* @returns {number}
|
|
277
|
+
*/
|
|
278
|
+
function find_hook_safe_split_index(body_nodes) {
|
|
279
|
+
for (let i = 0; i < body_nodes.length; i += 1) {
|
|
280
|
+
if (!is_lone_return_if_statement(body_nodes[i])) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (body_contains_top_level_hook_call(body_nodes.slice(i + 1))) {
|
|
285
|
+
return i;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return -1;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @param {any[]} body_nodes
|
|
294
|
+
* @returns {boolean}
|
|
295
|
+
*/
|
|
296
|
+
function body_contains_top_level_hook_call(body_nodes) {
|
|
297
|
+
return body_nodes.some(statement_contains_top_level_hook_call);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @param {any} node
|
|
302
|
+
* @returns {boolean}
|
|
303
|
+
*/
|
|
304
|
+
function statement_contains_top_level_hook_call(node) {
|
|
305
|
+
return node_contains_top_level_hook_call(node, false);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* @param {any} node
|
|
310
|
+
* @param {boolean} inside_nested_function
|
|
311
|
+
* @returns {boolean}
|
|
312
|
+
*/
|
|
313
|
+
function node_contains_top_level_hook_call(node, inside_nested_function) {
|
|
314
|
+
if (!node || typeof node !== 'object') {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (
|
|
319
|
+
inside_nested_function &&
|
|
320
|
+
(node.type === 'FunctionDeclaration' ||
|
|
321
|
+
node.type === 'FunctionExpression' ||
|
|
322
|
+
node.type === 'ArrowFunctionExpression')
|
|
323
|
+
) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
node.type === 'FunctionDeclaration' ||
|
|
329
|
+
node.type === 'FunctionExpression' ||
|
|
330
|
+
node.type === 'ArrowFunctionExpression'
|
|
331
|
+
) {
|
|
332
|
+
const next_inside_nested_function = true;
|
|
333
|
+
for (const key of Object.keys(node)) {
|
|
334
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (node_contains_top_level_hook_call(node[key], next_inside_nested_function)) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (!inside_nested_function && node.type === 'CallExpression' && is_hook_callee(node.callee)) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (Array.isArray(node)) {
|
|
349
|
+
return node.some((child) => node_contains_top_level_hook_call(child, inside_nested_function));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (const key of Object.keys(node)) {
|
|
353
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (node_contains_top_level_hook_call(node[key], inside_nested_function)) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @param {any} callee
|
|
366
|
+
* @returns {boolean}
|
|
367
|
+
*/
|
|
368
|
+
function is_hook_callee(callee) {
|
|
369
|
+
if (!callee) return false;
|
|
370
|
+
|
|
371
|
+
if (callee.type === 'Identifier') {
|
|
372
|
+
return /^use[A-Z0-9]/.test(callee.name);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (
|
|
376
|
+
!callee.computed &&
|
|
377
|
+
callee.type === 'MemberExpression' &&
|
|
378
|
+
callee.property?.type === 'Identifier'
|
|
379
|
+
) {
|
|
380
|
+
return /^use[A-Z0-9]/.test(callee.property.name);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* @param {any[]} body_nodes
|
|
388
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
389
|
+
* @param {Map<string, AST.Identifier>} available_bindings
|
|
390
|
+
* @param {any} source_node
|
|
391
|
+
* @param {string} suffix
|
|
392
|
+
* @param {TransformContext} transform_context
|
|
393
|
+
* @returns {any}
|
|
394
|
+
*/
|
|
395
|
+
function create_helper_component_expression(
|
|
396
|
+
body_nodes,
|
|
397
|
+
helper_state,
|
|
398
|
+
available_bindings,
|
|
399
|
+
source_node,
|
|
400
|
+
suffix,
|
|
401
|
+
transform_context,
|
|
402
|
+
) {
|
|
403
|
+
if (body_nodes.length === 0) {
|
|
404
|
+
return create_null_literal();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const helper_name = create_helper_name(helper_state, suffix);
|
|
408
|
+
const helper_id = set_loc(create_generated_identifier(helper_name), source_node);
|
|
409
|
+
const helper_bindings = Array.from(available_bindings.values());
|
|
410
|
+
const helper_fn = create_helper_function_declaration(
|
|
411
|
+
helper_id,
|
|
412
|
+
body_nodes,
|
|
413
|
+
helper_state,
|
|
414
|
+
available_bindings,
|
|
415
|
+
helper_bindings,
|
|
416
|
+
source_node,
|
|
417
|
+
transform_context,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
helper_state.helpers.push(helper_fn);
|
|
421
|
+
|
|
422
|
+
return create_helper_component_element(helper_id, helper_bindings, source_node);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @param {AST.Identifier} helper_id
|
|
427
|
+
* @param {any[]} body_nodes
|
|
428
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
429
|
+
* @param {Map<string, AST.Identifier>} available_bindings
|
|
430
|
+
* @param {AST.Identifier[]} helper_bindings
|
|
431
|
+
* @param {any} source_node
|
|
432
|
+
* @param {TransformContext} transform_context
|
|
433
|
+
* @returns {AST.FunctionDeclaration}
|
|
434
|
+
*/
|
|
435
|
+
function create_helper_function_declaration(
|
|
436
|
+
helper_id,
|
|
437
|
+
body_nodes,
|
|
438
|
+
helper_state,
|
|
439
|
+
available_bindings,
|
|
440
|
+
helper_bindings,
|
|
441
|
+
source_node,
|
|
442
|
+
transform_context,
|
|
443
|
+
) {
|
|
444
|
+
const fn = /** @type {any} */ ({
|
|
445
|
+
type: 'FunctionDeclaration',
|
|
446
|
+
id: helper_id,
|
|
447
|
+
params: helper_bindings.length > 0 ? [create_helper_props_pattern(helper_bindings)] : [],
|
|
448
|
+
body: {
|
|
449
|
+
type: 'BlockStatement',
|
|
450
|
+
body: build_component_statements(
|
|
451
|
+
body_nodes,
|
|
452
|
+
helper_state,
|
|
453
|
+
new Map(available_bindings),
|
|
454
|
+
transform_context,
|
|
455
|
+
),
|
|
456
|
+
metadata: { path: [] },
|
|
457
|
+
},
|
|
458
|
+
async: false,
|
|
459
|
+
generator: false,
|
|
460
|
+
metadata: {
|
|
461
|
+
path: [],
|
|
462
|
+
is_component: true,
|
|
463
|
+
is_method: true,
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return set_loc(fn, source_node);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @param {AST.Identifier[]} bindings
|
|
472
|
+
* @returns {AST.ObjectPattern}
|
|
473
|
+
*/
|
|
474
|
+
function create_helper_props_pattern(bindings) {
|
|
475
|
+
return /** @type {any} */ ({
|
|
476
|
+
type: 'ObjectPattern',
|
|
477
|
+
properties: bindings.map((binding) => create_helper_props_property(binding)),
|
|
478
|
+
metadata: { path: [] },
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* @param {AST.Identifier} binding
|
|
484
|
+
* @returns {AST.Property}
|
|
485
|
+
*/
|
|
486
|
+
function create_helper_props_property(binding) {
|
|
487
|
+
const key = clone_identifier(binding);
|
|
488
|
+
const value = clone_identifier(binding);
|
|
489
|
+
|
|
490
|
+
return /** @type {any} */ ({
|
|
491
|
+
type: 'Property',
|
|
492
|
+
key,
|
|
493
|
+
value,
|
|
494
|
+
kind: 'init',
|
|
495
|
+
method: false,
|
|
496
|
+
shorthand: true,
|
|
497
|
+
computed: false,
|
|
498
|
+
metadata: { path: [] },
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @param {AST.Identifier} helper_id
|
|
504
|
+
* @param {AST.Identifier[]} bindings
|
|
505
|
+
* @param {any} source_node
|
|
506
|
+
* @returns {ESTreeJSX.JSXElement}
|
|
507
|
+
*/
|
|
508
|
+
function create_helper_component_element(helper_id, bindings, source_node) {
|
|
509
|
+
const attributes = bindings.map(
|
|
510
|
+
(binding) =>
|
|
511
|
+
/** @type {any} */ ({
|
|
512
|
+
type: 'JSXAttribute',
|
|
513
|
+
name: identifier_to_jsx_name(clone_identifier(binding)),
|
|
514
|
+
value: to_jsx_expression_container(clone_identifier(binding), binding),
|
|
515
|
+
metadata: { path: [] },
|
|
516
|
+
}),
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
return set_loc(
|
|
520
|
+
/** @type {any} */ ({
|
|
521
|
+
type: 'JSXElement',
|
|
522
|
+
openingElement: set_loc(
|
|
523
|
+
{
|
|
524
|
+
type: 'JSXOpeningElement',
|
|
525
|
+
name: identifier_to_jsx_name(clone_identifier(helper_id)),
|
|
526
|
+
attributes,
|
|
527
|
+
selfClosing: true,
|
|
528
|
+
metadata: { path: [] },
|
|
529
|
+
},
|
|
530
|
+
source_node,
|
|
531
|
+
),
|
|
532
|
+
closingElement: null,
|
|
533
|
+
children: [],
|
|
534
|
+
metadata: { path: [] },
|
|
535
|
+
}),
|
|
536
|
+
source_node,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
542
|
+
* @param {string} suffix
|
|
543
|
+
* @returns {string}
|
|
544
|
+
*/
|
|
545
|
+
function create_helper_name(helper_state, suffix) {
|
|
546
|
+
helper_state.next_id += 1;
|
|
547
|
+
return `${helper_state.base_name}__${suffix}${helper_state.next_id}`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* @param {string} base_name
|
|
552
|
+
* @returns {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }}
|
|
553
|
+
*/
|
|
554
|
+
function create_helper_state(base_name) {
|
|
555
|
+
return {
|
|
556
|
+
base_name,
|
|
557
|
+
next_id: 0,
|
|
558
|
+
helpers: [],
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* @param {any[]} params
|
|
564
|
+
* @returns {Map<string, AST.Identifier>}
|
|
565
|
+
*/
|
|
566
|
+
function collect_param_bindings(params) {
|
|
567
|
+
const bindings = new Map();
|
|
568
|
+
for (const param of params) {
|
|
569
|
+
collect_pattern_bindings(param, bindings);
|
|
570
|
+
}
|
|
571
|
+
return bindings;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* @param {any} statement
|
|
576
|
+
* @param {Map<string, AST.Identifier>} bindings
|
|
577
|
+
* @returns {void}
|
|
578
|
+
*/
|
|
579
|
+
function collect_statement_bindings(statement, bindings) {
|
|
580
|
+
if (!statement) return;
|
|
581
|
+
|
|
582
|
+
if (statement.type === 'VariableDeclaration') {
|
|
583
|
+
for (const declaration of statement.declarations || []) {
|
|
584
|
+
collect_pattern_bindings(declaration.id, bindings);
|
|
585
|
+
}
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (
|
|
590
|
+
(statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
|
|
591
|
+
statement.id
|
|
592
|
+
) {
|
|
593
|
+
bindings.set(statement.id.name, statement.id);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* @param {any} pattern
|
|
599
|
+
* @param {Map<string, AST.Identifier>} bindings
|
|
600
|
+
* @returns {void}
|
|
601
|
+
*/
|
|
602
|
+
function collect_pattern_bindings(pattern, bindings) {
|
|
603
|
+
if (!pattern || typeof pattern !== 'object') return;
|
|
604
|
+
|
|
605
|
+
if (pattern.type === 'Identifier') {
|
|
606
|
+
bindings.set(pattern.name, pattern);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (pattern.type === 'RestElement') {
|
|
611
|
+
collect_pattern_bindings(pattern.argument, bindings);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
616
|
+
collect_pattern_bindings(pattern.left, bindings);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (pattern.type === 'ArrayPattern') {
|
|
621
|
+
for (const element of pattern.elements || []) {
|
|
622
|
+
collect_pattern_bindings(element, bindings);
|
|
623
|
+
}
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (pattern.type === 'ObjectPattern') {
|
|
628
|
+
for (const property of pattern.properties || []) {
|
|
629
|
+
if (property.type === 'RestElement') {
|
|
630
|
+
collect_pattern_bindings(property.argument, bindings);
|
|
631
|
+
} else {
|
|
632
|
+
collect_pattern_bindings(property.value, bindings);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* @param {AST.Identifier} identifier
|
|
640
|
+
* @returns {AST.Identifier}
|
|
641
|
+
*/
|
|
642
|
+
function clone_identifier(identifier) {
|
|
643
|
+
return set_loc(
|
|
644
|
+
/** @type {any} */ ({
|
|
645
|
+
type: 'Identifier',
|
|
646
|
+
name: identifier.name,
|
|
647
|
+
metadata: { path: [] },
|
|
648
|
+
}),
|
|
649
|
+
identifier,
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* @returns {AST.Literal}
|
|
655
|
+
*/
|
|
656
|
+
function create_null_literal() {
|
|
657
|
+
return /** @type {any} */ ({
|
|
658
|
+
type: 'Literal',
|
|
659
|
+
value: null,
|
|
660
|
+
raw: 'null',
|
|
661
|
+
metadata: { path: [] },
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* @param {AST.Program} program
|
|
667
|
+
* @returns {AST.Program}
|
|
668
|
+
*/
|
|
669
|
+
function expand_component_helpers(program) {
|
|
670
|
+
program.body = program.body.flatMap((statement) => {
|
|
671
|
+
if (statement.type === 'FunctionDeclaration') {
|
|
672
|
+
const helpers = /** @type {any} */ (statement.metadata)?.generated_helpers;
|
|
673
|
+
if (helpers?.length) {
|
|
674
|
+
return [...helpers, statement];
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (
|
|
679
|
+
(statement.type === 'ExportNamedDeclaration' ||
|
|
680
|
+
statement.type === 'ExportDefaultDeclaration') &&
|
|
681
|
+
statement.declaration?.type === 'FunctionDeclaration'
|
|
682
|
+
) {
|
|
683
|
+
const helpers = /** @type {any} */ (statement.declaration.metadata)?.generated_helpers;
|
|
684
|
+
if (helpers?.length) {
|
|
685
|
+
return [...helpers, statement];
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return [statement];
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
return program;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* @param {any} node
|
|
697
|
+
* @returns {boolean}
|
|
698
|
+
*/
|
|
699
|
+
function is_bare_return_statement(node) {
|
|
700
|
+
return node?.type === 'ReturnStatement' && node.argument == null;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* @param {any} node
|
|
705
|
+
* @returns {boolean}
|
|
706
|
+
*/
|
|
707
|
+
function is_lone_return_if_statement(node) {
|
|
708
|
+
if (node?.type !== 'IfStatement' || node.alternate) {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const consequent_body =
|
|
713
|
+
node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
|
|
714
|
+
|
|
715
|
+
return consequent_body.length === 1 && is_bare_return_statement(consequent_body[0]);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* @param {any[]} render_nodes
|
|
720
|
+
* @param {any} source_node
|
|
721
|
+
* @returns {any}
|
|
722
|
+
*/
|
|
723
|
+
function create_component_return_statement(render_nodes, source_node) {
|
|
724
|
+
return /** @type {any} */ ({
|
|
725
|
+
type: 'ReturnStatement',
|
|
726
|
+
argument: build_return_expression(render_nodes.slice()) || {
|
|
727
|
+
type: 'Literal',
|
|
728
|
+
value: null,
|
|
729
|
+
raw: 'null',
|
|
730
|
+
metadata: { path: [] },
|
|
731
|
+
},
|
|
732
|
+
metadata: { path: [] },
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* @param {any} node
|
|
738
|
+
* @param {any[]} render_nodes
|
|
739
|
+
* @returns {any}
|
|
740
|
+
*/
|
|
741
|
+
function create_component_lone_return_if_statement(node, render_nodes) {
|
|
742
|
+
const consequent_body =
|
|
743
|
+
node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
|
|
744
|
+
|
|
745
|
+
return set_loc(
|
|
746
|
+
/** @type {any} */ ({
|
|
747
|
+
type: 'IfStatement',
|
|
748
|
+
test: node.test,
|
|
749
|
+
consequent: set_loc(
|
|
750
|
+
/** @type {any} */ ({
|
|
751
|
+
type: 'BlockStatement',
|
|
752
|
+
body: [create_component_return_statement(render_nodes, consequent_body[0])],
|
|
753
|
+
metadata: { path: [] },
|
|
754
|
+
}),
|
|
755
|
+
node.consequent,
|
|
756
|
+
),
|
|
757
|
+
alternate: null,
|
|
758
|
+
metadata: { path: [] },
|
|
759
|
+
}),
|
|
760
|
+
node,
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Mark every selector inside the stylesheet as "used" so `renderStylesheets`
|
|
766
|
+
* does not comment it out. We skip Ripple's selector-pruning pass because
|
|
767
|
+
* React component boundaries are dynamic — any selector authored inside the
|
|
768
|
+
* component's `<style>` block is considered intentional.
|
|
769
|
+
*
|
|
770
|
+
* @param {any} stylesheet
|
|
771
|
+
* @returns {any}
|
|
772
|
+
*/
|
|
773
|
+
function prepare_stylesheet_for_render(stylesheet) {
|
|
774
|
+
walk(stylesheet, null, {
|
|
775
|
+
_(node, { next }) {
|
|
776
|
+
if (node && node.metadata && typeof node.metadata === 'object') {
|
|
777
|
+
node.metadata.used = true;
|
|
778
|
+
if (node.type === 'RelativeSelector' && !node.metadata.is_global) {
|
|
779
|
+
node.metadata.scoped = true;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return next();
|
|
783
|
+
},
|
|
784
|
+
});
|
|
785
|
+
return stylesheet;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* @param {any} node
|
|
790
|
+
* @returns {boolean}
|
|
791
|
+
*/
|
|
792
|
+
function is_style_element(node) {
|
|
793
|
+
return (
|
|
794
|
+
node &&
|
|
795
|
+
node.type === 'Element' &&
|
|
796
|
+
node.id &&
|
|
797
|
+
node.id.type === 'Identifier' &&
|
|
798
|
+
node.id.name === 'style'
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Recursively walk Element nodes within a component body and add the hash
|
|
804
|
+
* class name so scope-qualified selectors (e.g. `.foo.hash`) match.
|
|
805
|
+
*
|
|
806
|
+
* @param {any} node
|
|
807
|
+
* @param {string} hash
|
|
808
|
+
* @returns {any}
|
|
809
|
+
*/
|
|
810
|
+
function annotate_with_hash(node, hash) {
|
|
811
|
+
if (!node || typeof node !== 'object') return node;
|
|
812
|
+
if (
|
|
813
|
+
node.type === 'Component' ||
|
|
814
|
+
node.type === 'FunctionDeclaration' ||
|
|
815
|
+
node.type === 'FunctionExpression' ||
|
|
816
|
+
node.type === 'ArrowFunctionExpression'
|
|
817
|
+
) {
|
|
818
|
+
return node;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (node.type === 'Element') {
|
|
822
|
+
if (!is_style_element(node)) {
|
|
823
|
+
add_hash_class(node, hash);
|
|
824
|
+
}
|
|
825
|
+
if (Array.isArray(node.children)) {
|
|
826
|
+
node.children = node.children
|
|
827
|
+
.filter((/** @type {any} */ child) => !is_style_element(child))
|
|
828
|
+
.map((/** @type {any} */ child) => annotate_with_hash(child, hash));
|
|
829
|
+
}
|
|
830
|
+
return node;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
for (const key of Object.keys(node)) {
|
|
834
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const value = node[key];
|
|
839
|
+
if (Array.isArray(value)) {
|
|
840
|
+
node[key] = value.map((/** @type {any} */ child) => annotate_with_hash(child, hash));
|
|
841
|
+
} else if (value && typeof value === 'object') {
|
|
842
|
+
node[key] = annotate_with_hash(value, hash);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return node;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* @param {any} component
|
|
851
|
+
* @param {string} hash
|
|
852
|
+
* @returns {void}
|
|
853
|
+
*/
|
|
854
|
+
function annotate_component_with_hash(component, hash) {
|
|
855
|
+
/** @type {any[]} */
|
|
856
|
+
const body = component.body;
|
|
857
|
+
component.body = body
|
|
858
|
+
.filter((/** @type {any} */ child) => !is_style_element(child))
|
|
859
|
+
.map((/** @type {any} */ child) => annotate_with_hash(child, hash));
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Ensure the element carries a `class` attribute containing the scoping hash.
|
|
864
|
+
* @param {any} element
|
|
865
|
+
* @param {string} hash
|
|
866
|
+
*/
|
|
867
|
+
function add_hash_class(element, hash) {
|
|
868
|
+
const attrs = element.attributes || (element.attributes = []);
|
|
869
|
+
const existing = attrs.find(
|
|
870
|
+
(/** @type {any} */ a) =>
|
|
871
|
+
a.type === 'Attribute' &&
|
|
872
|
+
a.name &&
|
|
873
|
+
a.name.type === 'Identifier' &&
|
|
874
|
+
(a.name.name === 'class' || a.name.name === 'className'),
|
|
875
|
+
);
|
|
876
|
+
|
|
877
|
+
if (!existing) {
|
|
878
|
+
attrs.push({
|
|
879
|
+
type: 'Attribute',
|
|
880
|
+
name: { type: 'Identifier', name: 'class' },
|
|
881
|
+
value: { type: 'Literal', value: hash, raw: JSON.stringify(hash) },
|
|
882
|
+
});
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const value = existing.value;
|
|
887
|
+
if (!value) {
|
|
888
|
+
existing.value = { type: 'Literal', value: hash, raw: JSON.stringify(hash) };
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (value.type === 'Literal' && typeof value.value === 'string') {
|
|
893
|
+
const merged = `${value.value} ${hash}`;
|
|
894
|
+
existing.value = { type: 'Literal', value: merged, raw: JSON.stringify(merged) };
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Dynamic expression. Concatenate at runtime via template literal.
|
|
899
|
+
const expression = value.type === 'JSXExpressionContainer' ? value.expression : value;
|
|
900
|
+
existing.value = {
|
|
901
|
+
type: 'TemplateLiteral',
|
|
902
|
+
expressions: [expression],
|
|
903
|
+
quasis: [
|
|
904
|
+
{
|
|
905
|
+
type: 'TemplateElement',
|
|
906
|
+
value: { raw: '', cooked: '' },
|
|
907
|
+
tail: false,
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
type: 'TemplateElement',
|
|
911
|
+
value: { raw: ` ${hash}`, cooked: ` ${hash}` },
|
|
912
|
+
tail: true,
|
|
913
|
+
},
|
|
914
|
+
],
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* @param {any} node
|
|
920
|
+
* @returns {boolean}
|
|
921
|
+
*/
|
|
922
|
+
function is_jsx_child(node) {
|
|
923
|
+
if (!node) return false;
|
|
924
|
+
const t = node.type;
|
|
925
|
+
return (
|
|
926
|
+
t === 'JSXElement' ||
|
|
927
|
+
t === 'JSXFragment' ||
|
|
928
|
+
t === 'JSXExpressionContainer' ||
|
|
929
|
+
t === 'JSXText' ||
|
|
930
|
+
t === 'Tsx' ||
|
|
931
|
+
t === 'TsxCompat' ||
|
|
932
|
+
t === 'IfStatement' ||
|
|
933
|
+
t === 'ForOfStatement' ||
|
|
934
|
+
t === 'SwitchStatement' ||
|
|
935
|
+
t === 'TryStatement'
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* @param {any} node
|
|
941
|
+
* @param {TransformContext} transform_context
|
|
942
|
+
* @returns {any}
|
|
943
|
+
*/
|
|
944
|
+
function to_jsx_element(node, transform_context) {
|
|
945
|
+
if (node.type === 'JSXElement') return node;
|
|
946
|
+
if (is_dynamic_element_id(node.id)) {
|
|
947
|
+
return dynamic_element_to_jsx_child(node, transform_context);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const name = identifier_to_jsx_name(node.id);
|
|
951
|
+
const attributes = (node.attributes || []).map(to_jsx_attribute);
|
|
952
|
+
const selfClosing = !!node.selfClosing;
|
|
953
|
+
const children = create_element_children(node.children || [], transform_context);
|
|
954
|
+
const has_unmappable_attribute = attributes.some(
|
|
955
|
+
(/** @type {any} */ attribute) => attribute?.metadata?.has_unmappable_value,
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
/** @type {ESTreeJSX.JSXOpeningElement} */
|
|
959
|
+
const openingElement = /** @type {ESTreeJSX.JSXOpeningElement} */ (
|
|
960
|
+
has_unmappable_attribute
|
|
961
|
+
? {
|
|
962
|
+
type: 'JSXOpeningElement',
|
|
963
|
+
name,
|
|
964
|
+
attributes,
|
|
965
|
+
selfClosing,
|
|
966
|
+
metadata: { path: [] },
|
|
967
|
+
}
|
|
968
|
+
: set_loc(
|
|
969
|
+
/** @type {any} */ ({
|
|
970
|
+
type: 'JSXOpeningElement',
|
|
971
|
+
name,
|
|
972
|
+
attributes,
|
|
973
|
+
selfClosing,
|
|
974
|
+
}),
|
|
975
|
+
node.openingElement || node,
|
|
976
|
+
)
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
/** @type {ESTreeJSX.JSXClosingElement | null} */
|
|
980
|
+
const closingElement = selfClosing
|
|
981
|
+
? null
|
|
982
|
+
: set_loc(
|
|
983
|
+
/** @type {any} */ ({
|
|
984
|
+
type: 'JSXClosingElement',
|
|
985
|
+
name: clone_jsx_name(name, node.closingElement || node),
|
|
986
|
+
}),
|
|
987
|
+
node.closingElement || node,
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
return set_loc(
|
|
991
|
+
/** @type {any} */ ({
|
|
992
|
+
type: 'JSXElement',
|
|
993
|
+
openingElement,
|
|
994
|
+
closingElement,
|
|
995
|
+
children,
|
|
996
|
+
}),
|
|
997
|
+
node,
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* @param {any[]} children
|
|
1003
|
+
* @param {TransformContext} transform_context
|
|
1004
|
+
* @returns {any[]}
|
|
1005
|
+
*/
|
|
1006
|
+
|
|
1007
|
+
function create_element_children(children, transform_context) {
|
|
1008
|
+
if (children.length === 0) {
|
|
1009
|
+
return [];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (children.every(is_inline_element_child) && !children_contain_return_semantics(children)) {
|
|
1013
|
+
return children.map((/** @type {any} */ child) => to_jsx_child(child, transform_context));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
return [statement_body_to_jsx_child(children, transform_context)];
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* @param {any[]} children
|
|
1021
|
+
* @returns {boolean}
|
|
1022
|
+
*/
|
|
1023
|
+
function children_contain_return_semantics(children) {
|
|
1024
|
+
return children.some(child_contains_return_semantics);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* @param {any} node
|
|
1029
|
+
* @returns {boolean}
|
|
1030
|
+
*/
|
|
1031
|
+
function child_contains_return_semantics(node) {
|
|
1032
|
+
if (!node || typeof node !== 'object') {
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (node.type === 'ReturnStatement' || is_lone_return_if_statement(node)) {
|
|
1037
|
+
return true;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (
|
|
1041
|
+
node.type === 'FunctionDeclaration' ||
|
|
1042
|
+
node.type === 'FunctionExpression' ||
|
|
1043
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
1044
|
+
node.type === 'Component'
|
|
1045
|
+
) {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (Array.isArray(node)) {
|
|
1050
|
+
return node.some(child_contains_return_semantics);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
for (const key of Object.keys(node)) {
|
|
1054
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (child_contains_return_semantics(node[key])) {
|
|
1058
|
+
return true;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* @param {any} node
|
|
1067
|
+
* @returns {boolean}
|
|
1068
|
+
*/
|
|
1069
|
+
function is_inline_element_child(node) {
|
|
1070
|
+
return node && is_jsx_child(node);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* @param {any[]} body_nodes
|
|
1075
|
+
* @param {TransformContext} transform_context
|
|
1076
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1077
|
+
*/
|
|
1078
|
+
function statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
1079
|
+
if (body_contains_top_level_hook_call(body_nodes)) {
|
|
1080
|
+
return hook_safe_statement_body_to_jsx_child(body_nodes, transform_context);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return to_jsx_expression_container(
|
|
1084
|
+
/** @type {any} */ ({
|
|
1085
|
+
type: 'CallExpression',
|
|
1086
|
+
callee: {
|
|
1087
|
+
type: 'ArrowFunctionExpression',
|
|
1088
|
+
params: [],
|
|
1089
|
+
body: /** @type {any} */ ({
|
|
1090
|
+
type: 'BlockStatement',
|
|
1091
|
+
body: build_render_statements(body_nodes, true, transform_context),
|
|
1092
|
+
metadata: { path: [] },
|
|
1093
|
+
}),
|
|
1094
|
+
async: false,
|
|
1095
|
+
generator: false,
|
|
1096
|
+
expression: false,
|
|
1097
|
+
metadata: { path: [] },
|
|
1098
|
+
},
|
|
1099
|
+
arguments: [],
|
|
1100
|
+
optional: false,
|
|
1101
|
+
metadata: { path: [] },
|
|
1102
|
+
}),
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* @param {any[]} body_nodes
|
|
1108
|
+
* @param {TransformContext} transform_context
|
|
1109
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1110
|
+
*/
|
|
1111
|
+
function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
1112
|
+
const source_node = get_body_source_node(body_nodes);
|
|
1113
|
+
const helper_id = set_loc(
|
|
1114
|
+
create_generated_identifier(create_local_statement_component_name(transform_context)),
|
|
1115
|
+
source_node,
|
|
1116
|
+
);
|
|
1117
|
+
const helper_fn = set_loc(
|
|
1118
|
+
/** @type {any} */ ({
|
|
1119
|
+
type: 'FunctionDeclaration',
|
|
1120
|
+
id: helper_id,
|
|
1121
|
+
params: [],
|
|
1122
|
+
body: {
|
|
1123
|
+
type: 'BlockStatement',
|
|
1124
|
+
body: build_render_statements(body_nodes, true, transform_context),
|
|
1125
|
+
metadata: { path: [] },
|
|
1126
|
+
},
|
|
1127
|
+
async: false,
|
|
1128
|
+
generator: false,
|
|
1129
|
+
metadata: {
|
|
1130
|
+
path: [],
|
|
1131
|
+
is_component: true,
|
|
1132
|
+
is_method: false,
|
|
1133
|
+
},
|
|
1134
|
+
}),
|
|
1135
|
+
source_node,
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
return to_jsx_expression_container(
|
|
1139
|
+
/** @type {any} */ ({
|
|
1140
|
+
type: 'CallExpression',
|
|
1141
|
+
callee: {
|
|
1142
|
+
type: 'ArrowFunctionExpression',
|
|
1143
|
+
params: [],
|
|
1144
|
+
body: /** @type {any} */ ({
|
|
1145
|
+
type: 'BlockStatement',
|
|
1146
|
+
body: [
|
|
1147
|
+
helper_fn,
|
|
1148
|
+
{
|
|
1149
|
+
type: 'ReturnStatement',
|
|
1150
|
+
argument: create_helper_component_element(helper_id, [], source_node),
|
|
1151
|
+
metadata: { path: [] },
|
|
1152
|
+
},
|
|
1153
|
+
],
|
|
1154
|
+
metadata: { path: [] },
|
|
1155
|
+
}),
|
|
1156
|
+
async: false,
|
|
1157
|
+
generator: false,
|
|
1158
|
+
expression: false,
|
|
1159
|
+
metadata: { path: [] },
|
|
1160
|
+
},
|
|
1161
|
+
arguments: [],
|
|
1162
|
+
optional: false,
|
|
1163
|
+
metadata: { path: [] },
|
|
1164
|
+
}),
|
|
1165
|
+
source_node,
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* @param {TransformContext} transform_context
|
|
1171
|
+
* @returns {string}
|
|
1172
|
+
*/
|
|
1173
|
+
function create_local_statement_component_name(transform_context) {
|
|
1174
|
+
transform_context.local_statement_component_index += 1;
|
|
1175
|
+
return `StatementBodyHook${transform_context.local_statement_component_index}`;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* Wraps a list of body nodes into a locally-declared component and returns
|
|
1180
|
+
* statements that declare the component then return `<ComponentName />`.
|
|
1181
|
+
* Used when a control flow branch contains hook calls that must be moved
|
|
1182
|
+
* into their own component boundary to satisfy the Rules of Hooks.
|
|
1183
|
+
*
|
|
1184
|
+
* @param {any[]} body_nodes
|
|
1185
|
+
* @param {any} key_expression - Optional key expression to add to the component element (for for-of loops)
|
|
1186
|
+
* @param {TransformContext} transform_context
|
|
1187
|
+
* @returns {any[]}
|
|
1188
|
+
*/
|
|
1189
|
+
function hook_safe_render_statements(body_nodes, key_expression, transform_context) {
|
|
1190
|
+
const source_node = get_body_source_node(body_nodes);
|
|
1191
|
+
const helper_id = set_loc(
|
|
1192
|
+
create_generated_identifier(create_local_statement_component_name(transform_context)),
|
|
1193
|
+
source_node,
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
const helper_fn = set_loc(
|
|
1197
|
+
/** @type {any} */ ({
|
|
1198
|
+
type: 'FunctionDeclaration',
|
|
1199
|
+
id: helper_id,
|
|
1200
|
+
params: [],
|
|
1201
|
+
body: {
|
|
1202
|
+
type: 'BlockStatement',
|
|
1203
|
+
body: build_render_statements(body_nodes, true, transform_context),
|
|
1204
|
+
metadata: { path: [] },
|
|
1205
|
+
},
|
|
1206
|
+
async: false,
|
|
1207
|
+
generator: false,
|
|
1208
|
+
metadata: {
|
|
1209
|
+
path: [],
|
|
1210
|
+
is_component: true,
|
|
1211
|
+
is_method: false,
|
|
1212
|
+
},
|
|
1213
|
+
}),
|
|
1214
|
+
source_node,
|
|
1215
|
+
);
|
|
1216
|
+
|
|
1217
|
+
const component_element = create_helper_component_element(helper_id, [], source_node);
|
|
1218
|
+
|
|
1219
|
+
if (key_expression) {
|
|
1220
|
+
component_element.openingElement.attributes.push(
|
|
1221
|
+
/** @type {any} */ ({
|
|
1222
|
+
type: 'JSXAttribute',
|
|
1223
|
+
name: { type: 'JSXIdentifier', name: 'key', metadata: { path: [] } },
|
|
1224
|
+
value: to_jsx_expression_container(key_expression, key_expression),
|
|
1225
|
+
metadata: { path: [] },
|
|
1226
|
+
}),
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
return [
|
|
1231
|
+
helper_fn,
|
|
1232
|
+
{
|
|
1233
|
+
type: 'ReturnStatement',
|
|
1234
|
+
argument: component_element,
|
|
1235
|
+
metadata: { path: [] },
|
|
1236
|
+
},
|
|
1237
|
+
];
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* @param {any[]} body_nodes
|
|
1242
|
+
* @returns {any}
|
|
1243
|
+
*/
|
|
1244
|
+
function get_body_source_node(body_nodes) {
|
|
1245
|
+
const first = body_nodes[0];
|
|
1246
|
+
const last = body_nodes[body_nodes.length - 1];
|
|
1247
|
+
|
|
1248
|
+
if (first?.loc && last?.loc) {
|
|
1249
|
+
return {
|
|
1250
|
+
start: first.start,
|
|
1251
|
+
end: last.end,
|
|
1252
|
+
loc: {
|
|
1253
|
+
start: first.loc.start,
|
|
1254
|
+
end: last.loc.end,
|
|
1255
|
+
},
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
return first;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* @param {any} node
|
|
1264
|
+
* @param {TransformContext} transform_context
|
|
1265
|
+
* @returns {any}
|
|
1266
|
+
*/
|
|
1267
|
+
function to_jsx_child(node, transform_context) {
|
|
1268
|
+
if (!node) return node;
|
|
1269
|
+
switch (node.type) {
|
|
1270
|
+
case 'Tsx':
|
|
1271
|
+
return tsx_node_to_jsx_expression(node);
|
|
1272
|
+
case 'TsxCompat':
|
|
1273
|
+
return tsx_compat_node_to_jsx_expression(node);
|
|
1274
|
+
case 'Element':
|
|
1275
|
+
return to_jsx_element(node, transform_context);
|
|
1276
|
+
case 'Text':
|
|
1277
|
+
case 'TSRXExpression':
|
|
1278
|
+
return to_jsx_expression_container(node.expression, node);
|
|
1279
|
+
case 'IfStatement':
|
|
1280
|
+
return if_statement_to_jsx_child(node, transform_context);
|
|
1281
|
+
case 'ForOfStatement':
|
|
1282
|
+
return for_of_statement_to_jsx_child(node, transform_context);
|
|
1283
|
+
case 'SwitchStatement':
|
|
1284
|
+
return switch_statement_to_jsx_child(node, transform_context);
|
|
1285
|
+
case 'TryStatement':
|
|
1286
|
+
return try_statement_to_jsx_child(node, transform_context);
|
|
1287
|
+
default:
|
|
1288
|
+
return node;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* @param {any} node
|
|
1294
|
+
* @param {TransformContext} transform_context
|
|
1295
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1296
|
+
*/
|
|
1297
|
+
function if_statement_to_jsx_child(node, transform_context) {
|
|
1298
|
+
return to_jsx_expression_container(
|
|
1299
|
+
/** @type {any} */ ({
|
|
1300
|
+
type: 'CallExpression',
|
|
1301
|
+
callee: {
|
|
1302
|
+
type: 'ArrowFunctionExpression',
|
|
1303
|
+
params: [],
|
|
1304
|
+
body: /** @type {any} */ ({
|
|
1305
|
+
type: 'BlockStatement',
|
|
1306
|
+
body: [
|
|
1307
|
+
create_render_if_statement(node, transform_context),
|
|
1308
|
+
create_null_return_statement(),
|
|
1309
|
+
],
|
|
1310
|
+
metadata: { path: [] },
|
|
1311
|
+
}),
|
|
1312
|
+
async: false,
|
|
1313
|
+
generator: false,
|
|
1314
|
+
expression: false,
|
|
1315
|
+
metadata: { path: [] },
|
|
1316
|
+
},
|
|
1317
|
+
arguments: [],
|
|
1318
|
+
optional: false,
|
|
1319
|
+
metadata: { path: [] },
|
|
1320
|
+
}),
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* Find the first `key` attribute expression in the top-level elements of a body.
|
|
1326
|
+
* Used to propagate keys from loop body elements to wrapper components.
|
|
1327
|
+
* Works on both pre-transform (Ripple Element) and post-transform (JSXElement) nodes.
|
|
1328
|
+
*
|
|
1329
|
+
* @param {any[]} body_nodes
|
|
1330
|
+
* @returns {any | undefined}
|
|
1331
|
+
*/
|
|
1332
|
+
function find_key_expression_in_body(body_nodes) {
|
|
1333
|
+
for (const node of body_nodes) {
|
|
1334
|
+
// Pre-transform: Ripple Element node
|
|
1335
|
+
if (node.type === 'Element') {
|
|
1336
|
+
for (const attr of node.attributes || []) {
|
|
1337
|
+
if (attr.type === 'Attribute') {
|
|
1338
|
+
const attr_name = typeof attr.name === 'string' ? attr.name : attr.name?.name;
|
|
1339
|
+
if (attr_name === 'key') {
|
|
1340
|
+
return attr.value?.expression ?? attr.value;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
// Post-transform: JSXElement node
|
|
1346
|
+
if (node.type === 'JSXElement') {
|
|
1347
|
+
for (const attr of node.openingElement?.attributes || []) {
|
|
1348
|
+
if (
|
|
1349
|
+
attr.type === 'JSXAttribute' &&
|
|
1350
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
1351
|
+
attr.name.name === 'key'
|
|
1352
|
+
) {
|
|
1353
|
+
// Value is a JSXExpressionContainer
|
|
1354
|
+
if (attr.value?.type === 'JSXExpressionContainer') {
|
|
1355
|
+
return attr.value.expression;
|
|
1356
|
+
}
|
|
1357
|
+
return attr.value;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
return undefined;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* @param {any} node
|
|
1367
|
+
* @param {TransformContext} transform_context
|
|
1368
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1369
|
+
*/
|
|
1370
|
+
function for_of_statement_to_jsx_child(node, transform_context) {
|
|
1371
|
+
if (node.key) {
|
|
1372
|
+
throw create_compile_error(
|
|
1373
|
+
node.key,
|
|
1374
|
+
'React TSRX does not support `key` in `for` control flow. Put the key on the rendered element instead, for example `<div key={i}>...</div>`.',
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const loop_params = get_for_of_iteration_params(node.left, node.index);
|
|
1379
|
+
const loop_body = node.body.type === 'BlockStatement' ? node.body.body : [node.body];
|
|
1380
|
+
const has_hooks = body_contains_top_level_hook_call(loop_body);
|
|
1381
|
+
const key_expression = has_hooks ? find_key_expression_in_body(loop_body) : undefined;
|
|
1382
|
+
|
|
1383
|
+
return to_jsx_expression_container(
|
|
1384
|
+
/** @type {any} */ ({
|
|
1385
|
+
type: 'CallExpression',
|
|
1386
|
+
callee: {
|
|
1387
|
+
type: 'MemberExpression',
|
|
1388
|
+
object: node.right,
|
|
1389
|
+
property: create_generated_identifier('map'),
|
|
1390
|
+
computed: false,
|
|
1391
|
+
optional: false,
|
|
1392
|
+
metadata: { path: [] },
|
|
1393
|
+
},
|
|
1394
|
+
arguments: [
|
|
1395
|
+
{
|
|
1396
|
+
type: 'ArrowFunctionExpression',
|
|
1397
|
+
params: loop_params,
|
|
1398
|
+
body: /** @type {any} */ ({
|
|
1399
|
+
type: 'BlockStatement',
|
|
1400
|
+
body: has_hooks
|
|
1401
|
+
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
1402
|
+
: build_render_statements(loop_body, true, transform_context),
|
|
1403
|
+
metadata: { path: [] },
|
|
1404
|
+
}),
|
|
1405
|
+
async: false,
|
|
1406
|
+
generator: false,
|
|
1407
|
+
expression: false,
|
|
1408
|
+
metadata: { path: [] },
|
|
1409
|
+
},
|
|
1410
|
+
],
|
|
1411
|
+
async: false,
|
|
1412
|
+
optional: false,
|
|
1413
|
+
metadata: { path: [] },
|
|
1414
|
+
}),
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* @param {any} node
|
|
1420
|
+
* @param {TransformContext} transform_context
|
|
1421
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1422
|
+
*/
|
|
1423
|
+
function switch_statement_to_jsx_child(node, transform_context) {
|
|
1424
|
+
return to_jsx_expression_container(
|
|
1425
|
+
/** @type {any} */ ({
|
|
1426
|
+
type: 'CallExpression',
|
|
1427
|
+
callee: {
|
|
1428
|
+
type: 'ArrowFunctionExpression',
|
|
1429
|
+
params: [],
|
|
1430
|
+
body: /** @type {any} */ ({
|
|
1431
|
+
type: 'BlockStatement',
|
|
1432
|
+
body: [
|
|
1433
|
+
create_render_switch_statement(node, transform_context),
|
|
1434
|
+
create_null_return_statement(),
|
|
1435
|
+
],
|
|
1436
|
+
metadata: { path: [] },
|
|
1437
|
+
}),
|
|
1438
|
+
async: false,
|
|
1439
|
+
generator: false,
|
|
1440
|
+
expression: false,
|
|
1441
|
+
metadata: { path: [] },
|
|
1442
|
+
},
|
|
1443
|
+
arguments: [],
|
|
1444
|
+
optional: false,
|
|
1445
|
+
metadata: { path: [] },
|
|
1446
|
+
}),
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/**
|
|
1451
|
+
* Transform a `try { ... } pending { ... } catch (err, reset) { ... }` block
|
|
1452
|
+
* into React `<TsrxErrorBoundary>` and/or `<Suspense>` JSX elements.
|
|
1453
|
+
*
|
|
1454
|
+
* - `pending` → `<Suspense fallback={...}>`
|
|
1455
|
+
* - `catch` → `<TsrxErrorBoundary fallback={(err, reset) => ...}>`
|
|
1456
|
+
* - both → ErrorBoundary wraps Suspense
|
|
1457
|
+
* - `finally` blocks are not supported in component template context
|
|
1458
|
+
*
|
|
1459
|
+
* @param {any} node
|
|
1460
|
+
* @param {TransformContext} transform_context
|
|
1461
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1462
|
+
*/
|
|
1463
|
+
function try_statement_to_jsx_child(node, transform_context) {
|
|
1464
|
+
const pending = node.pending;
|
|
1465
|
+
const handler = node.handler;
|
|
1466
|
+
const finalizer = node.finalizer;
|
|
1467
|
+
|
|
1468
|
+
if (finalizer) {
|
|
1469
|
+
throw create_compile_error(
|
|
1470
|
+
finalizer,
|
|
1471
|
+
'React TSRX does not support `finally` blocks in component templates. Move the try statement into a function if you need a finally block.',
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
if (!pending && !handler) {
|
|
1476
|
+
throw create_compile_error(
|
|
1477
|
+
node,
|
|
1478
|
+
'Component try statements must have a `pending` or `catch` block.',
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// Validate that try body contains JSX if pending block is present
|
|
1483
|
+
if (pending) {
|
|
1484
|
+
const try_body = node.block.body || [];
|
|
1485
|
+
if (!try_body.some(is_jsx_child)) {
|
|
1486
|
+
throw create_compile_error(
|
|
1487
|
+
node.block,
|
|
1488
|
+
'Component try statements must contain a template in their main body. Move the try statement into a function if it does not render anything.',
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
const pending_body = pending.body || [];
|
|
1492
|
+
if (!pending_body.some(is_jsx_child)) {
|
|
1493
|
+
throw create_compile_error(
|
|
1494
|
+
pending,
|
|
1495
|
+
'Component try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// Build the try body content as JSX children
|
|
1501
|
+
const try_body_nodes = node.block.body || [];
|
|
1502
|
+
const try_content = statement_body_to_jsx_child(try_body_nodes, transform_context);
|
|
1503
|
+
|
|
1504
|
+
/** @type {any} */
|
|
1505
|
+
let result = try_content;
|
|
1506
|
+
|
|
1507
|
+
// Wrap in <Suspense> if pending block exists
|
|
1508
|
+
if (pending) {
|
|
1509
|
+
transform_context.needs_suspense = true;
|
|
1510
|
+
const pending_body_nodes = pending.body || [];
|
|
1511
|
+
const fallback_content = statement_body_to_jsx_child(pending_body_nodes, transform_context);
|
|
1512
|
+
|
|
1513
|
+
result = create_jsx_element(
|
|
1514
|
+
'Suspense',
|
|
1515
|
+
[
|
|
1516
|
+
{
|
|
1517
|
+
type: 'JSXAttribute',
|
|
1518
|
+
name: { type: 'JSXIdentifier', name: 'fallback', metadata: { path: [] } },
|
|
1519
|
+
value: fallback_content,
|
|
1520
|
+
metadata: { path: [] },
|
|
1521
|
+
},
|
|
1522
|
+
],
|
|
1523
|
+
[result],
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// Wrap in <TsrxErrorBoundary> if catch block exists
|
|
1528
|
+
if (handler) {
|
|
1529
|
+
transform_context.needs_error_boundary = true;
|
|
1530
|
+
|
|
1531
|
+
const catch_params = [];
|
|
1532
|
+
if (handler.param) {
|
|
1533
|
+
catch_params.push(handler.param);
|
|
1534
|
+
} else {
|
|
1535
|
+
catch_params.push(create_generated_identifier('_error'));
|
|
1536
|
+
}
|
|
1537
|
+
if (handler.resetParam) {
|
|
1538
|
+
catch_params.push(handler.resetParam);
|
|
1539
|
+
} else {
|
|
1540
|
+
catch_params.push(create_generated_identifier('_reset'));
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
const catch_body_nodes = handler.body.body || [];
|
|
1544
|
+
const fallback_fn = {
|
|
1545
|
+
type: 'ArrowFunctionExpression',
|
|
1546
|
+
params: catch_params,
|
|
1547
|
+
body: /** @type {any} */ ({
|
|
1548
|
+
type: 'BlockStatement',
|
|
1549
|
+
body: build_render_statements(catch_body_nodes, true, transform_context),
|
|
1550
|
+
metadata: { path: [] },
|
|
1551
|
+
}),
|
|
1552
|
+
async: false,
|
|
1553
|
+
generator: false,
|
|
1554
|
+
expression: false,
|
|
1555
|
+
metadata: { path: [] },
|
|
1556
|
+
};
|
|
1557
|
+
|
|
1558
|
+
result = create_jsx_element(
|
|
1559
|
+
'TsrxErrorBoundary',
|
|
1560
|
+
[
|
|
1561
|
+
{
|
|
1562
|
+
type: 'JSXAttribute',
|
|
1563
|
+
name: { type: 'JSXIdentifier', name: 'fallback', metadata: { path: [] } },
|
|
1564
|
+
value: to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
|
|
1565
|
+
metadata: { path: [] },
|
|
1566
|
+
},
|
|
1567
|
+
],
|
|
1568
|
+
[result],
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// result is a JSXElement, but we need to return a JSXExpressionContainer
|
|
1573
|
+
// for embedding in the parent component's render return
|
|
1574
|
+
if (result.type === 'JSXElement') {
|
|
1575
|
+
return to_jsx_expression_container(result);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
return result;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/**
|
|
1582
|
+
* Create a simple JSX element AST node.
|
|
1583
|
+
*
|
|
1584
|
+
* @param {string} tag_name
|
|
1585
|
+
* @param {any[]} attributes
|
|
1586
|
+
* @param {any[]} children
|
|
1587
|
+
* @returns {any}
|
|
1588
|
+
*/
|
|
1589
|
+
function create_jsx_element(tag_name, attributes, children) {
|
|
1590
|
+
const name = { type: 'JSXIdentifier', name: tag_name, metadata: { path: [] } };
|
|
1591
|
+
return {
|
|
1592
|
+
type: 'JSXElement',
|
|
1593
|
+
openingElement: {
|
|
1594
|
+
type: 'JSXOpeningElement',
|
|
1595
|
+
name,
|
|
1596
|
+
attributes,
|
|
1597
|
+
selfClosing: children.length === 0,
|
|
1598
|
+
metadata: { path: [] },
|
|
1599
|
+
},
|
|
1600
|
+
closingElement:
|
|
1601
|
+
children.length > 0
|
|
1602
|
+
? {
|
|
1603
|
+
type: 'JSXClosingElement',
|
|
1604
|
+
name: { type: 'JSXIdentifier', name: tag_name, metadata: { path: [] } },
|
|
1605
|
+
metadata: { path: [] },
|
|
1606
|
+
}
|
|
1607
|
+
: null,
|
|
1608
|
+
children,
|
|
1609
|
+
metadata: { path: [] },
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
/**
|
|
1614
|
+
* Inject import declarations for `Suspense` and `TsrxErrorBoundary` if the
|
|
1615
|
+
* transform determined they are needed.
|
|
1616
|
+
*
|
|
1617
|
+
* @param {AST.Program} program
|
|
1618
|
+
* @param {TransformContext} transform_context
|
|
1619
|
+
*/
|
|
1620
|
+
function inject_try_imports(program, transform_context) {
|
|
1621
|
+
/** @type {any[]} */
|
|
1622
|
+
const imports = [];
|
|
1623
|
+
|
|
1624
|
+
if (transform_context.needs_suspense) {
|
|
1625
|
+
imports.push({
|
|
1626
|
+
type: 'ImportDeclaration',
|
|
1627
|
+
specifiers: [
|
|
1628
|
+
{
|
|
1629
|
+
type: 'ImportSpecifier',
|
|
1630
|
+
imported: { type: 'Identifier', name: 'Suspense', metadata: { path: [] } },
|
|
1631
|
+
local: { type: 'Identifier', name: 'Suspense', metadata: { path: [] } },
|
|
1632
|
+
metadata: { path: [] },
|
|
1633
|
+
},
|
|
1634
|
+
],
|
|
1635
|
+
source: { type: 'Literal', value: 'react', raw: "'react'" },
|
|
1636
|
+
metadata: { path: [] },
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
if (transform_context.needs_error_boundary) {
|
|
1641
|
+
imports.push({
|
|
1642
|
+
type: 'ImportDeclaration',
|
|
1643
|
+
specifiers: [
|
|
1644
|
+
{
|
|
1645
|
+
type: 'ImportSpecifier',
|
|
1646
|
+
imported: {
|
|
1647
|
+
type: 'Identifier',
|
|
1648
|
+
name: 'TsrxErrorBoundary',
|
|
1649
|
+
metadata: { path: [] },
|
|
1650
|
+
},
|
|
1651
|
+
local: {
|
|
1652
|
+
type: 'Identifier',
|
|
1653
|
+
name: 'TsrxErrorBoundary',
|
|
1654
|
+
metadata: { path: [] },
|
|
1655
|
+
},
|
|
1656
|
+
metadata: { path: [] },
|
|
1657
|
+
},
|
|
1658
|
+
],
|
|
1659
|
+
source: {
|
|
1660
|
+
type: 'Literal',
|
|
1661
|
+
value: '@tsrx/react/error-boundary',
|
|
1662
|
+
raw: "'@tsrx/react/error-boundary'",
|
|
1663
|
+
},
|
|
1664
|
+
metadata: { path: [] },
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
if (imports.length > 0) {
|
|
1669
|
+
program.body.unshift(...imports);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* @param {any} node
|
|
1675
|
+
* @param {TransformContext} transform_context
|
|
1676
|
+
* @returns {any}
|
|
1677
|
+
*/
|
|
1678
|
+
function create_render_if_statement(node, transform_context) {
|
|
1679
|
+
const consequent_body =
|
|
1680
|
+
node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
|
|
1681
|
+
const consequent_has_hooks = body_contains_top_level_hook_call(consequent_body);
|
|
1682
|
+
|
|
1683
|
+
let alternate = null;
|
|
1684
|
+
if (node.alternate) {
|
|
1685
|
+
if (node.alternate.type === 'IfStatement') {
|
|
1686
|
+
alternate = create_render_if_statement(node.alternate, transform_context);
|
|
1687
|
+
} else {
|
|
1688
|
+
const alternate_body = node.alternate.body || [node.alternate];
|
|
1689
|
+
const alternate_has_hooks = body_contains_top_level_hook_call(alternate_body);
|
|
1690
|
+
alternate = set_loc(
|
|
1691
|
+
/** @type {any} */ ({
|
|
1692
|
+
type: 'BlockStatement',
|
|
1693
|
+
body: alternate_has_hooks
|
|
1694
|
+
? hook_safe_render_statements(alternate_body, undefined, transform_context)
|
|
1695
|
+
: build_render_statements(alternate_body, true, transform_context),
|
|
1696
|
+
metadata: { path: [] },
|
|
1697
|
+
}),
|
|
1698
|
+
node.alternate,
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
return set_loc(
|
|
1704
|
+
{
|
|
1705
|
+
type: 'IfStatement',
|
|
1706
|
+
test: node.test,
|
|
1707
|
+
consequent: set_loc(
|
|
1708
|
+
/** @type {any} */ ({
|
|
1709
|
+
type: 'BlockStatement',
|
|
1710
|
+
body: consequent_has_hooks
|
|
1711
|
+
? hook_safe_render_statements(consequent_body, undefined, transform_context)
|
|
1712
|
+
: build_render_statements(consequent_body, true, transform_context),
|
|
1713
|
+
metadata: { path: [] },
|
|
1714
|
+
}),
|
|
1715
|
+
node.consequent,
|
|
1716
|
+
),
|
|
1717
|
+
alternate,
|
|
1718
|
+
},
|
|
1719
|
+
node,
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/**
|
|
1724
|
+
* @param {any} node
|
|
1725
|
+
* @param {TransformContext} transform_context
|
|
1726
|
+
* @returns {any}
|
|
1727
|
+
*/
|
|
1728
|
+
function create_render_switch_statement(node, transform_context) {
|
|
1729
|
+
return /** @type {any} */ ({
|
|
1730
|
+
type: 'SwitchStatement',
|
|
1731
|
+
discriminant: node.discriminant,
|
|
1732
|
+
cases: node.cases.map((/** @type {any} */ c) =>
|
|
1733
|
+
create_render_switch_case(c, transform_context),
|
|
1734
|
+
),
|
|
1735
|
+
metadata: { path: [] },
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* @param {any} switch_case
|
|
1741
|
+
* @param {TransformContext} transform_context
|
|
1742
|
+
* @returns {any}
|
|
1743
|
+
*/
|
|
1744
|
+
function create_render_switch_case(switch_case, transform_context) {
|
|
1745
|
+
const consequent = flatten_switch_consequent(switch_case.consequent || []);
|
|
1746
|
+
|
|
1747
|
+
// Strip trailing break statements for hook analysis
|
|
1748
|
+
const body_without_break = [];
|
|
1749
|
+
for (const child of consequent) {
|
|
1750
|
+
if (child.type === 'BreakStatement') break;
|
|
1751
|
+
body_without_break.push(child);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
if (body_contains_top_level_hook_call(body_without_break)) {
|
|
1755
|
+
return /** @type {any} */ ({
|
|
1756
|
+
type: 'SwitchCase',
|
|
1757
|
+
test: switch_case.test,
|
|
1758
|
+
consequent: hook_safe_render_statements(body_without_break, undefined, transform_context),
|
|
1759
|
+
metadata: { path: [] },
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
const case_body = [];
|
|
1764
|
+
const render_nodes = [];
|
|
1765
|
+
let has_terminal = false;
|
|
1766
|
+
|
|
1767
|
+
for (const child of consequent) {
|
|
1768
|
+
if (child.type === 'BreakStatement') {
|
|
1769
|
+
if (render_nodes.length > 0 && !has_terminal) {
|
|
1770
|
+
case_body.push(create_component_return_statement(render_nodes, switch_case));
|
|
1771
|
+
} else if (!has_terminal) {
|
|
1772
|
+
case_body.push(child);
|
|
1773
|
+
}
|
|
1774
|
+
has_terminal = true;
|
|
1775
|
+
break;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
if (is_bare_return_statement(child)) {
|
|
1779
|
+
case_body.push(create_component_return_statement(render_nodes, child));
|
|
1780
|
+
has_terminal = true;
|
|
1781
|
+
break;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
if (is_jsx_child(child)) {
|
|
1785
|
+
render_nodes.push(to_jsx_child(child, transform_context));
|
|
1786
|
+
} else {
|
|
1787
|
+
case_body.push(child);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
if (!has_terminal && render_nodes.length > 0) {
|
|
1792
|
+
case_body.push(create_component_return_statement(render_nodes, switch_case));
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
return /** @type {any} */ ({
|
|
1796
|
+
type: 'SwitchCase',
|
|
1797
|
+
test: switch_case.test,
|
|
1798
|
+
consequent: case_body,
|
|
1799
|
+
metadata: { path: [] },
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* @returns {any}
|
|
1805
|
+
*/
|
|
1806
|
+
function create_null_return_statement() {
|
|
1807
|
+
return {
|
|
1808
|
+
type: 'ReturnStatement',
|
|
1809
|
+
argument: { type: 'Literal', value: null, raw: 'null' },
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/**
|
|
1814
|
+
* @param {AST.Expression} expression
|
|
1815
|
+
* @param {any} [source_node]
|
|
1816
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1817
|
+
*/
|
|
1818
|
+
function to_jsx_expression_container(expression, source_node = expression) {
|
|
1819
|
+
// NOTE: JSXExpressionContainer nodes are intentionally created without loc.
|
|
1820
|
+
// They are synthetic wrappers whose source positions do not correspond to
|
|
1821
|
+
// entries in the generated source map, so adding loc causes Volar mapping failures.
|
|
1822
|
+
return /** @type {any} */ ({
|
|
1823
|
+
type: 'JSXExpressionContainer',
|
|
1824
|
+
expression: /** @type {any} */ (expression),
|
|
1825
|
+
metadata: { path: [] },
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* @param {any} attr
|
|
1831
|
+
* @returns {ESTreeJSX.JSXAttribute | ESTreeJSX.JSXSpreadAttribute}
|
|
1832
|
+
*/
|
|
1833
|
+
function to_jsx_attribute(attr) {
|
|
1834
|
+
if (!attr) return attr;
|
|
1835
|
+
if (attr.type === 'JSXAttribute' || attr.type === 'JSXSpreadAttribute') {
|
|
1836
|
+
return attr;
|
|
1837
|
+
}
|
|
1838
|
+
if (attr.type === 'SpreadAttribute') {
|
|
1839
|
+
return set_loc(
|
|
1840
|
+
/** @type {any} */ ({
|
|
1841
|
+
type: 'JSXSpreadAttribute',
|
|
1842
|
+
argument: attr.argument,
|
|
1843
|
+
}),
|
|
1844
|
+
attr,
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
if (attr.type === 'RefAttribute') {
|
|
1848
|
+
// RefAttribute uses `{ref expr}` syntax whose source positions don't map to the
|
|
1849
|
+
// generated `ref={expr}` JSX attribute, so we intentionally omit loc.
|
|
1850
|
+
return /** @type {any} */ ({
|
|
1851
|
+
type: 'JSXAttribute',
|
|
1852
|
+
name: { type: 'JSXIdentifier', name: 'ref', metadata: { path: [] } },
|
|
1853
|
+
value: to_jsx_expression_container(attr.argument),
|
|
1854
|
+
shorthand: false,
|
|
1855
|
+
metadata: { path: [] },
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// Rewrite Ripple-style `class` → React's `className`.
|
|
1860
|
+
let attr_name = attr.name;
|
|
1861
|
+
if (attr_name && attr_name.type === 'Identifier' && attr_name.name === 'class') {
|
|
1862
|
+
attr_name = set_loc(
|
|
1863
|
+
/** @type {any} */ ({ type: 'Identifier', name: 'className', metadata: { path: [] } }),
|
|
1864
|
+
attr.name,
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
const name =
|
|
1869
|
+
attr_name && attr_name.type === 'Identifier' ? identifier_to_jsx_name(attr_name) : attr_name;
|
|
1870
|
+
|
|
1871
|
+
let value = attr.value;
|
|
1872
|
+
if (value) {
|
|
1873
|
+
if (value.type === 'Literal' && typeof value.value === 'string') {
|
|
1874
|
+
// Keep string literal as attribute string.
|
|
1875
|
+
} else if (value.type !== 'JSXExpressionContainer') {
|
|
1876
|
+
value = to_jsx_expression_container(value);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
const jsx_attribute = /** @type {any} */ ({
|
|
1881
|
+
type: 'JSXAttribute',
|
|
1882
|
+
name,
|
|
1883
|
+
value: value || null,
|
|
1884
|
+
shorthand: false,
|
|
1885
|
+
metadata: { path: [] },
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
if (value_has_unmappable_jsx_loc(value)) {
|
|
1889
|
+
/** @type {any} */ (jsx_attribute.metadata).has_unmappable_value = true;
|
|
1890
|
+
return jsx_attribute;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
return set_loc(jsx_attribute, attr);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
/**
|
|
1897
|
+
* @param {any} value
|
|
1898
|
+
* @returns {boolean}
|
|
1899
|
+
*/
|
|
1900
|
+
function value_has_unmappable_jsx_loc(value) {
|
|
1901
|
+
return !!(
|
|
1902
|
+
value?.type === 'JSXExpressionContainer' &&
|
|
1903
|
+
(value.expression?.type === 'JSXElement' || value.expression?.type === 'JSXFragment') &&
|
|
1904
|
+
!value.expression.loc
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
/**
|
|
1909
|
+
* @param {any} id
|
|
1910
|
+
* @returns {boolean}
|
|
1911
|
+
*/
|
|
1912
|
+
function is_dynamic_element_id(id) {
|
|
1913
|
+
if (!id || typeof id !== 'object') {
|
|
1914
|
+
return false;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
if (id.type === 'Identifier') {
|
|
1918
|
+
return !!id.tracked;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
if (id.type === 'MemberExpression') {
|
|
1922
|
+
return is_dynamic_element_id(id.object);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
return false;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
/**
|
|
1929
|
+
* @param {any} node
|
|
1930
|
+
* @param {TransformContext} transform_context
|
|
1931
|
+
* @returns {ESTreeJSX.JSXExpressionContainer}
|
|
1932
|
+
*/
|
|
1933
|
+
function dynamic_element_to_jsx_child(node, transform_context) {
|
|
1934
|
+
const dynamic_id = set_loc(create_generated_identifier('DynamicElement'), node.id);
|
|
1935
|
+
const alias_declaration = set_loc(
|
|
1936
|
+
/** @type {any} */ ({
|
|
1937
|
+
type: 'VariableDeclaration',
|
|
1938
|
+
kind: 'const',
|
|
1939
|
+
declarations: [
|
|
1940
|
+
{
|
|
1941
|
+
type: 'VariableDeclarator',
|
|
1942
|
+
id: dynamic_id,
|
|
1943
|
+
init: clone_expression_node(node.id),
|
|
1944
|
+
metadata: { path: [] },
|
|
1945
|
+
},
|
|
1946
|
+
],
|
|
1947
|
+
metadata: { path: [] },
|
|
1948
|
+
}),
|
|
1949
|
+
node,
|
|
1950
|
+
);
|
|
1951
|
+
const jsx_element = create_dynamic_jsx_element(dynamic_id, node, transform_context);
|
|
1952
|
+
|
|
1953
|
+
return to_jsx_expression_container(
|
|
1954
|
+
/** @type {any} */ ({
|
|
1955
|
+
type: 'CallExpression',
|
|
1956
|
+
callee: {
|
|
1957
|
+
type: 'ArrowFunctionExpression',
|
|
1958
|
+
params: [],
|
|
1959
|
+
body: /** @type {any} */ ({
|
|
1960
|
+
type: 'BlockStatement',
|
|
1961
|
+
body: [
|
|
1962
|
+
alias_declaration,
|
|
1963
|
+
{
|
|
1964
|
+
type: 'ReturnStatement',
|
|
1965
|
+
argument: {
|
|
1966
|
+
type: 'ConditionalExpression',
|
|
1967
|
+
test: clone_identifier(dynamic_id),
|
|
1968
|
+
consequent: jsx_element,
|
|
1969
|
+
alternate: create_null_literal(),
|
|
1970
|
+
metadata: { path: [] },
|
|
1971
|
+
},
|
|
1972
|
+
metadata: { path: [] },
|
|
1973
|
+
},
|
|
1974
|
+
],
|
|
1975
|
+
metadata: { path: [] },
|
|
1976
|
+
}),
|
|
1977
|
+
async: false,
|
|
1978
|
+
generator: false,
|
|
1979
|
+
expression: false,
|
|
1980
|
+
metadata: { path: [] },
|
|
1981
|
+
},
|
|
1982
|
+
arguments: [],
|
|
1983
|
+
optional: false,
|
|
1984
|
+
metadata: { path: [] },
|
|
1985
|
+
}),
|
|
1986
|
+
node,
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
/**
|
|
1991
|
+
* @param {AST.Identifier} dynamic_id
|
|
1992
|
+
* @param {any} node
|
|
1993
|
+
* @param {TransformContext} transform_context
|
|
1994
|
+
* @returns {ESTreeJSX.JSXElement}
|
|
1995
|
+
*/
|
|
1996
|
+
function create_dynamic_jsx_element(dynamic_id, node, transform_context) {
|
|
1997
|
+
const attributes = (node.attributes || []).map(to_jsx_attribute);
|
|
1998
|
+
const selfClosing = !!node.selfClosing;
|
|
1999
|
+
const children = create_element_children(node.children || [], transform_context);
|
|
2000
|
+
const name = identifier_to_jsx_name(clone_identifier(dynamic_id));
|
|
2001
|
+
|
|
2002
|
+
return /** @type {any} */ ({
|
|
2003
|
+
type: 'JSXElement',
|
|
2004
|
+
openingElement: {
|
|
2005
|
+
type: 'JSXOpeningElement',
|
|
2006
|
+
name,
|
|
2007
|
+
attributes,
|
|
2008
|
+
selfClosing,
|
|
2009
|
+
metadata: { path: [] },
|
|
2010
|
+
},
|
|
2011
|
+
closingElement: selfClosing
|
|
2012
|
+
? null
|
|
2013
|
+
: {
|
|
2014
|
+
type: 'JSXClosingElement',
|
|
2015
|
+
name: clone_jsx_name(name),
|
|
2016
|
+
metadata: { path: [] },
|
|
2017
|
+
},
|
|
2018
|
+
children,
|
|
2019
|
+
metadata: { path: [] },
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* @param {any} node
|
|
2025
|
+
* @returns {any}
|
|
2026
|
+
*/
|
|
2027
|
+
function clone_expression_node(node) {
|
|
2028
|
+
if (!node || typeof node !== 'object') {
|
|
2029
|
+
return node;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
if (Array.isArray(node)) {
|
|
2033
|
+
return node.map(clone_expression_node);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
const clone = { ...node };
|
|
2037
|
+
for (const key of Object.keys(clone)) {
|
|
2038
|
+
if (key === 'metadata') {
|
|
2039
|
+
clone.metadata = clone.metadata ? { ...clone.metadata } : { path: [] };
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
clone[key] = clone_expression_node(clone[key]);
|
|
2043
|
+
}
|
|
2044
|
+
return clone;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
/**
|
|
2048
|
+
* @param {AST.Identifier | AST.MemberExpression | any} id
|
|
2049
|
+
* @returns {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXMemberExpression}
|
|
2050
|
+
*/
|
|
2051
|
+
function identifier_to_jsx_name(id) {
|
|
2052
|
+
if (id.type === 'Identifier') {
|
|
2053
|
+
return set_loc(
|
|
2054
|
+
/** @type {any} */ ({
|
|
2055
|
+
type: 'JSXIdentifier',
|
|
2056
|
+
name: id.name,
|
|
2057
|
+
metadata: { path: [], is_component: /^[A-Z]/.test(id.name) },
|
|
2058
|
+
}),
|
|
2059
|
+
id,
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
if (id.type === 'MemberExpression') {
|
|
2063
|
+
return set_loc(
|
|
2064
|
+
/** @type {any} */ ({
|
|
2065
|
+
type: 'JSXMemberExpression',
|
|
2066
|
+
object: /** @type {any} */ (identifier_to_jsx_name(id.object)),
|
|
2067
|
+
property: /** @type {any} */ (identifier_to_jsx_name(id.property)),
|
|
2068
|
+
}),
|
|
2069
|
+
id,
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
return id;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
/**
|
|
2076
|
+
* @param {any} name
|
|
2077
|
+
* @param {any} [source_node]
|
|
2078
|
+
* @returns {any}
|
|
2079
|
+
*/
|
|
2080
|
+
function clone_jsx_name(name, source_node = name) {
|
|
2081
|
+
if (name.type === 'JSXIdentifier') {
|
|
2082
|
+
return set_loc(
|
|
2083
|
+
{
|
|
2084
|
+
type: 'JSXIdentifier',
|
|
2085
|
+
name: name.name,
|
|
2086
|
+
metadata: name.metadata || { path: [] },
|
|
2087
|
+
},
|
|
2088
|
+
source_node,
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
if (name.type === 'JSXMemberExpression') {
|
|
2092
|
+
return set_loc(
|
|
2093
|
+
{
|
|
2094
|
+
type: 'JSXMemberExpression',
|
|
2095
|
+
object: clone_jsx_name(name.object, source_node.object || name.object),
|
|
2096
|
+
property: clone_jsx_name(name.property, source_node.property || name.property),
|
|
2097
|
+
metadata: name.metadata || { path: [] },
|
|
2098
|
+
},
|
|
2099
|
+
source_node,
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
return name;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
/**
|
|
2106
|
+
* @param {any[]} render_nodes
|
|
2107
|
+
* @returns {any}
|
|
2108
|
+
*/
|
|
2109
|
+
function build_return_expression(render_nodes) {
|
|
2110
|
+
if (render_nodes.length === 0) return null;
|
|
2111
|
+
if (render_nodes.length === 1) {
|
|
2112
|
+
const only = render_nodes[0];
|
|
2113
|
+
if (only.type === 'JSXExpressionContainer') {
|
|
2114
|
+
return only.expression;
|
|
2115
|
+
}
|
|
2116
|
+
return only;
|
|
2117
|
+
}
|
|
2118
|
+
const first = render_nodes[0];
|
|
2119
|
+
const last = render_nodes[render_nodes.length - 1];
|
|
2120
|
+
return set_loc(
|
|
2121
|
+
{
|
|
2122
|
+
type: 'JSXFragment',
|
|
2123
|
+
openingFragment: /** @type {any} */ ({
|
|
2124
|
+
type: 'JSXOpeningFragment',
|
|
2125
|
+
metadata: { path: [] },
|
|
2126
|
+
}),
|
|
2127
|
+
closingFragment: /** @type {any} */ ({
|
|
2128
|
+
type: 'JSXClosingFragment',
|
|
2129
|
+
metadata: { path: [] },
|
|
2130
|
+
}),
|
|
2131
|
+
children: render_nodes,
|
|
2132
|
+
metadata: { path: [] },
|
|
2133
|
+
},
|
|
2134
|
+
first?.loc && last?.loc
|
|
2135
|
+
? {
|
|
2136
|
+
start: first.start,
|
|
2137
|
+
end: last.end,
|
|
2138
|
+
loc: {
|
|
2139
|
+
start: first.loc.start,
|
|
2140
|
+
end: last.loc.end,
|
|
2141
|
+
},
|
|
2142
|
+
}
|
|
2143
|
+
: undefined,
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
/**
|
|
2148
|
+
* @template T
|
|
2149
|
+
* @param {T} node
|
|
2150
|
+
* @param {any} source_node
|
|
2151
|
+
* @returns {T}
|
|
2152
|
+
*/
|
|
2153
|
+
function set_loc(node, source_node) {
|
|
2154
|
+
/** @type {any} */ (node).metadata ??= { path: [] };
|
|
2155
|
+
if (source_node?.loc) {
|
|
2156
|
+
return /** @type {T} */ (setLocation(/** @type {any} */ (node), source_node, true));
|
|
2157
|
+
}
|
|
2158
|
+
return node;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
/**
|
|
2162
|
+
* @param {any} left
|
|
2163
|
+
* @param {any} index
|
|
2164
|
+
* @returns {AST.Pattern[]}
|
|
2165
|
+
*/
|
|
2166
|
+
function get_for_of_iteration_params(left, index) {
|
|
2167
|
+
const params = [];
|
|
2168
|
+
if (left?.type === 'VariableDeclaration') {
|
|
2169
|
+
params.push(left.declarations[0]?.id);
|
|
2170
|
+
} else {
|
|
2171
|
+
params.push(left);
|
|
2172
|
+
}
|
|
2173
|
+
if (index) {
|
|
2174
|
+
params.push(index);
|
|
2175
|
+
}
|
|
2176
|
+
return params;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
/**
|
|
2180
|
+
* @param {string} name
|
|
2181
|
+
* @returns {AST.Identifier}
|
|
2182
|
+
*/
|
|
2183
|
+
function create_generated_identifier(name) {
|
|
2184
|
+
return /** @type {any} */ ({
|
|
2185
|
+
type: 'Identifier',
|
|
2186
|
+
name,
|
|
2187
|
+
metadata: { path: [] },
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
/**
|
|
2192
|
+
* @param {any} node
|
|
2193
|
+
* @param {string} message
|
|
2194
|
+
* @returns {Error & { pos: number, end: number }}
|
|
2195
|
+
*/
|
|
2196
|
+
function create_compile_error(node, message) {
|
|
2197
|
+
const error = /** @type {Error & { pos: number, end: number }} */ (new Error(message));
|
|
2198
|
+
error.pos = node.start ?? 0;
|
|
2199
|
+
error.end = node.end ?? error.pos + 1;
|
|
2200
|
+
return error;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
/**
|
|
2204
|
+
* @param {any} node
|
|
2205
|
+
* @returns {any}
|
|
2206
|
+
*/
|
|
2207
|
+
function tsx_compat_node_to_jsx_expression(node) {
|
|
2208
|
+
if (node.kind !== 'react') {
|
|
2209
|
+
throw create_compile_error(
|
|
2210
|
+
node,
|
|
2211
|
+
`React TSRX does not support <tsx:${node.kind}> blocks. Use <tsx> or <tsx:react>.`,
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
return tsx_node_to_jsx_expression(node);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
/**
|
|
2219
|
+
* @param {any} node
|
|
2220
|
+
* @returns {any}
|
|
2221
|
+
*/
|
|
2222
|
+
function tsx_node_to_jsx_expression(node) {
|
|
2223
|
+
const children = (node.children || []).filter(
|
|
2224
|
+
(/** @type {any} */ child) => child.type !== 'JSXText' || child.value.trim() !== '',
|
|
2225
|
+
);
|
|
2226
|
+
|
|
2227
|
+
if (children.length === 1 && children[0].type !== 'JSXText') {
|
|
2228
|
+
return strip_locations(children[0]);
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
return strip_locations(
|
|
2232
|
+
/** @type {any} */ ({
|
|
2233
|
+
type: 'JSXFragment',
|
|
2234
|
+
openingFragment: { type: 'JSXOpeningFragment', metadata: { path: [] } },
|
|
2235
|
+
closingFragment: { type: 'JSXClosingFragment', metadata: { path: [] } },
|
|
2236
|
+
children,
|
|
2237
|
+
metadata: { path: [] },
|
|
2238
|
+
}),
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
/**
|
|
2243
|
+
* @param {any} node
|
|
2244
|
+
* @returns {any}
|
|
2245
|
+
*/
|
|
2246
|
+
function strip_locations(node) {
|
|
2247
|
+
if (!node || typeof node !== 'object') {
|
|
2248
|
+
return node;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
if (Array.isArray(node)) {
|
|
2252
|
+
return node.map(strip_locations);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
delete node.loc;
|
|
2256
|
+
delete node.start;
|
|
2257
|
+
delete node.end;
|
|
2258
|
+
|
|
2259
|
+
for (const key of Object.keys(node)) {
|
|
2260
|
+
if (key === 'metadata') {
|
|
2261
|
+
continue;
|
|
2262
|
+
}
|
|
2263
|
+
node[key] = strip_locations(node[key]);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
return node;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
/**
|
|
2270
|
+
* @param {any[]} consequent
|
|
2271
|
+
* @returns {any[]}
|
|
2272
|
+
*/
|
|
2273
|
+
function flatten_switch_consequent(consequent) {
|
|
2274
|
+
const result = [];
|
|
2275
|
+
for (const node of consequent) {
|
|
2276
|
+
if (node.type === 'BlockStatement') {
|
|
2277
|
+
result.push(...node.body);
|
|
2278
|
+
} else {
|
|
2279
|
+
result.push(node);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
return result;
|
|
2283
|
+
}
|