+++ /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,
- })
-}