@tsrx/core 0.0.22 → 0.0.24
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/README.md +1 -1
- package/package.json +1 -1
- package/src/analyze/validation.js +79 -4
- package/src/identifier-utils.js +1 -2
- package/src/index.js +11 -1
- package/src/plugin.js +189 -99
- package/src/scope.js +12 -4
- package/src/transform/jsx/helpers.js +6 -0
- package/src/transform/jsx/index.js +596 -43
- package/src/transform/segments.js +32 -5
- package/src/utils/builders.js +10 -0
- package/types/index.d.ts +24 -33
- package/types/jsx-platform.d.ts +6 -0
- package/types/parse.d.ts +2 -5
|
@@ -40,7 +40,12 @@ import {
|
|
|
40
40
|
} from '../lazy.js';
|
|
41
41
|
import { find_first_top_level_await_in_component_body } from '../await.js';
|
|
42
42
|
import { prepare_stylesheet_for_render, annotate_component_with_hash } from '../scoping.js';
|
|
43
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
validate_component_loop_break_statement,
|
|
45
|
+
validate_component_loop_return_statement,
|
|
46
|
+
validate_component_return_statement,
|
|
47
|
+
validate_component_unsupported_loop_statement,
|
|
48
|
+
} from '../../analyze/validation.js';
|
|
44
49
|
import { get_component_from_path } from '../../utils/ast.js';
|
|
45
50
|
import {
|
|
46
51
|
is_interleaved_body as is_interleaved_body_core,
|
|
@@ -61,6 +66,63 @@ import { is_hoist_safe_jsx_node } from '../jsx-hoist.js';
|
|
|
61
66
|
* @typedef {{ source_name: string, read: () => any }} LazyBinding
|
|
62
67
|
*/
|
|
63
68
|
|
|
69
|
+
/**
|
|
70
|
+
* @param {any} node
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
function is_function_or_class_boundary(node) {
|
|
74
|
+
return (
|
|
75
|
+
node?.type === 'FunctionDeclaration' ||
|
|
76
|
+
node?.type === 'FunctionExpression' ||
|
|
77
|
+
node?.type === 'ArrowFunctionExpression' ||
|
|
78
|
+
node?.type === 'ClassDeclaration' ||
|
|
79
|
+
node?.type === 'ClassExpression'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {any[]} path
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
function is_inside_component_for_of(path) {
|
|
88
|
+
for (let i = path.length - 1; i >= 0; i -= 1) {
|
|
89
|
+
const node = path[i];
|
|
90
|
+
if (is_function_or_class_boundary(node) || node?.type === 'Component') {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (node?.type === 'ForOfStatement') {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {any[]} path
|
|
102
|
+
* @returns {boolean}
|
|
103
|
+
*/
|
|
104
|
+
function break_targets_component_loop(path) {
|
|
105
|
+
for (let i = path.length - 1; i >= 0; i -= 1) {
|
|
106
|
+
const node = path[i];
|
|
107
|
+
if (is_function_or_class_boundary(node) || node?.type === 'Component') {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (node?.type === 'SwitchStatement') {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (
|
|
114
|
+
node?.type === 'ForOfStatement' ||
|
|
115
|
+
node?.type === 'ForStatement' ||
|
|
116
|
+
node?.type === 'ForInStatement' ||
|
|
117
|
+
node?.type === 'WhileStatement' ||
|
|
118
|
+
node?.type === 'DoWhileStatement'
|
|
119
|
+
) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
64
126
|
/**
|
|
65
127
|
* Build a `transform()` function for a specific JSX platform (React, Preact,
|
|
66
128
|
* Solid). Given a `JsxPlatform` descriptor, returns a transform that parses
|
|
@@ -104,6 +166,7 @@ export function createJsxTransform(platform) {
|
|
|
104
166
|
needs_error_boundary: false,
|
|
105
167
|
needs_suspense: false,
|
|
106
168
|
needs_merge_refs: false,
|
|
169
|
+
needs_fragment: false,
|
|
107
170
|
helper_state: null,
|
|
108
171
|
available_bindings: new Map(),
|
|
109
172
|
lazy_next_id: 0,
|
|
@@ -122,7 +185,81 @@ export function createJsxTransform(platform) {
|
|
|
122
185
|
walk(/** @type {any} */ (ast), transform_context, {
|
|
123
186
|
ReturnStatement(node, { next, path }) {
|
|
124
187
|
if (get_component_from_path(path)) {
|
|
125
|
-
|
|
188
|
+
if (is_inside_component_for_of(path)) {
|
|
189
|
+
validate_component_loop_return_statement(
|
|
190
|
+
node,
|
|
191
|
+
filename,
|
|
192
|
+
transform_context.errors,
|
|
193
|
+
transform_context.comments,
|
|
194
|
+
);
|
|
195
|
+
} else {
|
|
196
|
+
validate_component_return_statement(
|
|
197
|
+
node,
|
|
198
|
+
filename,
|
|
199
|
+
transform_context.errors,
|
|
200
|
+
transform_context.comments,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return next();
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
BreakStatement(node, { next, path }) {
|
|
209
|
+
if (get_component_from_path(path) && break_targets_component_loop(path)) {
|
|
210
|
+
validate_component_loop_break_statement(
|
|
211
|
+
node,
|
|
212
|
+
filename,
|
|
213
|
+
transform_context.errors,
|
|
214
|
+
transform_context.comments,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return next();
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
ForStatement(node, { next, path }) {
|
|
222
|
+
if (get_component_from_path(path)) {
|
|
223
|
+
validate_component_unsupported_loop_statement(
|
|
224
|
+
node,
|
|
225
|
+
filename,
|
|
226
|
+
transform_context.errors,
|
|
227
|
+
transform_context.comments,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return next();
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
ForInStatement(node, { next, path }) {
|
|
235
|
+
if (get_component_from_path(path)) {
|
|
236
|
+
validate_component_unsupported_loop_statement(
|
|
237
|
+
node,
|
|
238
|
+
filename,
|
|
239
|
+
transform_context.errors,
|
|
240
|
+
transform_context.comments,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return next();
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
WhileStatement(node, { next, path }) {
|
|
248
|
+
if (get_component_from_path(path)) {
|
|
249
|
+
validate_component_unsupported_loop_statement(
|
|
250
|
+
node,
|
|
251
|
+
filename,
|
|
252
|
+
transform_context.errors,
|
|
253
|
+
transform_context.comments,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return next();
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
DoWhileStatement(node, { next, path }) {
|
|
261
|
+
if (get_component_from_path(path)) {
|
|
262
|
+
validate_component_unsupported_loop_statement(
|
|
126
263
|
node,
|
|
127
264
|
filename,
|
|
128
265
|
transform_context.errors,
|
|
@@ -254,14 +391,13 @@ export function createJsxTransform(platform) {
|
|
|
254
391
|
return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
|
|
255
392
|
},
|
|
256
393
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
return next();
|
|
394
|
+
Style(node, { state, path }) {
|
|
395
|
+
validate_style_directive(node, state, path);
|
|
396
|
+
const class_name = typeof node.value.value === 'string' ? node.value.value : '';
|
|
397
|
+
const value = state.current_css_hash
|
|
398
|
+
? `${state.current_css_hash} ${class_name}`
|
|
399
|
+
: class_name;
|
|
400
|
+
return /** @type {any} */ (b.literal(value, node));
|
|
265
401
|
},
|
|
266
402
|
|
|
267
403
|
// Default .metadata on every function-like node so downstream consumers
|
|
@@ -441,6 +577,7 @@ function build_component_statements(body_nodes, transform_context) {
|
|
|
441
577
|
function build_render_statements(body_nodes, return_null_when_empty, transform_context) {
|
|
442
578
|
const statements = [];
|
|
443
579
|
const render_nodes = [];
|
|
580
|
+
let has_bare_return = false;
|
|
444
581
|
|
|
445
582
|
// Create a new bindings map so inner-scope bindings from
|
|
446
583
|
// collect_statement_bindings don't leak to the caller's scope.
|
|
@@ -461,6 +598,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
461
598
|
if (is_bare_return_statement(child)) {
|
|
462
599
|
statements.push(create_component_return_statement(render_nodes, child));
|
|
463
600
|
render_nodes.length = 0;
|
|
601
|
+
has_bare_return = true;
|
|
464
602
|
continue;
|
|
465
603
|
}
|
|
466
604
|
|
|
@@ -709,7 +847,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
709
847
|
}
|
|
710
848
|
|
|
711
849
|
const return_arg = build_return_expression(render_nodes);
|
|
712
|
-
if (return_arg || return_null_when_empty) {
|
|
850
|
+
if (return_arg || (return_null_when_empty && !has_bare_return)) {
|
|
713
851
|
statements.push({
|
|
714
852
|
type: 'ReturnStatement',
|
|
715
853
|
argument: return_arg || { type: 'Literal', value: null, raw: 'null' },
|
|
@@ -1340,6 +1478,126 @@ function append_tail_invocation(body, tail_helper) {
|
|
|
1340
1478
|
return [...body, clone_tail_invocation(tail_helper)];
|
|
1341
1479
|
}
|
|
1342
1480
|
|
|
1481
|
+
/**
|
|
1482
|
+
* @param {AST.Identifier} tail_synthetic_id
|
|
1483
|
+
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1484
|
+
* @returns {any}
|
|
1485
|
+
*/
|
|
1486
|
+
function create_loop_tail_expression(tail_synthetic_id, tail_helper) {
|
|
1487
|
+
return b.logical('&&', clone_identifier(tail_synthetic_id), clone_tail_invocation(tail_helper));
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* @param {AST.Identifier} tail_synthetic_id
|
|
1492
|
+
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1493
|
+
* @returns {any}
|
|
1494
|
+
*/
|
|
1495
|
+
function create_loop_tail_conditional(tail_synthetic_id, tail_helper) {
|
|
1496
|
+
return b.conditional(
|
|
1497
|
+
clone_identifier(tail_synthetic_id),
|
|
1498
|
+
clone_tail_invocation(tail_helper),
|
|
1499
|
+
create_null_literal(),
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* @param {any[]} statements
|
|
1505
|
+
* @param {AST.Identifier} tail_synthetic_id
|
|
1506
|
+
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1507
|
+
* @returns {void}
|
|
1508
|
+
*/
|
|
1509
|
+
function append_loop_tail_to_return_statements(statements, tail_synthetic_id, tail_helper) {
|
|
1510
|
+
for (const statement of statements) {
|
|
1511
|
+
append_loop_tail_to_return_statement(statement, tail_synthetic_id, tail_helper, false);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* @param {any} node
|
|
1517
|
+
* @param {AST.Identifier} tail_synthetic_id
|
|
1518
|
+
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1519
|
+
* @param {boolean} inside_nested_function
|
|
1520
|
+
* @returns {void}
|
|
1521
|
+
*/
|
|
1522
|
+
function append_loop_tail_to_return_statement(
|
|
1523
|
+
node,
|
|
1524
|
+
tail_synthetic_id,
|
|
1525
|
+
tail_helper,
|
|
1526
|
+
inside_nested_function,
|
|
1527
|
+
) {
|
|
1528
|
+
if (!node || typeof node !== 'object') {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
if (
|
|
1533
|
+
node.type === 'FunctionDeclaration' ||
|
|
1534
|
+
node.type === 'FunctionExpression' ||
|
|
1535
|
+
node.type === 'ArrowFunctionExpression'
|
|
1536
|
+
) {
|
|
1537
|
+
inside_nested_function = true;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
if (!inside_nested_function && node.type === 'ReturnStatement') {
|
|
1541
|
+
if (
|
|
1542
|
+
references_scope_bindings(
|
|
1543
|
+
node.argument,
|
|
1544
|
+
new Map([[tail_synthetic_id.name, tail_synthetic_id]]),
|
|
1545
|
+
)
|
|
1546
|
+
) {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
node.argument = append_loop_tail_to_return_argument(
|
|
1550
|
+
node.argument,
|
|
1551
|
+
tail_synthetic_id,
|
|
1552
|
+
tail_helper,
|
|
1553
|
+
);
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
if (Array.isArray(node)) {
|
|
1558
|
+
for (const child of node) {
|
|
1559
|
+
append_loop_tail_to_return_statement(
|
|
1560
|
+
child,
|
|
1561
|
+
tail_synthetic_id,
|
|
1562
|
+
tail_helper,
|
|
1563
|
+
inside_nested_function,
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
for (const key of Object.keys(node)) {
|
|
1570
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
append_loop_tail_to_return_statement(
|
|
1574
|
+
node[key],
|
|
1575
|
+
tail_synthetic_id,
|
|
1576
|
+
tail_helper,
|
|
1577
|
+
inside_nested_function,
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* @param {any} return_argument
|
|
1584
|
+
* @param {AST.Identifier} tail_synthetic_id
|
|
1585
|
+
* @param {{ component_element: ESTreeJSX.JSXElement }} tail_helper
|
|
1586
|
+
* @returns {any}
|
|
1587
|
+
*/
|
|
1588
|
+
function append_loop_tail_to_return_argument(return_argument, tail_synthetic_id, tail_helper) {
|
|
1589
|
+
if (return_argument == null || is_null_literal(return_argument)) {
|
|
1590
|
+
return create_loop_tail_conditional(tail_synthetic_id, tail_helper);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
return (
|
|
1594
|
+
build_return_expression([
|
|
1595
|
+
return_argument_to_render_node(return_argument),
|
|
1596
|
+
to_jsx_expression_container(create_loop_tail_expression(tail_synthetic_id, tail_helper)),
|
|
1597
|
+
]) || create_null_literal()
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1343
1601
|
/**
|
|
1344
1602
|
* Build a `return <combined-render-fragment>;` statement, prepending any
|
|
1345
1603
|
* `render_nodes` collected before the control-flow construct so they don't
|
|
@@ -1704,7 +1962,11 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1704
1962
|
}
|
|
1705
1963
|
|
|
1706
1964
|
const has_tail = continuation_body.length > 0;
|
|
1707
|
-
const original_loop_body =
|
|
1965
|
+
const original_loop_body = /** @type {any[]} */ (
|
|
1966
|
+
rewrite_loop_continues_to_bare_returns(
|
|
1967
|
+
node.body.type === 'BlockStatement' ? node.body.body : [node.body],
|
|
1968
|
+
)
|
|
1969
|
+
);
|
|
1708
1970
|
|
|
1709
1971
|
// When there's a tail, build TailHelper first so its component_element can
|
|
1710
1972
|
// be embedded inside the loop helper's body (gated on isLast). The
|
|
@@ -1721,18 +1983,13 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1721
1983
|
} else {
|
|
1722
1984
|
tail_synthetic_id = /** @type {any} */ (null);
|
|
1723
1985
|
}
|
|
1724
|
-
const
|
|
1725
|
-
?
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
clone_tail_invocation(/** @type {any} */ (tail_helper)),
|
|
1732
|
-
),
|
|
1733
|
-
),
|
|
1734
|
-
]
|
|
1735
|
-
: original_loop_body;
|
|
1986
|
+
const loop_tail_expression = has_tail
|
|
1987
|
+
? create_loop_tail_expression(tail_synthetic_id, /** @type {any} */ (tail_helper))
|
|
1988
|
+
: null;
|
|
1989
|
+
const loop_body =
|
|
1990
|
+
has_tail && loop_tail_expression
|
|
1991
|
+
? [...original_loop_body, b.jsx_expression_container(loop_tail_expression)]
|
|
1992
|
+
: original_loop_body;
|
|
1736
1993
|
|
|
1737
1994
|
const source_id = create_generated_identifier(
|
|
1738
1995
|
`_tsrx_iteration_items_${transform_context.local_statement_component_index + 1}`,
|
|
@@ -1768,10 +2025,8 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1768
2025
|
);
|
|
1769
2026
|
|
|
1770
2027
|
// Synthetic `isLast` prop on the loop helper when there's a tail. It's
|
|
1771
|
-
// passed from the .map callback as `i === source.length - 1` so
|
|
1772
|
-
// helper
|
|
1773
|
-
// gate on this prop's value here — the JSXLogicalExpression appended to
|
|
1774
|
-
// `loop_body` does the gating at render time.
|
|
2028
|
+
// passed from the .map callback as `i === source.length - 1` so every
|
|
2029
|
+
// loop-helper return can append the tail helper on the last iteration.
|
|
1775
2030
|
const tail_isLast_alias = has_tail
|
|
1776
2031
|
? {
|
|
1777
2032
|
id: create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
|
|
@@ -1809,6 +2064,13 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1809
2064
|
transform_context.available_bindings.set(tail_synthetic_id.name, tail_synthetic_id);
|
|
1810
2065
|
}
|
|
1811
2066
|
const fn_body_statements = build_render_statements(loop_body, true, transform_context);
|
|
2067
|
+
if (has_tail) {
|
|
2068
|
+
append_loop_tail_to_return_statements(
|
|
2069
|
+
fn_body_statements,
|
|
2070
|
+
tail_synthetic_id,
|
|
2071
|
+
/** @type {any} */ (tail_helper),
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
1812
2074
|
transform_context.available_bindings = fn_saved_bindings;
|
|
1813
2075
|
|
|
1814
2076
|
const helper_fn = /** @type {any} */ (
|
|
@@ -1852,7 +2114,7 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1852
2114
|
index_identifier = null;
|
|
1853
2115
|
}
|
|
1854
2116
|
|
|
1855
|
-
const body_key_expression = find_key_expression_in_body(
|
|
2117
|
+
const body_key_expression = find_key_expression_in_body(original_loop_body);
|
|
1856
2118
|
const explicit_key_expression =
|
|
1857
2119
|
body_key_expression ?? (node.key ? clone_expression_node(node.key) : undefined);
|
|
1858
2120
|
const key_expression =
|
|
@@ -2088,7 +2350,7 @@ function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nes
|
|
|
2088
2350
|
function combine_render_return_argument(render_nodes, return_argument) {
|
|
2089
2351
|
const combined = render_nodes.map((node) => clone_expression_node_without_locations(node));
|
|
2090
2352
|
|
|
2091
|
-
if (!is_null_literal(return_argument)) {
|
|
2353
|
+
if (return_argument != null && !is_null_literal(return_argument)) {
|
|
2092
2354
|
combined.push(return_argument_to_render_node(return_argument));
|
|
2093
2355
|
}
|
|
2094
2356
|
|
|
@@ -2771,6 +3033,108 @@ function get_body_source_node(body_nodes) {
|
|
|
2771
3033
|
return first;
|
|
2772
3034
|
}
|
|
2773
3035
|
|
|
3036
|
+
/**
|
|
3037
|
+
* @param {any} node
|
|
3038
|
+
* @param {TransformContext} transform_context
|
|
3039
|
+
* @param {any[]} path
|
|
3040
|
+
*/
|
|
3041
|
+
function validate_style_directive(node, transform_context, path) {
|
|
3042
|
+
const { attribute, element } = get_style_attribute_context(node, path);
|
|
3043
|
+
|
|
3044
|
+
if (!attribute) {
|
|
3045
|
+
error(
|
|
3046
|
+
'`{style "class_name"}` can only be used as an element attribute value.',
|
|
3047
|
+
transform_context.filename,
|
|
3048
|
+
node,
|
|
3049
|
+
transform_context.errors,
|
|
3050
|
+
transform_context.comments,
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
if (element && is_dom_style_target(element)) {
|
|
3055
|
+
error(
|
|
3056
|
+
'`{style "class_name"}` cannot be used directly on DOM elements. Pass the class to a child component instead.',
|
|
3057
|
+
transform_context.filename,
|
|
3058
|
+
node,
|
|
3059
|
+
transform_context.errors,
|
|
3060
|
+
transform_context.comments,
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
if (!transform_context.current_css_hash) {
|
|
3065
|
+
error(
|
|
3066
|
+
'`{style "class_name"}` requires a <style> block in the current component.',
|
|
3067
|
+
transform_context.filename,
|
|
3068
|
+
node,
|
|
3069
|
+
transform_context.errors,
|
|
3070
|
+
transform_context.comments,
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
/**
|
|
3076
|
+
* @param {any} node
|
|
3077
|
+
* @param {any[]} path
|
|
3078
|
+
* @returns {{ attribute: any, element: any }}
|
|
3079
|
+
*/
|
|
3080
|
+
function get_style_attribute_context(node, path) {
|
|
3081
|
+
const parent = path.at(-1);
|
|
3082
|
+
const attribute =
|
|
3083
|
+
parent?.type === 'Attribute' && parent.value === node
|
|
3084
|
+
? parent
|
|
3085
|
+
: path
|
|
3086
|
+
.findLast((ancestor) => ancestor?.type === 'Element')
|
|
3087
|
+
?.attributes?.find(
|
|
3088
|
+
(/** @type {any} */ attr) =>
|
|
3089
|
+
attr?.type === 'Attribute' &&
|
|
3090
|
+
(attr.value === node || node_contains(attr.value, node)),
|
|
3091
|
+
);
|
|
3092
|
+
const element = path.findLast(
|
|
3093
|
+
(ancestor) =>
|
|
3094
|
+
ancestor?.type === 'Element' &&
|
|
3095
|
+
(!attribute || ancestor.attributes?.some((/** @type {any} */ attr) => attr === attribute)),
|
|
3096
|
+
);
|
|
3097
|
+
|
|
3098
|
+
return { attribute: attribute ?? null, element: element ?? null };
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
/**
|
|
3102
|
+
* @param {any} root
|
|
3103
|
+
* @param {any} target
|
|
3104
|
+
* @returns {boolean}
|
|
3105
|
+
*/
|
|
3106
|
+
function node_contains(root, target) {
|
|
3107
|
+
if (!root || typeof root !== 'object') {
|
|
3108
|
+
return false;
|
|
3109
|
+
}
|
|
3110
|
+
if (root === target) {
|
|
3111
|
+
return true;
|
|
3112
|
+
}
|
|
3113
|
+
if (Array.isArray(root)) {
|
|
3114
|
+
return root.some((child) => node_contains(child, target));
|
|
3115
|
+
}
|
|
3116
|
+
for (const key of Object.keys(root)) {
|
|
3117
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3118
|
+
continue;
|
|
3119
|
+
}
|
|
3120
|
+
if (node_contains(root[key], target)) {
|
|
3121
|
+
return true;
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
/**
|
|
3128
|
+
* @param {any} element
|
|
3129
|
+
* @returns {boolean}
|
|
3130
|
+
*/
|
|
3131
|
+
function is_dom_style_target(element) {
|
|
3132
|
+
if (!element?.id || is_dynamic_element_id(element.id)) {
|
|
3133
|
+
return false;
|
|
3134
|
+
}
|
|
3135
|
+
return element.id.type === 'Identifier' && /^[a-z]/.test(element.id.name);
|
|
3136
|
+
}
|
|
3137
|
+
|
|
2774
3138
|
/**
|
|
2775
3139
|
* @param {any} node
|
|
2776
3140
|
* @param {TransformContext} transform_context
|
|
@@ -2948,6 +3312,71 @@ function find_key_expression_in_body(body_nodes) {
|
|
|
2948
3312
|
return undefined;
|
|
2949
3313
|
}
|
|
2950
3314
|
|
|
3315
|
+
/**
|
|
3316
|
+
* @param {any} source_node
|
|
3317
|
+
* @returns {any}
|
|
3318
|
+
*/
|
|
3319
|
+
function continue_to_bare_return(source_node) {
|
|
3320
|
+
return set_loc(
|
|
3321
|
+
/** @type {any} */ ({
|
|
3322
|
+
type: 'ReturnStatement',
|
|
3323
|
+
argument: null,
|
|
3324
|
+
metadata: { path: [] },
|
|
3325
|
+
}),
|
|
3326
|
+
source_node,
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
/**
|
|
3331
|
+
* `continue` in a component `for...of` body means "skip this item". JSX targets
|
|
3332
|
+
* lower `for...of` to callbacks, so a raw ContinueStatement would be invalid JS;
|
|
3333
|
+
* a bare `return` from the callback preserves the item-skip behavior.
|
|
3334
|
+
*
|
|
3335
|
+
* @param {any[] | any} node
|
|
3336
|
+
* @param {boolean} [is_root]
|
|
3337
|
+
* @returns {any[] | any}
|
|
3338
|
+
*/
|
|
3339
|
+
export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
|
|
3340
|
+
if (Array.isArray(node)) {
|
|
3341
|
+
return node.map((child) => rewrite_loop_continues_to_bare_returns(child, false));
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
if (!node || typeof node !== 'object') {
|
|
3345
|
+
return node;
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
if (node.type === 'ContinueStatement') {
|
|
3349
|
+
return continue_to_bare_return(node);
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
if (is_function_or_class_boundary(node) || (!is_root && is_loop_statement(node))) {
|
|
3353
|
+
return node;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
for (const key of Object.keys(node)) {
|
|
3357
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3358
|
+
continue;
|
|
3359
|
+
}
|
|
3360
|
+
node[key] = rewrite_loop_continues_to_bare_returns(node[key], false);
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
return node;
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
/**
|
|
3367
|
+
* @param {any} node
|
|
3368
|
+
* @returns {boolean}
|
|
3369
|
+
*/
|
|
3370
|
+
function is_loop_statement(node) {
|
|
3371
|
+
return (
|
|
3372
|
+
node?.type === 'ForOfStatement' ||
|
|
3373
|
+
node?.type === 'ForStatement' ||
|
|
3374
|
+
node?.type === 'ForInStatement' ||
|
|
3375
|
+
node?.type === 'WhileStatement' ||
|
|
3376
|
+
node?.type === 'DoWhileStatement'
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
2951
3380
|
/**
|
|
2952
3381
|
* @param {any} node
|
|
2953
3382
|
* @param {TransformContext} transform_context
|
|
@@ -2965,7 +3394,11 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
2965
3394
|
}
|
|
2966
3395
|
|
|
2967
3396
|
const loop_params = get_for_of_iteration_params(node.left, node.index);
|
|
2968
|
-
const loop_body =
|
|
3397
|
+
const loop_body = /** @type {any[]} */ (
|
|
3398
|
+
rewrite_loop_continues_to_bare_returns(
|
|
3399
|
+
node.body.type === 'BlockStatement' ? node.body.body : [node.body],
|
|
3400
|
+
)
|
|
3401
|
+
);
|
|
2969
3402
|
const has_hooks = body_contains_top_level_hook_call(loop_body, transform_context, true);
|
|
2970
3403
|
const body_key_expression = find_key_expression_in_body(loop_body);
|
|
2971
3404
|
const explicit_key_expression =
|
|
@@ -2990,14 +3423,14 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
2990
3423
|
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
2991
3424
|
}
|
|
2992
3425
|
|
|
3426
|
+
if (implicit_non_hook_key_expression && should_apply_key_to_loop_body(loop_body)) {
|
|
3427
|
+
apply_key_to_loop_body(loop_body, implicit_non_hook_key_expression);
|
|
3428
|
+
}
|
|
3429
|
+
|
|
2993
3430
|
const body_statements = has_hooks
|
|
2994
3431
|
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
2995
3432
|
: build_render_statements(loop_body, true, transform_context);
|
|
2996
3433
|
|
|
2997
|
-
if (implicit_non_hook_key_expression) {
|
|
2998
|
-
apply_key_to_render_statements(body_statements, implicit_non_hook_key_expression);
|
|
2999
|
-
}
|
|
3000
|
-
|
|
3001
3434
|
const platform_for_of = transform_context.platform.hooks?.renderForOf?.(
|
|
3002
3435
|
node,
|
|
3003
3436
|
loop_params,
|
|
@@ -3009,6 +3442,11 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
3009
3442
|
return platform_for_of;
|
|
3010
3443
|
}
|
|
3011
3444
|
|
|
3445
|
+
const non_hook_key_expression = key_expression ?? implicit_non_hook_key_expression;
|
|
3446
|
+
if (!has_hooks && non_hook_key_expression) {
|
|
3447
|
+
apply_key_to_render_statements(body_statements, non_hook_key_expression, transform_context);
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3012
3450
|
// Restore bindings
|
|
3013
3451
|
transform_context.available_bindings = saved_bindings;
|
|
3014
3452
|
|
|
@@ -3046,19 +3484,33 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
3046
3484
|
}
|
|
3047
3485
|
|
|
3048
3486
|
/**
|
|
3049
|
-
* @param {any[]}
|
|
3487
|
+
* @param {any[]} body_nodes
|
|
3050
3488
|
* @param {any} key_expression
|
|
3051
3489
|
* @returns {void}
|
|
3052
3490
|
*/
|
|
3053
|
-
function
|
|
3054
|
-
for (
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3491
|
+
function apply_key_to_loop_body(body_nodes, key_expression) {
|
|
3492
|
+
for (const node of body_nodes) {
|
|
3493
|
+
if (node.type === 'Element') {
|
|
3494
|
+
const attributes = node.attributes || (node.attributes = []);
|
|
3495
|
+
const has_key = attributes.some((/** @type {any} */ attr) => {
|
|
3496
|
+
const attr_name = typeof attr.name === 'string' ? attr.name : attr.name?.name;
|
|
3497
|
+
return attr_name === 'key';
|
|
3498
|
+
});
|
|
3499
|
+
|
|
3500
|
+
if (!has_key) {
|
|
3501
|
+
attributes.push({
|
|
3502
|
+
type: 'Attribute',
|
|
3503
|
+
name: { type: 'Identifier', name: 'key', metadata: { path: [] } },
|
|
3504
|
+
value: clone_expression_node(key_expression),
|
|
3505
|
+
shorthand: false,
|
|
3506
|
+
metadata: { path: [] },
|
|
3507
|
+
});
|
|
3508
|
+
}
|
|
3509
|
+
return;
|
|
3058
3510
|
}
|
|
3059
3511
|
|
|
3060
|
-
if (
|
|
3061
|
-
const attributes =
|
|
3512
|
+
if (node.type === 'JSXElement') {
|
|
3513
|
+
const attributes = node.openingElement?.attributes || [];
|
|
3062
3514
|
const has_key = attributes.some(
|
|
3063
3515
|
(/** @type {any} */ attr) =>
|
|
3064
3516
|
attr.type === 'JSXAttribute' &&
|
|
@@ -3079,12 +3531,92 @@ function apply_key_to_render_statements(statements, key_expression) {
|
|
|
3079
3531
|
}),
|
|
3080
3532
|
);
|
|
3081
3533
|
}
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
/**
|
|
3540
|
+
* @param {any[]} body_nodes
|
|
3541
|
+
* @returns {boolean}
|
|
3542
|
+
*/
|
|
3543
|
+
function should_apply_key_to_loop_body(body_nodes) {
|
|
3544
|
+
let keyable_children = 0;
|
|
3545
|
+
for (const node of body_nodes) {
|
|
3546
|
+
if (node.type === 'Element' || node.type === 'JSXElement') {
|
|
3547
|
+
keyable_children += 1;
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
return keyable_children === 1;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
/**
|
|
3554
|
+
* @param {any[]} statements
|
|
3555
|
+
* @param {any} key_expression
|
|
3556
|
+
* @param {TransformContext} transform_context
|
|
3557
|
+
* @returns {void}
|
|
3558
|
+
*/
|
|
3559
|
+
function apply_key_to_render_statements(statements, key_expression, transform_context) {
|
|
3560
|
+
for (let i = statements.length - 1; i >= 0; i -= 1) {
|
|
3561
|
+
const statement = statements[i];
|
|
3562
|
+
if (statement?.type !== 'ReturnStatement' || !statement.argument) {
|
|
3563
|
+
continue;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
if (statement.argument.type === 'JSXElement') {
|
|
3567
|
+
apply_key_to_jsx_element(statement.argument, key_expression);
|
|
3568
|
+
} else if (statement.argument.type === 'JSXFragment') {
|
|
3569
|
+
transform_context.needs_fragment = true;
|
|
3570
|
+
statement.argument = keyed_fragment_to_jsx_element(statement.argument, key_expression);
|
|
3082
3571
|
}
|
|
3083
3572
|
|
|
3084
3573
|
return;
|
|
3085
3574
|
}
|
|
3086
3575
|
}
|
|
3087
3576
|
|
|
3577
|
+
/**
|
|
3578
|
+
* @param {any} element
|
|
3579
|
+
* @param {any} key_expression
|
|
3580
|
+
* @returns {void}
|
|
3581
|
+
*/
|
|
3582
|
+
function apply_key_to_jsx_element(element, key_expression) {
|
|
3583
|
+
const attributes = element.openingElement?.attributes || [];
|
|
3584
|
+
const has_key = attributes.some(
|
|
3585
|
+
(/** @type {any} */ attr) =>
|
|
3586
|
+
attr.type === 'JSXAttribute' &&
|
|
3587
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
3588
|
+
attr.name.name === 'key',
|
|
3589
|
+
);
|
|
3590
|
+
|
|
3591
|
+
if (!has_key) {
|
|
3592
|
+
attributes.push(
|
|
3593
|
+
b.jsx_attribute(
|
|
3594
|
+
b.jsx_id('key'),
|
|
3595
|
+
to_jsx_expression_container(clone_expression_node(key_expression), key_expression),
|
|
3596
|
+
),
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
/**
|
|
3602
|
+
* @param {any} fragment
|
|
3603
|
+
* @param {any} key_expression
|
|
3604
|
+
* @returns {any}
|
|
3605
|
+
*/
|
|
3606
|
+
function keyed_fragment_to_jsx_element(fragment, key_expression) {
|
|
3607
|
+
const name = b.jsx_id('Fragment');
|
|
3608
|
+
const key_attribute = b.jsx_attribute(
|
|
3609
|
+
b.jsx_id('key'),
|
|
3610
|
+
to_jsx_expression_container(clone_expression_node(key_expression), key_expression),
|
|
3611
|
+
);
|
|
3612
|
+
|
|
3613
|
+
return b.jsx_element_fresh(
|
|
3614
|
+
b.jsx_opening_element(name, [key_attribute]),
|
|
3615
|
+
b.jsx_closing_element(clone_jsx_name(name)),
|
|
3616
|
+
fragment.children,
|
|
3617
|
+
);
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3088
3620
|
/**
|
|
3089
3621
|
* @param {any} node
|
|
3090
3622
|
* @param {TransformContext} transform_context
|
|
@@ -3372,6 +3904,27 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
|
|
|
3372
3904
|
/** @type {any[]} */
|
|
3373
3905
|
const imports = [];
|
|
3374
3906
|
|
|
3907
|
+
if (transform_context.needs_fragment && platform.imports.fragment) {
|
|
3908
|
+
const fragment_source = platform.imports.fragment;
|
|
3909
|
+
imports.push({
|
|
3910
|
+
type: 'ImportDeclaration',
|
|
3911
|
+
specifiers: [
|
|
3912
|
+
{
|
|
3913
|
+
type: 'ImportSpecifier',
|
|
3914
|
+
imported: { type: 'Identifier', name: 'Fragment', metadata: { path: [] } },
|
|
3915
|
+
local: { type: 'Identifier', name: 'Fragment', metadata: { path: [] } },
|
|
3916
|
+
metadata: { path: [] },
|
|
3917
|
+
},
|
|
3918
|
+
],
|
|
3919
|
+
source: {
|
|
3920
|
+
type: 'Literal',
|
|
3921
|
+
value: fragment_source,
|
|
3922
|
+
raw: `'${fragment_source}'`,
|
|
3923
|
+
},
|
|
3924
|
+
metadata: { path: [] },
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3375
3928
|
if (transform_context.needs_suspense) {
|
|
3376
3929
|
imports.push({
|
|
3377
3930
|
type: 'ImportDeclaration',
|