diff --git a/internal/fourslash/tests/destructuredInterfaceJSDoc_test.go b/internal/fourslash/tests/destructuredInterfaceJSDoc_test.go new file mode 100644 index 0000000000..8981a1a6db --- /dev/null +++ b/internal/fourslash/tests/destructuredInterfaceJSDoc_test.go @@ -0,0 +1,53 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestDestructuredInterfaceJSDoc(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` +interface FooBar { + /** foo comment */ + foo: number; + /** bar comment */ + bar: string; + /** baz comment */ + baz: string; +} + +declare const fubar: FooBar; + +const {/*1*/foo, /*2*/bar, /*3*/baz} = fubar; +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyQuickInfoAt(t, "1", "const foo: number", "foo comment") + f.VerifyQuickInfoAt(t, "2", "const bar: string", "bar comment") + f.VerifyQuickInfoAt(t, "3", "const baz: string", "baz comment") +} + +func TestDestructuredInterfaceJSDocWithRename(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` +interface FooBar { + /** foo comment */ + foo: number; + /** bar comment */ + bar: string; +} + +declare const fubar: FooBar; + +const {foo: /*1*/myFoo, bar: /*2*/myBar} = fubar; +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyQuickInfoAt(t, "1", "const myFoo: number", "foo comment") + f.VerifyQuickInfoAt(t, "2", "const myBar: string", "bar comment") +} diff --git a/internal/ls/hover.go b/internal/ls/hover.go index 6c8c644adc..172269c231 100644 --- a/internal/ls/hover.go +++ b/internal/ls/hover.go @@ -67,10 +67,31 @@ func (l *LanguageService) getQuickInfoAndDocumentationForSymbol(c *checker.Check if quickInfo == "" { return "", "" } - return quickInfo, l.getDocumentationFromDeclaration(c, declaration, contentFormat) + return quickInfo, l.getDocumentationFromDeclaration(c, symbol, declaration, node, contentFormat) } -func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, declaration *ast.Node, contentFormat lsproto.MarkupKind) string { +func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, symbol *ast.Symbol, declaration *ast.Node, location *ast.Node, contentFormat lsproto.MarkupKind) string { + // Handle binding elements specially (variables created from destructuring) - we need to get the documentation from the property type + // The declaration passed in might be the binding element itself, but we need the interface property declaration + if symbol != nil && symbol.ValueDeclaration != nil && ast.IsBindingElement(symbol.ValueDeclaration) && ast.IsIdentifier(location) { + bindingElement := symbol.ValueDeclaration + parent := bindingElement.Parent + name := bindingElement.PropertyName() + if name == nil { + name = bindingElement.Name() + } + if ast.IsIdentifier(name) && ast.IsObjectBindingPattern(parent) { + propertyName := name.Text() + objectType := c.GetTypeAtLocation(parent) + if objectType != nil { + propertySymbol := findPropertyInType(c, objectType, propertyName) + if propertySymbol != nil && propertySymbol.ValueDeclaration != nil { + declaration = propertySymbol.ValueDeclaration + } + } + } + } + if declaration == nil { return "" } @@ -582,6 +603,20 @@ func writeQuotedString(b *strings.Builder, str string, quote bool) { } } +// findPropertyInType finds a property in a type, handling union types by searching constituent types +func findPropertyInType(c *checker.Checker, objectType *checker.Type, propertyName string) *ast.Symbol { + // For union types, try to find the property in any of the constituent types + if objectType.IsUnion() { + for _, t := range objectType.Types() { + if prop := c.GetPropertyOfType(t, propertyName); prop != nil { + return prop + } + } + return nil + } + return c.GetPropertyOfType(objectType, propertyName) +} + func getEntityNameString(name *ast.Node) string { var b strings.Builder writeEntityNameParts(&b, name) diff --git a/internal/ls/signaturehelp.go b/internal/ls/signaturehelp.go index c404a764bd..68bc385e6d 100644 --- a/internal/ls/signaturehelp.go +++ b/internal/ls/signaturehelp.go @@ -428,7 +428,7 @@ func (l *LanguageService) getSignatureHelpItem(candidate *checker.Signature, isT // Generate documentation from the signature's declaration var documentation *string if declaration := candidate.Declaration(); declaration != nil { - doc := l.getDocumentationFromDeclaration(c, declaration, docFormat) + doc := l.getDocumentationFromDeclaration(c, nil, declaration, nil, docFormat) if doc != "" { documentation = &doc } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName03.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName03.baseline index 0e0a6ea1c5..05542c9250 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName03.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName03.baseline @@ -14,7 +14,7 @@ // | ```tsx // | var foo: string // | ``` -// | +// | A description of foo // | ---------------------------------------------------------------------- // } [ @@ -31,7 +31,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar foo: string\n```\n" + "value": "```tsx\nvar foo: string\n```\nA description of foo" }, "range": { "start": { diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName04.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName04.baseline index 926bfdf720..049f680029 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName04.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName04.baseline @@ -19,7 +19,7 @@ // | ```tsx // | var a: { b: string; } // | ``` -// | +// | A description of 'a' // | ---------------------------------------------------------------------- // b; // ^ @@ -27,7 +27,7 @@ // | ```tsx // | var b: string // | ``` -// | +// | A description of 'b' // | ---------------------------------------------------------------------- // } [ @@ -44,7 +44,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar a: { b: string; }\n```\n" + "value": "```tsx\nvar a: { b: string; }\n```\nA description of 'a'" }, "range": { "start": { @@ -71,7 +71,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar b: string\n```\n" + "value": "```tsx\nvar b: string\n```\nA description of 'b'" }, "range": { "start": { diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName05.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName05.baseline index 8ba464b2a4..68f0adb544 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName05.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName05.baseline @@ -17,7 +17,7 @@ // | ```tsx // | var a: string | number // | ``` -// | +// | A description of a // | ---------------------------------------------------------------------- // } [ @@ -34,7 +34,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar a: string | number\n```\n" + "value": "```tsx\nvar a: string | number\n```\nA description of a" }, "range": { "start": { diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName06.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName06.baseline index 71b808900c..a0431ca1be 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName06.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForObjectBindingElementName06.baseline @@ -23,7 +23,7 @@ // | ```tsx // | const isBar: boolean // | ``` -// | +// | Thing is a baz // | ---------------------------------------------------------------------- [ { @@ -39,7 +39,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nconst isBar: boolean\n```\n" + "value": "```tsx\nconst isBar: boolean\n```\nThing is a baz" }, "range": { "start": {