-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
hover.go
176 lines (160 loc) · 4.95 KB
/
hover.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"fmt"
"go/ast"
"go/doc"
"go/format"
"go/types"
"strings"
"golang.org/x/tools/internal/telemetry/trace"
errors "golang.org/x/xerrors"
)
type HoverInformation struct {
// Signature is the symbol's signature.
Signature string `json:"signature"`
// SingleLine is a single line describing the symbol.
// This is recommended only for use in clients that show a single line for hover.
SingleLine string `json:"singleLine"`
// Synopsis is a single sentence synopsis of the symbol's documentation.
Synopsis string `json:"synopsis"`
// FullDocumentation is the symbol's full documentation.
FullDocumentation string `json:"fullDocumentation"`
source interface{}
comment *ast.CommentGroup
}
func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) {
ctx, done := trace.StartSpan(ctx, "source.Hover")
defer done()
h, err := i.Declaration.hover(ctx)
if err != nil {
return nil, err
}
// Determine the symbol's signature.
switch x := h.source.(type) {
case ast.Node:
var b strings.Builder
if err := format.Node(&b, i.Snapshot.View().Session().Cache().FileSet(), x); err != nil {
return nil, err
}
h.Signature = b.String()
case types.Object:
h.Signature = objectString(x, i.qf)
}
// Set the documentation.
if i.Declaration.obj != nil {
h.SingleLine = objectString(i.Declaration.obj, i.qf)
}
if h.comment != nil {
h.FullDocumentation = h.comment.Text()
h.Synopsis = doc.Synopsis(h.FullDocumentation)
}
return h, nil
}
// objectString is a wrapper around the types.ObjectString function.
// It handles adding more information to the object string.
func objectString(obj types.Object, qf types.Qualifier) string {
str := types.ObjectString(obj, qf)
switch obj := obj.(type) {
case *types.Const:
str = fmt.Sprintf("%s = %s", str, obj.Val())
}
return str
}
func (d Declaration) hover(ctx context.Context) (*HoverInformation, error) {
_, done := trace.StartSpan(ctx, "source.hover")
defer done()
obj := d.obj
switch node := d.node.(type) {
case *ast.ImportSpec:
return &HoverInformation{source: node}, nil
case *ast.GenDecl:
switch obj := obj.(type) {
case *types.TypeName, *types.Var, *types.Const, *types.Func:
return formatGenDecl(node, obj, obj.Type())
}
case *ast.TypeSpec:
if obj.Parent() == types.Universe {
if obj.Name() == "error" {
return &HoverInformation{source: node}, nil
}
return &HoverInformation{source: node.Name}, nil // comments not needed for builtins
}
case *ast.FuncDecl:
switch obj.(type) {
case *types.Func:
return &HoverInformation{source: obj, comment: node.Doc}, nil
case *types.Builtin:
return &HoverInformation{source: node.Type, comment: node.Doc}, nil
}
}
return &HoverInformation{source: obj}, nil
}
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*HoverInformation, error) {
if _, ok := typ.(*types.Named); ok {
switch typ.Underlying().(type) {
case *types.Interface, *types.Struct:
return formatGenDecl(node, obj, typ.Underlying())
}
}
var spec ast.Spec
for _, s := range node.Specs {
if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() {
spec = s
break
}
}
if spec == nil {
return nil, errors.Errorf("no spec for node %v at position %v", node, obj.Pos())
}
// If we have a field or method.
switch obj.(type) {
case *types.Var, *types.Const, *types.Func:
return formatVar(spec, obj)
}
// Handle types.
switch spec := spec.(type) {
case *ast.TypeSpec:
if len(node.Specs) > 1 {
// If multiple types are declared in the same block.
return &HoverInformation{source: spec.Type, comment: spec.Doc}, nil
} else {
return &HoverInformation{source: spec, comment: node.Doc}, nil
}
case *ast.ValueSpec:
return &HoverInformation{source: spec, comment: spec.Doc}, nil
case *ast.ImportSpec:
return &HoverInformation{source: spec, comment: spec.Doc}, nil
}
return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec)
}
func formatVar(node ast.Spec, obj types.Object) (*HoverInformation, error) {
var fieldList *ast.FieldList
if spec, ok := node.(*ast.TypeSpec); ok {
switch t := spec.Type.(type) {
case *ast.StructType:
fieldList = t.Fields
case *ast.InterfaceType:
fieldList = t.Methods
}
}
// If we have a struct or interface declaration,
// we need to match the object to the corresponding field or method.
if fieldList != nil {
for i := 0; i < len(fieldList.List); i++ {
field := fieldList.List[i]
if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
if field.Doc.Text() != "" {
return &HoverInformation{source: obj, comment: field.Doc}, nil
} else if field.Comment.Text() != "" {
return &HoverInformation{source: obj, comment: field.Comment}, nil
}
}
}
}
// If we weren't able to find documentation for the object.
return &HoverInformation{source: obj}, nil
}