aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/source/hover.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/source/hover.go')
-rw-r--r--internal/lsp/source/hover.go870
1 files changed, 0 insertions, 870 deletions
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
deleted file mode 100644
index b6fd9acf9..000000000
--- a/internal/lsp/source/hover.go
+++ /dev/null
@@ -1,870 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package source
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "go/ast"
- "go/constant"
- "go/doc"
- "go/format"
- "go/token"
- "go/types"
- "strconv"
- "strings"
- "time"
- "unicode/utf8"
-
- "golang.org/x/text/unicode/runenames"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/typeparams"
- errors "golang.org/x/xerrors"
-)
-
-// HoverContext contains context extracted from the syntax and type information
-// of a given node, for use in various summaries (hover, autocomplete,
-// signature help).
-type HoverContext struct {
- // signatureSource is the object or node use to derive the hover signature.
- //
- // It may also hold a precomputed string.
- // TODO(rfindley): pre-compute all signatures to avoid this indirection.
- signatureSource interface{}
-
- // comment is the most relevant comment group associated with the hovered object.
- Comment *ast.CommentGroup
-}
-
-// HoverJSON contains information used by hover. It is also the JSON returned
-// for the "structured" hover format
-type HoverJSON struct {
- // Synopsis is a single sentence synopsis of the symbol's documentation.
- Synopsis string `json:"synopsis"`
-
- // FullDocumentation is the symbol's full documentation.
- FullDocumentation string `json:"fullDocumentation"`
-
- // Signature is the symbol's signature.
- Signature string `json:"signature"`
-
- // SingleLine is a single line describing the symbol.
- // This is recommended only for use in clients that show a single line for hover.
- SingleLine string `json:"singleLine"`
-
- // SymbolName is the types.Object.Name for the given symbol.
- SymbolName string `json:"symbolName"`
-
- // LinkPath is the pkg.go.dev link for the given symbol.
- // For example, the "go/ast" part of "pkg.go.dev/go/ast#Node".
- LinkPath string `json:"linkPath"`
-
- // LinkAnchor is the pkg.go.dev link anchor for the given symbol.
- // For example, the "Node" part of "pkg.go.dev/go/ast#Node".
- LinkAnchor string `json:"linkAnchor"`
-}
-
-func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
- ident, err := Identifier(ctx, snapshot, fh, position)
- if err != nil {
- if hover, innerErr := hoverRune(ctx, snapshot, fh, position); innerErr == nil {
- return hover, nil
- }
- return nil, nil
- }
- h, err := HoverIdentifier(ctx, ident)
- if err != nil {
- return nil, err
- }
- rng, err := ident.Range()
- if err != nil {
- return nil, err
- }
- hover, err := FormatHover(h, snapshot.View().Options())
- if err != nil {
- return nil, err
- }
- return &protocol.Hover{
- Contents: protocol.MarkupContent{
- Kind: snapshot.View().Options().PreferredContentFormat,
- Value: hover,
- },
- Range: rng,
- }, nil
-}
-
-func hoverRune(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
- ctx, done := event.Start(ctx, "source.hoverRune")
- defer done()
-
- r, mrng, err := findRune(ctx, snapshot, fh, position)
- if err != nil {
- return nil, err
- }
- rng, err := mrng.Range()
- if err != nil {
- return nil, err
- }
-
- var desc string
- runeName := runenames.Name(r)
- if len(runeName) > 0 && runeName[0] == '<' {
- // Check if the rune looks like an HTML tag. If so, trim the surrounding <>
- // characters to work around https://github.com/microsoft/vscode/issues/124042.
- runeName = strings.TrimRight(runeName[1:], ">")
- }
- if strconv.IsPrint(r) {
- desc = fmt.Sprintf("'%s', U+%04X, %s", string(r), uint32(r), runeName)
- } else {
- desc = fmt.Sprintf("U+%04X, %s", uint32(r), runeName)
- }
- return &protocol.Hover{
- Contents: protocol.MarkupContent{
- Kind: snapshot.View().Options().PreferredContentFormat,
- Value: desc,
- },
- Range: rng,
- }, nil
-}
-
-// ErrNoRuneFound is the error returned when no rune is found at a particular position.
-var ErrNoRuneFound = errors.New("no rune found")
-
-// findRune returns rune information for a position in a file.
-func findRune(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (rune, MappedRange, error) {
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return 0, MappedRange{}, err
- }
- spn, err := pgf.Mapper.PointSpan(position)
- if err != nil {
- return 0, MappedRange{}, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- if err != nil {
- return 0, MappedRange{}, err
- }
- pos := rng.Start
-
- // Find the basic literal enclosing the given position, if there is one.
- var lit *ast.BasicLit
- var found bool
- ast.Inspect(pgf.File, func(n ast.Node) bool {
- if found {
- return false
- }
- if n, ok := n.(*ast.BasicLit); ok && pos >= n.Pos() && pos <= n.End() {
- lit = n
- found = true
- }
- return !found
- })
- if !found {
- return 0, MappedRange{}, ErrNoRuneFound
- }
-
- var r rune
- var start, end token.Pos
- switch lit.Kind {
- case token.CHAR:
- s, err := strconv.Unquote(lit.Value)
- if err != nil {
- // If the conversion fails, it's because of an invalid syntax, therefore
- // there is no rune to be found.
- return 0, MappedRange{}, ErrNoRuneFound
- }
- r, _ = utf8.DecodeRuneInString(s)
- if r == utf8.RuneError {
- return 0, MappedRange{}, fmt.Errorf("rune error")
- }
- start, end = lit.Pos(), lit.End()
- case token.INT:
- // It's an integer, scan only if it is a hex litteral whose bitsize in
- // ranging from 8 to 32.
- if !(strings.HasPrefix(lit.Value, "0x") && len(lit.Value[2:]) >= 2 && len(lit.Value[2:]) <= 8) {
- return 0, MappedRange{}, ErrNoRuneFound
- }
- v, err := strconv.ParseUint(lit.Value[2:], 16, 32)
- if err != nil {
- return 0, MappedRange{}, err
- }
- r = rune(v)
- if r == utf8.RuneError {
- return 0, MappedRange{}, fmt.Errorf("rune error")
- }
- start, end = lit.Pos(), lit.End()
- case token.STRING:
- // It's a string, scan only if it contains a unicode escape sequence under or before the
- // current cursor position.
- var found bool
- litOffset, err := Offset(pgf.Tok, lit.Pos())
- if err != nil {
- return 0, MappedRange{}, err
- }
- offset, err := Offset(pgf.Tok, pos)
- if err != nil {
- return 0, MappedRange{}, err
- }
- for i := offset - litOffset; i > 0; i-- {
- // Start at the cursor position and search backward for the beginning of a rune escape sequence.
- rr, _ := utf8.DecodeRuneInString(lit.Value[i:])
- if rr == utf8.RuneError {
- return 0, MappedRange{}, fmt.Errorf("rune error")
- }
- if rr == '\\' {
- // Got the beginning, decode it.
- var tail string
- r, _, tail, err = strconv.UnquoteChar(lit.Value[i:], '"')
- if err != nil {
- // If the conversion fails, it's because of an invalid syntax, therefore is no rune to be found.
- return 0, MappedRange{}, ErrNoRuneFound
- }
- // Only the rune escape sequence part of the string has to be highlighted, recompute the range.
- runeLen := len(lit.Value) - (int(i) + len(tail))
- start = token.Pos(int(lit.Pos()) + int(i))
- end = token.Pos(int(start) + runeLen)
- found = true
- break
- }
- }
- if !found {
- // No escape sequence found
- return 0, MappedRange{}, ErrNoRuneFound
- }
- default:
- return 0, MappedRange{}, ErrNoRuneFound
- }
-
- mappedRange, err := posToMappedRange(snapshot, pkg, start, end)
- if err != nil {
- return 0, MappedRange{}, err
- }
- return r, mappedRange, nil
-}
-
-func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverJSON, error) {
- ctx, done := event.Start(ctx, "source.Hover")
- defer done()
-
- hoverCtx, err := FindHoverContext(ctx, i.Snapshot, i.pkg, i.Declaration.obj, i.Declaration.node, i.Declaration.fullDecl)
- if err != nil {
- return nil, err
- }
-
- h := &HoverJSON{
- FullDocumentation: hoverCtx.Comment.Text(),
- Synopsis: doc.Synopsis(hoverCtx.Comment.Text()),
- }
-
- fset := i.Snapshot.FileSet()
- // Determine the symbol's signature.
- switch x := hoverCtx.signatureSource.(type) {
- case string:
- h.Signature = x // a pre-computed signature
-
- case *ast.TypeSpec:
- x2 := *x
- // Don't duplicate comments when formatting type specs.
- x2.Doc = nil
- x2.Comment = nil
- var b strings.Builder
- b.WriteString("type ")
- if err := format.Node(&b, fset, &x2); err != nil {
- return nil, err
- }
- h.Signature = b.String()
-
- case ast.Node:
- var b strings.Builder
- if err := format.Node(&b, fset, x); err != nil {
- return nil, err
- }
- h.Signature = b.String()
-
- // Check if the variable is an integer whose value we can present in a more
- // user-friendly way, i.e. `var hex = 0xe34e` becomes `var hex = 58190`
- if spec, ok := x.(*ast.ValueSpec); ok && len(spec.Values) > 0 {
- if lit, ok := spec.Values[0].(*ast.BasicLit); ok && len(spec.Names) > 0 {
- val := constant.MakeFromLiteral(types.ExprString(lit), lit.Kind, 0)
- h.Signature = fmt.Sprintf("var %s = %s", spec.Names[0], val)
- }
- }
-
- case types.Object:
- // If the variable is implicitly declared in a type switch, we need to
- // manually generate its object string.
- if typ := i.Declaration.typeSwitchImplicit; typ != nil {
- if v, ok := x.(*types.Var); ok {
- h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf))
- break
- }
- }
- h.Signature = objectString(x, i.qf, i.Inferred)
- }
- if obj := i.Declaration.obj; obj != nil {
- h.SingleLine = objectString(obj, i.qf, nil)
- }
- obj := i.Declaration.obj
- if obj == nil {
- return h, nil
- }
-
- // Check if the identifier is test-only (and is therefore not part of a
- // package's API). This is true if the request originated in a test package,
- // and if the declaration is also found in the same test package.
- if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" {
- if _, err := i.pkg.File(i.Declaration.MappedRange[0].URI()); err == nil {
- return h, nil
- }
- }
-
- h.SymbolName, h.LinkPath, h.LinkAnchor = linkData(obj, i.enclosing)
-
- // See golang/go#36998: don't link to modules matching GOPRIVATE.
- //
- // The path returned by linkData is an import path.
- if i.Snapshot.View().IsGoPrivatePath(h.LinkPath) {
- h.LinkPath = ""
- } else if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok {
- h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1)
- }
-
- return h, nil
-}
-
-// linkData returns the name, import path, and anchor to use in building links
-// to obj.
-//
-// If obj is not visible in documentation, the returned name will be empty.
-func linkData(obj types.Object, enclosing *types.TypeName) (name, importPath, anchor string) {
- // Package names simply link to the package.
- if obj, ok := obj.(*types.PkgName); ok {
- return obj.Name(), obj.Imported().Path(), ""
- }
-
- // Builtins link to the special builtin package.
- if obj.Parent() == types.Universe {
- return obj.Name(), "builtin", obj.Name()
- }
-
- // In all other cases, the object must be exported.
- if !obj.Exported() {
- return "", "", ""
- }
-
- var recv types.Object // If non-nil, the field or method receiver base.
-
- switch obj := obj.(type) {
- case *types.Var:
- // If the object is a field, and we have an associated selector
- // composite literal, or struct, we can determine the link.
- if obj.IsField() && enclosing != nil {
- recv = enclosing
- }
- case *types.Func:
- typ, ok := obj.Type().(*types.Signature)
- if !ok {
- // Note: this should never happen. go/types guarantees that the type of
- // *Funcs are Signatures.
- //
- // TODO(rfindley): given a 'debug' mode, we should panic here.
- return "", "", ""
- }
- if r := typ.Recv(); r != nil {
- if rtyp, _ := Deref(r.Type()).(*types.Named); rtyp != nil {
- // If we have an unexported type, see if the enclosing type is
- // exported (we may have an interface or struct we can link
- // to). If not, don't show any link.
- if !rtyp.Obj().Exported() {
- if enclosing != nil {
- recv = enclosing
- } else {
- return "", "", ""
- }
- } else {
- recv = rtyp.Obj()
- }
- }
- }
- }
-
- if recv != nil && !recv.Exported() {
- return "", "", ""
- }
-
- // Either the object or its receiver must be in the package scope.
- scopeObj := obj
- if recv != nil {
- scopeObj = recv
- }
- if scopeObj.Pkg() == nil || scopeObj.Pkg().Scope().Lookup(scopeObj.Name()) != scopeObj {
- return "", "", ""
- }
-
- importPath = obj.Pkg().Path()
- if recv != nil {
- anchor = fmt.Sprintf("%s.%s", recv.Name(), obj.Name())
- name = fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), recv.Name(), obj.Name())
- } else {
- // For most cases, the link is "package/path#symbol".
- anchor = obj.Name()
- name = fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name())
- }
- return name, importPath, anchor
-}
-
-func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {
- // TODO(rfindley): moduleAtVersion should not be responsible for deciding
- // whether or not the link target supports module version links.
- if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" {
- return "", "", false
- }
- impPkg, err := i.pkg.GetImport(path)
- if err != nil {
- return "", "", false
- }
- if impPkg.Version() == nil {
- return "", "", false
- }
- version, modpath := impPkg.Version().Version, impPkg.Version().Path
- if modpath == "" || version == "" {
- return "", "", false
- }
- return modpath, version, true
-}
-
-// objectString is a wrapper around the types.ObjectString function.
-// It handles adding more information to the object string.
-func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string {
- // If the signature type was inferred, prefer the preferred signature with a
- // comment showing the generic signature.
- if sig, _ := obj.Type().(*types.Signature); sig != nil && typeparams.ForSignature(sig).Len() > 0 && inferred != nil {
- obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred)
- str := types.ObjectString(obj2, qf)
- // Try to avoid overly long lines.
- if len(str) > 60 {
- str += "\n"
- } else {
- str += " "
- }
- str += "// " + types.TypeString(sig, qf)
- return str
- }
- str := types.ObjectString(obj, qf)
- switch obj := obj.(type) {
- case *types.Const:
- str = fmt.Sprintf("%s = %s", str, obj.Val())
-
- // Try to add a formatted duration as an inline comment
- typ, ok := obj.Type().(*types.Named)
- if !ok {
- break
- }
- pkg := typ.Obj().Pkg()
- if pkg.Path() == "time" && typ.Obj().Name() == "Duration" {
- if d, ok := constant.Int64Val(obj.Val()); ok {
- str += " // " + time.Duration(d).String()
- }
- }
- }
- return str
-}
-
-// FindHoverContext returns a HoverContext struct for an AST node and its
-// declaration object. node should be the actual node used in type checking,
-// while fullNode could be a separate node with more complete syntactic
-// information.
-func FindHoverContext(ctx context.Context, s Snapshot, pkg Package, obj types.Object, pkgNode ast.Node, fullDecl ast.Decl) (*HoverContext, error) {
- var info *HoverContext
-
- // Type parameters get their signature from their declaration object.
- if _, isTypeName := obj.(*types.TypeName); isTypeName {
- if _, isTypeParam := obj.Type().(*typeparams.TypeParam); isTypeParam {
- return &HoverContext{signatureSource: obj}, nil
- }
- }
-
- // This is problematic for a number of reasons. We really need to have a more
- // general mechanism to validate the coherency of AST with type information,
- // but absent that we must do our best to ensure that we don't use fullNode
- // when we actually need the node that was type checked.
- //
- // pkgNode may be nil, if it was eliminated from the type-checked syntax. In
- // that case, use fullDecl if available.
- node := pkgNode
- if node == nil && fullDecl != nil {
- node = fullDecl
- }
-
- switch node := node.(type) {
- case *ast.Ident:
- // The package declaration.
- for _, f := range pkg.GetSyntax() {
- if f.Name == pkgNode {
- info = &HoverContext{Comment: f.Doc}
- }
- }
- case *ast.ImportSpec:
- // Try to find the package documentation for an imported package.
- if pkgName, ok := obj.(*types.PkgName); ok {
- imp, err := pkg.GetImport(pkgName.Imported().Path())
- if err != nil {
- return nil, err
- }
- // Assume that only one file will contain package documentation,
- // so pick the first file that has a doc comment.
- for _, file := range imp.GetSyntax() {
- if file.Doc != nil {
- info = &HoverContext{signatureSource: obj, Comment: file.Doc}
- break
- }
- }
- }
- info = &HoverContext{signatureSource: node}
- case *ast.GenDecl:
- switch obj := obj.(type) {
- case *types.TypeName, *types.Var, *types.Const, *types.Func:
- // Always use the full declaration here if we have it, because the
- // dependent code doesn't rely on pointer identity. This is fragile.
- if d, _ := fullDecl.(*ast.GenDecl); d != nil {
- node = d
- }
- // obj may not have been produced by type checking the AST containing
- // node, so we need to be careful about using token.Pos.
- tok := s.FileSet().File(obj.Pos())
- offset, err := Offset(tok, obj.Pos())
- if err != nil {
- return nil, err
- }
-
- // fullTok and fullPos are the *token.File and object position in for the
- // full AST.
- fullTok := s.FileSet().File(node.Pos())
- fullPos, err := Pos(fullTok, offset)
- if err != nil {
- return nil, err
- }
-
- var spec ast.Spec
- for _, s := range node.Specs {
- // Avoid panics by guarding the calls to token.Offset (golang/go#48249).
- start, err := Offset(fullTok, s.Pos())
- if err != nil {
- return nil, err
- }
- end, err := Offset(fullTok, s.End())
- if err != nil {
- return nil, err
- }
- if start <= offset && offset <= end {
- spec = s
- break
- }
- }
-
- info, err = hoverGenDecl(node, spec, fullPos, obj)
- if err != nil {
- return nil, err
- }
- }
- case *ast.TypeSpec:
- if obj.Parent() == types.Universe {
- if genDecl, ok := fullDecl.(*ast.GenDecl); ok {
- info = hoverTypeSpec(node, genDecl)
- }
- }
- case *ast.FuncDecl:
- switch obj.(type) {
- case *types.Func:
- info = &HoverContext{signatureSource: obj, Comment: node.Doc}
- case *types.Builtin:
- info = &HoverContext{Comment: node.Doc}
- if sig, err := NewBuiltinSignature(ctx, s, obj.Name()); err == nil {
- info.signatureSource = "func " + sig.name + sig.Format()
- } else {
- // Fall back on the object as a signature source.
-
- // TODO(rfindley): refactor so that we can report bugs from the source
- // package.
-
- // debug.Bug(ctx, "invalid builtin hover", "did not find builtin signature: %v", err)
- info.signatureSource = obj
- }
- case *types.Var:
- // Object is a function param or the field of an anonymous struct
- // declared with ':='. Skip the first one because only fields
- // can have docs.
- if isFunctionParam(obj, node) {
- break
- }
-
- field, err := s.PosToField(ctx, pkg, obj.Pos())
- if err != nil {
- return nil, err
- }
-
- if field != nil {
- comment := field.Doc
- if comment.Text() == "" {
- comment = field.Comment
- }
- info = &HoverContext{signatureSource: obj, Comment: comment}
- }
- }
- }
-
- if info == nil {
- info = &HoverContext{signatureSource: obj}
- }
-
- return info, nil
-}
-
-// isFunctionParam returns true if the passed object is either an incoming
-// or an outgoing function param
-func isFunctionParam(obj types.Object, node *ast.FuncDecl) bool {
- for _, f := range node.Type.Params.List {
- if f.Pos() == obj.Pos() {
- return true
- }
- }
- if node.Type.Results != nil {
- for _, f := range node.Type.Results.List {
- if f.Pos() == obj.Pos() {
- return true
- }
- }
- }
- return false
-}
-
-// hoverGenDecl returns hover information an object declared via spec inside
-// of the GenDecl node. obj is the type-checked object corresponding to the
-// declaration, but may have been type-checked using a different AST than the
-// given nodes; fullPos is the position of obj in node's AST.
-func hoverGenDecl(node *ast.GenDecl, spec ast.Spec, fullPos token.Pos, obj types.Object) (*HoverContext, error) {
- if spec == nil {
- return nil, errors.Errorf("no spec for node %v at position %v", node, fullPos)
- }
-
- // If we have a field or method.
- switch obj.(type) {
- case *types.Var, *types.Const, *types.Func:
- return hoverVar(spec, fullPos, obj, node), nil
- }
- // Handle types.
- switch spec := spec.(type) {
- case *ast.TypeSpec:
- return hoverTypeSpec(spec, node), nil
- case *ast.ValueSpec:
- return &HoverContext{signatureSource: spec, Comment: spec.Doc}, nil
- case *ast.ImportSpec:
- return &HoverContext{signatureSource: spec, Comment: spec.Doc}, nil
- }
- return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec)
-}
-
-// TODO(rfindley): rename this function.
-func hoverTypeSpec(spec *ast.TypeSpec, decl *ast.GenDecl) *HoverContext {
- comment := spec.Doc
- if comment == nil && decl != nil {
- comment = decl.Doc
- }
- if comment == nil {
- comment = spec.Comment
- }
- return &HoverContext{
- signatureSource: spec,
- Comment: comment,
- }
-}
-
-func hoverVar(node ast.Spec, fullPos token.Pos, obj types.Object, decl *ast.GenDecl) *HoverContext {
- var fieldList *ast.FieldList
- switch spec := node.(type) {
- case *ast.TypeSpec:
- switch t := spec.Type.(type) {
- case *ast.StructType:
- fieldList = t.Fields
- case *ast.InterfaceType:
- fieldList = t.Methods
- }
- case *ast.ValueSpec:
- // Try to extract the field list of an anonymous struct
- if fieldList = extractFieldList(spec.Type); fieldList != nil {
- break
- }
-
- comment := spec.Doc
- if comment == nil {
- comment = decl.Doc
- }
- if comment == nil {
- comment = spec.Comment
- }
-
- // We need the AST nodes for variable declarations of basic literals with
- // associated values so that we can augment their hover with more information.
- if _, ok := obj.(*types.Var); ok && spec.Type == nil && len(spec.Values) > 0 {
- if _, ok := spec.Values[0].(*ast.BasicLit); ok {
- return &HoverContext{signatureSource: spec, Comment: comment}
- }
- }
-
- return &HoverContext{signatureSource: obj, Comment: comment}
- }
-
- if fieldList != nil {
- comment := findFieldComment(fullPos, fieldList)
- return &HoverContext{signatureSource: obj, Comment: comment}
- }
- return &HoverContext{signatureSource: obj, Comment: decl.Doc}
-}
-
-// extractFieldList recursively tries to extract a field list.
-// If it is not found, nil is returned.
-func extractFieldList(specType ast.Expr) *ast.FieldList {
- switch t := specType.(type) {
- case *ast.StructType:
- return t.Fields
- case *ast.InterfaceType:
- return t.Methods
- case *ast.ArrayType:
- return extractFieldList(t.Elt)
- case *ast.MapType:
- // Map value has a greater chance to be a struct
- if fields := extractFieldList(t.Value); fields != nil {
- return fields
- }
- return extractFieldList(t.Key)
- case *ast.ChanType:
- return extractFieldList(t.Value)
- }
- return nil
-}
-
-// findFieldComment visits all fields in depth-first order and returns
-// the comment of a field with passed position. If no comment is found,
-// nil is returned.
-func findFieldComment(pos token.Pos, fieldList *ast.FieldList) *ast.CommentGroup {
- for _, field := range fieldList.List {
- if field.Pos() == pos {
- if field.Doc.Text() != "" {
- return field.Doc
- }
- return field.Comment
- }
-
- if nestedFieldList := extractFieldList(field.Type); nestedFieldList != nil {
- if c := findFieldComment(pos, nestedFieldList); c != nil {
- return c
- }
- }
- }
- return nil
-}
-
-func FormatHover(h *HoverJSON, options *Options) (string, error) {
- signature := formatSignature(h, options)
-
- switch options.HoverKind {
- case SingleLine:
- return h.SingleLine, nil
- case NoDocumentation:
- return signature, nil
- case Structured:
- b, err := json.Marshal(h)
- if err != nil {
- return "", err
- }
- return string(b), nil
- }
-
- link := formatLink(h, options)
- doc := formatDoc(h, options)
-
- var b strings.Builder
- parts := []string{signature, doc, link}
- for i, el := range parts {
- if el != "" {
- b.WriteString(el)
-
- // Don't write out final newline.
- if i == len(parts) {
- continue
- }
- // If any elements of the remainder of the list are non-empty,
- // write a newline.
- if anyNonEmpty(parts[i+1:]) {
- if options.PreferredContentFormat == protocol.Markdown {
- b.WriteString("\n\n")
- } else {
- b.WriteRune('\n')
- }
- }
- }
- }
- return b.String(), nil
-}
-
-func formatSignature(h *HoverJSON, options *Options) string {
- signature := h.Signature
- if signature != "" && options.PreferredContentFormat == protocol.Markdown {
- signature = fmt.Sprintf("```go\n%s\n```", signature)
- }
- return signature
-}
-
-func formatLink(h *HoverJSON, options *Options) string {
- if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" {
- return ""
- }
- plainLink := BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor)
- switch options.PreferredContentFormat {
- case protocol.Markdown:
- return fmt.Sprintf("[`%s` on %s](%s)", h.SymbolName, options.LinkTarget, plainLink)
- case protocol.PlainText:
- return ""
- default:
- return plainLink
- }
-}
-
-// BuildLink constructs a link with the given target, path, and anchor.
-func BuildLink(target, path, anchor string) string {
- link := fmt.Sprintf("https://%s/%s", target, path)
- if target == "pkg.go.dev" {
- link += "?utm_source=gopls"
- }
- if anchor == "" {
- return link
- }
- return link + "#" + anchor
-}
-
-func formatDoc(h *HoverJSON, options *Options) string {
- var doc string
- switch options.HoverKind {
- case SynopsisDocumentation:
- doc = h.Synopsis
- case FullDocumentation:
- doc = h.FullDocumentation
- }
- if options.PreferredContentFormat == protocol.Markdown {
- return CommentToMarkdown(doc)
- }
- return doc
-}
-
-func anyNonEmpty(x []string) bool {
- for _, el := range x {
- if el != "" {
- return true
- }
- }
- return false
-}