github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/source/hover.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package source
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/constant"
    13  	"go/doc"
    14  	"go/format"
    15  	"go/token"
    16  	"go/types"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  	"unicode/utf8"
    21  
    22  	"golang.org/x/text/unicode/runenames"
    23  	"github.com/powerman/golang-tools/internal/event"
    24  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    25  	"github.com/powerman/golang-tools/internal/typeparams"
    26  	errors "golang.org/x/xerrors"
    27  )
    28  
    29  // HoverContext contains context extracted from the syntax and type information
    30  // of a given node, for use in various summaries (hover, autocomplete,
    31  // signature help).
    32  type HoverContext struct {
    33  	// signatureSource is the object or node use to derive the hover signature.
    34  	//
    35  	// It may also hold a precomputed string.
    36  	// TODO(rfindley): pre-compute all signatures to avoid this indirection.
    37  	signatureSource interface{}
    38  
    39  	// comment is the most relevant comment group associated with the hovered object.
    40  	Comment *ast.CommentGroup
    41  }
    42  
    43  // HoverJSON contains information used by hover. It is also the JSON returned
    44  // for the "structured" hover format
    45  type HoverJSON struct {
    46  	// Synopsis is a single sentence synopsis of the symbol's documentation.
    47  	Synopsis string `json:"synopsis"`
    48  
    49  	// FullDocumentation is the symbol's full documentation.
    50  	FullDocumentation string `json:"fullDocumentation"`
    51  
    52  	// Signature is the symbol's signature.
    53  	Signature string `json:"signature"`
    54  
    55  	// SingleLine is a single line describing the symbol.
    56  	// This is recommended only for use in clients that show a single line for hover.
    57  	SingleLine string `json:"singleLine"`
    58  
    59  	// SymbolName is the types.Object.Name for the given symbol.
    60  	SymbolName string `json:"symbolName"`
    61  
    62  	// LinkPath is the pkg.go.dev link for the given symbol.
    63  	// For example, the "go/ast" part of "pkg.go.dev/go/ast#Node".
    64  	LinkPath string `json:"linkPath"`
    65  
    66  	// LinkAnchor is the pkg.go.dev link anchor for the given symbol.
    67  	// For example, the "Node" part of "pkg.go.dev/go/ast#Node".
    68  	LinkAnchor string `json:"linkAnchor"`
    69  }
    70  
    71  func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
    72  	ident, err := Identifier(ctx, snapshot, fh, position)
    73  	if err != nil {
    74  		if hover, innerErr := hoverRune(ctx, snapshot, fh, position); innerErr == nil {
    75  			return hover, nil
    76  		}
    77  		return nil, nil
    78  	}
    79  	h, err := HoverIdentifier(ctx, ident)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	rng, err := ident.Range()
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	hover, err := FormatHover(h, snapshot.View().Options())
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	return &protocol.Hover{
    92  		Contents: protocol.MarkupContent{
    93  			Kind:  snapshot.View().Options().PreferredContentFormat,
    94  			Value: hover,
    95  		},
    96  		Range: rng,
    97  	}, nil
    98  }
    99  
   100  func hoverRune(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
   101  	ctx, done := event.Start(ctx, "source.hoverRune")
   102  	defer done()
   103  
   104  	r, mrng, err := findRune(ctx, snapshot, fh, position)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	rng, err := mrng.Range()
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	var desc string
   114  	runeName := runenames.Name(r)
   115  	if len(runeName) > 0 && runeName[0] == '<' {
   116  		// Check if the rune looks like an HTML tag. If so, trim the surrounding <>
   117  		// characters to work around https://github.com/microsoft/vscode/issues/124042.
   118  		runeName = strings.TrimRight(runeName[1:], ">")
   119  	}
   120  	if strconv.IsPrint(r) {
   121  		desc = fmt.Sprintf("'%s', U+%04X, %s", string(r), uint32(r), runeName)
   122  	} else {
   123  		desc = fmt.Sprintf("U+%04X, %s", uint32(r), runeName)
   124  	}
   125  	return &protocol.Hover{
   126  		Contents: protocol.MarkupContent{
   127  			Kind:  snapshot.View().Options().PreferredContentFormat,
   128  			Value: desc,
   129  		},
   130  		Range: rng,
   131  	}, nil
   132  }
   133  
   134  // ErrNoRuneFound is the error returned when no rune is found at a particular position.
   135  var ErrNoRuneFound = errors.New("no rune found")
   136  
   137  // findRune returns rune information for a position in a file.
   138  func findRune(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (rune, MappedRange, error) {
   139  	pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
   140  	if err != nil {
   141  		return 0, MappedRange{}, err
   142  	}
   143  	spn, err := pgf.Mapper.PointSpan(position)
   144  	if err != nil {
   145  		return 0, MappedRange{}, err
   146  	}
   147  	rng, err := spn.Range(pgf.Mapper.Converter)
   148  	if err != nil {
   149  		return 0, MappedRange{}, err
   150  	}
   151  	pos := rng.Start
   152  
   153  	// Find the basic literal enclosing the given position, if there is one.
   154  	var lit *ast.BasicLit
   155  	var found bool
   156  	ast.Inspect(pgf.File, func(n ast.Node) bool {
   157  		if found {
   158  			return false
   159  		}
   160  		if n, ok := n.(*ast.BasicLit); ok && pos >= n.Pos() && pos <= n.End() {
   161  			lit = n
   162  			found = true
   163  		}
   164  		return !found
   165  	})
   166  	if !found {
   167  		return 0, MappedRange{}, ErrNoRuneFound
   168  	}
   169  
   170  	var r rune
   171  	var start, end token.Pos
   172  	switch lit.Kind {
   173  	case token.CHAR:
   174  		s, err := strconv.Unquote(lit.Value)
   175  		if err != nil {
   176  			// If the conversion fails, it's because of an invalid syntax, therefore
   177  			// there is no rune to be found.
   178  			return 0, MappedRange{}, ErrNoRuneFound
   179  		}
   180  		r, _ = utf8.DecodeRuneInString(s)
   181  		if r == utf8.RuneError {
   182  			return 0, MappedRange{}, fmt.Errorf("rune error")
   183  		}
   184  		start, end = lit.Pos(), lit.End()
   185  	case token.INT:
   186  		// It's an integer, scan only if it is a hex litteral whose bitsize in
   187  		// ranging from 8 to 32.
   188  		if !(strings.HasPrefix(lit.Value, "0x") && len(lit.Value[2:]) >= 2 && len(lit.Value[2:]) <= 8) {
   189  			return 0, MappedRange{}, ErrNoRuneFound
   190  		}
   191  		v, err := strconv.ParseUint(lit.Value[2:], 16, 32)
   192  		if err != nil {
   193  			return 0, MappedRange{}, err
   194  		}
   195  		r = rune(v)
   196  		if r == utf8.RuneError {
   197  			return 0, MappedRange{}, fmt.Errorf("rune error")
   198  		}
   199  		start, end = lit.Pos(), lit.End()
   200  	case token.STRING:
   201  		// It's a string, scan only if it contains a unicode escape sequence under or before the
   202  		// current cursor position.
   203  		var found bool
   204  		litOffset, err := Offset(pgf.Tok, lit.Pos())
   205  		if err != nil {
   206  			return 0, MappedRange{}, err
   207  		}
   208  		offset, err := Offset(pgf.Tok, pos)
   209  		if err != nil {
   210  			return 0, MappedRange{}, err
   211  		}
   212  		for i := offset - litOffset; i > 0; i-- {
   213  			// Start at the cursor position and search backward for the beginning of a rune escape sequence.
   214  			rr, _ := utf8.DecodeRuneInString(lit.Value[i:])
   215  			if rr == utf8.RuneError {
   216  				return 0, MappedRange{}, fmt.Errorf("rune error")
   217  			}
   218  			if rr == '\\' {
   219  				// Got the beginning, decode it.
   220  				var tail string
   221  				r, _, tail, err = strconv.UnquoteChar(lit.Value[i:], '"')
   222  				if err != nil {
   223  					// If the conversion fails, it's because of an invalid syntax, therefore is no rune to be found.
   224  					return 0, MappedRange{}, ErrNoRuneFound
   225  				}
   226  				// Only the rune escape sequence part of the string has to be highlighted, recompute the range.
   227  				runeLen := len(lit.Value) - (int(i) + len(tail))
   228  				start = token.Pos(int(lit.Pos()) + int(i))
   229  				end = token.Pos(int(start) + runeLen)
   230  				found = true
   231  				break
   232  			}
   233  		}
   234  		if !found {
   235  			// No escape sequence found
   236  			return 0, MappedRange{}, ErrNoRuneFound
   237  		}
   238  	default:
   239  		return 0, MappedRange{}, ErrNoRuneFound
   240  	}
   241  
   242  	mappedRange, err := posToMappedRange(snapshot, pkg, start, end)
   243  	if err != nil {
   244  		return 0, MappedRange{}, err
   245  	}
   246  	return r, mappedRange, nil
   247  }
   248  
   249  func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverJSON, error) {
   250  	ctx, done := event.Start(ctx, "source.Hover")
   251  	defer done()
   252  
   253  	hoverCtx, err := FindHoverContext(ctx, i.Snapshot, i.pkg, i.Declaration.obj, i.Declaration.node, i.Declaration.fullDecl)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	h := &HoverJSON{
   259  		FullDocumentation: hoverCtx.Comment.Text(),
   260  		Synopsis:          doc.Synopsis(hoverCtx.Comment.Text()),
   261  	}
   262  
   263  	fset := i.Snapshot.FileSet()
   264  	// Determine the symbol's signature.
   265  	switch x := hoverCtx.signatureSource.(type) {
   266  	case string:
   267  		h.Signature = x // a pre-computed signature
   268  
   269  	case *ast.TypeSpec:
   270  		x2 := *x
   271  		// Don't duplicate comments when formatting type specs.
   272  		x2.Doc = nil
   273  		x2.Comment = nil
   274  		var b strings.Builder
   275  		b.WriteString("type ")
   276  		if err := format.Node(&b, fset, &x2); err != nil {
   277  			return nil, err
   278  		}
   279  		h.Signature = b.String()
   280  
   281  	case ast.Node:
   282  		var b strings.Builder
   283  		if err := format.Node(&b, fset, x); err != nil {
   284  			return nil, err
   285  		}
   286  		h.Signature = b.String()
   287  
   288  		// Check if the variable is an integer whose value we can present in a more
   289  		// user-friendly way, i.e. `var hex = 0xe34e` becomes `var hex = 58190`
   290  		if spec, ok := x.(*ast.ValueSpec); ok && len(spec.Values) > 0 {
   291  			if lit, ok := spec.Values[0].(*ast.BasicLit); ok && len(spec.Names) > 0 {
   292  				val := constant.MakeFromLiteral(types.ExprString(lit), lit.Kind, 0)
   293  				h.Signature = fmt.Sprintf("var %s = %s", spec.Names[0], val)
   294  			}
   295  		}
   296  
   297  	case types.Object:
   298  		// If the variable is implicitly declared in a type switch, we need to
   299  		// manually generate its object string.
   300  		if typ := i.Declaration.typeSwitchImplicit; typ != nil {
   301  			if v, ok := x.(*types.Var); ok {
   302  				h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf))
   303  				break
   304  			}
   305  		}
   306  		h.Signature = objectString(x, i.qf, i.Inferred)
   307  	}
   308  	if obj := i.Declaration.obj; obj != nil {
   309  		h.SingleLine = objectString(obj, i.qf, nil)
   310  	}
   311  	obj := i.Declaration.obj
   312  	if obj == nil {
   313  		return h, nil
   314  	}
   315  
   316  	// Check if the identifier is test-only (and is therefore not part of a
   317  	// package's API). This is true if the request originated in a test package,
   318  	// and if the declaration is also found in the same test package.
   319  	if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" {
   320  		if _, err := i.pkg.File(i.Declaration.MappedRange[0].URI()); err == nil {
   321  			return h, nil
   322  		}
   323  	}
   324  
   325  	h.SymbolName, h.LinkPath, h.LinkAnchor = linkData(obj, i.enclosing)
   326  
   327  	// See golang/go#36998: don't link to modules matching GOPRIVATE.
   328  	//
   329  	// The path returned by linkData is an import path.
   330  	if i.Snapshot.View().IsGoPrivatePath(h.LinkPath) {
   331  		h.LinkPath = ""
   332  	} else if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok {
   333  		h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1)
   334  	}
   335  
   336  	return h, nil
   337  }
   338  
   339  // linkData returns the name, import path, and anchor to use in building links
   340  // to obj.
   341  //
   342  // If obj is not visible in documentation, the returned name will be empty.
   343  func linkData(obj types.Object, enclosing *types.TypeName) (name, importPath, anchor string) {
   344  	// Package names simply link to the package.
   345  	if obj, ok := obj.(*types.PkgName); ok {
   346  		return obj.Name(), obj.Imported().Path(), ""
   347  	}
   348  
   349  	// Builtins link to the special builtin package.
   350  	if obj.Parent() == types.Universe {
   351  		return obj.Name(), "builtin", obj.Name()
   352  	}
   353  
   354  	// In all other cases, the object must be exported.
   355  	if !obj.Exported() {
   356  		return "", "", ""
   357  	}
   358  
   359  	var recv types.Object // If non-nil, the field or method receiver base.
   360  
   361  	switch obj := obj.(type) {
   362  	case *types.Var:
   363  		// If the object is a field, and we have an associated selector
   364  		// composite literal, or struct, we can determine the link.
   365  		if obj.IsField() && enclosing != nil {
   366  			recv = enclosing
   367  		}
   368  	case *types.Func:
   369  		typ, ok := obj.Type().(*types.Signature)
   370  		if !ok {
   371  			// Note: this should never happen. go/types guarantees that the type of
   372  			// *Funcs are Signatures.
   373  			//
   374  			// TODO(rfindley): given a 'debug' mode, we should panic here.
   375  			return "", "", ""
   376  		}
   377  		if r := typ.Recv(); r != nil {
   378  			if rtyp, _ := Deref(r.Type()).(*types.Named); rtyp != nil {
   379  				// If we have an unexported type, see if the enclosing type is
   380  				// exported (we may have an interface or struct we can link
   381  				// to). If not, don't show any link.
   382  				if !rtyp.Obj().Exported() {
   383  					if enclosing != nil {
   384  						recv = enclosing
   385  					} else {
   386  						return "", "", ""
   387  					}
   388  				} else {
   389  					recv = rtyp.Obj()
   390  				}
   391  			}
   392  		}
   393  	}
   394  
   395  	if recv != nil && !recv.Exported() {
   396  		return "", "", ""
   397  	}
   398  
   399  	// Either the object or its receiver must be in the package scope.
   400  	scopeObj := obj
   401  	if recv != nil {
   402  		scopeObj = recv
   403  	}
   404  	if scopeObj.Pkg() == nil || scopeObj.Pkg().Scope().Lookup(scopeObj.Name()) != scopeObj {
   405  		return "", "", ""
   406  	}
   407  
   408  	importPath = obj.Pkg().Path()
   409  	if recv != nil {
   410  		anchor = fmt.Sprintf("%s.%s", recv.Name(), obj.Name())
   411  		name = fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), recv.Name(), obj.Name())
   412  	} else {
   413  		// For most cases, the link is "package/path#symbol".
   414  		anchor = obj.Name()
   415  		name = fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name())
   416  	}
   417  	return name, importPath, anchor
   418  }
   419  
   420  func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {
   421  	// TODO(rfindley): moduleAtVersion should not be responsible for deciding
   422  	// whether or not the link target supports module version links.
   423  	if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" {
   424  		return "", "", false
   425  	}
   426  	impPkg, err := i.pkg.GetImport(path)
   427  	if err != nil {
   428  		return "", "", false
   429  	}
   430  	if impPkg.Version() == nil {
   431  		return "", "", false
   432  	}
   433  	version, modpath := impPkg.Version().Version, impPkg.Version().Path
   434  	if modpath == "" || version == "" {
   435  		return "", "", false
   436  	}
   437  	return modpath, version, true
   438  }
   439  
   440  // objectString is a wrapper around the types.ObjectString function.
   441  // It handles adding more information to the object string.
   442  func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string {
   443  	// If the signature type was inferred, prefer the preferred signature with a
   444  	// comment showing the generic signature.
   445  	if sig, _ := obj.Type().(*types.Signature); sig != nil && typeparams.ForSignature(sig).Len() > 0 && inferred != nil {
   446  		obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred)
   447  		str := types.ObjectString(obj2, qf)
   448  		// Try to avoid overly long lines.
   449  		if len(str) > 60 {
   450  			str += "\n"
   451  		} else {
   452  			str += " "
   453  		}
   454  		str += "// " + types.TypeString(sig, qf)
   455  		return str
   456  	}
   457  	str := types.ObjectString(obj, qf)
   458  	switch obj := obj.(type) {
   459  	case *types.Const:
   460  		str = fmt.Sprintf("%s = %s", str, obj.Val())
   461  
   462  		// Try to add a formatted duration as an inline comment
   463  		typ, ok := obj.Type().(*types.Named)
   464  		if !ok {
   465  			break
   466  		}
   467  		pkg := typ.Obj().Pkg()
   468  		if pkg.Path() == "time" && typ.Obj().Name() == "Duration" {
   469  			if d, ok := constant.Int64Val(obj.Val()); ok {
   470  				str += " // " + time.Duration(d).String()
   471  			}
   472  		}
   473  	}
   474  	return str
   475  }
   476  
   477  // FindHoverContext returns a HoverContext struct for an AST node and its
   478  // declaration object. node should be the actual node used in type checking,
   479  // while fullNode could be a separate node with more complete syntactic
   480  // information.
   481  func FindHoverContext(ctx context.Context, s Snapshot, pkg Package, obj types.Object, pkgNode ast.Node, fullDecl ast.Decl) (*HoverContext, error) {
   482  	var info *HoverContext
   483  
   484  	// Type parameters get their signature from their declaration object.
   485  	if _, isTypeName := obj.(*types.TypeName); isTypeName {
   486  		if _, isTypeParam := obj.Type().(*typeparams.TypeParam); isTypeParam {
   487  			return &HoverContext{signatureSource: obj}, nil
   488  		}
   489  	}
   490  
   491  	// This is problematic for a number of reasons. We really need to have a more
   492  	// general mechanism to validate the coherency of AST with type information,
   493  	// but absent that we must do our best to ensure that we don't use fullNode
   494  	// when we actually need the node that was type checked.
   495  	//
   496  	// pkgNode may be nil, if it was eliminated from the type-checked syntax. In
   497  	// that case, use fullDecl if available.
   498  	node := pkgNode
   499  	if node == nil && fullDecl != nil {
   500  		node = fullDecl
   501  	}
   502  
   503  	switch node := node.(type) {
   504  	case *ast.Ident:
   505  		// The package declaration.
   506  		for _, f := range pkg.GetSyntax() {
   507  			if f.Name == pkgNode {
   508  				info = &HoverContext{Comment: f.Doc}
   509  			}
   510  		}
   511  	case *ast.ImportSpec:
   512  		// Try to find the package documentation for an imported package.
   513  		if pkgName, ok := obj.(*types.PkgName); ok {
   514  			imp, err := pkg.GetImport(pkgName.Imported().Path())
   515  			if err != nil {
   516  				return nil, err
   517  			}
   518  			// Assume that only one file will contain package documentation,
   519  			// so pick the first file that has a doc comment.
   520  			for _, file := range imp.GetSyntax() {
   521  				if file.Doc != nil {
   522  					info = &HoverContext{signatureSource: obj, Comment: file.Doc}
   523  					break
   524  				}
   525  			}
   526  		}
   527  		info = &HoverContext{signatureSource: node}
   528  	case *ast.GenDecl:
   529  		switch obj := obj.(type) {
   530  		case *types.TypeName, *types.Var, *types.Const, *types.Func:
   531  			// Always use the full declaration here if we have it, because the
   532  			// dependent code doesn't rely on pointer identity. This is fragile.
   533  			if d, _ := fullDecl.(*ast.GenDecl); d != nil {
   534  				node = d
   535  			}
   536  			// obj may not have been produced by type checking the AST containing
   537  			// node, so we need to be careful about using token.Pos.
   538  			tok := s.FileSet().File(obj.Pos())
   539  			offset, err := Offset(tok, obj.Pos())
   540  			if err != nil {
   541  				return nil, err
   542  			}
   543  
   544  			// fullTok and fullPos are the *token.File and object position in for the
   545  			// full AST.
   546  			fullTok := s.FileSet().File(node.Pos())
   547  			fullPos, err := Pos(fullTok, offset)
   548  			if err != nil {
   549  				return nil, err
   550  			}
   551  
   552  			var spec ast.Spec
   553  			for _, s := range node.Specs {
   554  				// Avoid panics by guarding the calls to token.Offset (golang/go#48249).
   555  				start, err := Offset(fullTok, s.Pos())
   556  				if err != nil {
   557  					return nil, err
   558  				}
   559  				end, err := Offset(fullTok, s.End())
   560  				if err != nil {
   561  					return nil, err
   562  				}
   563  				if start <= offset && offset <= end {
   564  					spec = s
   565  					break
   566  				}
   567  			}
   568  
   569  			info, err = hoverGenDecl(node, spec, fullPos, obj)
   570  			if err != nil {
   571  				return nil, err
   572  			}
   573  		}
   574  	case *ast.TypeSpec:
   575  		if obj.Parent() == types.Universe {
   576  			if genDecl, ok := fullDecl.(*ast.GenDecl); ok {
   577  				info = hoverTypeSpec(node, genDecl)
   578  			}
   579  		}
   580  	case *ast.FuncDecl:
   581  		switch obj.(type) {
   582  		case *types.Func:
   583  			info = &HoverContext{signatureSource: obj, Comment: node.Doc}
   584  		case *types.Builtin:
   585  			info = &HoverContext{Comment: node.Doc}
   586  			if sig, err := NewBuiltinSignature(ctx, s, obj.Name()); err == nil {
   587  				info.signatureSource = "func " + sig.name + sig.Format()
   588  			} else {
   589  				// Fall back on the object as a signature source.
   590  
   591  				// TODO(rfindley): refactor so that we can report bugs from the source
   592  				// package.
   593  
   594  				// debug.Bug(ctx, "invalid builtin hover", "did not find builtin signature: %v", err)
   595  				info.signatureSource = obj
   596  			}
   597  		case *types.Var:
   598  			// Object is a function param or the field of an anonymous struct
   599  			// declared with ':='. Skip the first one because only fields
   600  			// can have docs.
   601  			if isFunctionParam(obj, node) {
   602  				break
   603  			}
   604  
   605  			field, err := s.PosToField(ctx, pkg, obj.Pos())
   606  			if err != nil {
   607  				return nil, err
   608  			}
   609  
   610  			if field != nil {
   611  				comment := field.Doc
   612  				if comment.Text() == "" {
   613  					comment = field.Comment
   614  				}
   615  				info = &HoverContext{signatureSource: obj, Comment: comment}
   616  			}
   617  		}
   618  	}
   619  
   620  	if info == nil {
   621  		info = &HoverContext{signatureSource: obj}
   622  	}
   623  
   624  	return info, nil
   625  }
   626  
   627  // isFunctionParam returns true if the passed object is either an incoming
   628  // or an outgoing function param
   629  func isFunctionParam(obj types.Object, node *ast.FuncDecl) bool {
   630  	for _, f := range node.Type.Params.List {
   631  		if f.Pos() == obj.Pos() {
   632  			return true
   633  		}
   634  	}
   635  	if node.Type.Results != nil {
   636  		for _, f := range node.Type.Results.List {
   637  			if f.Pos() == obj.Pos() {
   638  				return true
   639  			}
   640  		}
   641  	}
   642  	return false
   643  }
   644  
   645  // hoverGenDecl returns hover information an object declared via spec inside
   646  // of the GenDecl node. obj is the type-checked object corresponding to the
   647  // declaration, but may have been type-checked using a different AST than the
   648  // given nodes; fullPos is the position of obj in node's AST.
   649  func hoverGenDecl(node *ast.GenDecl, spec ast.Spec, fullPos token.Pos, obj types.Object) (*HoverContext, error) {
   650  	if spec == nil {
   651  		return nil, errors.Errorf("no spec for node %v at position %v", node, fullPos)
   652  	}
   653  
   654  	// If we have a field or method.
   655  	switch obj.(type) {
   656  	case *types.Var, *types.Const, *types.Func:
   657  		return hoverVar(spec, fullPos, obj, node), nil
   658  	}
   659  	// Handle types.
   660  	switch spec := spec.(type) {
   661  	case *ast.TypeSpec:
   662  		return hoverTypeSpec(spec, node), nil
   663  	case *ast.ValueSpec:
   664  		return &HoverContext{signatureSource: spec, Comment: spec.Doc}, nil
   665  	case *ast.ImportSpec:
   666  		return &HoverContext{signatureSource: spec, Comment: spec.Doc}, nil
   667  	}
   668  	return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec)
   669  }
   670  
   671  // TODO(rfindley): rename this function.
   672  func hoverTypeSpec(spec *ast.TypeSpec, decl *ast.GenDecl) *HoverContext {
   673  	comment := spec.Doc
   674  	if comment == nil && decl != nil {
   675  		comment = decl.Doc
   676  	}
   677  	if comment == nil {
   678  		comment = spec.Comment
   679  	}
   680  	return &HoverContext{
   681  		signatureSource: spec,
   682  		Comment:         comment,
   683  	}
   684  }
   685  
   686  func hoverVar(node ast.Spec, fullPos token.Pos, obj types.Object, decl *ast.GenDecl) *HoverContext {
   687  	var fieldList *ast.FieldList
   688  	switch spec := node.(type) {
   689  	case *ast.TypeSpec:
   690  		switch t := spec.Type.(type) {
   691  		case *ast.StructType:
   692  			fieldList = t.Fields
   693  		case *ast.InterfaceType:
   694  			fieldList = t.Methods
   695  		}
   696  	case *ast.ValueSpec:
   697  		// Try to extract the field list of an anonymous struct
   698  		if fieldList = extractFieldList(spec.Type); fieldList != nil {
   699  			break
   700  		}
   701  
   702  		comment := spec.Doc
   703  		if comment == nil {
   704  			comment = decl.Doc
   705  		}
   706  		if comment == nil {
   707  			comment = spec.Comment
   708  		}
   709  
   710  		// We need the AST nodes for variable declarations of basic literals with
   711  		// associated values so that we can augment their hover with more information.
   712  		if _, ok := obj.(*types.Var); ok && spec.Type == nil && len(spec.Values) > 0 {
   713  			if _, ok := spec.Values[0].(*ast.BasicLit); ok {
   714  				return &HoverContext{signatureSource: spec, Comment: comment}
   715  			}
   716  		}
   717  
   718  		return &HoverContext{signatureSource: obj, Comment: comment}
   719  	}
   720  
   721  	if fieldList != nil {
   722  		comment := findFieldComment(fullPos, fieldList)
   723  		return &HoverContext{signatureSource: obj, Comment: comment}
   724  	}
   725  	return &HoverContext{signatureSource: obj, Comment: decl.Doc}
   726  }
   727  
   728  // extractFieldList recursively tries to extract a field list.
   729  // If it is not found, nil is returned.
   730  func extractFieldList(specType ast.Expr) *ast.FieldList {
   731  	switch t := specType.(type) {
   732  	case *ast.StructType:
   733  		return t.Fields
   734  	case *ast.InterfaceType:
   735  		return t.Methods
   736  	case *ast.ArrayType:
   737  		return extractFieldList(t.Elt)
   738  	case *ast.MapType:
   739  		// Map value has a greater chance to be a struct
   740  		if fields := extractFieldList(t.Value); fields != nil {
   741  			return fields
   742  		}
   743  		return extractFieldList(t.Key)
   744  	case *ast.ChanType:
   745  		return extractFieldList(t.Value)
   746  	}
   747  	return nil
   748  }
   749  
   750  // findFieldComment visits all fields in depth-first order and returns
   751  // the comment of a field with passed position. If no comment is found,
   752  // nil is returned.
   753  func findFieldComment(pos token.Pos, fieldList *ast.FieldList) *ast.CommentGroup {
   754  	for _, field := range fieldList.List {
   755  		if field.Pos() == pos {
   756  			if field.Doc.Text() != "" {
   757  				return field.Doc
   758  			}
   759  			return field.Comment
   760  		}
   761  
   762  		if nestedFieldList := extractFieldList(field.Type); nestedFieldList != nil {
   763  			if c := findFieldComment(pos, nestedFieldList); c != nil {
   764  				return c
   765  			}
   766  		}
   767  	}
   768  	return nil
   769  }
   770  
   771  func FormatHover(h *HoverJSON, options *Options) (string, error) {
   772  	signature := formatSignature(h, options)
   773  
   774  	switch options.HoverKind {
   775  	case SingleLine:
   776  		return h.SingleLine, nil
   777  	case NoDocumentation:
   778  		return signature, nil
   779  	case Structured:
   780  		b, err := json.Marshal(h)
   781  		if err != nil {
   782  			return "", err
   783  		}
   784  		return string(b), nil
   785  	}
   786  
   787  	link := formatLink(h, options)
   788  	doc := formatDoc(h, options)
   789  
   790  	var b strings.Builder
   791  	parts := []string{signature, doc, link}
   792  	for i, el := range parts {
   793  		if el != "" {
   794  			b.WriteString(el)
   795  
   796  			// Don't write out final newline.
   797  			if i == len(parts) {
   798  				continue
   799  			}
   800  			// If any elements of the remainder of the list are non-empty,
   801  			// write a newline.
   802  			if anyNonEmpty(parts[i+1:]) {
   803  				if options.PreferredContentFormat == protocol.Markdown {
   804  					b.WriteString("\n\n")
   805  				} else {
   806  					b.WriteRune('\n')
   807  				}
   808  			}
   809  		}
   810  	}
   811  	return b.String(), nil
   812  }
   813  
   814  func formatSignature(h *HoverJSON, options *Options) string {
   815  	signature := h.Signature
   816  	if signature != "" && options.PreferredContentFormat == protocol.Markdown {
   817  		signature = fmt.Sprintf("```go\n%s\n```", signature)
   818  	}
   819  	return signature
   820  }
   821  
   822  func formatLink(h *HoverJSON, options *Options) string {
   823  	if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" {
   824  		return ""
   825  	}
   826  	plainLink := BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor)
   827  	switch options.PreferredContentFormat {
   828  	case protocol.Markdown:
   829  		return fmt.Sprintf("[`%s` on %s](%s)", h.SymbolName, options.LinkTarget, plainLink)
   830  	case protocol.PlainText:
   831  		return ""
   832  	default:
   833  		return plainLink
   834  	}
   835  }
   836  
   837  // BuildLink constructs a link with the given target, path, and anchor.
   838  func BuildLink(target, path, anchor string) string {
   839  	link := fmt.Sprintf("https://%s/%s", target, path)
   840  	if target == "pkg.go.dev" {
   841  		link += "?utm_source=gopls"
   842  	}
   843  	if anchor == "" {
   844  		return link
   845  	}
   846  	return link + "#" + anchor
   847  }
   848  
   849  func formatDoc(h *HoverJSON, options *Options) string {
   850  	var doc string
   851  	switch options.HoverKind {
   852  	case SynopsisDocumentation:
   853  		doc = h.Synopsis
   854  	case FullDocumentation:
   855  		doc = h.FullDocumentation
   856  	}
   857  	if options.PreferredContentFormat == protocol.Markdown {
   858  		return CommentToMarkdown(doc)
   859  	}
   860  	return doc
   861  }
   862  
   863  func anyNonEmpty(x []string) bool {
   864  	for _, el := range x {
   865  		if el != "" {
   866  			return true
   867  		}
   868  	}
   869  	return false
   870  }