// 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 }
// Referrers reports all identifiers that resolve to the same object // as the queried identifier, within any package in the analysis scope. func referrers(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) if _, err := importQueryPackage(q.Pos, &lconf); err != nil { return err } var id *ast.Ident var obj types.Object var lprog *loader.Program var pass2 bool var qpos *queryPos for { // Load/parse/type-check the program. var err error lprog, err = lconf.Load() if err != nil { return err } q.Fset = lprog.Fset 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? pkg := qpos.info.Pkg obj = types.NewPkgName(id.Pos(), pkg, pkg.Name(), pkg) } else { return fmt.Errorf("no object for identifier: %T", qpos.path[1]) } } if pass2 { break } // If the identifier is exported, we must load all packages that // depend transitively upon the package that defines it. // Treat PkgNames as exported, even though they're lowercase. if _, isPkg := obj.(*types.PkgName); !(isPkg || obj.Exported()) { break // not exported } // Scan the workspace and build the import graph. // Ignore broken packages. _, rev, _ := importgraph.Build(q.Build) // Re-load the larger program. // Create a new file set so that ... // External test packages are never imported, // so they will never appear in the graph. // (We must reset the Config here, not just reset the Fset field.) lconf = loader.Config{ Fset: token.NewFileSet(), Build: q.Build, } allowErrors(&lconf) for path := range rev.Search(obj.Pkg().Path()) { lconf.ImportWithTests(path) } pass2 = true } // Iterate over all go/types' Uses facts for the entire program. var refs []*ast.Ident for _, info := range lprog.AllPackages { for id2, obj2 := range info.Uses { if sameObj(obj, obj2) { refs = append(refs, id2) } } } sort.Sort(byNamePos{q.Fset, refs}) q.result = &referrersResult{ qpos: qpos, query: id, obj: obj, refs: refs, } return nil }