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 }