You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(core, ts-plugin): replace secondaryMapping with CustomSourceMap
Remove the secondaryMapping workaround for the Single-Quote Span Problem and
replace it with a custom Mapper that strips outer quote characters when a
direct range lookup fails. The primary mapping now covers only the inner
token name, while CustomSourceMap (registered via language.mapperFactory)
handles getDefinitionAtPosition spans that include the surrounding quotes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `data: { navigation: true }` flag enables navigation features (Go to Definition, Find References, Rename) for these mappings.
214
-
215
-
Volar.js searches mappings **in order**. The primary mapping (without quotes) is checked first; if it cannot resolve the position, the secondary mapping (with quotes) is used as fallback.
198
+
The `data: { navigation: true }` flag enables navigation features (Go to Definition, Find References, Rename) for this mapping.
216
199
217
200
## How Volar.js Translates Positions
218
201
@@ -330,7 +313,7 @@ The issue is that `findReferences` returns the position of `a` (offset 27), whic
330
313
331
314
### Why overlapping mappings in a single object don't work
332
315
333
-
Putting both mappings in the same CodeMapping object:
316
+
Putting both ranges in the same CodeMapping object:
334
317
335
318
```
336
319
Mapping: {
@@ -345,39 +328,41 @@ This doesn't work because Volar.js does not support overlapping ranges within a
### The solution: separate mapping objects with priority
331
+
### The solution: a custom mapper that strips outer quotes on fallback
349
332
350
-
CSS Modules Kit uses **two separate mapping objects**:
333
+
As a rule, CSS Modules Kit registers mappings that **do not include the surrounding quotes**, and instead swaps Volar.js's default mapper for a custom one that retries with outer characters stripped when the direct lookup fails. The custom mapper is installed by overriding `language.mapperFactory`:
351
334
352
335
```ts
353
-
// Primary mapping (checked first): maps the unquoted token name
-**`getDefinitionAtPosition`** (start=26, length=5): the inner mapping doesn't match (26 < 27). The fallback retries with `toSourceRange(27, 29, …)`, which matches → returns `1`. Correct!
Note: Despite the names `sourceOffsets` and `generatedOffsets`, both refer to positions within the same generated `.d.ts` file. The names are interchangeable for `linkedCodeMappings`.
417
402
403
+
**Why the ranges include the surrounding quotes:** When `getDefinitionAtPosition` is invoked on a quoted property name, TypeScript returns a `textSpan` that includes the surrounding single quotes (see [The Single-Quote Span Problem](#the-single-quote-span-problem)). To resolve links between related positions, Volar.js feeds that raw `textSpan.start` into `LinkedCodeMap.getLinkedOffsets` — and this path does **not** go through `mapperFactory`, so the `CustomSourceMap` quote-strip fallback cannot help here. For the linked-code lookup to match, the offsets stored in `linkedCodeMappings` must themselves cover the opening quote.
404
+
418
405
### How LinkedCodeMap works
419
406
420
407
The `LinkedCodeMap` class (in `@volar/language-core`) extends `SourceMap` and provides `getLinkedOffsets(start)`:
@@ -458,7 +445,7 @@ The proxy in `packages/ts-plugin/src/language-service/proxy.ts` adds CSS-specifi
|`getDefinitionAndBoundSpan`| Adds `contextSpan` for Definition Preview (shows the full CSS rule) |
461
-
|`findReferences`| Merges duplicate `ReferencedSymbol`s caused by multiple mappings|
448
+
|`findReferences`| Merges `ReferencedSymbol`s that share the same definition |
462
449
|`getCompletionsAtPosition`| Prioritizes `styles` import, converts `className` to JSX format, filters named exports |
463
450
|`getCompletionEntryDetails`| Converts default imports to namespace imports for CSS Modules |
464
451
|`getSyntacticDiagnostics`| Reports parse-time diagnostics that do not overlap with the standard CSS LS |
@@ -479,64 +466,3 @@ Some features are implemented as custom protocol handlers rather than relying on
479
466
|`_css-modules-kit:documentLink`| Returns import specifier positions as document links |
480
467
481
468
These handlers do **not** bypass CSS Modules Kit's own proxied Language Service. They call `project.getLanguageService()`, and that service has already been wrapped by both Volar.js and CSS Modules Kit during plugin setup.
In Volar.js, `transform.ts` contains the low-level translation logic, while `proxyLanguageService.ts` decides how each Language Service API uses it. Key differences:
486
-
487
-
### fallbackToAnyMatch parameter
488
-
489
-
Different APIs use different strictness for mapping resolution:
|`getDefinitionAtPosition`|`true`| Cross-file definitions need loose matching |
494
-
|`findReferences`|`true`| References may span multiple mappings |
495
-
|`findRenameLocations`|`false`| Strict position matching for safety |
496
-
|`getCompletionsAtPosition`|`false`| Precise position needed |
497
-
| Diagnostics |`true`| Report even with loose mapping |
498
-
499
-
### Empty span fallback
500
-
501
-
When Volar.js cannot find a valid mapping for a span, some APIs fall back to `{ start: 0, length: 0 }` instead of dropping the result entirely. This happens in `transformDocumentSpan` when `shouldFallback` is true (typically for cross-file definitions).
502
-
503
-
### CodeInformation filters
504
-
505
-
Each Language Service API uses a different filter function to select which mappings to search:
506
-
507
-
- Navigation features (definition, references, rename): truthy `data.navigation`
508
-
- Completion: truthy `data.completion`
509
-
- Diagnostics: truthy `data.verification`
510
-
- Semantic features (hover, inlay hints): truthy `data.semantic`
511
-
512
-
CSS Modules Kit registers all mappings with `{ navigation: true }`, enabling them for navigation features only.
513
-
514
-
## Summary of data flow
515
-
516
-
### Go to Definition (from .ts file)
517
-
518
-
```
519
-
1. User triggers "Go to Definition" on `styles.a_1` in index.ts
520
-
2. Volar.js receives the request with position in index.ts
521
-
3. TypeScript LS returns definition span in generated .d.ts (e.g., { start: 26, length: 5 })
522
-
4. Volar.js translates span back to .module.css using CodeMapping
523
-
- Tries primary mapping first (offset 27) → no match for start=26
524
-
- Falls back to secondary mapping (offset 26) → matches → source position 1
0 commit comments