@tsrx/core 0.1.46 → 0.1.48

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.
@@ -1,7 +1,8 @@
1
1
  /** @import * as AST from 'estree' */
2
2
  /** @import * as ESTreeJSX from 'estree-jsx' */
3
3
 
4
- import { set_location } from '../../utils/builders.js';
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 {any} source_node
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
- /** @type {any} */ (node).metadata ??= { path: [] };
23
- if (source_node?.loc) {
24
- return /** @type {T} */ (set_location(/** @type {any} */ (node), source_node, true));
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 {any} name
53
- * @param {any} [source_node]
54
- * @returns {any}
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 set_loc(
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 set_loc(
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
- return set_loc(
81
- /** @type {any} */ ({
82
- type: 'JSXIdentifier',
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 {any} generated
98
- * @param {any} source
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.loc) return;
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
- if (generated[key] && source[key]) {
114
- add_extra_source_mappings_from_matching_expression(generated[key], source[key]);
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 /** @type {any} */ ({
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 /** @type {any} */ ({
137
- type: 'Identifier',
138
- name,
139
- metadata: { path: [] },
140
- });
150
+ return b.id(name);
141
151
  }
142
152
 
143
153
  /**
144
- * @param {any} node
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 {any} id
164
- * @returns {any}
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 set_loc(
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
- return id;
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 {any} name
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 {any} node
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 {any} node
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 {any} node
291
- * @returns {boolean}
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 {any} left
339
- * @param {any} [index]
340
- * @returns {any[]}
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 {any[]} */
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 {any[]} consequent
363
- * @returns {any[]}
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 {any} [source_node]
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(clone_expression_node(expression), source_node);
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: clone_expression_node(expression),
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: clone_expression_node(expression),
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
- * @param {any} node
462
+ * @template T
463
+ * @param {T} node
452
464
  * @param {boolean} with_locations
453
- * @returns {any}
465
+ * @returns {T}
454
466
  */
455
- export function clone_expression_node(node, with_locations = true) {
467
+ export function clone_ast_node(node, with_locations = true) {
456
468
  if (!node || typeof node !== 'object') return node;
457
- if (Array.isArray(node)) return node.map((child) => clone_expression_node(child, with_locations));
458
- const clone = /** @type {Record<string, any>} */ ({});
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
- clone.metadata = node.metadata ? { ...node.metadata } : { path: [] };
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
- clone[key] = clone_expression_node(node[key], with_locations);
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,8 +1,9 @@
1
1
  /** @import * as AST from 'estree' */
2
- /** @import { Visitors } from 'zimmerframe' */
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';
6
+ import { with_deferred_imports } from '../imports.js';
6
7
 
7
8
  /**
8
9
  * Zimmerframe provides `path` as the ancestor chain. A native template node in
@@ -12,7 +13,7 @@ import { should_preserve_comment, format_comment } from '../../comment-utils.js'
12
13
  * transform built around render children — either way a bare expression in a
13
14
  * child slot would print as JSX text.
14
15
  *
15
- * @param {any[]} path
16
+ * @param {AST.Node[]} path
16
17
  * @returns {boolean}
17
18
  */
18
19
  export function in_jsx_child_context(path) {
@@ -21,15 +22,14 @@ export function in_jsx_child_context(path) {
21
22
  }
22
23
 
23
24
  /**
24
- * @param {any} node
25
+ * @param {AST.Node | null | undefined} node
25
26
  * @returns {boolean}
26
27
  */
27
28
  export function is_empty_jsx_fragment(node) {
28
29
  return (
29
30
  node?.type === 'JSXFragment' &&
30
31
  !(node.children || []).some(
31
- (/** @type {any} */ child) =>
32
- child && (child.type !== 'JSXText' || child.value.trim() !== ''),
32
+ (child) => child && (child.type !== 'JSXText' || child.value.trim() !== ''),
33
33
  )
34
34
  );
35
35
  }
@@ -39,8 +39,8 @@ export function is_empty_jsx_fragment(node) {
39
39
  * carries its current ancestor path for downstream CSS pruning and mapping
40
40
  * helpers.
41
41
  *
42
- * @param {any} node
43
- * @param {any[]} path
42
+ * @param {AST.Node} node
43
+ * @param {AST.Node[]} path
44
44
  * @returns {void}
45
45
  */
46
46
  export function set_node_path_metadata(node, path) {
@@ -62,7 +62,7 @@ export function set_node_path_metadata(node, path) {
62
62
  *
63
63
  * Shared across all JSX-producing targets (React, Preact, Solid).
64
64
  *
65
- * @returns {any}
65
+ * @returns {ESRap.Visitors<AST.Node>}
66
66
  */
67
67
  /**
68
68
  * @param {boolean} [boundary_tokens] Enable esrap's `boundaryTokens` anchors
@@ -76,16 +76,17 @@ export function set_node_path_metadata(node, path) {
76
76
  * TS input — dropping a leading pragma changes how the whole file checks.
77
77
  */
78
78
  export function tsx_with_ts_locations(boundary_tokens = false, comments = undefined) {
79
- const base = /** @type {any} */ (tsx({ boundaryTokens: boundary_tokens }));
79
+ const base = with_deferred_imports(tsx({ boundaryTokens: boundary_tokens }));
80
+ const { _: base_visitor, ...base_visitors } = base;
80
81
 
81
- const leading_preserved = (/** @type {any} */ program) => {
82
+ const leading_preserved = (/** @type {AST.Program} */ program) => {
82
83
  if (!comments?.length) return [];
83
84
  // Injected statements (dynamic-import/try-import prepends) carry no
84
85
  // loc; anchor "leading" on the first statement that maps to source,
85
86
  // else every preserved comment in the file would hoist to the top.
86
- const first = program.body?.find((/** @type {any} */ node) => node.loc);
87
+ const first = program.body.find((node) => node.loc);
87
88
  return comments.filter(
88
- (/** @type {any} */ comment) =>
89
+ (comment) =>
89
90
  should_preserve_comment(comment) &&
90
91
  (first?.loc == null ||
91
92
  (comment.loc &&
@@ -95,22 +96,7 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
95
96
  );
96
97
  };
97
98
 
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>} */
99
+ /** @type {ESRap.Visitors<AST.Node>} */
114
100
  const wrappers = {
115
101
  Program: (node, context) => {
116
102
  for (const comment of leading_preserved(node)) {
@@ -119,10 +105,10 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
119
105
  if (comment.loc) context.location(comment.loc.end.line, comment.loc.end.column);
120
106
  context.newline();
121
107
  }
122
- base.Program(node, context);
108
+ /** @type {NonNullable<typeof base.Program>} */ (base.Program)(node, context);
123
109
  },
124
110
  ArrayPattern: (node, context) => {
125
- base.ArrayPattern(node, context);
111
+ /** @type {NonNullable<typeof base.ArrayPattern>} */ (base.ArrayPattern)(node, context);
126
112
  if (node.typeAnnotation) {
127
113
  context.visit(node.typeAnnotation);
128
114
  }
@@ -143,7 +129,7 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
143
129
  // must fall through to base.Property to preserve their printed form.
144
130
  Property: (node, context) => {
145
131
  if (!node.method || node.value.type !== 'FunctionExpression') {
146
- base.Property(node, context);
132
+ /** @type {NonNullable<typeof base.Property>} */ (base.Property)(node, context);
147
133
  return;
148
134
  }
149
135
  const value = node.value;
@@ -204,58 +190,68 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
204
190
  context.visit(node.id);
205
191
  context.visit(node.body);
206
192
  },
193
+ _(node, context, visit) {
194
+ const visit_with_locations = () => {
195
+ if (!LOCATION_WRAPPED_NODE_TYPES.has(node.type) || !node.loc) {
196
+ visit(node);
197
+ return;
198
+ }
199
+ context.location(node.loc.start.line, node.loc.start.column);
200
+ visit(node);
201
+ context.location(node.loc.end.line, node.loc.end.column);
202
+ };
203
+ if (base_visitor) {
204
+ base_visitor(node, context, visit_with_locations);
205
+ } else {
206
+ visit_with_locations();
207
+ }
208
+ },
207
209
  };
208
210
 
209
- // Be careful when duplicating visitors that are already defined
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 };
211
+ return { ...base_visitors, ...wrappers };
255
212
  }
256
213
 
214
+ // Be careful when adding visitors that are already defined in `wrappers`.
215
+ // JSXOpeningElement is intentionally in both places: its custom printer still
216
+ // needs a location marker around the whole node.
217
+ const LOCATION_WRAPPED_NODE_TYPES = new Set([
218
+ // JS nodes with boundary positions esrap still cannot map. Keyword
219
+ // writes (if/new/return/for/switch/await) and `boundaryTokens`
220
+ // anchors (brackets, braces, parens, computed/call closers) cover
221
+ // many STARTS, but statement ENDS land on unanchored characters
222
+ // (`;`, a block's `}`), `class` is not a keyword-write, template
223
+ // literals' backticks carry no location, and an arrow's span can
224
+ // start at a bare `(` — so these node-level markers remain the
225
+ // source of both boundaries until esrap can anchor them.
226
+ 'ClassDeclaration',
227
+ 'ClassExpression',
228
+ 'IfStatement',
229
+ 'NewExpression',
230
+ 'MemberExpression',
231
+ 'ObjectExpression',
232
+ 'ReturnStatement',
233
+ 'ForStatement',
234
+ 'ForInStatement',
235
+ 'ForOfStatement',
236
+ 'TemplateLiteral',
237
+ 'AwaitExpression',
238
+ 'SwitchStatement',
239
+ 'TaggedTemplateExpression',
240
+ 'ArrowFunctionExpression',
241
+ // JSX wrapper nodes: esrap writes `<`, `>`, `</`, `{`, `}` without
242
+ // locations, so the opening/closing element's and expression
243
+ // container's start and end don't resolve.
244
+ 'JSXOpeningElement',
245
+ 'JSXClosingElement',
246
+ 'JSXExpressionContainer',
247
+ // TS wrapper nodes with the same issue.
248
+ 'TSTypeParameterInstantiation',
249
+ 'TSTypeParameterDeclaration',
250
+ 'TSTypeParameter',
251
+ ]);
252
+
257
253
  /**
258
- * @param {any} node
254
+ * @param {AST.Node | null | undefined} node
259
255
  * @returns {boolean}
260
256
  */
261
257
  export function is_template_if_node(node) {
@@ -266,7 +262,7 @@ export function is_template_if_node(node) {
266
262
  }
267
263
 
268
264
  /**
269
- * @param {any} node
265
+ * @param {AST.Node | null | undefined} node
270
266
  * @returns {boolean}
271
267
  */
272
268
  export function is_template_for_of_node(node) {
@@ -277,7 +273,7 @@ export function is_template_for_of_node(node) {
277
273
  }
278
274
 
279
275
  /**
280
- * @param {any} node
276
+ * @param {AST.Node | null | undefined} node
281
277
  * @returns {boolean}
282
278
  */
283
279
  export function is_template_switch_node(node) {
@@ -288,7 +284,7 @@ export function is_template_switch_node(node) {
288
284
  }
289
285
 
290
286
  /**
291
- * @param {any} node
287
+ * @param {AST.Node | null | undefined} node
292
288
  * @returns {boolean}
293
289
  */
294
290
  export function is_template_try_node(node) {