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 5package source 6 7import ( 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 "strings" 18 "time" 19 20 "golang.org/x/tools/internal/event" 21 "golang.org/x/tools/internal/lsp/protocol" 22 "golang.org/x/tools/internal/typeparams" 23 errors "golang.org/x/xerrors" 24) 25 26type HoverInformation struct { 27 // Signature is the symbol's signature. 28 Signature string `json:"signature"` 29 30 // SingleLine is a single line describing the symbol. 31 // This is recommended only for use in clients that show a single line for hover. 32 SingleLine string `json:"singleLine"` 33 34 // Synopsis is a single sentence synopsis of the symbol's documentation. 35 Synopsis string `json:"synopsis"` 36 37 // FullDocumentation is the symbol's full documentation. 38 FullDocumentation string `json:"fullDocumentation"` 39 40 // LinkPath is the pkg.go.dev link for the given symbol. 41 // For example, the "go/ast" part of "pkg.go.dev/go/ast#Node". 42 LinkPath string `json:"linkPath"` 43 44 // LinkAnchor is the pkg.go.dev link anchor for the given symbol. 45 // For example, the "Node" part of "pkg.go.dev/go/ast#Node". 46 LinkAnchor string `json:"linkAnchor"` 47 48 // importPath is the import path for the package containing the given 49 // symbol. 50 importPath string 51 52 // symbolName is the types.Object.Name for the given symbol. 53 symbolName string 54 55 source interface{} 56 comment *ast.CommentGroup 57 58 // typeName contains the identifier name when the identifier is a type declaration. 59 // If it is not empty, the hover will have the prefix "type <typeName> ". 60 typeName string 61 // isTypeAlias indicates whether the identifier is a type alias declaration. 62 // If it is true, the hover will have the prefix "type <typeName> = ". 63 isTypeAlias bool 64} 65 66func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) { 67 ident, err := Identifier(ctx, snapshot, fh, position) 68 if err != nil { 69 return nil, nil 70 } 71 h, err := HoverIdentifier(ctx, ident) 72 if err != nil { 73 return nil, err 74 } 75 rng, err := ident.Range() 76 if err != nil { 77 return nil, err 78 } 79 // See golang/go#36998: don't link to modules matching GOPRIVATE. 80 if snapshot.View().IsGoPrivatePath(h.importPath) { 81 h.LinkPath = "" 82 } 83 hover, err := FormatHover(h, snapshot.View().Options()) 84 if err != nil { 85 return nil, err 86 } 87 return &protocol.Hover{ 88 Contents: protocol.MarkupContent{ 89 Kind: snapshot.View().Options().PreferredContentFormat, 90 Value: hover, 91 }, 92 Range: rng, 93 }, nil 94} 95 96func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverInformation, error) { 97 ctx, done := event.Start(ctx, "source.Hover") 98 defer done() 99 100 fset := i.Snapshot.FileSet() 101 h, err := HoverInfo(ctx, i.Snapshot, i.pkg, i.Declaration.obj, i.Declaration.node, i.Declaration.fullSpec) 102 if err != nil { 103 return nil, err 104 } 105 // Determine the symbol's signature. 106 switch x := h.source.(type) { 107 case ast.Node: 108 var b strings.Builder 109 if err := format.Node(&b, fset, x); err != nil { 110 return nil, err 111 } 112 h.Signature = b.String() 113 if h.typeName != "" { 114 prefix := "type " + h.typeName + " " 115 if h.isTypeAlias { 116 prefix += "= " 117 } 118 h.Signature = prefix + h.Signature 119 } 120 case types.Object: 121 // If the variable is implicitly declared in a type switch, we need to 122 // manually generate its object string. 123 if typ := i.Declaration.typeSwitchImplicit; typ != nil { 124 if v, ok := x.(*types.Var); ok { 125 h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf)) 126 break 127 } 128 } 129 h.Signature = objectString(x, i.qf, i.Inferred) 130 } 131 if obj := i.Declaration.obj; obj != nil { 132 h.SingleLine = objectString(obj, i.qf, nil) 133 } 134 obj := i.Declaration.obj 135 if obj == nil { 136 return h, nil 137 } 138 switch obj := obj.(type) { 139 case *types.PkgName: 140 h.importPath = obj.Imported().Path() 141 h.LinkPath = h.importPath 142 h.symbolName = obj.Name() 143 if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok { 144 h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1) 145 } 146 return h, nil 147 case *types.Builtin: 148 h.importPath = "builtin" 149 h.LinkPath = h.importPath 150 h.LinkAnchor = obj.Name() 151 h.symbolName = h.LinkAnchor 152 return h, nil 153 } 154 // Check if the identifier is test-only (and is therefore not part of a 155 // package's API). This is true if the request originated in a test package, 156 // and if the declaration is also found in the same test package. 157 if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" { 158 if _, err := i.pkg.File(i.Declaration.MappedRange[0].URI()); err == nil { 159 return h, nil 160 } 161 } 162 // Don't return links for other unexported types. 163 if !obj.Exported() { 164 return h, nil 165 } 166 var rTypeName string 167 switch obj := obj.(type) { 168 case *types.Var: 169 // If the object is a field, and we have an associated selector 170 // composite literal, or struct, we can determine the link. 171 if obj.IsField() { 172 if named, ok := i.enclosing.(*types.Named); ok { 173 rTypeName = named.Obj().Name() 174 } 175 } 176 case *types.Func: 177 typ, ok := obj.Type().(*types.Signature) 178 if !ok { 179 return h, nil 180 } 181 if r := typ.Recv(); r != nil { 182 switch rtyp := Deref(r.Type()).(type) { 183 case *types.Struct: 184 rTypeName = r.Name() 185 case *types.Named: 186 // If we have an unexported type, see if the enclosing type is 187 // exported (we may have an interface or struct we can link 188 // to). If not, don't show any link. 189 if !rtyp.Obj().Exported() { 190 if named, ok := i.enclosing.(*types.Named); ok && named.Obj().Exported() { 191 rTypeName = named.Obj().Name() 192 } else { 193 return h, nil 194 } 195 } else { 196 rTypeName = rtyp.Obj().Name() 197 } 198 } 199 } 200 } 201 if obj.Pkg() == nil { 202 event.Log(ctx, fmt.Sprintf("nil package for %s", obj)) 203 return h, nil 204 } 205 h.importPath = obj.Pkg().Path() 206 h.LinkPath = h.importPath 207 if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok { 208 h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1) 209 } 210 if rTypeName != "" { 211 h.LinkAnchor = fmt.Sprintf("%s.%s", rTypeName, obj.Name()) 212 h.symbolName = fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), rTypeName, obj.Name()) 213 return h, nil 214 } 215 // For most cases, the link is "package/path#symbol". 216 h.LinkAnchor = obj.Name() 217 h.symbolName = fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name()) 218 return h, nil 219} 220 221func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) { 222 if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" { 223 return "", "", false 224 } 225 impPkg, err := i.pkg.GetImport(path) 226 if err != nil { 227 return "", "", false 228 } 229 if impPkg.Version() == nil { 230 return "", "", false 231 } 232 version, modpath := impPkg.Version().Version, impPkg.Version().Path 233 if modpath == "" || version == "" { 234 return "", "", false 235 } 236 return modpath, version, true 237} 238 239// objectString is a wrapper around the types.ObjectString function. 240// It handles adding more information to the object string. 241func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string { 242 // If the signature type was inferred, prefer the preferred signature with a 243 // comment showing the generic signature. 244 if sig, _ := obj.Type().(*types.Signature); sig != nil && len(typeparams.ForSignature(sig)) > 0 && inferred != nil { 245 obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred) 246 str := types.ObjectString(obj2, qf) 247 // Try to avoid overly long lines. 248 if len(str) > 60 { 249 str += "\n" 250 } else { 251 str += " " 252 } 253 str += "// " + types.TypeString(sig, qf) 254 return str 255 } 256 str := types.ObjectString(obj, qf) 257 switch obj := obj.(type) { 258 case *types.Const: 259 str = fmt.Sprintf("%s = %s", str, obj.Val()) 260 261 // Try to add a formatted duration as an inline comment 262 typ, ok := obj.Type().(*types.Named) 263 if !ok { 264 break 265 } 266 pkg := typ.Obj().Pkg() 267 if pkg.Path() == "time" && typ.Obj().Name() == "Duration" { 268 if d, ok := constant.Int64Val(obj.Val()); ok { 269 str += " // " + time.Duration(d).String() 270 } 271 } 272 } 273 return str 274} 275 276// HoverInfo returns a HoverInformation struct for an ast node and its type 277// object. 278func HoverInfo(ctx context.Context, s Snapshot, pkg Package, obj types.Object, node ast.Node, spec ast.Spec) (*HoverInformation, error) { 279 var info *HoverInformation 280 281 switch node := node.(type) { 282 case *ast.Ident: 283 // The package declaration. 284 for _, f := range pkg.GetSyntax() { 285 if f.Name == node { 286 info = &HoverInformation{comment: f.Doc} 287 } 288 } 289 case *ast.ImportSpec: 290 // Try to find the package documentation for an imported package. 291 if pkgName, ok := obj.(*types.PkgName); ok { 292 imp, err := pkg.GetImport(pkgName.Imported().Path()) 293 if err != nil { 294 return nil, err 295 } 296 // Assume that only one file will contain package documentation, 297 // so pick the first file that has a doc comment. 298 for _, file := range imp.GetSyntax() { 299 if file.Doc != nil { 300 info = &HoverInformation{source: obj, comment: file.Doc} 301 break 302 } 303 } 304 } 305 info = &HoverInformation{source: node} 306 case *ast.GenDecl: 307 switch obj := obj.(type) { 308 case *types.TypeName, *types.Var, *types.Const, *types.Func: 309 var err error 310 info, err = formatGenDecl(node, spec, obj, obj.Type()) 311 if err != nil { 312 return nil, err 313 } 314 } 315 case *ast.TypeSpec: 316 if obj.Parent() == types.Universe { 317 if obj.Name() == "error" { 318 info = &HoverInformation{source: node} 319 } else { 320 info = &HoverInformation{source: node.Name} // comments not needed for builtins 321 } 322 } 323 case *ast.FuncDecl: 324 switch obj.(type) { 325 case *types.Func: 326 info = &HoverInformation{source: obj, comment: node.Doc} 327 case *types.Builtin: 328 info = &HoverInformation{source: node.Type, comment: node.Doc} 329 case *types.Var: 330 // Object is a function param or the field of an anonymous struct 331 // declared with ':='. Skip the first one because only fields 332 // can have docs. 333 if isFunctionParam(obj, node) { 334 break 335 } 336 337 field, err := s.PosToField(ctx, pkg, obj.Pos()) 338 if err != nil { 339 return nil, err 340 } 341 342 if field != nil { 343 comment := field.Doc 344 if comment.Text() == "" { 345 comment = field.Comment 346 } 347 info = &HoverInformation{source: obj, comment: comment} 348 } 349 } 350 } 351 352 if info == nil { 353 info = &HoverInformation{source: obj} 354 } 355 356 if info.comment != nil { 357 info.FullDocumentation = info.comment.Text() 358 info.Synopsis = doc.Synopsis(info.FullDocumentation) 359 } 360 361 return info, nil 362} 363 364// isFunctionParam returns true if the passed object is either an incoming 365// or an outgoing function param 366func isFunctionParam(obj types.Object, node *ast.FuncDecl) bool { 367 for _, f := range node.Type.Params.List { 368 if f.Pos() == obj.Pos() { 369 return true 370 } 371 } 372 if node.Type.Results != nil { 373 for _, f := range node.Type.Results.List { 374 if f.Pos() == obj.Pos() { 375 return true 376 } 377 } 378 } 379 return false 380} 381 382func formatGenDecl(node *ast.GenDecl, spec ast.Spec, obj types.Object, typ types.Type) (*HoverInformation, error) { 383 if _, ok := typ.(*types.Named); ok { 384 switch typ.Underlying().(type) { 385 case *types.Interface, *types.Struct: 386 return formatGenDecl(node, spec, obj, typ.Underlying()) 387 } 388 } 389 if spec == nil { 390 for _, s := range node.Specs { 391 if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() { 392 spec = s 393 break 394 } 395 } 396 } 397 if spec == nil { 398 return nil, errors.Errorf("no spec for node %v at position %v", node, obj.Pos()) 399 } 400 401 // If we have a field or method. 402 switch obj.(type) { 403 case *types.Var, *types.Const, *types.Func: 404 return formatVar(spec, obj, node), nil 405 } 406 // Handle types. 407 switch spec := spec.(type) { 408 case *ast.TypeSpec: 409 return formatTypeSpec(spec, node), nil 410 case *ast.ValueSpec: 411 return &HoverInformation{source: spec, comment: spec.Doc}, nil 412 case *ast.ImportSpec: 413 return &HoverInformation{source: spec, comment: spec.Doc}, nil 414 } 415 return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec) 416} 417 418func formatTypeSpec(spec *ast.TypeSpec, decl *ast.GenDecl) *HoverInformation { 419 comment := spec.Doc 420 if comment == nil && decl != nil { 421 comment = decl.Doc 422 } 423 if comment == nil { 424 comment = spec.Comment 425 } 426 return &HoverInformation{ 427 source: spec.Type, 428 comment: comment, 429 typeName: spec.Name.Name, 430 isTypeAlias: spec.Assign.IsValid(), 431 } 432} 433 434func formatVar(node ast.Spec, obj types.Object, decl *ast.GenDecl) *HoverInformation { 435 var fieldList *ast.FieldList 436 switch spec := node.(type) { 437 case *ast.TypeSpec: 438 switch t := spec.Type.(type) { 439 case *ast.StructType: 440 fieldList = t.Fields 441 case *ast.InterfaceType: 442 fieldList = t.Methods 443 } 444 case *ast.ValueSpec: 445 // Try to extract the field list of an anonymous struct 446 if fieldList = extractFieldList(spec.Type); fieldList != nil { 447 break 448 } 449 450 comment := spec.Doc 451 if comment == nil { 452 comment = decl.Doc 453 } 454 if comment == nil { 455 comment = spec.Comment 456 } 457 return &HoverInformation{source: obj, comment: comment} 458 } 459 460 if fieldList != nil { 461 comment := findFieldComment(obj.Pos(), fieldList) 462 return &HoverInformation{source: obj, comment: comment} 463 } 464 return &HoverInformation{source: obj, comment: decl.Doc} 465} 466 467// extractFieldList recursively tries to extract a field list. 468// If it is not found, nil is returned. 469func extractFieldList(specType ast.Expr) *ast.FieldList { 470 switch t := specType.(type) { 471 case *ast.StructType: 472 return t.Fields 473 case *ast.InterfaceType: 474 return t.Methods 475 case *ast.ArrayType: 476 return extractFieldList(t.Elt) 477 case *ast.MapType: 478 // Map value has a greater chance to be a struct 479 if fields := extractFieldList(t.Value); fields != nil { 480 return fields 481 } 482 return extractFieldList(t.Key) 483 case *ast.ChanType: 484 return extractFieldList(t.Value) 485 } 486 return nil 487} 488 489// findFieldComment visits all fields in depth-first order and returns 490// the comment of a field with passed position. If no comment is found, 491// nil is returned. 492func findFieldComment(pos token.Pos, fieldList *ast.FieldList) *ast.CommentGroup { 493 for _, field := range fieldList.List { 494 if field.Pos() == pos { 495 if field.Doc.Text() != "" { 496 return field.Doc 497 } 498 return field.Comment 499 } 500 501 if nestedFieldList := extractFieldList(field.Type); nestedFieldList != nil { 502 if c := findFieldComment(pos, nestedFieldList); c != nil { 503 return c 504 } 505 } 506 } 507 return nil 508} 509 510func FormatHover(h *HoverInformation, options *Options) (string, error) { 511 signature := h.Signature 512 if signature != "" && options.PreferredContentFormat == protocol.Markdown { 513 signature = fmt.Sprintf("```go\n%s\n```", signature) 514 } 515 516 switch options.HoverKind { 517 case SingleLine: 518 return h.SingleLine, nil 519 case NoDocumentation: 520 return signature, nil 521 case Structured: 522 b, err := json.Marshal(h) 523 if err != nil { 524 return "", err 525 } 526 return string(b), nil 527 } 528 link := formatLink(h, options) 529 switch options.HoverKind { 530 case SynopsisDocumentation: 531 doc := formatDoc(h.Synopsis, options) 532 return formatHover(options, signature, link, doc), nil 533 case FullDocumentation: 534 doc := formatDoc(h.FullDocumentation, options) 535 return formatHover(options, signature, link, doc), nil 536 } 537 return "", errors.Errorf("no hover for %v", h.source) 538} 539 540func formatLink(h *HoverInformation, options *Options) string { 541 if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" { 542 return "" 543 } 544 plainLink := BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor) 545 switch options.PreferredContentFormat { 546 case protocol.Markdown: 547 return fmt.Sprintf("[`%s` on %s](%s)", h.symbolName, options.LinkTarget, plainLink) 548 case protocol.PlainText: 549 return "" 550 default: 551 return plainLink 552 } 553} 554 555// BuildLink constructs a link with the given target, path, and anchor. 556func BuildLink(target, path, anchor string) string { 557 link := fmt.Sprintf("https://%s/%s", target, path) 558 if target == "pkg.go.dev" { 559 link += "?utm_source=gopls" 560 } 561 if anchor == "" { 562 return link 563 } 564 return link + "#" + anchor 565} 566 567func formatDoc(doc string, options *Options) string { 568 if options.PreferredContentFormat == protocol.Markdown { 569 return CommentToMarkdown(doc) 570 } 571 return doc 572} 573 574func formatHover(options *Options, x ...string) string { 575 var b strings.Builder 576 for i, el := range x { 577 if el != "" { 578 b.WriteString(el) 579 580 // Don't write out final newline. 581 if i == len(x) { 582 continue 583 } 584 // If any elements of the remainder of the list are non-empty, 585 // write a newline. 586 if anyNonEmpty(x[i+1:]) { 587 if options.PreferredContentFormat == protocol.Markdown { 588 b.WriteString("\n\n") 589 } else { 590 b.WriteRune('\n') 591 } 592 } 593 } 594 } 595 return b.String() 596} 597 598func anyNonEmpty(x []string) bool { 599 for _, el := range x { 600 if el != "" { 601 return true 602 } 603 } 604 return false 605} 606