.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.0 / internal / lsp / source / hover.go
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/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
22 type 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
59 func 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
89 func 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
206 func 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.
226 func 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.
237 func 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
304 func 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
344 func 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
380 func 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
410 func 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.
426 func 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
437 func formatDoc(doc string, options *Options) string {
438         if options.PreferredContentFormat == protocol.Markdown {
439                 return CommentToMarkdown(doc)
440         }
441         return doc
442 }
443
444 func 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
468 func anyNonEmpty(x []string) bool {
469         for _, el := range x {
470                 if el != "" {
471                         return true
472                 }
473         }
474         return false
475 }