@tsrx/core 0.1.46 → 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/package.json +1 -1
- package/src/index.js +2 -2
- package/src/parse/index.js +3 -2
- package/src/transform/jsx/ast-builders.js +131 -110
- package/src/transform/jsx/helpers.js +76 -81
- package/src/transform/jsx/index.js +342 -360
- package/src/transform/lazy.js +5 -5
- package/src/transform/segments.js +29 -28
- package/src/transform/style-ref.js +9 -13
- package/src/utils/ast.js +11 -0
- package/src/utils/builders.js +3 -3
- package/types/index.d.ts +86 -11
- package/types/jsx-platform.d.ts +88 -54
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -89,6 +89,7 @@ export {
|
|
|
89
89
|
is_function_node as isFunctionNode,
|
|
90
90
|
is_function_or_class_node as isFunctionOrClassNode,
|
|
91
91
|
is_function_or_component_node as isFunctionOrComponentNode,
|
|
92
|
+
has_location,
|
|
92
93
|
is_inside_component as isInsideComponent,
|
|
93
94
|
is_template_directive as isTemplateDirective,
|
|
94
95
|
is_tsrx_render_output_node as isTsrxRenderOutputNode,
|
|
@@ -167,7 +168,6 @@ export {
|
|
|
167
168
|
plan_switch_lift as planSwitchLift,
|
|
168
169
|
return_value_body_to_expression as returnValueBodyToExpression,
|
|
169
170
|
rewrite_loop_continues_to_bare_returns as rewriteLoopContinuesToBareReturns,
|
|
170
|
-
to_jsx_attribute as toJsxAttribute,
|
|
171
171
|
validate_at_most_one_ref_attribute as validateAtMostOneRefAttribute,
|
|
172
172
|
wrap_edge_whitespace as wrapEdgeWhitespace,
|
|
173
173
|
} from './transform/jsx/index.js';
|
|
@@ -189,7 +189,7 @@ export {
|
|
|
189
189
|
} from './transform/style-ref.js';
|
|
190
190
|
export {
|
|
191
191
|
add_extra_source_mappings_from_matching_expression,
|
|
192
|
-
|
|
192
|
+
clone_ast_node,
|
|
193
193
|
clone_identifier,
|
|
194
194
|
clone_jsx_name,
|
|
195
195
|
contains_component_jsx,
|
package/src/parse/index.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import * as acorn from 'acorn';
|
|
9
9
|
import { tsPlugin } from '@sveltejs/acorn-typescript';
|
|
10
10
|
import { walk } from 'zimmerframe';
|
|
11
|
+
import { has_location } from '../utils/ast.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @typedef {(BaseParser: typeof acorn.Parser) => typeof acorn.Parser} AcornPlugin
|
|
@@ -607,8 +608,8 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
607
608
|
}
|
|
608
609
|
|
|
609
610
|
const ancestorElements = path
|
|
610
|
-
.filter(
|
|
611
|
-
.
|
|
611
|
+
.filter(has_location)
|
|
612
|
+
.filter(isNativeTemplateNode)
|
|
612
613
|
.sort((a, b) => a.loc.start.line - b.loc.start.line);
|
|
613
614
|
|
|
614
615
|
const targetAncestor = ancestorElements.find(
|
|
@@ -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
|
}
|