211 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| // 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 oracle
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/build"
 | |
| 	"go/token"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 
 | |
| 	"golang.org/x/tools/go/ast/astutil"
 | |
| 	"golang.org/x/tools/oracle/serial"
 | |
| )
 | |
| 
 | |
| // 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.Pos)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	q.Fset = qpos.fset
 | |
| 
 | |
| 	// (ignore errors)
 | |
| 	srcdir, importPath, _ := guessImportPath(q.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, 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:
 | |
| 				enable["pointsto"] = false // not an expr
 | |
| 
 | |
| 			case ast.Expr, ast.Decl, *ast.ValueSpec:
 | |
| 				enable["pointsto"] = true // an expr, maybe
 | |
| 
 | |
| 			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)
 | |
| 
 | |
| 	q.result = &whatResult{
 | |
| 		path:       qpos.path,
 | |
| 		srcdir:     srcdir,
 | |
| 		importPath: importPath,
 | |
| 		modes:      modes,
 | |
| 	}
 | |
| 	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 {
 | |
| 		err = fmt.Errorf("can't form absolute path of %s", filename)
 | |
| 		return
 | |
| 	}
 | |
| 	absFileDir := segments(filepath.Dir(absFile))
 | |
| 
 | |
| 	// 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
 | |
| 		}
 | |
| 		d := prefixLen(segments(absDir), absFileDir)
 | |
| 		// If there are multiple matches,
 | |
| 		// prefer the innermost enclosing directory
 | |
| 		// (smallest d).
 | |
| 		if d >= 0 && d < minD {
 | |
| 			minD = d
 | |
| 			srcdir = gopathDir
 | |
| 			importPath = strings.Join(absFileDir[len(absFileDir)-minD:], string(os.PathSeparator))
 | |
| 		}
 | |
| 	}
 | |
| 	if srcdir == "" {
 | |
| 		err = fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
 | |
| 			filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| func (r *whatResult) display(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)
 | |
| }
 | |
| 
 | |
| func (r *whatResult) toSerial(res *serial.Result, fset *token.FileSet) {
 | |
| 	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,
 | |
| 		})
 | |
| 	}
 | |
| 	res.What = &serial.What{
 | |
| 		Modes:      r.modes,
 | |
| 		SrcDir:     r.srcdir,
 | |
| 		ImportPath: r.importPath,
 | |
| 		Enclosing:  enclosing,
 | |
| 	}
 | |
| }
 |