Skip to content

Commit 2a26cbd

Browse files
authored
Fix and simplify JSDoc handling for binding elements (#4029)
1 parent 94f31f3 commit 2a26cbd

4 files changed

Lines changed: 155 additions & 43 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/typescript-go/internal/fourslash"
7+
"github.com/microsoft/typescript-go/internal/testutil"
8+
)
9+
10+
func TestDestructuredIntersectionJSDoc(t *testing.T) {
11+
t.Parallel()
12+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
13+
const content = `
14+
type X = {
15+
/** Description of a. */
16+
a: {}
17+
}
18+
19+
type Y = X & { a: {} }
20+
21+
declare function f({ /*1*/a }: Y): void
22+
`
23+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
24+
defer done()
25+
f.VerifyBaselineHover(t)
26+
}
27+
28+
func TestDestructuredIntersectionJSDocVariable(t *testing.T) {
29+
t.Parallel()
30+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
31+
const content = `
32+
type X = {
33+
/** Description of a. */
34+
a: {}
35+
}
36+
37+
type Y = X & { a: {} }
38+
39+
declare const y: Y;
40+
const { /*1*/a } = y;
41+
`
42+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
43+
defer done()
44+
f.VerifyBaselineHover(t)
45+
}

internal/ls/hover.go

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -155,37 +155,9 @@ func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, sy
155155
if declaration == nil {
156156
return ""
157157
}
158-
159158
isMarkdown := contentFormat == lsproto.MarkupKindMarkdown
160159
var b strings.Builder
161-
jsdoc := getJSDocOrTag(c, declaration)
162-
163-
// Handle binding elements specially (variables created from destructuring) - we need to get the documentation from the property type
164-
// If the binding element doesn't have its own JSDoc, fall back to the property's JSDoc
165-
if jsdoc == nil && symbol != nil && symbol.ValueDeclaration != nil && ast.IsBindingElement(symbol.ValueDeclaration) && ast.IsIdentifier(location) {
166-
bindingElement := symbol.ValueDeclaration
167-
parent := bindingElement.Parent
168-
name := bindingElement.PropertyName()
169-
if name == nil {
170-
name = bindingElement.Name()
171-
}
172-
if ast.IsIdentifier(name) && ast.IsObjectBindingPattern(parent) {
173-
propertyName := name.Text()
174-
objectType := c.GetTypeAtLocation(parent)
175-
if objectType != nil {
176-
propertySymbol := findPropertyInType(c, objectType, propertyName)
177-
if propertySymbol != nil && propertySymbol.ValueDeclaration != nil {
178-
jsdoc = getJSDocOrTag(c, propertySymbol.ValueDeclaration)
179-
if jsdoc != nil {
180-
// Use property declaration for typedef check
181-
declaration = propertySymbol.ValueDeclaration
182-
}
183-
}
184-
}
185-
}
186-
}
187-
188-
if jsdoc != nil && !(declaration.Flags&ast.NodeFlagsReparsed == 0 && containsTypedefTag(jsdoc)) {
160+
if jsdoc := getJSDocOrTag(c, declaration); jsdoc != nil && !(declaration.Flags&ast.NodeFlagsReparsed == 0 && containsTypedefTag(jsdoc)) {
189161
l.writeComments(&b, c, jsdoc.Comments(), isMarkdown)
190162
if jsdoc.Kind == ast.KindJSDoc && !commentOnly {
191163
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
@@ -909,6 +881,18 @@ func getJSDocOrTag(c *checker.Checker, node *ast.Node) *ast.Node {
909881
case (ast.IsFunctionExpressionOrArrowFunction(node) || ast.IsClassExpression(node)) &&
910882
(ast.IsVariableDeclaration(node.Parent) || ast.IsPropertyDeclaration(node.Parent) || ast.IsPropertyAssignment(node.Parent)) && node.Parent.Initializer() == node:
911883
return getJSDocOrTag(c, node.Parent)
884+
case ast.IsBindingElement(node) && ast.IsObjectBindingPattern(node.Parent):
885+
if name := node.PropertyNameOrName(); ast.IsIdentifier(name) {
886+
if objectType := c.GetTypeAtLocation(node.Parent); objectType != nil {
887+
if prop := c.GetPropertyOfType(objectType, name.Text()); prop != nil {
888+
for _, d := range prop.Declarations {
889+
if jsdoc := getJSDoc(d); jsdoc != nil {
890+
return jsdoc
891+
}
892+
}
893+
}
894+
}
895+
}
912896
}
913897
if symbol := node.Symbol(); symbol != nil && node.Parent != nil {
914898
if ast.IsFunctionDeclaration(node) || ast.IsMethodDeclaration(node) || ast.IsMethodSignatureDeclaration(node) || ast.IsConstructorDeclaration(node) || ast.IsConstructSignatureDeclaration(node) {
@@ -1135,20 +1119,6 @@ func writeQuotedString(b *strings.Builder, str string, quote bool) {
11351119
}
11361120
}
11371121

1138-
// findPropertyInType finds a property in a type, handling union types by searching constituent types
1139-
func findPropertyInType(c *checker.Checker, objectType *checker.Type, propertyName string) *ast.Symbol {
1140-
// For union types, try to find the property in any of the constituent types
1141-
if objectType.IsUnion() {
1142-
for _, t := range objectType.Types() {
1143-
if prop := c.GetPropertyOfType(t, propertyName); prop != nil {
1144-
return prop
1145-
}
1146-
}
1147-
return nil
1148-
}
1149-
return c.GetPropertyOfType(objectType, propertyName)
1150-
}
1151-
11521122
func getEntityNameString(name *ast.Node) string {
11531123
var b strings.Builder
11541124
writeEntityNameParts(&b, name)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// === QuickInfo ===
2+
=== /destructuredIntersectionJSDoc.ts ===
3+
//
4+
// type X = {
5+
// /** Description of a. */
6+
// a: {}
7+
// }
8+
//
9+
// type Y = X & { a: {} }
10+
//
11+
// declare function f({ a }: Y): void
12+
// ^
13+
// | ----------------------------------------------------------------------
14+
// | ```typescript
15+
// | (parameter) a: {}
16+
// | ```
17+
// | Description of a.
18+
// | ----------------------------------------------------------------------
19+
//
20+
[
21+
{
22+
"marker": {
23+
"Position": 99,
24+
"LSPosition": {
25+
"line": 8,
26+
"character": 21
27+
},
28+
"Name": "1",
29+
"Data": {}
30+
},
31+
"item": {
32+
"contents": {
33+
"kind": "markdown",
34+
"value": "```typescript\n(parameter) a: {}\n```\nDescription of a."
35+
},
36+
"range": {
37+
"start": {
38+
"line": 8,
39+
"character": 21
40+
},
41+
"end": {
42+
"line": 8,
43+
"character": 22
44+
}
45+
}
46+
}
47+
}
48+
]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// === QuickInfo ===
2+
=== /destructuredIntersectionJSDocVariable.ts ===
3+
//
4+
// type X = {
5+
// /** Description of a. */
6+
// a: {}
7+
// }
8+
//
9+
// type Y = X & { a: {} }
10+
//
11+
// declare const y: Y;
12+
// const { a } = y;
13+
// ^
14+
// | ----------------------------------------------------------------------
15+
// | ```typescript
16+
// | const a: {}
17+
// | ```
18+
// | Description of a.
19+
// | ----------------------------------------------------------------------
20+
//
21+
[
22+
{
23+
"marker": {
24+
"Position": 106,
25+
"LSPosition": {
26+
"line": 9,
27+
"character": 8
28+
},
29+
"Name": "1",
30+
"Data": {}
31+
},
32+
"item": {
33+
"contents": {
34+
"kind": "markdown",
35+
"value": "```typescript\nconst a: {}\n```\nDescription of a."
36+
},
37+
"range": {
38+
"start": {
39+
"line": 9,
40+
"character": 8
41+
},
42+
"end": {
43+
"line": 9,
44+
"character": 9
45+
}
46+
}
47+
}
48+
}
49+
]

0 commit comments

Comments
 (0)