golang.org/x/tools/gopls@v0.15.3/internal/golang/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 golang 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "go/ast" 13 "go/constant" 14 "go/doc" 15 "go/format" 16 "go/token" 17 "go/types" 18 "io/fs" 19 "path/filepath" 20 "strconv" 21 "strings" 22 "text/tabwriter" 23 "time" 24 "unicode/utf8" 25 26 "golang.org/x/text/unicode/runenames" 27 "golang.org/x/tools/go/ast/astutil" 28 "golang.org/x/tools/go/types/typeutil" 29 "golang.org/x/tools/gopls/internal/cache" 30 "golang.org/x/tools/gopls/internal/cache/metadata" 31 "golang.org/x/tools/gopls/internal/cache/parsego" 32 "golang.org/x/tools/gopls/internal/file" 33 "golang.org/x/tools/gopls/internal/protocol" 34 "golang.org/x/tools/gopls/internal/settings" 35 "golang.org/x/tools/gopls/internal/util/bug" 36 "golang.org/x/tools/gopls/internal/util/safetoken" 37 "golang.org/x/tools/gopls/internal/util/slices" 38 "golang.org/x/tools/gopls/internal/util/typesutil" 39 "golang.org/x/tools/internal/event" 40 "golang.org/x/tools/internal/tokeninternal" 41 ) 42 43 // hoverJSON contains the structured result of a hover query. It is 44 // formatted in one of several formats as determined by the HoverKind 45 // setting, one of which is JSON. 46 // 47 // We believe this is used only by govim. 48 // TODO(adonovan): see if we can wean all clients of this interface. 49 type hoverJSON struct { 50 // Synopsis is a single sentence synopsis of the symbol's documentation. 51 Synopsis string `json:"synopsis"` 52 53 // FullDocumentation is the symbol's full documentation. 54 FullDocumentation string `json:"fullDocumentation"` 55 56 // Signature is the symbol's signature. 57 Signature string `json:"signature"` 58 59 // SingleLine is a single line describing the symbol. 60 // This is recommended only for use in clients that show a single line for hover. 61 SingleLine string `json:"singleLine"` 62 63 // SymbolName is the human-readable name to use for the symbol in links. 64 SymbolName string `json:"symbolName"` 65 66 // LinkPath is the pkg.go.dev link for the given symbol. 67 // For example, the "go/ast" part of "pkg.go.dev/go/ast#Node". 68 LinkPath string `json:"linkPath"` 69 70 // LinkAnchor is the pkg.go.dev link anchor for the given symbol. 71 // For example, the "Node" part of "pkg.go.dev/go/ast#Node". 72 LinkAnchor string `json:"linkAnchor"` 73 74 // New fields go below, and are unexported. The existing 75 // exported fields are underspecified and have already 76 // constrained our movements too much. A detailed JSON 77 // interface might be nice, but it needs a design and a 78 // precise specification. 79 80 // typeDecl is the declaration syntax for a type, 81 // or "" for a non-type. 82 typeDecl string 83 84 // methods is the list of descriptions of methods of a type, 85 // omitting any that are obvious from typeDecl. 86 // It is "" for a non-type. 87 methods string 88 89 // promotedFields is the list of descriptions of accessible 90 // fields of a (struct) type that were promoted through an 91 // embedded field. 92 promotedFields string 93 } 94 95 // Hover implements the "textDocument/hover" RPC for Go files. 96 func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) { 97 ctx, done := event.Start(ctx, "golang.Hover") 98 defer done() 99 100 rng, h, err := hover(ctx, snapshot, fh, position) 101 if err != nil { 102 return nil, err 103 } 104 if h == nil { 105 return nil, nil 106 } 107 hover, err := formatHover(h, snapshot.Options()) 108 if err != nil { 109 return nil, err 110 } 111 return &protocol.Hover{ 112 Contents: protocol.MarkupContent{ 113 Kind: snapshot.Options().PreferredContentFormat, 114 Value: hover, 115 }, 116 Range: rng, 117 }, nil 118 } 119 120 // hover computes hover information at the given position. If we do not support 121 // hovering at the position, it returns _, nil, nil: an error is only returned 122 // if the position is valid but we fail to compute hover information. 123 func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) (protocol.Range, *hoverJSON, error) { 124 pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) 125 if err != nil { 126 return protocol.Range{}, nil, err 127 } 128 pos, err := pgf.PositionPos(pp) 129 if err != nil { 130 return protocol.Range{}, nil, err 131 } 132 133 // Handle hovering over import paths, which do not have an associated 134 // identifier. 135 for _, spec := range pgf.File.Imports { 136 // We are inclusive of the end point here to allow hovering when the cursor 137 // is just after the import path. 138 if spec.Path.Pos() <= pos && pos <= spec.Path.End() { 139 return hoverImport(ctx, snapshot, pkg, pgf, spec) 140 } 141 } 142 143 // Handle hovering over the package name, which does not have an associated 144 // object. 145 // As with import paths, we allow hovering just after the package name. 146 if pgf.File.Name != nil && pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.Pos() { 147 return hoverPackageName(pkg, pgf) 148 } 149 150 // Handle hovering over (non-import-path) literals. 151 if path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos); len(path) > 0 { 152 if lit, _ := path[0].(*ast.BasicLit); lit != nil { 153 return hoverLit(pgf, lit, pos) 154 } 155 } 156 157 // Handle hovering over embed directive argument. 158 pattern, embedRng := parseEmbedDirective(pgf.Mapper, pp) 159 if pattern != "" { 160 return hoverEmbed(fh, embedRng, pattern) 161 } 162 163 // Handle linkname directive by overriding what to look for. 164 var linkedRange *protocol.Range // range referenced by linkname directive, or nil 165 if pkgPath, name, offset := parseLinkname(pgf.Mapper, pp); pkgPath != "" && name != "" { 166 // rng covering 2nd linkname argument: pkgPath.name. 167 rng, err := pgf.PosRange(pgf.Tok.Pos(offset), pgf.Tok.Pos(offset+len(pkgPath)+len(".")+len(name))) 168 if err != nil { 169 return protocol.Range{}, nil, fmt.Errorf("range over linkname arg: %w", err) 170 } 171 linkedRange = &rng 172 173 pkg, pgf, pos, err = findLinkname(ctx, snapshot, PackagePath(pkgPath), name) 174 if err != nil { 175 return protocol.Range{}, nil, fmt.Errorf("find linkname: %w", err) 176 } 177 } 178 179 // The general case: compute hover information for the object referenced by 180 // the identifier at pos. 181 ident, obj, selectedType := referencedObject(pkg, pgf, pos) 182 if obj == nil || ident == nil { 183 return protocol.Range{}, nil, nil // no object to hover 184 } 185 186 // Unless otherwise specified, rng covers the ident being hovered. 187 var rng protocol.Range 188 if linkedRange != nil { 189 rng = *linkedRange 190 } else { 191 rng, err = pgf.NodeRange(ident) 192 if err != nil { 193 return protocol.Range{}, nil, err 194 } 195 } 196 197 // By convention, we qualify hover information relative to the package 198 // from which the request originated. 199 qf := typesutil.FileQualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()) 200 201 // Handle type switch identifiers as a special case, since they don't have an 202 // object. 203 // 204 // There's not much useful information to provide. 205 if selectedType != nil { 206 fakeObj := types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), selectedType) 207 signature := types.ObjectString(fakeObj, qf) 208 return rng, &hoverJSON{ 209 Signature: signature, 210 SingleLine: signature, 211 SymbolName: fakeObj.Name(), 212 }, nil 213 } 214 215 // Handle builtins, which don't have a package or position. 216 if !obj.Pos().IsValid() { 217 h, err := hoverBuiltin(ctx, snapshot, obj) 218 return rng, h, err 219 } 220 221 // For all other objects, consider the full syntax of their declaration in 222 // order to correctly compute their documentation, signature, and link. 223 // 224 // Beware: decl{PGF,Pos} are not necessarily associated with pkg.FileSet(). 225 declPGF, declPos, err := parseFull(ctx, snapshot, pkg.FileSet(), obj.Pos()) 226 if err != nil { 227 return protocol.Range{}, nil, fmt.Errorf("re-parsing declaration of %s: %v", obj.Name(), err) 228 } 229 decl, spec, field := findDeclInfo([]*ast.File{declPGF.File}, declPos) // may be nil^3 230 comment := chooseDocComment(decl, spec, field) 231 docText := comment.Text() 232 233 // By default, types.ObjectString provides a reasonable signature. 234 signature := objectString(obj, qf, declPos, declPGF.Tok, spec) 235 singleLineSignature := signature 236 237 // TODO(rfindley): we could do much better for inferred signatures. 238 if inferred := inferredSignature(pkg.GetTypesInfo(), ident); inferred != nil { 239 if s := inferredSignatureString(obj, qf, inferred); s != "" { 240 signature = s 241 } 242 } 243 244 var typeDecl, methods, fields string 245 246 // For "objects defined by a type spec", the signature produced by 247 // objectString is insufficient: 248 // (1) large structs are formatted poorly, with no newlines 249 // (2) we lose inline comments 250 // Furthermore, we include a summary of their method set. 251 _, isTypeName := obj.(*types.TypeName) 252 _, isTypeParam := obj.Type().(*types.TypeParam) 253 if isTypeName && !isTypeParam { 254 spec, ok := spec.(*ast.TypeSpec) 255 if !ok { 256 // We cannot find a TypeSpec for this type or alias declaration 257 // (that is not a type parameter or a built-in). 258 // This should be impossible even for ill-formed trees; 259 // we suspect that AST repair may be creating inconsistent 260 // positions. Don't report a bug in that case. (#64241) 261 errorf := fmt.Errorf 262 if !declPGF.Fixed() { 263 errorf = bug.Errorf 264 } 265 return protocol.Range{}, nil, errorf("type name %q without type spec", obj.Name()) 266 } 267 268 // Format the type's declaration syntax. 269 { 270 // Don't duplicate comments. 271 spec2 := *spec 272 spec2.Doc = nil 273 spec2.Comment = nil 274 275 var b strings.Builder 276 b.WriteString("type ") 277 fset := tokeninternal.FileSetFor(declPGF.Tok) 278 // TODO(adonovan): use a smarter formatter that omits 279 // inaccessible fields (non-exported ones from other packages). 280 if err := format.Node(&b, fset, &spec2); err != nil { 281 return protocol.Range{}, nil, err 282 } 283 typeDecl = b.String() 284 } 285 286 // Promoted fields 287 // 288 // Show a table of accessible fields of the (struct) 289 // type that may not be visible in the syntax (above) 290 // due to promotion through embedded fields. 291 // 292 // Example: 293 // 294 // // Embedded fields: 295 // foo int // through x.y 296 // z string // through x.y 297 if prom := promotedFields(obj.Type(), pkg.GetTypes()); len(prom) > 0 { 298 var b strings.Builder 299 b.WriteString("// Embedded fields:\n") 300 w := tabwriter.NewWriter(&b, 0, 8, 1, ' ', 0) 301 for _, f := range prom { 302 fmt.Fprintf(w, "%s\t%s\t// through %s\t\n", 303 f.field.Name(), 304 types.TypeString(f.field.Type(), qf), 305 f.path) 306 } 307 w.Flush() 308 b.WriteByte('\n') 309 fields = b.String() 310 } 311 312 // -- methods -- 313 314 // For an interface type, explicit methods will have 315 // already been displayed when the node was formatted 316 // above. Don't list these again. 317 var skip map[string]bool 318 if iface, ok := spec.Type.(*ast.InterfaceType); ok { 319 if iface.Methods.List != nil { 320 for _, m := range iface.Methods.List { 321 if len(m.Names) == 1 { 322 if skip == nil { 323 skip = make(map[string]bool) 324 } 325 skip[m.Names[0].Name] = true 326 } 327 } 328 } 329 } 330 331 // Display all the type's accessible methods, 332 // including those that require a pointer receiver, 333 // and those promoted from embedded struct fields or 334 // embedded interfaces. 335 var b strings.Builder 336 for _, m := range typeutil.IntuitiveMethodSet(obj.Type(), nil) { 337 if !accessibleTo(m.Obj(), pkg.GetTypes()) { 338 continue // inaccessible 339 } 340 if skip[m.Obj().Name()] { 341 continue // redundant with format.Node above 342 } 343 if b.Len() > 0 { 344 b.WriteByte('\n') 345 } 346 347 // Use objectString for its prettier rendering of method receivers. 348 b.WriteString(objectString(m.Obj(), qf, token.NoPos, nil, nil)) 349 } 350 methods = b.String() 351 352 signature = typeDecl + "\n" + methods 353 } 354 355 // Compute link data (on pkg.go.dev or other documentation host). 356 // 357 // If linkPath is empty, the symbol is not linkable. 358 var ( 359 linkName string // => link title, always non-empty 360 linkPath string // => link path 361 anchor string // link anchor 362 linkMeta *metadata.Package // metadata for the linked package 363 ) 364 { 365 linkMeta = findFileInDeps(snapshot, pkg.Metadata(), declPGF.URI) 366 if linkMeta == nil { 367 return protocol.Range{}, nil, bug.Errorf("no package data for %s", declPGF.URI) 368 } 369 370 // For package names, we simply link to their imported package. 371 if pkgName, ok := obj.(*types.PkgName); ok { 372 linkName = pkgName.Name() 373 linkPath = pkgName.Imported().Path() 374 impID := linkMeta.DepsByPkgPath[PackagePath(pkgName.Imported().Path())] 375 linkMeta = snapshot.Metadata(impID) 376 if linkMeta == nil { 377 // Broken imports have fake package paths, so it is not a bug if we 378 // don't have metadata. As of writing, there is no way to distinguish 379 // broken imports from a true bug where expected metadata is missing. 380 return protocol.Range{}, nil, fmt.Errorf("no package data for %s", declPGF.URI) 381 } 382 } else { 383 // For all others, check whether the object is in the package scope, or 384 // an exported field or method of an object in the package scope. 385 // 386 // We try to match pkgsite's heuristics for what is linkable, and what is 387 // not. 388 var recv types.Object 389 switch obj := obj.(type) { 390 case *types.Func: 391 sig := obj.Type().(*types.Signature) 392 if sig.Recv() != nil { 393 tname := typeToObject(sig.Recv().Type()) 394 if tname != nil { // beware typed nil 395 recv = tname 396 } 397 } 398 case *types.Var: 399 if obj.IsField() { 400 if spec, ok := spec.(*ast.TypeSpec); ok { 401 typeName := spec.Name 402 scopeObj, _ := obj.Pkg().Scope().Lookup(typeName.Name).(*types.TypeName) 403 if scopeObj != nil { 404 if st, _ := scopeObj.Type().Underlying().(*types.Struct); st != nil { 405 for i := 0; i < st.NumFields(); i++ { 406 if obj == st.Field(i) { 407 recv = scopeObj 408 } 409 } 410 } 411 } 412 } 413 } 414 } 415 416 // Even if the object is not available in package documentation, it may 417 // be embedded in a documented receiver. Detect this by searching 418 // enclosing selector expressions. 419 // 420 // TODO(rfindley): pkgsite doesn't document fields from embedding, just 421 // methods. 422 if recv == nil || !recv.Exported() { 423 path := pathEnclosingObjNode(pgf.File, pos) 424 if enclosing := searchForEnclosing(pkg.GetTypesInfo(), path); enclosing != nil { 425 recv = enclosing 426 } else { 427 recv = nil // note: just recv = ... could result in a typed nil. 428 } 429 } 430 431 pkg := obj.Pkg() 432 if recv != nil { 433 linkName = fmt.Sprintf("(%s.%s).%s", pkg.Name(), recv.Name(), obj.Name()) 434 if obj.Exported() && recv.Exported() && pkg.Scope().Lookup(recv.Name()) == recv { 435 linkPath = pkg.Path() 436 anchor = fmt.Sprintf("%s.%s", recv.Name(), obj.Name()) 437 } 438 } else { 439 linkName = fmt.Sprintf("%s.%s", pkg.Name(), obj.Name()) 440 if obj.Exported() && pkg.Scope().Lookup(obj.Name()) == obj { 441 linkPath = pkg.Path() 442 anchor = obj.Name() 443 } 444 } 445 } 446 } 447 448 if snapshot.IsGoPrivatePath(linkPath) || linkMeta.ForTest != "" { 449 linkPath = "" 450 } else if linkMeta.Module != nil && linkMeta.Module.Version != "" { 451 mod := linkMeta.Module 452 linkPath = strings.Replace(linkPath, mod.Path, mod.Path+"@"+mod.Version, 1) 453 } 454 455 return rng, &hoverJSON{ 456 Synopsis: doc.Synopsis(docText), 457 FullDocumentation: docText, 458 SingleLine: singleLineSignature, 459 SymbolName: linkName, 460 Signature: signature, 461 LinkPath: linkPath, 462 LinkAnchor: anchor, 463 typeDecl: typeDecl, 464 methods: methods, 465 promotedFields: fields, 466 }, nil 467 } 468 469 // hoverBuiltin computes hover information when hovering over a builtin 470 // identifier. 471 func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) (*hoverJSON, error) { 472 // Special handling for error.Error, which is the only builtin method. 473 // 474 // TODO(rfindley): can this be unified with the handling below? 475 if obj.Name() == "Error" { 476 signature := obj.String() 477 return &hoverJSON{ 478 Signature: signature, 479 SingleLine: signature, 480 // TODO(rfindley): these are better than the current behavior. 481 // SymbolName: "(error).Error", 482 // LinkPath: "builtin", 483 // LinkAnchor: "error.Error", 484 }, nil 485 } 486 487 pgf, node, err := builtinDecl(ctx, snapshot, obj) 488 if err != nil { 489 return nil, err 490 } 491 492 var comment *ast.CommentGroup 493 path, _ := astutil.PathEnclosingInterval(pgf.File, node.Pos(), node.End()) 494 for _, n := range path { 495 switch n := n.(type) { 496 case *ast.GenDecl: 497 // Separate documentation and signature. 498 comment = n.Doc 499 node2 := *n 500 node2.Doc = nil 501 node = &node2 502 case *ast.FuncDecl: 503 // Ditto. 504 comment = n.Doc 505 node2 := *n 506 node2.Doc = nil 507 node = &node2 508 } 509 } 510 511 signature := FormatNodeFile(pgf.Tok, node) 512 // Replace fake types with their common equivalent. 513 // TODO(rfindley): we should instead use obj.Type(), which would have the 514 // *actual* types of the builtin call. 515 signature = replacer.Replace(signature) 516 517 docText := comment.Text() 518 return &hoverJSON{ 519 Synopsis: doc.Synopsis(docText), 520 FullDocumentation: docText, 521 Signature: signature, 522 SingleLine: obj.String(), 523 SymbolName: obj.Name(), 524 LinkPath: "builtin", 525 LinkAnchor: obj.Name(), 526 }, nil 527 } 528 529 // hoverImport computes hover information when hovering over the import path of 530 // imp in the file pgf of pkg. 531 // 532 // If we do not have metadata for the hovered import, it returns _ 533 func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *ParsedGoFile, imp *ast.ImportSpec) (protocol.Range, *hoverJSON, error) { 534 rng, err := pgf.NodeRange(imp.Path) 535 if err != nil { 536 return protocol.Range{}, nil, err 537 } 538 539 importPath := metadata.UnquoteImportPath(imp) 540 if importPath == "" { 541 return protocol.Range{}, nil, fmt.Errorf("invalid import path") 542 } 543 impID := pkg.Metadata().DepsByImpPath[importPath] 544 if impID == "" { 545 return protocol.Range{}, nil, fmt.Errorf("no package data for import %q", importPath) 546 } 547 impMetadata := snapshot.Metadata(impID) 548 if impMetadata == nil { 549 return protocol.Range{}, nil, bug.Errorf("failed to resolve import ID %q", impID) 550 } 551 552 // Find the first file with a package doc comment. 553 var comment *ast.CommentGroup 554 for _, f := range impMetadata.CompiledGoFiles { 555 fh, err := snapshot.ReadFile(ctx, f) 556 if err != nil { 557 if ctx.Err() != nil { 558 return protocol.Range{}, nil, ctx.Err() 559 } 560 continue 561 } 562 pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader) 563 if err != nil { 564 if ctx.Err() != nil { 565 return protocol.Range{}, nil, ctx.Err() 566 } 567 continue 568 } 569 if pgf.File.Doc != nil { 570 comment = pgf.File.Doc 571 break 572 } 573 } 574 575 docText := comment.Text() 576 return rng, &hoverJSON{ 577 Synopsis: doc.Synopsis(docText), 578 FullDocumentation: docText, 579 }, nil 580 } 581 582 // hoverPackageName computes hover information for the package name of the file 583 // pgf in pkg. 584 func hoverPackageName(pkg *cache.Package, pgf *ParsedGoFile) (protocol.Range, *hoverJSON, error) { 585 var comment *ast.CommentGroup 586 for _, pgf := range pkg.CompiledGoFiles() { 587 if pgf.File.Doc != nil { 588 comment = pgf.File.Doc 589 break 590 } 591 } 592 rng, err := pgf.NodeRange(pgf.File.Name) 593 if err != nil { 594 return protocol.Range{}, nil, err 595 } 596 docText := comment.Text() 597 return rng, &hoverJSON{ 598 Synopsis: doc.Synopsis(docText), 599 FullDocumentation: docText, 600 // Note: including a signature is redundant, since the cursor is already on the 601 // package name. 602 }, nil 603 } 604 605 // hoverLit computes hover information when hovering over the basic literal lit 606 // in the file pgf. The provided pos must be the exact position of the cursor, 607 // as it is used to extract the hovered rune in strings. 608 // 609 // For example, hovering over "\u2211" in "foo \u2211 bar" yields: 610 // 611 // '∑', U+2211, N-ARY SUMMATION 612 func hoverLit(pgf *ParsedGoFile, lit *ast.BasicLit, pos token.Pos) (protocol.Range, *hoverJSON, error) { 613 var ( 614 value string // if non-empty, a constant value to format in hover 615 r rune // if non-zero, format a description of this rune in hover 616 start, end token.Pos // hover span 617 ) 618 // Extract a rune from the current position. 619 // 'Ω', "...Ω...", or 0x03A9 => 'Ω', U+03A9, GREEK CAPITAL LETTER OMEGA 620 switch lit.Kind { 621 case token.CHAR: 622 s, err := strconv.Unquote(lit.Value) 623 if err != nil { 624 // If the conversion fails, it's because of an invalid syntax, therefore 625 // there is no rune to be found. 626 return protocol.Range{}, nil, nil 627 } 628 r, _ = utf8.DecodeRuneInString(s) 629 if r == utf8.RuneError { 630 return protocol.Range{}, nil, fmt.Errorf("rune error") 631 } 632 start, end = lit.Pos(), lit.End() 633 634 case token.INT: 635 // Short literals (e.g. 99 decimal, 07 octal) are uninteresting. 636 if len(lit.Value) < 3 { 637 return protocol.Range{}, nil, nil 638 } 639 640 v := constant.MakeFromLiteral(lit.Value, lit.Kind, 0) 641 if v.Kind() != constant.Int { 642 return protocol.Range{}, nil, nil 643 } 644 645 switch lit.Value[:2] { 646 case "0x", "0X": 647 // As a special case, try to recognize hexadecimal literals as runes if 648 // they are within the range of valid unicode values. 649 if v, ok := constant.Int64Val(v); ok && v > 0 && v <= utf8.MaxRune && utf8.ValidRune(rune(v)) { 650 r = rune(v) 651 } 652 fallthrough 653 case "0o", "0O", "0b", "0B": 654 // Format the decimal value of non-decimal literals. 655 value = v.ExactString() 656 start, end = lit.Pos(), lit.End() 657 default: 658 return protocol.Range{}, nil, nil 659 } 660 661 case token.STRING: 662 // It's a string, scan only if it contains a unicode escape sequence under or before the 663 // current cursor position. 664 litOffset, err := safetoken.Offset(pgf.Tok, lit.Pos()) 665 if err != nil { 666 return protocol.Range{}, nil, err 667 } 668 offset, err := safetoken.Offset(pgf.Tok, pos) 669 if err != nil { 670 return protocol.Range{}, nil, err 671 } 672 for i := offset - litOffset; i > 0; i-- { 673 // Start at the cursor position and search backward for the beginning of a rune escape sequence. 674 rr, _ := utf8.DecodeRuneInString(lit.Value[i:]) 675 if rr == utf8.RuneError { 676 return protocol.Range{}, nil, fmt.Errorf("rune error") 677 } 678 if rr == '\\' { 679 // Got the beginning, decode it. 680 var tail string 681 r, _, tail, err = strconv.UnquoteChar(lit.Value[i:], '"') 682 if err != nil { 683 // If the conversion fails, it's because of an invalid syntax, 684 // therefore is no rune to be found. 685 return protocol.Range{}, nil, nil 686 } 687 // Only the rune escape sequence part of the string has to be highlighted, recompute the range. 688 runeLen := len(lit.Value) - (i + len(tail)) 689 start = token.Pos(int(lit.Pos()) + i) 690 end = token.Pos(int(start) + runeLen) 691 break 692 } 693 } 694 } 695 696 if value == "" && r == 0 { // nothing to format 697 return protocol.Range{}, nil, nil 698 } 699 700 rng, err := pgf.PosRange(start, end) 701 if err != nil { 702 return protocol.Range{}, nil, err 703 } 704 705 var b strings.Builder 706 if value != "" { 707 b.WriteString(value) 708 } 709 if r != 0 { 710 runeName := runenames.Name(r) 711 if len(runeName) > 0 && runeName[0] == '<' { 712 // Check if the rune looks like an HTML tag. If so, trim the surrounding <> 713 // characters to work around https://github.com/microsoft/vscode/issues/124042. 714 runeName = strings.TrimRight(runeName[1:], ">") 715 } 716 if b.Len() > 0 { 717 b.WriteString(", ") 718 } 719 if strconv.IsPrint(r) { 720 fmt.Fprintf(&b, "'%c', ", r) 721 } 722 fmt.Fprintf(&b, "U+%04X, %s", r, runeName) 723 } 724 hover := b.String() 725 return rng, &hoverJSON{ 726 Synopsis: hover, 727 FullDocumentation: hover, 728 }, nil 729 } 730 731 // hoverEmbed computes hover information for a filepath.Match pattern. 732 // Assumes that the pattern is relative to the location of fh. 733 func hoverEmbed(fh file.Handle, rng protocol.Range, pattern string) (protocol.Range, *hoverJSON, error) { 734 s := &strings.Builder{} 735 736 dir := filepath.Dir(fh.URI().Path()) 737 var matches []string 738 err := filepath.WalkDir(dir, func(abs string, d fs.DirEntry, e error) error { 739 if e != nil { 740 return e 741 } 742 rel, err := filepath.Rel(dir, abs) 743 if err != nil { 744 return err 745 } 746 ok, err := filepath.Match(pattern, rel) 747 if err != nil { 748 return err 749 } 750 if ok && !d.IsDir() { 751 matches = append(matches, rel) 752 } 753 return nil 754 }) 755 if err != nil { 756 return protocol.Range{}, nil, err 757 } 758 759 for _, m := range matches { 760 // TODO: Renders each file as separate markdown paragraphs. 761 // If forcing (a single) newline is possible it might be more clear. 762 fmt.Fprintf(s, "%s\n\n", m) 763 } 764 765 json := &hoverJSON{ 766 Signature: fmt.Sprintf("Embedding %q", pattern), 767 Synopsis: s.String(), 768 FullDocumentation: s.String(), 769 } 770 return rng, json, nil 771 } 772 773 // inferredSignatureString is a wrapper around the types.ObjectString function 774 // that adds more information to inferred signatures. It will return an empty string 775 // if the passed types.Object is not a signature. 776 func inferredSignatureString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string { 777 // If the signature type was inferred, prefer the inferred signature with a 778 // comment showing the generic signature. 779 if sig, _ := obj.Type().(*types.Signature); sig != nil && sig.TypeParams().Len() > 0 && inferred != nil { 780 obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred) 781 str := types.ObjectString(obj2, qf) 782 // Try to avoid overly long lines. 783 if len(str) > 60 { 784 str += "\n" 785 } else { 786 str += " " 787 } 788 str += "// " + types.TypeString(sig, qf) 789 return str 790 } 791 return "" 792 } 793 794 // objectString is a wrapper around the types.ObjectString function. 795 // It handles adding more information to the object string. 796 // If spec is non-nil, it may be used to format additional declaration 797 // syntax, and file must be the token.File describing its positions. 798 // 799 // Precondition: obj is not a built-in function or method. 800 func objectString(obj types.Object, qf types.Qualifier, declPos token.Pos, file *token.File, spec ast.Spec) string { 801 str := types.ObjectString(obj, qf) 802 803 switch obj := obj.(type) { 804 case *types.Func: 805 // We fork ObjectString to improve its rendering of methods: 806 // specifically, we show the receiver name, 807 // and replace the period in (T).f by a space (#62190). 808 809 sig := obj.Type().(*types.Signature) 810 811 var buf bytes.Buffer 812 buf.WriteString("func ") 813 if recv := sig.Recv(); recv != nil { 814 buf.WriteByte('(') 815 if _, ok := recv.Type().(*types.Interface); ok { 816 // gcimporter creates abstract methods of 817 // named interfaces using the interface type 818 // (not the named type) as the receiver. 819 // Don't print it in full. 820 buf.WriteString("interface") 821 } else { 822 // Show receiver name (go/types does not). 823 name := recv.Name() 824 if name != "" && name != "_" { 825 buf.WriteString(name) 826 buf.WriteString(" ") 827 } 828 types.WriteType(&buf, recv.Type(), qf) 829 } 830 buf.WriteByte(')') 831 buf.WriteByte(' ') // space (go/types uses a period) 832 } else if s := qf(obj.Pkg()); s != "" { 833 buf.WriteString(s) 834 buf.WriteString(".") 835 } 836 buf.WriteString(obj.Name()) 837 types.WriteSignature(&buf, sig, qf) 838 str = buf.String() 839 840 case *types.Const: 841 // Show value of a constant. 842 var ( 843 declaration = obj.Val().String() // default formatted declaration 844 comment = "" // if non-empty, a clarifying comment 845 ) 846 847 // Try to use the original declaration. 848 switch obj.Val().Kind() { 849 case constant.String: 850 // Usually the original declaration of a string doesn't carry much information. 851 // Also strings can be very long. So, just use the constant's value. 852 853 default: 854 if spec, _ := spec.(*ast.ValueSpec); spec != nil { 855 for i, name := range spec.Names { 856 if declPos == name.Pos() { 857 if i < len(spec.Values) { 858 originalDeclaration := FormatNodeFile(file, spec.Values[i]) 859 if originalDeclaration != declaration { 860 comment = declaration 861 declaration = originalDeclaration 862 } 863 } 864 break 865 } 866 } 867 } 868 } 869 870 // Special formatting cases. 871 switch typ := obj.Type().(type) { 872 case *types.Named: 873 // Try to add a formatted duration as an inline comment. 874 pkg := typ.Obj().Pkg() 875 if pkg.Path() == "time" && typ.Obj().Name() == "Duration" { 876 if d, ok := constant.Int64Val(obj.Val()); ok { 877 comment = time.Duration(d).String() 878 } 879 } 880 } 881 if comment == declaration { 882 comment = "" 883 } 884 885 str += " = " + declaration 886 if comment != "" { 887 str += " // " + comment 888 } 889 } 890 return str 891 } 892 893 // HoverDocForObject returns the best doc comment for obj (for which 894 // fset provides file/line information). 895 // 896 // TODO(rfindley): there appears to be zero(!) tests for this functionality. 897 func HoverDocForObject(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, obj types.Object) (*ast.CommentGroup, error) { 898 if _, isTypeName := obj.(*types.TypeName); isTypeName { 899 if _, isTypeParam := obj.Type().(*types.TypeParam); isTypeParam { 900 return nil, nil 901 } 902 } 903 904 pgf, pos, err := parseFull(ctx, snapshot, fset, obj.Pos()) 905 if err != nil { 906 return nil, fmt.Errorf("re-parsing: %v", err) 907 } 908 909 decl, spec, field := findDeclInfo([]*ast.File{pgf.File}, pos) 910 return chooseDocComment(decl, spec, field), nil 911 } 912 913 func chooseDocComment(decl ast.Decl, spec ast.Spec, field *ast.Field) *ast.CommentGroup { 914 if field != nil { 915 if field.Doc != nil { 916 return field.Doc 917 } 918 if field.Comment != nil { 919 return field.Comment 920 } 921 return nil 922 } 923 switch decl := decl.(type) { 924 case *ast.FuncDecl: 925 return decl.Doc 926 case *ast.GenDecl: 927 switch spec := spec.(type) { 928 case *ast.ValueSpec: 929 if spec.Doc != nil { 930 return spec.Doc 931 } 932 if decl.Doc != nil { 933 return decl.Doc 934 } 935 return spec.Comment 936 case *ast.TypeSpec: 937 if spec.Doc != nil { 938 return spec.Doc 939 } 940 if decl.Doc != nil { 941 return decl.Doc 942 } 943 return spec.Comment 944 } 945 } 946 return nil 947 } 948 949 // parseFull fully parses the file corresponding to position pos (for 950 // which fset provides file/line information). 951 // 952 // It returns the resulting parsego.File as well as new pos contained 953 // in the parsed file. 954 // 955 // BEWARE: the provided FileSet is used only to interpret the provided 956 // pos; the resulting File and Pos may belong to the same or a 957 // different FileSet, such as one synthesized by the parser cache, if 958 // parse-caching is enabled. 959 func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, pos token.Pos) (*parsego.File, token.Pos, error) { 960 f := fset.File(pos) 961 if f == nil { 962 return nil, 0, bug.Errorf("internal error: no file for position %d", pos) 963 } 964 965 uri := protocol.URIFromPath(f.Name()) 966 fh, err := snapshot.ReadFile(ctx, uri) 967 if err != nil { 968 return nil, 0, err 969 } 970 971 pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) 972 if err != nil { 973 return nil, 0, err 974 } 975 976 offset, err := safetoken.Offset(f, pos) 977 if err != nil { 978 return nil, 0, bug.Errorf("offset out of bounds in %q", uri) 979 } 980 981 fullPos, err := safetoken.Pos(pgf.Tok, offset) 982 if err != nil { 983 return nil, 0, err 984 } 985 986 return pgf, fullPos, nil 987 } 988 989 func formatHover(h *hoverJSON, options *settings.Options) (string, error) { 990 maybeMarkdown := func(s string) string { 991 if s != "" && options.PreferredContentFormat == protocol.Markdown { 992 s = fmt.Sprintf("```go\n%s\n```", strings.Trim(s, "\n")) 993 } 994 return s 995 } 996 997 switch options.HoverKind { 998 case settings.SingleLine: 999 return h.SingleLine, nil 1000 1001 case settings.NoDocumentation: 1002 return maybeMarkdown(h.Signature), nil 1003 1004 case settings.Structured: 1005 b, err := json.Marshal(h) 1006 if err != nil { 1007 return "", err 1008 } 1009 return string(b), nil 1010 1011 case settings.SynopsisDocumentation, 1012 settings.FullDocumentation: 1013 // For types, we display TypeDecl and Methods, 1014 // but not Signature, which is redundant (= TypeDecl + "\n" + Methods). 1015 // For all other symbols, we display Signature; 1016 // TypeDecl and Methods are empty. 1017 // (This awkwardness is to preserve JSON compatibility.) 1018 parts := []string{ 1019 maybeMarkdown(h.Signature), 1020 maybeMarkdown(h.typeDecl), 1021 formatDoc(h, options), 1022 maybeMarkdown(h.promotedFields), 1023 maybeMarkdown(h.methods), 1024 formatLink(h, options), 1025 } 1026 if h.typeDecl != "" { 1027 parts[0] = "" // type: suppress redundant Signature 1028 } 1029 parts = slices.Remove(parts, "") 1030 1031 var b strings.Builder 1032 for i, part := range parts { 1033 if i > 0 { 1034 if options.PreferredContentFormat == protocol.Markdown { 1035 b.WriteString("\n\n") 1036 } else { 1037 b.WriteByte('\n') 1038 } 1039 } 1040 b.WriteString(part) 1041 } 1042 return b.String(), nil 1043 1044 default: 1045 return "", fmt.Errorf("invalid HoverKind: %v", options.HoverKind) 1046 } 1047 } 1048 1049 func formatLink(h *hoverJSON, options *settings.Options) string { 1050 if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" { 1051 return "" 1052 } 1053 plainLink := cache.BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor) 1054 switch options.PreferredContentFormat { 1055 case protocol.Markdown: 1056 return fmt.Sprintf("[`%s` on %s](%s)", h.SymbolName, options.LinkTarget, plainLink) 1057 case protocol.PlainText: 1058 return "" 1059 default: 1060 return plainLink 1061 } 1062 } 1063 1064 func formatDoc(h *hoverJSON, options *settings.Options) string { 1065 var doc string 1066 switch options.HoverKind { 1067 case settings.SynopsisDocumentation: 1068 doc = h.Synopsis 1069 case settings.FullDocumentation: 1070 doc = h.FullDocumentation 1071 } 1072 if options.PreferredContentFormat == protocol.Markdown { 1073 return CommentToMarkdown(doc, options) 1074 } 1075 return doc 1076 } 1077 1078 // findDeclInfo returns the syntax nodes involved in the declaration of the 1079 // types.Object with position pos, searching the given list of file syntax 1080 // trees. 1081 // 1082 // Pos may be the position of the name-defining identifier in a FuncDecl, 1083 // ValueSpec, TypeSpec, Field, or as a special case the position of 1084 // Ellipsis.Elt in an ellipsis field. 1085 // 1086 // If found, the resulting decl, spec, and field will be the inner-most 1087 // instance of each node type surrounding pos. 1088 // 1089 // If field is non-nil, pos is the position of a field Var. If field is nil and 1090 // spec is non-nil, pos is the position of a Var, Const, or TypeName object. If 1091 // both field and spec are nil and decl is non-nil, pos is the position of a 1092 // Func object. 1093 // 1094 // It returns a nil decl if no object-defining node is found at pos. 1095 // 1096 // TODO(rfindley): this function has tricky semantics, and may be worth unit 1097 // testing and/or refactoring. 1098 func findDeclInfo(files []*ast.File, pos token.Pos) (decl ast.Decl, spec ast.Spec, field *ast.Field) { 1099 // panic(found{}) breaks off the traversal and 1100 // causes the function to return normally. 1101 type found struct{} 1102 defer func() { 1103 switch x := recover().(type) { 1104 case nil: 1105 case found: 1106 default: 1107 panic(x) 1108 } 1109 }() 1110 1111 // Visit the files in search of the node at pos. 1112 stack := make([]ast.Node, 0, 20) 1113 // Allocate the closure once, outside the loop. 1114 f := func(n ast.Node) bool { 1115 if n != nil { 1116 stack = append(stack, n) // push 1117 } else { 1118 stack = stack[:len(stack)-1] // pop 1119 return false 1120 } 1121 1122 // Skip subtrees (incl. files) that don't contain the search point. 1123 if !(n.Pos() <= pos && pos < n.End()) { 1124 return false 1125 } 1126 1127 switch n := n.(type) { 1128 case *ast.Field: 1129 findEnclosingDeclAndSpec := func() { 1130 for i := len(stack) - 1; i >= 0; i-- { 1131 switch n := stack[i].(type) { 1132 case ast.Spec: 1133 spec = n 1134 case ast.Decl: 1135 decl = n 1136 return 1137 } 1138 } 1139 } 1140 1141 // Check each field name since you can have 1142 // multiple names for the same type expression. 1143 for _, id := range n.Names { 1144 if id.Pos() == pos { 1145 field = n 1146 findEnclosingDeclAndSpec() 1147 panic(found{}) 1148 } 1149 } 1150 1151 // Check *ast.Field itself. This handles embedded 1152 // fields which have no associated *ast.Ident name. 1153 if n.Pos() == pos { 1154 field = n 1155 findEnclosingDeclAndSpec() 1156 panic(found{}) 1157 } 1158 1159 // Also check "X" in "...X". This makes it easy to format variadic 1160 // signature params properly. 1161 // 1162 // TODO(rfindley): I don't understand this comment. How does finding the 1163 // field in this case make it easier to format variadic signature params? 1164 if ell, ok := n.Type.(*ast.Ellipsis); ok && ell.Elt != nil && ell.Elt.Pos() == pos { 1165 field = n 1166 findEnclosingDeclAndSpec() 1167 panic(found{}) 1168 } 1169 1170 case *ast.FuncDecl: 1171 if n.Name.Pos() == pos { 1172 decl = n 1173 panic(found{}) 1174 } 1175 1176 case *ast.GenDecl: 1177 for _, s := range n.Specs { 1178 switch s := s.(type) { 1179 case *ast.TypeSpec: 1180 if s.Name.Pos() == pos { 1181 decl = n 1182 spec = s 1183 panic(found{}) 1184 } 1185 case *ast.ValueSpec: 1186 for _, id := range s.Names { 1187 if id.Pos() == pos { 1188 decl = n 1189 spec = s 1190 panic(found{}) 1191 } 1192 } 1193 } 1194 } 1195 } 1196 return true 1197 } 1198 for _, file := range files { 1199 ast.Inspect(file, f) 1200 } 1201 1202 return nil, nil, nil 1203 } 1204 1205 type promotedField struct { 1206 path string // path (e.g. "x.y" through embedded fields) 1207 field *types.Var 1208 } 1209 1210 // promotedFields returns the list of accessible promoted fields of a struct type t. 1211 // (Logic plundered from x/tools/cmd/guru/describe.go.) 1212 func promotedFields(t types.Type, from *types.Package) []promotedField { 1213 wantField := func(f *types.Var) bool { 1214 if !accessibleTo(f, from) { 1215 return false 1216 } 1217 // Check that the field is not shadowed. 1218 obj, _, _ := types.LookupFieldOrMethod(t, true, f.Pkg(), f.Name()) 1219 return obj == f 1220 } 1221 1222 var fields []promotedField 1223 var visit func(t types.Type, stack []*types.Named) 1224 visit = func(t types.Type, stack []*types.Named) { 1225 tStruct, ok := Deref(t).Underlying().(*types.Struct) 1226 if !ok { 1227 return 1228 } 1229 fieldloop: 1230 for i := 0; i < tStruct.NumFields(); i++ { 1231 f := tStruct.Field(i) 1232 1233 // Handle recursion through anonymous fields. 1234 if f.Anonymous() { 1235 tf := f.Type() 1236 if ptr, ok := tf.(*types.Pointer); ok { 1237 tf = ptr.Elem() 1238 } 1239 if named, ok := tf.(*types.Named); ok { // (be defensive) 1240 // If we've already visited this named type 1241 // on this path, break the cycle. 1242 for _, x := range stack { 1243 if x.Origin() == named.Origin() { 1244 continue fieldloop 1245 } 1246 } 1247 visit(f.Type(), append(stack, named)) 1248 } 1249 } 1250 1251 // Save accessible promoted fields. 1252 if len(stack) > 0 && wantField(f) { 1253 var path strings.Builder 1254 for i, t := range stack { 1255 if i > 0 { 1256 path.WriteByte('.') 1257 } 1258 path.WriteString(t.Obj().Name()) 1259 } 1260 fields = append(fields, promotedField{ 1261 path: path.String(), 1262 field: f, 1263 }) 1264 } 1265 } 1266 } 1267 visit(t, nil) 1268 1269 return fields 1270 } 1271 1272 func accessibleTo(obj types.Object, pkg *types.Package) bool { 1273 return obj.Exported() || obj.Pkg() == pkg 1274 }