|
|
// Copyright 2013 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 main
import ( "fmt" "go/ast" "go/build" "go/token" "os" "path/filepath" "sort" "strings"
"golang.org/x/tools/cmd/guru/serial" "golang.org/x/tools/go/ast/astutil" )
// what reports all the information about the query selection that can be
// obtained from parsing only its containing source file.
// It is intended to be a very low-latency query callable from GUI
// tools, e.g. to populate a menu of options of slower queries about
// the selected location.
//
func what(q *Query) error { qpos, err := fastQueryPos(q.Build, q.Pos) if err != nil { return err }
// (ignore errors)
srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build)
// Determine which query modes are applicable to the selection.
enable := map[string]bool{ "describe": true, // any syntax; always enabled
}
if qpos.end > qpos.start { enable["freevars"] = true // nonempty selection?
}
for _, n := range qpos.path { switch n := n.(type) { case *ast.Ident: enable["definition"] = true enable["referrers"] = true enable["implements"] = true case *ast.CallExpr: enable["callees"] = true case *ast.FuncDecl: enable["callers"] = true enable["callstack"] = true case *ast.SendStmt: enable["peers"] = true case *ast.UnaryExpr: if n.Op == token.ARROW { enable["peers"] = true } }
// For implements, we approximate findInterestingNode.
if _, ok := enable["implements"]; !ok { switch n.(type) { case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: enable["implements"] = true } }
// For pointsto and whicherrs, we approximate findInterestingNode.
if _, ok := enable["pointsto"]; !ok { switch n.(type) { case ast.Stmt, *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: // not an expression
enable["pointsto"] = false enable["whicherrs"] = false
case ast.Expr, ast.Decl, *ast.ValueSpec: // an expression, maybe
enable["pointsto"] = true enable["whicherrs"] = true
default: // Comment, Field, KeyValueExpr, etc: ascend.
} } }
// If we don't have an exact selection, disable modes that need one.
if !qpos.exact { enable["callees"] = false enable["pointsto"] = false enable["whicherrs"] = false enable["describe"] = false }
var modes []string for mode := range enable { modes = append(modes, mode) } sort.Strings(modes)
// Find the object referred to by the selection (if it's an
// identifier) and report the position of each identifier
// that refers to the same object.
//
// This may return spurious matches (e.g. struct fields) because
// it uses the best-effort name resolution done by go/parser.
var sameids []token.Pos var object string if id, ok := qpos.path[0].(*ast.Ident); ok { if id.Obj == nil { // An unresolved identifier is potentially a package name.
// Resolve them with a simple importer (adds ~100µs).
importer := func(imports map[string]*ast.Object, path string) (*ast.Object, error) { pkg, ok := imports[path] if !ok { pkg = &ast.Object{ Kind: ast.Pkg, Name: filepath.Base(path), // a guess
} imports[path] = pkg } return pkg, nil } f := qpos.path[len(qpos.path)-1].(*ast.File) ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil) }
if id.Obj != nil { object = id.Obj.Name decl := qpos.path[len(qpos.path)-1] ast.Inspect(decl, func(n ast.Node) bool { if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj { sameids = append(sameids, n.Pos()) } return true }) } }
q.Output(qpos.fset, &whatResult{ path: qpos.path, srcdir: srcdir, importPath: importPath, modes: modes, object: object, sameids: sameids, }) return nil }
// guessImportPath finds the package containing filename, and returns
// its source directory (an element of $GOPATH) and its import path
// relative to it.
//
// TODO(adonovan): what about _test.go files that are not part of the
// package?
//
func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) { absFile, err := filepath.Abs(filename) if err != nil { return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err) }
absFileDir := filepath.Dir(absFile) resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir) if err != nil { return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err) }
segmentedAbsFileDir := segments(resolvedAbsFileDir) // Find the innermost directory in $GOPATH that encloses filename.
minD := 1024 for _, gopathDir := range buildContext.SrcDirs() { absDir, err := filepath.Abs(gopathDir) if err != nil { continue // e.g. non-existent dir on $GOPATH
} resolvedAbsDir, err := filepath.EvalSymlinks(absDir) if err != nil { continue // e.g. non-existent dir on $GOPATH
}
d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir) // If there are multiple matches,
// prefer the innermost enclosing directory
// (smallest d).
if d >= 0 && d < minD { minD = d srcdir = gopathDir importPath = strings.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:], string(os.PathSeparator)) } } if srcdir == "" { return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s", filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", ")) } if importPath == "" { // This happens for e.g. $GOPATH/src/a.go, but
// "" is not a valid path for (*go/build).Import.
return "", "", fmt.Errorf("cannot load package in root of source directory %s", srcdir) } return srcdir, importPath, nil }
func segments(path string) []string { return strings.Split(path, string(os.PathSeparator)) }
// prefixLen returns the length of the remainder of y if x is a prefix
// of y, a negative number otherwise.
func prefixLen(x, y []string) int { d := len(y) - len(x) if d >= 0 { for i := range x { if y[i] != x[i] { return -1 // not a prefix
} } } return d }
type whatResult struct { path []ast.Node modes []string srcdir string importPath string object string sameids []token.Pos }
func (r *whatResult) PrintPlain(printf printfFunc) { for _, n := range r.path { printf(n, "%s", astutil.NodeDescription(n)) } printf(nil, "modes: %s", r.modes) printf(nil, "srcdir: %s", r.srcdir) printf(nil, "import path: %s", r.importPath) for _, pos := range r.sameids { printf(pos, "%s", r.object) } }
func (r *whatResult) JSON(fset *token.FileSet) []byte { var enclosing []serial.SyntaxNode for _, n := range r.path { enclosing = append(enclosing, serial.SyntaxNode{ Description: astutil.NodeDescription(n), Start: fset.Position(n.Pos()).Offset, End: fset.Position(n.End()).Offset, }) }
var sameids []string for _, pos := range r.sameids { sameids = append(sameids, fset.Position(pos).String()) }
return toJSON(&serial.What{ Modes: r.modes, SrcDir: r.srcdir, ImportPath: r.importPath, Enclosing: enclosing, Object: r.object, SameIDs: sameids, }) }
|