github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/oracle/referrers.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  // +build go1.5
     6  
     7  package oracle
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/token"
    14  	"go/types"
    15  	"io/ioutil"
    16  	"sort"
    17  
    18  	"golang.org/x/tools/go/loader"
    19  	"golang.org/x/tools/oracle/serial"
    20  	"golang.org/x/tools/refactor/importgraph"
    21  )
    22  
    23  // Referrers reports all identifiers that resolve to the same object
    24  // as the queried identifier, within any package in the analysis scope.
    25  func referrers(q *Query) error {
    26  	lconf := loader.Config{Build: q.Build}
    27  	allowErrors(&lconf)
    28  
    29  	if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
    30  		return err
    31  	}
    32  
    33  	var id *ast.Ident
    34  	var obj types.Object
    35  	var lprog *loader.Program
    36  	var pass2 bool
    37  	var qpos *queryPos
    38  	for {
    39  		// Load/parse/type-check the program.
    40  		var err error
    41  		lprog, err = lconf.Load()
    42  		if err != nil {
    43  			return err
    44  		}
    45  		q.Fset = lprog.Fset
    46  
    47  		qpos, err = parseQueryPos(lprog, q.Pos, false)
    48  		if err != nil {
    49  			return err
    50  		}
    51  
    52  		id, _ = qpos.path[0].(*ast.Ident)
    53  		if id == nil {
    54  			return fmt.Errorf("no identifier here")
    55  		}
    56  
    57  		obj = qpos.info.ObjectOf(id)
    58  		if obj == nil {
    59  			// Happens for y in "switch y := x.(type)",
    60  			// the package declaration,
    61  			// and unresolved identifiers.
    62  			if _, ok := qpos.path[1].(*ast.File); ok { // package decl?
    63  				pkg := qpos.info.Pkg
    64  				obj = types.NewPkgName(id.Pos(), pkg, pkg.Name(), pkg)
    65  			} else {
    66  				return fmt.Errorf("no object for identifier: %T", qpos.path[1])
    67  			}
    68  		}
    69  
    70  		if pass2 {
    71  			break
    72  		}
    73  
    74  		// If the identifier is exported, we must load all packages that
    75  		// depend transitively upon the package that defines it.
    76  		// Treat PkgNames as exported, even though they're lowercase.
    77  		if _, isPkg := obj.(*types.PkgName); !(isPkg || obj.Exported()) {
    78  			break // not exported
    79  		}
    80  
    81  		// Scan the workspace and build the import graph.
    82  		// Ignore broken packages.
    83  		_, rev, _ := importgraph.Build(q.Build)
    84  
    85  		// Re-load the larger program.
    86  		// Create a new file set so that ...
    87  		// External test packages are never imported,
    88  		// so they will never appear in the graph.
    89  		// (We must reset the Config here, not just reset the Fset field.)
    90  		lconf = loader.Config{
    91  			Fset:  token.NewFileSet(),
    92  			Build: q.Build,
    93  		}
    94  		allowErrors(&lconf)
    95  		for path := range rev.Search(obj.Pkg().Path()) {
    96  			lconf.ImportWithTests(path)
    97  		}
    98  		pass2 = true
    99  	}
   100  
   101  	// Iterate over all go/types' Uses facts for the entire program.
   102  	var refs []*ast.Ident
   103  	for _, info := range lprog.AllPackages {
   104  		for id2, obj2 := range info.Uses {
   105  			if sameObj(obj, obj2) {
   106  				refs = append(refs, id2)
   107  			}
   108  		}
   109  	}
   110  	sort.Sort(byNamePos{q.Fset, refs})
   111  
   112  	q.result = &referrersResult{
   113  		qpos:  qpos,
   114  		query: id,
   115  		obj:   obj,
   116  		refs:  refs,
   117  	}
   118  	return nil
   119  }
   120  
   121  // same reports whether x and y are identical, or both are PkgNames
   122  // that import the same Package.
   123  //
   124  func sameObj(x, y types.Object) bool {
   125  	if x == y {
   126  		return true
   127  	}
   128  	if x, ok := x.(*types.PkgName); ok {
   129  		if y, ok := y.(*types.PkgName); ok {
   130  			return x.Imported() == y.Imported()
   131  		}
   132  	}
   133  	return false
   134  }
   135  
   136  // -------- utils --------
   137  
   138  // An deterministic ordering for token.Pos that doesn't
   139  // depend on the order in which packages were loaded.
   140  func lessPos(fset *token.FileSet, x, y token.Pos) bool {
   141  	fx := fset.File(x)
   142  	fy := fset.File(y)
   143  	if fx != fy {
   144  		return fx.Name() < fy.Name()
   145  	}
   146  	return x < y
   147  }
   148  
   149  type byNamePos struct {
   150  	fset *token.FileSet
   151  	ids  []*ast.Ident
   152  }
   153  
   154  func (p byNamePos) Len() int      { return len(p.ids) }
   155  func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] }
   156  func (p byNamePos) Less(i, j int) bool {
   157  	return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos)
   158  }
   159  
   160  type referrersResult struct {
   161  	qpos  *queryPos
   162  	query *ast.Ident   // identifier of query
   163  	obj   types.Object // object it denotes
   164  	refs  []*ast.Ident // set of all other references to it
   165  }
   166  
   167  func (r *referrersResult) display(printf printfFunc) {
   168  	printf(r.obj, "%d references to %s", len(r.refs), r.qpos.objectString(r.obj))
   169  
   170  	// Show referring lines, like grep.
   171  	type fileinfo struct {
   172  		refs     []*ast.Ident
   173  		linenums []int            // line number of refs[i]
   174  		data     chan interface{} // file contents or error
   175  	}
   176  	var fileinfos []*fileinfo
   177  	fileinfosByName := make(map[string]*fileinfo)
   178  
   179  	// First pass: start the file reads concurrently.
   180  	sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
   181  	for _, ref := range r.refs {
   182  		posn := r.qpos.fset.Position(ref.Pos())
   183  		fi := fileinfosByName[posn.Filename]
   184  		if fi == nil {
   185  			fi = &fileinfo{data: make(chan interface{})}
   186  			fileinfosByName[posn.Filename] = fi
   187  			fileinfos = append(fileinfos, fi)
   188  
   189  			// First request for this file:
   190  			// start asynchronous read.
   191  			go func() {
   192  				sema <- struct{}{} // acquire token
   193  				content, err := ioutil.ReadFile(posn.Filename)
   194  				<-sema // release token
   195  				if err != nil {
   196  					fi.data <- err
   197  				} else {
   198  					fi.data <- content
   199  				}
   200  			}()
   201  		}
   202  		fi.refs = append(fi.refs, ref)
   203  		fi.linenums = append(fi.linenums, posn.Line)
   204  	}
   205  
   206  	// Second pass: print refs in original order.
   207  	// One line may have several refs at different columns.
   208  	for _, fi := range fileinfos {
   209  		v := <-fi.data // wait for I/O completion
   210  
   211  		// Print one item for all refs in a file that could not
   212  		// be loaded (perhaps due to //line directives).
   213  		if err, ok := v.(error); ok {
   214  			var suffix string
   215  			if more := len(fi.refs) - 1; more > 0 {
   216  				suffix = fmt.Sprintf(" (+ %d more refs in this file)", more)
   217  			}
   218  			printf(fi.refs[0], "%v%s", err, suffix)
   219  			continue
   220  		}
   221  
   222  		lines := bytes.Split(v.([]byte), []byte("\n"))
   223  		for i, ref := range fi.refs {
   224  			printf(ref, "%s", lines[fi.linenums[i]-1])
   225  		}
   226  	}
   227  }
   228  
   229  // TODO(adonovan): encode extent, not just Pos info, in Serial form.
   230  
   231  func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) {
   232  	referrers := &serial.Referrers{
   233  		Pos:  fset.Position(r.query.Pos()).String(),
   234  		Desc: r.obj.String(),
   235  	}
   236  	if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos()
   237  		referrers.ObjPos = fset.Position(pos).String()
   238  	}
   239  	for _, ref := range r.refs {
   240  		referrers.Refs = append(referrers.Refs, fset.Position(ref.NamePos).String())
   241  	}
   242  	res.Referrers = referrers
   243  }