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