GoPLS Viewer

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