244 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			6.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.
 | 
						|
 | 
						|
// +build !go1.5
 | 
						|
 | 
						|
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 interface{} // file contents or error
 | 
						|
	}
 | 
						|
	var fileinfos []*fileinfo
 | 
						|
	fileinfosByName := make(map[string]*fileinfo)
 | 
						|
 | 
						|
	// First pass: start the file reads concurrently.
 | 
						|
	sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
 | 
						|
	for _, ref := range r.refs {
 | 
						|
		posn := r.qpos.fset.Position(ref.Pos())
 | 
						|
		fi := fileinfosByName[posn.Filename]
 | 
						|
		if fi == nil {
 | 
						|
			fi = &fileinfo{data: make(chan interface{})}
 | 
						|
			fileinfosByName[posn.Filename] = fi
 | 
						|
			fileinfos = append(fileinfos, fi)
 | 
						|
 | 
						|
			// First request for this file:
 | 
						|
			// start asynchronous read.
 | 
						|
			go func() {
 | 
						|
				sema <- struct{}{} // acquire token
 | 
						|
				content, err := ioutil.ReadFile(posn.Filename)
 | 
						|
				<-sema // release token
 | 
						|
				if err != nil {
 | 
						|
					fi.data <- err
 | 
						|
				} else {
 | 
						|
					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 {
 | 
						|
		v := <-fi.data // wait for I/O completion
 | 
						|
 | 
						|
		// Print one item for all refs in a file that could not
 | 
						|
		// be loaded (perhaps due to //line directives).
 | 
						|
		if err, ok := v.(error); ok {
 | 
						|
			var suffix string
 | 
						|
			if more := len(fi.refs) - 1; more > 0 {
 | 
						|
				suffix = fmt.Sprintf(" (+ %d more refs in this file)", more)
 | 
						|
			}
 | 
						|
			printf(fi.refs[0], "%v%s", err, suffix)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		lines := bytes.Split(v.([]byte), []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
 | 
						|
}
 |