162 lines
5.2 KiB
Go
162 lines
5.2 KiB
Go
package ls
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/checker"
|
|
"github.com/microsoft/typescript-go/internal/collections"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
|
|
"github.com/microsoft/typescript-go/internal/scanner"
|
|
)
|
|
|
|
// JSDocTagInfo mirrors Strada's `JSDocTagInfo`, but renders the tag's text as a
|
|
// plain string instead of `SymbolDisplayPart[]`.
|
|
type JSDocTagInfo struct {
|
|
Name string
|
|
Text string
|
|
}
|
|
|
|
// GetSymbolDocumentationComment renders a symbol's documentation comment as plain text.
|
|
// It backs the API's Symbol.getDocumentationComment and mirrors Strada's
|
|
// getJsDocCommentsFromDeclarations: comments are gathered from each unique declaration,
|
|
// deduplicated, and joined with line breaks. Like Strada, it does not resolve aliases —
|
|
// consumers resolve aliases themselves (via getAliasedSymbol) and re-query if desired.
|
|
func (l *LanguageService) GetSymbolDocumentationComment(c *checker.Checker, symbol *ast.Symbol) string {
|
|
if symbol == nil {
|
|
return ""
|
|
}
|
|
var parts []string
|
|
var seen collections.Set[*ast.Node]
|
|
for _, decl := range symbol.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) {
|
|
parts = append(parts, doc)
|
|
}
|
|
}
|
|
return strings.Join(parts, "\n")
|
|
}
|
|
|
|
// GetSymbolJSDocTags collects a symbol's JSDoc tags. It backs the API's Symbol.getJsDocTags
|
|
// and mirrors Strada's getJsDocTagsFromDeclarations, except each tag's text is rendered as a
|
|
// plain string rather than SymbolDisplayPart[]. Tags with no text have an empty Text field.
|
|
func (l *LanguageService) GetSymbolJSDocTags(symbol *ast.Symbol) []JSDocTagInfo {
|
|
if symbol == nil {
|
|
return nil
|
|
}
|
|
var infos []JSDocTagInfo
|
|
var seen collections.Set[*ast.Node]
|
|
for _, decl := range symbol.Declarations {
|
|
if decl == nil {
|
|
continue
|
|
}
|
|
if !seen.AddIfAbsent(decl) {
|
|
continue
|
|
}
|
|
tags := declarationJSDocTags(decl)
|
|
// Skip comments containing @typedef/@callback since they're not associated with a
|
|
// particular declaration, unless they also carry @param/@return (treated as local docs).
|
|
hasTypedef := core.Some(tags, func(t *ast.Node) bool {
|
|
return t.Kind == ast.KindJSDocTypedefTag || t.Kind == ast.KindJSDocCallbackTag
|
|
})
|
|
hasParamOrReturn := core.Some(tags, func(t *ast.Node) bool {
|
|
return t.Kind == ast.KindJSDocParameterTag || t.Kind == ast.KindJSDocReturnTag
|
|
})
|
|
if hasTypedef && !hasParamOrReturn {
|
|
continue
|
|
}
|
|
for _, tag := range tags {
|
|
infos = append(infos, JSDocTagInfo{Name: tag.TagName().Text(), Text: getJSDocTagText(tag)})
|
|
}
|
|
}
|
|
return infos
|
|
}
|
|
|
|
// declarationJSDocTags returns the JSDoc tags associated with a declaration, walking the
|
|
// JSDoc comment location chain like the checker's getAllJSDocTags.
|
|
func declarationJSDocTags(node *ast.Node) []*ast.Node {
|
|
if node.Flags&ast.NodeFlagsJSDoc == 0 {
|
|
for current := node; current != nil; current = ast.GetNextJSDocCommentLocation(current) {
|
|
jsdocs := current.JSDoc(nil)
|
|
if len(jsdocs) == 0 {
|
|
continue
|
|
}
|
|
lastJSDoc := jsdocs[len(jsdocs)-1].AsJSDoc()
|
|
if lastJSDoc.Tags != nil {
|
|
return lastJSDoc.Tags.Nodes
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getJSDocTagText renders the text of a single JSDoc tag as a plain string, mirroring
|
|
// Strada's getCommentDisplayParts collapsed from SymbolDisplayPart[] to a string.
|
|
func getJSDocTagText(tag *ast.Node) string {
|
|
comment := scanner.GetTextOfJSDocComment(tag.CommentList())
|
|
addComment := func(s string) string {
|
|
if comment == "" {
|
|
return s
|
|
}
|
|
return s + " " + comment
|
|
}
|
|
switch tag.Kind {
|
|
case ast.KindJSDocThrowsTag:
|
|
if te := tag.AsJSDocThrowsTag().TypeExpression; te != nil {
|
|
return addComment(scanner.GetTextOfNode(te))
|
|
}
|
|
return comment
|
|
case ast.KindJSDocImplementsTag:
|
|
return addComment(scanner.GetTextOfNode(tag.AsJSDocImplementsTag().ClassName))
|
|
case ast.KindJSDocAugmentsTag:
|
|
return addComment(scanner.GetTextOfNode(tag.AsJSDocAugmentsTag().ClassName))
|
|
case ast.KindJSDocTemplateTag:
|
|
templateTag := tag.AsJSDocTemplateTag()
|
|
var b strings.Builder
|
|
if templateTag.Constraint != nil {
|
|
b.WriteString(scanner.GetTextOfNode(templateTag.Constraint))
|
|
}
|
|
if templateTag.TypeParameters != nil {
|
|
for i, tp := range templateTag.TypeParameters.Nodes {
|
|
if i == 0 && b.Len() != 0 {
|
|
b.WriteString(" ")
|
|
}
|
|
if i != 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
b.WriteString(scanner.GetTextOfNode(tp))
|
|
}
|
|
}
|
|
if comment != "" {
|
|
if b.Len() != 0 {
|
|
b.WriteString(" ")
|
|
}
|
|
b.WriteString(comment)
|
|
}
|
|
return b.String()
|
|
case ast.KindJSDocTypeTag:
|
|
return addComment(scanner.GetTextOfNode(tag.AsJSDocTypeTag().TypeExpression))
|
|
case ast.KindJSDocSatisfiesTag:
|
|
return addComment(scanner.GetTextOfNode(tag.AsJSDocSatisfiesTag().TypeExpression))
|
|
case ast.KindJSDocSeeTag:
|
|
if ne := tag.AsJSDocSeeTag().NameExpression; ne != nil {
|
|
return addComment(scanner.GetTextOfNode(ne))
|
|
}
|
|
return comment
|
|
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
|
|
if name := tag.Name(); name != nil {
|
|
return addComment(scanner.GetTextOfNode(name))
|
|
}
|
|
return comment
|
|
default:
|
|
return comment
|
|
}
|
|
}
|