Skip to content

support quick info and go to definition on mapped keys#3264

Open
Zzzen wants to merge 3 commits into
microsoft:mainfrom
Zzzen:quick-info-on-remapped-props
Open

support quick info and go to definition on mapped keys#3264
Zzzen wants to merge 3 commits into
microsoft:mainfrom
Zzzen:quick-info-on-remapped-props

Conversation

@Zzzen

@Zzzen Zzzen commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) on MappedTypeNode so hover can show documentation for remapped mapped properties.
  • Improve go-to-definition for mapped properties by falling back to the mapped symbol’s syntheticOrigin declarations 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();`

Comment on lines +45 to +53
// ❌ 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.

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.

@DanielRosenwasser DanielRosenwasser Mar 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we should not have comments because they were purely for the repo maintainers. Just drop these.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. PTAL when you have a chance. Thanks!

d: string;
}

type T50 = Getters<Y>; // { getFoo: () => string, getBar: () => number }

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
type T50 = Getters<Y>; // { getFoo: () => string, getBar: () => number }
type T50 = Getters<Y>; // { getD: () => string }

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just remove the comments. They are lossy artifacts.

d: string;
}

type T50 = Getters<Y>; // { getFoo: () => string, getBar: () => number }

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
type T50 = Getters<Y>; // { getFoo: () => string, getBar: () => number }
type T50 = Getters<Y>; // { getD: () => string }

Copilot uses AI. Check for mistakes.
Comment thread internal/ls/hover.go Outdated
Comment on lines +74 to +75
// For mapped type symbols with @inheritDoc on the mapped type, synthesize documentation
// from the mapped type's declarations and the syntheticOrigin's declarations.

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
Comment thread internal/parser/parser.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

};

interface Person {
// ✅ When hovering here, the documentation is displayed, as it should.

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@geekeren

Copy link
Copy Markdown

do we have any beta version to validate?

@gabritto gabritto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach looks ok to me, but I have some questions/concerns, especially around @inheritDoc.


{
let gotoDef!: JustMapIt<Foo>
gotoDef.property

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 */ {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a quick info case without property remapping?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

6 participants