@tsrx/core 0.0.1

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.
@@ -0,0 +1,2140 @@
1
+ /**
2
+ @import * as AST from 'estree';
3
+ @import * as ESTreeJSX from 'estree-jsx';
4
+ @import { DocumentHighlightKind } from 'vscode-languageserver-types';
5
+ @import { RawSourceMap } from 'source-map';
6
+ @import {
7
+ CustomMappingData,
8
+ PluginActionOverrides,
9
+ CodeMapping,
10
+ VolarMappingsResult,
11
+ PostProcessingChanges,
12
+ } from '../../types/index';
13
+ @import { CodeMapping as VolarCodeMapping } from '@volar/language-core';
14
+ */
15
+
16
+ /**
17
+ @typedef {{
18
+ start: number,
19
+ end: number,
20
+ content: string,
21
+ id: string,
22
+ }} CssSourceRegion;
23
+ @typedef {{
24
+ source: string | null | undefined;
25
+ generated: string;
26
+ loc: AST.SourceLocation;
27
+ metadata: PluginActionOverrides;
28
+ end_loc?: AST.SourceLocation;
29
+ mappingData?: Partial<VolarCodeMapping['data']>;
30
+ }} Token;
31
+ @typedef {{
32
+ name: string,
33
+ line: number,
34
+ column: number,
35
+ offset: number,
36
+ length: number,
37
+ sourceOffset: number,
38
+ }} TokenClass;
39
+ */
40
+
41
+ /** @typedef {Map<string, { scopedClasses: Map<string, { start: number; end: number; selector: any }>; hash: string } | undefined>} CssElementInfo */
42
+
43
+ import { walk } from 'zimmerframe';
44
+ import {
45
+ build_src_to_gen_map,
46
+ get_generated_position,
47
+ offset_to_line_col,
48
+ loc_to_offset,
49
+ mapping_data,
50
+ mapping_data_verify_only,
51
+ mapping_data_verify_complete,
52
+ build_line_offsets,
53
+ get_mapping_from_node,
54
+ } from '../source-map-utils.js';
55
+
56
+ const LABEL_TO_COMPONENT_REPLACE_REGEX = /(function|\((property|method)\))/;
57
+
58
+ /**
59
+ * @param {string} content
60
+ * @returns {string}
61
+ */
62
+ function replace_label_to_component(content) {
63
+ return content.replace(LABEL_TO_COMPONENT_REPLACE_REGEX, (_, fn, kind) => {
64
+ if (fn === 'function') return 'component';
65
+ return `(component ${kind})`;
66
+ });
67
+ }
68
+
69
+ /**
70
+ * @param {string} [hash]
71
+ * @param {string} [fallback]
72
+ * @returns `style-${hash | fallback}`
73
+ */
74
+ function get_style_region_id(hash, fallback) {
75
+ return `style-${hash || fallback}`;
76
+ }
77
+
78
+ /**
79
+ * Extract CSS source regions from style elements in the AST
80
+ * @param {AST.Node} ast - The parsed AST
81
+ * @param {number[]} src_line_offsets
82
+ * @param {{
83
+ * regions: CssSourceRegion[],
84
+ * css_element_info: CssElementInfo,
85
+ * }} param2
86
+ * @returns {void}
87
+ */
88
+ function visit_source_ast(ast, src_line_offsets, { regions, css_element_info }) {
89
+ let region_id = 0;
90
+ walk(ast, null, {
91
+ Element(node, context) {
92
+ // Check if this is a style element with CSS content
93
+ if (node.id?.type === 'Identifier' && node.id?.name === 'style' && node.css) {
94
+ const openLoc = /** @type {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} */ (
95
+ node.openingElement
96
+ ).loc;
97
+ const cssStart = loc_to_offset(openLoc.end.line, openLoc.end.column, src_line_offsets);
98
+
99
+ const closeLoc = /** @type {ESTreeJSX.JSXClosingElement & AST.NodeWithLocation} */ (
100
+ node.closingElement
101
+ ).loc;
102
+ const cssEnd = loc_to_offset(closeLoc.start.line, closeLoc.start.column, src_line_offsets);
103
+
104
+ regions.push({
105
+ start: cssStart,
106
+ end: cssEnd,
107
+ content: node.css,
108
+ id: get_style_region_id(node.metadata.styleScopeHash, `head-${region_id}`),
109
+ });
110
+ }
111
+
112
+ context.next();
113
+ },
114
+ Attribute(node, context) {
115
+ const element = context.path?.find((n) => n.type === 'Element');
116
+ if (element?.metadata?.css?.scopedClasses) {
117
+ // we don't need to check is_element_dom_element(node)
118
+ // since scopedClasses are added during pruning only to DOM elements
119
+ const css = element.metadata.css;
120
+ const { line, column } = node.value?.loc?.start ?? {};
121
+
122
+ if (line === undefined || column === undefined) {
123
+ return;
124
+ }
125
+
126
+ css_element_info.set(`${line}:${column}`, css);
127
+ }
128
+ },
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Extract individual class names and their offsets from class attribute values
134
+ * Handles: "foo bar", { foo: true }, ['foo', { bar: true }], etc.
135
+ *
136
+ * @param {AST.Node} node - The attribute value node
137
+ * @param {ReturnType<typeof build_src_to_gen_map>[0]} src_to_gen_map
138
+ * @param {number[]} gen_line_offsets
139
+ * @param {number[]} src_line_offsets
140
+ * @returns {TokenClass[]}
141
+ */
142
+ function extract_classes(node, src_to_gen_map, gen_line_offsets, src_line_offsets) {
143
+ /** @type {TokenClass[]} */
144
+ const classes = [];
145
+
146
+ switch (node.type) {
147
+ case 'Literal': {
148
+ // Static: class="foo bar baz"
149
+
150
+ const content = node.raw ?? '';
151
+ let text = content;
152
+ let textOffset = 0;
153
+
154
+ // Remove quotes
155
+ if (
156
+ (content.startsWith(`'`) && content.endsWith(`'`)) ||
157
+ (content.startsWith(`"`) && content.endsWith(`"`)) ||
158
+ (content.startsWith('`') && content.endsWith('`'))
159
+ ) {
160
+ text = content.slice(1, -1);
161
+ textOffset = 1;
162
+ }
163
+
164
+ // Split by whitespace
165
+ const classNames = text.split(/\s+/).filter((c) => c.length > 0);
166
+ const nodeSrcStart = /** @type {AST.Position} */ (node.loc?.start);
167
+
168
+ let currentPos = 0;
169
+ const nodeGenStart = get_generated_position(
170
+ nodeSrcStart.line,
171
+ nodeSrcStart.column,
172
+ src_to_gen_map,
173
+ );
174
+ const offset = loc_to_offset(nodeGenStart.line, nodeGenStart.column, gen_line_offsets);
175
+ const sourceOffset = loc_to_offset(nodeSrcStart.line, nodeSrcStart.column, src_line_offsets);
176
+
177
+ for (const name of classNames) {
178
+ const classStart = text.indexOf(name, currentPos);
179
+ const classOffset = offset + textOffset + classStart;
180
+ const classSourceOffset = sourceOffset + textOffset + classStart;
181
+ const { line, column } = offset_to_line_col(classOffset, gen_line_offsets);
182
+
183
+ classes.push({
184
+ name,
185
+ line,
186
+ column,
187
+ offset: classOffset,
188
+ length: name.length,
189
+ sourceOffset: classSourceOffset,
190
+ });
191
+
192
+ currentPos = classStart + name.length;
193
+ }
194
+ break;
195
+ }
196
+
197
+ case 'ObjectExpression': {
198
+ // Dynamic: class={{ foo: true, bar: @show }}
199
+ for (const prop of node.properties) {
200
+ if (prop.type === 'Property' && prop.key) {
201
+ const key = prop.key;
202
+ if (key.type === 'Identifier' && key.name && key.loc) {
203
+ const nodeSrcStart = /** @type {AST.Position} */ (key.loc?.start);
204
+ const nodeGenStart = get_generated_position(
205
+ nodeSrcStart.line,
206
+ nodeSrcStart.column,
207
+ src_to_gen_map,
208
+ );
209
+ const offset = loc_to_offset(nodeGenStart.line, nodeGenStart.column, gen_line_offsets);
210
+ const sourceOffset = loc_to_offset(
211
+ nodeSrcStart.line,
212
+ nodeSrcStart.column,
213
+ src_line_offsets,
214
+ );
215
+ const { line, column } = offset_to_line_col(offset, gen_line_offsets);
216
+
217
+ classes.push({
218
+ name: key.name,
219
+ line,
220
+ column,
221
+ offset,
222
+ length: key.name.length,
223
+ sourceOffset,
224
+ });
225
+ }
226
+ }
227
+ }
228
+ break;
229
+ }
230
+
231
+ case 'ArrayExpression': {
232
+ // Dynamic: class={['foo', { bar: true }]}
233
+ for (const el of node.elements) {
234
+ if (el) {
235
+ classes.push(...extract_classes(el, src_to_gen_map, gen_line_offsets, src_line_offsets));
236
+ }
237
+ }
238
+ break;
239
+ }
240
+
241
+ case 'ConditionalExpression': {
242
+ // Conditional: class={@show ? 'active' : 'inactive'}
243
+ if (node.consequent) {
244
+ classes.push(
245
+ ...extract_classes(node.consequent, src_to_gen_map, gen_line_offsets, src_line_offsets),
246
+ );
247
+ }
248
+ if (node.alternate) {
249
+ classes.push(
250
+ ...extract_classes(node.alternate, src_to_gen_map, gen_line_offsets, src_line_offsets),
251
+ );
252
+ }
253
+ break;
254
+ }
255
+
256
+ case 'LogicalExpression': {
257
+ // Logical: class={[@show && 'active']}
258
+ if (node.operator === '&&' && node.right) {
259
+ classes.push(
260
+ ...extract_classes(node.right, src_to_gen_map, gen_line_offsets, src_line_offsets),
261
+ );
262
+ } else if (node.operator === '||') {
263
+ if (node.left) {
264
+ classes.push(
265
+ ...extract_classes(node.left, src_to_gen_map, gen_line_offsets, src_line_offsets),
266
+ );
267
+ }
268
+ if (node.right) {
269
+ classes.push(
270
+ ...extract_classes(node.right, src_to_gen_map, gen_line_offsets, src_line_offsets),
271
+ );
272
+ }
273
+ }
274
+ break;
275
+ }
276
+ }
277
+
278
+ return classes;
279
+ }
280
+
281
+ /**
282
+ * Create Volar mappings by walking the transformed AST
283
+ * @param {AST.Node} ast - The transformed AST
284
+ * @param {AST.Node} ast_from_source - The original AST from source
285
+ * @param {string} source - Original source code
286
+ * @param {string} generated_code - Generated code (returned in output, not used for searching)
287
+ * @param {RawSourceMap} source_map - Esrap source map for accurate position lookup
288
+ * @param {PostProcessingChanges } post_processing_changes - Optional post-processing changes
289
+ * @param {number[]} line_offsets - Pre-computed line offsets array for generated code
290
+ * @returns {Omit<VolarMappingsResult, 'errors'>}
291
+ */
292
+ export function convert_source_map_to_mappings(
293
+ ast,
294
+ ast_from_source,
295
+ source,
296
+ generated_code,
297
+ source_map,
298
+ post_processing_changes,
299
+ line_offsets,
300
+ ) {
301
+ /** @type {CodeMapping[]} */
302
+ const mappings = [];
303
+ let isImportDeclarationPresent = false;
304
+
305
+ const src_line_offsets = build_line_offsets(source);
306
+ const gen_line_offsets = build_line_offsets(generated_code);
307
+
308
+ const [src_to_gen_map] = build_src_to_gen_map(
309
+ source_map,
310
+ post_processing_changes,
311
+ line_offsets,
312
+ generated_code,
313
+ );
314
+
315
+ /** @type {Token[]} */
316
+ const tokens = [];
317
+ /** @type {CssSourceRegion[]} */
318
+ const css_regions = [];
319
+ /** @type {CssElementInfo} */
320
+ const css_element_info = new Map();
321
+
322
+ visit_source_ast(ast_from_source, src_line_offsets, {
323
+ regions: css_regions,
324
+ css_element_info,
325
+ });
326
+
327
+ /**
328
+ * Needed for a mapping that includes the computed brackets for diagnostics
329
+ * @param {AST.MethodDefinition | AST.Property} node
330
+ * @param {CodeMapping[]} mappings
331
+ * @returns {void}
332
+ */
333
+ function set_bracket_computed_mapping(node, mappings) {
334
+ if (node.loc) {
335
+ const key = /** @type {typeof node.key & AST.NodeWithLocation} */ (node.key);
336
+ mappings.push(
337
+ get_mapping_from_node(
338
+ /** @type {AST.NodeWithLocation} */ ({
339
+ start: key.start - 1,
340
+ end: key.end + 1,
341
+ loc: {
342
+ start: { line: key.loc.start.line, column: key.loc.start.column - 1 },
343
+ end: { line: key.loc.end.line, column: key.loc.end.column + 1 },
344
+ },
345
+ }),
346
+ src_to_gen_map,
347
+ gen_line_offsets,
348
+ mapping_data_verify_only,
349
+ ),
350
+ );
351
+ }
352
+ }
353
+
354
+ /**
355
+ * @typedef {AST.MethodDefinition & {value: {metadata: {is_component: true}}}} MethodIsComponent
356
+ * @typedef {AST.Property & {value: AST.FunctionExpression, method: true} & {value: {metadata: {is_component: true}}}} PropertyIsComponent
357
+ */
358
+
359
+ /**
360
+ * Maps `component` to the identifier's location
361
+ * e.g. const obj = { component something() { } }
362
+ * since there is no function keyword in source maps
363
+ * @param {MethodIsComponent | PropertyIsComponent} node
364
+ * @returns {void}
365
+ */
366
+ function set_component_mapping_to_name(node) {
367
+ if (node.key.loc) {
368
+ /** @type {CodeMapping} */
369
+ let mapping;
370
+ let start = /** @type {AST.NodeWithLocation} */ (node).start;
371
+ let length = 'component'.length;
372
+
373
+ if (node.value.type === 'FunctionExpression' && node.value.id) {
374
+ const id = /** @type {AST.Identifier & AST.NodeWithLocation} */ (node.value.id);
375
+ mapping = get_mapping_from_node(id, src_to_gen_map, gen_line_offsets);
376
+ } else {
377
+ // e.g. key is computed or literal
378
+ mapping = get_mapping_from_node(node.key, src_to_gen_map, gen_line_offsets);
379
+ }
380
+
381
+ // overwrite source start and length to point to 'component' keyword
382
+ mapping.sourceOffsets = [start];
383
+ mapping.lengths = [length];
384
+ mapping.data.customData.hover = replace_label_to_component;
385
+
386
+ mappings.push(mapping);
387
+ }
388
+ }
389
+
390
+ /**
391
+ * @param {AST.Literal} node
392
+ * @param {boolean} [is_component]
393
+ */
394
+ function handle_literal(node, is_component = false) {
395
+ if (node.loc) {
396
+ const mapping = get_mapping_from_node(node, src_to_gen_map, gen_line_offsets);
397
+
398
+ if (is_component) {
399
+ mapping.data.customData.hover = replace_label_to_component;
400
+ }
401
+
402
+ mappings.push(mapping);
403
+ }
404
+ }
405
+
406
+ // We have to visit everything in generated order to maintain correct indices
407
+
408
+ walk(ast, null, {
409
+ _(node, { visit }) {
410
+ // Collect key node types: Identifiers, Literals, and JSX Elements
411
+ if (node.type === 'Identifier') {
412
+ // Only create mappings for identifiers with location info (from source)
413
+ // Synthesized identifiers (created by builders) don't have .loc and are skipped
414
+ if (node.name && node.loc) {
415
+ /** @type {Token} */
416
+ let token;
417
+ // Check if this identifier was changed in metadata (e.g., #Map -> RippleMap)
418
+ // Or if it was capitalized during transformation
419
+ if (node.metadata?.source_name) {
420
+ token = {
421
+ source: node.metadata.source_name,
422
+ generated: node.name,
423
+ loc: node.loc,
424
+ metadata: {},
425
+ };
426
+ } else {
427
+ token = {
428
+ source: node.name,
429
+ generated: node.name,
430
+ loc: node.loc,
431
+ metadata: {},
432
+ };
433
+ }
434
+
435
+ if (node.metadata?.is_component) {
436
+ // only if the node has a component as the parent
437
+ token.metadata.hover = replace_label_to_component;
438
+ }
439
+ tokens.push(token);
440
+ }
441
+ return; // Leaf node, don't traverse further
442
+ } else if (node.type === 'JSXIdentifier') {
443
+ // JSXIdentifiers can also be capitalized (for dynamic components)
444
+ if (node.loc && node.name) {
445
+ if (node.metadata?.is_capitalized) {
446
+ tokens.push({
447
+ source: node.metadata.source_name,
448
+ generated: node.name,
449
+ loc: node.loc,
450
+ metadata: {},
451
+ });
452
+ } else {
453
+ tokens.push({ source: node.name, generated: node.name, loc: node.loc, metadata: {} });
454
+ }
455
+ }
456
+ return; // Leaf node, don't traverse further
457
+ } else if (node.type === 'Literal') {
458
+ handle_literal(node);
459
+ return; // Leaf node, don't traverse further
460
+ } else if (node.type === 'ImportDeclaration') {
461
+ isImportDeclarationPresent = true;
462
+
463
+ // Add 'import' keyword token to anchor statement-level diagnostics
464
+ // And the last character of the statement (semicolon or closing brace)
465
+ // (e.g., when ALL imports are unused, TS reports on the whole statement)
466
+ // We only map the 'import' and the last character
467
+ // to avoid overlapping with individual specifier mappings
468
+ // which would interfere when only SOME imports are unused.
469
+ if (node.loc) {
470
+ tokens.push({
471
+ source: 'import',
472
+ generated: 'import',
473
+ loc: {
474
+ start: node.loc.start,
475
+ end: {
476
+ line: node.loc.start.line,
477
+ column: node.loc.start.column + 'import'.length,
478
+ },
479
+ },
480
+ metadata: {},
481
+ });
482
+
483
+ tokens.push({
484
+ source:
485
+ source[loc_to_offset(node.loc.end.line, node.loc.end.column - 1, src_line_offsets)],
486
+ // we always add `;' in the generated import
487
+ generated: ';',
488
+ loc: {
489
+ start: {
490
+ line: node.loc.end.line,
491
+ column: node.loc.end.column - 1,
492
+ },
493
+ end: node.loc.end,
494
+ },
495
+ metadata: {},
496
+ });
497
+ }
498
+
499
+ // Visit specifiers in source order
500
+ if (node.specifiers) {
501
+ for (const specifier of node.specifiers) {
502
+ visit(specifier);
503
+ }
504
+ }
505
+ visit(node.source);
506
+ return;
507
+ } else if (node.type === 'ImportSpecifier') {
508
+ // If local and imported are the same, only visit local to avoid duplicates
509
+ // Otherwise visit both in order
510
+ if (
511
+ node.imported &&
512
+ node.local &&
513
+ /** @type {AST.Identifier} */ (node.imported).name !== node.local.name
514
+ ) {
515
+ visit(node.imported);
516
+ visit(node.local);
517
+ } else if (node.local) {
518
+ visit(node.local);
519
+ }
520
+ return;
521
+ } else if (
522
+ node.type === 'ImportDefaultSpecifier' ||
523
+ node.type === 'ImportNamespaceSpecifier'
524
+ ) {
525
+ // Just visit local
526
+ if (node.local) {
527
+ visit(node.local);
528
+ }
529
+ return;
530
+ } else if (node.type === 'ExportSpecifier') {
531
+ // If local and exported are the same, only visit local to avoid duplicates
532
+ // Otherwise visit both in order
533
+ if (
534
+ node.local &&
535
+ node.exported &&
536
+ /** @type {AST.Identifier} */ (node.local).name !==
537
+ /** @type {AST.Identifier} */ (node.exported).name
538
+ ) {
539
+ visit(node.local);
540
+ visit(node.exported);
541
+ } else if (node.local) {
542
+ visit(node.local);
543
+ }
544
+ return;
545
+ } else if (node.type === 'ExportNamedDeclaration') {
546
+ if (node.specifiers && node.specifiers.length > 0) {
547
+ for (const specifier of node.specifiers) {
548
+ visit(specifier);
549
+ }
550
+ }
551
+ if (node.declaration) {
552
+ // The declaration will be visited with proper ordering
553
+ visit(node.declaration);
554
+ }
555
+ return;
556
+ } else if (node.type === 'ExportDefaultDeclaration') {
557
+ // Visit the declaration
558
+ if (node.declaration) {
559
+ visit(/** @type {AST.Node} */ (node.declaration));
560
+ }
561
+ return;
562
+ } else if (node.type === 'ExportAllDeclaration') {
563
+ // Nothing to visit (just source string)
564
+ return;
565
+ } else if (node.type === 'JSXOpeningElement') {
566
+ // Visit name and attributes in source order
567
+ visit(node.name);
568
+ for (const attr of node.attributes) {
569
+ visit(attr);
570
+ }
571
+ return;
572
+ } else if (node.type === 'JSXClosingElement') {
573
+ visit(node.name);
574
+ return;
575
+ } else if (node.type === 'JSXAttribute') {
576
+ // Visit name and value in source order
577
+ // For shorthand attributes ({ count }), key and value are the same node, only visit once
578
+ if (node.shorthand) {
579
+ if (node.value) {
580
+ visit(node.value);
581
+ }
582
+ } else {
583
+ const attr =
584
+ node.name.name === 'class' && node.value?.type === 'JSXExpressionContainer'
585
+ ? node.value.expression
586
+ : node.value;
587
+
588
+ const css = attr
589
+ ? css_element_info.get(`${attr.loc?.start.line}:${attr.loc?.start.column}`)
590
+ : null;
591
+
592
+ if (attr && css) {
593
+ // Extract class names from the attribute value
594
+ const classes = extract_classes(
595
+ attr,
596
+ src_to_gen_map,
597
+ gen_line_offsets,
598
+ src_line_offsets,
599
+ );
600
+
601
+ // For each class name, look up CSS location and create token
602
+ for (const { name, line, column, offset, sourceOffset, length } of classes) {
603
+ const cssLocation = css.scopedClasses.get(name);
604
+
605
+ if (!cssLocation) {
606
+ continue;
607
+ }
608
+
609
+ mappings.push({
610
+ sourceOffsets: [sourceOffset],
611
+ generatedOffsets: [offset],
612
+ lengths: [length],
613
+ generatedLengths: [length],
614
+ data: {
615
+ ...mapping_data,
616
+ customData: {
617
+ hover:
618
+ '```css\n.' +
619
+ name +
620
+ '\n```\n\nCSS class selector.\n\nUse **Cmd+Click** (macOS) or **Ctrl+Click** (Windows/Linux) to navigate to its definition.',
621
+ definition: {
622
+ description: `CSS class selector for '.${name}'`,
623
+ location: {
624
+ embeddedId: get_style_region_id(css.hash),
625
+ start: cssLocation.start,
626
+ end: cssLocation.end,
627
+ },
628
+ },
629
+ },
630
+ },
631
+ });
632
+ }
633
+ } else {
634
+ if (node.name) {
635
+ visit(node.name);
636
+ }
637
+
638
+ if (
639
+ node.name.type === 'JSXIdentifier' &&
640
+ node.name.metadata?.is_component &&
641
+ node.name.loc
642
+ ) {
643
+ const mapping = get_mapping_from_node(
644
+ node.name,
645
+ src_to_gen_map,
646
+ gen_line_offsets,
647
+ mapping_data,
648
+ );
649
+ mapping.sourceOffsets = [
650
+ /** @type {AST.NodeWithLocation} */ (node.name).start - 'component '.length,
651
+ ];
652
+ mapping.lengths = ['component'.length];
653
+
654
+ mapping.data.customData.hover = replace_label_to_component;
655
+ mappings.push(mapping);
656
+ }
657
+ if (node.value) {
658
+ visit(node.value);
659
+ }
660
+ }
661
+ }
662
+ return;
663
+ } else if (node.type === 'JSXSpreadAttribute') {
664
+ // Visit the spread argument
665
+ if (node.argument) {
666
+ visit(node.argument);
667
+ }
668
+ return;
669
+ } else if (node.type === 'JSXExpressionContainer') {
670
+ if (node.loc) {
671
+ mappings.push(
672
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
673
+ );
674
+ }
675
+ // Visit the expression inside {}
676
+ if (node.expression) {
677
+ visit(node.expression);
678
+ }
679
+ return;
680
+ } else if (node.type === 'JSXText') {
681
+ // Text content, no tokens to collect
682
+ return;
683
+ } else if (node.type === 'JSXElement') {
684
+ // Manually visit in source order: opening element, children, closing element
685
+
686
+ // 1. Visit opening element (name and attributes)
687
+ // Add tokens for '<' and '>' brackets to ensure auto-close feature works
688
+ const opening = node.openingElement;
689
+ const closing = node.closingElement;
690
+
691
+ if (opening.loc) {
692
+ // Add tokens for '<' and '>' brackets to ensure auto-close feature works
693
+ tokens.push({
694
+ source: '<',
695
+ generated: '<',
696
+ loc: {
697
+ start: { line: opening.loc.start.line, column: opening.loc.start.column },
698
+ end: { line: opening.loc.start.line, column: opening.loc.start.column + 1 },
699
+ },
700
+ metadata: {},
701
+ mappingData: mapping_data_verify_only,
702
+ });
703
+
704
+ if (!opening.selfClosing) {
705
+ tokens.push({
706
+ source: '>',
707
+ generated: '>',
708
+ loc: {
709
+ start: { line: opening.loc.end.line, column: opening.loc.end.column - 1 },
710
+ end: { line: opening.loc.end.line, column: opening.loc.end.column },
711
+ },
712
+ metadata: {},
713
+ // we need the completion only on the closing tag `>`
714
+ // to cause the closing tag to be auto-added
715
+ mappingData: mapping_data_verify_complete,
716
+ });
717
+ }
718
+ }
719
+
720
+ visit(opening);
721
+
722
+ // 2. Visit children in order
723
+ if (node.children) {
724
+ for (const child of node.children) {
725
+ visit(/** @type {AST.Node} */ (child));
726
+ }
727
+ }
728
+
729
+ if (closing || opening.selfClosing) {
730
+ // Add the whole closing tag or the self-closing
731
+ const mapping = get_mapping_from_node(
732
+ closing ? closing : opening,
733
+ src_to_gen_map,
734
+ gen_line_offsets,
735
+ mapping_data_verify_only,
736
+ );
737
+
738
+ // The generated code includes a semicolon after the closing or self-closed tag
739
+ // We're extending the mapping to include the semicolon
740
+ // because the diagnostics errors can include the whole element
741
+ // and we need to account for the semicolon as it's a part of the diagnostic
742
+ // At the same time, we could've instead applied this logic to the whole `node` element
743
+ // but since we already map the opening - start, we just need the proper end
744
+ // and it was causing some issues with mappings
745
+ mapping.generatedLengths = [mapping.generatedLengths[0] + 1];
746
+ mappings.push(mapping);
747
+ }
748
+
749
+ if (closing) {
750
+ visit(closing);
751
+ }
752
+
753
+ return;
754
+ } else if (
755
+ node.type === 'FunctionDeclaration' ||
756
+ node.type === 'FunctionExpression' ||
757
+ node.type === 'ArrowFunctionExpression'
758
+ ) {
759
+ const is_method = node.metadata?.is_method;
760
+ // Add function/component keyword token
761
+ if (
762
+ (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') &&
763
+ !is_method
764
+ ) {
765
+ const node_fn = /** @type (typeof node) & AST.NodeWithLocation */ (node);
766
+ const is_component = node_fn.metadata?.is_component;
767
+ const source_func_keyword = is_component ? 'component' : 'function';
768
+ let start_col = node_fn.loc.start.column;
769
+ let start = node_fn.start;
770
+ const async_keyword = 'async';
771
+
772
+ if (node_fn.async) {
773
+ // We explicitly mapped async and function in esrap
774
+ tokens.push({
775
+ source: async_keyword,
776
+ generated: async_keyword,
777
+ loc: {
778
+ start: { line: node_fn.loc.start.line, column: start_col },
779
+ end: {
780
+ line: node_fn.loc.start.line,
781
+ column: start_col + async_keyword.length,
782
+ },
783
+ },
784
+ metadata: {},
785
+ });
786
+
787
+ start_col += async_keyword.length + 1; // +1 for space
788
+ start += async_keyword.length + 1;
789
+ }
790
+
791
+ tokens.push({
792
+ source: source_func_keyword,
793
+ generated: 'function',
794
+ loc: {
795
+ start: { line: node_fn.loc.start.line, column: start_col },
796
+ end: {
797
+ line: node_fn.loc.start.line,
798
+ column: start_col + source_func_keyword.length,
799
+ },
800
+ },
801
+ metadata: is_component ? { hover: replace_label_to_component } : {},
802
+ });
803
+ }
804
+
805
+ // Visit in source order: id, params, body
806
+ // If it's a part of a method, skip visiting id
807
+ // as the name was already covered by the key in MethodDefinition or Property
808
+ if (
809
+ /** @type {AST.FunctionDeclaration | AST.FunctionExpression} */ (node).id &&
810
+ !is_method
811
+ ) {
812
+ visit(
813
+ /** @type {AST.Node} */ (
814
+ /** @type {AST.FunctionDeclaration | AST.FunctionExpression} */ (node).id
815
+ ),
816
+ );
817
+ }
818
+
819
+ if (node.typeParameters) {
820
+ visit(node.typeParameters);
821
+ }
822
+
823
+ if (node.params) {
824
+ for (const param of node.params) {
825
+ visit(param);
826
+ if (param.typeAnnotation) {
827
+ visit(param.typeAnnotation);
828
+ }
829
+ }
830
+ }
831
+
832
+ if (node.returnType) {
833
+ visit(node.returnType);
834
+ }
835
+
836
+ if (node.body) {
837
+ visit(node.body);
838
+ }
839
+ return;
840
+ } else if (node.type === 'VariableDeclaration') {
841
+ // Visit declarators in order
842
+ if (node.declarations) {
843
+ for (const declarator of node.declarations) {
844
+ visit(declarator);
845
+ }
846
+ }
847
+ return;
848
+ } else if (node.type === 'VariableDeclarator') {
849
+ // Visit in source order: id, typeAnnotation, init
850
+ if (node.id) {
851
+ visit(node.id);
852
+ // Visit type annotation if present
853
+ if (node.id.typeAnnotation) {
854
+ visit(node.id.typeAnnotation);
855
+ }
856
+ }
857
+ if (node.init) {
858
+ visit(node.init);
859
+ }
860
+ return;
861
+ } else if (node.type === 'IfStatement') {
862
+ // Visit in source order: test, consequent, alternate
863
+ if (node.test) {
864
+ visit(node.test);
865
+ }
866
+
867
+ if (node.consequent) {
868
+ if (node.consequent.loc) {
869
+ // We're mapping only the brackets because mapping the whole thing
870
+ // would be way too broad and causes
871
+ // issues with partial mapping of something inside the body that we need
872
+ tokens.push(
873
+ {
874
+ source: '{',
875
+ generated: '{',
876
+ loc: {
877
+ start: {
878
+ line: node.consequent.loc.start.line,
879
+ column: node.consequent.loc.start.column,
880
+ },
881
+ end: {
882
+ line: node.consequent.loc.start.line,
883
+ column: node.consequent.loc.start.column + 1,
884
+ },
885
+ },
886
+ metadata: {},
887
+ mappingData: mapping_data_verify_only,
888
+ },
889
+ {
890
+ source: '}',
891
+ generated: '}',
892
+ loc: {
893
+ start: {
894
+ line: node.consequent.loc.end.line,
895
+ column: node.consequent.loc.end.column - 1,
896
+ },
897
+ end: {
898
+ line: node.consequent.loc.end.line,
899
+ column: node.consequent.loc.end.column,
900
+ },
901
+ },
902
+ metadata: {},
903
+ mappingData: mapping_data_verify_only,
904
+ },
905
+ );
906
+ }
907
+
908
+ visit(node.consequent);
909
+ }
910
+
911
+ if (node.alternate) {
912
+ if (node.alternate.loc) {
913
+ tokens.push(
914
+ {
915
+ source: '{',
916
+ generated: '{',
917
+ loc: {
918
+ start: {
919
+ line: node.alternate.loc.start.line,
920
+ column: node.alternate.loc.start.column,
921
+ },
922
+ end: {
923
+ line: node.alternate.loc.start.line,
924
+ column: node.alternate.loc.start.column + 1,
925
+ },
926
+ },
927
+ metadata: {},
928
+ mappingData: mapping_data_verify_only,
929
+ },
930
+ {
931
+ source: '}',
932
+ generated: '}',
933
+ loc: {
934
+ start: {
935
+ line: node.alternate.loc.end.line,
936
+ column: node.alternate.loc.end.column - 1,
937
+ },
938
+ end: { line: node.alternate.loc.end.line, column: node.alternate.loc.end.column },
939
+ },
940
+ metadata: {},
941
+ mappingData: mapping_data_verify_only,
942
+ },
943
+ );
944
+ }
945
+
946
+ visit(node.alternate);
947
+ }
948
+
949
+ return;
950
+ } else if (node.type === 'ForStatement') {
951
+ // Visit in source order: init, test, update, body
952
+ if (node.init) {
953
+ visit(node.init);
954
+ }
955
+ if (node.test) {
956
+ visit(node.test);
957
+ }
958
+ if (node.update) {
959
+ visit(node.update);
960
+ }
961
+ if (node.body) {
962
+ visit(node.body);
963
+ }
964
+
965
+ mappings.push(
966
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
967
+ );
968
+ return;
969
+ } else if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
970
+ // Visit in source order: left, right, index (Ripple-specific), body
971
+ if (node.left) {
972
+ visit(node.left);
973
+ }
974
+ if (node.right) {
975
+ visit(node.right);
976
+ }
977
+ // Ripple-specific: index variable
978
+ if (/** @type {AST.ForOfStatement} */ (node).index) {
979
+ visit(/** @type {AST.Node} */ (/** @type {AST.ForOfStatement} */ (node).index));
980
+ }
981
+ if (node.body) {
982
+ visit(node.body);
983
+ }
984
+
985
+ mappings.push(
986
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
987
+ );
988
+
989
+ return;
990
+ } else if (node.type === 'WhileStatement' || node.type === 'DoWhileStatement') {
991
+ // Visit in source order: test, body (while) or body, test (do-while)
992
+ if (node.type === 'WhileStatement') {
993
+ if (node.test) {
994
+ visit(node.test);
995
+ }
996
+ if (node.body) {
997
+ visit(node.body);
998
+ }
999
+ } else {
1000
+ if (node.body) {
1001
+ visit(node.body);
1002
+ }
1003
+ if (node.test) {
1004
+ visit(node.test);
1005
+ }
1006
+ }
1007
+ return;
1008
+ } else if (node.type === 'TryStatement') {
1009
+ // Visit in source order: block, pending, handler, finalizer
1010
+ if (node.block) {
1011
+ mappings.push(
1012
+ get_mapping_from_node(
1013
+ node.block,
1014
+ src_to_gen_map,
1015
+ gen_line_offsets,
1016
+ mapping_data_verify_only,
1017
+ ),
1018
+ );
1019
+ visit(node.block);
1020
+ }
1021
+ if (node.pending) {
1022
+ mappings.push(
1023
+ get_mapping_from_node(
1024
+ node.pending,
1025
+ src_to_gen_map,
1026
+ gen_line_offsets,
1027
+ mapping_data_verify_only,
1028
+ ),
1029
+ );
1030
+
1031
+ // Add a special token for the 'pending' keyword with customData
1032
+ // to suppress TypeScript diagnostics and provide custom hover/definition
1033
+ const pending = /** @type {(typeof node.pending) & AST.NodeWithLocation} */ (
1034
+ node.pending
1035
+ );
1036
+ const pendingKeywordLoc = {
1037
+ start: {
1038
+ line: pending.loc.start.line,
1039
+ column: pending.loc.start.column - 'pending '.length,
1040
+ },
1041
+ end: {
1042
+ line: pending.loc.start.line,
1043
+ column: pending.loc.start.column - 1,
1044
+ },
1045
+ };
1046
+ tokens.push({
1047
+ source: 'pending',
1048
+ generated: 'pending',
1049
+ loc: pendingKeywordLoc,
1050
+ metadata: {
1051
+ wordHighlight: {
1052
+ /** @type {DocumentHighlightKind} */
1053
+ kind: 1,
1054
+ },
1055
+ suppressedDiagnostics: [
1056
+ 1472, // 'catch' or 'finally' expected
1057
+ 2304, // Cannot find name 'pending'
1058
+ ],
1059
+ // suppress all hovers
1060
+ hover: false,
1061
+
1062
+ // Example of a custom hover contents (uses markdown)
1063
+ // hover: '```ripple\npending\n```\n\nRipple-specific keyword for try/pending blocks.\n\nThe `pending` block executes while async operations inside the `try` block are awaiting. This provides a built-in loading state for async components.',
1064
+
1065
+ // Example of a custom definition and its type definition file
1066
+ // definition: {
1067
+ // typeReplace: {
1068
+ // name: 'SomeType',
1069
+ // path: 'types/index.d.ts',
1070
+ // },
1071
+ // },
1072
+ },
1073
+ });
1074
+ visit(node.pending);
1075
+ }
1076
+ if (node.handler) {
1077
+ visit(node.handler);
1078
+ }
1079
+ if (node.finalizer) {
1080
+ visit(node.finalizer);
1081
+ }
1082
+ return;
1083
+ } else if (node.type === 'CatchClause') {
1084
+ // Visit in source order: param, resetParam, body
1085
+ if (node.param) {
1086
+ visit(node.param);
1087
+ }
1088
+ if (node.resetParam) {
1089
+ visit(node.resetParam);
1090
+ }
1091
+ if (node.body) {
1092
+ visit(node.body);
1093
+ }
1094
+ return;
1095
+ } else if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1096
+ if (node.type === 'NewExpression' && node.loc) {
1097
+ mappings.push(
1098
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1099
+ );
1100
+ }
1101
+
1102
+ if (node.arguments) {
1103
+ for (const arg of node.arguments) {
1104
+ visit(arg);
1105
+ }
1106
+ }
1107
+
1108
+ if (node.typeArguments) {
1109
+ visit(node.typeArguments);
1110
+ }
1111
+
1112
+ if (node.callee) {
1113
+ visit(node.callee);
1114
+ }
1115
+ return;
1116
+ } else if (node.type === 'LogicalExpression' || node.type === 'BinaryExpression') {
1117
+ // Visit in source order: left, right
1118
+ if (node.left) {
1119
+ visit(node.left);
1120
+ }
1121
+ if (node.right) {
1122
+ visit(node.right);
1123
+ }
1124
+ return;
1125
+ } else if (node.type === 'MemberExpression') {
1126
+ if (node.loc) {
1127
+ const mapping = get_mapping_from_node(
1128
+ node,
1129
+ src_to_gen_map,
1130
+ gen_line_offsets,
1131
+ mapping_data_verify_only,
1132
+ );
1133
+
1134
+ mappings.push(mapping);
1135
+ }
1136
+
1137
+ if (node.object) {
1138
+ visit(node.object);
1139
+ }
1140
+ if (node.property) {
1141
+ visit(node.property);
1142
+
1143
+ if (node.computed && node.property.loc) {
1144
+ mappings.push(
1145
+ get_mapping_from_node(
1146
+ node.property,
1147
+ src_to_gen_map,
1148
+ gen_line_offsets,
1149
+ mapping_data_verify_only,
1150
+ ),
1151
+ );
1152
+ }
1153
+ }
1154
+ return;
1155
+ } else if (node.type === 'AssignmentExpression' || node.type === 'AssignmentPattern') {
1156
+ // Visit in source order: left, typeAnnotation, right
1157
+ if (node.left) {
1158
+ visit(node.left);
1159
+ // Visit type annotation if present (for AssignmentPattern)
1160
+ if (node.left.typeAnnotation) {
1161
+ visit(node.left.typeAnnotation);
1162
+ }
1163
+ }
1164
+ if (node.right) {
1165
+ visit(node.right);
1166
+ }
1167
+
1168
+ if (node.type === 'AssignmentPattern') {
1169
+ // We need a mapping for the whole AssignmentPattern for diagnostics
1170
+ // Only enable diagnostic verification here to avoid duplicate mappings
1171
+ // that can cause things like double definitions
1172
+ mappings.push(
1173
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1174
+ );
1175
+ }
1176
+
1177
+ return;
1178
+ } else if (node.type === 'ObjectExpression' || node.type === 'ObjectPattern') {
1179
+ if (node.loc && node.type === 'ObjectExpression') {
1180
+ mappings.push(
1181
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1182
+ );
1183
+ }
1184
+
1185
+ // Visit properties in order
1186
+ if (node.properties) {
1187
+ for (const prop of node.properties) {
1188
+ visit(prop);
1189
+ }
1190
+ }
1191
+ return;
1192
+ } else if (node.type === 'Property') {
1193
+ // Visit in source order: key, value
1194
+ // For shorthand properties ({ count }), key and value are the same node, only visit once
1195
+ if (node.shorthand) {
1196
+ if (node.value) {
1197
+ visit(node.value);
1198
+ }
1199
+ } else {
1200
+ if (node.computed) {
1201
+ set_bracket_computed_mapping(node, mappings);
1202
+ }
1203
+
1204
+ if (
1205
+ node.value.type === 'FunctionExpression' &&
1206
+ node.method &&
1207
+ node.value.metadata.is_component
1208
+ ) {
1209
+ set_component_mapping_to_name(/** @type {PropertyIsComponent} */ (node));
1210
+ }
1211
+
1212
+ if (node.key.type === 'Literal') {
1213
+ handle_literal(
1214
+ node.key,
1215
+ /** @type {AST.FunctionExpression} */ (node.value).metadata.is_component,
1216
+ );
1217
+ } else {
1218
+ visit(node.key);
1219
+ }
1220
+
1221
+ if (node.value) {
1222
+ visit(node.value);
1223
+ }
1224
+ }
1225
+ return;
1226
+ } else if (node.type === 'ArrayExpression' || node.type === 'ArrayPattern') {
1227
+ // Visit elements in order
1228
+ if (node.elements) {
1229
+ for (const element of node.elements) {
1230
+ if (element) visit(element);
1231
+ }
1232
+ }
1233
+ return;
1234
+ } else if (node.type === 'ConditionalExpression') {
1235
+ // Visit in source order: test, consequent, alternate
1236
+ if (node.test) {
1237
+ visit(node.test);
1238
+ }
1239
+ if (node.consequent) {
1240
+ visit(node.consequent);
1241
+ }
1242
+ if (node.alternate) {
1243
+ visit(node.alternate);
1244
+ }
1245
+ return;
1246
+ } else if (node.type === 'UnaryExpression' || node.type === 'UpdateExpression') {
1247
+ // Visit argument
1248
+ if (node.argument) {
1249
+ visit(node.argument);
1250
+ }
1251
+ return;
1252
+ } else if (node.type === 'TemplateLiteral') {
1253
+ if (node.loc) {
1254
+ mappings.push(
1255
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1256
+ );
1257
+ }
1258
+
1259
+ // Visit quasis and expressions in order
1260
+ for (let i = 0; i < node.quasis.length; i++) {
1261
+ if (node.quasis[i]) {
1262
+ visit(node.quasis[i]);
1263
+ }
1264
+ if (i < node.expressions.length && node.expressions[i]) {
1265
+ visit(node.expressions[i]);
1266
+ }
1267
+ }
1268
+ return;
1269
+ } else if (node.type === 'TaggedTemplateExpression') {
1270
+ // Visit in source order: tag, quasi
1271
+ if (node.tag) {
1272
+ visit(node.tag);
1273
+ }
1274
+ if (node.quasi) {
1275
+ visit(node.quasi);
1276
+ }
1277
+ return;
1278
+ } else if (node.type === 'ReturnStatement' || node.type === 'ThrowStatement') {
1279
+ // Visit argument
1280
+ if (node.argument) {
1281
+ visit(node.argument);
1282
+ }
1283
+
1284
+ if (node.type === 'ReturnStatement' && node.loc) {
1285
+ const mapping = get_mapping_from_node(
1286
+ node,
1287
+ src_to_gen_map,
1288
+ gen_line_offsets,
1289
+ mapping_data_verify_only,
1290
+ );
1291
+ // We're only mapping the 'return' keyword, otherwise the mapping would be too broad
1292
+ // and likely may cause issues with partial mappings of something inside the return statement that we need
1293
+ const return_keyword_length = 'return'.length;
1294
+ mapping.lengths = [return_keyword_length];
1295
+ mapping.generatedLengths = [return_keyword_length];
1296
+
1297
+ mappings.push(mapping);
1298
+ }
1299
+ return;
1300
+ } else if (node.type === 'ExpressionStatement') {
1301
+ if (node.expression) {
1302
+ visit(node.expression);
1303
+ }
1304
+ return;
1305
+ } else if (node.type === 'BlockStatement' || node.type === 'Program') {
1306
+ // Visit body statements in order
1307
+ if (node.body) {
1308
+ for (const statement of node.body) {
1309
+ visit(statement);
1310
+ }
1311
+ }
1312
+ return;
1313
+ } else if (node.type === 'SwitchStatement') {
1314
+ // Visit in source order: discriminant, cases
1315
+ if (node.discriminant) {
1316
+ visit(node.discriminant);
1317
+ }
1318
+ if (node.cases) {
1319
+ for (const caseNode of node.cases) {
1320
+ visit(caseNode);
1321
+ }
1322
+ }
1323
+
1324
+ mappings.push(
1325
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1326
+ );
1327
+
1328
+ return;
1329
+ } else if (node.type === 'SwitchCase') {
1330
+ // Visit in source order: test, consequent
1331
+ if (node.test) {
1332
+ visit(node.test);
1333
+ }
1334
+ if (node.consequent) {
1335
+ for (const statement of node.consequent) {
1336
+ visit(statement);
1337
+ }
1338
+ }
1339
+ return;
1340
+ } else if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
1341
+ // Visit in source order: id, superClass, body
1342
+ if (node.id) {
1343
+ visit(node.id);
1344
+ }
1345
+ if (node.superClass) {
1346
+ visit(node.superClass);
1347
+ }
1348
+ if (node.body) {
1349
+ visit(node.body);
1350
+ }
1351
+ return;
1352
+ } else if (node.type === 'ClassBody') {
1353
+ // Visit body in order
1354
+ if (node.body) {
1355
+ for (const member of node.body) {
1356
+ visit(member);
1357
+ }
1358
+ }
1359
+ return;
1360
+ } else if (node.type === 'MethodDefinition') {
1361
+ if (node.computed) {
1362
+ set_bracket_computed_mapping(node, mappings);
1363
+ }
1364
+
1365
+ if (node.value.metadata.is_component) {
1366
+ set_component_mapping_to_name(/** @type {MethodIsComponent} */ (node));
1367
+ }
1368
+
1369
+ if (node.key.type === 'Literal') {
1370
+ handle_literal(
1371
+ node.key,
1372
+ /** @type {AST.FunctionExpression} */ (node.value).metadata.is_component,
1373
+ );
1374
+ } else {
1375
+ visit(node.key);
1376
+ }
1377
+
1378
+ if (node.value) {
1379
+ visit(node.value);
1380
+ }
1381
+ return;
1382
+ } else if (node.type === 'SequenceExpression') {
1383
+ // Visit expressions in order
1384
+ if (node.expressions) {
1385
+ for (const expr of node.expressions) {
1386
+ visit(expr);
1387
+ }
1388
+ }
1389
+ return;
1390
+ } else if (node.type === 'SpreadElement' || node.type === 'RestElement') {
1391
+ // Visit the argument
1392
+ if (node.argument) {
1393
+ visit(node.argument);
1394
+ // Visit type annotation if present (for RestElement)
1395
+ if (/** @type {AST.Pattern} */ (node.argument).typeAnnotation) {
1396
+ visit(
1397
+ /** @type {AST.Node} */ (/** @type {AST.Pattern} */ (node.argument).typeAnnotation),
1398
+ );
1399
+ }
1400
+ }
1401
+ // RestElement itself can have typeAnnotation
1402
+ if (node.typeAnnotation) {
1403
+ visit(node.typeAnnotation);
1404
+ }
1405
+ return;
1406
+ } else if (node.type === 'YieldExpression' || node.type === 'AwaitExpression') {
1407
+ // Visit the argument if present
1408
+ if (node.argument) {
1409
+ visit(node.argument);
1410
+ }
1411
+
1412
+ if (node.type === 'AwaitExpression') {
1413
+ const max_len = 'await'.length;
1414
+ // We need a mapping for diagnostics but only on the 'await' keyword
1415
+ const mapping = get_mapping_from_node(
1416
+ node,
1417
+ src_to_gen_map,
1418
+ gen_line_offsets,
1419
+ mapping_data_verify_only,
1420
+ max_len,
1421
+ max_len,
1422
+ );
1423
+
1424
+ mappings.push(mapping);
1425
+ }
1426
+ return;
1427
+ } else if (node.type === 'ChainExpression') {
1428
+ // Visit the expression
1429
+ if (node.expression) {
1430
+ visit(node.expression);
1431
+ }
1432
+ return;
1433
+ } else if (node.type === 'Super' || node.type === 'ThisExpression') {
1434
+ // Leaf nodes, no children
1435
+ return;
1436
+ } else if (node.type === 'MetaProperty') {
1437
+ // Visit meta and property (e.g., new.target, import.meta)
1438
+ if (node.meta) {
1439
+ visit(node.meta);
1440
+ }
1441
+ if (node.property) {
1442
+ visit(node.property);
1443
+ }
1444
+ return;
1445
+ } else if (node.type === 'EmptyStatement' || node.type === 'DebuggerStatement') {
1446
+ // No children to visit
1447
+ return;
1448
+ } else if (node.type === 'LabeledStatement') {
1449
+ // Visit label and statement
1450
+ if (node.label) {
1451
+ visit(node.label);
1452
+ }
1453
+ if (node.body) {
1454
+ visit(node.body);
1455
+ }
1456
+ return;
1457
+ } else if (node.type === 'BreakStatement' || node.type === 'ContinueStatement') {
1458
+ // Visit label if present
1459
+ if (node.label) {
1460
+ visit(node.label);
1461
+ }
1462
+ return;
1463
+ } else if (node.type === 'WithStatement') {
1464
+ // Visit object and body
1465
+ if (node.object) {
1466
+ visit(node.object);
1467
+ }
1468
+ if (node.body) {
1469
+ visit(node.body);
1470
+ }
1471
+ return;
1472
+ } else if (node.type === 'JSXFragment') {
1473
+ // Visit children in order
1474
+ if (node.children) {
1475
+ for (const child of node.children) {
1476
+ visit(/** @type {AST.Node} */ (child));
1477
+ }
1478
+ }
1479
+ return;
1480
+ } else if (node.type === 'JSXClosingFragment' || node.type === 'JSXOpeningFragment') {
1481
+ // These are handled by their parent nodes
1482
+ return;
1483
+ } else if (node.type === 'JSXMemberExpression') {
1484
+ // Visit object and property (e.g., <Foo.Bar>)
1485
+ if (node.object) {
1486
+ visit(node.object);
1487
+ }
1488
+ if (node.property) {
1489
+ visit(node.property);
1490
+ }
1491
+ return;
1492
+ } else if (node.type === 'JSXNamespacedName') {
1493
+ // Visit namespace and name (e.g., <svg:circle>)
1494
+ if (node.namespace) {
1495
+ visit(node.namespace);
1496
+ }
1497
+ if (node.name) {
1498
+ visit(node.name);
1499
+ }
1500
+ return;
1501
+ } else if (node.type === 'JSXEmptyExpression') {
1502
+ // No children
1503
+ return;
1504
+ } else if (node.type === 'TemplateElement') {
1505
+ // Leaf node, no children to visit
1506
+ return;
1507
+ } else if (node.type === 'PrivateIdentifier') {
1508
+ // Leaf node
1509
+ return;
1510
+ } else if (node.type === 'PropertyDefinition') {
1511
+ // Visit key and value
1512
+ if (node.key) {
1513
+ visit(node.key);
1514
+ }
1515
+ if (node.value) {
1516
+ visit(node.value);
1517
+ }
1518
+ return;
1519
+ } else if (node.type === 'StaticBlock') {
1520
+ // Visit body
1521
+ if (node.body) {
1522
+ for (const statement of node.body) {
1523
+ visit(statement);
1524
+ }
1525
+ }
1526
+ return;
1527
+ } else if (node.type === 'ImportExpression') {
1528
+ // Visit source
1529
+ if (node.source) {
1530
+ visit(node.source);
1531
+ }
1532
+ return;
1533
+ } else if (node.type === 'ParenthesizedExpression') {
1534
+ if (node.metadata.forceMapping && node.loc) {
1535
+ const mapping = get_mapping_from_node(node, src_to_gen_map, gen_line_offsets);
1536
+ if (node.metadata.skipParenthesisMapping) {
1537
+ mapping.generatedOffsets[0] = mapping.generatedOffsets[0] + 1; // Skip the opening parenthesis
1538
+ mapping.generatedLengths[0] = mapping.generatedLengths[0] - 2; // Skip both parentheses
1539
+ }
1540
+ mappings.push(mapping);
1541
+ }
1542
+ // Visit the wrapped expression
1543
+ if (node.expression) {
1544
+ visit(node.expression);
1545
+ }
1546
+ return;
1547
+ } else if (node.type === 'TSAsExpression' || node.type === 'TSSatisfiesExpression') {
1548
+ // Type assertion: value as Type
1549
+ if (node.expression) {
1550
+ visit(node.expression);
1551
+ }
1552
+ if (node.typeAnnotation) {
1553
+ visit(node.typeAnnotation);
1554
+ }
1555
+ return;
1556
+ } else if (node.type === 'TSNonNullExpression') {
1557
+ // Non-null assertion: value!
1558
+ if (node.expression) {
1559
+ visit(node.expression);
1560
+ }
1561
+ return;
1562
+ } else if (node.type === 'TSTypeAssertion') {
1563
+ // Type assertion: <Type>value
1564
+ if (node.expression) {
1565
+ visit(node.expression);
1566
+ }
1567
+ // Skip typeAnnotation
1568
+ return;
1569
+ } else if (
1570
+ node.type === 'TSTypeParameterInstantiation' ||
1571
+ node.type === 'TSTypeParameterDeclaration'
1572
+ ) {
1573
+ if (node.loc) {
1574
+ mappings.push(
1575
+ get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1576
+ );
1577
+ }
1578
+ // Generic type parameters - visit to collect type variable names
1579
+ if (node.params) {
1580
+ for (const param of node.params) {
1581
+ visit(param);
1582
+ }
1583
+ }
1584
+ return;
1585
+ } else if (node.type === 'TSTypeParameter') {
1586
+ // Type parameter like T in <T> or key in mapped types
1587
+ // Note: node.name is a string, not an Identifier node
1588
+ if (node.name && node.loc && typeof node.name === 'string') {
1589
+ tokens.push({ source: node.name, generated: node.name, loc: node.loc, metadata: {} });
1590
+ } else if (node.name && typeof node.name === 'object') {
1591
+ // In some cases, name might be an Identifier node
1592
+ visit(node.name);
1593
+ }
1594
+ if (node.constraint) {
1595
+ visit(node.constraint);
1596
+ }
1597
+ if (node.default) {
1598
+ visit(node.default);
1599
+ }
1600
+ return;
1601
+ } else if (node.type === 'TSTypeAnnotation') {
1602
+ // Type annotation - visit the type
1603
+ if (node.typeAnnotation) {
1604
+ visit(node.typeAnnotation);
1605
+ }
1606
+ return;
1607
+ } else if (node.type === 'TSTypeReference') {
1608
+ // Type reference like "string" or "Array<T>"
1609
+ if (node.typeName) {
1610
+ visit(node.typeName);
1611
+ }
1612
+
1613
+ // typeParameters and typeArguments (different parsers use different names)
1614
+ // tsTypeParameters is a bug in the estree-typescript
1615
+ // but we fixed in the analyzer to typeArguments.
1616
+
1617
+ if (node.typeArguments) {
1618
+ visit(node.typeArguments);
1619
+ }
1620
+ return;
1621
+ } else if (node.type === 'TSQualifiedName') {
1622
+ // Qualified name (e.g., Foo.Bar in types)
1623
+ if (node.left) {
1624
+ visit(node.left);
1625
+ }
1626
+ if (node.right) {
1627
+ visit(node.right);
1628
+ }
1629
+ return;
1630
+ } else if (node.type === 'TSArrayType') {
1631
+ // Array type like T[]
1632
+ if (node.elementType) {
1633
+ visit(node.elementType);
1634
+ }
1635
+ return;
1636
+ } else if (node.type === 'TSTupleType') {
1637
+ // Tuple type like [string, number]
1638
+ if (node.elementTypes) {
1639
+ for (const type of node.elementTypes) {
1640
+ visit(type);
1641
+ }
1642
+ }
1643
+ return;
1644
+ } else if (node.type === 'TSUnionType' || node.type === 'TSIntersectionType') {
1645
+ // Union (A | B) or Intersection (A & B) types
1646
+ if (node.types) {
1647
+ for (const type of node.types) {
1648
+ visit(type);
1649
+ }
1650
+ }
1651
+ return;
1652
+ } else if (node.type === 'TSFunctionType' || node.type === 'TSConstructorType') {
1653
+ // Function or constructor type
1654
+ if (node.typeParameters) {
1655
+ visit(node.typeParameters);
1656
+ }
1657
+ if (node.parameters) {
1658
+ for (const param of node.parameters) {
1659
+ visit(param);
1660
+ // Visit type annotation on the parameter
1661
+ if (
1662
+ /** @type {Exclude<AST.Parameter, AST.TSParameterProperty>} */ (param).typeAnnotation
1663
+ ) {
1664
+ visit(
1665
+ /** @type {AST.Node} */ (
1666
+ /** @type {Exclude<AST.Parameter, AST.TSParameterProperty>} */ (param)
1667
+ .typeAnnotation
1668
+ ),
1669
+ );
1670
+ }
1671
+ }
1672
+ }
1673
+ if (node.typeAnnotation) {
1674
+ visit(node.typeAnnotation);
1675
+ }
1676
+ return;
1677
+ } else if (node.type === 'TSTypeLiteral') {
1678
+ // Object type literal { foo: string }
1679
+ if (node.members) {
1680
+ for (const member of node.members) {
1681
+ visit(member);
1682
+ }
1683
+ }
1684
+ return;
1685
+ } else if (node.type === 'TSPropertySignature') {
1686
+ // Property signature in type
1687
+ if (node.key) {
1688
+ visit(node.key);
1689
+ }
1690
+ if (node.typeAnnotation) {
1691
+ visit(node.typeAnnotation);
1692
+ }
1693
+ return;
1694
+ } else if (node.type === 'TSMethodSignature') {
1695
+ // Method signature in type
1696
+ if (node.key) {
1697
+ visit(node.key);
1698
+ }
1699
+ if (node.typeParameters) {
1700
+ visit(node.typeParameters);
1701
+ }
1702
+ if (node.parameters) {
1703
+ for (const param of node.parameters) {
1704
+ visit(param);
1705
+ // Visit type annotation on the parameter
1706
+ if (
1707
+ /** @type {Exclude<AST.Parameter, AST.TSParameterProperty>} */ (param).typeAnnotation
1708
+ ) {
1709
+ visit(
1710
+ /** @type {AST.Node} */ (
1711
+ /** @type {Exclude<AST.Parameter, AST.TSParameterProperty>} */ (param)
1712
+ .typeAnnotation
1713
+ ),
1714
+ );
1715
+ }
1716
+ }
1717
+ }
1718
+ if (node.typeAnnotation) {
1719
+ visit(node.typeAnnotation);
1720
+ }
1721
+ return;
1722
+ } else if (node.type === 'TSIndexSignature') {
1723
+ // Index signature [key: string]: Type
1724
+ if (node.parameters) {
1725
+ for (const param of node.parameters) {
1726
+ visit(param);
1727
+ // Visit type annotation on the parameter
1728
+ if (
1729
+ /** @type {Exclude<AST.Parameter, AST.TSParameterProperty>} */ (param).typeAnnotation
1730
+ ) {
1731
+ visit(
1732
+ /** @type {AST.Node} */ (
1733
+ /** @type {Exclude<AST.Parameter, AST.TSParameterProperty>} */ (param)
1734
+ .typeAnnotation
1735
+ ),
1736
+ );
1737
+ }
1738
+ }
1739
+ }
1740
+ if (node.typeAnnotation) {
1741
+ visit(node.typeAnnotation);
1742
+ }
1743
+ return;
1744
+ } else if (
1745
+ node.type === 'TSCallSignatureDeclaration' ||
1746
+ node.type === 'TSConstructSignatureDeclaration'
1747
+ ) {
1748
+ // Call or construct signature
1749
+ if (node.typeParameters) {
1750
+ visit(node.typeParameters);
1751
+ }
1752
+ if (node.parameters) {
1753
+ for (const param of node.parameters) {
1754
+ visit(param);
1755
+ // Visit type annotation on the parameter
1756
+ if (
1757
+ /** @type {Exclude<AST.Parameter, AST.TSParameterProperty>} */ (param).typeAnnotation
1758
+ ) {
1759
+ visit(
1760
+ /** @type {AST.Node} */ (
1761
+ /** @type {Exclude<AST.Parameter, AST.TSParameterProperty>} */ (param)
1762
+ .typeAnnotation
1763
+ ),
1764
+ );
1765
+ }
1766
+ }
1767
+ }
1768
+ if (node.typeAnnotation) {
1769
+ visit(node.typeAnnotation);
1770
+ }
1771
+ return;
1772
+ } else if (node.type === 'TSConditionalType') {
1773
+ // Conditional type: T extends U ? X : Y
1774
+ if (node.checkType) {
1775
+ visit(node.checkType);
1776
+ }
1777
+ if (node.extendsType) {
1778
+ visit(node.extendsType);
1779
+ }
1780
+ if (node.trueType) {
1781
+ visit(node.trueType);
1782
+ }
1783
+ if (node.falseType) {
1784
+ visit(node.falseType);
1785
+ }
1786
+ return;
1787
+ } else if (node.type === 'TSInferType') {
1788
+ // Infer type: infer T
1789
+ if (node.typeParameter) {
1790
+ visit(node.typeParameter);
1791
+ }
1792
+ return;
1793
+ } else if (node.type === 'TSParenthesizedType') {
1794
+ // Parenthesized type: (T)
1795
+ if (node.typeAnnotation) {
1796
+ visit(node.typeAnnotation);
1797
+ }
1798
+ return;
1799
+ } else if (node.type === 'TSTypeOperator') {
1800
+ // Type operator: keyof T, readonly T
1801
+ if (node.typeAnnotation) {
1802
+ visit(node.typeAnnotation);
1803
+ }
1804
+ return;
1805
+ } else if (node.type === 'TSIndexedAccessType') {
1806
+ // Indexed access: T[K]
1807
+ if (node.objectType) {
1808
+ visit(node.objectType);
1809
+ }
1810
+ if (node.indexType) {
1811
+ visit(node.indexType);
1812
+ }
1813
+ return;
1814
+ } else if (node.type === 'TSMappedType') {
1815
+ // Mapped type: { [K in keyof T]: ... }
1816
+ if (node.typeParameter) {
1817
+ visit(node.typeParameter);
1818
+ }
1819
+ if (node.typeAnnotation) {
1820
+ visit(node.typeAnnotation);
1821
+ }
1822
+ return;
1823
+ } else if (node.type === 'TSLiteralType') {
1824
+ // Literal type: "foo" | 123 | true
1825
+ if (node.literal) {
1826
+ visit(node.literal);
1827
+ }
1828
+ return;
1829
+ } else if (node.type === 'TSExpressionWithTypeArguments') {
1830
+ // Expression with type arguments: Foo<Bar>
1831
+ if (node.expression) {
1832
+ visit(node.expression);
1833
+ }
1834
+ if (node.typeParameters) {
1835
+ visit(node.typeParameters);
1836
+ }
1837
+ return;
1838
+ } else if (node.type === 'TSImportType') {
1839
+ // Import type: import("module").Type
1840
+ if (node.argument) {
1841
+ visit(node.argument);
1842
+ }
1843
+ if (node.qualifier) {
1844
+ visit(node.qualifier);
1845
+ }
1846
+ if (node.typeParameters) {
1847
+ visit(node.typeParameters);
1848
+ }
1849
+ return;
1850
+ } else if (node.type === 'TSTypeQuery') {
1851
+ // Type query: typeof x
1852
+ if (node.exprName) {
1853
+ visit(node.exprName);
1854
+ }
1855
+ if (node.typeArguments) {
1856
+ visit(node.typeArguments);
1857
+ }
1858
+ return;
1859
+ } else if (node.type === 'TSInterfaceDeclaration') {
1860
+ // Interface declaration
1861
+ if (node.id) {
1862
+ visit(node.id);
1863
+ }
1864
+ if (node.typeParameters) {
1865
+ visit(node.typeParameters);
1866
+ }
1867
+ if (node.extends) {
1868
+ for (const ext of node.extends) {
1869
+ visit(ext);
1870
+ }
1871
+ }
1872
+ if (node.body) {
1873
+ visit(node.body);
1874
+ }
1875
+ return;
1876
+ } else if (node.type === 'TSInterfaceBody') {
1877
+ // Interface body
1878
+ if (node.body) {
1879
+ for (const member of node.body) {
1880
+ visit(member);
1881
+ }
1882
+ }
1883
+ return;
1884
+ } else if (node.type === 'TSTypeAliasDeclaration') {
1885
+ // Type alias
1886
+ if (node.id) {
1887
+ visit(node.id);
1888
+ }
1889
+ if (node.typeParameters) {
1890
+ visit(node.typeParameters);
1891
+ }
1892
+ if (node.typeAnnotation) {
1893
+ visit(node.typeAnnotation);
1894
+ }
1895
+ return;
1896
+ } else if (node.type === 'TSEnumDeclaration') {
1897
+ // Visit id and members
1898
+ if (node.id) {
1899
+ visit(node.id);
1900
+ }
1901
+ if (node.members) {
1902
+ for (const member of node.members) {
1903
+ visit(member);
1904
+ }
1905
+ }
1906
+ return;
1907
+ } else if (node.type === 'TSEnumMember') {
1908
+ // Visit id and initializer
1909
+ if (node.id) {
1910
+ visit(node.id);
1911
+ }
1912
+ if (node.initializer) {
1913
+ visit(node.initializer);
1914
+ }
1915
+ return;
1916
+ } else if (node.type === 'TSModuleDeclaration') {
1917
+ // Namespace/module declaration
1918
+ if (node.id) {
1919
+ visit(node.id);
1920
+ }
1921
+ if (node.body) {
1922
+ visit(node.body);
1923
+ }
1924
+ return;
1925
+ } else if (node.type === 'TSModuleBlock') {
1926
+ // Module body
1927
+ if (node.body) {
1928
+ for (const statement of node.body) {
1929
+ visit(statement);
1930
+ }
1931
+ }
1932
+ return;
1933
+ } else if (node.type === 'TSNamedTupleMember') {
1934
+ // Named tuple member: [name: Type]
1935
+ if (node.label) {
1936
+ visit(node.label);
1937
+ }
1938
+ if (node.elementType) {
1939
+ visit(node.elementType);
1940
+ }
1941
+ return;
1942
+ } else if (node.type === 'TSRestType') {
1943
+ // Rest type: ...T[]
1944
+ if (node.typeAnnotation) {
1945
+ visit(node.typeAnnotation);
1946
+ }
1947
+ return;
1948
+ } else if (node.type === 'TSOptionalType') {
1949
+ // Optional type: T?
1950
+ if (node.typeAnnotation) {
1951
+ visit(node.typeAnnotation);
1952
+ }
1953
+ return;
1954
+ } else if (
1955
+ node.type === 'TSAnyKeyword' ||
1956
+ node.type === 'TSUnknownKeyword' ||
1957
+ node.type === 'TSNumberKeyword' ||
1958
+ node.type === 'TSObjectKeyword' ||
1959
+ node.type === 'TSBooleanKeyword' ||
1960
+ node.type === 'TSBigIntKeyword' ||
1961
+ node.type === 'TSStringKeyword' ||
1962
+ node.type === 'TSSymbolKeyword' ||
1963
+ node.type === 'TSVoidKeyword' ||
1964
+ node.type === 'TSUndefinedKeyword' ||
1965
+ node.type === 'TSNullKeyword' ||
1966
+ node.type === 'TSNeverKeyword' ||
1967
+ node.type === 'TSThisType' ||
1968
+ node.type === 'TSIntrinsicKeyword'
1969
+ ) {
1970
+ // Primitive type keywords - leaf nodes, no children
1971
+ return;
1972
+ } else if (node.type === 'TSDeclareFunction') {
1973
+ // TypeScript declare function: declare function foo(): void;
1974
+ // Visit in source order: id, typeParameters, params, returnType
1975
+ if (node.id) {
1976
+ visit(node.id);
1977
+ }
1978
+ if (node.typeParameters) {
1979
+ visit(node.typeParameters);
1980
+ }
1981
+ if (node.params) {
1982
+ for (const param of node.params) {
1983
+ visit(param);
1984
+ }
1985
+ }
1986
+ if (node.returnType) {
1987
+ visit(node.returnType);
1988
+ }
1989
+ return;
1990
+ } else if (node.type === 'TSExportAssignment') {
1991
+ // TypeScript export assignment: export = foo;
1992
+ if (node.expression) {
1993
+ visit(node.expression);
1994
+ }
1995
+ return;
1996
+ } else if (node.type === 'TSNamespaceExportDeclaration') {
1997
+ // TypeScript namespace export: export as namespace foo;
1998
+ if (node.id) {
1999
+ visit(node.id);
2000
+ }
2001
+ return;
2002
+ } else if (node.type === 'TSExternalModuleReference') {
2003
+ // TypeScript external module reference: import foo = require('bar');
2004
+ if (node.expression) {
2005
+ visit(node.expression);
2006
+ }
2007
+ return;
2008
+ } else if (node.type === 'TSInstantiationExpression') {
2009
+ // TypeScript instantiation expression: new Foo<T>()
2010
+ if (node.expression) {
2011
+ visit(node.expression);
2012
+ }
2013
+ if (node.typeArguments) {
2014
+ visit(node.typeArguments);
2015
+ }
2016
+ return;
2017
+ }
2018
+
2019
+ throw new Error(`Unhandled AST node type in mapping walker: ${node.type}`);
2020
+ },
2021
+ });
2022
+
2023
+ for (const token of tokens) {
2024
+ const source_text = token.source ?? '';
2025
+ const gen_text = token.generated;
2026
+ const source_start = loc_to_offset(
2027
+ token.loc.start.line,
2028
+ token.loc.start.column,
2029
+ src_line_offsets,
2030
+ );
2031
+ const source_length = source_text.length;
2032
+ const gen_length = gen_text.length;
2033
+ const gen_line_col = get_generated_position(
2034
+ token.loc.start.line,
2035
+ token.loc.start.column,
2036
+ src_to_gen_map,
2037
+ );
2038
+ const gen_start = loc_to_offset(gen_line_col.line, gen_line_col.column, gen_line_offsets);
2039
+
2040
+ /** @type {CustomMappingData} */
2041
+ const customData = {};
2042
+
2043
+ // Add optional metadata from token if present
2044
+ if ('wordHighlight' in token.metadata) {
2045
+ customData.wordHighlight = token.metadata.wordHighlight;
2046
+ }
2047
+ if ('suppressedDiagnostics' in token.metadata) {
2048
+ customData.suppressedDiagnostics = token.metadata.suppressedDiagnostics;
2049
+ }
2050
+ if ('hover' in token.metadata) {
2051
+ customData.hover = token.metadata.hover;
2052
+ }
2053
+ if ('definition' in token.metadata) {
2054
+ customData.definition = token.metadata.definition;
2055
+ }
2056
+
2057
+ mappings.push({
2058
+ sourceOffsets: [source_start],
2059
+ generatedOffsets: [gen_start],
2060
+ lengths: [source_length],
2061
+ generatedLengths: [gen_length],
2062
+ data: {
2063
+ ...(token.mappingData ?? mapping_data),
2064
+ customData,
2065
+ },
2066
+ });
2067
+ }
2068
+
2069
+ // Sort mappings by start position, but prioritize narrower ranges that are fully contained
2070
+ // within wider ones. This ensures that specific tokens (like identifiers) take precedence
2071
+ // over broader ranges (like `if` consequent blocks) during language server lookups.
2072
+ // Otherwise, volar may pick the wrong mapping for diagnostics or other features.
2073
+ mappings.sort((a, b) => {
2074
+ const aStart = a.sourceOffsets[0];
2075
+ const aEnd = aStart + a.lengths[0];
2076
+ const bStart = b.sourceOffsets[0];
2077
+ const bEnd = bStart + b.lengths[0];
2078
+
2079
+ if (aStart === bStart && aEnd === bEnd) {
2080
+ // ranges are identical
2081
+ return 0;
2082
+ }
2083
+
2084
+ // Check if one range is fully contained within the other
2085
+ const bInsideA = bStart >= aStart && bEnd <= aEnd;
2086
+ const aInsideB = aStart >= bStart && aEnd <= bEnd;
2087
+
2088
+ if (bInsideA) {
2089
+ // B (narrower) should come first
2090
+ return 1;
2091
+ }
2092
+ if (aInsideB) {
2093
+ // A (narrower) should come first
2094
+ return -1;
2095
+ }
2096
+
2097
+ // Neither contains the other - sort by start position
2098
+ return aStart - bStart;
2099
+ });
2100
+
2101
+ // Add a mapping for the very beginning of the file to handle import additions
2102
+ // This ensures that code actions adding imports at the top work correctly
2103
+ if (!isImportDeclarationPresent && mappings.length > 0 && mappings[0].sourceOffsets[0] > 0) {
2104
+ mappings.unshift({
2105
+ sourceOffsets: [0],
2106
+ generatedOffsets: [0],
2107
+ lengths: [1],
2108
+ generatedLengths: [1],
2109
+ data: {
2110
+ ...mapping_data,
2111
+ customData: {},
2112
+ },
2113
+ });
2114
+ }
2115
+
2116
+ /** @type {CodeMapping[]} */
2117
+ const cssMappings = [];
2118
+ for (let i = 0; i < css_regions.length; i++) {
2119
+ const region = css_regions[i];
2120
+ cssMappings.push({
2121
+ sourceOffsets: [region.start],
2122
+ generatedOffsets: [0],
2123
+ lengths: [region.content.length],
2124
+ generatedLengths: [region.content.length],
2125
+ data: {
2126
+ ...mapping_data,
2127
+ customData: {
2128
+ embeddedId: region.id,
2129
+ content: region.content,
2130
+ },
2131
+ },
2132
+ });
2133
+ }
2134
+
2135
+ return {
2136
+ code: generated_code,
2137
+ mappings,
2138
+ cssMappings,
2139
+ };
2140
+ }