@tsrx/core 0.1.41 → 0.1.42
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 +4 -4
- package/src/parse/index.js +30 -0
- package/src/transform/jsx/helpers.js +28 -52
- package/src/transform/jsx/index.js +280 -132
- package/src/transform/segments.js +63 -43
- package/src/utils/builders.js +16 -0
- package/types/index.d.ts +12 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Core compiler infrastructure for TSRX syntax",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.42",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@jridgewell/sourcemap-codec": "^1.5.5",
|
|
65
65
|
"@noble/hashes": "^2.2.0",
|
|
66
|
-
"@sveltejs/acorn-typescript": "^1.0.
|
|
66
|
+
"@sveltejs/acorn-typescript": "^1.0.11",
|
|
67
67
|
"@types/estree-jsx": "^1.0.5",
|
|
68
68
|
"@types/estree": "^1.0.8",
|
|
69
|
-
"acorn": "^8.
|
|
70
|
-
"esrap": "^2.
|
|
69
|
+
"acorn": "^8.17.0",
|
|
70
|
+
"esrap": "^2.3.0",
|
|
71
71
|
"is-reference": "^3.0.3",
|
|
72
72
|
"magic-string": "^0.30.18",
|
|
73
73
|
"zimmerframe": "^1.1.2"
|
package/src/parse/index.js
CHANGED
|
@@ -219,12 +219,38 @@ export function createParser(...plugins) {
|
|
|
219
219
|
/** @type {AST.Program} */
|
|
220
220
|
let ast;
|
|
221
221
|
|
|
222
|
+
// Lexer-authoritative keyword positions (volar opt-in): the mapping
|
|
223
|
+
// collector needs the SOURCE spans of `async`/`function`, which no AST
|
|
224
|
+
// node records. The tokenizer is the only correct source — offset
|
|
225
|
+
// arithmetic breaks on extra whitespace, and text search breaks on
|
|
226
|
+
// comments (`async /* function */ function`).
|
|
227
|
+
/** @type {Array<{ value: string, start: number, end: number, loc: AST.SourceLocation }> | undefined} */
|
|
228
|
+
const keyword_tokens = options?.keywordTokens ? [] : undefined;
|
|
229
|
+
/** @type {Parse.Options['onToken'] | undefined} */
|
|
230
|
+
const onToken = keyword_tokens
|
|
231
|
+
? (token) => {
|
|
232
|
+
const t = /** @type {any} */ (token);
|
|
233
|
+
const is_function_keyword = t.type?.keyword === 'function';
|
|
234
|
+
const is_async_name = t.type?.label === 'name' && t.value === 'async';
|
|
235
|
+
if (is_function_keyword || is_async_name) {
|
|
236
|
+
keyword_tokens.push({
|
|
237
|
+
value: is_function_keyword ? 'function' : 'async',
|
|
238
|
+
start: t.start,
|
|
239
|
+
end: t.end,
|
|
240
|
+
loc: t.loc,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
: undefined;
|
|
245
|
+
|
|
222
246
|
try {
|
|
223
247
|
ast = parser.parse(source, {
|
|
224
248
|
sourceType: 'module',
|
|
225
249
|
ecmaVersion: 13,
|
|
226
250
|
allowReturnOutsideFunction: true,
|
|
227
251
|
locations: true,
|
|
252
|
+
onToken,
|
|
253
|
+
preserveParens: !!options?.preserveParens,
|
|
228
254
|
onComment,
|
|
229
255
|
tsrxOptions: {
|
|
230
256
|
filename,
|
|
@@ -245,6 +271,10 @@ export function createParser(...plugins) {
|
|
|
245
271
|
|
|
246
272
|
add_comments(ast);
|
|
247
273
|
|
|
274
|
+
if (keyword_tokens) {
|
|
275
|
+
/** @type {any} */ (ast).tsrx_keyword_tokens = keyword_tokens;
|
|
276
|
+
}
|
|
277
|
+
|
|
248
278
|
return ast;
|
|
249
279
|
};
|
|
250
280
|
}
|
|
@@ -51,18 +51,26 @@ export function set_node_path_metadata(node, path) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* Wrap esrap's `tsx()` printer with location markers for
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
54
|
+
* Wrap esrap's `tsx()` printer with location markers for the remaining nodes
|
|
55
|
+
* whose spans are invisible to the source map (e.g. `class`, template-literal
|
|
56
|
+
* backticks, JSX angle brackets, generic-argument delimiters). Without these
|
|
57
|
+
* markers, Volar mapping collection in `segments.js` throws when looking up
|
|
58
|
+
* the node's start/end positions. esrap ≥2.3.0 (keyword writes +
|
|
59
|
+
* `boundaryTokens`) covers most starts, but not statement ends — see the
|
|
60
|
+
* list below for what each entry still compensates.
|
|
59
61
|
*
|
|
60
62
|
* Shared across all JSX-producing targets (React, Preact, Solid).
|
|
61
63
|
*
|
|
62
64
|
* @returns {any}
|
|
63
65
|
*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
/**
|
|
67
|
+
* @param {boolean} [boundary_tokens] Enable esrap's `boundaryTokens` anchors
|
|
68
|
+
* (structural tokens carry one-character source locations). typeOnly/volar
|
|
69
|
+
* prints opt in — their maps are consumed positionally by the language
|
|
70
|
+
* tooling and never shipped; build prints stay sparse.
|
|
71
|
+
*/
|
|
72
|
+
export function tsx_with_ts_locations(boundary_tokens = false) {
|
|
73
|
+
const base = /** @type {any} */ (tsx({ boundaryTokens: boundary_tokens }));
|
|
66
74
|
|
|
67
75
|
/**
|
|
68
76
|
* @param {any} node
|
|
@@ -87,15 +95,6 @@ export function tsx_with_ts_locations() {
|
|
|
87
95
|
context.visit(node.typeAnnotation);
|
|
88
96
|
}
|
|
89
97
|
},
|
|
90
|
-
Identifier: (node, context) => {
|
|
91
|
-
context.write(node.name, node);
|
|
92
|
-
if (node.optional) {
|
|
93
|
-
context.write('?');
|
|
94
|
-
}
|
|
95
|
-
if (node.typeAnnotation) {
|
|
96
|
-
context.visit(node.typeAnnotation);
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
98
|
TSNamedTupleMember: (node, context) => {
|
|
100
99
|
context.visit(node.label);
|
|
101
100
|
if (node.optional) {
|
|
@@ -134,39 +133,6 @@ export function tsx_with_ts_locations() {
|
|
|
134
133
|
context.write(' ');
|
|
135
134
|
context.visit(value.body);
|
|
136
135
|
},
|
|
137
|
-
// esrap's ArrowFunctionExpression printer ignores `typeParameters` and
|
|
138
|
-
// `returnType`, so an annotated arrow like `(): Record<...> => ...`
|
|
139
|
-
// prints as `() => ...` and segments.js can't resolve the return-type
|
|
140
|
-
// nodes' positions in the generated output.
|
|
141
|
-
ArrowFunctionExpression: (node, context) => {
|
|
142
|
-
if (node.async) context.write('async ');
|
|
143
|
-
if (node.typeParameters) {
|
|
144
|
-
context.visit(node.typeParameters);
|
|
145
|
-
}
|
|
146
|
-
context.write('(');
|
|
147
|
-
for (let i = 0; i < node.params.length; i++) {
|
|
148
|
-
if (i > 0) context.write(', ');
|
|
149
|
-
context.visit(node.params[i]);
|
|
150
|
-
}
|
|
151
|
-
context.write(')');
|
|
152
|
-
if (node.returnType) {
|
|
153
|
-
context.visit(node.returnType);
|
|
154
|
-
}
|
|
155
|
-
context.write(' => ');
|
|
156
|
-
const body = node.body;
|
|
157
|
-
const wrap_body =
|
|
158
|
-
body.type === 'ObjectExpression' ||
|
|
159
|
-
(body.type === 'AssignmentExpression' && body.left.type === 'ObjectPattern') ||
|
|
160
|
-
(body.type === 'LogicalExpression' && body.left.type === 'ObjectExpression') ||
|
|
161
|
-
(body.type === 'ConditionalExpression' && body.test.type === 'ObjectExpression');
|
|
162
|
-
if (wrap_body) {
|
|
163
|
-
context.write('(');
|
|
164
|
-
context.visit(body);
|
|
165
|
-
context.write(')');
|
|
166
|
-
} else {
|
|
167
|
-
context.visit(body);
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
136
|
|
|
171
137
|
// esrap's JSXOpeningElement printer doesn't emit `typeArguments`, so generic
|
|
172
138
|
// component tags like `<RenderProp<User>>` lose the `<User>` in the output.
|
|
@@ -196,6 +162,11 @@ export function tsx_with_ts_locations() {
|
|
|
196
162
|
}
|
|
197
163
|
},
|
|
198
164
|
TSModuleDeclaration: (node, context) => {
|
|
165
|
+
// Ambient `declare module '…' { … }` must keep its `declare` — the
|
|
166
|
+
// typeOnly/volar output is real TS and `module '…' { … }` alone is a
|
|
167
|
+
// syntax error (TS1035). Non-ambient `module name { }` blocks have no
|
|
168
|
+
// `declare` and print unchanged.
|
|
169
|
+
if (node.declare) context.write('declare ');
|
|
199
170
|
context.write(node.metadata?.module_keyword ?? 'module');
|
|
200
171
|
context.write(' ');
|
|
201
172
|
context.visit(node.id);
|
|
@@ -209,9 +180,14 @@ export function tsx_with_ts_locations() {
|
|
|
209
180
|
// on the whole node, only then duplicate it here
|
|
210
181
|
// e.g. JSXOpeningElement is such a case
|
|
211
182
|
for (const type of [
|
|
212
|
-
// JS nodes
|
|
213
|
-
//
|
|
214
|
-
//
|
|
183
|
+
// JS nodes with boundary positions esrap still cannot map. Keyword
|
|
184
|
+
// writes (if/new/return/for/switch/await) and `boundaryTokens`
|
|
185
|
+
// anchors (brackets, braces, parens, computed/call closers) cover
|
|
186
|
+
// many STARTS, but statement ENDS land on unanchored characters
|
|
187
|
+
// (`;`, a block's `}`), `class` is not a keyword-write, template
|
|
188
|
+
// literals' backticks carry no location, and an arrow's span can
|
|
189
|
+
// start at a bare `(` — so these node-level markers remain the
|
|
190
|
+
// source of both boundaries until esrap can anchor them.
|
|
215
191
|
'ClassDeclaration',
|
|
216
192
|
'ClassExpression',
|
|
217
193
|
'IfStatement',
|
|
@@ -214,32 +214,53 @@ function lower_code_block_child(block) {
|
|
|
214
214
|
* `lower_code_block_child`). This is the element-scoped equivalent of
|
|
215
215
|
* `transform_function`'s body lowering — function and arrow bodies are never
|
|
216
216
|
* element children, so they are untouched here.
|
|
217
|
+
*
|
|
218
|
+
* The input tree is never mutated: replacements land on a shallow copy of the
|
|
219
|
+
* owning node (or array), so the return value must be used in place of the
|
|
220
|
+
* argument. Untouched subtrees are shared by reference with the input.
|
|
217
221
|
* @param {any} node
|
|
218
222
|
* @param {Set<any>} [seen]
|
|
219
|
-
* @returns {
|
|
223
|
+
* @returns {any}
|
|
220
224
|
*/
|
|
221
225
|
function expand_child_code_blocks(node, seen = new Set()) {
|
|
222
|
-
if (!node || typeof node !== 'object' || seen.has(node)) return;
|
|
226
|
+
if (!node || typeof node !== 'object' || seen.has(node)) return node;
|
|
223
227
|
seen.add(node);
|
|
224
228
|
|
|
225
229
|
if (Array.isArray(node)) {
|
|
226
|
-
|
|
227
|
-
|
|
230
|
+
let changed = false;
|
|
231
|
+
const result = node.map((item) => {
|
|
232
|
+
const walked = expand_child_code_blocks(item, seen);
|
|
233
|
+
if (walked !== item) changed = true;
|
|
234
|
+
return walked;
|
|
235
|
+
});
|
|
236
|
+
return changed ? result : node;
|
|
228
237
|
}
|
|
229
238
|
|
|
239
|
+
let out = node;
|
|
240
|
+
const set = (/** @type {string} */ key, /** @type {any} */ value) => {
|
|
241
|
+
if (out[key] === value) return;
|
|
242
|
+
if (out === node) out = { ...node };
|
|
243
|
+
out[key] = value;
|
|
244
|
+
};
|
|
245
|
+
|
|
230
246
|
if (
|
|
231
247
|
Array.isArray(node.children) &&
|
|
232
248
|
node.children.some((/** @type {any} */ c) => c?.type === 'JSXCodeBlock')
|
|
233
249
|
) {
|
|
234
|
-
|
|
235
|
-
|
|
250
|
+
set(
|
|
251
|
+
'children',
|
|
252
|
+
node.children.flatMap((/** @type {any} */ child) =>
|
|
253
|
+
child?.type === 'JSXCodeBlock' ? lower_code_block_child(child) : [child],
|
|
254
|
+
),
|
|
236
255
|
);
|
|
237
256
|
}
|
|
238
257
|
|
|
239
|
-
for (const key of Object.keys(
|
|
258
|
+
for (const key of Object.keys(out)) {
|
|
240
259
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
241
|
-
expand_child_code_blocks(
|
|
260
|
+
set(key, expand_child_code_blocks(out[key], seen));
|
|
242
261
|
}
|
|
262
|
+
|
|
263
|
+
return out;
|
|
243
264
|
}
|
|
244
265
|
|
|
245
266
|
/**
|
|
@@ -418,13 +439,17 @@ function wrap_lowered_value_in_fragment(expression, source) {
|
|
|
418
439
|
* its rendered value. Either way nothing leaks to the printer as a raw
|
|
419
440
|
* `JSX…Expression`.
|
|
420
441
|
*
|
|
442
|
+
* The input tree is never mutated: every replacement lands on a shallow copy of
|
|
443
|
+
* the owning node (or array), so the return value must be used in place of the
|
|
444
|
+
* argument. Untouched subtrees are shared by reference with the input.
|
|
445
|
+
*
|
|
421
446
|
* @param {any} node
|
|
422
447
|
* @param {TransformContext} transform_context
|
|
423
448
|
* @param {Set<any>} [seen]
|
|
424
|
-
* @returns {
|
|
449
|
+
* @returns {any}
|
|
425
450
|
*/
|
|
426
451
|
function wrap_control_flow_expression_values(node, transform_context, seen = new Set()) {
|
|
427
|
-
if (!node || typeof node !== 'object' || seen.has(node)) return;
|
|
452
|
+
if (!node || typeof node !== 'object' || seen.has(node)) return node;
|
|
428
453
|
seen.add(node);
|
|
429
454
|
|
|
430
455
|
// Dynamic tags on factory platforms must lower before control-flow
|
|
@@ -454,11 +479,17 @@ function wrap_control_flow_expression_values(node, transform_context, seen = new
|
|
|
454
479
|
: value;
|
|
455
480
|
|
|
456
481
|
if (Array.isArray(node)) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
wrap_control_flow_expression_values(
|
|
460
|
-
|
|
461
|
-
|
|
482
|
+
let changed = false;
|
|
483
|
+
const result = node.map((entry) => {
|
|
484
|
+
const walked = wrap_control_flow_expression_values(
|
|
485
|
+
lower_child(entry),
|
|
486
|
+
transform_context,
|
|
487
|
+
seen,
|
|
488
|
+
);
|
|
489
|
+
if (walked !== entry) changed = true;
|
|
490
|
+
return walked;
|
|
491
|
+
});
|
|
492
|
+
return changed ? result : node;
|
|
462
493
|
}
|
|
463
494
|
|
|
464
495
|
// Wrap a bare control-flow directive that is the sole value of a render-output
|
|
@@ -469,50 +500,72 @@ function wrap_control_flow_expression_values(node, transform_context, seen = new
|
|
|
469
500
|
const wrap_value = (/** @type {any} */ value) =>
|
|
470
501
|
is_jsx_control_flow_expression(value) ? wrap_in_native_tsrx_fragment(value) : value;
|
|
471
502
|
|
|
503
|
+
// All replacements land on `out`, a shallow copy made on first write; the
|
|
504
|
+
// input node's fields are never reassigned.
|
|
505
|
+
let out = node;
|
|
506
|
+
const set = (/** @type {string} */ key, /** @type {any} */ value) => {
|
|
507
|
+
if (out[key] === value) return;
|
|
508
|
+
if (out === node) out = { ...node };
|
|
509
|
+
out[key] = value;
|
|
510
|
+
};
|
|
511
|
+
|
|
472
512
|
if (
|
|
473
513
|
node.type === 'ArrowFunctionExpression' &&
|
|
474
514
|
node.body?.type !== 'BlockStatement' &&
|
|
475
515
|
is_jsx_control_flow_expression(node.body)
|
|
476
516
|
) {
|
|
477
|
-
|
|
517
|
+
set('body', wrap_in_native_tsrx_fragment(node.body));
|
|
478
518
|
} else if (node.type === 'ReturnStatement' && is_jsx_control_flow_expression(node.argument)) {
|
|
479
|
-
|
|
519
|
+
set('argument', wrap_in_native_tsrx_fragment(node.argument));
|
|
480
520
|
} else if (
|
|
481
521
|
node.type === 'ExpressionStatement' &&
|
|
482
522
|
is_jsx_control_flow_expression(node.expression)
|
|
483
523
|
) {
|
|
484
|
-
|
|
524
|
+
set('expression', wrap_in_native_tsrx_fragment(node.expression));
|
|
485
525
|
} else if (node.type === 'VariableDeclarator' && is_jsx_control_flow_expression(node.init)) {
|
|
486
|
-
|
|
526
|
+
set('init', wrap_in_native_tsrx_fragment(node.init));
|
|
487
527
|
} else if (node.type === 'AssignmentExpression' && is_jsx_control_flow_expression(node.right)) {
|
|
488
|
-
|
|
528
|
+
set('right', wrap_in_native_tsrx_fragment(node.right));
|
|
489
529
|
} else if (
|
|
490
530
|
(node.type === 'CallExpression' || node.type === 'NewExpression') &&
|
|
491
531
|
Array.isArray(node.arguments)
|
|
492
532
|
) {
|
|
493
|
-
|
|
533
|
+
const wrapped = node.arguments.map(wrap_value);
|
|
534
|
+
if (
|
|
535
|
+
wrapped.some(
|
|
536
|
+
(/** @type {any} */ argument, /** @type {number} */ i) => argument !== node.arguments[i],
|
|
537
|
+
)
|
|
538
|
+
) {
|
|
539
|
+
set('arguments', wrapped);
|
|
540
|
+
}
|
|
494
541
|
}
|
|
495
542
|
|
|
496
|
-
for (const key of Object.keys(
|
|
543
|
+
for (const key of Object.keys(out)) {
|
|
497
544
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
498
545
|
// A directive is allowed as a render child/statement, and as the sole value
|
|
499
546
|
// of a render-output slot (handled above for control flow; `@{ … }` blocks
|
|
500
547
|
// self-lower). Everywhere else it is combined into an expression — wrap it.
|
|
501
548
|
const allowed_slot =
|
|
502
549
|
is_statement_or_template_slot(node, key) || is_render_output_value_slot(node, key);
|
|
503
|
-
const value =
|
|
550
|
+
const value = out[key];
|
|
504
551
|
if (Array.isArray(value)) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
552
|
+
let changed = false;
|
|
553
|
+
const result = value.map((entry) => {
|
|
554
|
+
let next = lower_child(entry);
|
|
555
|
+
if (!allowed_slot) next = wrap_directive_in_expression(next);
|
|
556
|
+
next = wrap_control_flow_expression_values(next, transform_context, seen);
|
|
557
|
+
if (next !== entry) changed = true;
|
|
558
|
+
return next;
|
|
559
|
+
});
|
|
560
|
+
if (changed) set(key, result);
|
|
510
561
|
} else {
|
|
511
|
-
|
|
512
|
-
if (!allowed_slot)
|
|
513
|
-
|
|
562
|
+
let next = lower_child(value);
|
|
563
|
+
if (!allowed_slot) next = wrap_directive_in_expression(next);
|
|
564
|
+
set(key, wrap_control_flow_expression_values(next, transform_context, seen));
|
|
514
565
|
}
|
|
515
566
|
}
|
|
567
|
+
|
|
568
|
+
return out;
|
|
516
569
|
}
|
|
517
570
|
|
|
518
571
|
/**
|
|
@@ -578,8 +631,8 @@ export function createJsxTransform(platform) {
|
|
|
578
631
|
...(platform.hooks?.initialState?.() ?? {}),
|
|
579
632
|
};
|
|
580
633
|
|
|
581
|
-
expand_child_code_blocks(/** @type {any} */ (ast));
|
|
582
|
-
wrap_control_flow_expression_values(/** @type {any} */ (ast), transform_context);
|
|
634
|
+
ast = expand_child_code_blocks(/** @type {any} */ (ast));
|
|
635
|
+
ast = wrap_control_flow_expression_values(/** @type {any} */ (ast), transform_context);
|
|
583
636
|
|
|
584
637
|
if (!transform_context.typeOnly) {
|
|
585
638
|
preallocate_lazy_ids(/** @type {any} */ (ast), transform_context);
|
|
@@ -735,7 +788,15 @@ export function createJsxTransform(platform) {
|
|
|
735
788
|
},
|
|
736
789
|
});
|
|
737
790
|
|
|
738
|
-
|
|
791
|
+
let transformed_program = /** @type {AST.Program} */ (transformed);
|
|
792
|
+
// The walk returns the input program unchanged when no visitor replaced
|
|
793
|
+
// anything beneath it. The post-passes below (style anchors, helper
|
|
794
|
+
// expansion, import injection) write into the program's `body`, so
|
|
795
|
+
// detach from the caller's AST first; a changed program is already a
|
|
796
|
+
// fresh walk-owned node with a fresh `body` array.
|
|
797
|
+
if (/** @type {any} */ (transformed_program) === /** @type {any} */ (ast)) {
|
|
798
|
+
transformed_program = { ...transformed_program, body: [...transformed_program.body] };
|
|
799
|
+
}
|
|
739
800
|
if (type_only_style_anchors.length > 0) {
|
|
740
801
|
transformed_program.body.unshift(...type_only_style_anchors);
|
|
741
802
|
}
|
|
@@ -751,7 +812,7 @@ export function createJsxTransform(platform) {
|
|
|
751
812
|
// lazy transform runs, so every `@{ … }` block / `@`-directive has already
|
|
752
813
|
// been lowered to its final closure / block shape. The lazy transform can
|
|
753
814
|
// then walk the complete function structure in one pass.
|
|
754
|
-
lower_remaining_jsx_code_blocks(expanded, transform_context);
|
|
815
|
+
const lowered_program = lower_remaining_jsx_code_blocks(expanded, transform_context);
|
|
755
816
|
|
|
756
817
|
// Apply lazy destructuring transforms to module-level code (top-level function
|
|
757
818
|
// declarations, arrow functions, etc.).
|
|
@@ -772,15 +833,15 @@ export function createJsxTransform(platform) {
|
|
|
772
833
|
// so lazy bindings declared inside a nested block or directive body are
|
|
773
834
|
// rewritten just like a flat function body.
|
|
774
835
|
if (!transform_context.typeOnly) {
|
|
775
|
-
preallocate_lazy_ids(/** @type {any} */ (
|
|
836
|
+
preallocate_lazy_ids(/** @type {any} */ (lowered_program), transform_context);
|
|
776
837
|
}
|
|
777
838
|
const final_program = /** @type {any} */ (
|
|
778
839
|
transform_context.typeOnly
|
|
779
|
-
?
|
|
780
|
-
: apply_lazy_transforms(/** @type {any} */ (
|
|
840
|
+
? lowered_program
|
|
841
|
+
: apply_lazy_transforms(/** @type {any} */ (lowered_program), new Map())
|
|
781
842
|
);
|
|
782
843
|
|
|
783
|
-
const result = print(
|
|
844
|
+
const result = print(final_program, tsx_with_ts_locations(transform_context.typeOnly), {
|
|
784
845
|
sourceMapSource: filename,
|
|
785
846
|
sourceMapContent: source,
|
|
786
847
|
});
|
|
@@ -1707,7 +1768,7 @@ function get_active_native_tsrx_function(path) {
|
|
|
1707
1768
|
|
|
1708
1769
|
/**
|
|
1709
1770
|
* @param {any} node
|
|
1710
|
-
* @param {{ next: () => any, state: TransformContext, path: AST.Node[] }} context
|
|
1771
|
+
* @param {{ next: () => any, visit: (node: any, state?: TransformContext) => any, state: TransformContext, path: AST.Node[] }} context
|
|
1711
1772
|
* @returns {any}
|
|
1712
1773
|
*/
|
|
1713
1774
|
function transform_function(node, context) {
|
|
@@ -1717,15 +1778,26 @@ function transform_function(node, context) {
|
|
|
1717
1778
|
// from here it flows through the existing native-component machinery exactly
|
|
1718
1779
|
// like the older fenced `{ return <> … </> }` shape.
|
|
1719
1780
|
const has_jsx_code_block_body = node.body?.type === 'JSXCodeBlock';
|
|
1720
|
-
lower_jsx_code_block_function_body(node);
|
|
1781
|
+
const lowered = lower_jsx_code_block_function_body(node);
|
|
1782
|
+
if (lowered !== node) {
|
|
1783
|
+
// The lowering produced a COPY; carry the native-body fact through the
|
|
1784
|
+
// sanctioned metadata channel and re-dispatch so the walker transforms
|
|
1785
|
+
// the lowered tree (terminates: the copy's body is a BlockStatement).
|
|
1786
|
+
lowered.metadata = { ...(lowered.metadata || {}), native_tsrx_body: true };
|
|
1787
|
+
return context.visit(lowered);
|
|
1788
|
+
}
|
|
1721
1789
|
|
|
1722
1790
|
if (
|
|
1723
1791
|
has_jsx_code_block_body ||
|
|
1724
1792
|
node.metadata?.native_tsrx_function ||
|
|
1793
|
+
node.metadata?.native_tsrx_body ||
|
|
1725
1794
|
function_has_native_tsrx_return(node)
|
|
1726
1795
|
) {
|
|
1727
1796
|
return transform_native_tsrx_function(node, context, {
|
|
1728
|
-
nativeBody:
|
|
1797
|
+
nativeBody:
|
|
1798
|
+
has_jsx_code_block_body ||
|
|
1799
|
+
!!node.metadata?.native_tsrx_function ||
|
|
1800
|
+
!!node.metadata?.native_tsrx_body,
|
|
1729
1801
|
});
|
|
1730
1802
|
}
|
|
1731
1803
|
|
|
@@ -1733,11 +1805,14 @@ function transform_function(node, context) {
|
|
|
1733
1805
|
}
|
|
1734
1806
|
|
|
1735
1807
|
/**
|
|
1808
|
+
* Lower a `@{ … }` body (JSXCodeBlock) to an ordinary block on a COPY built
|
|
1809
|
+
* with the AST builders — the source function node is never mutated. Returns
|
|
1810
|
+
* the input node unchanged when there is nothing to lower.
|
|
1736
1811
|
* @param {any} node
|
|
1737
|
-
* @returns {
|
|
1812
|
+
* @returns {any}
|
|
1738
1813
|
*/
|
|
1739
1814
|
function lower_jsx_code_block_function_body(node) {
|
|
1740
|
-
if (node.body?.type !== 'JSXCodeBlock') return;
|
|
1815
|
+
if (node.body?.type !== 'JSXCodeBlock') return node;
|
|
1741
1816
|
|
|
1742
1817
|
const code_block = node.body;
|
|
1743
1818
|
const statements = [...code_block.body];
|
|
@@ -1758,10 +1833,11 @@ function lower_jsx_code_block_function_body(node) {
|
|
|
1758
1833
|
}
|
|
1759
1834
|
statements.push(b.return(render, code_block.render));
|
|
1760
1835
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1836
|
+
return {
|
|
1837
|
+
...node,
|
|
1838
|
+
body: b.block(statements, code_block),
|
|
1839
|
+
...(node.type === 'ArrowFunctionExpression' ? { expression: false } : null),
|
|
1840
|
+
};
|
|
1765
1841
|
}
|
|
1766
1842
|
|
|
1767
1843
|
/**
|
|
@@ -3039,46 +3115,62 @@ function expand_component_helpers(program) {
|
|
|
3039
3115
|
* If one of those helpers contains a statement-container body, lower it before
|
|
3040
3116
|
* the printer sees the helper subtree.
|
|
3041
3117
|
*
|
|
3118
|
+
* The tree is never mutated: replacements land on a shallow copy of the owning
|
|
3119
|
+
* node (or array), so the return value must be used in place of the argument.
|
|
3120
|
+
* Untouched subtrees are shared by reference with the input.
|
|
3121
|
+
*
|
|
3042
3122
|
* @param {any} node
|
|
3043
3123
|
* @param {TransformContext} transform_context
|
|
3044
3124
|
* @param {Set<any>} [seen]
|
|
3045
|
-
* @returns {
|
|
3125
|
+
* @returns {any}
|
|
3046
3126
|
*/
|
|
3047
3127
|
function lower_remaining_jsx_code_blocks(node, transform_context, seen = new Set()) {
|
|
3048
|
-
if (!node || typeof node !== 'object' || seen.has(node)) return;
|
|
3128
|
+
if (!node || typeof node !== 'object' || seen.has(node)) return node;
|
|
3049
3129
|
seen.add(node);
|
|
3050
3130
|
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3131
|
+
// A code-block function body lowers to a fresh copy of the function node;
|
|
3132
|
+
// its children are then walked below like any other node's.
|
|
3133
|
+
let out = is_function_or_class_boundary(node) ? lower_jsx_code_block_function_body(node) : node;
|
|
3134
|
+
const set = (/** @type {string} */ key, /** @type {any} */ value) => {
|
|
3135
|
+
if (out[key] === value) return;
|
|
3136
|
+
if (out === node) out = { ...node };
|
|
3137
|
+
out[key] = value;
|
|
3138
|
+
};
|
|
3054
3139
|
|
|
3055
|
-
for (const key of Object.keys(
|
|
3140
|
+
for (const key of Object.keys(out)) {
|
|
3056
3141
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
3057
|
-
|
|
3142
|
+
const value = out[key];
|
|
3058
3143
|
if (!value || typeof value !== 'object') continue;
|
|
3059
3144
|
|
|
3060
3145
|
if (Array.isArray(value)) {
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3146
|
+
const expanded =
|
|
3147
|
+
key === 'body' && value.some((child) => child?.type === 'JSXCodeBlock')
|
|
3148
|
+
? value.flatMap((child) => {
|
|
3149
|
+
if (child?.type !== 'JSXCodeBlock') return [child];
|
|
3150
|
+
const body_nodes = get_jsx_code_block_body_nodes(child, transform_context);
|
|
3151
|
+
return mark_native_pretransformed_jsx(
|
|
3152
|
+
build_render_statements(
|
|
3153
|
+
body_nodes,
|
|
3154
|
+
true,
|
|
3155
|
+
transform_context,
|
|
3156
|
+
is_authored_native_fragment(child.render) ? child.render : null,
|
|
3157
|
+
),
|
|
3158
|
+
);
|
|
3159
|
+
})
|
|
3160
|
+
: value;
|
|
3161
|
+
let changed = expanded !== value;
|
|
3162
|
+
const result = expanded.map((child) => {
|
|
3163
|
+
const walked = lower_remaining_jsx_code_blocks(child, transform_context, seen);
|
|
3164
|
+
if (walked !== child) changed = true;
|
|
3165
|
+
return walked;
|
|
3166
|
+
});
|
|
3167
|
+
if (changed) set(key, result);
|
|
3078
3168
|
} else {
|
|
3079
|
-
lower_remaining_jsx_code_blocks(value, transform_context, seen);
|
|
3169
|
+
set(key, lower_remaining_jsx_code_blocks(value, transform_context, seen));
|
|
3080
3170
|
}
|
|
3081
3171
|
}
|
|
3172
|
+
|
|
3173
|
+
return out;
|
|
3082
3174
|
}
|
|
3083
3175
|
|
|
3084
3176
|
/**
|
|
@@ -3202,9 +3294,8 @@ function get_loop_skip_if_consequent_body(node) {
|
|
|
3202
3294
|
*/
|
|
3203
3295
|
function create_component_loop_skip_if_statement(node, render_nodes, transform_context) {
|
|
3204
3296
|
const consequent_body = /** @type {any[]} */ (get_loop_skip_if_consequent_body(node));
|
|
3205
|
-
const branch_statements =
|
|
3206
|
-
|
|
3207
|
-
branch_statements,
|
|
3297
|
+
const branch_statements = prepend_render_nodes_to_return_statements(
|
|
3298
|
+
build_render_statements(consequent_body, true, transform_context),
|
|
3208
3299
|
render_nodes,
|
|
3209
3300
|
transform_context.typeOnly,
|
|
3210
3301
|
);
|
|
@@ -3221,19 +3312,23 @@ function create_component_loop_skip_if_statement(node, render_nodes, transform_c
|
|
|
3221
3312
|
}
|
|
3222
3313
|
|
|
3223
3314
|
/**
|
|
3315
|
+
* Statements can be passed through `build_render_statements` by reference, so
|
|
3316
|
+
* rewritten returns land on shallow copies; the returned array must be used in
|
|
3317
|
+
* place of the argument.
|
|
3318
|
+
*
|
|
3224
3319
|
* @param {any[]} statements
|
|
3225
3320
|
* @param {any[]} render_nodes
|
|
3226
3321
|
* @param {boolean} [type_only]
|
|
3227
|
-
* @returns {
|
|
3322
|
+
* @returns {any[]}
|
|
3228
3323
|
*/
|
|
3229
3324
|
function prepend_render_nodes_to_return_statements(statements, render_nodes, type_only = false) {
|
|
3230
3325
|
if (render_nodes.length === 0) {
|
|
3231
|
-
return;
|
|
3326
|
+
return statements;
|
|
3232
3327
|
}
|
|
3233
3328
|
|
|
3234
|
-
|
|
3235
|
-
prepend_render_nodes_to_return_statement(
|
|
3236
|
-
|
|
3329
|
+
return /** @type {any[]} */ (
|
|
3330
|
+
prepend_render_nodes_to_return_statement(statements, render_nodes, false, type_only)
|
|
3331
|
+
);
|
|
3237
3332
|
}
|
|
3238
3333
|
|
|
3239
3334
|
/**
|
|
@@ -3241,7 +3336,7 @@ function prepend_render_nodes_to_return_statements(statements, render_nodes, typ
|
|
|
3241
3336
|
* @param {any[]} render_nodes
|
|
3242
3337
|
* @param {boolean} inside_nested_function
|
|
3243
3338
|
* @param {boolean} [type_only]
|
|
3244
|
-
* @returns {
|
|
3339
|
+
* @returns {any}
|
|
3245
3340
|
*/
|
|
3246
3341
|
function prepend_render_nodes_to_return_statement(
|
|
3247
3342
|
node,
|
|
@@ -3250,7 +3345,7 @@ function prepend_render_nodes_to_return_statement(
|
|
|
3250
3345
|
type_only = false,
|
|
3251
3346
|
) {
|
|
3252
3347
|
if (!node || typeof node !== 'object') {
|
|
3253
|
-
return;
|
|
3348
|
+
return node;
|
|
3254
3349
|
}
|
|
3255
3350
|
|
|
3256
3351
|
if (
|
|
@@ -3262,33 +3357,44 @@ function prepend_render_nodes_to_return_statement(
|
|
|
3262
3357
|
}
|
|
3263
3358
|
|
|
3264
3359
|
if (!inside_nested_function && node.type === 'ReturnStatement') {
|
|
3265
|
-
|
|
3266
|
-
|
|
3360
|
+
return {
|
|
3361
|
+
...node,
|
|
3362
|
+
argument: combine_render_return_argument(render_nodes, node.argument, type_only),
|
|
3363
|
+
};
|
|
3267
3364
|
}
|
|
3268
3365
|
|
|
3269
3366
|
if (Array.isArray(node)) {
|
|
3270
|
-
|
|
3271
|
-
|
|
3367
|
+
let changed = false;
|
|
3368
|
+
const result = node.map((child) => {
|
|
3369
|
+
const walked = prepend_render_nodes_to_return_statement(
|
|
3272
3370
|
child,
|
|
3273
3371
|
render_nodes,
|
|
3274
3372
|
inside_nested_function,
|
|
3275
3373
|
type_only,
|
|
3276
3374
|
);
|
|
3277
|
-
|
|
3278
|
-
|
|
3375
|
+
if (walked !== child) changed = true;
|
|
3376
|
+
return walked;
|
|
3377
|
+
});
|
|
3378
|
+
return changed ? result : node;
|
|
3279
3379
|
}
|
|
3280
3380
|
|
|
3381
|
+
let out = node;
|
|
3281
3382
|
for (const key of Object.keys(node)) {
|
|
3282
3383
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
3283
3384
|
continue;
|
|
3284
3385
|
}
|
|
3285
|
-
prepend_render_nodes_to_return_statement(
|
|
3386
|
+
const walked = prepend_render_nodes_to_return_statement(
|
|
3286
3387
|
node[key],
|
|
3287
3388
|
render_nodes,
|
|
3288
3389
|
inside_nested_function,
|
|
3289
3390
|
type_only,
|
|
3290
3391
|
);
|
|
3392
|
+
if (walked !== node[key]) {
|
|
3393
|
+
if (out === node) out = { ...node };
|
|
3394
|
+
out[key] = walked;
|
|
3395
|
+
}
|
|
3291
3396
|
}
|
|
3397
|
+
return out;
|
|
3292
3398
|
}
|
|
3293
3399
|
|
|
3294
3400
|
/**
|
|
@@ -4733,9 +4839,16 @@ function continue_to_bare_return(source_node) {
|
|
|
4733
4839
|
*/
|
|
4734
4840
|
export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
|
|
4735
4841
|
if (Array.isArray(node)) {
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4842
|
+
let changed = false;
|
|
4843
|
+
const result = node.map((child) => {
|
|
4844
|
+
const walked = rewrite_loop_continues_to_bare_returns(
|
|
4845
|
+
child,
|
|
4846
|
+
is_root && !is_loop_statement(child),
|
|
4847
|
+
);
|
|
4848
|
+
if (walked !== child) changed = true;
|
|
4849
|
+
return walked;
|
|
4850
|
+
});
|
|
4851
|
+
return changed ? result : node;
|
|
4739
4852
|
}
|
|
4740
4853
|
|
|
4741
4854
|
if (!node || typeof node !== 'object') {
|
|
@@ -4750,14 +4863,19 @@ export function rewrite_loop_continues_to_bare_returns(node, is_root = true) {
|
|
|
4750
4863
|
return node;
|
|
4751
4864
|
}
|
|
4752
4865
|
|
|
4866
|
+
let out = node;
|
|
4753
4867
|
for (const key of Object.keys(node)) {
|
|
4754
4868
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
4755
4869
|
continue;
|
|
4756
4870
|
}
|
|
4757
|
-
|
|
4871
|
+
const walked = rewrite_loop_continues_to_bare_returns(node[key], false);
|
|
4872
|
+
if (walked !== node[key]) {
|
|
4873
|
+
if (out === node) out = { ...node };
|
|
4874
|
+
out[key] = walked;
|
|
4875
|
+
}
|
|
4758
4876
|
}
|
|
4759
4877
|
|
|
4760
|
-
return
|
|
4878
|
+
return out;
|
|
4761
4879
|
}
|
|
4762
4880
|
|
|
4763
4881
|
/**
|
|
@@ -4921,7 +5039,7 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
4921
5039
|
}
|
|
4922
5040
|
|
|
4923
5041
|
const loop_params = get_for_of_iteration_params(node.left, node.index);
|
|
4924
|
-
|
|
5042
|
+
let loop_body = /** @type {any[]} */ (
|
|
4925
5043
|
node.body.type === 'BlockStatement' ? node.body.body : [node.body]
|
|
4926
5044
|
);
|
|
4927
5045
|
validate_for_body_control_flow(loop_body, transform_context);
|
|
@@ -4952,10 +5070,10 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
4952
5070
|
}
|
|
4953
5071
|
|
|
4954
5072
|
if (implicit_non_hook_key_expression && should_apply_key_to_loop_body(loop_body)) {
|
|
4955
|
-
apply_key_to_loop_body(loop_body, implicit_non_hook_key_expression);
|
|
5073
|
+
loop_body = apply_key_to_loop_body(loop_body, implicit_non_hook_key_expression);
|
|
4956
5074
|
}
|
|
4957
5075
|
|
|
4958
|
-
|
|
5076
|
+
let body_statements = has_hooks
|
|
4959
5077
|
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
4960
5078
|
: build_render_statements(loop_body, true, transform_context);
|
|
4961
5079
|
|
|
@@ -4972,7 +5090,11 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
4972
5090
|
|
|
4973
5091
|
const non_hook_key_expression = key_expression ?? implicit_non_hook_key_expression;
|
|
4974
5092
|
if (!has_hooks && non_hook_key_expression) {
|
|
4975
|
-
|
|
5093
|
+
body_statements = apply_key_to_render_statements(
|
|
5094
|
+
body_statements,
|
|
5095
|
+
non_hook_key_expression,
|
|
5096
|
+
transform_context,
|
|
5097
|
+
);
|
|
4976
5098
|
}
|
|
4977
5099
|
|
|
4978
5100
|
// Restore bindings
|
|
@@ -5025,32 +5147,40 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
5025
5147
|
}
|
|
5026
5148
|
|
|
5027
5149
|
/**
|
|
5150
|
+
* Returns a copy of `body_nodes` where the first keyable element carries the
|
|
5151
|
+
* key attribute on a rebuilt opening element — the source nodes are never
|
|
5152
|
+
* mutated (they may belong to the caller's parsed AST).
|
|
5028
5153
|
* @param {any[]} body_nodes
|
|
5029
5154
|
* @param {any} key_expression
|
|
5030
|
-
* @returns {
|
|
5155
|
+
* @returns {any[]}
|
|
5031
5156
|
*/
|
|
5032
5157
|
function apply_key_to_loop_body(body_nodes, key_expression) {
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5158
|
+
let applied = false;
|
|
5159
|
+
return body_nodes.map((node) => {
|
|
5160
|
+
if (applied || node.type !== 'JSXElement') return node;
|
|
5161
|
+
applied = true;
|
|
5162
|
+
const attributes = node.openingElement?.attributes || [];
|
|
5163
|
+
const has_key = attributes.some(
|
|
5164
|
+
(/** @type {any} */ attr) =>
|
|
5165
|
+
attr.type === 'JSXAttribute' &&
|
|
5166
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
5167
|
+
attr.name.name === 'key',
|
|
5168
|
+
);
|
|
5169
|
+
if (has_key) return node;
|
|
5170
|
+
return {
|
|
5171
|
+
...node,
|
|
5172
|
+
openingElement: {
|
|
5173
|
+
...node.openingElement,
|
|
5174
|
+
attributes: [
|
|
5175
|
+
...attributes,
|
|
5045
5176
|
b.jsx_attribute(
|
|
5046
5177
|
b.jsx_id('key'),
|
|
5047
5178
|
to_jsx_expression_container(clone_expression_node(key_expression), key_expression),
|
|
5048
5179
|
),
|
|
5049
|
-
|
|
5050
|
-
}
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
}
|
|
5180
|
+
],
|
|
5181
|
+
},
|
|
5182
|
+
};
|
|
5183
|
+
});
|
|
5054
5184
|
}
|
|
5055
5185
|
|
|
5056
5186
|
/**
|
|
@@ -5068,10 +5198,14 @@ function should_apply_key_to_loop_body(body_nodes) {
|
|
|
5068
5198
|
}
|
|
5069
5199
|
|
|
5070
5200
|
/**
|
|
5201
|
+
* Statement entries can be shared with the source tree, so the keyed return
|
|
5202
|
+
* lands on shallow copies; the returned array must be used in place of the
|
|
5203
|
+
* argument.
|
|
5204
|
+
*
|
|
5071
5205
|
* @param {any[]} statements
|
|
5072
5206
|
* @param {any} key_expression
|
|
5073
5207
|
* @param {TransformContext} transform_context
|
|
5074
|
-
* @returns {
|
|
5208
|
+
* @returns {any[]}
|
|
5075
5209
|
*/
|
|
5076
5210
|
function apply_key_to_render_statements(statements, key_expression, transform_context) {
|
|
5077
5211
|
for (let i = statements.length - 1; i >= 0; i -= 1) {
|
|
@@ -5080,21 +5214,29 @@ function apply_key_to_render_statements(statements, key_expression, transform_co
|
|
|
5080
5214
|
continue;
|
|
5081
5215
|
}
|
|
5082
5216
|
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5217
|
+
let argument = statement.argument;
|
|
5218
|
+
if (argument.type === 'JSXElement') {
|
|
5219
|
+
argument = apply_key_to_jsx_element(argument, key_expression);
|
|
5220
|
+
} else if (argument.type === 'JSXFragment') {
|
|
5086
5221
|
transform_context.needs_fragment = true;
|
|
5087
|
-
|
|
5222
|
+
argument = keyed_fragment_to_jsx_element(argument, key_expression);
|
|
5088
5223
|
}
|
|
5089
5224
|
|
|
5090
|
-
|
|
5225
|
+
if (argument === statement.argument) {
|
|
5226
|
+
return statements;
|
|
5227
|
+
}
|
|
5228
|
+
const result = [...statements];
|
|
5229
|
+
result[i] = { ...statement, argument };
|
|
5230
|
+
return result;
|
|
5091
5231
|
}
|
|
5232
|
+
return statements;
|
|
5092
5233
|
}
|
|
5093
5234
|
|
|
5094
5235
|
/**
|
|
5095
5236
|
* @param {any} element
|
|
5096
5237
|
* @param {any} key_expression
|
|
5097
|
-
* @returns {
|
|
5238
|
+
* @returns {any} the element itself when it already has a `key`, otherwise a
|
|
5239
|
+
* shallow copy with the key attribute appended.
|
|
5098
5240
|
*/
|
|
5099
5241
|
function apply_key_to_jsx_element(element, key_expression) {
|
|
5100
5242
|
const attributes = element.openingElement?.attributes || [];
|
|
@@ -5104,15 +5246,21 @@ function apply_key_to_jsx_element(element, key_expression) {
|
|
|
5104
5246
|
attr.name?.type === 'JSXIdentifier' &&
|
|
5105
5247
|
attr.name.name === 'key',
|
|
5106
5248
|
);
|
|
5249
|
+
if (has_key) return element;
|
|
5107
5250
|
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5251
|
+
return {
|
|
5252
|
+
...element,
|
|
5253
|
+
openingElement: {
|
|
5254
|
+
...element.openingElement,
|
|
5255
|
+
attributes: [
|
|
5256
|
+
...attributes,
|
|
5257
|
+
b.jsx_attribute(
|
|
5258
|
+
b.jsx_id('key'),
|
|
5259
|
+
to_jsx_expression_container(clone_expression_node(key_expression), key_expression),
|
|
5260
|
+
),
|
|
5261
|
+
],
|
|
5262
|
+
},
|
|
5263
|
+
};
|
|
5116
5264
|
}
|
|
5117
5265
|
|
|
5118
5266
|
/**
|
|
@@ -962,19 +962,12 @@ export function convert_source_map_to_mappings(
|
|
|
962
962
|
const is_method = node.metadata?.is_method;
|
|
963
963
|
|
|
964
964
|
if (node.type === 'ArrowFunctionExpression' && node.loc) {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
node,
|
|
972
|
-
src_to_gen_map,
|
|
973
|
-
gen_line_offsets,
|
|
974
|
-
mapping_data_verify_only,
|
|
975
|
-
),
|
|
976
|
-
);
|
|
977
|
-
}
|
|
965
|
+
// The printer emits node-level boundary markers for arrows (their
|
|
966
|
+
// span can start at a bare `(`), so the strict lookup always
|
|
967
|
+
// resolves — no defensive has() guard.
|
|
968
|
+
mappings.push(
|
|
969
|
+
get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
|
|
970
|
+
);
|
|
978
971
|
}
|
|
979
972
|
|
|
980
973
|
// Add the function keyword token.
|
|
@@ -987,41 +980,68 @@ export function convert_source_map_to_mappings(
|
|
|
987
980
|
const function_hover = create_function_hover_replacement(
|
|
988
981
|
/** @type {AST.Parameter[]} */ (node.params),
|
|
989
982
|
);
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
983
|
+
// Keyword SOURCE spans come from the LEXER (parse-time
|
|
984
|
+
// `tsrx_keyword_tokens`, opt-in via ParseOptions.keywordTokens):
|
|
985
|
+
// no AST node records them, offset arithmetic breaks on extra
|
|
986
|
+
// whitespace, and text search breaks on comments. Fall back to
|
|
987
|
+
// node-start-anchored arithmetic when tokens were not collected.
|
|
988
|
+
const keyword_bound =
|
|
989
|
+
node_fn.id?.start ?? node_fn.params?.[0]?.start ?? node_fn.body?.start ?? node_fn.end;
|
|
990
|
+
/** @type {Array<{ value: string, start: number, end: number, loc: AST.SourceLocation }>} */
|
|
991
|
+
const lexer_tokens = /** @type {any} */ (ast_from_source).tsrx_keyword_tokens ?? [];
|
|
992
|
+
/**
|
|
993
|
+
* @param {'async' | 'function'} keyword
|
|
994
|
+
* @param {number} from
|
|
995
|
+
* @returns {AST.SourceLocation | null}
|
|
996
|
+
*/
|
|
997
|
+
const keyword_loc = (keyword, from) => {
|
|
998
|
+
const token = lexer_tokens.find(
|
|
999
|
+
(candidate) =>
|
|
1000
|
+
candidate.value === keyword &&
|
|
1001
|
+
candidate.start >= from &&
|
|
1002
|
+
candidate.start < keyword_bound,
|
|
1003
|
+
);
|
|
1004
|
+
if (token) return token.loc;
|
|
1005
|
+
if (lexer_tokens.length > 0) return null;
|
|
1006
|
+
// Arithmetic fallback (callers that do not collect tokens):
|
|
1007
|
+
// assumes the historical `async` + one-space + `function`
|
|
1008
|
+
// single-line layout.
|
|
1009
|
+
const offset =
|
|
1010
|
+
keyword === 'function' && node_fn.async
|
|
1011
|
+
? node_fn.start + 'async '.length
|
|
1012
|
+
: node_fn.start;
|
|
1013
|
+
const start_pos = offset_to_line_col(offset, src_line_offsets);
|
|
1014
|
+
const end_pos = offset_to_line_col(offset + keyword.length, src_line_offsets);
|
|
1015
|
+
return { start: start_pos, end: end_pos };
|
|
1016
|
+
};
|
|
993
1017
|
|
|
1018
|
+
let function_from = node_fn.start;
|
|
994
1019
|
if (node_fn.async) {
|
|
995
|
-
|
|
1020
|
+
const async_loc = keyword_loc('async', node_fn.start);
|
|
1021
|
+
if (async_loc) {
|
|
1022
|
+
tokens.push({
|
|
1023
|
+
source: 'async',
|
|
1024
|
+
generated: 'async',
|
|
1025
|
+
loc: async_loc,
|
|
1026
|
+
metadata: {},
|
|
1027
|
+
});
|
|
1028
|
+
function_from = loc_to_offset(
|
|
1029
|
+
async_loc.end.line,
|
|
1030
|
+
async_loc.end.column,
|
|
1031
|
+
src_line_offsets,
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const function_loc = keyword_loc('function', function_from);
|
|
1037
|
+
if (function_loc) {
|
|
996
1038
|
tokens.push({
|
|
997
|
-
source:
|
|
998
|
-
generated:
|
|
999
|
-
loc:
|
|
1000
|
-
|
|
1001
|
-
end: {
|
|
1002
|
-
line: node_fn.loc.start.line,
|
|
1003
|
-
column: start_col + async_keyword.length,
|
|
1004
|
-
},
|
|
1005
|
-
},
|
|
1006
|
-
metadata: {},
|
|
1039
|
+
source: 'function',
|
|
1040
|
+
generated: 'function',
|
|
1041
|
+
loc: function_loc,
|
|
1042
|
+
metadata: function_hover ? { hover: function_hover } : {},
|
|
1007
1043
|
});
|
|
1008
|
-
|
|
1009
|
-
start_col += async_keyword.length + 1; // +1 for space
|
|
1010
|
-
start += async_keyword.length + 1;
|
|
1011
1044
|
}
|
|
1012
|
-
|
|
1013
|
-
tokens.push({
|
|
1014
|
-
source: 'function',
|
|
1015
|
-
generated: 'function',
|
|
1016
|
-
loc: {
|
|
1017
|
-
start: { line: node_fn.loc.start.line, column: start_col },
|
|
1018
|
-
end: {
|
|
1019
|
-
line: node_fn.loc.start.line,
|
|
1020
|
-
column: start_col + 'function'.length,
|
|
1021
|
-
},
|
|
1022
|
-
},
|
|
1023
|
-
metadata: function_hover ? { hover: function_hover } : {},
|
|
1024
|
-
});
|
|
1025
1045
|
}
|
|
1026
1046
|
|
|
1027
1047
|
// Visit in source order: id, params, body
|
package/src/utils/builders.js
CHANGED
|
@@ -251,6 +251,22 @@ export function export_default(declaration) {
|
|
|
251
251
|
return { type: 'ExportDefaultDeclaration', declaration, metadata: { path: [] } };
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
/**
|
|
255
|
+
* @param {string | AST.Identifier} local
|
|
256
|
+
* @param {string | AST.Identifier} [exported]
|
|
257
|
+
* @param {AST.ExportSpecifier['exportKind']} [exportKind]
|
|
258
|
+
* @returns {AST.ExportSpecifier}
|
|
259
|
+
*/
|
|
260
|
+
export function export_specifier(local, exported = local, exportKind = 'value') {
|
|
261
|
+
return {
|
|
262
|
+
type: 'ExportSpecifier',
|
|
263
|
+
local: typeof local === 'string' ? id(local) : local,
|
|
264
|
+
exported: typeof exported === 'string' ? id(exported) : exported,
|
|
265
|
+
exportKind,
|
|
266
|
+
metadata: { path: [] },
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
254
270
|
/**
|
|
255
271
|
* @param {AST.Declaration | null} declaration
|
|
256
272
|
* @param {AST.ExportSpecifier[]} [specifiers]
|
package/types/index.d.ts
CHANGED
|
@@ -230,12 +230,14 @@ declare module 'estree' {
|
|
|
230
230
|
interface MethodDefinition {
|
|
231
231
|
typeParameters?: TSTypeParameterDeclaration;
|
|
232
232
|
accessibility?: Accessibility;
|
|
233
|
+
optional?: boolean;
|
|
233
234
|
}
|
|
234
235
|
|
|
235
236
|
interface PropertyDefinition {
|
|
236
237
|
accessibility?: Accessibility;
|
|
237
238
|
readonly?: boolean;
|
|
238
239
|
optional?: boolean;
|
|
240
|
+
definite?: boolean;
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
interface ClassDeclaration {
|
|
@@ -484,6 +486,9 @@ declare module 'estree' {
|
|
|
484
486
|
interface ExportNamedDeclaration {
|
|
485
487
|
exportKind: TSESTree.ExportNamedDeclaration['exportKind'];
|
|
486
488
|
}
|
|
489
|
+
interface ExportSpecifier {
|
|
490
|
+
exportKind: TSESTree.ExportSpecifier['exportKind'];
|
|
491
|
+
}
|
|
487
492
|
|
|
488
493
|
interface BaseNodeWithoutComments {
|
|
489
494
|
// Adding start, end for now as always there
|
|
@@ -1231,6 +1236,13 @@ export interface ParseError {
|
|
|
1231
1236
|
export interface ParseOptions {
|
|
1232
1237
|
collect?: boolean;
|
|
1233
1238
|
loose?: boolean;
|
|
1239
|
+
preserveParens?: boolean;
|
|
1240
|
+
/**
|
|
1241
|
+
* Collect `async`/`function` keyword tokens from the lexer onto the
|
|
1242
|
+
* returned program (`tsrx_keyword_tokens`) so mapping collection can span
|
|
1243
|
+
* keywords exactly. Volar/typeOnly parses opt in.
|
|
1244
|
+
*/
|
|
1245
|
+
keywordTokens?: boolean;
|
|
1234
1246
|
errors?: CompileError[];
|
|
1235
1247
|
comments?: AST.CommentWithLocation[];
|
|
1236
1248
|
}
|