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.
18 "golang.org/x/tools/cmd/guru/serial"
19 "golang.org/x/tools/go/ast/astutil"
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.
28 func what(q *Query) error {
29 qpos, err := fastQueryPos(q.Build, q.Pos)
35 srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build)
37 // Determine which query modes are applicable to the selection.
38 enable := map[string]bool{
39 "describe": true, // any syntax; always enabled
42 if qpos.end > qpos.start {
43 enable["freevars"] = true // nonempty selection?
46 for _, n := range qpos.path {
47 switch n := n.(type) {
49 enable["definition"] = true
50 enable["referrers"] = true
51 enable["implements"] = true
53 enable["callees"] = true
55 enable["callers"] = true
56 enable["callstack"] = true
58 enable["peers"] = true
60 if n.Op == token.ARROW {
61 enable["peers"] = true
65 // For implements, we approximate findInterestingNode.
66 if _, ok := enable["implements"]; !ok {
74 enable["implements"] = true
78 // For pointsto and whicherrs, we approximate findInterestingNode.
79 if _, ok := enable["pointsto"]; !ok {
89 enable["pointsto"] = false
90 enable["whicherrs"] = false
92 case ast.Expr, ast.Decl, *ast.ValueSpec:
93 // an expression, maybe
94 enable["pointsto"] = true
95 enable["whicherrs"] = true
98 // Comment, Field, KeyValueExpr, etc: ascend.
103 // If we don't have an exact selection, disable modes that need one.
105 enable["callees"] = false
106 enable["pointsto"] = false
107 enable["whicherrs"] = false
108 enable["describe"] = false
112 for mode := range enable {
113 modes = append(modes, mode)
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.
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
125 if id, ok := qpos.path[0].(*ast.Ident); ok {
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]
134 Name: filepath.Base(path), // a guess
140 f := qpos.path[len(qpos.path)-1].(*ast.File)
141 ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil)
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())
156 q.Output(qpos.fset, &whatResult{
159 importPath: importPath,
167 // guessImportPath finds the package containing filename, and returns
168 // its source directory (an element of $GOPATH) and its import path
171 // TODO(adonovan): what about _test.go files that are not part of the
174 func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
175 absFile, err := filepath.Abs(filename)
177 return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err)
180 absFileDir := filepath.Dir(absFile)
181 resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir)
183 return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err)
186 segmentedAbsFileDir := segments(resolvedAbsFileDir)
187 // Find the innermost directory in $GOPATH that encloses filename.
189 for _, gopathDir := range buildContext.SrcDirs() {
190 absDir, err := filepath.Abs(gopathDir)
192 continue // e.g. non-existent dir on $GOPATH
194 resolvedAbsDir, err := filepath.EvalSymlinks(absDir)
196 continue // e.g. non-existent dir on $GOPATH
199 d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir)
200 // If there are multiple matches,
201 // prefer the innermost enclosing directory
203 if d >= 0 && d < minD {
206 importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
210 return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
211 filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
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)
218 return srcdir, importPath, nil
221 func segments(path string) []string {
222 return strings.Split(path, string(os.PathSeparator))
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 {
232 return -1 // not a prefix
239 type whatResult struct {
248 func (r *whatResult) PrintPlain(printf printfFunc) {
249 for _, n := range r.path {
250 printf(n, "%s", astutil.NodeDescription(n))
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)
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,
271 for _, pos := range r.sameids {
272 sameids = append(sameids, fset.Position(pos).String())
275 return toJSON(&serial.What{
278 ImportPath: r.importPath,
279 Enclosing: enclosing,