mirror of
https://github.com/golang/go
synced 2024-11-18 14:14:46 -07:00
2477c0d578
This change will ensure that the tree continues to work with go1.4.1. All files continue to depend on golang.org/x/tools/go/types, but in a follow-up change, I will switch the primary files to depend on the standard go/types package. Another (smaller) set of files will be forked and tagged, this time !1.6, due to API differences between the two packages. All tests pass using 1.4.1, 1.5, and ~1.6 (tip). Change-Id: Ifd75a6330e120957d646be91693daaba1ce0e8c9 Reviewed-on: https://go-review.googlesource.com/18333 Reviewed-by: Robert Griesemer <gri@golang.org>
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
|
|
}
|