@tsrx/core 0.0.23 → 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/package.json +1 -1
- package/src/analyze/validation.js +79 -4
- package/src/index.js +11 -0
- package/src/plugin.js +47 -0
- package/src/transform/jsx/index.js +487 -35
- package/src/utils/builders.js +10 -0
- package/types/index.d.ts +1 -0
- package/types/jsx-platform.d.ts +6 -0
package/package.json
CHANGED
|
@@ -7,6 +7,18 @@ import { error } from '../errors.js';
|
|
|
7
7
|
|
|
8
8
|
export const COMPONENT_RETURN_VALUE_ERROR =
|
|
9
9
|
'Return statements inside components cannot have a return value.';
|
|
10
|
+
export const COMPONENT_LOOP_RETURN_ERROR =
|
|
11
|
+
'Return statements are not allowed inside component for...of loops. Use continue instead.';
|
|
12
|
+
export const COMPONENT_LOOP_BREAK_ERROR =
|
|
13
|
+
'Break statements are not allowed inside component for...of loops.';
|
|
14
|
+
export const COMPONENT_FOR_STATEMENT_ERROR =
|
|
15
|
+
'For loops are not supported in components. Use for...of instead.';
|
|
16
|
+
export const COMPONENT_FOR_IN_STATEMENT_ERROR =
|
|
17
|
+
'For...in loops are not supported in components. Use for...of instead.';
|
|
18
|
+
export const COMPONENT_WHILE_STATEMENT_ERROR =
|
|
19
|
+
'While loops are not supported in components. Move the while loop into a function.';
|
|
20
|
+
export const COMPONENT_DO_WHILE_STATEMENT_ERROR =
|
|
21
|
+
'Do...while loops are not supported in components. Move the do...while loop into a function.';
|
|
10
22
|
|
|
11
23
|
const invalid_nestings = {
|
|
12
24
|
// <p> cannot contain block-level elements
|
|
@@ -133,19 +145,29 @@ function get_element_tag(element) {
|
|
|
133
145
|
* @returns {AST.ReturnStatement}
|
|
134
146
|
*/
|
|
135
147
|
export function get_return_keyword_node(node) {
|
|
136
|
-
|
|
148
|
+
return get_statement_keyword_node(node, 'return');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @template {AST.Node} T
|
|
153
|
+
* @param {T} node
|
|
154
|
+
* @param {string} keyword
|
|
155
|
+
* @returns {T}
|
|
156
|
+
*/
|
|
157
|
+
export function get_statement_keyword_node(node, keyword) {
|
|
158
|
+
const keyword_length = keyword.length;
|
|
137
159
|
const start = /** @type {AST.NodeWithLocation} */ (node).start ?? 0;
|
|
138
160
|
const loc = /** @type {AST.NodeWithLocation} */ (node).loc;
|
|
139
161
|
|
|
140
|
-
return /** @type {
|
|
162
|
+
return /** @type {T} */ ({
|
|
141
163
|
...node,
|
|
142
|
-
end: start +
|
|
164
|
+
end: start + keyword_length,
|
|
143
165
|
loc: loc
|
|
144
166
|
? {
|
|
145
167
|
start: loc.start,
|
|
146
168
|
end: {
|
|
147
169
|
line: loc.start.line,
|
|
148
|
-
column: loc.start.column +
|
|
170
|
+
column: loc.start.column + keyword_length,
|
|
149
171
|
},
|
|
150
172
|
}
|
|
151
173
|
: undefined,
|
|
@@ -172,6 +194,59 @@ export function validate_component_return_statement(node, filename, errors, comm
|
|
|
172
194
|
);
|
|
173
195
|
}
|
|
174
196
|
|
|
197
|
+
/**
|
|
198
|
+
* @param {AST.ReturnStatement} node
|
|
199
|
+
* @param {string | null | undefined} filename
|
|
200
|
+
* @param {CompileError[]} [errors]
|
|
201
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
202
|
+
*/
|
|
203
|
+
export function validate_component_loop_return_statement(node, filename, errors, comments) {
|
|
204
|
+
error(
|
|
205
|
+
COMPONENT_LOOP_RETURN_ERROR,
|
|
206
|
+
filename ?? null,
|
|
207
|
+
get_return_keyword_node(node),
|
|
208
|
+
errors,
|
|
209
|
+
comments,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {AST.BreakStatement} node
|
|
215
|
+
* @param {string | null | undefined} filename
|
|
216
|
+
* @param {CompileError[]} [errors]
|
|
217
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
218
|
+
*/
|
|
219
|
+
export function validate_component_loop_break_statement(node, filename, errors, comments) {
|
|
220
|
+
error(
|
|
221
|
+
COMPONENT_LOOP_BREAK_ERROR,
|
|
222
|
+
filename ?? null,
|
|
223
|
+
get_statement_keyword_node(node, 'break'),
|
|
224
|
+
errors,
|
|
225
|
+
comments,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {AST.ForStatement | AST.ForInStatement | AST.WhileStatement | AST.DoWhileStatement} node
|
|
231
|
+
* @param {string | null | undefined} filename
|
|
232
|
+
* @param {CompileError[]} [errors]
|
|
233
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
234
|
+
*/
|
|
235
|
+
export function validate_component_unsupported_loop_statement(node, filename, errors, comments) {
|
|
236
|
+
let message;
|
|
237
|
+
if (node.type === 'ForStatement') {
|
|
238
|
+
message = COMPONENT_FOR_STATEMENT_ERROR;
|
|
239
|
+
} else if (node.type === 'ForInStatement') {
|
|
240
|
+
message = COMPONENT_FOR_IN_STATEMENT_ERROR;
|
|
241
|
+
} else if (node.type === 'WhileStatement') {
|
|
242
|
+
message = COMPONENT_WHILE_STATEMENT_ERROR;
|
|
243
|
+
} else {
|
|
244
|
+
message = COMPONENT_DO_WHILE_STATEMENT_ERROR;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
error(message, filename ?? null, node, errors, comments);
|
|
248
|
+
}
|
|
249
|
+
|
|
175
250
|
/**
|
|
176
251
|
* @param {AST.Element} element
|
|
177
252
|
* @param {AnalysisContext} context
|
package/src/index.js
CHANGED
|
@@ -140,6 +140,7 @@ export { escape } from './utils/escaping.js';
|
|
|
140
140
|
export {
|
|
141
141
|
createJsxTransform,
|
|
142
142
|
merge_duplicate_refs as mergeDuplicateRefs,
|
|
143
|
+
rewrite_loop_continues_to_bare_returns as rewriteLoopContinuesToBareReturns,
|
|
143
144
|
to_jsx_attribute as toJsxAttribute,
|
|
144
145
|
validate_at_most_one_ref_attribute as validateAtMostOneRefAttribute,
|
|
145
146
|
component_to_function_declaration as componentToFunctionDeclaration,
|
|
@@ -209,8 +210,18 @@ export {
|
|
|
209
210
|
// Analyze
|
|
210
211
|
export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
|
|
211
212
|
export {
|
|
213
|
+
COMPONENT_DO_WHILE_STATEMENT_ERROR,
|
|
214
|
+
COMPONENT_FOR_IN_STATEMENT_ERROR,
|
|
215
|
+
COMPONENT_FOR_STATEMENT_ERROR,
|
|
216
|
+
COMPONENT_LOOP_BREAK_ERROR,
|
|
217
|
+
COMPONENT_LOOP_RETURN_ERROR,
|
|
212
218
|
COMPONENT_RETURN_VALUE_ERROR,
|
|
219
|
+
COMPONENT_WHILE_STATEMENT_ERROR,
|
|
213
220
|
get_return_keyword_node as getReturnKeywordNode,
|
|
221
|
+
get_statement_keyword_node as getStatementKeywordNode,
|
|
222
|
+
validate_component_loop_break_statement as validateComponentLoopBreakStatement,
|
|
223
|
+
validate_component_loop_return_statement as validateComponentLoopReturnStatement,
|
|
214
224
|
validate_component_return_statement as validateComponentReturnStatement,
|
|
225
|
+
validate_component_unsupported_loop_statement as validateComponentUnsupportedLoopStatement,
|
|
215
226
|
validate_nesting as validateNesting,
|
|
216
227
|
} from './analyze/validation.js';
|
package/src/plugin.js
CHANGED
|
@@ -269,6 +269,50 @@ export function TSRXPlugin(config) {
|
|
|
269
269
|
return null;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
#popTsxTokenContextBeforeTemplateExpressionChild() {
|
|
273
|
+
let index = this.pos;
|
|
274
|
+
let has_newline = false;
|
|
275
|
+
|
|
276
|
+
// Text-only Tsx nodes can leave the tokenizer in JSX text mode.
|
|
277
|
+
// Only unwind it for ASI before a following TSRX `{expr}` child;
|
|
278
|
+
// fragment props like `content={<></>}` still need the JSX context.
|
|
279
|
+
while (index < this.input.length) {
|
|
280
|
+
const ch = this.input.charCodeAt(index);
|
|
281
|
+
if (ch === 32 || ch === 9) {
|
|
282
|
+
index++;
|
|
283
|
+
} else if (ch === 10 || ch === 13) {
|
|
284
|
+
has_newline = true;
|
|
285
|
+
index++;
|
|
286
|
+
} else if (ch === 47 && this.input.charCodeAt(index + 1) === 42) {
|
|
287
|
+
const end = this.input.indexOf('*/', index + 2);
|
|
288
|
+
const comment_end = end === -1 ? this.input.length : end + 2;
|
|
289
|
+
if (this.input.slice(index, comment_end).match(regex_newline_characters)) {
|
|
290
|
+
has_newline = true;
|
|
291
|
+
}
|
|
292
|
+
index = comment_end;
|
|
293
|
+
} else if (ch === 47 && this.input.charCodeAt(index + 1) === 47) {
|
|
294
|
+
has_newline = true;
|
|
295
|
+
index += 2;
|
|
296
|
+
while (index < this.input.length) {
|
|
297
|
+
const comment_ch = this.input.charCodeAt(index);
|
|
298
|
+
if (comment_ch === 10 || comment_ch === 13) break;
|
|
299
|
+
index++;
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!has_newline || this.input.charCodeAt(index) !== 123) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const context_index = this.context.lastIndexOf(tstc.tc_expr);
|
|
311
|
+
if (context_index !== -1) {
|
|
312
|
+
this.context.length = context_index;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
272
316
|
#isDoubleQuotedTextChildStart() {
|
|
273
317
|
if (this.#path.findLast((n) => n.type === 'TsxCompat' || n.type === 'Tsx')) {
|
|
274
318
|
return false;
|
|
@@ -2009,6 +2053,7 @@ export function TSRXPlugin(config) {
|
|
|
2009
2053
|
if (this.type !== tstt.jsxTagEnd) {
|
|
2010
2054
|
raise_error();
|
|
2011
2055
|
}
|
|
2056
|
+
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2012
2057
|
this.next();
|
|
2013
2058
|
}
|
|
2014
2059
|
}
|
|
@@ -2194,6 +2239,7 @@ export function TSRXPlugin(config) {
|
|
|
2194
2239
|
if (this.type !== tstt.jsxTagEnd) {
|
|
2195
2240
|
raise_error();
|
|
2196
2241
|
}
|
|
2242
|
+
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2197
2243
|
this.next();
|
|
2198
2244
|
}
|
|
2199
2245
|
} else if (element.type === 'TsxCompat') {
|
|
@@ -2225,6 +2271,7 @@ export function TSRXPlugin(config) {
|
|
|
2225
2271
|
if (this.type !== tstt.jsxTagEnd) {
|
|
2226
2272
|
raise_error();
|
|
2227
2273
|
}
|
|
2274
|
+
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2228
2275
|
this.next();
|
|
2229
2276
|
}
|
|
2230
2277
|
} else if (this.#path[this.#path.length - 1] === element) {
|
|
@@ -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,
|
|
@@ -440,6 +577,7 @@ function build_component_statements(body_nodes, transform_context) {
|
|
|
440
577
|
function build_render_statements(body_nodes, return_null_when_empty, transform_context) {
|
|
441
578
|
const statements = [];
|
|
442
579
|
const render_nodes = [];
|
|
580
|
+
let has_bare_return = false;
|
|
443
581
|
|
|
444
582
|
// Create a new bindings map so inner-scope bindings from
|
|
445
583
|
// collect_statement_bindings don't leak to the caller's scope.
|
|
@@ -460,6 +598,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
460
598
|
if (is_bare_return_statement(child)) {
|
|
461
599
|
statements.push(create_component_return_statement(render_nodes, child));
|
|
462
600
|
render_nodes.length = 0;
|
|
601
|
+
has_bare_return = true;
|
|
463
602
|
continue;
|
|
464
603
|
}
|
|
465
604
|
|
|
@@ -708,7 +847,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
708
847
|
}
|
|
709
848
|
|
|
710
849
|
const return_arg = build_return_expression(render_nodes);
|
|
711
|
-
if (return_arg || return_null_when_empty) {
|
|
850
|
+
if (return_arg || (return_null_when_empty && !has_bare_return)) {
|
|
712
851
|
statements.push({
|
|
713
852
|
type: 'ReturnStatement',
|
|
714
853
|
argument: return_arg || { type: 'Literal', value: null, raw: 'null' },
|
|
@@ -1339,6 +1478,126 @@ function append_tail_invocation(body, tail_helper) {
|
|
|
1339
1478
|
return [...body, clone_tail_invocation(tail_helper)];
|
|
1340
1479
|
}
|
|
1341
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
|
+
|
|
1342
1601
|
/**
|
|
1343
1602
|
* Build a `return <combined-render-fragment>;` statement, prepending any
|
|
1344
1603
|
* `render_nodes` collected before the control-flow construct so they don't
|
|
@@ -1703,7 +1962,11 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1703
1962
|
}
|
|
1704
1963
|
|
|
1705
1964
|
const has_tail = continuation_body.length > 0;
|
|
1706
|
-
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
|
+
);
|
|
1707
1970
|
|
|
1708
1971
|
// When there's a tail, build TailHelper first so its component_element can
|
|
1709
1972
|
// be embedded inside the loop helper's body (gated on isLast). The
|
|
@@ -1720,18 +1983,13 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1720
1983
|
} else {
|
|
1721
1984
|
tail_synthetic_id = /** @type {any} */ (null);
|
|
1722
1985
|
}
|
|
1723
|
-
const
|
|
1724
|
-
?
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
clone_tail_invocation(/** @type {any} */ (tail_helper)),
|
|
1731
|
-
),
|
|
1732
|
-
),
|
|
1733
|
-
]
|
|
1734
|
-
: 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;
|
|
1735
1993
|
|
|
1736
1994
|
const source_id = create_generated_identifier(
|
|
1737
1995
|
`_tsrx_iteration_items_${transform_context.local_statement_component_index + 1}`,
|
|
@@ -1767,10 +2025,8 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1767
2025
|
);
|
|
1768
2026
|
|
|
1769
2027
|
// Synthetic `isLast` prop on the loop helper when there's a tail. It's
|
|
1770
|
-
// passed from the .map callback as `i === source.length - 1` so
|
|
1771
|
-
// helper
|
|
1772
|
-
// gate on this prop's value here — the JSXLogicalExpression appended to
|
|
1773
|
-
// `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.
|
|
1774
2030
|
const tail_isLast_alias = has_tail
|
|
1775
2031
|
? {
|
|
1776
2032
|
id: create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
|
|
@@ -1808,6 +2064,13 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1808
2064
|
transform_context.available_bindings.set(tail_synthetic_id.name, tail_synthetic_id);
|
|
1809
2065
|
}
|
|
1810
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
|
+
}
|
|
1811
2074
|
transform_context.available_bindings = fn_saved_bindings;
|
|
1812
2075
|
|
|
1813
2076
|
const helper_fn = /** @type {any} */ (
|
|
@@ -1851,7 +2114,7 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
1851
2114
|
index_identifier = null;
|
|
1852
2115
|
}
|
|
1853
2116
|
|
|
1854
|
-
const body_key_expression = find_key_expression_in_body(
|
|
2117
|
+
const body_key_expression = find_key_expression_in_body(original_loop_body);
|
|
1855
2118
|
const explicit_key_expression =
|
|
1856
2119
|
body_key_expression ?? (node.key ? clone_expression_node(node.key) : undefined);
|
|
1857
2120
|
const key_expression =
|
|
@@ -2087,7 +2350,7 @@ function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nes
|
|
|
2087
2350
|
function combine_render_return_argument(render_nodes, return_argument) {
|
|
2088
2351
|
const combined = render_nodes.map((node) => clone_expression_node_without_locations(node));
|
|
2089
2352
|
|
|
2090
|
-
if (!is_null_literal(return_argument)) {
|
|
2353
|
+
if (return_argument != null && !is_null_literal(return_argument)) {
|
|
2091
2354
|
combined.push(return_argument_to_render_node(return_argument));
|
|
2092
2355
|
}
|
|
2093
2356
|
|
|
@@ -3049,6 +3312,71 @@ function find_key_expression_in_body(body_nodes) {
|
|
|
3049
3312
|
return undefined;
|
|
3050
3313
|
}
|
|
3051
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
|
+
|
|
3052
3380
|
/**
|
|
3053
3381
|
* @param {any} node
|
|
3054
3382
|
* @param {TransformContext} transform_context
|
|
@@ -3066,7 +3394,11 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
3066
3394
|
}
|
|
3067
3395
|
|
|
3068
3396
|
const loop_params = get_for_of_iteration_params(node.left, node.index);
|
|
3069
|
-
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
|
+
);
|
|
3070
3402
|
const has_hooks = body_contains_top_level_hook_call(loop_body, transform_context, true);
|
|
3071
3403
|
const body_key_expression = find_key_expression_in_body(loop_body);
|
|
3072
3404
|
const explicit_key_expression =
|
|
@@ -3091,14 +3423,14 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
3091
3423
|
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
3092
3424
|
}
|
|
3093
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
|
+
|
|
3094
3430
|
const body_statements = has_hooks
|
|
3095
3431
|
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
3096
3432
|
: build_render_statements(loop_body, true, transform_context);
|
|
3097
3433
|
|
|
3098
|
-
if (implicit_non_hook_key_expression) {
|
|
3099
|
-
apply_key_to_render_statements(body_statements, implicit_non_hook_key_expression);
|
|
3100
|
-
}
|
|
3101
|
-
|
|
3102
3434
|
const platform_for_of = transform_context.platform.hooks?.renderForOf?.(
|
|
3103
3435
|
node,
|
|
3104
3436
|
loop_params,
|
|
@@ -3110,6 +3442,11 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
3110
3442
|
return platform_for_of;
|
|
3111
3443
|
}
|
|
3112
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
|
+
|
|
3113
3450
|
// Restore bindings
|
|
3114
3451
|
transform_context.available_bindings = saved_bindings;
|
|
3115
3452
|
|
|
@@ -3147,19 +3484,33 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
3147
3484
|
}
|
|
3148
3485
|
|
|
3149
3486
|
/**
|
|
3150
|
-
* @param {any[]}
|
|
3487
|
+
* @param {any[]} body_nodes
|
|
3151
3488
|
* @param {any} key_expression
|
|
3152
3489
|
* @returns {void}
|
|
3153
3490
|
*/
|
|
3154
|
-
function
|
|
3155
|
-
for (
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
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;
|
|
3159
3510
|
}
|
|
3160
3511
|
|
|
3161
|
-
if (
|
|
3162
|
-
const attributes =
|
|
3512
|
+
if (node.type === 'JSXElement') {
|
|
3513
|
+
const attributes = node.openingElement?.attributes || [];
|
|
3163
3514
|
const has_key = attributes.some(
|
|
3164
3515
|
(/** @type {any} */ attr) =>
|
|
3165
3516
|
attr.type === 'JSXAttribute' &&
|
|
@@ -3180,12 +3531,92 @@ function apply_key_to_render_statements(statements, key_expression) {
|
|
|
3180
3531
|
}),
|
|
3181
3532
|
);
|
|
3182
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);
|
|
3183
3571
|
}
|
|
3184
3572
|
|
|
3185
3573
|
return;
|
|
3186
3574
|
}
|
|
3187
3575
|
}
|
|
3188
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
|
+
|
|
3189
3620
|
/**
|
|
3190
3621
|
* @param {any} node
|
|
3191
3622
|
* @param {TransformContext} transform_context
|
|
@@ -3473,6 +3904,27 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
|
|
|
3473
3904
|
/** @type {any[]} */
|
|
3474
3905
|
const imports = [];
|
|
3475
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
|
+
|
|
3476
3928
|
if (transform_context.needs_suspense) {
|
|
3477
3929
|
imports.push({
|
|
3478
3930
|
type: 'ImportDeclaration',
|
package/src/utils/builders.js
CHANGED
|
@@ -1343,6 +1343,15 @@ export const break_statement = {
|
|
|
1343
1343
|
metadata: { path: [] },
|
|
1344
1344
|
};
|
|
1345
1345
|
|
|
1346
|
+
/**
|
|
1347
|
+
* @type {AST.ContinueStatement}
|
|
1348
|
+
*/
|
|
1349
|
+
export const continue_statement = {
|
|
1350
|
+
type: 'ContinueStatement',
|
|
1351
|
+
label: null,
|
|
1352
|
+
metadata: { path: [] },
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1346
1355
|
export {
|
|
1347
1356
|
await_builder as await,
|
|
1348
1357
|
let_builder as let,
|
|
@@ -1352,6 +1361,7 @@ export {
|
|
|
1352
1361
|
true_instance as true,
|
|
1353
1362
|
false_instance as false,
|
|
1354
1363
|
break_statement as break,
|
|
1364
|
+
continue_statement as continue,
|
|
1355
1365
|
for_builder as for,
|
|
1356
1366
|
switch_builder as switch,
|
|
1357
1367
|
function_builder as function,
|
package/types/index.d.ts
CHANGED
package/types/jsx-platform.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface JsxTransformContext {
|
|
|
29
29
|
needs_error_boundary: boolean;
|
|
30
30
|
needs_suspense: boolean;
|
|
31
31
|
needs_merge_refs: boolean;
|
|
32
|
+
needs_fragment: boolean;
|
|
32
33
|
helper_state: {
|
|
33
34
|
base_name: string;
|
|
34
35
|
next_id: number;
|
|
@@ -239,6 +240,11 @@ export interface JsxPlatform {
|
|
|
239
240
|
name: string;
|
|
240
241
|
|
|
241
242
|
imports: {
|
|
243
|
+
/**
|
|
244
|
+
* Module to import `Fragment` from when a keyed fragment is required
|
|
245
|
+
* for a multi-child loop body. React: `'react'`. Preact: `'preact'`.
|
|
246
|
+
*/
|
|
247
|
+
fragment?: string;
|
|
242
248
|
/**
|
|
243
249
|
* Module to import `Suspense` from when a `try { ... } pending { ... }`
|
|
244
250
|
* block appears. React: `'react'`. Preact: `'preact/compat'`.
|