--- /dev/null
+// 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"
+ "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 = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
+ }
+ }
+ 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,
+ })
+}