github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/oracle/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 oracle
     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/go/ast/astutil"
    18  	"golang.org/x/tools/oracle/serial"
    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.Pos)
    29  	if err != nil {
    30  		return err
    31  	}
    32  	q.Fset = qpos.fset
    33  
    34  	// (ignore errors)
    35  	srcdir, importPath, _ := guessImportPath(q.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, 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  				enable["pointsto"] = false // not an expr
    89  
    90  			case ast.Expr, ast.Decl, *ast.ValueSpec:
    91  				enable["pointsto"] = true // an expr, maybe
    92  
    93  			default:
    94  				// Comment, Field, KeyValueExpr, etc: ascend.
    95  			}
    96  		}
    97  	}
    98  
    99  	// If we don't have an exact selection, disable modes that need one.
   100  	if !qpos.exact {
   101  		enable["callees"] = false
   102  		enable["pointsto"] = false
   103  		enable["whicherrs"] = false
   104  		enable["describe"] = false
   105  	}
   106  
   107  	var modes []string
   108  	for mode := range enable {
   109  		modes = append(modes, mode)
   110  	}
   111  	sort.Strings(modes)
   112  
   113  	q.result = &whatResult{
   114  		path:       qpos.path,
   115  		srcdir:     srcdir,
   116  		importPath: importPath,
   117  		modes:      modes,
   118  	}
   119  	return nil
   120  }
   121  
   122  // guessImportPath finds the package containing filename, and returns
   123  // its source directory (an element of $GOPATH) and its import path
   124  // relative to it.
   125  //
   126  // TODO(adonovan): what about _test.go files that are not part of the
   127  // package?
   128  //
   129  func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
   130  	absFile, err := filepath.Abs(filename)
   131  	if err != nil {
   132  		err = fmt.Errorf("can't form absolute path of %s", filename)
   133  		return
   134  	}
   135  	absFileDir := segments(filepath.Dir(absFile))
   136  
   137  	// Find the innermost directory in $GOPATH that encloses filename.
   138  	minD := 1024
   139  	for _, gopathDir := range buildContext.SrcDirs() {
   140  		absDir, err := filepath.Abs(gopathDir)
   141  		if err != nil {
   142  			continue // e.g. non-existent dir on $GOPATH
   143  		}
   144  		d := prefixLen(segments(absDir), absFileDir)
   145  		// If there are multiple matches,
   146  		// prefer the innermost enclosing directory
   147  		// (smallest d).
   148  		if d >= 0 && d < minD {
   149  			minD = d
   150  			srcdir = gopathDir
   151  			importPath = strings.Join(absFileDir[len(absFileDir)-minD:], string(os.PathSeparator))
   152  		}
   153  	}
   154  	if srcdir == "" {
   155  		err = fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
   156  			filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
   157  	}
   158  	return
   159  }
   160  
   161  func segments(path string) []string {
   162  	return strings.Split(path, string(os.PathSeparator))
   163  }
   164  
   165  // prefixLen returns the length of the remainder of y if x is a prefix
   166  // of y, a negative number otherwise.
   167  func prefixLen(x, y []string) int {
   168  	d := len(y) - len(x)
   169  	if d >= 0 {
   170  		for i := range x {
   171  			if y[i] != x[i] {
   172  				return -1 // not a prefix
   173  			}
   174  		}
   175  	}
   176  	return d
   177  }
   178  
   179  type whatResult struct {
   180  	path       []ast.Node
   181  	modes      []string
   182  	srcdir     string
   183  	importPath string
   184  }
   185  
   186  func (r *whatResult) display(printf printfFunc) {
   187  	for _, n := range r.path {
   188  		printf(n, "%s", astutil.NodeDescription(n))
   189  	}
   190  	printf(nil, "modes: %s", r.modes)
   191  	printf(nil, "srcdir: %s", r.srcdir)
   192  	printf(nil, "import path: %s", r.importPath)
   193  }
   194  
   195  func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) {
   196  	var enclosing []serial.SyntaxNode
   197  	for _, n := range r.path {
   198  		enclosing = append(enclosing, serial.SyntaxNode{
   199  			Description: astutil.NodeDescription(n),
   200  			Start:       fset.Position(n.Pos()).Offset,
   201  			End:         fset.Position(n.End()).Offset,
   202  		})
   203  	}
   204  	res.What = &serial.What{
   205  		Modes:      r.modes,
   206  		SrcDir:     r.srcdir,
   207  		ImportPath: r.importPath,
   208  		Enclosing:  enclosing,
   209  	}
   210  }