From 902b6f455c6f5b2ae2c4f645a3300dfb538cc948 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Wed, 15 Jul 2026 16:21:57 +0800 Subject: [PATCH] Fix hover documentation for intersected properties --- ...rIntersectionPropertyDocumentation_test.go | 34 +++++++++++++++++++ internal/ls/hover.go | 7 ++++ internal/ls/jsdoc.go | 13 +++++-- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 internal/fourslash/tests/hoverIntersectionPropertyDocumentation_test.go diff --git a/internal/fourslash/tests/hoverIntersectionPropertyDocumentation_test.go b/internal/fourslash/tests/hoverIntersectionPropertyDocumentation_test.go new file mode 100644 index 00000000000..ab07ac61f27 --- /dev/null +++ b/internal/fourslash/tests/hoverIntersectionPropertyDocumentation_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestHoverIntersectionPropertyDocumentation(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + + const content = ` +interface A { + /** interface A */ + prop: () => number; +} + +interface B { + /** interface B */ + prop: () => number; +} + +type C = A & B; +declare const c: C; + +c./*1*/prop; +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + + f.VerifyQuickInfoAt(t, "1", "(property) prop: () => number", "interface A\ninterface B") +} diff --git a/internal/ls/hover.go b/internal/ls/hover.go index da4f11883c0..a156c4df85e 100644 --- a/internal/ls/hover.go +++ b/internal/ls/hover.go @@ -101,6 +101,13 @@ func (l *LanguageService) getQuickInfoAndDocumentationForSymbol(c *checker.Check if documentation != "" { return quickInfo, documentation } + // Synthetic union and intersection properties can have declarations without a value declaration. + if info.declaration == nil && symbol != nil { + documentation = l.getDocumentationFromDeclarations(c, symbol, symbol.Declarations, node, contentFormat, false /*commentOnly*/) + if documentation != "" { + return quickInfo, documentation + } + } return quickInfo, l.documentationFromAlias(c, symbol, node, contentFormat) } diff --git a/internal/ls/jsdoc.go b/internal/ls/jsdoc.go index a5992fa7358..40671d47bb2 100644 --- a/internal/ls/jsdoc.go +++ b/internal/ls/jsdoc.go @@ -28,16 +28,25 @@ func (l *LanguageService) GetSymbolDocumentationComment(c *checker.Checker, symb if symbol == nil { return "" } + return l.getDocumentationFromDeclarations(c, symbol, symbol.Declarations, nil, lsproto.MarkupKindPlainText, true /*commentOnly*/) +} + +// getDocumentationFromDeclarations gathers and deduplicates documentation from a set of declarations. +func (l *LanguageService) getDocumentationFromDeclarations(c *checker.Checker, symbol *ast.Symbol, declarations []*ast.Node, location *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { var parts []string var seen collections.Set[*ast.Node] - for _, decl := range symbol.Declarations { + for _, decl := range declarations { if decl == nil { continue } if !seen.AddIfAbsent(decl) { continue } - if doc := l.getDocumentationFromDeclaration(c, symbol, decl, decl, lsproto.MarkupKindPlainText, true /*commentOnly*/); doc != "" && !slices.Contains(parts, doc) { + documentationLocation := location + if documentationLocation == nil { + documentationLocation = decl + } + if doc := l.getDocumentationFromDeclaration(c, symbol, decl, documentationLocation, contentFormat, commentOnly); doc != "" && !slices.Contains(parts, doc) { parts = append(parts, doc) } }