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.
19 "golang.org/x/tools/internal/event"
20 "golang.org/x/tools/internal/lsp/protocol"
21 errors "golang.org/x/xerrors"
24 type HoverInformation struct {
25 // Signature is the symbol's signature.
26 Signature string `json:"signature"`
28 // SingleLine is a single line describing the symbol.
29 // This is recommended only for use in clients that show a single line for hover.
30 SingleLine string `json:"singleLine"`
32 // Synopsis is a single sentence synopsis of the symbol's documentation.
33 Synopsis string `json:"synopsis"`
35 // FullDocumentation is the symbol's full documentation.
36 FullDocumentation string `json:"fullDocumentation"`
38 // LinkPath is the pkg.go.dev link for the given symbol.
39 // For example, the "go/ast" part of "pkg.go.dev/go/ast#Node".
40 LinkPath string `json:"linkPath"`
42 // LinkAnchor is the pkg.go.dev link anchor for the given symbol.
43 // For example, the "Node" part of "pkg.go.dev/go/ast#Node".
44 LinkAnchor string `json:"linkAnchor"`
46 // importPath is the import path for the package containing the given
50 // symbolName is the types.Object.Name for the given symbol.
54 comment *ast.CommentGroup
56 // isTypeName reports whether the identifier is a type name. In such cases,
57 // the hover has the prefix "type ".
61 func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
62 ident, err := Identifier(ctx, snapshot, fh, position)
66 h, err := HoverIdentifier(ctx, ident)
70 rng, err := ident.Range()
74 // See golang/go#36998: don't link to modules matching GOPRIVATE.
75 if snapshot.View().IsGoPrivatePath(h.importPath) {
78 hover, err := FormatHover(h, snapshot.View().Options())
82 return &protocol.Hover{
83 Contents: protocol.MarkupContent{
84 Kind: snapshot.View().Options().PreferredContentFormat,
91 func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverInformation, error) {
92 ctx, done := event.Start(ctx, "source.Hover")
95 fset := i.Snapshot.FileSet()
96 h, err := HoverInfo(ctx, i.pkg, i.Declaration.obj, i.Declaration.node)
100 // Determine the symbol's signature.
101 switch x := h.source.(type) {
103 var b strings.Builder
104 if err := format.Node(&b, fset, x); err != nil {
107 h.Signature = b.String()
109 h.Signature = "type " + h.Signature
112 // If the variable is implicitly declared in a type switch, we need to
113 // manually generate its object string.
114 if typ := i.Declaration.typeSwitchImplicit; typ != nil {
115 if v, ok := x.(*types.Var); ok {
116 h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf))
120 h.Signature = objectString(x, i.qf)
122 if obj := i.Declaration.obj; obj != nil {
123 h.SingleLine = objectString(obj, i.qf)
125 obj := i.Declaration.obj
129 switch obj := obj.(type) {
131 h.importPath = obj.Imported().Path()
132 h.LinkPath = h.importPath
133 h.symbolName = obj.Name()
134 if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok {
135 h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1)
139 h.importPath = "builtin"
140 h.LinkPath = h.importPath
141 h.LinkAnchor = obj.Name()
142 h.symbolName = h.LinkAnchor
145 // Check if the identifier is test-only (and is therefore not part of a
146 // package's API). This is true if the request originated in a test package,
147 // and if the declaration is also found in the same test package.
148 if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" {
149 if _, err := i.pkg.File(i.Declaration.MappedRange[0].URI()); err == nil {
153 // Don't return links for other unexported types.
158 switch obj := obj.(type) {
160 // If the object is a field, and we have an associated selector
161 // composite literal, or struct, we can determine the link.
163 if named, ok := i.enclosing.(*types.Named); ok {
164 rTypeName = named.Obj().Name()
168 typ, ok := obj.Type().(*types.Signature)
172 if r := typ.Recv(); r != nil {
173 switch rtyp := Deref(r.Type()).(type) {
177 // If we have an unexported type, see if the enclosing type is
178 // exported (we may have an interface or struct we can link
179 // to). If not, don't show any link.
180 if !rtyp.Obj().Exported() {
181 if named, ok := i.enclosing.(*types.Named); ok && named.Obj().Exported() {
182 rTypeName = named.Obj().Name()
187 rTypeName = rtyp.Obj().Name()
192 if obj.Pkg() == nil {
193 event.Log(ctx, fmt.Sprintf("nil package for %s", obj))
196 h.importPath = obj.Pkg().Path()
197 h.LinkPath = h.importPath
198 if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok {
199 h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1)
202 h.LinkAnchor = fmt.Sprintf("%s.%s", rTypeName, obj.Name())
203 h.symbolName = fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), rTypeName, obj.Name())
206 // For most cases, the link is "package/path#symbol".
207 h.LinkAnchor = obj.Name()
208 h.symbolName = fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name())
212 func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {
213 if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" {
216 impPkg, err := i.pkg.GetImport(path)
220 if impPkg.Version() == nil {
223 version, modpath := impPkg.Version().Version, impPkg.Version().Path
224 if modpath == "" || version == "" {
227 return modpath, version, true
230 // objectString is a wrapper around the types.ObjectString function.
231 // It handles adding more information to the object string.
232 func objectString(obj types.Object, qf types.Qualifier) string {
233 str := types.ObjectString(obj, qf)
234 switch obj := obj.(type) {
236 str = fmt.Sprintf("%s = %s", str, obj.Val())
238 // Try to add a formatted duration as an inline comment
239 typ, ok := obj.Type().(*types.Named)
243 pkg := typ.Obj().Pkg()
244 if pkg.Path() == "time" && typ.Obj().Name() == "Duration" {
245 if d, ok := constant.Int64Val(obj.Val()); ok {
246 str += " // " + time.Duration(d).String()
253 // HoverInfo returns a HoverInformation struct for an ast node and its type
255 func HoverInfo(ctx context.Context, pkg Package, obj types.Object, node ast.Node) (*HoverInformation, error) {
256 var info *HoverInformation
258 switch node := node.(type) {
260 // The package declaration.
261 for _, f := range pkg.GetSyntax() {
263 info = &HoverInformation{comment: f.Doc}
266 case *ast.ImportSpec:
267 // Try to find the package documentation for an imported package.
268 if pkgName, ok := obj.(*types.PkgName); ok {
269 imp, err := pkg.GetImport(pkgName.Imported().Path())
273 // Assume that only one file will contain package documentation,
274 // so pick the first file that has a doc comment.
275 for _, file := range imp.GetSyntax() {
277 info = &HoverInformation{source: obj, comment: file.Doc}
282 info = &HoverInformation{source: node}
284 switch obj := obj.(type) {
285 case *types.TypeName, *types.Var, *types.Const, *types.Func:
287 info, err = formatGenDecl(node, obj, obj.Type())
291 _, info.isType = obj.(*types.TypeName)
294 if obj.Parent() == types.Universe {
295 if obj.Name() == "error" {
296 info = &HoverInformation{source: node}
298 info = &HoverInformation{source: node.Name} // comments not needed for builtins
304 info = &HoverInformation{source: obj, comment: node.Doc}
306 info = &HoverInformation{source: node.Type, comment: node.Doc}
311 info = &HoverInformation{source: obj}
314 if info.comment != nil {
315 info.FullDocumentation = info.comment.Text()
316 info.Synopsis = doc.Synopsis(info.FullDocumentation)
322 func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*HoverInformation, error) {
323 if _, ok := typ.(*types.Named); ok {
324 switch typ.Underlying().(type) {
325 case *types.Interface, *types.Struct:
326 return formatGenDecl(node, obj, typ.Underlying())
330 for _, s := range node.Specs {
331 if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() {
337 return nil, errors.Errorf("no spec for node %v at position %v", node, obj.Pos())
340 // If we have a field or method.
342 case *types.Var, *types.Const, *types.Func:
343 return formatVar(spec, obj, node), nil
346 switch spec := spec.(type) {
348 if len(node.Specs) > 1 {
349 // If multiple types are declared in the same block.
350 return &HoverInformation{source: spec.Type, comment: spec.Doc}, nil
352 return &HoverInformation{source: spec, comment: node.Doc}, nil
355 return &HoverInformation{source: spec, comment: spec.Doc}, nil
356 case *ast.ImportSpec:
357 return &HoverInformation{source: spec, comment: spec.Doc}, nil
359 return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec)
362 func formatVar(node ast.Spec, obj types.Object, decl *ast.GenDecl) *HoverInformation {
363 var fieldList *ast.FieldList
364 switch spec := node.(type) {
366 switch t := spec.Type.(type) {
367 case *ast.StructType:
369 case *ast.InterfaceType:
370 fieldList = t.Methods
378 comment = spec.Comment
380 return &HoverInformation{source: obj, comment: comment}
382 // If we have a struct or interface declaration,
383 // we need to match the object to the corresponding field or method.
384 if fieldList != nil {
385 for i := 0; i < len(fieldList.List); i++ {
386 field := fieldList.List[i]
387 if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
388 if field.Doc.Text() != "" {
389 return &HoverInformation{source: obj, comment: field.Doc}
391 return &HoverInformation{source: obj, comment: field.Comment}
395 return &HoverInformation{source: obj, comment: decl.Doc}
398 func FormatHover(h *HoverInformation, options *Options) (string, error) {
399 signature := h.Signature
400 if signature != "" && options.PreferredContentFormat == protocol.Markdown {
401 signature = fmt.Sprintf("```go\n%s\n```", signature)
404 switch options.HoverKind {
406 return h.SingleLine, nil
407 case NoDocumentation:
408 return signature, nil
410 b, err := json.Marshal(h)
414 return string(b), nil
416 link := formatLink(h, options)
417 switch options.HoverKind {
418 case SynopsisDocumentation:
419 doc := formatDoc(h.Synopsis, options)
420 return formatHover(options, signature, link, doc), nil
421 case FullDocumentation:
422 doc := formatDoc(h.FullDocumentation, options)
423 return formatHover(options, signature, link, doc), nil
425 return "", errors.Errorf("no hover for %v", h.source)
428 func formatLink(h *HoverInformation, options *Options) string {
429 if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" {
432 plainLink := BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor)
433 switch options.PreferredContentFormat {
434 case protocol.Markdown:
435 return fmt.Sprintf("[`%s` on %s](%s)", h.symbolName, options.LinkTarget, plainLink)
436 case protocol.PlainText:
443 // BuildLink constructs a link with the given target, path, and anchor.
444 func BuildLink(target, path, anchor string) string {
445 link := fmt.Sprintf("https://%s/%s", target, path)
446 if target == "pkg.go.dev" {
447 link += "?utm_source=gopls"
452 return link + "#" + anchor
455 func formatDoc(doc string, options *Options) string {
456 if options.PreferredContentFormat == protocol.Markdown {
457 return CommentToMarkdown(doc)
462 func formatHover(options *Options, x ...string) string {
463 var b strings.Builder
464 for i, el := range x {
468 // Don't write out final newline.
472 // If any elements of the remainder of the list are non-empty,
474 if anyNonEmpty(x[i+1:]) {
475 if options.PreferredContentFormat == protocol.Markdown {
476 b.WriteString("\n\n")
486 func anyNonEmpty(x []string) bool {
487 for _, el := range x {