226 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			5.6 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 (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/token"
 | |
| 	"io/ioutil"
 | |
| 	"sort"
 | |
| 
 | |
| 	"golang.org/x/tools/go/loader"
 | |
| 	"golang.org/x/tools/go/types"
 | |
| 	"golang.org/x/tools/oracle/serial"
 | |
| 	"golang.org/x/tools/refactor/importgraph"
 | |
| )
 | |
| 
 | |
| // Referrers reports all identifiers that resolve to the same object
 | |
| // as the queried identifier, within any package in the analysis scope.
 | |
| func referrers(q *Query) error {
 | |
| 	lconf := loader.Config{Build: q.Build}
 | |
| 	allowErrors(&lconf)
 | |
| 
 | |
| 	if err := importQueryPackage(q.Pos, &lconf); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	var id *ast.Ident
 | |
| 	var obj types.Object
 | |
| 	var lprog *loader.Program
 | |
| 	var pass2 bool
 | |
| 	var qpos *queryPos
 | |
| 	for {
 | |
| 		// Load/parse/type-check the program.
 | |
| 		var err error
 | |
| 		lprog, err = lconf.Load()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		q.Fset = lprog.Fset
 | |
| 
 | |
| 		qpos, err = parseQueryPos(lprog, q.Pos, false)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		id, _ = qpos.path[0].(*ast.Ident)
 | |
| 		if id == nil {
 | |
| 			return fmt.Errorf("no identifier here")
 | |
| 		}
 | |
| 
 | |
| 		obj = qpos.info.ObjectOf(id)
 | |
| 		if obj == nil {
 | |
| 			// Happens for y in "switch y := x.(type)",
 | |
| 			// the package declaration,
 | |
| 			// and unresolved identifiers.
 | |
| 			if _, ok := qpos.path[1].(*ast.File); ok { // package decl?
 | |
| 				pkg := qpos.info.Pkg
 | |
| 				obj = types.NewPkgName(id.Pos(), pkg, pkg.Name(), pkg)
 | |
| 			} else {
 | |
| 				return fmt.Errorf("no object for identifier: %T", qpos.path[1])
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if pass2 {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		// If the identifier is exported, we must load all packages that
 | |
| 		// depend transitively upon the package that defines it.
 | |
| 		// Treat PkgNames as exported, even though they're lowercase.
 | |
| 		if _, isPkg := obj.(*types.PkgName); !(isPkg || obj.Exported()) {
 | |
| 			break // not exported
 | |
| 		}
 | |
| 
 | |
| 		// Scan the workspace and build the import graph.
 | |
| 		// Ignore broken packages.
 | |
| 		_, rev, _ := importgraph.Build(q.Build)
 | |
| 
 | |
| 		// Re-load the larger program.
 | |
| 		// Create a new file set so that ...
 | |
| 		// External test packages are never imported,
 | |
| 		// so they will never appear in the graph.
 | |
| 		// (We must reset the Config here, not just reset the Fset field.)
 | |
| 		lconf = loader.Config{
 | |
| 			Fset:  token.NewFileSet(),
 | |
| 			Build: q.Build,
 | |
| 		}
 | |
| 		allowErrors(&lconf)
 | |
| 		for path := range rev.Search(obj.Pkg().Path()) {
 | |
| 			lconf.ImportWithTests(path)
 | |
| 		}
 | |
| 		pass2 = true
 | |
| 	}
 | |
| 
 | |
| 	// Iterate over all go/types' Uses facts for the entire program.
 | |
| 	var refs []*ast.Ident
 | |
| 	for _, info := range lprog.AllPackages {
 | |
| 		for id2, obj2 := range info.Uses {
 | |
| 			if sameObj(obj, obj2) {
 | |
| 				refs = append(refs, id2)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	sort.Sort(byNamePos{q.Fset, refs})
 | |
| 
 | |
| 	q.result = &referrersResult{
 | |
| 		qpos:  qpos,
 | |
| 		query: id,
 | |
| 		obj:   obj,
 | |
| 		refs:  refs,
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // same reports whether x and y are identical, or both are PkgNames
 | |
| // that import the same Package.
 | |
| //
 | |
| func sameObj(x, y types.Object) bool {
 | |
| 	if x == y {
 | |
| 		return true
 | |
| 	}
 | |
| 	if x, ok := x.(*types.PkgName); ok {
 | |
| 		if y, ok := y.(*types.PkgName); ok {
 | |
| 			return x.Imported() == y.Imported()
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // -------- utils --------
 | |
| 
 | |
| // An deterministic ordering for token.Pos that doesn't
 | |
| // depend on the order in which packages were loaded.
 | |
| func lessPos(fset *token.FileSet, x, y token.Pos) bool {
 | |
| 	fx := fset.File(x)
 | |
| 	fy := fset.File(y)
 | |
| 	if fx != fy {
 | |
| 		return fx.Name() < fy.Name()
 | |
| 	}
 | |
| 	return x < y
 | |
| }
 | |
| 
 | |
| type byNamePos struct {
 | |
| 	fset *token.FileSet
 | |
| 	ids  []*ast.Ident
 | |
| }
 | |
| 
 | |
| func (p byNamePos) Len() int      { return len(p.ids) }
 | |
| func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] }
 | |
| func (p byNamePos) Less(i, j int) bool {
 | |
| 	return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos)
 | |
| }
 | |
| 
 | |
| type referrersResult struct {
 | |
| 	qpos  *queryPos
 | |
| 	query *ast.Ident   // identifier of query
 | |
| 	obj   types.Object // object it denotes
 | |
| 	refs  []*ast.Ident // set of all other references to it
 | |
| }
 | |
| 
 | |
| func (r *referrersResult) display(printf printfFunc) {
 | |
| 	printf(r.obj, "%d references to %s", len(r.refs), r.qpos.objectString(r.obj))
 | |
| 
 | |
| 	// Show referring lines, like grep.
 | |
| 	type fileinfo struct {
 | |
| 		refs     []*ast.Ident
 | |
| 		linenums []int       // line number of refs[i]
 | |
| 		data     chan []byte // file contents
 | |
| 	}
 | |
| 	var fileinfos []*fileinfo
 | |
| 	fileinfosByName := make(map[string]*fileinfo)
 | |
| 
 | |
| 	// First pass: start the file reads concurrently.
 | |
| 	for _, ref := range r.refs {
 | |
| 		posn := r.qpos.fset.Position(ref.Pos())
 | |
| 		fi := fileinfosByName[posn.Filename]
 | |
| 		if fi == nil {
 | |
| 			fi = &fileinfo{data: make(chan []byte)}
 | |
| 			fileinfosByName[posn.Filename] = fi
 | |
| 			fileinfos = append(fileinfos, fi)
 | |
| 
 | |
| 			// First request for this file:
 | |
| 			// start asynchronous read.
 | |
| 			go func() {
 | |
| 				content, err := ioutil.ReadFile(posn.Filename)
 | |
| 				if err != nil {
 | |
| 					content = []byte(fmt.Sprintf("error: %v", err))
 | |
| 				}
 | |
| 				fi.data <- content
 | |
| 			}()
 | |
| 		}
 | |
| 		fi.refs = append(fi.refs, ref)
 | |
| 		fi.linenums = append(fi.linenums, posn.Line)
 | |
| 	}
 | |
| 
 | |
| 	// Second pass: print refs in original order.
 | |
| 	// One line may have several refs at different columns.
 | |
| 	for _, fi := range fileinfos {
 | |
| 		content := <-fi.data // wait for I/O completion
 | |
| 		lines := bytes.Split(content, []byte("\n"))
 | |
| 		for i, ref := range fi.refs {
 | |
| 			printf(ref, "%s", lines[fi.linenums[i]-1])
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TODO(adonovan): encode extent, not just Pos info, in Serial form.
 | |
| 
 | |
| func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) {
 | |
| 	referrers := &serial.Referrers{
 | |
| 		Pos:  fset.Position(r.query.Pos()).String(),
 | |
| 		Desc: r.obj.String(),
 | |
| 	}
 | |
| 	if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
 | |
| 		referrers.ObjPos = fset.Position(pos).String()
 | |
| 	}
 | |
| 	for _, ref := range r.refs {
 | |
| 		referrers.Refs = append(referrers.Refs, fset.Position(ref.NamePos).String())
 | |
| 	}
 | |
| 	res.Referrers = referrers
 | |
| }
 |