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