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 / cmd / guru / what.go
1 // Copyright 2013 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 main
6
7 import (
8         "fmt"
9         "go/ast"
10         "go/build"
11         "go/token"
12         "os"
13         "path"
14         "path/filepath"
15         "sort"
16         "strings"
17
18         "golang.org/x/tools/cmd/guru/serial"
19         "golang.org/x/tools/go/ast/astutil"
20 )
21
22 // what reports all the information about the query selection that can be
23 // obtained from parsing only its containing source file.
24 // It is intended to be a very low-latency query callable from GUI
25 // tools, e.g. to populate a menu of options of slower queries about
26 // the selected location.
27 //
28 func what(q *Query) error {
29         qpos, err := fastQueryPos(q.Build, q.Pos)
30         if err != nil {
31                 return err
32         }
33
34         // (ignore errors)
35         srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build)
36
37         // Determine which query modes are applicable to the selection.
38         enable := map[string]bool{
39                 "describe": true, // any syntax; always enabled
40         }
41
42         if qpos.end > qpos.start {
43                 enable["freevars"] = true // nonempty selection?
44         }
45
46         for _, n := range qpos.path {
47                 switch n := n.(type) {
48                 case *ast.Ident:
49                         enable["definition"] = true
50                         enable["referrers"] = true
51                         enable["implements"] = true
52                 case *ast.CallExpr:
53                         enable["callees"] = true
54                 case *ast.FuncDecl:
55                         enable["callers"] = true
56                         enable["callstack"] = true
57                 case *ast.SendStmt:
58                         enable["peers"] = true
59                 case *ast.UnaryExpr:
60                         if n.Op == token.ARROW {
61                                 enable["peers"] = true
62                         }
63                 }
64
65                 // For implements, we approximate findInterestingNode.
66                 if _, ok := enable["implements"]; !ok {
67                         switch n.(type) {
68                         case *ast.ArrayType,
69                                 *ast.StructType,
70                                 *ast.FuncType,
71                                 *ast.InterfaceType,
72                                 *ast.MapType,
73                                 *ast.ChanType:
74                                 enable["implements"] = true
75                         }
76                 }
77
78                 // For pointsto and whicherrs, we approximate findInterestingNode.
79                 if _, ok := enable["pointsto"]; !ok {
80                         switch n.(type) {
81                         case ast.Stmt,
82                                 *ast.ArrayType,
83                                 *ast.StructType,
84                                 *ast.FuncType,
85                                 *ast.InterfaceType,
86                                 *ast.MapType,
87                                 *ast.ChanType:
88                                 // not an expression
89                                 enable["pointsto"] = false
90                                 enable["whicherrs"] = false
91
92                         case ast.Expr, ast.Decl, *ast.ValueSpec:
93                                 // an expression, maybe
94                                 enable["pointsto"] = true
95                                 enable["whicherrs"] = true
96
97                         default:
98                                 // Comment, Field, KeyValueExpr, etc: ascend.
99                         }
100                 }
101         }
102
103         // If we don't have an exact selection, disable modes that need one.
104         if !qpos.exact {
105                 enable["callees"] = false
106                 enable["pointsto"] = false
107                 enable["whicherrs"] = false
108                 enable["describe"] = false
109         }
110
111         var modes []string
112         for mode := range enable {
113                 modes = append(modes, mode)
114         }
115         sort.Strings(modes)
116
117         // Find the object referred to by the selection (if it's an
118         // identifier) and report the position of each identifier
119         // that refers to the same object.
120         //
121         // This may return spurious matches (e.g. struct fields) because
122         // it uses the best-effort name resolution done by go/parser.
123         var sameids []token.Pos
124         var object string
125         if id, ok := qpos.path[0].(*ast.Ident); ok {
126                 if id.Obj == nil {
127                         // An unresolved identifier is potentially a package name.
128                         // Resolve them with a simple importer (adds ~100µs).
129                         importer := func(imports map[string]*ast.Object, path string) (*ast.Object, error) {
130                                 pkg, ok := imports[path]
131                                 if !ok {
132                                         pkg = &ast.Object{
133                                                 Kind: ast.Pkg,
134                                                 Name: filepath.Base(path), // a guess
135                                         }
136                                         imports[path] = pkg
137                                 }
138                                 return pkg, nil
139                         }
140                         f := qpos.path[len(qpos.path)-1].(*ast.File)
141                         ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil)
142                 }
143
144                 if id.Obj != nil {
145                         object = id.Obj.Name
146                         decl := qpos.path[len(qpos.path)-1]
147                         ast.Inspect(decl, func(n ast.Node) bool {
148                                 if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj {
149                                         sameids = append(sameids, n.Pos())
150                                 }
151                                 return true
152                         })
153                 }
154         }
155
156         q.Output(qpos.fset, &whatResult{
157                 path:       qpos.path,
158                 srcdir:     srcdir,
159                 importPath: importPath,
160                 modes:      modes,
161                 object:     object,
162                 sameids:    sameids,
163         })
164         return nil
165 }
166
167 // guessImportPath finds the package containing filename, and returns
168 // its source directory (an element of $GOPATH) and its import path
169 // relative to it.
170 //
171 // TODO(adonovan): what about _test.go files that are not part of the
172 // package?
173 //
174 func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
175         absFile, err := filepath.Abs(filename)
176         if err != nil {
177                 return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err)
178         }
179
180         absFileDir := filepath.Dir(absFile)
181         resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir)
182         if err != nil {
183                 return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err)
184         }
185
186         segmentedAbsFileDir := segments(resolvedAbsFileDir)
187         // Find the innermost directory in $GOPATH that encloses filename.
188         minD := 1024
189         for _, gopathDir := range buildContext.SrcDirs() {
190                 absDir, err := filepath.Abs(gopathDir)
191                 if err != nil {
192                         continue // e.g. non-existent dir on $GOPATH
193                 }
194                 resolvedAbsDir, err := filepath.EvalSymlinks(absDir)
195                 if err != nil {
196                         continue // e.g. non-existent dir on $GOPATH
197                 }
198
199                 d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir)
200                 // If there are multiple matches,
201                 // prefer the innermost enclosing directory
202                 // (smallest d).
203                 if d >= 0 && d < minD {
204                         minD = d
205                         srcdir = gopathDir
206                         importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
207                 }
208         }
209         if srcdir == "" {
210                 return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
211                         filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
212         }
213         if importPath == "" {
214                 // This happens for e.g. $GOPATH/src/a.go, but
215                 // "" is not a valid path for (*go/build).Import.
216                 return "", "", fmt.Errorf("cannot load package in root of source directory %s", srcdir)
217         }
218         return srcdir, importPath, nil
219 }
220
221 func segments(path string) []string {
222         return strings.Split(path, string(os.PathSeparator))
223 }
224
225 // prefixLen returns the length of the remainder of y if x is a prefix
226 // of y, a negative number otherwise.
227 func prefixLen(x, y []string) int {
228         d := len(y) - len(x)
229         if d >= 0 {
230                 for i := range x {
231                         if y[i] != x[i] {
232                                 return -1 // not a prefix
233                         }
234                 }
235         }
236         return d
237 }
238
239 type whatResult struct {
240         path       []ast.Node
241         modes      []string
242         srcdir     string
243         importPath string
244         object     string
245         sameids    []token.Pos
246 }
247
248 func (r *whatResult) PrintPlain(printf printfFunc) {
249         for _, n := range r.path {
250                 printf(n, "%s", astutil.NodeDescription(n))
251         }
252         printf(nil, "modes: %s", r.modes)
253         printf(nil, "srcdir: %s", r.srcdir)
254         printf(nil, "import path: %s", r.importPath)
255         for _, pos := range r.sameids {
256                 printf(pos, "%s", r.object)
257         }
258 }
259
260 func (r *whatResult) JSON(fset *token.FileSet) []byte {
261         var enclosing []serial.SyntaxNode
262         for _, n := range r.path {
263                 enclosing = append(enclosing, serial.SyntaxNode{
264                         Description: astutil.NodeDescription(n),
265                         Start:       fset.Position(n.Pos()).Offset,
266                         End:         fset.Position(n.End()).Offset,
267                 })
268         }
269
270         var sameids []string
271         for _, pos := range r.sameids {
272                 sameids = append(sameids, fset.Position(pos).String())
273         }
274
275         return toJSON(&serial.What{
276                 Modes:      r.modes,
277                 SrcDir:     r.srcdir,
278                 ImportPath: r.importPath,
279                 Enclosing:  enclosing,
280                 Object:     r.object,
281                 SameIDs:    sameids,
282         })
283 }