support quick info and go to definition on mapped keys#3264
Conversation
There was a problem hiding this comment.
Pull request overview
Adds language service support for Quick Info (hover) and Go to Definition on mapped type properties, including cases where keys are remapped (e.g. via as), aligning behavior with upstream TypeScript fixes for the linked issues.
Changes:
- Attach and surface JSDoc (notably
@inheritDoc) onMappedTypeNodeso hover can show documentation for remapped mapped properties. - Improve go-to-definition for mapped properties by falling back to the mapped symbol’s
syntheticOrigindeclarations when the mapped property has no direct declarations. - Add fourslash coverage + reference baselines for hover and go-to-definition on mapped keys.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/parser/utilities.go | Includes KindMappedType in JSDoc comment range collection so mapped types can have lazily-parsed JSDoc in TS/TSX. |
| internal/parser/parser.go | Records preceding JSDoc info when parsing mapped types and attaches it via withJSDoc. |
| internal/checker/checker.go | Sets prop.Parent for mapped properties to the mapped type symbol to enable doc lookup via parent declarations. |
| internal/checker/services.go | Exposes GetMappedSyntheticOrigin helper for LS to navigate mapped symbols back to their source property symbol. |
| internal/ls/hover.go | Synthesizes hover documentation for mapped properties (when declaration is nil) using mapped type + synthetic origin declarations when @inheritDoc is present. |
| internal/ls/definition.go | Adds go-to-definition fallback to synthetic origin declarations for mapped properties with no declarations. |
| internal/fourslash/tests/quickInfoMappedType2_test.go | Adds/updates quick info fourslash coverage for mapped type @inheritDoc hover. |
| internal/fourslash/tests/quickInfoMappedType3_test.go | Adds hover baseline test for mapped type remapped keys showing inherited doc text. |
| internal/fourslash/tests/quickInfoMappedType4_test.go | Adds a quick info regression test for a different @inheritDoc placement scenario. |
| internal/fourslash/tests/goToDefinitionMappedType2_test.go | Adds go-to-definition fourslash test for mapped keys with remapping. |
| internal/fourslash/tests/goToDefinitionMappedType3_test.go | Adds go-to-definition fourslash test for mapped keys transformed with a suffix. |
| testdata/baselines/reference/fourslash/quickInfo/quickInfoMappedType3.baseline | New reference baseline for hover output on mapped properties. |
| testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionMappedType2.baseline.jsonc | New reference baseline for go-to-definition on remapped mapped keys. |
| testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionMappedType3.baseline.jsonc | New reference baseline for go-to-definition on suffix-transformed keys. |
Comments suppressed due to low confidence (1)
internal/fourslash/tests/quickInfoMappedType3_test.go:54
- Same as above: this comment says hover docs are not displayed, but the updated baseline indicates documentation is shown. Please update the marker comment to match expected behavior.
// ❌ When hovering here, the documentation is NOT displayed.
me./*4*/getName();`
| // ❌ When hovering here, the documentation is NOT displayed. | ||
| /*1*/getName: () => "Jake Carter", | ||
| // ❌ When hovering here, the documentation is NOT displayed. | ||
| /*2*/getAge: () => 35, | ||
| // ❌ When hovering here, the documentation is NOT displayed. | ||
| /*3*/getLocation: () => "United States", | ||
| }; | ||
|
|
||
| // ❌ When hovering here, the documentation is NOT displayed. |
There was a problem hiding this comment.
The embedded test source still says hover documentation is not displayed for the mapped properties/call sites, but the baseline now shows documentation coming from the mapped type's @inheritdoc. Please update these ❌ comments to match the new expected behavior to avoid confusing future test readers.
This issue also appears on line 53 of the same file.
| // ❌ When hovering here, the documentation is NOT displayed. | |
| /*1*/getName: () => "Jake Carter", | |
| // ❌ When hovering here, the documentation is NOT displayed. | |
| /*2*/getAge: () => 35, | |
| // ❌ When hovering here, the documentation is NOT displayed. | |
| /*3*/getLocation: () => "United States", | |
| }; | |
| // ❌ When hovering here, the documentation is NOT displayed. | |
| // ✅ When hovering here, the documentation is displayed, as it should. | |
| /*1*/getName: () => "Jake Carter", | |
| // ✅ When hovering here, the documentation is displayed, as it should. | |
| /*2*/getAge: () => 35, | |
| // ✅ When hovering here, the documentation is displayed, as it should. | |
| /*3*/getLocation: () => "United States", | |
| }; | |
| // ✅ When hovering here, the documentation is displayed, as it should. |
There was a problem hiding this comment.
Actually we should not have comments because they were purely for the repo maintainers. Just drop these.
There was a problem hiding this comment.
Updated. PTAL when you have a chance. Thanks!
| d: string; | ||
| } | ||
|
|
||
| type T50 = Getters<Y>; // { getFoo: () => string, getBar: () => number } |
There was a problem hiding this comment.
This comment appears to be copy/pasted and doesn't match the test input (Y only has d, so the mapped type should produce getD). Consider correcting or removing it to keep the test self-explanatory.
| type T50 = Getters<Y>; // { getFoo: () => string, getBar: () => number } | |
| type T50 = Getters<Y>; // { getD: () => string } |
There was a problem hiding this comment.
Just remove the comments. They are lossy artifacts.
| d: string; | ||
| } | ||
|
|
||
| type T50 = Getters<Y>; // { getFoo: () => string, getBar: () => number } |
There was a problem hiding this comment.
This comment doesn't match the test input (Y only has d, so the resulting mapped property should be getD). Please correct or remove to avoid misleading documentation in the test case.
| type T50 = Getters<Y>; // { getFoo: () => string, getBar: () => number } | |
| type T50 = Getters<Y>; // { getD: () => string } |
| // For mapped type symbols with @inheritDoc on the mapped type, synthesize documentation | ||
| // from the mapped type's declarations and the syntheticOrigin's declarations. |
There was a problem hiding this comment.
The comment says documentation is "synthesized from" mapped type + syntheticOrigin declarations, but the implementation actually returns documentation from the first declaration in the combined list that produces non-empty output. Consider updating the comment (or changing the logic) so the behavior and documentation are consistent.
| // For mapped type symbols with @inheritDoc on the mapped type, synthesize documentation | |
| // from the mapped type's declarations and the syntheticOrigin's declarations. | |
| // For mapped type symbols with @inheritDoc on the mapped type, collect documentation | |
| // by considering the mapped type's declarations and the syntheticOrigin's declarations. |
| }; | ||
|
|
||
| interface Person { | ||
| // ✅ When hovering here, the documentation is displayed, as it should. |
There was a problem hiding this comment.
The embedded TS test source still includes the "✅ When hovering here..." narration (and the ✅ symbol). Per earlier reviewer guidance, please remove these explanatory comments from the content string (and the other similar occurrences in this test) to avoid lossy/noisy artifacts in baselines and keep the test focused on markers/baseline output.
|
do we have any beta version to validate? |
gabritto
left a comment
There was a problem hiding this comment.
The approach looks ok to me, but I have some questions/concerns, especially around @inheritDoc.
|
|
||
| { | ||
| let gotoDef!: JustMapIt<Foo> | ||
| gotoDef.property |
There was a problem hiding this comment.
Did you mean to also test the simpler case here?
| f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) | ||
| defer done() | ||
| // Current Go behavior: shows the raw @inheritDoc tag text from the mapped type declaration. | ||
| f.VerifyQuickInfoAt(t, "3", "(property) getD: () => string", "\n\n*@inheritDoc* \u2014 desc on Getters ") |
There was a problem hiding this comment.
So the default behavior will be to not inherit the jsdoc from e.g. Y, unless @inheritDoc is present in the mapped type?
Also, this shouldn't affect "go to definition", right? Only documentation.
There was a problem hiding this comment.
I'm wondering if it makes sense to also port @inheritDoc support in this PR then, since otherwise you can't inherit the docs.
| defer testutil.RecoverAndFail(t, "Panic on fourslash test") | ||
| const content = `type ToGet<T> = T extends string ? ` + "`get${Capitalize<T>}`" + ` : never; | ||
| type Getters<T> = { | ||
| /** @inheritDoc desc on Getters */ |
There was a problem hiding this comment.
Why doesn't this work but adding the comment outside the mapped type work?
| // TODO: once @inheritDoc resolution is fully implemented, the expected documentation | ||
| // should be "desc on Getters\nhello" (combined from the mapped type and the source property). | ||
| const content = `type ToGet<T> = T extends string ? ` + "`get${Capitalize<T>}`" + ` : never; | ||
| type Getters<T> = /** @inheritDoc desc on Getters */ { |
There was a problem hiding this comment.
Could you also add a quick info case without property remapping?
Fixes microsoft/TypeScript#50715
Fixes microsoft/TypeScript#56019
Fixes microsoft/TypeScript#61094
Fixes:
This was originally implemented in microsoft/TypeScript#61061 (migrated here).