github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/what.go (about)

     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  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/build"
    11  	"go/token"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"sort"
    16  	"strings"
    17  
    18  	"github.com/april1989/origin-go-tools/cmd/guru/serial"
    19  	"github.com/april1989/origin-go-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.
    27  //
    28  func what(q *Query) error {
    29  	qpos, err := fastQueryPos(q.Build, q.Pos)
    30  	if err != nil {
    31  		return err
    32  	}
    33  
    34  	// (ignore errors)
    35  	srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build)
    36  
    37  	// Determine which query modes are applicable to the selection.
    38  	enable := map[string]bool{
    39  		"describe": true, // any syntax; always enabled
    40  	}
    41  
    42  	if qpos.end > qpos.start {
    43  		enable["freevars"] = true // nonempty selection?
    44  	}
    45  
    46  	for _, n := range qpos.path {
    47  		switch n := n.(type) {
    48  		case *ast.Ident:
    49  			enable["definition"] = true
    50  			enable["referrers"] = true
    51  			enable["implements"] = true
    52  		case *ast.CallExpr:
    53  			enable["callees"] = true
    54  		case *ast.FuncDecl:
    55  			enable["callers"] = true
    56  			enable["callstack"] = true
    57  		case *ast.SendStmt:
    58  			enable["peers"] = true
    59  		case *ast.UnaryExpr:
    60  			if n.Op == token.ARROW {
    61  				enable["peers"] = true
    62  			}
    63  		}
    64  
    65  		// For implements, we approximate findInterestingNode.
    66  		if _, ok := enable["implements"]; !ok {
    67  			switch n.(type) {
    68  			case *ast.ArrayType,
    69  				*ast.StructType,
    70  				*ast.FuncType,
    71  				*ast.InterfaceType,
    72  				*ast.MapType,
    73  				*ast.ChanType:
    74  				enable["implements"] = true
    75  			}
    76  		}
    77  
    78  		// For pointsto and whicherrs, we approximate findInterestingNode.
    79  		if _, ok := enable["pointsto"]; !ok {
    80  			switch n.(type) {
    81  			case ast.Stmt,
    82  				*ast.ArrayType,
    83  				*ast.StructType,
    84  				*ast.FuncType,
    85  				*ast.InterfaceType,
    86  				*ast.MapType,
    87  				*ast.ChanType:
    88  				// not an expression
    89  				enable["pointsto"] = false
    90  				enable["whicherrs"] = false
    91  
    92  			case ast.Expr, ast.Decl, *ast.ValueSpec:
    93  				// an expression, maybe
    94  				enable["pointsto"] = true
    95  				enable["whicherrs"] = true
    96  
    97  			default:
    98  				// Comment, Field, KeyValueExpr, etc: ascend.
    99  			}
   100  		}
   101  	}
   102  
   103  	// If we don't have an exact selection, disable modes that need one.
   104  	if !qpos.exact {
   105  		enable["callees"] = false
   106  		enable["pointsto"] = false
   107  		enable["whicherrs"] = false
   108  		enable["describe"] = false
   109  	}
   110  
   111  	var modes []string
   112  	for mode := range enable {
   113  		modes = append(modes, mode)
   114  	}
   115  	sort.Strings(modes)
   116  
   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.
   120  	//
   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
   124  	var object string
   125  	if id, ok := qpos.path[0].(*ast.Ident); ok {
   126  		if id.Obj == nil {
   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]
   131  				if !ok {
   132  					pkg = &ast.Object{
   133  						Kind: ast.Pkg,
   134  						Name: filepath.Base(path), // a guess
   135  					}
   136  					imports[path] = pkg
   137  				}
   138  				return pkg, nil
   139  			}
   140  			f := qpos.path[len(qpos.path)-1].(*ast.File)
   141  			ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil)
   142  		}
   143  
   144  		if id.Obj != nil {
   145  			object = id.Obj.Name
   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())
   150  				}
   151  				return true
   152  			})
   153  		}
   154  	}
   155  
   156  	q.Output(qpos.fset, &whatResult{
   157  		path:       qpos.path,
   158  		srcdir:     srcdir,
   159  		importPath: importPath,
   160  		modes:      modes,
   161  		object:     object,
   162  		sameids:    sameids,
   163  	})
   164  	return nil
   165  }
   166  
   167  // guessImportPath finds the package containing filename, and returns
   168  // its source directory (an element of $GOPATH) and its import path
   169  // relative to it.
   170  //
   171  // TODO(adonovan): what about _test.go files that are not part of the
   172  // package?
   173  //
   174  func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
   175  	absFile, err := filepath.Abs(filename)
   176  	if err != nil {
   177  		return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err)
   178  	}
   179  
   180  	absFileDir := filepath.Dir(absFile)
   181  	resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir)
   182  	if err != nil {
   183  		return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err)
   184  	}
   185  
   186  	segmentedAbsFileDir := segments(resolvedAbsFileDir)
   187  	// Find the innermost directory in $GOPATH that encloses filename.
   188  	minD := 1024
   189  	for _, gopathDir := range buildContext.SrcDirs() {
   190  		absDir, err := filepath.Abs(gopathDir)
   191  		if err != nil {
   192  			continue // e.g. non-existent dir on $GOPATH
   193  		}
   194  		resolvedAbsDir, err := filepath.EvalSymlinks(absDir)
   195  		if err != nil {
   196  			continue // e.g. non-existent dir on $GOPATH
   197  		}
   198  
   199  		d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir)
   200  		// If there are multiple matches,
   201  		// prefer the innermost enclosing directory
   202  		// (smallest d).
   203  		if d >= 0 && d < minD {
   204  			minD = d
   205  			srcdir = gopathDir
   206  			importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
   207  		}
   208  	}
   209  	if srcdir == "" {
   210  		return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
   211  			filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
   212  	}
   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)
   217  	}
   218  	return srcdir, importPath, nil
   219  }
   220  
   221  func segments(path string) []string {
   222  	return strings.Split(path, string(os.PathSeparator))
   223  }
   224  
   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 {
   228  	d := len(y) - len(x)
   229  	if d >= 0 {
   230  		for i := range x {
   231  			if y[i] != x[i] {
   232  				return -1 // not a prefix
   233  			}
   234  		}
   235  	}
   236  	return d
   237  }
   238  
   239  type whatResult struct {
   240  	path       []ast.Node
   241  	modes      []string
   242  	srcdir     string
   243  	importPath string
   244  	object     string
   245  	sameids    []token.Pos
   246  }
   247  
   248  func (r *whatResult) PrintPlain(printf printfFunc) {
   249  	for _, n := range r.path {
   250  		printf(n, "%s", astutil.NodeDescription(n))
   251  	}
   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)
   257  	}
   258  }
   259  
   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,
   267  		})
   268  	}
   269  
   270  	var sameids []string
   271  	for _, pos := range r.sameids {
   272  		sameids = append(sameids, fset.Position(pos).String())
   273  	}
   274  
   275  	return toJSON(&serial.What{
   276  		Modes:      r.modes,
   277  		SrcDir:     r.srcdir,
   278  		ImportPath: r.importPath,
   279  		Enclosing:  enclosing,
   280  		Object:     r.object,
   281  		SameIDs:    sameids,
   282  	})
   283  }