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/doc" 13 "go/format" 14 "go/types" 15 "strings" 16 17 "golang.org/x/tools/internal/event" 18 "golang.org/x/tools/internal/lsp/protocol" 19 errors "golang.org/x/xerrors" 20) 21 22type HoverInformation struct { 23 // Signature is the symbol's signature. 24 Signature string `json:"signature"` 25 26 // SingleLine is a single line describing the symbol. 27 // This is recommended only for use in clients that show a single line for hover. 28 SingleLine string `json:"singleLine"` 29 30 // Synopsis is a single sentence synopsis of the symbol's documentation. 31 Synopsis string `json:"synopsis"` 32 33 // FullDocumentation is the symbol's full documentation. 34 FullDocumentation string `json:"fullDocumentation"` 35 36 // LinkPath is the pkg.go.dev link for the given symbol. 37 // For example, the "go/ast" part of "pkg.go.dev/go/ast#Node". 38 LinkPath string `json:"linkPath"` 39 40 // LinkAnchor is the pkg.go.dev link anchor for the given symbol. 41 // For example, the "Node" part of "pkg.go.dev/go/ast#Node". 42 LinkAnchor string `json:"linkAnchor"` 43 44 // importPath is the import path for the package containing the given 45 // symbol. 46 importPath string 47 48 // symbolName is the types.Object.Name for the given symbol. 49 symbolName string 50 51 source interface{} 52 comment *ast.CommentGroup 53 54 // isTypeName reports whether the identifier is a type name. In such cases, 55 // the hover has the prefix "type ". 56 isType bool 57} 58 59func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) { 60 ident, err := Identifier(ctx, snapshot, fh, position) 61 if err != nil { 62 return nil, nil 63 } 64 h, err := HoverIdentifier(ctx, ident) 65 if err != nil { 66 return nil, err 67 } 68 rng, err := ident.Range() 69 if err != nil { 70 return nil, err 71 } 72 // See golang/go#36998: don't link to modules matching GOPRIVATE. 73 if snapshot.View().IsGoPrivatePath(h.importPath) { 74 h.LinkPath = "" 75 } 76 hover, err := FormatHover(h, snapshot.View().Options()) 77 if err != nil { 78 return nil, err 79 } 80 return &protocol.Hover{ 81 Contents: protocol.MarkupContent{ 82 Kind: snapshot.View().Options().PreferredContentFormat, 83 Value: hover, 84 }, 85 Range: rng, 86 }, nil 87} 88 89func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverInformation, error) { 90 ctx, done := event.Start(ctx, "source.Hover") 91 defer done() 92 93 fset := i.Snapshot.FileSet() 94 h, err := HoverInfo(ctx, i.pkg, i.Declaration.obj, i.Declaration.node) 95 if err != nil { 96 return nil, err 97 } 98 // Determine the symbol's signature. 99 switch x := h.source.(type) { 100 case ast.Node: 101 var b strings.Builder 102 if err := format.Node(&b, fset, x); err != nil { 103 return nil, err 104 } 105 h.Signature = b.String() 106 if h.isType { 107 h.Signature = "type " + h.Signature 108 } 109 case types.Object: 110 // If the variable is implicitly declared in a type switch, we need to 111 // manually generate its object string. 112 if typ := i.Declaration.typeSwitchImplicit; typ != nil { 113 if v, ok := x.(*types.Var); ok { 114 h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf)) 115 break 116 } 117 } 118 h.Signature = objectString(x, i.qf) 119 } 120 if obj := i.Declaration.obj; obj != nil { 121 h.SingleLine = objectString(obj, i.qf) 122 } 123 obj := i.Declaration.obj 124 if obj == nil { 125 return h, nil 126 } 127 switch obj := obj.(type) { 128 case *types.PkgName: 129 h.importPath = obj.Imported().Path() 130 h.LinkPath = h.importPath 131 h.symbolName = obj.Name() 132 if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok { 133 h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1) 134 } 135 return h, nil 136 case *types.Builtin: 137 h.importPath = "builtin" 138 h.LinkPath = h.importPath 139 h.LinkAnchor = obj.Name() 140 h.symbolName = h.LinkAnchor 141 return h, nil 142 } 143 // Check if the identifier is test-only (and is therefore not part of a 144 // package's API). This is true if the request originated in a test package, 145 // and if the declaration is also found in the same test package. 146 if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" { 147 if _, err := i.pkg.File(i.Declaration.MappedRange[0].URI()); err == nil { 148 return h, nil 149 } 150 } 151 // Don't return links for other unexported types. 152 if !obj.Exported() { 153 return h, nil 154 } 155 var rTypeName string 156 switch obj := obj.(type) { 157 case *types.Var: 158 // If the object is a field, and we have an associated selector 159 // composite literal, or struct, we can determine the link. 160 if obj.IsField() { 161 if named, ok := i.enclosing.(*types.Named); ok { 162 rTypeName = named.Obj().Name() 163 } 164 } 165 case *types.Func: 166 typ, ok := obj.Type().(*types.Signature) 167 if !ok { 168 return h, nil 169 } 170 if r := typ.Recv(); r != nil { 171 switch rtyp := Deref(r.Type()).(type) { 172 case *types.Struct: 173 rTypeName = r.Name() 174 case *types.Named: 175 // If we have an unexported type, see if the enclosing type is 176 // exported (we may have an interface or struct we can link 177 // to). If not, don't show any link. 178 if !rtyp.Obj().Exported() { 179 if named, ok := i.enclosing.(*types.Named); ok && named.Obj().Exported() { 180 rTypeName = named.Obj().Name() 181 } else { 182 return h, nil 183 } 184 } else { 185 rTypeName = rtyp.Obj().Name() 186 } 187 } 188 } 189 } 190 h.importPath = obj.Pkg().Path() 191 h.LinkPath = h.importPath 192 if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok { 193 h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1) 194 } 195 if rTypeName != "" { 196 h.LinkAnchor = fmt.Sprintf("%s.%s", rTypeName, obj.Name()) 197 h.symbolName = fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), rTypeName, obj.Name()) 198 return h, nil 199 } 200 // For most cases, the link is "package/path#symbol". 201 h.LinkAnchor = obj.Name() 202 h.symbolName = fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name()) 203 return h, nil 204} 205 206func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) { 207 if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" { 208 return "", "", false 209 } 210 impPkg, err := i.pkg.GetImport(path) 211 if err != nil { 212 return "", "", false 213 } 214 if impPkg.Version() == nil { 215 return "", "", false 216 } 217 version, modpath := impPkg.Version().Version, impPkg.Version().Path 218 if modpath == "" || version == "" { 219 return "", "", false 220 } 221 return modpath, version, true 222} 223 224// objectString is a wrapper around the types.ObjectString function. 225// It handles adding more information to the object string. 226func objectString(obj types.Object, qf types.Qualifier) string { 227 str := types.ObjectString(obj, qf) 228 switch obj := obj.(type) { 229 case *types.Const: 230 str = fmt.Sprintf("%s = %s", str, obj.Val()) 231 } 232 return str 233} 234 235// HoverInfo returns a HoverInformation struct for an ast node and its type 236// object. 237func HoverInfo(ctx context.Context, pkg Package, obj types.Object, node ast.Node) (*HoverInformation, error) { 238 var info *HoverInformation 239 240 switch node := node.(type) { 241 case *ast.Ident: 242 // The package declaration. 243 for _, f := range pkg.GetSyntax() { 244 if f.Name == node { 245 info = &HoverInformation{comment: f.Doc} 246 } 247 } 248 case *ast.ImportSpec: 249 // Try to find the package documentation for an imported package. 250 if pkgName, ok := obj.(*types.PkgName); ok { 251 imp, err := pkg.GetImport(pkgName.Imported().Path()) 252 if err != nil { 253 return nil, err 254 } 255 // Assume that only one file will contain package documentation, 256 // so pick the first file that has a doc comment. 257 for _, file := range imp.GetSyntax() { 258 if file.Doc != nil { 259 info = &HoverInformation{source: obj, comment: file.Doc} 260 break 261 } 262 } 263 } 264 info = &HoverInformation{source: node} 265 case *ast.GenDecl: 266 switch obj := obj.(type) { 267 case *types.TypeName, *types.Var, *types.Const, *types.Func: 268 var err error 269 info, err = formatGenDecl(node, obj, obj.Type()) 270 if err != nil { 271 return nil, err 272 } 273 _, info.isType = obj.(*types.TypeName) 274 } 275 case *ast.TypeSpec: 276 if obj.Parent() == types.Universe { 277 if obj.Name() == "error" { 278 info = &HoverInformation{source: node} 279 } else { 280 info = &HoverInformation{source: node.Name} // comments not needed for builtins 281 } 282 } 283 case *ast.FuncDecl: 284 switch obj.(type) { 285 case *types.Func: 286 info = &HoverInformation{source: obj, comment: node.Doc} 287 case *types.Builtin: 288 info = &HoverInformation{source: node.Type, comment: node.Doc} 289 } 290 } 291 292 if info == nil { 293 info = &HoverInformation{source: obj} 294 } 295 296 if info.comment != nil { 297 info.FullDocumentation = info.comment.Text() 298 info.Synopsis = doc.Synopsis(info.FullDocumentation) 299 } 300 301 return info, nil 302} 303 304func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*HoverInformation, error) { 305 if _, ok := typ.(*types.Named); ok { 306 switch typ.Underlying().(type) { 307 case *types.Interface, *types.Struct: 308 return formatGenDecl(node, obj, typ.Underlying()) 309 } 310 } 311 var spec ast.Spec 312 for _, s := range node.Specs { 313 if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() { 314 spec = s 315 break 316 } 317 } 318 if spec == nil { 319 return nil, errors.Errorf("no spec for node %v at position %v", node, obj.Pos()) 320 } 321 322 // If we have a field or method. 323 switch obj.(type) { 324 case *types.Var, *types.Const, *types.Func: 325 return formatVar(spec, obj, node), nil 326 } 327 // Handle types. 328 switch spec := spec.(type) { 329 case *ast.TypeSpec: 330 if len(node.Specs) > 1 { 331 // If multiple types are declared in the same block. 332 return &HoverInformation{source: spec.Type, comment: spec.Doc}, nil 333 } else { 334 return &HoverInformation{source: spec, comment: node.Doc}, nil 335 } 336 case *ast.ValueSpec: 337 return &HoverInformation{source: spec, comment: spec.Doc}, nil 338 case *ast.ImportSpec: 339 return &HoverInformation{source: spec, comment: spec.Doc}, nil 340 } 341 return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec) 342} 343 344func formatVar(node ast.Spec, obj types.Object, decl *ast.GenDecl) *HoverInformation { 345 var fieldList *ast.FieldList 346 switch spec := node.(type) { 347 case *ast.TypeSpec: 348 switch t := spec.Type.(type) { 349 case *ast.StructType: 350 fieldList = t.Fields 351 case *ast.InterfaceType: 352 fieldList = t.Methods 353 } 354 case *ast.ValueSpec: 355 comment := spec.Doc 356 if comment == nil { 357 comment = decl.Doc 358 } 359 if comment == nil { 360 comment = spec.Comment 361 } 362 return &HoverInformation{source: obj, comment: comment} 363 } 364 // If we have a struct or interface declaration, 365 // we need to match the object to the corresponding field or method. 366 if fieldList != nil { 367 for i := 0; i < len(fieldList.List); i++ { 368 field := fieldList.List[i] 369 if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() { 370 if field.Doc.Text() != "" { 371 return &HoverInformation{source: obj, comment: field.Doc} 372 } 373 return &HoverInformation{source: obj, comment: field.Comment} 374 } 375 } 376 } 377 return &HoverInformation{source: obj, comment: decl.Doc} 378} 379 380func FormatHover(h *HoverInformation, options *Options) (string, error) { 381 signature := h.Signature 382 if signature != "" && options.PreferredContentFormat == protocol.Markdown { 383 signature = fmt.Sprintf("```go\n%s\n```", signature) 384 } 385 386 switch options.HoverKind { 387 case SingleLine: 388 return h.SingleLine, nil 389 case NoDocumentation: 390 return signature, nil 391 case Structured: 392 b, err := json.Marshal(h) 393 if err != nil { 394 return "", err 395 } 396 return string(b), nil 397 } 398 link := formatLink(h, options) 399 switch options.HoverKind { 400 case SynopsisDocumentation: 401 doc := formatDoc(h.Synopsis, options) 402 return formatHover(options, signature, link, doc), nil 403 case FullDocumentation: 404 doc := formatDoc(h.FullDocumentation, options) 405 return formatHover(options, signature, link, doc), nil 406 } 407 return "", errors.Errorf("no hover for %v", h.source) 408} 409 410func formatLink(h *HoverInformation, options *Options) string { 411 if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" { 412 return "" 413 } 414 plainLink := BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor) 415 switch options.PreferredContentFormat { 416 case protocol.Markdown: 417 return fmt.Sprintf("[`%s` on %s](%s)", h.symbolName, options.LinkTarget, plainLink) 418 case protocol.PlainText: 419 return "" 420 default: 421 return plainLink 422 } 423} 424 425// BuildLink constructs a link with the given target, path, and anchor. 426func BuildLink(target, path, anchor string) string { 427 link := fmt.Sprintf("https://%s/%s", target, path) 428 if target == "pkg.go.dev" { 429 link += "?utm_source=gopls" 430 } 431 if anchor == "" { 432 return link 433 } 434 return link + "#" + anchor 435} 436 437func formatDoc(doc string, options *Options) string { 438 if options.PreferredContentFormat == protocol.Markdown { 439 return CommentToMarkdown(doc) 440 } 441 return doc 442} 443 444func formatHover(options *Options, x ...string) string { 445 var b strings.Builder 446 for i, el := range x { 447 if el != "" { 448 b.WriteString(el) 449 450 // Don't write out final newline. 451 if i == len(x) { 452 continue 453 } 454 // If any elements of the remainder of the list are non-empty, 455 // write a newline. 456 if anyNonEmpty(x[i+1:]) { 457 if options.PreferredContentFormat == protocol.Markdown { 458 b.WriteString("\n\n") 459 } else { 460 b.WriteRune('\n') 461 } 462 } 463 } 464 } 465 return b.String() 466} 467 468func anyNonEmpty(x []string) bool { 469 for _, el := range x { 470 if el != "" { 471 return true 472 } 473 } 474 return false 475} 476