github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/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/filepath"
    14  	"sort"
    15  	"strings"
    16  
    17  	"golang.org/x/tools/cmd/guru/serial"
    18  	"golang.org/x/tools/go/ast/astutil"
    19  )
    20  
    21  // what reports all the information about the query selection that can be
    22  // obtained from parsing only its containing source file.
    23  // It is intended to be a very low-latency query callable from GUI
    24  // tools, e.g. to populate a menu of options of slower queries about
    25  // the selected location.
    26  //
    27  func what(q *Query) error {
    28  	qpos, err := fastQueryPos(q.Build, q.Pos)
    29  	if err != nil {
    30  		return err
    31  	}
    32  
    33  	// (ignore errors)
    34  	srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build)
    35  
    36  	// Determine which query modes are applicable to the selection.
    37  	enable := map[string]bool{
    38  		"describe": true, // any syntax; always enabled
    39  	}
    40  
    41  	if qpos.end > qpos.start {
    42  		enable["freevars"] = true // nonempty selection?
    43  	}
    44  
    45  	for _, n := range qpos.path {
    46  		switch n := n.(type) {
    47  		case *ast.Ident:
    48  			enable["definition"] = true
    49  			enable["referrers"] = true
    50  			enable["implements"] = true
    51  		case *ast.CallExpr:
    52  			enable["callees"] = true
    53  		case *ast.FuncDecl:
    54  			enable["callers"] = true
    55  			enable["callstack"] = true
    56  		case *ast.SendStmt:
    57  			enable["peers"] = true
    58  		case *ast.UnaryExpr:
    59  			if n.Op == token.ARROW {
    60  				enable["peers"] = true
    61  			}
    62  		}
    63  
    64  		// For implements, we approximate findInterestingNode.
    65  		if _, ok := enable["implements"]; !ok {
    66  			switch n.(type) {
    67  			case *ast.ArrayType,
    68  				*ast.StructType,
    69  				*ast.FuncType,
    70  				*ast.InterfaceType,
    71  				*ast.MapType,
    72  				*ast.ChanType:
    73  				enable["implements"] = true
    74  			}
    75  		}
    76  
    77  		// For pointsto and whicherrs, we approximate findInterestingNode.
    78  		if _, ok := enable["pointsto"]; !ok {
    79  			switch n.(type) {
    80  			case ast.Stmt,
    81  				*ast.ArrayType,
    82  				*ast.StructType,
    83  				*ast.FuncType,
    84  				*ast.InterfaceType,
    85  				*ast.MapType,
    86  				*ast.ChanType:
    87  				// not an expression
    88  				enable["pointsto"] = false
    89  				enable["whicherrs"] = false
    90  
    91  			case ast.Expr, ast.Decl, *ast.ValueSpec:
    92  				// an expression, maybe
    93  				enable["pointsto"] = true
    94  				enable["whicherrs"] = true
    95  
    96  			default:
    97  				// Comment, Field, KeyValueExpr, etc: ascend.
    98  			}
    99  		}
   100  	}
   101  
   102  	// If we don't have an exact selection, disable modes that need one.
   103  	if !qpos.exact {
   104  		enable["callees"] = false
   105  		enable["pointsto"] = false
   106  		enable["whicherrs"] = false
   107  		enable["describe"] = false
   108  	}
   109  
   110  	var modes []string
   111  	for mode := range enable {
   112  		modes = append(modes, mode)
   113  	}
   114  	sort.Strings(modes)
   115  
   116  	// Find the object referred to by the selection (if it's an
   117  	// identifier) and report the position of each identifier
   118  	// that refers to the same object.
   119  	//
   120  	// This may return spurious matches (e.g. struct fields) because
   121  	// it uses the best-effort name resolution done by go/parser.
   122  	var sameids []token.Pos
   123  	var object string
   124  	if id, ok := qpos.path[0].(*ast.Ident); ok {
   125  		if id.Obj == nil {
   126  			// An unresolved identifier is potentially a package name.
   127  			// Resolve them with a simple importer (adds ~100µs).
   128  			importer := func(imports map[string]*ast.Object, path string) (*ast.Object, error) {
   129  				pkg, ok := imports[path]
   130  				if !ok {
   131  					pkg = &ast.Object{
   132  						Kind: ast.Pkg,
   133  						Name: filepath.Base(path), // a guess
   134  					}
   135  					imports[path] = pkg
   136  				}
   137  				return pkg, nil
   138  			}
   139  			f := qpos.path[len(qpos.path)-1].(*ast.File)
   140  			ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil)
   141  		}
   142  
   143  		if id.Obj != nil {
   144  			object = id.Obj.Name
   145  			decl := qpos.path[len(qpos.path)-1]
   146  			ast.Inspect(decl, func(n ast.Node) bool {
   147  				if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj {
   148  					sameids = append(sameids, n.Pos())
   149  				}
   150  				return true
   151  			})
   152  		}
   153  	}
   154  
   155  	q.Output(qpos.fset, &whatResult{
   156  		path:       qpos.path,
   157  		srcdir:     srcdir,
   158  		importPath: importPath,
   159  		modes:      modes,
   160  		object:     object,
   161  		sameids:    sameids,
   162  	})
   163  	return nil
   164  }
   165  
   166  // guessImportPath finds the package containing filename, and returns
   167  // its source directory (an element of $GOPATH) and its import path
   168  // relative to it.
   169  //
   170  // TODO(adonovan): what about _test.go files that are not part of the
   171  // package?
   172  //
   173  func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
   174  	absFile, err := filepath.Abs(filename)
   175  	if err != nil {
   176  		return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err)
   177  	}
   178  
   179  	absFileDir := filepath.Dir(absFile)
   180  	resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir)
   181  	if err != nil {
   182  		return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err)
   183  	}
   184  
   185  	segmentedAbsFileDir := segments(resolvedAbsFileDir)
   186  	// Find the innermost directory in $GOPATH that encloses filename.
   187  	minD := 1024
   188  	for _, gopathDir := range buildContext.SrcDirs() {
   189  		absDir, err := filepath.Abs(gopathDir)
   190  		if err != nil {
   191  			continue // e.g. non-existent dir on $GOPATH
   192  		}
   193  		resolvedAbsDir, err := filepath.EvalSymlinks(absDir)
   194  		if err != nil {
   195  			continue // e.g. non-existent dir on $GOPATH
   196  		}
   197  
   198  		d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir)
   199  		// If there are multiple matches,
   200  		// prefer the innermost enclosing directory
   201  		// (smallest d).
   202  		if d >= 0 && d < minD {
   203  			minD = d
   204  			srcdir = gopathDir
   205  			importPath = strings.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:], string(os.PathSeparator))
   206  		}
   207  	}
   208  	if srcdir == "" {
   209  		return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
   210  			filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
   211  	}
   212  	if importPath == "" {
   213  		// This happens for e.g. $GOPATH/src/a.go, but
   214  		// "" is not a valid path for (*go/build).Import.
   215  		return "", "", fmt.Errorf("cannot load package in root of source directory %s", srcdir)
   216  	}
   217  	return srcdir, importPath, nil
   218  }
   219  
   220  func segments(path string) []string {
   221  	return strings.Split(path, string(os.PathSeparator))
   222  }
   223  
   224  // prefixLen returns the length of the remainder of y if x is a prefix
   225  // of y, a negative number otherwise.
   226  func prefixLen(x, y []string) int {
   227  	d := len(y) - len(x)
   228  	if d >= 0 {
   229  		for i := range x {
   230  			if y[i] != x[i] {
   231  				return -1 // not a prefix
   232  			}
   233  		}
   234  	}
   235  	return d
   236  }
   237  
   238  type whatResult struct {
   239  	path       []ast.Node
   240  	modes      []string
   241  	srcdir     string
   242  	importPath string
   243  	object     string
   244  	sameids    []token.Pos
   245  }
   246  
   247  func (r *whatResult) PrintPlain(printf printfFunc) {
   248  	for _, n := range r.path {
   249  		printf(n, "%s", astutil.NodeDescription(n))
   250  	}
   251  	printf(nil, "modes: %s", r.modes)
   252  	printf(nil, "srcdir: %s", r.srcdir)
   253  	printf(nil, "import path: %s", r.importPath)
   254  	for _, pos := range r.sameids {
   255  		printf(pos, "%s", r.object)
   256  	}
   257  }
   258  
   259  func (r *whatResult) JSON(fset *token.FileSet) []byte {
   260  	var enclosing []serial.SyntaxNode
   261  	for _, n := range r.path {
   262  		enclosing = append(enclosing, serial.SyntaxNode{
   263  			Description: astutil.NodeDescription(n),
   264  			Start:       fset.Position(n.Pos()).Offset,
   265  			End:         fset.Position(n.End()).Offset,
   266  		})
   267  	}
   268  
   269  	var sameids []string
   270  	for _, pos := range r.sameids {
   271  		sameids = append(sameids, fset.Position(pos).String())
   272  	}
   273  
   274  	return toJSON(&serial.What{
   275  		Modes:      r.modes,
   276  		SrcDir:     r.srcdir,
   277  		ImportPath: r.importPath,
   278  		Enclosing:  enclosing,
   279  		Object:     r.object,
   280  		SameIDs:    sameids,
   281  	})
   282  }