| // 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. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/ast" |
| "go/build" |
| "go/parser" |
| "go/token" |
| "go/types" |
| "io" |
| "log" |
| "os" |
| "sort" |
| "strconv" |
| "strings" |
| "sync" |
| |
| "golang.org/x/tools/cmd/guru/serial" |
| "golang.org/x/tools/go/buildutil" |
| "golang.org/x/tools/go/loader" |
| "golang.org/x/tools/imports" |
| "golang.org/x/tools/refactor/importgraph" |
| ) |
| |
| // The referrers function reports all identifiers that resolve to the same object |
| // as the queried identifier, within any package in the workspace. |
| func referrers(q *Query) error { |
| fset := token.NewFileSet() |
| lconf := loader.Config{Fset: fset, Build: q.Build} |
| allowErrors(&lconf) |
| |
| if _, err := importQueryPackage(q.Pos, &lconf); err != nil { |
| return err |
| } |
| |
| // Load tests of the query package |
| // even if the query location is not in the tests. |
| for path := range lconf.ImportPkgs { |
| lconf.ImportPkgs[path] = true |
| } |
| |
| // Load/parse/type-check the query package. |
| lprog, err := lconf.Load() |
| if err != nil { |
| return err |
| } |
| |
| 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? |
| return packageReferrers(q, qpos.info.Pkg.Path()) |
| } |
| return fmt.Errorf("no object for identifier: %T", qpos.path[1]) |
| } |
| |
| // Imported package name? |
| if pkgname, ok := obj.(*types.PkgName); ok { |
| return packageReferrers(q, pkgname.Imported().Path()) |
| } |
| |
| if obj.Pkg() == nil { |
| return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name()) |
| } |
| |
| q.Output(fset, &referrersInitialResult{ |
| qinfo: qpos.info, |
| obj: obj, |
| }) |
| |
| // For a globally accessible object defined in package P, we |
| // must load packages that depend on P. Specifically, for a |
| // package-level object, we need load only direct importers |
| // of P, but for a field or method, we must load |
| // any package that transitively imports P. |
| |
| if global, pkglevel := classify(obj); global { |
| if pkglevel { |
| return globalReferrersPkgLevel(q, obj, fset) |
| } |
| // We'll use the object's position to identify it in the larger program. |
| objposn := fset.Position(obj.Pos()) |
| defpkg := obj.Pkg().Path() // defining package |
| return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn) |
| } |
| |
| outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg()) |
| |
| return nil // success |
| } |
| |
| // classify classifies objects by how far |
| // we have to look to find references to them. |
| func classify(obj types.Object) (global, pkglevel bool) { |
| if obj.Exported() { |
| if obj.Parent() == nil { |
| // selectable object (field or method) |
| return true, false |
| } |
| if obj.Parent() == obj.Pkg().Scope() { |
| // lexical object (package-level var/const/func/type) |
| return true, true |
| } |
| } |
| // object with unexported named or defined in local scope |
| return false, false |
| } |
| |
| // packageReferrers reports all references to the specified package |
| // throughout the workspace. |
| func packageReferrers(q *Query, path string) error { |
| // Scan the workspace and build the import graph. |
| // Ignore broken packages. |
| _, rev, _ := importgraph.Build(q.Build) |
| |
| // Find the set of packages that directly import the query package. |
| // Only those packages need typechecking of function bodies. |
| users := rev[path] |
| |
| // Load the larger program. |
| fset := token.NewFileSet() |
| lconf := loader.Config{ |
| Fset: fset, |
| Build: q.Build, |
| TypeCheckFuncBodies: func(p string) bool { |
| return users[strings.TrimSuffix(p, "_test")] |
| }, |
| } |
| allowErrors(&lconf) |
| |
| // The importgraph doesn't treat external test packages |
| // as separate nodes, so we must use ImportWithTests. |
| for path := range users { |
| lconf.ImportWithTests(path) |
| } |
| |
| // Subtle! AfterTypeCheck needs no mutex for qpkg because the |
| // topological import order gives us the necessary happens-before edges. |
| // TODO(adonovan): what about import cycles? |
| var qpkg *types.Package |
| |
| // For efficiency, we scan each package for references |
| // just after it has been type-checked. The loader calls |
| // AfterTypeCheck (concurrently), providing us with a stream of |
| // packages. |
| lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { |
| // AfterTypeCheck may be called twice for the same package due to augmentation. |
| |
| if info.Pkg.Path() == path && qpkg == nil { |
| // Found the package of interest. |
| qpkg = info.Pkg |
| fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg) |
| q.Output(fset, &referrersInitialResult{ |
| qinfo: info, |
| obj: fakepkgname, // bogus |
| }) |
| } |
| |
| // Only inspect packages that directly import the |
| // declaring package (and thus were type-checked). |
| if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { |
| // Find PkgNames that refer to qpkg. |
| // TODO(adonovan): perhaps more useful would be to show imports |
| // of the package instead of qualified identifiers. |
| var refs []*ast.Ident |
| for id, obj := range info.Uses { |
| if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg { |
| refs = append(refs, id) |
| } |
| } |
| outputUses(q, fset, refs, info.Pkg) |
| } |
| |
| clearInfoFields(info) // save memory |
| } |
| |
| lconf.Load() // ignore error |
| |
| if qpkg == nil { |
| log.Fatalf("query package %q not found during reloading", path) |
| } |
| |
| return nil |
| } |
| |
| func usesOf(queryObj types.Object, info *loader.PackageInfo) []*ast.Ident { |
| var refs []*ast.Ident |
| for id, obj := range info.Uses { |
| if sameObj(queryObj, obj) { |
| refs = append(refs, id) |
| } |
| } |
| return refs |
| } |
| |
| // outputUses outputs a result describing refs, which appear in the package denoted by info. |
| func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Package) { |
| if len(refs) > 0 { |
| sort.Sort(byNamePos{fset, refs}) |
| q.Output(fset, &referrersPackageResult{ |
| pkg: pkg, |
| build: q.Build, |
| fset: fset, |
| refs: refs, |
| }) |
| } |
| } |
| |
| // globalReferrers reports references throughout the entire workspace to the |
| // object (a field or method) at the specified source position. |
| // Its defining package is defpkg, and the query package is qpkg. |
| func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) error { |
| // Scan the workspace and build the import graph. |
| // Ignore broken packages. |
| _, rev, _ := importgraph.Build(q.Build) |
| |
| // Find the set of packages that depend on defpkg. |
| // Only function bodies in those packages need type-checking. |
| users := rev.Search(defpkg) // transitive importers |
| |
| // Prepare to load the larger program. |
| fset := token.NewFileSet() |
| lconf := loader.Config{ |
| Fset: fset, |
| Build: q.Build, |
| TypeCheckFuncBodies: func(p string) bool { |
| return users[strings.TrimSuffix(p, "_test")] |
| }, |
| } |
| allowErrors(&lconf) |
| |
| // The importgraph doesn't treat external test packages |
| // as separate nodes, so we must use ImportWithTests. |
| for path := range users { |
| lconf.ImportWithTests(path) |
| } |
| |
| // The remainder of this function is somewhat tricky because it |
| // operates on the concurrent stream of packages observed by the |
| // loader's AfterTypeCheck hook. Most of guru's helper |
| // functions assume the entire program has already been loaded, |
| // so we can't use them here. |
| // TODO(adonovan): smooth things out once the other changes have landed. |
| |
| // Results are reported concurrently from within the |
| // AfterTypeCheck hook. The program may provide a useful stream |
| // of information even if the user doesn't let the program run |
| // to completion. |
| |
| var ( |
| mu sync.Mutex |
| qobj types.Object |
| ) |
| |
| // For efficiency, we scan each package for references |
| // just after it has been type-checked. The loader calls |
| // AfterTypeCheck (concurrently), providing us with a stream of |
| // packages. |
| lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { |
| // AfterTypeCheck may be called twice for the same package due to augmentation. |
| |
| // Only inspect packages that depend on the declaring package |
| // (and thus were type-checked). |
| if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { |
| // Record the query object and its package when we see it. |
| mu.Lock() |
| if qobj == nil && info.Pkg.Path() == defpkg { |
| // Find the object by its position (slightly ugly). |
| qobj = findObject(fset, &info.Info, objposn) |
| if qobj == nil { |
| // It really ought to be there; |
| // we found it once already. |
| log.Fatalf("object at %s not found in package %s", |
| objposn, defpkg) |
| } |
| } |
| obj := qobj |
| mu.Unlock() |
| |
| // Look for references to the query object. |
| if obj != nil { |
| outputUses(q, fset, usesOf(obj, info), info.Pkg) |
| } |
| } |
| |
| clearInfoFields(info) // save memory |
| } |
| |
| lconf.Load() // ignore error |
| |
| if qobj == nil { |
| log.Fatal("query object not found during reloading") |
| } |
| |
| return nil // success |
| } |
| |
| // globalReferrersPkgLevel reports references throughout the entire workspace to the package-level object obj. |
| // It assumes that the query object itself has already been reported. |
| func globalReferrersPkgLevel(q *Query, obj types.Object, fset *token.FileSet) error { |
| // globalReferrersPkgLevel uses go/ast and friends instead of go/types. |
| // This affords a considerable performance benefit. |
| // It comes at the cost of some code complexity. |
| // |
| // Here's a high level summary. |
| // |
| // The goal is to find references to the query object p.Q. |
| // There are several possible scenarios, each handled differently. |
| // |
| // 1. We are looking in a package other than p, and p is not dot-imported. |
| // This is the simplest case. Q must be referred to as n.Q, |
| // where n is the name under which p is imported. |
| // We look at all imports of p to gather all names under which it is imported. |
| // (In the typical case, it is imported only once, under its default name.) |
| // Then we look at all selector expressions and report any matches. |
| // |
| // 2. We are looking in a package other than p, and p is dot-imported. |
| // In this case, Q will be referred to just as Q. |
| // Furthermore, go/ast's object resolution will not be able to resolve |
| // Q to any other object, unlike any local (file- or function- or block-scoped) object. |
| // So we look at all matching identifiers and report all unresolvable ones. |
| // |
| // 3. We are looking in package p. |
| // (Care must be taken to separate p and p_test (an xtest package), |
| // and make sure that they are treated as separate packages.) |
| // In this case, we give go/ast the entire package for object resolution, |
| // instead of going file by file. |
| // We then iterate over all identifiers that resolve to the query object. |
| // (The query object itself has already been reported, so we don't re-report it.) |
| // |
| // We always skip all files that don't contain the string Q, as they cannot be |
| // relevant to finding references to Q. |
| // |
| // We parse all files leniently. In the presence of parsing errors, results are best-effort. |
| |
| // Scan the workspace and build the import graph. |
| // Ignore broken packages. |
| _, rev, _ := importgraph.Build(q.Build) |
| |
| // Find the set of packages that directly import defpkg. |
| defpkg := obj.Pkg().Path() |
| defpkg = strings.TrimSuffix(defpkg, "_test") // package x_test actually has package name x |
| defpkg = imports.VendorlessPath(defpkg) // remove vendor goop |
| |
| users := rev[defpkg] |
| if len(users) == 0 { |
| users = make(map[string]bool) |
| } |
| // We also need to check defpkg itself, and its xtests. |
| // For the reverse graph packages, we process xtests with the main package. |
| // defpkg gets special handling; we must distinguish between in-package vs out-of-package. |
| // To make the control flow below simpler, add defpkg and defpkg xtest placeholders. |
| // Use "!test" instead of "_test" because "!" is not a valid character in an import path. |
| // (More precisely, it is not guaranteed to be a valid character in an import path, |
| // so it is unlikely that it will be in use. See https://golang.org/ref/spec#Import_declarations.) |
| users[defpkg] = true |
| users[defpkg+"!test"] = true |
| |
| cwd, err := os.Getwd() |
| if err != nil { |
| return err |
| } |
| |
| defname := obj.Pkg().Name() // name of defining package, used for imports using import path only |
| isxtest := strings.HasSuffix(defname, "_test") // indicates whether the query object is defined in an xtest package |
| |
| name := obj.Name() |
| namebytes := []byte(name) // byte slice version of query object name, for early filtering |
| objpos := fset.Position(obj.Pos()) // position of query object, used to prevent re-emitting original decl |
| |
| sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency |
| var wg sync.WaitGroup |
| |
| for u := range users { |
| u := u |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| |
| uIsXTest := strings.HasSuffix(u, "!test") // indicates whether this package is the special defpkg xtest package |
| u = strings.TrimSuffix(u, "!test") |
| |
| // Resolve package. |
| sema <- struct{}{} // acquire token |
| pkg, err := q.Build.Import(u, cwd, build.IgnoreVendor) |
| <-sema // release token |
| if err != nil { |
| return |
| } |
| |
| // If we're not in the query package, |
| // the object is in another package regardless, |
| // so we want to process all files. |
| // If we are in the query package, |
| // we want to only process the files that are |
| // part of that query package; |
| // that set depends on whether the query package itself is an xtest. |
| inQueryPkg := u == defpkg && isxtest == uIsXTest |
| var files []string |
| if !inQueryPkg || !isxtest { |
| files = append(files, pkg.GoFiles...) |
| files = append(files, pkg.TestGoFiles...) |
| files = append(files, pkg.CgoFiles...) // use raw cgo files, as we're only parsing |
| } |
| if !inQueryPkg || isxtest { |
| files = append(files, pkg.XTestGoFiles...) |
| } |
| |
| if len(files) == 0 { |
| return |
| } |
| |
| var deffiles map[string]*ast.File |
| if inQueryPkg { |
| deffiles = make(map[string]*ast.File) |
| } |
| |
| buf := new(bytes.Buffer) // reusable buffer for reading files |
| |
| for _, file := range files { |
| if !buildutil.IsAbsPath(q.Build, file) { |
| file = buildutil.JoinPath(q.Build, pkg.Dir, file) |
| } |
| buf.Reset() |
| sema <- struct{}{} // acquire token |
| src, err := readFile(q.Build, file, buf) |
| <-sema // release token |
| if err != nil { |
| continue |
| } |
| |
| // Fast path: If the object's name isn't present anywhere in the source, ignore the file. |
| if !bytes.Contains(src, namebytes) { |
| continue |
| } |
| |
| if inQueryPkg { |
| // If we're in the query package, we defer final processing until we have |
| // parsed all of the candidate files in the package. |
| // Best effort; allow errors and use what we can from what remains. |
| f, _ := parser.ParseFile(fset, file, src, parser.AllErrors) |
| if f != nil { |
| deffiles[file] = f |
| } |
| continue |
| } |
| |
| // We aren't in the query package. Go file by file. |
| |
| // Parse out only the imports, to check whether the defining package |
| // was imported, and if so, under what names. |
| // Best effort; allow errors and use what we can from what remains. |
| f, _ := parser.ParseFile(fset, file, src, parser.ImportsOnly|parser.AllErrors) |
| if f == nil { |
| continue |
| } |
| |
| // pkgnames is the set of names by which defpkg is imported in this file. |
| // (Multiple imports in the same file are legal but vanishingly rare.) |
| pkgnames := make([]string, 0, 1) |
| var isdotimport bool |
| for _, imp := range f.Imports { |
| path, err := strconv.Unquote(imp.Path.Value) |
| if err != nil || path != defpkg { |
| continue |
| } |
| switch { |
| case imp.Name == nil: |
| pkgnames = append(pkgnames, defname) |
| case imp.Name.Name == ".": |
| isdotimport = true |
| default: |
| pkgnames = append(pkgnames, imp.Name.Name) |
| } |
| } |
| if len(pkgnames) == 0 && !isdotimport { |
| // Defining package not imported, bail. |
| continue |
| } |
| |
| // Re-parse the entire file. |
| // Parse errors are ok; we'll do the best we can with a partial AST, if we have one. |
| f, _ = parser.ParseFile(fset, file, src, parser.AllErrors) |
| if f == nil { |
| continue |
| } |
| |
| // Walk the AST looking for references. |
| var refs []*ast.Ident |
| ast.Inspect(f, func(n ast.Node) bool { |
| // Check selector expressions. |
| // If the selector matches the target name, |
| // and the expression is one of the names |
| // that the defining package was imported under, |
| // then we have a match. |
| if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == name { |
| if id, ok := sel.X.(*ast.Ident); ok { |
| for _, n := range pkgnames { |
| if n == id.Name { |
| refs = append(refs, sel.Sel) |
| // Don't recurse further, to avoid duplicate entries |
| // from the dot import check below. |
| return false |
| } |
| } |
| } |
| } |
| // Dot imports are special. |
| // Objects imported from the defining package are placed in the package scope. |
| // go/ast does not resolve them to an object. |
| // At all other scopes (file, local), go/ast can do the resolution. |
| // So we're looking for object-free idents with the right name. |
| // The only other way to get something with the right name at the package scope |
| // is to *be* the defining package. We handle that case separately (inQueryPkg). |
| if isdotimport { |
| if id, ok := n.(*ast.Ident); ok && id.Obj == nil && id.Name == name { |
| refs = append(refs, id) |
| return false |
| } |
| } |
| return true |
| }) |
| |
| // Emit any references we found. |
| if len(refs) > 0 { |
| q.Output(fset, &referrersPackageResult{ |
| pkg: types.NewPackage(pkg.ImportPath, pkg.Name), |
| build: q.Build, |
| fset: fset, |
| refs: refs, |
| }) |
| } |
| } |
| |
| // If we're in the query package, we've now collected all the files in the package. |
| // (Or at least the ones that might contain references to the object.) |
| // Find and emit refs. |
| if inQueryPkg { |
| // Bundle the files together into a package. |
| // This does package-level object resolution. |
| qpkg, _ := ast.NewPackage(fset, deffiles, nil, nil) |
| // Look up the query object; we know that it is defined in the package scope. |
| pkgobj := qpkg.Scope.Objects[name] |
| if pkgobj == nil { |
| panic("missing defpkg object for " + defpkg + "." + name) |
| } |
| // Find all references to the query object. |
| var refs []*ast.Ident |
| ast.Inspect(qpkg, func(n ast.Node) bool { |
| if id, ok := n.(*ast.Ident); ok { |
| // Check both that this is a reference to the query object |
| // and that it is not the query object itself; |
| // the query object itself was already emitted. |
| if id.Obj == pkgobj && objpos != fset.Position(id.Pos()) { |
| refs = append(refs, id) |
| return false |
| } |
| } |
| return true |
| }) |
| if len(refs) > 0 { |
| q.Output(fset, &referrersPackageResult{ |
| pkg: types.NewPackage(pkg.ImportPath, pkg.Name), |
| build: q.Build, |
| fset: fset, |
| refs: refs, |
| }) |
| } |
| deffiles = nil // allow GC |
| } |
| }() |
| } |
| |
| wg.Wait() |
| |
| return nil |
| } |
| |
| // findObject returns the object defined at the specified position. |
| func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object { |
| good := func(obj types.Object) bool { |
| if obj == nil { |
| return false |
| } |
| posn := fset.Position(obj.Pos()) |
| return posn.Filename == objposn.Filename && posn.Offset == objposn.Offset |
| } |
| for _, obj := range info.Defs { |
| if good(obj) { |
| return obj |
| } |
| } |
| for _, obj := range info.Implicits { |
| if good(obj) { |
| return obj |
| } |
| } |
| 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 |
| } |
| |
| func clearInfoFields(info *loader.PackageInfo) { |
| // TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects. |
| // (Requires go/types change for Go 1.7.) |
| // info.Pkg.Scope().ClearChildren() |
| |
| // Discard the file ASTs and their accumulated type |
| // information to save memory. |
| info.Files = nil |
| info.Defs = make(map[*ast.Ident]types.Object) |
| info.Uses = make(map[*ast.Ident]types.Object) |
| info.Implicits = make(map[ast.Node]types.Object) |
| |
| // Also, disable future collection of wholly unneeded |
| // type information for the package in case there is |
| // more type-checking to do (augmentation). |
| info.Types = nil |
| info.Scopes = nil |
| info.Selections = nil |
| } |
| |
| // -------- 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) |
| } |
| |
| // referrersInitialResult is the initial result of a "referrers" query. |
| type referrersInitialResult struct { |
| qinfo *loader.PackageInfo |
| obj types.Object // object it denotes |
| } |
| |
| func (r *referrersInitialResult) PrintPlain(printf printfFunc) { |
| printf(r.obj, "references to %s", |
| types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg))) |
| } |
| |
| func (r *referrersInitialResult) JSON(fset *token.FileSet) []byte { |
| var objpos string |
| if pos := r.obj.Pos(); pos.IsValid() { |
| objpos = fset.Position(pos).String() |
| } |
| return toJSON(&serial.ReferrersInitial{ |
| Desc: r.obj.String(), |
| ObjPos: objpos, |
| }) |
| } |
| |
| // referrersPackageResult is the streaming result for one package of a "referrers" query. |
| type referrersPackageResult struct { |
| pkg *types.Package |
| build *build.Context |
| fset *token.FileSet |
| refs []*ast.Ident // set of all other references to it |
| } |
| |
| // foreachRef calls f(id, text) for id in r.refs, in order. |
| // Text is the text of the line on which id appears. |
| func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string)) { |
| // 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.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 := readFile(r.build, posn.Filename, nil) |
| <-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) |
| } |
| f(fi.refs[0], err.Error()+suffix) |
| continue |
| } |
| |
| lines := bytes.Split(v.([]byte), []byte("\n")) |
| for i, ref := range fi.refs { |
| f(ref, string(lines[fi.linenums[i]-1])) |
| } |
| } |
| } |
| |
| // readFile is like ioutil.ReadFile, but |
| // it goes through the virtualized build.Context. |
| // If non-nil, buf must have been reset. |
| func readFile(ctxt *build.Context, filename string, buf *bytes.Buffer) ([]byte, error) { |
| rc, err := buildutil.OpenFile(ctxt, filename) |
| if err != nil { |
| return nil, err |
| } |
| defer rc.Close() |
| if buf == nil { |
| buf = new(bytes.Buffer) |
| } |
| if _, err := io.Copy(buf, rc); err != nil { |
| return nil, err |
| } |
| return buf.Bytes(), nil |
| } |
| |
| func (r *referrersPackageResult) PrintPlain(printf printfFunc) { |
| r.foreachRef(func(id *ast.Ident, text string) { |
| printf(id, "%s", text) |
| }) |
| } |
| |
| func (r *referrersPackageResult) JSON(fset *token.FileSet) []byte { |
| refs := serial.ReferrersPackage{Package: r.pkg.Path()} |
| r.foreachRef(func(id *ast.Ident, text string) { |
| refs.Refs = append(refs.Refs, serial.Ref{ |
| Pos: fset.Position(id.NamePos).String(), |
| Text: text, |
| }) |
| }) |
| return toJSON(refs) |
| } |