@@ -42,16 +42,219 @@ import (
42
42
"path/filepath"
43
43
"strings"
44
44
45
+ "golang.org/x/tools/go/ast/astutil"
45
46
"golang.org/x/tools/gopls/internal/cache"
47
+ "golang.org/x/tools/gopls/internal/cache/parsego"
46
48
"golang.org/x/tools/gopls/internal/protocol"
47
- "golang.org/x/tools/gopls/internal/util/astutil"
49
+ goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
48
50
"golang.org/x/tools/gopls/internal/util/bug"
49
51
"golang.org/x/tools/gopls/internal/util/safetoken"
50
52
"golang.org/x/tools/gopls/internal/util/slices"
51
53
"golang.org/x/tools/gopls/internal/util/typesutil"
52
54
"golang.org/x/tools/internal/typesinternal"
53
55
)
54
56
57
+ // DocFragment finds the package and (optionally) symbol identified by
58
+ // the current selection, and returns the package path and the
59
+ // optional symbol URL fragment (e.g. "#Buffer.Len") for a symbol,
60
+ // along with a title for the code action.
61
+ //
62
+ // It is called once to offer the code action, and again when the
63
+ // command is executed. This is slightly inefficient but ensures that
64
+ // the title and package/symbol logic are consistent in all cases.
65
+ //
66
+ // It returns zeroes if there is nothing to see here (e.g. reference to a builtin).
67
+ func DocFragment (pkg * cache.Package , pgf * parsego.File , start , end token.Pos ) (pkgpath PackagePath , fragment , title string ) {
68
+ thing := thingAtPoint (pkg , pgf , start , end )
69
+
70
+ makeTitle := func (kind string , imp * types.Package , name string ) string {
71
+ title := "Browse documentation for " + kind + " "
72
+ if imp != nil && imp != pkg .Types () {
73
+ title += imp .Name () + "."
74
+ }
75
+ return title + name
76
+ }
77
+
78
+ wholePackage := func (pkg * types.Package ) (PackagePath , string , string ) {
79
+ // External test packages don't have /pkg doc pages,
80
+ // so instead show the doc for the package under test.
81
+ // (This named-based heuristic is imperfect.)
82
+ if forTest := strings .TrimSuffix (pkg .Path (), "_test" ); forTest != pkg .Path () {
83
+ return PackagePath (forTest ), "" , makeTitle ("package" , nil , filepath .Base (forTest ))
84
+ }
85
+
86
+ return PackagePath (pkg .Path ()), "" , makeTitle ("package" , nil , pkg .Name ())
87
+ }
88
+
89
+ // Conceptually, we check cases in the order:
90
+ // 1. symbol
91
+ // 2. package
92
+ // 3. enclosing
93
+ // but the logic of cases 1 and 3 are identical, hence the odd factoring.
94
+
95
+ // Imported package?
96
+ if thing .pkg != nil && thing .symbol == nil {
97
+ return wholePackage (thing .pkg )
98
+ }
99
+
100
+ // Symbol?
101
+ var sym types.Object
102
+ if thing .symbol != nil {
103
+ sym = thing .symbol // reference to a symbol
104
+ } else if thing .enclosing != nil {
105
+ sym = thing .enclosing // selection is within a declaration of a symbol
106
+ }
107
+ if sym == nil {
108
+ return wholePackage (pkg .Types ()) // no symbol
109
+ }
110
+
111
+ // Built-in (error.Error, append or unsafe).
112
+ // TODO(adonovan): handle builtins in /pkg viewer.
113
+ if sym .Pkg () == nil {
114
+ return "" , "" , "" // nothing to see here
115
+ }
116
+ pkgpath = PackagePath (sym .Pkg ().Path ())
117
+
118
+ // Unexported? Show enclosing type or package.
119
+ if ! sym .Exported () {
120
+ // Unexported method of exported type?
121
+ if fn , ok := sym .(* types.Func ); ok {
122
+ if recv := fn .Type ().(* types.Signature ).Recv (); recv != nil {
123
+ _ , named := typesinternal .ReceiverNamed (recv )
124
+ if named != nil && named .Obj ().Exported () {
125
+ sym = named .Obj ()
126
+ goto below
127
+ }
128
+ }
129
+ }
130
+
131
+ return wholePackage (sym .Pkg ())
132
+ below:
133
+ }
134
+
135
+ // Reference to symbol in external test package?
136
+ // Short-circuit: see comment in wholePackage.
137
+ if strings .HasSuffix (string (pkgpath ), "_test" ) {
138
+ return wholePackage (pkg .Types ())
139
+ }
140
+
141
+ // package-level symbol?
142
+ if isPackageLevel (sym ) {
143
+ return pkgpath , sym .Name (), makeTitle (objectKind (sym ), sym .Pkg (), sym .Name ())
144
+ }
145
+
146
+ // Inv: sym is field or method, or local.
147
+ switch sym := sym .(type ) {
148
+ case * types.Func : // => method
149
+ sig := sym .Type ().(* types.Signature )
150
+ isPtr , named := typesinternal .ReceiverNamed (sig .Recv ())
151
+ if named != nil {
152
+ if ! named .Obj ().Exported () {
153
+ return wholePackage (sym .Pkg ()) // exported method of unexported type
154
+ }
155
+ name := fmt .Sprintf ("(%s%s).%s" ,
156
+ strings .Repeat ("*" , btoi (isPtr )), // for *T
157
+ named .Obj ().Name (),
158
+ sym .Name ())
159
+ fragment := named .Obj ().Name () + "." + sym .Name ()
160
+ return pkgpath , fragment , makeTitle ("method" , sym .Pkg (), name )
161
+ }
162
+
163
+ case * types.Var :
164
+ if sym .IsField () {
165
+ // TODO(adonovan): support fields.
166
+ // The Var symbol doesn't include the struct
167
+ // type, so we need to use the logic from
168
+ // Hover. (This isn't important for
169
+ // DocFragment as fields don't have fragments,
170
+ // but it matters to the grand unification of
171
+ // Hover/Definition/DocFragment.
172
+ }
173
+ }
174
+
175
+ // Field, non-exported method, or local declaration:
176
+ // just show current package.
177
+ return wholePackage (pkg .Types ())
178
+ }
179
+
180
+ // thing describes the package or symbol denoted by a selection.
181
+ //
182
+ // TODO(adonovan): Hover, Definition, and References all start by
183
+ // identifying the selected object. Let's achieve a better factoring
184
+ // of the common parts using this structure, including uniform
185
+ // treatment of doc links, linkname, and suchlike.
186
+ type thing struct {
187
+ // At most one of these fields is set.
188
+ // (The 'enclosing' field is a fallback for when neither
189
+ // of the first two is set.)
190
+ symbol types.Object // referenced symbol
191
+ pkg * types.Package // referenced package
192
+ enclosing types.Object // package-level symbol or method decl enclosing selection
193
+ }
194
+
195
+ func thingAtPoint (pkg * cache.Package , pgf * parsego.File , start , end token.Pos ) thing {
196
+ path , _ := astutil .PathEnclosingInterval (pgf .File , start , end )
197
+
198
+ // In an import spec?
199
+ if len (path ) >= 3 { // [...ImportSpec GenDecl File]
200
+ if spec , ok := path [len (path )- 3 ].(* ast.ImportSpec ); ok {
201
+ if pkgname , ok := typesutil .ImportedPkgName (pkg .TypesInfo (), spec ); ok {
202
+ return thing {pkg : pkgname .Imported ()}
203
+ }
204
+ }
205
+ }
206
+
207
+ // Definition or reference to symbol?
208
+ var obj types.Object
209
+ if id , ok := path [0 ].(* ast.Ident ); ok {
210
+ obj = pkg .TypesInfo ().ObjectOf (id )
211
+
212
+ // Treat use to PkgName like ImportSpec.
213
+ if pkgname , ok := obj .(* types.PkgName ); ok {
214
+ return thing {pkg : pkgname .Imported ()}
215
+ }
216
+
217
+ } else if sel , ok := path [0 ].(* ast.SelectorExpr ); ok {
218
+ // e.g. selection is "fmt.Println" or just a portion ("mt.Prin")
219
+ obj = pkg .TypesInfo ().Uses [sel .Sel ]
220
+ }
221
+ if obj != nil {
222
+ return thing {symbol : obj }
223
+ }
224
+
225
+ // Find enclosing declaration.
226
+ if n := len (path ); n > 1 {
227
+ switch decl := path [n - 2 ].(type ) {
228
+ case * ast.FuncDecl :
229
+ // method?
230
+ if fn := pkg .TypesInfo ().Defs [decl .Name ]; fn != nil {
231
+ return thing {enclosing : fn }
232
+ }
233
+
234
+ case * ast.GenDecl :
235
+ // path=[... Spec? GenDecl File]
236
+ for _ , spec := range decl .Specs {
237
+ if n > 2 && spec == path [n - 3 ] {
238
+ var name * ast.Ident
239
+ switch spec := spec .(type ) {
240
+ case * ast.ValueSpec :
241
+ // var, const: use first name
242
+ name = spec .Names [0 ]
243
+ case * ast.TypeSpec :
244
+ name = spec .Name
245
+ }
246
+ if name != nil {
247
+ return thing {enclosing : pkg .TypesInfo ().Defs [name ]}
248
+ }
249
+ break
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ return thing {} // nothing to see here
256
+ }
257
+
55
258
// Web is an abstraction of gopls' web server.
56
259
type Web interface {
57
260
// PkgURL forms URLs of package or symbol documentation.
@@ -375,7 +578,7 @@ window.addEventListener('load', function() {
375
578
// appear as separate decls. We should too.
376
579
var buf bytes.Buffer
377
580
for _ , file := range pkg .CompiledGoFiles () {
378
- if astutil .NodeContains (file .File , n .Pos ()) {
581
+ if goplsastutil .NodeContains (file .File , n .Pos ()) {
379
582
pos := n .Pos ()
380
583
381
584
// emit emits source in the interval [pos:to] and updates pos.
@@ -633,7 +836,8 @@ window.addEventListener('load', function() {
633
836
method , _ , _ := types .LookupFieldOrMethod (tname .Type (), true , tname .Pkg (), docmethod .Name )
634
837
fmt .Fprintf (& buf , "<h4 id='%s.%s'>func (%s) %s</h4>\n " ,
635
838
doctype .Name , docmethod .Name ,
636
- doctype .Name , objHTML (pkg .FileSet (), web , method ))
839
+ docmethod .Orig , // T or *T
840
+ objHTML (pkg .FileSet (), web , method ))
637
841
638
842
// decl: func (x T) M(params) results
639
843
fmt .Fprintf (& buf , "<pre class='code'>%s</pre>\n " ,
0 commit comments