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