github.com/april1989/origin-go-tools@v0.0.32/cmd/guru/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 package main 6 7 import ( 8 "bytes" 9 "fmt" 10 "go/ast" 11 "go/build" 12 "go/parser" 13 "go/token" 14 "go/types" 15 "io" 16 "log" 17 "os" 18 "sort" 19 "strconv" 20 "strings" 21 "sync" 22 23 "github.com/april1989/origin-go-tools/cmd/guru/serial" 24 "github.com/april1989/origin-go-tools/go/buildutil" 25 "github.com/april1989/origin-go-tools/go/loader" 26 "github.com/april1989/origin-go-tools/imports" 27 "github.com/april1989/origin-go-tools/refactor/importgraph" 28 ) 29 30 // The referrers function reports all identifiers that resolve to the same object 31 // as the queried identifier, within any package in the workspace. 32 func referrers(q *Query) error { 33 fset := token.NewFileSet() 34 lconf := loader.Config{Fset: fset, Build: q.Build} 35 allowErrors(&lconf) 36 37 if _, err := importQueryPackage(q.Pos, &lconf); err != nil { 38 return err 39 } 40 41 // Load tests of the query package 42 // even if the query location is not in the tests. 43 for path := range lconf.ImportPkgs { 44 lconf.ImportPkgs[path] = true 45 } 46 47 // Load/parse/type-check the query package. 48 lprog, err := lconf.Load() 49 if err != nil { 50 return err 51 } 52 53 qpos, err := parseQueryPos(lprog, q.Pos, false) 54 if err != nil { 55 return err 56 } 57 58 id, _ := qpos.path[0].(*ast.Ident) 59 if id == nil { 60 return fmt.Errorf("no identifier here") 61 } 62 63 obj := qpos.info.ObjectOf(id) 64 if obj == nil { 65 // Happens for y in "switch y := x.(type)", 66 // the package declaration, 67 // and unresolved identifiers. 68 if _, ok := qpos.path[1].(*ast.File); ok { // package decl? 69 return packageReferrers(q, qpos.info.Pkg.Path()) 70 } 71 return fmt.Errorf("no object for identifier: %T", qpos.path[1]) 72 } 73 74 // Imported package name? 75 if pkgname, ok := obj.(*types.PkgName); ok { 76 return packageReferrers(q, pkgname.Imported().Path()) 77 } 78 79 if obj.Pkg() == nil { 80 return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name()) 81 } 82 83 q.Output(fset, &referrersInitialResult{ 84 qinfo: qpos.info, 85 obj: obj, 86 }) 87 88 // For a globally accessible object defined in package P, we 89 // must load packages that depend on P. Specifically, for a 90 // package-level object, we need load only direct importers 91 // of P, but for a field or method, we must load 92 // any package that transitively imports P. 93 94 if global, pkglevel := classify(obj); global { 95 if pkglevel { 96 return globalReferrersPkgLevel(q, obj, fset) 97 } 98 // We'll use the the object's position to identify it in the larger program. 99 objposn := fset.Position(obj.Pos()) 100 defpkg := obj.Pkg().Path() // defining package 101 return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn) 102 } 103 104 outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg()) 105 106 return nil // success 107 } 108 109 // classify classifies objects by how far 110 // we have to look to find references to them. 111 func classify(obj types.Object) (global, pkglevel bool) { 112 if obj.Exported() { 113 if obj.Parent() == nil { 114 // selectable object (field or method) 115 return true, false 116 } 117 if obj.Parent() == obj.Pkg().Scope() { 118 // lexical object (package-level var/const/func/type) 119 return true, true 120 } 121 } 122 // object with unexported named or defined in local scope 123 return false, false 124 } 125 126 // packageReferrers reports all references to the specified package 127 // throughout the workspace. 128 func packageReferrers(q *Query, path string) error { 129 // Scan the workspace and build the import graph. 130 // Ignore broken packages. 131 _, rev, _ := importgraph.Build(q.Build) 132 133 // Find the set of packages that directly import the query package. 134 // Only those packages need typechecking of function bodies. 135 users := rev[path] 136 137 // Load the larger program. 138 fset := token.NewFileSet() 139 lconf := loader.Config{ 140 Fset: fset, 141 Build: q.Build, 142 TypeCheckFuncBodies: func(p string) bool { 143 return users[strings.TrimSuffix(p, "_test")] 144 }, 145 } 146 allowErrors(&lconf) 147 148 // The importgraph doesn't treat external test packages 149 // as separate nodes, so we must use ImportWithTests. 150 for path := range users { 151 lconf.ImportWithTests(path) 152 } 153 154 // Subtle! AfterTypeCheck needs no mutex for qpkg because the 155 // topological import order gives us the necessary happens-before edges. 156 // TODO(adonovan): what about import cycles? 157 var qpkg *types.Package 158 159 // For efficiency, we scan each package for references 160 // just after it has been type-checked. The loader calls 161 // AfterTypeCheck (concurrently), providing us with a stream of 162 // packages. 163 lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { 164 // AfterTypeCheck may be called twice for the same package due to augmentation. 165 166 if info.Pkg.Path() == path && qpkg == nil { 167 // Found the package of interest. 168 qpkg = info.Pkg 169 fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg) 170 q.Output(fset, &referrersInitialResult{ 171 qinfo: info, 172 obj: fakepkgname, // bogus 173 }) 174 } 175 176 // Only inspect packages that directly import the 177 // declaring package (and thus were type-checked). 178 if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { 179 // Find PkgNames that refer to qpkg. 180 // TODO(adonovan): perhaps more useful would be to show imports 181 // of the package instead of qualified identifiers. 182 var refs []*ast.Ident 183 for id, obj := range info.Uses { 184 if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg { 185 refs = append(refs, id) 186 } 187 } 188 outputUses(q, fset, refs, info.Pkg) 189 } 190 191 clearInfoFields(info) // save memory 192 } 193 194 lconf.Load() // ignore error 195 196 if qpkg == nil { 197 log.Fatalf("query package %q not found during reloading", path) 198 } 199 200 return nil 201 } 202 203 func usesOf(queryObj types.Object, info *loader.PackageInfo) []*ast.Ident { 204 var refs []*ast.Ident 205 for id, obj := range info.Uses { 206 if sameObj(queryObj, obj) { 207 refs = append(refs, id) 208 } 209 } 210 return refs 211 } 212 213 // outputUses outputs a result describing refs, which appear in the package denoted by info. 214 func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Package) { 215 if len(refs) > 0 { 216 sort.Sort(byNamePos{fset, refs}) 217 q.Output(fset, &referrersPackageResult{ 218 pkg: pkg, 219 build: q.Build, 220 fset: fset, 221 refs: refs, 222 }) 223 } 224 } 225 226 // globalReferrers reports references throughout the entire workspace to the 227 // object (a field or method) at the specified source position. 228 // Its defining package is defpkg, and the query package is qpkg. 229 func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) error { 230 // Scan the workspace and build the import graph. 231 // Ignore broken packages. 232 _, rev, _ := importgraph.Build(q.Build) 233 234 // Find the set of packages that depend on defpkg. 235 // Only function bodies in those packages need type-checking. 236 users := rev.Search(defpkg) // transitive importers 237 238 // Prepare to load the larger program. 239 fset := token.NewFileSet() 240 lconf := loader.Config{ 241 Fset: fset, 242 Build: q.Build, 243 TypeCheckFuncBodies: func(p string) bool { 244 return users[strings.TrimSuffix(p, "_test")] 245 }, 246 } 247 allowErrors(&lconf) 248 249 // The importgraph doesn't treat external test packages 250 // as separate nodes, so we must use ImportWithTests. 251 for path := range users { 252 lconf.ImportWithTests(path) 253 } 254 255 // The remainder of this function is somewhat tricky because it 256 // operates on the concurrent stream of packages observed by the 257 // loader's AfterTypeCheck hook. Most of guru's helper 258 // functions assume the entire program has already been loaded, 259 // so we can't use them here. 260 // TODO(adonovan): smooth things out once the other changes have landed. 261 262 // Results are reported concurrently from within the 263 // AfterTypeCheck hook. The program may provide a useful stream 264 // of information even if the user doesn't let the program run 265 // to completion. 266 267 var ( 268 mu sync.Mutex 269 qobj types.Object 270 ) 271 272 // For efficiency, we scan each package for references 273 // just after it has been type-checked. The loader calls 274 // AfterTypeCheck (concurrently), providing us with a stream of 275 // packages. 276 lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { 277 // AfterTypeCheck may be called twice for the same package due to augmentation. 278 279 // Only inspect packages that depend on the declaring package 280 // (and thus were type-checked). 281 if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { 282 // Record the query object and its package when we see it. 283 mu.Lock() 284 if qobj == nil && info.Pkg.Path() == defpkg { 285 // Find the object by its position (slightly ugly). 286 qobj = findObject(fset, &info.Info, objposn) 287 if qobj == nil { 288 // It really ought to be there; 289 // we found it once already. 290 log.Fatalf("object at %s not found in package %s", 291 objposn, defpkg) 292 } 293 } 294 obj := qobj 295 mu.Unlock() 296 297 // Look for references to the query object. 298 if obj != nil { 299 outputUses(q, fset, usesOf(obj, info), info.Pkg) 300 } 301 } 302 303 clearInfoFields(info) // save memory 304 } 305 306 lconf.Load() // ignore error 307 308 if qobj == nil { 309 log.Fatal("query object not found during reloading") 310 } 311 312 return nil // success 313 } 314 315 // globalReferrersPkgLevel reports references throughout the entire workspace to the package-level object obj. 316 // It assumes that the query object itself has already been reported. 317 func globalReferrersPkgLevel(q *Query, obj types.Object, fset *token.FileSet) error { 318 // globalReferrersPkgLevel uses go/ast and friends instead of go/types. 319 // This affords a considerable performance benefit. 320 // It comes at the cost of some code complexity. 321 // 322 // Here's a high level summary. 323 // 324 // The goal is to find references to the query object p.Q. 325 // There are several possible scenarios, each handled differently. 326 // 327 // 1. We are looking in a package other than p, and p is not dot-imported. 328 // This is the simplest case. Q must be referred to as n.Q, 329 // where n is the name under which p is imported. 330 // We look at all imports of p to gather all names under which it is imported. 331 // (In the typical case, it is imported only once, under its default name.) 332 // Then we look at all selector expressions and report any matches. 333 // 334 // 2. We are looking in a package other than p, and p is dot-imported. 335 // In this case, Q will be referred to just as Q. 336 // Furthermore, go/ast's object resolution will not be able to resolve 337 // Q to any other object, unlike any local (file- or function- or block-scoped) object. 338 // So we look at all matching identifiers and report all unresolvable ones. 339 // 340 // 3. We are looking in package p. 341 // (Care must be taken to separate p and p_test (an xtest package), 342 // and make sure that they are treated as separate packages.) 343 // In this case, we give go/ast the entire package for object resolution, 344 // instead of going file by file. 345 // We then iterate over all identifiers that resolve to the query object. 346 // (The query object itself has already been reported, so we don't re-report it.) 347 // 348 // We always skip all files that don't contain the string Q, as they cannot be 349 // relevant to finding references to Q. 350 // 351 // We parse all files leniently. In the presence of parsing errors, results are best-effort. 352 353 // Scan the workspace and build the import graph. 354 // Ignore broken packages. 355 _, rev, _ := importgraph.Build(q.Build) 356 357 // Find the set of packages that directly import defpkg. 358 defpkg := obj.Pkg().Path() 359 defpkg = strings.TrimSuffix(defpkg, "_test") // package x_test actually has package name x 360 defpkg = imports.VendorlessPath(defpkg) // remove vendor goop 361 362 users := rev[defpkg] 363 if len(users) == 0 { 364 users = make(map[string]bool) 365 } 366 // We also need to check defpkg itself, and its xtests. 367 // For the reverse graph packages, we process xtests with the main package. 368 // defpkg gets special handling; we must distinguish between in-package vs out-of-package. 369 // To make the control flow below simpler, add defpkg and defpkg xtest placeholders. 370 // Use "!test" instead of "_test" because "!" is not a valid character in an import path. 371 // (More precisely, it is not guaranteed to be a valid character in an import path, 372 // so it is unlikely that it will be in use. See https://golang.org/ref/spec#Import_declarations.) 373 users[defpkg] = true 374 users[defpkg+"!test"] = true 375 376 cwd, err := os.Getwd() 377 if err != nil { 378 return err 379 } 380 381 defname := obj.Pkg().Name() // name of defining package, used for imports using import path only 382 isxtest := strings.HasSuffix(defname, "_test") // indicates whether the query object is defined in an xtest package 383 384 name := obj.Name() 385 namebytes := []byte(name) // byte slice version of query object name, for early filtering 386 objpos := fset.Position(obj.Pos()) // position of query object, used to prevent re-emitting original decl 387 388 sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency 389 var wg sync.WaitGroup 390 391 for u := range users { 392 u := u 393 wg.Add(1) 394 go func() { 395 defer wg.Done() 396 397 uIsXTest := strings.HasSuffix(u, "!test") // indicates whether this package is the special defpkg xtest package 398 u = strings.TrimSuffix(u, "!test") 399 400 // Resolve package. 401 sema <- struct{}{} // acquire token 402 pkg, err := q.Build.Import(u, cwd, build.IgnoreVendor) 403 <-sema // release token 404 if err != nil { 405 return 406 } 407 408 // If we're not in the query package, 409 // the object is in another package regardless, 410 // so we want to process all files. 411 // If we are in the query package, 412 // we want to only process the files that are 413 // part of that query package; 414 // that set depends on whether the query package itself is an xtest. 415 inQueryPkg := u == defpkg && isxtest == uIsXTest 416 var files []string 417 if !inQueryPkg || !isxtest { 418 files = append(files, pkg.GoFiles...) 419 files = append(files, pkg.TestGoFiles...) 420 files = append(files, pkg.CgoFiles...) // use raw cgo files, as we're only parsing 421 } 422 if !inQueryPkg || isxtest { 423 files = append(files, pkg.XTestGoFiles...) 424 } 425 426 if len(files) == 0 { 427 return 428 } 429 430 var deffiles map[string]*ast.File 431 if inQueryPkg { 432 deffiles = make(map[string]*ast.File) 433 } 434 435 buf := new(bytes.Buffer) // reusable buffer for reading files 436 437 for _, file := range files { 438 if !buildutil.IsAbsPath(q.Build, file) { 439 file = buildutil.JoinPath(q.Build, pkg.Dir, file) 440 } 441 buf.Reset() 442 sema <- struct{}{} // acquire token 443 src, err := readFile(q.Build, file, buf) 444 <-sema // release token 445 if err != nil { 446 continue 447 } 448 449 // Fast path: If the object's name isn't present anywhere in the source, ignore the file. 450 if !bytes.Contains(src, namebytes) { 451 continue 452 } 453 454 if inQueryPkg { 455 // If we're in the query package, we defer final processing until we have 456 // parsed all of the candidate files in the package. 457 // Best effort; allow errors and use what we can from what remains. 458 f, _ := parser.ParseFile(fset, file, src, parser.AllErrors) 459 if f != nil { 460 deffiles[file] = f 461 } 462 continue 463 } 464 465 // We aren't in the query package. Go file by file. 466 467 // Parse out only the imports, to check whether the defining package 468 // was imported, and if so, under what names. 469 // Best effort; allow errors and use what we can from what remains. 470 f, _ := parser.ParseFile(fset, file, src, parser.ImportsOnly|parser.AllErrors) 471 if f == nil { 472 continue 473 } 474 475 // pkgnames is the set of names by which defpkg is imported in this file. 476 // (Multiple imports in the same file are legal but vanishingly rare.) 477 pkgnames := make([]string, 0, 1) 478 var isdotimport bool 479 for _, imp := range f.Imports { 480 path, err := strconv.Unquote(imp.Path.Value) 481 if err != nil || path != defpkg { 482 continue 483 } 484 switch { 485 case imp.Name == nil: 486 pkgnames = append(pkgnames, defname) 487 case imp.Name.Name == ".": 488 isdotimport = true 489 default: 490 pkgnames = append(pkgnames, imp.Name.Name) 491 } 492 } 493 if len(pkgnames) == 0 && !isdotimport { 494 // Defining package not imported, bail. 495 continue 496 } 497 498 // Re-parse the entire file. 499 // Parse errors are ok; we'll do the best we can with a partial AST, if we have one. 500 f, _ = parser.ParseFile(fset, file, src, parser.AllErrors) 501 if f == nil { 502 continue 503 } 504 505 // Walk the AST looking for references. 506 var refs []*ast.Ident 507 ast.Inspect(f, func(n ast.Node) bool { 508 // Check selector expressions. 509 // If the selector matches the target name, 510 // and the expression is one of the names 511 // that the defining package was imported under, 512 // then we have a match. 513 if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == name { 514 if id, ok := sel.X.(*ast.Ident); ok { 515 for _, n := range pkgnames { 516 if n == id.Name { 517 refs = append(refs, sel.Sel) 518 // Don't recurse further, to avoid duplicate entries 519 // from the dot import check below. 520 return false 521 } 522 } 523 } 524 } 525 // Dot imports are special. 526 // Objects imported from the defining package are placed in the package scope. 527 // go/ast does not resolve them to an object. 528 // At all other scopes (file, local), go/ast can do the resolution. 529 // So we're looking for object-free idents with the right name. 530 // The only other way to get something with the right name at the package scope 531 // is to *be* the defining package. We handle that case separately (inQueryPkg). 532 if isdotimport { 533 if id, ok := n.(*ast.Ident); ok && id.Obj == nil && id.Name == name { 534 refs = append(refs, id) 535 return false 536 } 537 } 538 return true 539 }) 540 541 // Emit any references we found. 542 if len(refs) > 0 { 543 q.Output(fset, &referrersPackageResult{ 544 pkg: types.NewPackage(pkg.ImportPath, pkg.Name), 545 build: q.Build, 546 fset: fset, 547 refs: refs, 548 }) 549 } 550 } 551 552 // If we're in the query package, we've now collected all the files in the package. 553 // (Or at least the ones that might contain references to the object.) 554 // Find and emit refs. 555 if inQueryPkg { 556 // Bundle the files together into a package. 557 // This does package-level object resolution. 558 qpkg, _ := ast.NewPackage(fset, deffiles, nil, nil) 559 // Look up the query object; we know that it is defined in the package scope. 560 pkgobj := qpkg.Scope.Objects[name] 561 if pkgobj == nil { 562 panic("missing defpkg object for " + defpkg + "." + name) 563 } 564 // Find all references to the query object. 565 var refs []*ast.Ident 566 ast.Inspect(qpkg, func(n ast.Node) bool { 567 if id, ok := n.(*ast.Ident); ok { 568 // Check both that this is a reference to the query object 569 // and that it is not the query object itself; 570 // the query object itself was already emitted. 571 if id.Obj == pkgobj && objpos != fset.Position(id.Pos()) { 572 refs = append(refs, id) 573 return false 574 } 575 } 576 return true 577 }) 578 if len(refs) > 0 { 579 q.Output(fset, &referrersPackageResult{ 580 pkg: types.NewPackage(pkg.ImportPath, pkg.Name), 581 build: q.Build, 582 fset: fset, 583 refs: refs, 584 }) 585 } 586 deffiles = nil // allow GC 587 } 588 }() 589 } 590 591 wg.Wait() 592 593 return nil 594 } 595 596 // findObject returns the object defined at the specified position. 597 func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object { 598 good := func(obj types.Object) bool { 599 if obj == nil { 600 return false 601 } 602 posn := fset.Position(obj.Pos()) 603 return posn.Filename == objposn.Filename && posn.Offset == objposn.Offset 604 } 605 for _, obj := range info.Defs { 606 if good(obj) { 607 return obj 608 } 609 } 610 for _, obj := range info.Implicits { 611 if good(obj) { 612 return obj 613 } 614 } 615 return nil 616 } 617 618 // same reports whether x and y are identical, or both are PkgNames 619 // that import the same Package. 620 // 621 func sameObj(x, y types.Object) bool { 622 if x == y { 623 return true 624 } 625 if x, ok := x.(*types.PkgName); ok { 626 if y, ok := y.(*types.PkgName); ok { 627 return x.Imported() == y.Imported() 628 } 629 } 630 return false 631 } 632 633 func clearInfoFields(info *loader.PackageInfo) { 634 // TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects. 635 // (Requires go/types change for Go 1.7.) 636 // info.Pkg.Scope().ClearChildren() 637 638 // Discard the file ASTs and their accumulated type 639 // information to save memory. 640 info.Files = nil 641 info.Defs = make(map[*ast.Ident]types.Object) 642 info.Uses = make(map[*ast.Ident]types.Object) 643 info.Implicits = make(map[ast.Node]types.Object) 644 645 // Also, disable future collection of wholly unneeded 646 // type information for the package in case there is 647 // more type-checking to do (augmentation). 648 info.Types = nil 649 info.Scopes = nil 650 info.Selections = nil 651 } 652 653 // -------- utils -------- 654 655 // An deterministic ordering for token.Pos that doesn't 656 // depend on the order in which packages were loaded. 657 func lessPos(fset *token.FileSet, x, y token.Pos) bool { 658 fx := fset.File(x) 659 fy := fset.File(y) 660 if fx != fy { 661 return fx.Name() < fy.Name() 662 } 663 return x < y 664 } 665 666 type byNamePos struct { 667 fset *token.FileSet 668 ids []*ast.Ident 669 } 670 671 func (p byNamePos) Len() int { return len(p.ids) } 672 func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] } 673 func (p byNamePos) Less(i, j int) bool { 674 return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos) 675 } 676 677 // referrersInitialResult is the initial result of a "referrers" query. 678 type referrersInitialResult struct { 679 qinfo *loader.PackageInfo 680 obj types.Object // object it denotes 681 } 682 683 func (r *referrersInitialResult) PrintPlain(printf printfFunc) { 684 printf(r.obj, "references to %s", 685 types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg))) 686 } 687 688 func (r *referrersInitialResult) JSON(fset *token.FileSet) []byte { 689 var objpos string 690 if pos := r.obj.Pos(); pos.IsValid() { 691 objpos = fset.Position(pos).String() 692 } 693 return toJSON(&serial.ReferrersInitial{ 694 Desc: r.obj.String(), 695 ObjPos: objpos, 696 }) 697 } 698 699 // referrersPackageResult is the streaming result for one package of a "referrers" query. 700 type referrersPackageResult struct { 701 pkg *types.Package 702 build *build.Context 703 fset *token.FileSet 704 refs []*ast.Ident // set of all other references to it 705 } 706 707 // forEachRef calls f(id, text) for id in r.refs, in order. 708 // Text is the text of the line on which id appears. 709 func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string)) { 710 // Show referring lines, like grep. 711 type fileinfo struct { 712 refs []*ast.Ident 713 linenums []int // line number of refs[i] 714 data chan interface{} // file contents or error 715 } 716 var fileinfos []*fileinfo 717 fileinfosByName := make(map[string]*fileinfo) 718 719 // First pass: start the file reads concurrently. 720 sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency 721 for _, ref := range r.refs { 722 posn := r.fset.Position(ref.Pos()) 723 fi := fileinfosByName[posn.Filename] 724 if fi == nil { 725 fi = &fileinfo{data: make(chan interface{})} 726 fileinfosByName[posn.Filename] = fi 727 fileinfos = append(fileinfos, fi) 728 729 // First request for this file: 730 // start asynchronous read. 731 go func() { 732 sema <- struct{}{} // acquire token 733 content, err := readFile(r.build, posn.Filename, nil) 734 <-sema // release token 735 if err != nil { 736 fi.data <- err 737 } else { 738 fi.data <- content 739 } 740 }() 741 } 742 fi.refs = append(fi.refs, ref) 743 fi.linenums = append(fi.linenums, posn.Line) 744 } 745 746 // Second pass: print refs in original order. 747 // One line may have several refs at different columns. 748 for _, fi := range fileinfos { 749 v := <-fi.data // wait for I/O completion 750 751 // Print one item for all refs in a file that could not 752 // be loaded (perhaps due to //line directives). 753 if err, ok := v.(error); ok { 754 var suffix string 755 if more := len(fi.refs) - 1; more > 0 { 756 suffix = fmt.Sprintf(" (+ %d more refs in this file)", more) 757 } 758 f(fi.refs[0], err.Error()+suffix) 759 continue 760 } 761 762 lines := bytes.Split(v.([]byte), []byte("\n")) 763 for i, ref := range fi.refs { 764 f(ref, string(lines[fi.linenums[i]-1])) 765 } 766 } 767 } 768 769 // readFile is like ioutil.ReadFile, but 770 // it goes through the virtualized build.Context. 771 // If non-nil, buf must have been reset. 772 func readFile(ctxt *build.Context, filename string, buf *bytes.Buffer) ([]byte, error) { 773 rc, err := buildutil.OpenFile(ctxt, filename) 774 if err != nil { 775 return nil, err 776 } 777 defer rc.Close() 778 if buf == nil { 779 buf = new(bytes.Buffer) 780 } 781 if _, err := io.Copy(buf, rc); err != nil { 782 return nil, err 783 } 784 return buf.Bytes(), nil 785 } 786 787 func (r *referrersPackageResult) PrintPlain(printf printfFunc) { 788 r.foreachRef(func(id *ast.Ident, text string) { 789 printf(id, "%s", text) 790 }) 791 } 792 793 func (r *referrersPackageResult) JSON(fset *token.FileSet) []byte { 794 refs := serial.ReferrersPackage{Package: r.pkg.Path()} 795 r.foreachRef(func(id *ast.Ident, text string) { 796 refs.Refs = append(refs.Refs, serial.Ref{ 797 Pos: fset.Position(id.NamePos).String(), 798 Text: text, 799 }) 800 }) 801 return toJSON(refs) 802 }