+++ /dev/null
-// 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)
-}