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	"fmt"
10	"go/ast"
11	"go/doc"
12	"go/format"
13	"go/token"
14	"go/types"
15	"strings"
16)
17
18type documentation struct {
19	source  interface{}
20	comment *ast.CommentGroup
21}
22
23func (i *IdentifierInfo) Hover(ctx context.Context, markdownSupported, wantComments bool) (string, error) {
24	h, err := i.decl.hover(ctx)
25	if err != nil {
26		return "", err
27	}
28	c := h.comment
29	if !wantComments {
30		c = nil
31	}
32	var b strings.Builder
33	return writeHover(h.source, i.File.FileSet(), &b, c, markdownSupported, i.qf)
34}
35
36func (d declaration) hover(ctx context.Context) (*documentation, error) {
37	obj := d.obj
38	switch node := d.node.(type) {
39	case *ast.GenDecl:
40		switch obj := obj.(type) {
41		case *types.TypeName, *types.Var, *types.Const, *types.Func:
42			return formatGenDecl(node, obj, obj.Type())
43		}
44	case *ast.TypeSpec:
45		if obj.Parent() == types.Universe {
46			if obj.Name() == "error" {
47				return &documentation{node, nil}, nil
48			}
49			return &documentation{node.Name, nil}, nil // comments not needed for builtins
50		}
51	case *ast.FuncDecl:
52		switch obj.(type) {
53		case *types.Func:
54			return &documentation{obj, node.Doc}, nil
55		case *types.Builtin:
56			return &documentation{node.Type, node.Doc}, nil
57		}
58	}
59	return &documentation{obj, nil}, nil
60}
61
62func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*documentation, error) {
63	if _, ok := typ.(*types.Named); ok {
64		switch typ.Underlying().(type) {
65		case *types.Interface, *types.Struct:
66			return formatGenDecl(node, obj, typ.Underlying())
67		}
68	}
69	var spec ast.Spec
70	for _, s := range node.Specs {
71		if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() {
72			spec = s
73			break
74		}
75	}
76	if spec == nil {
77		return nil, fmt.Errorf("no spec for node %v at position %v", node, obj.Pos())
78	}
79	// If we have a field or method.
80	switch obj.(type) {
81	case *types.Var, *types.Const, *types.Func:
82		return formatVar(spec, obj)
83	}
84	// Handle types.
85	switch spec := spec.(type) {
86	case *ast.TypeSpec:
87		if len(node.Specs) > 1 {
88			// If multiple types are declared in the same block.
89			return &documentation{spec.Type, spec.Doc}, nil
90		} else {
91			return &documentation{spec, node.Doc}, nil
92		}
93	case *ast.ValueSpec:
94		return &documentation{spec, spec.Doc}, nil
95	case *ast.ImportSpec:
96		return &documentation{spec, spec.Doc}, nil
97	}
98	return nil, fmt.Errorf("unable to format spec %v (%T)", spec, spec)
99}
100
101func formatVar(node ast.Spec, obj types.Object) (*documentation, error) {
102	var fieldList *ast.FieldList
103	if spec, ok := node.(*ast.TypeSpec); ok {
104		switch t := spec.Type.(type) {
105		case *ast.StructType:
106			fieldList = t.Fields
107		case *ast.InterfaceType:
108			fieldList = t.Methods
109		}
110	}
111	// If we have a struct or interface declaration,
112	// we need to match the object to the corresponding field or method.
113	if fieldList != nil {
114		for i := 0; i < len(fieldList.List); i++ {
115			field := fieldList.List[i]
116			if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
117				if field.Doc.Text() != "" {
118					return &documentation{obj, field.Doc}, nil
119				} else if field.Comment.Text() != "" {
120					return &documentation{obj, field.Comment}, nil
121				}
122			}
123		}
124	}
125	// If we weren't able to find documentation for the object.
126	return &documentation{obj, nil}, nil
127}
128
129// writeHover writes the hover for a given node and its documentation.
130func writeHover(x interface{}, fset *token.FileSet, b *strings.Builder, c *ast.CommentGroup, markdownSupported bool, qf types.Qualifier) (string, error) {
131	if c != nil {
132		// TODO(rstambler): Improve conversion from Go docs to markdown.
133		b.WriteString(formatDocumentation(c))
134		b.WriteRune('\n')
135	}
136	if markdownSupported {
137		b.WriteString("```go\n")
138	}
139	switch x := x.(type) {
140	case ast.Node:
141		if err := format.Node(b, fset, x); err != nil {
142			return "", err
143		}
144	case types.Object:
145		b.WriteString(types.ObjectString(x, qf))
146	}
147	if markdownSupported {
148		b.WriteString("\n```")
149	}
150	return b.String(), nil
151}
152
153func formatDocumentation(c *ast.CommentGroup) string {
154	if c == nil {
155		return ""
156	}
157	return doc.Synopsis(c.Text())
158}
159