.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / 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/constant"
13         "go/doc"
14         "go/format"
15         "go/types"
16         "strings"
17         "time"
18
19         "golang.org/x/tools/internal/event"
20         "golang.org/x/tools/internal/lsp/protocol"
21         errors "golang.org/x/xerrors"
22 )
23
24 type HoverInformation struct {
25         // Signature is the symbol's signature.
26         Signature string `json:"signature"`
27
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"`
31
32         // Synopsis is a single sentence synopsis of the symbol's documentation.
33         Synopsis string `json:"synopsis"`
34
35         // FullDocumentation is the symbol's full documentation.
36         FullDocumentation string `json:"fullDocumentation"`
37
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"`
41
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"`
45
46         // importPath is the import path for the package containing the given
47         // symbol.
48         importPath string
49
50         // symbolName is the types.Object.Name for the given symbol.
51         symbolName string
52
53         source  interface{}
54         comment *ast.CommentGroup
55
56         // isTypeName reports whether the identifier is a type name. In such cases,
57         // the hover has the prefix "type ".
58         isType bool
59 }
60
61 func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
62         ident, err := Identifier(ctx, snapshot, fh, position)
63         if err != nil {
64                 return nil, nil
65         }
66         h, err := HoverIdentifier(ctx, ident)
67         if err != nil {
68                 return nil, err
69         }
70         rng, err := ident.Range()
71         if err != nil {
72                 return nil, err
73         }
74         // See golang/go#36998: don't link to modules matching GOPRIVATE.
75         if snapshot.View().IsGoPrivatePath(h.importPath) {
76                 h.LinkPath = ""
77         }
78         hover, err := FormatHover(h, snapshot.View().Options())
79         if err != nil {
80                 return nil, err
81         }
82         return &protocol.Hover{
83                 Contents: protocol.MarkupContent{
84                         Kind:  snapshot.View().Options().PreferredContentFormat,
85                         Value: hover,
86                 },
87                 Range: rng,
88         }, nil
89 }
90
91 func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverInformation, error) {
92         ctx, done := event.Start(ctx, "source.Hover")
93         defer done()
94
95         fset := i.Snapshot.FileSet()
96         h, err := HoverInfo(ctx, i.pkg, i.Declaration.obj, i.Declaration.node)
97         if err != nil {
98                 return nil, err
99         }
100         // Determine the symbol's signature.
101         switch x := h.source.(type) {
102         case ast.Node:
103                 var b strings.Builder
104                 if err := format.Node(&b, fset, x); err != nil {
105                         return nil, err
106                 }
107                 h.Signature = b.String()
108                 if h.isType {
109                         h.Signature = "type " + h.Signature
110                 }
111         case types.Object:
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))
117                                 break
118                         }
119                 }
120                 h.Signature = objectString(x, i.qf)
121         }
122         if obj := i.Declaration.obj; obj != nil {
123                 h.SingleLine = objectString(obj, i.qf)
124         }
125         obj := i.Declaration.obj
126         if obj == nil {
127                 return h, nil
128         }
129         switch obj := obj.(type) {
130         case *types.PkgName:
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)
136                 }
137                 return h, nil
138         case *types.Builtin:
139                 h.importPath = "builtin"
140                 h.LinkPath = h.importPath
141                 h.LinkAnchor = obj.Name()
142                 h.symbolName = h.LinkAnchor
143                 return h, nil
144         }
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 {
150                         return h, nil
151                 }
152         }
153         // Don't return links for other unexported types.
154         if !obj.Exported() {
155                 return h, nil
156         }
157         var rTypeName string
158         switch obj := obj.(type) {
159         case *types.Var:
160                 // If the object is a field, and we have an associated selector
161                 // composite literal, or struct, we can determine the link.
162                 if obj.IsField() {
163                         if named, ok := i.enclosing.(*types.Named); ok {
164                                 rTypeName = named.Obj().Name()
165                         }
166                 }
167         case *types.Func:
168                 typ, ok := obj.Type().(*types.Signature)
169                 if !ok {
170                         return h, nil
171                 }
172                 if r := typ.Recv(); r != nil {
173                         switch rtyp := Deref(r.Type()).(type) {
174                         case *types.Struct:
175                                 rTypeName = r.Name()
176                         case *types.Named:
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()
183                                         } else {
184                                                 return h, nil
185                                         }
186                                 } else {
187                                         rTypeName = rtyp.Obj().Name()
188                                 }
189                         }
190                 }
191         }
192         if obj.Pkg() == nil {
193                 event.Log(ctx, fmt.Sprintf("nil package for %s", obj))
194                 return h, nil
195         }
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)
200         }
201         if rTypeName != "" {
202                 h.LinkAnchor = fmt.Sprintf("%s.%s", rTypeName, obj.Name())
203                 h.symbolName = fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), rTypeName, obj.Name())
204                 return h, nil
205         }
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())
209         return h, nil
210 }
211
212 func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {
213         if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" {
214                 return "", "", false
215         }
216         impPkg, err := i.pkg.GetImport(path)
217         if err != nil {
218                 return "", "", false
219         }
220         if impPkg.Version() == nil {
221                 return "", "", false
222         }
223         version, modpath := impPkg.Version().Version, impPkg.Version().Path
224         if modpath == "" || version == "" {
225                 return "", "", false
226         }
227         return modpath, version, true
228 }
229
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) {
235         case *types.Const:
236                 str = fmt.Sprintf("%s = %s", str, obj.Val())
237
238                 // Try to add a formatted duration as an inline comment
239                 typ, ok := obj.Type().(*types.Named)
240                 if !ok {
241                         break
242                 }
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()
247                         }
248                 }
249         }
250         return str
251 }
252
253 // HoverInfo returns a HoverInformation struct for an ast node and its type
254 // object.
255 func HoverInfo(ctx context.Context, pkg Package, obj types.Object, node ast.Node) (*HoverInformation, error) {
256         var info *HoverInformation
257
258         switch node := node.(type) {
259         case *ast.Ident:
260                 // The package declaration.
261                 for _, f := range pkg.GetSyntax() {
262                         if f.Name == node {
263                                 info = &HoverInformation{comment: f.Doc}
264                         }
265                 }
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())
270                         if err != nil {
271                                 return nil, err
272                         }
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() {
276                                 if file.Doc != nil {
277                                         info = &HoverInformation{source: obj, comment: file.Doc}
278                                         break
279                                 }
280                         }
281                 }
282                 info = &HoverInformation{source: node}
283         case *ast.GenDecl:
284                 switch obj := obj.(type) {
285                 case *types.TypeName, *types.Var, *types.Const, *types.Func:
286                         var err error
287                         info, err = formatGenDecl(node, obj, obj.Type())
288                         if err != nil {
289                                 return nil, err
290                         }
291                         _, info.isType = obj.(*types.TypeName)
292                 }
293         case *ast.TypeSpec:
294                 if obj.Parent() == types.Universe {
295                         if obj.Name() == "error" {
296                                 info = &HoverInformation{source: node}
297                         } else {
298                                 info = &HoverInformation{source: node.Name} // comments not needed for builtins
299                         }
300                 }
301         case *ast.FuncDecl:
302                 switch obj.(type) {
303                 case *types.Func:
304                         info = &HoverInformation{source: obj, comment: node.Doc}
305                 case *types.Builtin:
306                         info = &HoverInformation{source: node.Type, comment: node.Doc}
307                 }
308         }
309
310         if info == nil {
311                 info = &HoverInformation{source: obj}
312         }
313
314         if info.comment != nil {
315                 info.FullDocumentation = info.comment.Text()
316                 info.Synopsis = doc.Synopsis(info.FullDocumentation)
317         }
318
319         return info, nil
320 }
321
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())
327                 }
328         }
329         var spec ast.Spec
330         for _, s := range node.Specs {
331                 if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() {
332                         spec = s
333                         break
334                 }
335         }
336         if spec == nil {
337                 return nil, errors.Errorf("no spec for node %v at position %v", node, obj.Pos())
338         }
339
340         // If we have a field or method.
341         switch obj.(type) {
342         case *types.Var, *types.Const, *types.Func:
343                 return formatVar(spec, obj, node), nil
344         }
345         // Handle types.
346         switch spec := spec.(type) {
347         case *ast.TypeSpec:
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
351                 } else {
352                         return &HoverInformation{source: spec, comment: node.Doc}, nil
353                 }
354         case *ast.ValueSpec:
355                 return &HoverInformation{source: spec, comment: spec.Doc}, nil
356         case *ast.ImportSpec:
357                 return &HoverInformation{source: spec, comment: spec.Doc}, nil
358         }
359         return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec)
360 }
361
362 func formatVar(node ast.Spec, obj types.Object, decl *ast.GenDecl) *HoverInformation {
363         var fieldList *ast.FieldList
364         switch spec := node.(type) {
365         case *ast.TypeSpec:
366                 switch t := spec.Type.(type) {
367                 case *ast.StructType:
368                         fieldList = t.Fields
369                 case *ast.InterfaceType:
370                         fieldList = t.Methods
371                 }
372         case *ast.ValueSpec:
373                 comment := spec.Doc
374                 if comment == nil {
375                         comment = decl.Doc
376                 }
377                 if comment == nil {
378                         comment = spec.Comment
379                 }
380                 return &HoverInformation{source: obj, comment: comment}
381         }
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}
390                                 }
391                                 return &HoverInformation{source: obj, comment: field.Comment}
392                         }
393                 }
394         }
395         return &HoverInformation{source: obj, comment: decl.Doc}
396 }
397
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)
402         }
403
404         switch options.HoverKind {
405         case SingleLine:
406                 return h.SingleLine, nil
407         case NoDocumentation:
408                 return signature, nil
409         case Structured:
410                 b, err := json.Marshal(h)
411                 if err != nil {
412                         return "", err
413                 }
414                 return string(b), nil
415         }
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
424         }
425         return "", errors.Errorf("no hover for %v", h.source)
426 }
427
428 func formatLink(h *HoverInformation, options *Options) string {
429         if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" {
430                 return ""
431         }
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:
437                 return ""
438         default:
439                 return plainLink
440         }
441 }
442
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"
448         }
449         if anchor == "" {
450                 return link
451         }
452         return link + "#" + anchor
453 }
454
455 func formatDoc(doc string, options *Options) string {
456         if options.PreferredContentFormat == protocol.Markdown {
457                 return CommentToMarkdown(doc)
458         }
459         return doc
460 }
461
462 func formatHover(options *Options, x ...string) string {
463         var b strings.Builder
464         for i, el := range x {
465                 if el != "" {
466                         b.WriteString(el)
467
468                         // Don't write out final newline.
469                         if i == len(x) {
470                                 continue
471                         }
472                         // If any elements of the remainder of the list are non-empty,
473                         // write a newline.
474                         if anyNonEmpty(x[i+1:]) {
475                                 if options.PreferredContentFormat == protocol.Markdown {
476                                         b.WriteString("\n\n")
477                                 } else {
478                                         b.WriteRune('\n')
479                                 }
480                         }
481                 }
482         }
483         return b.String()
484 }
485
486 func anyNonEmpty(x []string) bool {
487         for _, el := range x {
488                 if el != "" {
489                         return true
490                 }
491         }
492         return false
493 }