diff --git a/internal/api/encoder/encoder.go b/internal/api/encoder/encoder.go index 55812fd2ba..d143981778 100644 --- a/internal/api/encoder/encoder.go +++ b/internal/api/encoder/encoder.go @@ -635,7 +635,7 @@ func getChildrenPropertyMask(node *ast.Node) uint8 { return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1) case ast.KindJSDocTemplateTag: n := node.AsJSDocTemplateTag() - return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Constraint != nil) << 1) | (boolToByte(n.TypeParameters() != nil) << 2) | (boolToByte(n.Comment != nil) << 3) + return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Constraint != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Comment != nil) << 3) case ast.KindJSDocReturnTag: n := node.AsJSDocReturnTag() return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 0173465420..bcc72ac647 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -289,6 +289,8 @@ func (n *Node) Text() string { return n.AsJsxNamespacedName().Namespace.Text() + ":" + n.AsJsxNamespacedName().name.Text() case KindRegularExpressionLiteral: return n.AsRegularExpressionLiteral().Text + case KindJSDocText: + return n.AsJSDocText().Text } panic(fmt.Sprintf("Unhandled case in Node.Text: %T", n.data)) } @@ -429,6 +431,8 @@ func (n *Node) TypeParameterList() *NodeList { return n.AsInterfaceDeclaration().TypeParameters case KindTypeAliasDeclaration, KindJSTypeAliasDeclaration: return n.AsTypeAliasDeclaration().TypeParameters + case KindJSDocTemplateTag: + return n.AsJSDocTemplateTag().TypeParameters default: funcLike := n.FunctionLikeData() if funcLike != nil { @@ -9108,39 +9112,37 @@ func IsJSDocUnknownTag(node *Node) bool { type JSDocTemplateTag struct { JSDocTagBase Constraint *Node - typeParameters *TypeParameterList + TypeParameters *TypeParameterList } func (f *NodeFactory) NewJSDocTemplateTag(tagName *IdentifierNode, constraint *Node, typeParameters *TypeParameterList, comment *NodeList) *Node { data := &JSDocTemplateTag{} data.TagName = tagName data.Constraint = constraint - data.typeParameters = typeParameters + data.TypeParameters = typeParameters data.Comment = comment return f.newNode(KindJSDocTemplateTag, data) } func (f *NodeFactory) UpdateJSDocTemplateTag(node *JSDocTemplateTag, tagName *IdentifierNode, constraint *Node, typeParameters *TypeParameterList, comment *NodeList) *Node { - if tagName != node.TagName || constraint != node.Constraint || typeParameters != node.typeParameters || comment != node.Comment { + if tagName != node.TagName || constraint != node.Constraint || typeParameters != node.TypeParameters || comment != node.Comment { return updateNode(f.NewJSDocTemplateTag(tagName, constraint, typeParameters, comment), node.AsNode(), f.hooks) } return node.AsNode() } func (node *JSDocTemplateTag) ForEachChild(v Visitor) bool { - return visit(v, node.TagName) || visit(v, node.Constraint) || visitNodeList(v, node.typeParameters) || visitNodeList(v, node.Comment) + return visit(v, node.TagName) || visit(v, node.Constraint) || visitNodeList(v, node.TypeParameters) || visitNodeList(v, node.Comment) } func (node *JSDocTemplateTag) VisitEachChild(v *NodeVisitor) *Node { - return v.Factory.UpdateJSDocTemplateTag(node, v.visitNode(node.TagName), v.visitNode(node.Constraint), v.visitNodes(node.typeParameters), v.visitNodes(node.Comment)) + return v.Factory.UpdateJSDocTemplateTag(node, v.visitNode(node.TagName), v.visitNode(node.Constraint), v.visitNodes(node.TypeParameters), v.visitNodes(node.Comment)) } func (node *JSDocTemplateTag) Clone(f NodeFactoryCoercible) *Node { - return cloneNode(f.AsNodeFactory().NewJSDocTemplateTag(node.TagName, node.Constraint, node.TypeParameters(), node.Comment), node.AsNode(), f.AsNodeFactory().hooks) + return cloneNode(f.AsNodeFactory().NewJSDocTemplateTag(node.TagName, node.Constraint, node.TypeParameters, node.Comment), node.AsNode(), f.AsNodeFactory().hooks) } -func (node *JSDocTemplateTag) TypeParameters() *TypeParameterList { return node.typeParameters } - // JSDocPropertyTag type JSDocPropertyTag struct { JSDocTagBase diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 166d3063ed..c21c2bc1b4 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -3303,3 +3303,16 @@ func IsTypeDeclarationName(name *Node) bool { IsTypeDeclaration(name.Parent) && GetNameOfDeclaration(name.Parent) == name } + +func IsRightSideOfQualifiedNameOrPropertyAccess(node *Node) bool { + parent := node.Parent + switch parent.Kind { + case KindQualifiedName: + return parent.AsQualifiedName().Right == node + case KindPropertyAccessExpression: + return parent.AsPropertyAccessExpression().Name() == node + case KindMetaProperty: + return parent.AsMetaProperty().Name() == node + } + return false +} diff --git a/internal/binder/binder.go b/internal/binder/binder.go index 8828bf5617..5e3dcdbcda 100644 --- a/internal/binder/binder.go +++ b/internal/binder/binder.go @@ -1562,8 +1562,8 @@ func (b *Binder) bindChildren(node *ast.Node) { // and set it before we descend into nodes that could actually be part of an assignment pattern. b.inAssignmentPattern = false if b.checkUnreachable(node) { - b.bindEachChild(node) b.setJSDocParents(node) + b.bindEachChild(node) b.inAssignmentPattern = saveInAssignmentPattern return } @@ -1574,6 +1574,7 @@ func (b *Binder) bindChildren(node *ast.Node) { hasFlowNodeData.FlowNode = b.currentFlow } } + b.setJSDocParents(node) switch node.Kind { case ast.KindWhileStatement: b.bindWhileStatement(node) @@ -1653,7 +1654,6 @@ func (b *Binder) bindChildren(node *ast.Node) { default: b.bindEachChild(node) } - b.setJSDocParents(node) b.inAssignmentPattern = saveInAssignmentPattern } diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 9a1d547ac9..40c392e261 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -13692,7 +13692,7 @@ func (c *Checker) getSymbolOfPartOfRightHandSideOfImportEquals(entityName *ast.N // import a = |b|; // Namespace // import a = |b.c|; // Value, type, namespace // import a = |b.c|.d; // Namespace - if entityName.Kind == ast.KindIdentifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName) { + if entityName.Kind == ast.KindIdentifier && ast.IsRightSideOfQualifiedNameOrPropertyAccess(entityName) { entityName = entityName.Parent // QualifiedName } // Check for case 1 and 3 in the above example @@ -15201,7 +15201,7 @@ func (c *Checker) GetTypeOfSymbolAtLocation(symbol *ast.Symbol, location *ast.No // of the expression (which will reflect control flow analysis). If the expression indeed // resolved to the given symbol, return the narrowed type. if ast.IsIdentifier(location) || ast.IsPrivateIdentifier(location) { - if isRightSideOfQualifiedNameOrPropertyAccess(location) { + if ast.IsRightSideOfQualifiedNameOrPropertyAccess(location) { location = location.Parent } if ast.IsExpressionNode(location) && (!ast.IsAssignmentTarget(location) || isWriteAccess(location)) { @@ -21554,7 +21554,7 @@ func (c *Checker) getUnresolvedSymbolForEntityName(name *ast.Node) *ast.Symbol { result = c.newSymbolEx(ast.SymbolFlagsTypeAlias, text, ast.CheckFlagsUnresolved) c.unresolvedSymbols[path] = result result.Parent = parentSymbol - c.declaredTypeLinks.Get(result).declaredType = c.unresolvedType + c.typeAliasLinks.Get(result).declaredType = c.unresolvedType } return result } @@ -29682,7 +29682,7 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast } } - for isRightSideOfQualifiedNameOrPropertyAccess(name) { + for ast.IsRightSideOfQualifiedNameOrPropertyAccess(name) { name = name.Parent } @@ -29975,7 +29975,7 @@ func (c *Checker) getApplicableIndexSymbol(t *Type, keyType *Type) *ast.Symbol { } func (c *Checker) getRegularTypeOfExpression(expr *ast.Node) *Type { - if isRightSideOfQualifiedNameOrPropertyAccess(expr) { + if ast.IsRightSideOfQualifiedNameOrPropertyAccess(expr) { expr = expr.Parent } return c.getRegularTypeOfLiteralType(c.getTypeOfExpression(expr)) diff --git a/internal/checker/printer.go b/internal/checker/printer.go index a96b5b291f..4b668e2c16 100644 --- a/internal/checker/printer.go +++ b/internal/checker/printer.go @@ -276,7 +276,7 @@ func (c *Checker) SignatureToStringEx(signature *Signature, enclosingDeclaration } func (c *Checker) signatureToStringEx(signature *Signature, enclosingDeclaration *ast.Node, flags TypeFormatFlags) string { - isConstructor := signature.flags&SignatureFlagsConstruct != 0 + isConstructor := signature.flags&SignatureFlagsConstruct != 0 && flags&TypeFormatFlagsWriteCallStyleSignature == 0 var sigOutput ast.Kind if flags&TypeFormatFlagsWriteArrowStyleSignature != 0 { if isConstructor { diff --git a/internal/checker/types.go b/internal/checker/types.go index 6cabb30449..6a2ace4023 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -63,6 +63,7 @@ const ( TypeFormatFlagsUseSingleQuotesForStringLiteralType TypeFormatFlags = 1 << 28 // Use single quotes for string literal type TypeFormatFlagsNoTypeReduction TypeFormatFlags = 1 << 29 // Don't call getReducedType TypeFormatFlagsOmitThisParameter TypeFormatFlags = 1 << 25 + TypeFormatFlagsWriteCallStyleSignature TypeFormatFlags = 1 << 27 // Write construct signatures as call style signatures // Error Handling TypeFormatFlagsAllowUniqueESSymbolType TypeFormatFlags = 1 << 20 // This is bit 20 to align with the same bit in `NodeBuilderFlags` // TypeFormatFlags exclusive @@ -776,7 +777,7 @@ func (t *LiteralType) Value() any { } func (t *LiteralType) String() string { - return ValueToString(t) + return ValueToString(t.value) } // UniqueESSymbolTypeData diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 61caddabee..7d7b7cf127 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -345,19 +345,6 @@ func getExternalModuleRequireArgument(node *ast.Node) *ast.Node { return nil } -func isRightSideOfQualifiedNameOrPropertyAccess(node *ast.Node) bool { - parent := node.Parent - switch parent.Kind { - case ast.KindQualifiedName: - return parent.AsQualifiedName().Right == node - case ast.KindPropertyAccessExpression: - return parent.AsPropertyAccessExpression().Name() == node - case ast.KindMetaProperty: - return parent.AsMetaProperty().Name() == node - } - return false -} - func isRightSideOfAccessExpression(node *ast.Node) bool { return node.Parent != nil && (ast.IsPropertyAccessExpression(node.Parent) && node.Parent.Name() == node || ast.IsElementAccessExpression(node.Parent) && node.Parent.AsElementAccessExpression().ArgumentExpression == node) diff --git a/internal/ls/hover.go b/internal/ls/hover.go index 52623511d0..225bbda496 100644 --- a/internal/ls/hover.go +++ b/internal/ls/hover.go @@ -17,32 +17,6 @@ const ( typeFormatFlags = checker.TypeFormatFlagsNone ) -func writeTypeParam(c *checker.Checker, tp *checker.Type, file *ast.SourceFile, b *strings.Builder) { - symbol := tp.Symbol() - b.WriteString(c.SymbolToStringEx(symbol, file.AsNode(), ast.SymbolFlagsNone, symbolFormatFlags)) - cons := c.GetConstraintOfTypeParameter(tp) - if cons != nil { - b.WriteString(" extends ") - b.WriteString(c.TypeToStringEx(cons, file.AsNode(), typeFormatFlags)) - } -} - -func writeTypeParams(params []*checker.Type, c *checker.Checker, file *ast.SourceFile, b *strings.Builder) { - if len(params) > 0 { - b.WriteString("<") - var tail bool - for _, param := range params { - if tail { - b.WriteString(",") - b.WriteString(" ") - } - writeTypeParam(c, param, file, b) - tail = true - } - b.WriteString(">") - } -} - func (l *LanguageService) ProvideHover(ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position) (*lsproto.Hover, error) { program, file := l.getProgramAndFile(documentURI) node := astnav.GetTouchingPropertyName(file, int(l.converters.LineAndCharacterToPosition(file, position))) @@ -52,143 +26,367 @@ func (l *LanguageService) ProvideHover(ctx context.Context, documentURI lsproto. } c, done := program.GetTypeCheckerForFile(ctx, file) defer done() + quickInfo, declaration := getQuickInfoAndDeclarationAtLocation(c, node) + if quickInfo != "" { + return &lsproto.Hover{ + Contents: lsproto.MarkupContentOrMarkedStringOrMarkedStrings{ + MarkupContent: &lsproto.MarkupContent{ + Kind: lsproto.MarkupKindMarkdown, + Value: formatQuickInfoAndJSDoc(quickInfo, declaration), + }, + }, + }, nil + } + return nil, nil +} - var result string +func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, node *ast.Node) (string, *ast.Node) { symbol := c.GetSymbolAtLocation(node) isAlias := symbol != nil && symbol.Flags&ast.SymbolFlagsAlias != 0 if isAlias { symbol = c.GetAliasedSymbol(symbol) } - if symbol != nil && symbol != c.GetUnknownSymbol() { - flags := symbol.Flags - if flags&ast.SymbolFlagsType != 0 && (ast.IsPartOfTypeNode(node) || ast.IsTypeDeclarationName(node)) { - // If the symbol has a type meaning and we're in a type context, remove value-only meanings - flags &^= ast.SymbolFlagsVariable | ast.SymbolFlagsFunction - } - var b strings.Builder - if isAlias { - b.WriteString("(alias) ") + if symbol == nil || symbol == c.GetUnknownSymbol() { + 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.SymbolFlagsType != 0 && (ast.IsPartOfTypeNode(node) || ast.IsTypeDeclarationName(node)) { + // If the symbol has a type meaning and we're in a type context, remove value-only meanings + flags &^= ast.SymbolFlagsVariable | ast.SymbolFlagsFunction + } + var b strings.Builder + if isAlias { + b.WriteString("(alias) ") + } + switch { + case flags&(ast.SymbolFlagsVariable|ast.SymbolFlagsProperty|ast.SymbolFlagsAccessor) != 0: switch { - case flags&(ast.SymbolFlagsVariable|ast.SymbolFlagsProperty|ast.SymbolFlagsAccessor) != 0: - switch { - case flags&ast.SymbolFlagsProperty != 0: - b.WriteString("(property) ") - case flags&ast.SymbolFlagsAccessor != 0: - b.WriteString("(accessor) ") - default: - decl := symbol.ValueDeclaration - if decl != nil { - switch { - case ast.IsParameter(decl): - b.WriteString("(parameter) ") - case ast.IsVarLet(decl): - b.WriteString("let ") - case ast.IsVarConst(decl): - b.WriteString("const ") - case ast.IsVarUsing(decl): - b.WriteString("using ") - case ast.IsVarAwaitUsing(decl): - b.WriteString("await using ") - default: - b.WriteString("var ") - } + case flags&ast.SymbolFlagsProperty != 0: + b.WriteString("(property) ") + case flags&ast.SymbolFlagsAccessor != 0: + b.WriteString("(accessor) ") + default: + decl := symbol.ValueDeclaration + if decl != nil { + switch { + case ast.IsParameter(decl): + b.WriteString("(parameter) ") + case ast.IsVarLet(decl): + b.WriteString("let ") + case ast.IsVarConst(decl): + b.WriteString("const ") + case ast.IsVarUsing(decl): + b.WriteString("using ") + case ast.IsVarAwaitUsing(decl): + b.WriteString("await using ") + default: + b.WriteString("var ") } } - b.WriteString(c.SymbolToStringEx(symbol, file.AsNode(), ast.SymbolFlagsNone, symbolFormatFlags)) - b.WriteString(": ") - b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbolAtLocation(symbol, node), file.AsNode(), typeFormatFlags)) - case flags&ast.SymbolFlagsEnumMember != 0: - b.WriteString("(enum member) ") - t := c.GetTypeOfSymbol(symbol) - b.WriteString(c.TypeToStringEx(t, file.AsNode(), typeFormatFlags)) - if t.Flags()&checker.TypeFlagsLiteral != 0 { - b.WriteString(" = ") - b.WriteString(t.AsLiteralType().String()) - } - case flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod) != 0: - t := c.GetTypeOfSymbol(symbol) - signatures := c.GetSignaturesOfType(t, checker.SignatureKindCall) - prefix := core.IfElse(symbol.Flags&ast.SymbolFlagsMethod != 0, "(method) ", "function ") - for i, sig := range signatures { - if i != 0 { - b.WriteString("\n") - } - if i == 3 && len(signatures) >= 5 { - b.WriteString(fmt.Sprintf("// +%v more overloads", len(signatures)-3)) - break - } - b.WriteString(prefix) - b.WriteString(c.SignatureToStringEx(sig, file.AsNode(), typeFormatFlags)) + } + b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags)) + b.WriteString(": ") + b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbolAtLocation(symbol, node), nil, typeFormatFlags)) + case flags&ast.SymbolFlagsEnumMember != 0: + b.WriteString("(enum member) ") + t := c.GetTypeOfSymbol(symbol) + b.WriteString(c.TypeToStringEx(t, nil, typeFormatFlags)) + if t.Flags()&checker.TypeFlagsLiteral != 0 { + b.WriteString(" = ") + b.WriteString(t.AsLiteralType().String()) + } + case flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod) != 0: + signatures := c.GetSignaturesOfType(c.GetTypeOfSymbol(symbol), checker.SignatureKindCall) + prefix := core.IfElse(symbol.Flags&ast.SymbolFlagsMethod != 0, "(method) ", "function ") + writeSignatures(&b, c, signatures, prefix, symbol) + case flags&ast.SymbolFlagsConstructor != 0: + signatures := c.GetSignaturesOfType(c.GetTypeOfSymbol(symbol.Parent), checker.SignatureKindConstruct) + writeSignatures(&b, c, signatures, "constructor ", symbol.Parent) + case flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0: + b.WriteString(core.IfElse(symbol.Flags&ast.SymbolFlagsClass != 0, "class ", "interface ")) + b.WriteString(c.SymbolToStringEx(symbol, nil, 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) + } + case flags&ast.SymbolFlagsEnum != 0: + b.WriteString("enum ") + b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags)) + case flags&ast.SymbolFlagsModule != 0: + b.WriteString(core.IfElse(symbol.ValueDeclaration != nil && ast.IsSourceFile(symbol.ValueDeclaration), "module ", "namespace ")) + b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags)) + case flags&ast.SymbolFlagsTypeParameter != 0: + b.WriteString("(type parameter) ") + tp := c.GetDeclaredTypeOfSymbol(symbol) + b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags)) + cons := c.GetConstraintOfTypeParameter(tp) + if cons != nil { + b.WriteString(" extends ") + b.WriteString(c.TypeToStringEx(cons, nil, typeFormatFlags)) + } + declaration = core.Find(symbol.Declarations, ast.IsTypeParameterDeclaration) + case flags&ast.SymbolFlagsTypeAlias != 0: + b.WriteString("type ") + b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags)) + writeTypeParams(&b, c, c.GetTypeAliasTypeParameters(symbol)) + if len(symbol.Declarations) != 0 { + b.WriteString(" = ") + b.WriteString(c.TypeToStringEx(c.GetDeclaredTypeOfSymbol(symbol), nil, typeFormatFlags|checker.TypeFormatFlagsInTypeAlias)) + } + declaration = core.Find(symbol.Declarations, ast.IsTypeAliasDeclaration) + case flags&ast.SymbolFlagsAlias != 0: + b.WriteString("import ") + b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags)) + default: + b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbol(symbol), nil, typeFormatFlags)) + } + return b.String(), declaration +} + +func inConstructorContext(node *ast.Node) bool { + if node.Kind == ast.KindConstructorKeyword { + return true + } + if ast.IsIdentifier(node) { + for ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) { + node = node.Parent + } + if ast.IsNewExpression(node.Parent) { + return true + } + } + return false +} + +func writeTypeParams(b *strings.Builder, c *checker.Checker, params []*checker.Type) { + if len(params) > 0 { + b.WriteString("<") + for i, tp := range params { + if i != 0 { + b.WriteString(", ") } - case flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0: - b.WriteString(core.IfElse(symbol.Flags&ast.SymbolFlagsClass != 0, "class ", "interface ")) - b.WriteString(c.SymbolToStringEx(symbol, file.AsNode(), ast.SymbolFlagsNone, symbolFormatFlags)) - params := c.GetDeclaredTypeOfSymbol(symbol).AsInterfaceType().LocalTypeParameters() - writeTypeParams(params, c, file, &b) - case flags&ast.SymbolFlagsEnum != 0: - b.WriteString("enum ") - b.WriteString(c.SymbolToStringEx(symbol, file.AsNode(), ast.SymbolFlagsNone, symbolFormatFlags)) - case flags&ast.SymbolFlagsModule != 0: - b.WriteString(core.IfElse(symbol.ValueDeclaration != nil && ast.IsSourceFile(symbol.ValueDeclaration), "module ", "namespace ")) - b.WriteString(c.SymbolToStringEx(symbol, file.AsNode(), ast.SymbolFlagsNone, symbolFormatFlags)) - case flags&ast.SymbolFlagsTypeParameter != 0: - b.WriteString("(type parameter) ") - tp := c.GetDeclaredTypeOfSymbol(symbol) - b.WriteString(c.SymbolToStringEx(symbol, file.AsNode(), ast.SymbolFlagsNone, symbolFormatFlags)) + symbol := tp.Symbol() + b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags)) cons := c.GetConstraintOfTypeParameter(tp) if cons != nil { b.WriteString(" extends ") - b.WriteString(c.TypeToStringEx(cons, file.AsNode(), typeFormatFlags)) + b.WriteString(c.TypeToStringEx(cons, nil, typeFormatFlags)) + } + } + b.WriteString(">") + } +} + +func writeSignatures(b *strings.Builder, c *checker.Checker, signatures []*checker.Signature, prefix string, symbol *ast.Symbol) { + for i, sig := range signatures { + if i != 0 { + b.WriteString("\n") + } + if i == 3 && len(signatures) >= 5 { + b.WriteString(fmt.Sprintf("// +%v more overloads", len(signatures)-3)) + break + } + b.WriteString(prefix) + b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags)) + b.WriteString(c.SignatureToStringEx(sig, nil, typeFormatFlags|checker.TypeFormatFlagsWriteCallStyleSignature)) + } +} + +func formatQuickInfoAndJSDoc(quickInfo string, declaration *ast.Node) string { + var b strings.Builder + b.Grow(32) + writeCode(&b, "tsx", quickInfo) + if declaration != nil { + if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) { + writeComments(&b, jsdoc.Comments()) + if jsdoc.Kind == ast.KindJSDoc { + if tags := jsdoc.AsJSDoc().Tags; tags != nil { + for _, tag := range tags.Nodes { + if tag.Kind == ast.KindJSDocTypeTag { + continue + } + b.WriteString("\n\n*@") + b.WriteString(tag.TagName().Text()) + b.WriteString("*") + switch tag.Kind { + case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag: + writeOptionalEntityName(&b, tag.Name()) + case ast.KindJSDocAugmentsTag: + writeOptionalEntityName(&b, tag.AsJSDocAugmentsTag().ClassName) + case ast.KindJSDocSeeTag: + writeOptionalEntityName(&b, tag.AsJSDocSeeTag().NameExpression) + case ast.KindJSDocTemplateTag: + for i, tp := range tag.TypeParameters() { + if i != 0 { + b.WriteString(",") + } + writeOptionalEntityName(&b, tp.Name()) + } + } + comments := tag.Comments() + if len(comments) != 0 { + if commentHasPrefix(comments, "```") { + b.WriteString("\n") + } else { + b.WriteString(" ") + if !commentHasPrefix(comments, "-") { + b.WriteString("— ") + } + } + writeComments(&b, comments) + } + } + } } - case flags&ast.SymbolFlagsTypeAlias != 0: - b.WriteString("type ") - b.WriteString(c.SymbolToStringEx(symbol, file.AsNode(), ast.SymbolFlagsNone, symbolFormatFlags)) - writeTypeParams(c.GetTypeAliasTypeParameters(symbol), c, file, &b) - if len(symbol.Declarations) != 0 { - b.WriteString(" = ") - b.WriteString(c.TypeToStringEx(c.GetDeclaredTypeOfSymbol(symbol), file.AsNode(), typeFormatFlags|checker.TypeFormatFlagsInTypeAlias)) + } + } + return b.String() +} + +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 { + return true + } } - case flags&ast.SymbolFlagsAlias != 0: - b.WriteString("import ") - b.WriteString(c.SymbolToStringEx(symbol, file.AsNode(), ast.SymbolFlagsNone, symbolFormatFlags)) - default: - b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbol(symbol), file.AsNode(), typeFormatFlags)) } - result = b.String() } - if result != "" { - return &lsproto.Hover{ - Contents: lsproto.MarkupContentOrMarkedStringOrMarkedStrings{ - MarkupContent: &lsproto.MarkupContent{ - Kind: lsproto.MarkupKindMarkdown, - Value: codeFence("typescript", result), - }, - }, - }, nil + return false +} + +func commentHasPrefix(comments []*ast.Node, prefix string) bool { + return comments[0].Kind == ast.KindJSDocText && strings.HasPrefix(comments[0].Text(), prefix) +} + +func getJSDoc(node *ast.Node) *ast.Node { + return core.LastOrNil(node.JSDoc(nil)) +} + +func getJSDocOrTag(node *ast.Node) *ast.Node { + if jsdoc := getJSDoc(node); jsdoc != nil { + return jsdoc } - return nil, nil + switch { + case ast.IsParameter(node): + return getMatchingJSDocTag(node.Parent, node.Name().Text(), isMatchingParameterTag) + case ast.IsTypeParameterDeclaration(node): + return getMatchingJSDocTag(node.Parent, node.Name().Text(), isMatchingTemplateTag) + case ast.IsVariableDeclaration(node) && core.FirstOrNil(node.Parent.AsVariableDeclarationList().Declarations.Nodes) == node: + return getJSDocOrTag(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 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 { + if tags := jsdoc.AsJSDoc().Tags; tags != nil { + for _, tag := range tags.Nodes { + if match(tag, name) { + return tag + } + } + } + } + return nil } -func codeFence(lang string, code string) string { +func isMatchingParameterTag(tag *ast.Node, name string) bool { + return tag.Kind == ast.KindJSDocParameterTag && isNodeWithName(tag, name) +} + +func isMatchingTemplateTag(tag *ast.Node, name string) bool { + return tag.Kind == ast.KindJSDocTemplateTag && core.Some(tag.TypeParameters(), func(tp *ast.Node) bool { return isNodeWithName(tp, name) }) +} + +func isNodeWithName(node *ast.Node, name string) bool { + nodeName := node.Name() + return ast.IsIdentifier(nodeName) && nodeName.Text() == name +} + +func writeCode(b *strings.Builder, lang string, code string) { if code == "" { - return "" + return } ticks := 3 for strings.Contains(code, strings.Repeat("`", ticks)) { ticks++ } - var result strings.Builder - result.Grow(len(code) + len(lang) + 2*ticks + 2) for range ticks { - result.WriteByte('`') + b.WriteByte('`') } - result.WriteString(lang) - result.WriteByte('\n') - result.WriteString(code) - result.WriteByte('\n') + b.WriteString(lang) + b.WriteByte('\n') + b.WriteString(code) + b.WriteByte('\n') for range ticks { - result.WriteByte('`') + b.WriteByte('`') + } + b.WriteByte('\n') +} + +func writeComments(b *strings.Builder, comments []*ast.Node) { + for _, comment := range comments { + switch comment.Kind { + case ast.KindJSDocText: + b.WriteString(comment.AsJSDocText().Text) + case ast.KindJSDocLink: + name := comment.Name() + text := comment.AsJSDocLink().Text + if name != nil { + if text == "" { + writeEntityName(b, name) + } else { + writeEntityNameParts(b, name) + } + } + b.WriteString(text) + } + } +} + +func writeOptionalEntityName(b *strings.Builder, name *ast.Node) { + if name != nil { + b.WriteString(" ") + writeEntityName(b, name) + } +} + +func writeEntityName(b *strings.Builder, name *ast.Node) { + b.WriteString("`") + writeEntityNameParts(b, name) + b.WriteString("`") +} + +func writeEntityNameParts(b *strings.Builder, node *ast.Node) { + switch node.Kind { + case ast.KindIdentifier: + b.WriteString(node.Text()) + case ast.KindQualifiedName: + writeEntityNameParts(b, node.AsQualifiedName().Left) + b.WriteByte('.') + writeEntityNameParts(b, node.AsQualifiedName().Right) + case ast.KindPropertyAccessExpression: + writeEntityNameParts(b, node.Expression()) + b.WriteByte('.') + writeEntityNameParts(b, node.Name()) + case ast.KindParenthesizedExpression, ast.KindExpressionWithTypeArguments: + writeEntityNameParts(b, node.Expression()) + case ast.KindJSDocNameReference: + writeEntityNameParts(b, node.Name()) } - return result.String() } diff --git a/internal/nodebuilder/types.go b/internal/nodebuilder/types.go index ce7b8491a0..bc492ce855 100644 --- a/internal/nodebuilder/types.go +++ b/internal/nodebuilder/types.go @@ -50,6 +50,7 @@ const ( FlagsUseSingleQuotesForStringLiteralType Flags = 1 << 28 FlagsNoTypeReduction Flags = 1 << 29 FlagsOmitThisParameter Flags = 1 << 25 + FlagsWriteCallStyleSignature Flags = 1 << 27 // Error handling FlagsAllowThisInObjectLiteral Flags = 1 << 15 FlagsAllowQualifiedNameInPlaceOfIdentifier Flags = 1 << 16 diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go index f6e32ec8e9..1a3094f877 100644 --- a/internal/parser/reparser.go +++ b/internal/parser/reparser.go @@ -208,7 +208,7 @@ func (p *Parser) gatherTypeParameters(j *ast.Node) *ast.NodeList { end = tag.End() constraint := tag.AsJSDocTemplateTag().Constraint - for _, tp := range tag.AsJSDocTemplateTag().TypeParameters().Nodes { + for _, tp := range tag.TypeParameters() { typeParameter := tp.AsTypeParameter() var reparse *ast.Node if constraint == nil {