Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,7 @@ func (c *Checker) ResolveName(name string, location *ast.Node, meaning ast.Symbo
func (c *Checker) GetSymbolFlags(symbol *ast.Symbol) ast.SymbolFlags {
return c.getSymbolFlags(symbol)
}

func (c *Checker) GetBaseTypes(t *Type) []*Type {
return c.getBaseTypes(t)
}
1 change: 1 addition & 0 deletions internal/fourslash/_scripts/manualTests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ completionsAtIncompleteObjectLiteralProperty
completionsSelfDeclaring1
completionsWithDeprecatedTag4
parserCorruptionAfterMapInClass
quickInfoForOverloadOnConst1
renameDefaultKeyword
renameForDefaultExport01
tsxCompletion12
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ c.x1(1, (x/*10*/x) => { return 1; } );`
f.VerifyQuickInfoAt(t, "1", "(method) I.x1(a: number, callback: (x: \"hi\") => number): any", "")
f.VerifyQuickInfoAt(t, "2", "(method) C.x1(a: number, callback: (x: \"hi\") => number): any", "")
f.VerifyQuickInfoAt(t, "3", "(parameter) callback: (x: \"hi\") => number", "")
f.VerifyQuickInfoAt(t, "4", "(method) C.x1(a: number, callback: (x: \"hi\") => number): any", "")
f.VerifyQuickInfoAt(t, "4", "(method) C.x1(a: number, callback: (x: string) => number): void", "")
f.VerifyQuickInfoAt(t, "5", "(parameter) callback: (x: string) => number", "")
f.VerifyQuickInfoAt(t, "6", "(parameter) callback: (x: string) => number", "")
f.VerifyQuickInfoAt(t, "7", "(method) C.x1(a: number, callback: (x: \"hi\") => number): any", "")
Expand Down
87 changes: 53 additions & 34 deletions internal/ls/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, de
}
isMarkdown := contentFormat == lsproto.MarkupKindMarkdown
var b strings.Builder
if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) {
if jsdoc := getJSDocOrTag(c, declaration); jsdoc != nil && !containsTypedefTag(jsdoc) {
l.writeComments(&b, c, jsdoc.Comments(), isMarkdown)
if jsdoc.Kind == ast.KindJSDoc {
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
Expand Down Expand Up @@ -143,14 +143,6 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol
return "", nil
}
declaration := symbol.ValueDeclaration
if symbol.Flags&ast.SymbolFlagsClass != 0 && inConstructorContext(node) {
if s := symbol.Members[ast.InternalSymbolNameConstructor]; s != nil {
symbol = s
declaration = core.Find(symbol.Declarations, func(d *ast.Node) bool {
return ast.IsConstructorDeclaration(d) || ast.IsConstructSignatureDeclaration(d)
})
}
}
flags := symbol.Flags
if flags&ast.SymbolFlagsProperty != 0 && declaration != nil && ast.IsMethodDeclaration(declaration) {
flags = ast.SymbolFlagsMethod
Expand Down Expand Up @@ -209,30 +201,43 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol
b.WriteString(t.AsLiteralType().String())
}
case flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod) != 0:
signatures := getSignaturesAtLocation(c, symbol, checker.SignatureKindCall, node)
if len(signatures) == 1 {
if d := signatures[0].Declaration(); d != nil && d.Flags&ast.NodeFlagsJSDoc == 0 {
declaration = d
}
}
prefix := core.IfElse(flags&ast.SymbolFlagsMethod != 0, "(method) ", "function ")
writeSignatures(&b, c, signatures, container, prefix, symbol)
case flags&ast.SymbolFlagsConstructor != 0:
signatures := getSignaturesAtLocation(c, symbol.Parent, checker.SignatureKindConstruct, node)
if len(signatures) == 1 {
if d := signatures[0].Declaration(); d != nil && d.Flags&ast.NodeFlagsJSDoc == 0 {
declaration = d
if ast.IsIdentifier(node) && ast.IsFunctionLikeDeclaration(node.Parent) && node.Parent.Name() == node {
declaration = node.Parent
signatures := []*checker.Signature{c.GetSignatureFromDeclaration(declaration)}
writeSignatures(&b, c, signatures, container, prefix, symbol)
} else {
signatures := getSignaturesAtLocation(c, symbol, checker.SignatureKindCall, node)
if len(signatures) == 1 {
if d := signatures[0].Declaration(); d != nil && d.Flags&ast.NodeFlagsJSDoc == 0 {
declaration = d
}
}
writeSignatures(&b, c, signatures, container, prefix, symbol)
}
writeSignatures(&b, c, signatures, container, "constructor ", symbol.Parent)
case flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0:
if node.Kind == ast.KindThisKeyword || ast.IsThisInTypeQuery(node) {
b.WriteString("this")
} else if node.Kind == ast.KindConstructorKeyword && (ast.IsConstructorDeclaration(node.Parent) || ast.IsConstructSignatureDeclaration(node.Parent)) {
declaration = node.Parent
signatures := []*checker.Signature{c.GetSignatureFromDeclaration(declaration)}
writeSignatures(&b, c, signatures, container, "constructor ", symbol)
} else {
b.WriteString(core.IfElse(flags&ast.SymbolFlagsClass != 0, "class ", "interface "))
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
params := c.GetDeclaredTypeOfSymbol(symbol).AsInterfaceType().LocalTypeParameters()
writeTypeParams(&b, c, params)
var signatures []*checker.Signature
if flags&ast.SymbolFlagsClass != 0 && getCallOrNewExpression(node) != nil {
signatures = getSignaturesAtLocation(c, symbol, checker.SignatureKindConstruct, node)
Comment on lines +227 to +228
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The condition getCallOrNewExpression(node) != nil matches both call and new expressions, but construct signatures should only be retrieved for new expressions. When hovering over a class name in a call expression context (e.g., MyClass()), this code would incorrectly attempt to get construct signatures. Consider checking specifically for new expressions using ast.IsNewExpression(callOrNewExpr) to ensure construct signatures are only shown in the appropriate context.

Suggested change
if flags&ast.SymbolFlagsClass != 0 && getCallOrNewExpression(node) != nil {
signatures = getSignaturesAtLocation(c, symbol, checker.SignatureKindConstruct, node)
if flags&ast.SymbolFlagsClass != 0 {
callOrNewExpr := getCallOrNewExpression(node)
if callOrNewExpr != nil && ast.IsNewExpression(callOrNewExpr) {
signatures = getSignaturesAtLocation(c, symbol, checker.SignatureKindConstruct, node)
}

Copilot uses AI. Check for mistakes.
}
if len(signatures) == 1 {
if d := signatures[0].Declaration(); d != nil && d.Flags&ast.NodeFlagsJSDoc == 0 {
declaration = d
}
writeSignatures(&b, c, signatures, container, "constructor ", symbol)
} else {
b.WriteString(core.IfElse(flags&ast.SymbolFlagsClass != 0, "class ", "interface "))
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
params := c.GetDeclaredTypeOfSymbol(symbol).AsInterfaceType().LocalTypeParameters()
writeTypeParams(&b, c, params)
}
}
if flags&ast.SymbolFlagsInterface != 0 {
declaration = core.Find(symbol.Declarations, ast.IsInterfaceDeclaration)
Expand Down Expand Up @@ -381,7 +386,7 @@ func containsTypedefTag(jsdoc *ast.Node) bool {
if jsdoc.Kind == ast.KindJSDoc {
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if tag.Kind == ast.KindJSDocTypedefTag {
if tag.Kind == ast.KindJSDocTypedefTag || tag.Kind == ast.KindJSDocCallbackTag {
return true
}
}
Expand All @@ -398,26 +403,40 @@ func getJSDoc(node *ast.Node) *ast.Node {
return core.LastOrNil(node.JSDoc(nil))
}

func getJSDocOrTag(node *ast.Node) *ast.Node {
func getJSDocOrTag(c *checker.Checker, node *ast.Node) *ast.Node {
if jsdoc := getJSDoc(node); jsdoc != nil {
return jsdoc
}
switch {
case ast.IsParameter(node):
return getMatchingJSDocTag(node.Parent, node.Name().Text(), isMatchingParameterTag)
return getMatchingJSDocTag(c, node.Parent, node.Name().Text(), isMatchingParameterTag)
case ast.IsTypeParameterDeclaration(node):
return getMatchingJSDocTag(node.Parent, node.Name().Text(), isMatchingTemplateTag)
return getMatchingJSDocTag(c, node.Parent, node.Name().Text(), isMatchingTemplateTag)
case ast.IsVariableDeclaration(node) && ast.IsVariableDeclarationList(node.Parent) && core.FirstOrNil(node.Parent.AsVariableDeclarationList().Declarations.Nodes) == node:
return getJSDocOrTag(node.Parent.Parent)
return getJSDocOrTag(c, node.Parent.Parent)
case (ast.IsFunctionExpressionOrArrowFunction(node) || ast.IsClassExpression(node)) &&
(ast.IsVariableDeclaration(node.Parent) || ast.IsPropertyDeclaration(node.Parent) || ast.IsPropertyAssignment(node.Parent)) && node.Parent.Initializer() == node:
return getJSDocOrTag(node.Parent)
return getJSDocOrTag(c, node.Parent)
}
if symbol := node.Symbol(); symbol != nil && node.Parent != nil && ast.IsClassOrInterfaceLike(node.Parent) {
isStatic := ast.HasStaticModifier(node)
for _, baseType := range c.GetBaseTypes(c.GetDeclaredTypeOfSymbol(node.Parent.Symbol())) {
t := baseType
if isStatic {
t = c.GetTypeOfSymbol(baseType.Symbol())
}
if prop := c.GetPropertyOfType(t, symbol.Name); prop != nil && prop.ValueDeclaration != nil {
if jsDoc := getJSDocOrTag(c, prop.ValueDeclaration); jsDoc != nil {
return jsDoc
}
}
}
}
return nil
}

func getMatchingJSDocTag(node *ast.Node, name string, match func(*ast.Node, string) bool) *ast.Node {
if jsdoc := getJSDocOrTag(node); jsdoc != nil && jsdoc.Kind == ast.KindJSDoc {
func getMatchingJSDocTag(c *checker.Checker, node *ast.Node, name string, match func(*ast.Node, string) bool) *ast.Node {
if jsdoc := getJSDocOrTag(c, node); jsdoc != nil && jsdoc.Kind == ast.KindJSDoc {
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if match(tag, name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// | ```tsx
// | (method) B.method(): void
// | ```
// |
// | Method documentation.
// | ----------------------------------------------------------------------
[
{
Expand All @@ -34,7 +34,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) B.method(): void\n```\n"
"value": "```tsx\n(method) B.method(): void\n```\nMethod documentation."
},
"range": {
"start": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// | ```tsx
// | (method) B.method(): void
// | ```
// |
// | Method documentation.
// | ----------------------------------------------------------------------
[
{
Expand All @@ -34,7 +34,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) B.method(): void\n```\n"
"value": "```tsx\n(method) B.method(): void\n```\nMethod documentation."
},
"range": {
"start": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// ^^
// | ----------------------------------------------------------------------
// | ```tsx
// | class c2
// | constructor c2(): c2
// | ```
// | This is class c2 without constructor
// | ----------------------------------------------------------------------
Expand Down Expand Up @@ -147,7 +147,7 @@
// ^^
// | ----------------------------------------------------------------------
// | ```tsx
// | class c5
// | constructor c5(): c5
// | ```
// | Class with statics
// | ----------------------------------------------------------------------
Expand Down Expand Up @@ -307,7 +307,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nclass c2\n```\nThis is class c2 without constructor"
"value": "```tsx\nconstructor c2(): c2\n```\nThis is class c2 without constructor"
},
"range": {
"start": {
Expand Down Expand Up @@ -712,7 +712,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nclass c5\n```\nClass with statics"
"value": "```tsx\nconstructor c5(): c5\n```\nClass with statics"
},
"range": {
"start": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// ^
// | ----------------------------------------------------------------------
// | ```tsx
// | class c
// | constructor c(): c
// | ```
// |
// | ----------------------------------------------------------------------
Expand Down Expand Up @@ -107,7 +107,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nclass c\n```\n"
"value": "```tsx\nconstructor c(): c\n```\n"
},
"range": {
"start": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@
// | ----------------------------------------------------------------------
// | ```tsx
// | constructor cWithOverloads(x: string): cWithOverloads
// | constructor cWithOverloads(x: number): cWithOverloads
// | ```
// |
// | ----------------------------------------------------------------------
// constructor(x: number);
// ^^^^^^^^^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | constructor cWithOverloads(x: string): cWithOverloads
// | constructor cWithOverloads(x: number): cWithOverloads
// | ```
// |
Expand All @@ -64,8 +62,7 @@
// ^^^^^^^^^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | constructor cWithOverloads(x: string): cWithOverloads
// | constructor cWithOverloads(x: number): cWithOverloads
// | constructor cWithOverloads(x: any): cWithOverloads
// | ```
// |
// | ----------------------------------------------------------------------
Expand Down Expand Up @@ -122,27 +119,21 @@
// | ----------------------------------------------------------------------
// | ```tsx
// | constructor cWithMultipleOverloads(x: string): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: number): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads
// | ```
// |
// | ----------------------------------------------------------------------
// constructor(x: number);
// ^^^^^^^^^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | constructor cWithMultipleOverloads(x: string): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: number): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads
// | ```
// |
// | ----------------------------------------------------------------------
// constructor(x: boolean);
// ^^^^^^^^^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | constructor cWithMultipleOverloads(x: string): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: number): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads
// | ```
// |
Expand All @@ -151,9 +142,7 @@
// ^^^^^^^^^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | constructor cWithMultipleOverloads(x: string): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: number): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads
// | constructor cWithMultipleOverloads(x: any): cWithMultipleOverloads
// | ```
// |
// | ----------------------------------------------------------------------
Expand Down Expand Up @@ -368,7 +357,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nconstructor cWithOverloads(x: string): cWithOverloads\nconstructor cWithOverloads(x: number): cWithOverloads\n```\n"
"value": "```tsx\nconstructor cWithOverloads(x: string): cWithOverloads\n```\n"
},
"range": {
"start": {
Expand All @@ -395,7 +384,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nconstructor cWithOverloads(x: string): cWithOverloads\nconstructor cWithOverloads(x: number): cWithOverloads\n```\n"
"value": "```tsx\nconstructor cWithOverloads(x: number): cWithOverloads\n```\n"
},
"range": {
"start": {
Expand All @@ -422,7 +411,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nconstructor cWithOverloads(x: string): cWithOverloads\nconstructor cWithOverloads(x: number): cWithOverloads\n```\n"
"value": "```tsx\nconstructor cWithOverloads(x: any): cWithOverloads\n```\n"
},
"range": {
"start": {
Expand Down Expand Up @@ -611,7 +600,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nconstructor cWithMultipleOverloads(x: string): cWithMultipleOverloads\nconstructor cWithMultipleOverloads(x: number): cWithMultipleOverloads\nconstructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads\n```\n"
"value": "```tsx\nconstructor cWithMultipleOverloads(x: string): cWithMultipleOverloads\n```\n"
},
"range": {
"start": {
Expand All @@ -638,7 +627,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nconstructor cWithMultipleOverloads(x: string): cWithMultipleOverloads\nconstructor cWithMultipleOverloads(x: number): cWithMultipleOverloads\nconstructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads\n```\n"
"value": "```tsx\nconstructor cWithMultipleOverloads(x: number): cWithMultipleOverloads\n```\n"
},
"range": {
"start": {
Expand All @@ -665,7 +654,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nconstructor cWithMultipleOverloads(x: string): cWithMultipleOverloads\nconstructor cWithMultipleOverloads(x: number): cWithMultipleOverloads\nconstructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads\n```\n"
"value": "```tsx\nconstructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads\n```\n"
},
"range": {
"start": {
Expand All @@ -692,7 +681,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\nconstructor cWithMultipleOverloads(x: string): cWithMultipleOverloads\nconstructor cWithMultipleOverloads(x: number): cWithMultipleOverloads\nconstructor cWithMultipleOverloads(x: boolean): cWithMultipleOverloads\n```\n"
"value": "```tsx\nconstructor cWithMultipleOverloads(x: any): cWithMultipleOverloads\n```\n"
},
"range": {
"start": {
Expand Down
Loading