@tsrx/core 0.1.45 → 0.1.47
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 +6 -1
- package/package.json +1 -1
- package/src/analyze/index.js +177 -0
- package/src/analyze/validation.js +19 -0
- package/src/index.js +12 -2
- package/src/parse/index.js +3 -2
- package/src/plugin.js +3 -104
- package/src/transform/jsx/ast-builders.js +131 -110
- package/src/transform/jsx/helpers.js +76 -81
- package/src/transform/jsx/index.js +346 -390
- package/src/transform/lazy.js +5 -5
- package/src/transform/segments.js +29 -28
- package/src/transform/style-ref.js +10 -27
- package/src/utils/ast.js +101 -1
- package/src/utils/builders.js +3 -3
- package/types/index.d.ts +118 -11
- package/types/jsx-platform.d.ts +88 -54
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
2
|
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import * as b from '../../utils/builders.js';
|
|
5
|
+
import { has_location } from '../../utils/ast.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* AST-building utilities shared across every JSX target (React, Preact,
|
|
@@ -13,15 +14,15 @@ import { set_location } from '../../utils/builders.js';
|
|
|
13
14
|
* Attach `source_node`'s `loc` to `node` (deep), defaulting `node.metadata`
|
|
14
15
|
* so downstream walks / serializers don't trip on it being undefined.
|
|
15
16
|
*
|
|
16
|
-
* @template T
|
|
17
|
+
* @template {AST.Node} T
|
|
17
18
|
* @param {T} node
|
|
18
|
-
* @param {
|
|
19
|
+
* @param {AST.Node | AST.NodeWithLocation | undefined} source_node
|
|
19
20
|
* @returns {T}
|
|
20
21
|
*/
|
|
21
22
|
export function set_loc(node, source_node) {
|
|
22
|
-
|
|
23
|
-
if (source_node
|
|
24
|
-
return
|
|
23
|
+
node.metadata ??= { path: [] };
|
|
24
|
+
if (has_location(source_node)) {
|
|
25
|
+
return b.set_location(node, source_node, true);
|
|
25
26
|
}
|
|
26
27
|
return node;
|
|
27
28
|
}
|
|
@@ -35,97 +36,110 @@ export function set_loc(node, source_node) {
|
|
|
35
36
|
* @returns {AST.Identifier}
|
|
36
37
|
*/
|
|
37
38
|
export function clone_identifier(identifier) {
|
|
38
|
-
return set_loc(
|
|
39
|
-
/** @type {any} */ ({
|
|
40
|
-
type: 'Identifier',
|
|
41
|
-
name: identifier.name,
|
|
42
|
-
metadata: { path: [] },
|
|
43
|
-
}),
|
|
44
|
-
identifier,
|
|
45
|
-
);
|
|
39
|
+
return set_loc(b.id(identifier.name), identifier);
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
/**
|
|
49
43
|
* Clone a JSX element name (handles `JSXIdentifier`, `JSXMemberExpression`,
|
|
50
44
|
* and plain `Identifier`).
|
|
51
45
|
*
|
|
52
|
-
* @param {
|
|
53
|
-
* @param {
|
|
54
|
-
* @returns {
|
|
46
|
+
* @param {ESTreeJSX.TSRXJSXOpeningElement['name']} name
|
|
47
|
+
* @param {AST.Node} [source_node]
|
|
48
|
+
* @returns {ESTreeJSX.TSRXJSXOpeningElement['name']}
|
|
55
49
|
*/
|
|
56
50
|
export function clone_jsx_name(name, source_node = name) {
|
|
57
|
-
if (!name) return name;
|
|
58
51
|
if (name.type === 'JSXIdentifier') {
|
|
59
|
-
return
|
|
60
|
-
/** @type {any} */ ({
|
|
61
|
-
type: 'JSXIdentifier',
|
|
62
|
-
name: name.name,
|
|
63
|
-
metadata: name.metadata || { path: [] },
|
|
64
|
-
}),
|
|
65
|
-
source_node,
|
|
66
|
-
);
|
|
52
|
+
return clone_jsx_identifier(name, source_node);
|
|
67
53
|
}
|
|
68
54
|
if (name.type === 'JSXMemberExpression') {
|
|
69
|
-
return
|
|
70
|
-
/** @type {any} */ ({
|
|
71
|
-
type: 'JSXMemberExpression',
|
|
72
|
-
object: clone_jsx_name(name.object, source_node.object || name.object),
|
|
73
|
-
property: clone_jsx_name(name.property, source_node.property || name.property),
|
|
74
|
-
metadata: name.metadata || { path: [] },
|
|
75
|
-
}),
|
|
76
|
-
source_node,
|
|
77
|
-
);
|
|
55
|
+
return clone_jsx_member_expression(name, source_node);
|
|
78
56
|
}
|
|
79
57
|
if (name.type === 'Identifier') {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
name: name.name,
|
|
84
|
-
metadata: name.metadata || { path: [] },
|
|
85
|
-
}),
|
|
86
|
-
source_node,
|
|
87
|
-
);
|
|
58
|
+
const clone = b.jsx_id(name.name);
|
|
59
|
+
clone.metadata = name.metadata || { path: [] };
|
|
60
|
+
return set_loc(clone, source_node);
|
|
88
61
|
}
|
|
89
62
|
return name;
|
|
90
63
|
}
|
|
91
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @param {ESTreeJSX.JSXIdentifier} name
|
|
67
|
+
* @param {AST.Node} source_node
|
|
68
|
+
* @returns {ESTreeJSX.JSXIdentifier}
|
|
69
|
+
*/
|
|
70
|
+
function clone_jsx_identifier(name, source_node) {
|
|
71
|
+
const clone = b.jsx_id(name.name);
|
|
72
|
+
clone.metadata = name.metadata || { path: [] };
|
|
73
|
+
return set_loc(clone, source_node);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {ESTreeJSX.JSXMemberExpression} name
|
|
78
|
+
* @param {AST.Node} source_node
|
|
79
|
+
* @returns {ESTreeJSX.JSXMemberExpression}
|
|
80
|
+
*/
|
|
81
|
+
function clone_jsx_member_expression(name, source_node) {
|
|
82
|
+
const member_source = source_node.type === 'JSXMemberExpression' ? source_node : name;
|
|
83
|
+
const object =
|
|
84
|
+
name.object.type === 'JSXIdentifier'
|
|
85
|
+
? clone_jsx_identifier(
|
|
86
|
+
name.object,
|
|
87
|
+
member_source.object.type === 'JSXIdentifier' ? member_source.object : name.object,
|
|
88
|
+
)
|
|
89
|
+
: clone_jsx_member_expression(
|
|
90
|
+
name.object,
|
|
91
|
+
member_source.object.type === 'JSXMemberExpression' ? member_source.object : name.object,
|
|
92
|
+
);
|
|
93
|
+
const property = clone_jsx_identifier(name.property, member_source.property);
|
|
94
|
+
const clone = b.jsx_member(object, property);
|
|
95
|
+
clone.metadata = name.metadata || { path: [] };
|
|
96
|
+
return set_loc(clone, source_node);
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
/**
|
|
93
100
|
* Record extra source positions on a generated expression so one generated
|
|
94
101
|
* range can map back to several source ranges. Used for dynamic tags, where
|
|
95
102
|
* the generated `is={expr}` value stands in for both `<{expr}` and `</{expr}>`;
|
|
96
103
|
* segments.js turns each recorded node into an additional mapping token.
|
|
97
|
-
* @param {
|
|
98
|
-
* @param {
|
|
104
|
+
* @param {AST.Node} generated
|
|
105
|
+
* @param {AST.Node | null | undefined} source
|
|
99
106
|
* @returns {void}
|
|
100
107
|
*/
|
|
101
108
|
export function add_extra_source_mappings_from_matching_expression(generated, source) {
|
|
102
109
|
if (!generated || !source || generated.type !== source.type) return;
|
|
103
110
|
|
|
104
111
|
if (generated.type === 'Identifier' || generated.type === 'PrivateIdentifier') {
|
|
105
|
-
if (!source
|
|
112
|
+
if (!has_location(source)) return;
|
|
106
113
|
generated.metadata ??= { path: [] };
|
|
107
114
|
generated.metadata.extra_source_mappings ??= [];
|
|
108
115
|
generated.metadata.extra_source_mappings.push(source);
|
|
109
116
|
return;
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
const generated_node = /** @type {AST.TraversableAstNode} */ (generated);
|
|
120
|
+
const source_node = /** @type {AST.TraversableAstNode} */ (source);
|
|
112
121
|
for (const key of ['expression', 'object', 'property']) {
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
const generated_child = generated_node[key];
|
|
123
|
+
const source_child = source_node[key];
|
|
124
|
+
if (is_ast_node(generated_child) && is_ast_node(source_child)) {
|
|
125
|
+
add_extra_source_mappings_from_matching_expression(generated_child, source_child);
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
|
|
130
|
+
/**
|
|
131
|
+
* @param {unknown} value
|
|
132
|
+
* @returns {value is AST.Node}
|
|
133
|
+
*/
|
|
134
|
+
function is_ast_node(value) {
|
|
135
|
+
return !!value && typeof value === 'object' && 'type' in value;
|
|
136
|
+
}
|
|
137
|
+
|
|
119
138
|
/**
|
|
120
139
|
* @returns {AST.Literal}
|
|
121
140
|
*/
|
|
122
141
|
export function create_null_literal() {
|
|
123
|
-
return
|
|
124
|
-
type: 'Literal',
|
|
125
|
-
value: null,
|
|
126
|
-
raw: 'null',
|
|
127
|
-
metadata: { path: [] },
|
|
128
|
-
});
|
|
142
|
+
return b.literal(null, 'null');
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
/**
|
|
@@ -133,15 +147,11 @@ export function create_null_literal() {
|
|
|
133
147
|
* @returns {AST.Identifier}
|
|
134
148
|
*/
|
|
135
149
|
export function create_generated_identifier(name) {
|
|
136
|
-
return
|
|
137
|
-
type: 'Identifier',
|
|
138
|
-
name,
|
|
139
|
-
metadata: { path: [] },
|
|
140
|
-
});
|
|
150
|
+
return b.id(name);
|
|
141
151
|
}
|
|
142
152
|
|
|
143
153
|
/**
|
|
144
|
-
* @param {
|
|
154
|
+
* @param {AST.BaseNode} node
|
|
145
155
|
* @param {string} message
|
|
146
156
|
* @returns {Error & { pos: number, end: number }}
|
|
147
157
|
*/
|
|
@@ -160,33 +170,35 @@ export function create_compile_error(node, message) {
|
|
|
160
170
|
* component hover label — without that flag those source-map adjustments
|
|
161
171
|
* and editor hover features silently drop for any composite element.
|
|
162
172
|
*
|
|
163
|
-
* @param {
|
|
164
|
-
* @returns {
|
|
173
|
+
* @param {AST.Identifier | AST.MemberExpression} id
|
|
174
|
+
* @returns {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXMemberExpression}
|
|
165
175
|
*/
|
|
166
176
|
export function identifier_to_jsx_name(id) {
|
|
167
|
-
if (!id) return id;
|
|
168
177
|
if (id.type === 'Identifier') {
|
|
169
|
-
return
|
|
170
|
-
/** @type {any} */ ({
|
|
171
|
-
type: 'JSXIdentifier',
|
|
172
|
-
name: id.name,
|
|
173
|
-
metadata: { ...(id.metadata || {}), path: [], is_component: /^[A-Z]/.test(id.name) },
|
|
174
|
-
}),
|
|
175
|
-
id,
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
if (id.type === 'MemberExpression') {
|
|
179
|
-
return set_loc(
|
|
180
|
-
/** @type {any} */ ({
|
|
181
|
-
type: 'JSXMemberExpression',
|
|
182
|
-
object: identifier_to_jsx_name(id.object),
|
|
183
|
-
property: identifier_to_jsx_name(id.property),
|
|
184
|
-
metadata: id.metadata || { path: [] },
|
|
185
|
-
}),
|
|
186
|
-
id,
|
|
187
|
-
);
|
|
178
|
+
return identifier_to_jsx_identifier(id);
|
|
188
179
|
}
|
|
189
|
-
|
|
180
|
+
const object =
|
|
181
|
+
id.object.type === 'Identifier'
|
|
182
|
+
? identifier_to_jsx_name(id.object)
|
|
183
|
+
: identifier_to_jsx_name(/** @type {AST.MemberExpression} */ (id.object));
|
|
184
|
+
const property = identifier_to_jsx_identifier(/** @type {AST.Identifier} */ (id.property));
|
|
185
|
+
const name = b.jsx_member(object, property);
|
|
186
|
+
name.metadata = id.metadata || { path: [] };
|
|
187
|
+
return set_loc(name, id);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {AST.Identifier} id
|
|
192
|
+
* @returns {ESTreeJSX.JSXIdentifier}
|
|
193
|
+
*/
|
|
194
|
+
export function identifier_to_jsx_identifier(id) {
|
|
195
|
+
const name = b.jsx_id(id.name);
|
|
196
|
+
name.metadata = {
|
|
197
|
+
...(id.metadata || {}),
|
|
198
|
+
path: [],
|
|
199
|
+
is_component: /^[A-Z]/.test(id.name),
|
|
200
|
+
};
|
|
201
|
+
return set_loc(name, id);
|
|
190
202
|
}
|
|
191
203
|
|
|
192
204
|
/**
|
|
@@ -199,7 +211,7 @@ export function identifier_to_jsx_name(id) {
|
|
|
199
211
|
* Used by platforms that veto static-hoisting of component JSX (Vue, Solid)
|
|
200
212
|
* and by core's narrower bare-component-invocation predicate.
|
|
201
213
|
*
|
|
202
|
-
* @param {
|
|
214
|
+
* @param {ESTreeJSX.TSRXJSXOpeningElement['name'] | AST.Identifier} name
|
|
203
215
|
* @returns {boolean}
|
|
204
216
|
*/
|
|
205
217
|
export function is_component_jsx_name(name) {
|
|
@@ -227,26 +239,25 @@ export function is_component_jsx_name(name) {
|
|
|
227
239
|
* component instance to module identity, which doesn't help either framework
|
|
228
240
|
* the way it helps React, so it's wasted output.
|
|
229
241
|
*
|
|
230
|
-
* @param {
|
|
242
|
+
* @param {AST.Node | AST.Node[]} node
|
|
231
243
|
* @returns {boolean}
|
|
232
244
|
*/
|
|
233
245
|
export function contains_component_jsx(node) {
|
|
234
246
|
if (!node || typeof node !== 'object') {
|
|
235
247
|
return false;
|
|
236
248
|
}
|
|
237
|
-
|
|
238
|
-
if (node.type === 'JSXElement') {
|
|
249
|
+
if ('type' in node && node.type === 'JSXElement') {
|
|
239
250
|
if (is_component_jsx_name(node.openingElement?.name)) {
|
|
240
251
|
return true;
|
|
241
252
|
}
|
|
242
253
|
return node.children?.some(contains_component_jsx) ?? false;
|
|
243
254
|
}
|
|
244
255
|
|
|
245
|
-
if (node.type === 'JSXFragment') {
|
|
256
|
+
if ('type' in node && node.type === 'JSXFragment') {
|
|
246
257
|
return node.children?.some(contains_component_jsx) ?? false;
|
|
247
258
|
}
|
|
248
259
|
|
|
249
|
-
if (node.type === 'JSXExpressionContainer') {
|
|
260
|
+
if ('type' in node && node.type === 'JSXExpressionContainer') {
|
|
250
261
|
return contains_component_jsx(node.expression);
|
|
251
262
|
}
|
|
252
263
|
|
|
@@ -258,7 +269,7 @@ export function contains_component_jsx(node) {
|
|
|
258
269
|
}
|
|
259
270
|
|
|
260
271
|
/**
|
|
261
|
-
* @param {
|
|
272
|
+
* @param {AST.Node | null | undefined} node
|
|
262
273
|
* @returns {boolean}
|
|
263
274
|
*/
|
|
264
275
|
export function is_jsx_child(node) {
|
|
@@ -287,8 +298,8 @@ export function is_jsx_child(node) {
|
|
|
287
298
|
* the unwrapped expression is still render output rather than an executable
|
|
288
299
|
* statement.
|
|
289
300
|
*
|
|
290
|
-
* @param {
|
|
291
|
-
* @returns {
|
|
301
|
+
* @param {AST.Node | null | undefined} node
|
|
302
|
+
* @returns {node is AST.Expression}
|
|
292
303
|
*/
|
|
293
304
|
export function is_bare_render_expression(node) {
|
|
294
305
|
if (!node || typeof node !== 'object') {
|
|
@@ -335,17 +346,17 @@ export function is_bare_render_expression(node) {
|
|
|
335
346
|
* Gather the params a `for (x of y; index i)` loop should expose to its body
|
|
336
347
|
* JSX (value first, optional index second).
|
|
337
348
|
*
|
|
338
|
-
* @param {
|
|
339
|
-
* @param {
|
|
340
|
-
* @returns {
|
|
349
|
+
* @param {AST.ForOfStatement['left']} left
|
|
350
|
+
* @param {AST.Identifier | null} [index]
|
|
351
|
+
* @returns {AST.Pattern[]}
|
|
341
352
|
*/
|
|
342
353
|
export function get_for_of_iteration_params(left, index) {
|
|
343
|
-
/** @type {
|
|
354
|
+
/** @type {AST.Pattern[]} */
|
|
344
355
|
const params = [];
|
|
345
356
|
if (left?.type === 'VariableDeclaration' && left.declarations?.[0]) {
|
|
346
357
|
params.push(left.declarations[0].id);
|
|
347
358
|
} else {
|
|
348
|
-
params.push(left);
|
|
359
|
+
params.push(/** @type {AST.Pattern} */ (left));
|
|
349
360
|
}
|
|
350
361
|
if (index) {
|
|
351
362
|
params.push(index);
|
|
@@ -359,8 +370,8 @@ export function get_for_of_iteration_params(left, index) {
|
|
|
359
370
|
* under the case. This lets `case` arms use `{ ... }` for readability
|
|
360
371
|
* without the block becoming a fresh scope at the JSX level.
|
|
361
372
|
*
|
|
362
|
-
* @param {
|
|
363
|
-
* @returns {
|
|
373
|
+
* @param {AST.Statement[]} consequent
|
|
374
|
+
* @returns {AST.Statement[]}
|
|
364
375
|
*/
|
|
365
376
|
export function flatten_switch_consequent(consequent) {
|
|
366
377
|
const result = [];
|
|
@@ -404,12 +415,12 @@ function is_static_string_expression(expression) {
|
|
|
404
415
|
* get the ternary because the AST alone can't prove they're non-null strings.
|
|
405
416
|
*
|
|
406
417
|
* @param {AST.Expression} expression
|
|
407
|
-
* @param {
|
|
418
|
+
* @param {AST.Node | AST.NodeWithLocation} [source_node]
|
|
408
419
|
* @returns {AST.Expression}
|
|
409
420
|
*/
|
|
410
421
|
export function to_text_expression(expression, source_node = expression) {
|
|
411
422
|
if (is_static_string_expression(expression)) {
|
|
412
|
-
return set_loc(
|
|
423
|
+
return set_loc(clone_ast_node(expression), source_node);
|
|
413
424
|
}
|
|
414
425
|
return set_loc(
|
|
415
426
|
/** @type {AST.Expression} */ ({
|
|
@@ -417,7 +428,7 @@ export function to_text_expression(expression, source_node = expression) {
|
|
|
417
428
|
test: {
|
|
418
429
|
type: 'BinaryExpression',
|
|
419
430
|
operator: '==',
|
|
420
|
-
left:
|
|
431
|
+
left: clone_ast_node(expression),
|
|
421
432
|
right: create_null_literal(),
|
|
422
433
|
metadata: { path: [] },
|
|
423
434
|
},
|
|
@@ -430,7 +441,7 @@ export function to_text_expression(expression, source_node = expression) {
|
|
|
430
441
|
alternate: {
|
|
431
442
|
type: 'BinaryExpression',
|
|
432
443
|
operator: '+',
|
|
433
|
-
left:
|
|
444
|
+
left: clone_ast_node(expression),
|
|
434
445
|
right: {
|
|
435
446
|
type: 'Literal',
|
|
436
447
|
value: '',
|
|
@@ -448,24 +459,34 @@ export function to_text_expression(expression, source_node = expression) {
|
|
|
448
459
|
/**
|
|
449
460
|
* Deep-clone an AST subtree.
|
|
450
461
|
*
|
|
451
|
-
* @
|
|
462
|
+
* @template T
|
|
463
|
+
* @param {T} node
|
|
452
464
|
* @param {boolean} with_locations
|
|
453
|
-
* @returns {
|
|
465
|
+
* @returns {T}
|
|
454
466
|
*/
|
|
455
|
-
export function
|
|
467
|
+
export function clone_ast_node(node, with_locations = true) {
|
|
456
468
|
if (!node || typeof node !== 'object') return node;
|
|
457
|
-
if (Array.isArray(node))
|
|
458
|
-
|
|
469
|
+
if (Array.isArray(node)) {
|
|
470
|
+
return /** @type {T} */ (node.map((child) => clone_ast_node(child, with_locations)));
|
|
471
|
+
}
|
|
472
|
+
const clone = { ...node };
|
|
473
|
+
const clone_record = /** @type {Record<string, unknown>} */ (clone);
|
|
459
474
|
|
|
460
475
|
for (const key of Object.keys(node)) {
|
|
461
476
|
if (!with_locations && (key === 'loc' || key === 'start' || key === 'end')) {
|
|
477
|
+
delete clone_record[key];
|
|
462
478
|
continue;
|
|
463
479
|
}
|
|
464
480
|
if (key === 'metadata') {
|
|
465
|
-
|
|
481
|
+
const metadata = /** @type {Record<string, unknown>} */ (node).metadata;
|
|
482
|
+
clone_record.metadata =
|
|
483
|
+
metadata && typeof metadata === 'object' ? { ...metadata } : { path: [] };
|
|
466
484
|
continue;
|
|
467
485
|
}
|
|
468
|
-
|
|
486
|
+
clone_record[key] = clone_ast_node(
|
|
487
|
+
/** @type {Record<string, unknown>} */ (node)[key],
|
|
488
|
+
with_locations,
|
|
489
|
+
);
|
|
469
490
|
}
|
|
470
491
|
return clone;
|
|
471
492
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
|
-
/** @import
|
|
2
|
+
/** @import * as ESRap from 'esrap' */
|
|
3
3
|
|
|
4
4
|
import tsx from 'esrap/languages/tsx';
|
|
5
5
|
import { should_preserve_comment, format_comment } from '../../comment-utils.js';
|
|
@@ -12,7 +12,7 @@ import { should_preserve_comment, format_comment } from '../../comment-utils.js'
|
|
|
12
12
|
* transform built around render children — either way a bare expression in a
|
|
13
13
|
* child slot would print as JSX text.
|
|
14
14
|
*
|
|
15
|
-
* @param {
|
|
15
|
+
* @param {AST.Node[]} path
|
|
16
16
|
* @returns {boolean}
|
|
17
17
|
*/
|
|
18
18
|
export function in_jsx_child_context(path) {
|
|
@@ -21,15 +21,14 @@ export function in_jsx_child_context(path) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* @param {
|
|
24
|
+
* @param {AST.Node | null | undefined} node
|
|
25
25
|
* @returns {boolean}
|
|
26
26
|
*/
|
|
27
27
|
export function is_empty_jsx_fragment(node) {
|
|
28
28
|
return (
|
|
29
29
|
node?.type === 'JSXFragment' &&
|
|
30
30
|
!(node.children || []).some(
|
|
31
|
-
(
|
|
32
|
-
child && (child.type !== 'JSXText' || child.value.trim() !== ''),
|
|
31
|
+
(child) => child && (child.type !== 'JSXText' || child.value.trim() !== ''),
|
|
33
32
|
)
|
|
34
33
|
);
|
|
35
34
|
}
|
|
@@ -39,8 +38,8 @@ export function is_empty_jsx_fragment(node) {
|
|
|
39
38
|
* carries its current ancestor path for downstream CSS pruning and mapping
|
|
40
39
|
* helpers.
|
|
41
40
|
*
|
|
42
|
-
* @param {
|
|
43
|
-
* @param {
|
|
41
|
+
* @param {AST.Node} node
|
|
42
|
+
* @param {AST.Node[]} path
|
|
44
43
|
* @returns {void}
|
|
45
44
|
*/
|
|
46
45
|
export function set_node_path_metadata(node, path) {
|
|
@@ -62,7 +61,7 @@ export function set_node_path_metadata(node, path) {
|
|
|
62
61
|
*
|
|
63
62
|
* Shared across all JSX-producing targets (React, Preact, Solid).
|
|
64
63
|
*
|
|
65
|
-
* @returns {
|
|
64
|
+
* @returns {ESRap.Visitors<AST.Node>}
|
|
66
65
|
*/
|
|
67
66
|
/**
|
|
68
67
|
* @param {boolean} [boundary_tokens] Enable esrap's `boundaryTokens` anchors
|
|
@@ -76,16 +75,17 @@ export function set_node_path_metadata(node, path) {
|
|
|
76
75
|
* TS input — dropping a leading pragma changes how the whole file checks.
|
|
77
76
|
*/
|
|
78
77
|
export function tsx_with_ts_locations(boundary_tokens = false, comments = undefined) {
|
|
79
|
-
const base =
|
|
78
|
+
const base = tsx({ boundaryTokens: boundary_tokens });
|
|
79
|
+
const { _: base_visitor, ...base_visitors } = base;
|
|
80
80
|
|
|
81
|
-
const leading_preserved = (/** @type {
|
|
81
|
+
const leading_preserved = (/** @type {AST.Program} */ program) => {
|
|
82
82
|
if (!comments?.length) return [];
|
|
83
83
|
// Injected statements (dynamic-import/try-import prepends) carry no
|
|
84
84
|
// loc; anchor "leading" on the first statement that maps to source,
|
|
85
85
|
// else every preserved comment in the file would hoist to the top.
|
|
86
|
-
const first = program.body
|
|
86
|
+
const first = program.body.find((node) => node.loc);
|
|
87
87
|
return comments.filter(
|
|
88
|
-
(
|
|
88
|
+
(comment) =>
|
|
89
89
|
should_preserve_comment(comment) &&
|
|
90
90
|
(first?.loc == null ||
|
|
91
91
|
(comment.loc &&
|
|
@@ -95,22 +95,7 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
|
|
|
95
95
|
);
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
-
/**
|
|
99
|
-
* @param {any} node
|
|
100
|
-
* @param {any} context
|
|
101
|
-
* @param {any} visitor
|
|
102
|
-
*/
|
|
103
|
-
const wrap_with_locations = (node, context, visitor) => {
|
|
104
|
-
if (!node.loc) {
|
|
105
|
-
visitor(node, context);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
context.location(node.loc.start.line, node.loc.start.column);
|
|
109
|
-
visitor(node, context);
|
|
110
|
-
context.location(node.loc.end.line, node.loc.end.column);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/** @type {Record<string, (node: any, context: any) => void>} */
|
|
98
|
+
/** @type {ESRap.Visitors<AST.Node>} */
|
|
114
99
|
const wrappers = {
|
|
115
100
|
Program: (node, context) => {
|
|
116
101
|
for (const comment of leading_preserved(node)) {
|
|
@@ -119,10 +104,10 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
|
|
|
119
104
|
if (comment.loc) context.location(comment.loc.end.line, comment.loc.end.column);
|
|
120
105
|
context.newline();
|
|
121
106
|
}
|
|
122
|
-
base.Program(node, context);
|
|
107
|
+
/** @type {NonNullable<typeof base.Program>} */ (base.Program)(node, context);
|
|
123
108
|
},
|
|
124
109
|
ArrayPattern: (node, context) => {
|
|
125
|
-
base.ArrayPattern(node, context);
|
|
110
|
+
/** @type {NonNullable<typeof base.ArrayPattern>} */ (base.ArrayPattern)(node, context);
|
|
126
111
|
if (node.typeAnnotation) {
|
|
127
112
|
context.visit(node.typeAnnotation);
|
|
128
113
|
}
|
|
@@ -143,7 +128,7 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
|
|
|
143
128
|
// must fall through to base.Property to preserve their printed form.
|
|
144
129
|
Property: (node, context) => {
|
|
145
130
|
if (!node.method || node.value.type !== 'FunctionExpression') {
|
|
146
|
-
base.Property(node, context);
|
|
131
|
+
/** @type {NonNullable<typeof base.Property>} */ (base.Property)(node, context);
|
|
147
132
|
return;
|
|
148
133
|
}
|
|
149
134
|
const value = node.value;
|
|
@@ -204,58 +189,68 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
|
|
|
204
189
|
context.visit(node.id);
|
|
205
190
|
context.visit(node.body);
|
|
206
191
|
},
|
|
192
|
+
_(node, context, visit) {
|
|
193
|
+
const visit_with_locations = () => {
|
|
194
|
+
if (!LOCATION_WRAPPED_NODE_TYPES.has(node.type) || !node.loc) {
|
|
195
|
+
visit(node);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
context.location(node.loc.start.line, node.loc.start.column);
|
|
199
|
+
visit(node);
|
|
200
|
+
context.location(node.loc.end.line, node.loc.end.column);
|
|
201
|
+
};
|
|
202
|
+
if (base_visitor) {
|
|
203
|
+
base_visitor(node, context, visit_with_locations);
|
|
204
|
+
} else {
|
|
205
|
+
visit_with_locations();
|
|
206
|
+
}
|
|
207
|
+
},
|
|
207
208
|
};
|
|
208
209
|
|
|
209
|
-
|
|
210
|
-
// above in the `wrappers`
|
|
211
|
-
// if there is already a visitor but you still need a mapping
|
|
212
|
-
// on the whole node, only then duplicate it here
|
|
213
|
-
// e.g. JSXOpeningElement is such a case
|
|
214
|
-
for (const type of [
|
|
215
|
-
// JS nodes with boundary positions esrap still cannot map. Keyword
|
|
216
|
-
// writes (if/new/return/for/switch/await) and `boundaryTokens`
|
|
217
|
-
// anchors (brackets, braces, parens, computed/call closers) cover
|
|
218
|
-
// many STARTS, but statement ENDS land on unanchored characters
|
|
219
|
-
// (`;`, a block's `}`), `class` is not a keyword-write, template
|
|
220
|
-
// literals' backticks carry no location, and an arrow's span can
|
|
221
|
-
// start at a bare `(` — so these node-level markers remain the
|
|
222
|
-
// source of both boundaries until esrap can anchor them.
|
|
223
|
-
'ClassDeclaration',
|
|
224
|
-
'ClassExpression',
|
|
225
|
-
'IfStatement',
|
|
226
|
-
'NewExpression',
|
|
227
|
-
'MemberExpression',
|
|
228
|
-
'ObjectExpression',
|
|
229
|
-
'ReturnStatement',
|
|
230
|
-
'ForStatement',
|
|
231
|
-
'ForInStatement',
|
|
232
|
-
'ForOfStatement',
|
|
233
|
-
'TemplateLiteral',
|
|
234
|
-
'AwaitExpression',
|
|
235
|
-
'SwitchStatement',
|
|
236
|
-
'TaggedTemplateExpression',
|
|
237
|
-
'ArrowFunctionExpression',
|
|
238
|
-
// JSX wrapper nodes: esrap writes `<`, `>`, `</`, `{`, `}` without
|
|
239
|
-
// locations, so the opening/closing element's and expression
|
|
240
|
-
// container's start and end don't resolve.
|
|
241
|
-
'JSXOpeningElement',
|
|
242
|
-
'JSXClosingElement',
|
|
243
|
-
'JSXExpressionContainer',
|
|
244
|
-
// TS wrapper nodes with the same issue.
|
|
245
|
-
'TSTypeParameterInstantiation',
|
|
246
|
-
'TSTypeParameterDeclaration',
|
|
247
|
-
'TSTypeParameter',
|
|
248
|
-
]) {
|
|
249
|
-
const visitor = wrappers[type];
|
|
250
|
-
|
|
251
|
-
wrappers[type] = (node, context) => wrap_with_locations(node, context, visitor ?? base[type]);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return { ...base, ...wrappers };
|
|
210
|
+
return { ...base_visitors, ...wrappers };
|
|
255
211
|
}
|
|
256
212
|
|
|
213
|
+
// Be careful when adding visitors that are already defined in `wrappers`.
|
|
214
|
+
// JSXOpeningElement is intentionally in both places: its custom printer still
|
|
215
|
+
// needs a location marker around the whole node.
|
|
216
|
+
const LOCATION_WRAPPED_NODE_TYPES = new Set([
|
|
217
|
+
// JS nodes with boundary positions esrap still cannot map. Keyword
|
|
218
|
+
// writes (if/new/return/for/switch/await) and `boundaryTokens`
|
|
219
|
+
// anchors (brackets, braces, parens, computed/call closers) cover
|
|
220
|
+
// many STARTS, but statement ENDS land on unanchored characters
|
|
221
|
+
// (`;`, a block's `}`), `class` is not a keyword-write, template
|
|
222
|
+
// literals' backticks carry no location, and an arrow's span can
|
|
223
|
+
// start at a bare `(` — so these node-level markers remain the
|
|
224
|
+
// source of both boundaries until esrap can anchor them.
|
|
225
|
+
'ClassDeclaration',
|
|
226
|
+
'ClassExpression',
|
|
227
|
+
'IfStatement',
|
|
228
|
+
'NewExpression',
|
|
229
|
+
'MemberExpression',
|
|
230
|
+
'ObjectExpression',
|
|
231
|
+
'ReturnStatement',
|
|
232
|
+
'ForStatement',
|
|
233
|
+
'ForInStatement',
|
|
234
|
+
'ForOfStatement',
|
|
235
|
+
'TemplateLiteral',
|
|
236
|
+
'AwaitExpression',
|
|
237
|
+
'SwitchStatement',
|
|
238
|
+
'TaggedTemplateExpression',
|
|
239
|
+
'ArrowFunctionExpression',
|
|
240
|
+
// JSX wrapper nodes: esrap writes `<`, `>`, `</`, `{`, `}` without
|
|
241
|
+
// locations, so the opening/closing element's and expression
|
|
242
|
+
// container's start and end don't resolve.
|
|
243
|
+
'JSXOpeningElement',
|
|
244
|
+
'JSXClosingElement',
|
|
245
|
+
'JSXExpressionContainer',
|
|
246
|
+
// TS wrapper nodes with the same issue.
|
|
247
|
+
'TSTypeParameterInstantiation',
|
|
248
|
+
'TSTypeParameterDeclaration',
|
|
249
|
+
'TSTypeParameter',
|
|
250
|
+
]);
|
|
251
|
+
|
|
257
252
|
/**
|
|
258
|
-
* @param {
|
|
253
|
+
* @param {AST.Node | null | undefined} node
|
|
259
254
|
* @returns {boolean}
|
|
260
255
|
*/
|
|
261
256
|
export function is_template_if_node(node) {
|
|
@@ -266,7 +261,7 @@ export function is_template_if_node(node) {
|
|
|
266
261
|
}
|
|
267
262
|
|
|
268
263
|
/**
|
|
269
|
-
* @param {
|
|
264
|
+
* @param {AST.Node | null | undefined} node
|
|
270
265
|
* @returns {boolean}
|
|
271
266
|
*/
|
|
272
267
|
export function is_template_for_of_node(node) {
|
|
@@ -277,7 +272,7 @@ export function is_template_for_of_node(node) {
|
|
|
277
272
|
}
|
|
278
273
|
|
|
279
274
|
/**
|
|
280
|
-
* @param {
|
|
275
|
+
* @param {AST.Node | null | undefined} node
|
|
281
276
|
* @returns {boolean}
|
|
282
277
|
*/
|
|
283
278
|
export function is_template_switch_node(node) {
|
|
@@ -288,7 +283,7 @@ export function is_template_switch_node(node) {
|
|
|
288
283
|
}
|
|
289
284
|
|
|
290
285
|
/**
|
|
291
|
-
* @param {
|
|
286
|
+
* @param {AST.Node | null | undefined} node
|
|
292
287
|
* @returns {boolean}
|
|
293
288
|
*/
|
|
294
289
|
export function is_template_try_node(node) {
|