// Print a Markdown report of which AWS services use which AWS protocols func main() { _, reverseImports, _ := importgraph.Build(&build.Default) u := usage.GetShortUsage(reverseImports) markdown := ToMarkdown(u) fmt.Println(markdown) }
func (u *Unexporter) loadProgram(pkgPath string) (err error) { wd, err := os.Getwd() if err != nil { return err } bpkg, err := u.ctxt.Import(pkgPath, wd, build.ImportComment) if err != nil { return err } _, rev, _ := importgraph.Build(u.ctxt) pkgs := rev.Search(bpkg.ImportPath) conf := loader.Config{ Build: u.ctxt, ParserMode: parser.ParseComments, AllowErrors: false, } // Optimization: don't type-check the bodies of functions in our // dependencies, since we only need exported package members. conf.TypeCheckFuncBodies = func(p string) bool { return pkgs[p] || pkgs[strings.TrimSuffix(p, "_test")] } for pkg := range pkgs { conf.ImportWithTests(pkg) } u.prog, err = conf.Load() if err != nil { return } for p, info := range u.prog.AllPackages { if p.Path() == bpkg.ImportPath { u.pkgInfo = info break } } return }
func scanWorkspace(ctxt *build.Context, path string) []string { // Scan the workspace and build the import graph. _, rev, errors := importgraph.Build(ctxt) if len(errors) > 0 { // With a large GOPATH tree, errors are inevitable. // Report them but proceed. log.Printf("While scanning Go workspace:\n") for path, err := range errors { log.Printf("Package %q: %s.\n", path, err) } } // Enumerate the set of potentially affected packages. var affectedPackages []string // External test packages are never imported, // so they will never appear in the graph. for pkg := range rev.Search(path) { affectedPackages = append(affectedPackages, pkg) } return affectedPackages }
func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error { // -- Parse the -from or -offset specifier ---------------------------- if (offsetFlag == "") == (fromFlag == "") { return fmt.Errorf("exactly one of the -from and -offset flags must be specified") } if !isValidIdentifier(to) { return fmt.Errorf("-to %q: not a valid identifier", to) } var spec *spec var err error if fromFlag != "" { spec, err = parseFromFlag(ctxt, fromFlag) } else { spec, err = parseOffsetFlag(ctxt, offsetFlag) } if err != nil { return err } if spec.fromName == to { return fmt.Errorf("the old and new names are the same: %s", to) } // -- Load the program consisting of the initial package ------------- iprog, err := loadProgram(ctxt, map[string]bool{spec.pkg: true}) if err != nil { return err } fromObjects, err := findFromObjects(iprog, spec) if err != nil { return err } // -- Load a larger program, for global renamings --------------------- if requiresGlobalRename(fromObjects, to) { // For a local refactoring, we needn't load more // packages, but if the renaming affects the package's // API, we we must load all packages that depend on the // package defining the object, plus their tests. if Verbose { fmt.Fprintln(os.Stderr, "Potentially global renaming; scanning workspace...") } // Scan the workspace and build the import graph. _, rev, errors := importgraph.Build(ctxt) if len(errors) > 0 { fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n") for path, err := range errors { fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err) } } // Enumerate the set of potentially affected packages. affectedPackages := make(map[string]bool) for _, obj := range fromObjects { // External test packages are never imported, // so they will never appear in the graph. for path := range rev.Search(obj.Pkg().Path()) { affectedPackages[path] = true } } // TODO(adonovan): allow the user to specify the scope, // or -ignore patterns? Computing the scope when we // don't (yet) support inputs containing errors can make // the tool rather brittle. // Re-load the larger program. iprog, err = loadProgram(ctxt, affectedPackages) if err != nil { return err } fromObjects, err = findFromObjects(iprog, spec) if err != nil { return err } } // -- Do the renaming ------------------------------------------------- r := renamer{ iprog: iprog, objsToUpdate: make(map[types.Object]bool), to: to, packages: make(map[*types.Package]*loader.PackageInfo), } // A renaming initiated at an interface method indicates the // intention to rename abstract and concrete methods as needed // to preserve assignability. for _, obj := range fromObjects { if obj, ok := obj.(*types.Func); ok { recv := obj.Type().(*types.Signature).Recv() if recv != nil && isInterface(recv.Type().Underlying()) { r.changeMethods = true break } } } // Only the initially imported packages (iprog.Imported) and // their external tests (iprog.Created) should be inspected or // modified, as only they have type-checked functions bodies. // The rest are just dependencies, needed only for package-level // type information. for _, info := range iprog.Imported { r.packages[info.Pkg] = info } for _, info := range iprog.Created { // (tests) r.packages[info.Pkg] = info } for _, from := range fromObjects { r.check(from) } if r.hadConflicts && !Force { return ConflictError } if DryRun { // TODO(adonovan): print the delta? return nil } return r.update() }
// Implements displays the "implements" relation as it pertains to the // selected type. // If the selection is a method, 'implements' displays // the corresponding methods of the types that would have been reported // by an implements query on the receiver type. // func implements(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) qpkg, err := importQueryPackage(q.Pos, &lconf) if err != nil { return err } // Set the packages to search. if len(q.Scope) > 0 { // Inspect all packages in the analysis scope, if specified. if err := setPTAScope(&lconf, q.Scope); err != nil { return err } } else { // Otherwise inspect the forward and reverse // transitive closure of the selected package. // (In theory even this is incomplete.) _, rev, _ := importgraph.Build(q.Build) for path := range rev.Search(qpkg) { lconf.ImportWithTests(path) } // TODO(adonovan): for completeness, we should also // type-check and inspect function bodies in all // imported packages. This would be expensive, but we // could optimize by skipping functions that do not // contain type declarations. This would require // changing the loader's TypeCheckFuncBodies hook to // provide the []*ast.File. } // Load/parse/type-check the program. 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 } // Find the selected type. path, action := findInterestingNode(qpos.info, qpos.path) var method *types.Func var T types.Type // selected type (receiver if method != nil) switch action { case actionExpr: // method? if id, ok := path[0].(*ast.Ident); ok { if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok { recv := obj.Type().(*types.Signature).Recv() if recv == nil { return fmt.Errorf("this function is not a method") } method = obj T = recv.Type() } } case actionType: T = qpos.info.TypeOf(path[0].(ast.Expr)) } if T == nil { return fmt.Errorf("no type or method here") } // Find all named types, even local types (which can have // methods via promotion) and the built-in "error". var allNamed []types.Type for _, info := range lprog.AllPackages { for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok { allNamed = append(allNamed, obj.Type()) } } } allNamed = append(allNamed, types.Universe.Lookup("error").Type()) var msets typeutil.MethodSetCache // Test each named type. var to, from, fromPtr []types.Type for _, U := range allNamed { if isInterface(T) { if msets.MethodSet(T).Len() == 0 { continue // empty interface } if isInterface(U) { if msets.MethodSet(U).Len() == 0 { continue // empty interface } // T interface, U interface if !types.Identical(T, U) { if types.AssignableTo(U, T) { to = append(to, U) } if types.AssignableTo(T, U) { from = append(from, U) } } } else { // T interface, U concrete if types.AssignableTo(U, T) { to = append(to, U) } else if pU := types.NewPointer(U); types.AssignableTo(pU, T) { to = append(to, pU) } } } else if isInterface(U) { if msets.MethodSet(U).Len() == 0 { continue // empty interface } // T concrete, U interface if types.AssignableTo(T, U) { from = append(from, U) } else if pT := types.NewPointer(T); types.AssignableTo(pT, U) { fromPtr = append(fromPtr, U) } } } var pos interface{} = qpos if nt, ok := deref(T).(*types.Named); ok { pos = nt.Obj() } // Sort types (arbitrarily) to ensure test determinism. sort.Sort(typesByString(to)) sort.Sort(typesByString(from)) sort.Sort(typesByString(fromPtr)) var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils if method != nil { for _, t := range to { toMethod = append(toMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } for _, t := range from { fromMethod = append(fromMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } for _, t := range fromPtr { fromPtrMethod = append(fromPtrMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } } q.result = &implementsResult{ qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod, } return nil }
// globalReferrers reports references throughout the entire workspace to the // object at the specified source position. Its defining package is defpkg, // and the query package is qpkg. isPkgLevel indicates whether the object // is defined at package-level. func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position, isPkgLevel bool) 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. var users map[string]bool if isPkgLevel { users = rev[defpkg] // direct importers if users == nil { users = make(map[string]bool) } users[defpkg] = true // plus the defining package itself } else { 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 qinfo *loader.PackageInfo // info for qpkg ) // 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) } // Object found. qinfo = info q.Output(fset, &referrersInitialResult{ qinfo: qinfo, obj: qobj, }) } 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 }
// 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 }
// Move, given a package path and a destination package path, will try // to move the given package to the new path. The Move function will // first check for any conflicts preventing the move, such as a // package already existing at the destination package path. If the // move can proceed, it builds an import graph to find all imports of // the packages whose paths need to be renamed. This includes uses of // the subpackages of the package to be moved as those packages will // also need to be moved. It then renames all imports to point to the // new paths, and then moves the packages to their new paths. func Move(ctxt *build.Context, from, to, moveTmpl string) error { srcDir, err := srcDir(ctxt, from) if err != nil { return err } // This should be the only place in the program that constructs // file paths. // TODO(matloob): test on Microsoft Windows. fromDir := buildutil.JoinPath(ctxt, srcDir, filepath.FromSlash(from)) toDir := buildutil.JoinPath(ctxt, srcDir, filepath.FromSlash(to)) toParent := filepath.Dir(toDir) if !buildutil.IsDir(ctxt, toParent) { return fmt.Errorf("parent directory does not exist for path %s", toDir) } // Build the import graph and figure out which packages to update. fwd, rev, errors := importgraph.Build(ctxt) if len(errors) > 0 { fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n") for path, err := range errors { fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err) } return fmt.Errorf("failed to construct import graph") } // Determine the affected packages---the set of packages whose import // statements need updating. affectedPackages := map[string]bool{from: true} destinations := map[string]string{} // maps old dir to new dir for pkg := range subpackages(ctxt, srcDir, from) { for r := range rev[pkg] { affectedPackages[r] = true } destinations[pkg] = strings.Replace(pkg, // Ensure directories have a trailing "/". filepath.Join(from, ""), filepath.Join(to, ""), 1) } // Load all the affected packages. iprog, err := loadProgram(ctxt, affectedPackages) if err != nil { return err } // Prepare the move command, if one was supplied. var cmd string if moveTmpl != "" { if cmd, err = moveCmd(moveTmpl, fromDir, toDir); err != nil { return err } } m := mover{ ctxt: ctxt, fwd: fwd, rev: rev, iprog: iprog, from: from, to: to, fromDir: fromDir, toDir: toDir, affectedPackages: affectedPackages, destinations: destinations, cmd: cmd, } if err := m.checkValid(); err != nil { return err } m.move() return nil }
func TestBuild(t *testing.T) { forward, reverse, errors := importgraph.Build(&build.Default) // Test direct edges. // We throw in crypto/hmac to prove that external test files // (such as this one) are inspected. for _, p := range []string{"go/build", "testing", "crypto/hmac"} { if !forward[this][p] { t.Errorf("forward[importgraph][%s] not found", p) } if !reverse[p][this] { t.Errorf("reverse[%s][importgraph] not found", p) } } // Test non-existent direct edges for _, p := range []string{"errors", "reflect"} { if forward[this][p] { t.Errorf("unexpected: forward[importgraph][%s] found", p) } if reverse[p][this] { t.Errorf("unexpected: reverse[%s][importgraph] found", p) } } // Test Search is reflexive. if !forward.Search(this)[this] { t.Errorf("irreflexive: forward.Search(importgraph)[importgraph] not found") } if !reverse.Search(this)[this] { t.Errorf("irrefexive: reverse.Search(importgraph)[importgraph] not found") } // Test Search is transitive. (There is no direct edge to these packages.) for _, p := range []string{"errors", "reflect", "unsafe"} { if !forward.Search(this)[p] { t.Errorf("intransitive: forward.Search(importgraph)[%s] not found", p) } if !reverse.Search(p)[this] { t.Errorf("intransitive: reverse.Search(%s)[importgraph] not found", p) } } // Test strongly-connected components. Because A's external // test package can depend on B, and vice versa, most of the // standard libraries are mutually dependent when their external // tests are considered. // // For any nodes x, y in the same SCC, y appears in the results // of both forward and reverse searches starting from x if !forward.Search("fmt")["io"] || !forward.Search("io")["fmt"] || !reverse.Search("fmt")["io"] || !reverse.Search("io")["fmt"] { t.Errorf("fmt and io are not mutually reachable despite being in the same SCC") } // debugging if false { for path, err := range errors { t.Logf("%s: %s", path, err) } printSorted := func(direction string, g importgraph.Graph, start string) { t.Log(direction) var pkgs []string for pkg := range g.Search(start) { pkgs = append(pkgs, pkg) } sort.Strings(pkgs) for _, pkg := range pkgs { t.Logf("\t%s", pkg) } } printSorted("forward", forward, this) printSorted("reverse", reverse, this) } }
// runMain runs the actual command. It's an helper function so we can easily // calls defers or return errors. most of the functionality can be seen in // `gorename` code source. Actually unexport is something that probably // gorename can do for us. // // TODO(arslan): add tests // TODO(arslan): add vim-go integration ;) // TODO(arslan): check how this is acting for internal/ folders // TODO(arslan): check how this is acting for vendor/ folders func runMain(conf *config) error { if conf.importPath == "" { return errors.New("import path of the package must be given") } path := conf.importPath prog, err := loadProgram(conf.buildContext, map[string]bool{path: true}) if err != nil { return err } _, rev, errors := importgraph.Build(conf.buildContext) if len(errors) > 0 { // With a large GOPATH tree, errors are inevitable. // Report them but proceed. log.Printf("while scanning Go workspace:\n") for path, err := range errors { log.Printf("Package %q: %s.\n", path, err) } } // Enumerate the set of potentially affected packages. possiblePackages := make(map[string]bool) for _, obj := range findExportedObjects(prog, path) { for path := range rev.Search(obj.Pkg().Path()) { possiblePackages[path] = true } } if conf.verbose { log.Println("Possible affected packages:") for pkg := range possiblePackages { log.Println("\t", pkg) } } // reload the program with all possible packages to fetch the packageinfo's globalProg, err := loadProgram(conf.buildContext, possiblePackages) if err != nil { return err } objsToUpdate := make(map[types.Object]bool, 0) objects := findExportedObjects(globalProg, path) if conf.verbose { log.Println("Exported identififers are:") for _, obj := range objects { log.Println("\t", obj) } } // filter safeObjects check which exported identifiers are used by other packages var safeObjects map[*ast.Ident]types.Object for _, info := range globalProg.Imported { // we only check for packages other than ours if info.Pkg.Path() == path { continue } safeObjects = filterObjects(info, objects, conf.identifiers) } // filter out identifiers which can't be renamed due any collision in our package for _, info := range globalProg.Imported { // we don't care about other packages anymore if info.Pkg.Path() != path { continue } for _, obj := range safeObjects { // don't include collisions newName := toLowerCase(obj.Name()) if info.Pkg.Path() == obj.Pkg().Path() && hasObject(info, newName) { log.Printf("WARNING! can't unexport %q due collision. Identifier %q already exists.\n", obj.Name(), newName) continue } objsToUpdate[obj] = true } } if conf.verbose { log.Println("Safe to unexport identifiers are:") for obj := range objsToUpdate { log.Println("\t", obj) } } // first create the files that needs an update and modify the fileset var nidents int var filesToUpdate = make(map[*token.File]bool) for _, info := range globalProg.Imported { for id, obj := range info.Defs { if objsToUpdate[obj] { nidents++ id.Name = toLowerCase(obj.Name()) filesToUpdate[globalProg.Fset.File(id.Pos())] = true } } for id, obj := range info.Uses { if objsToUpdate[obj] { nidents++ id.Name = toLowerCase(obj.Name()) filesToUpdate[globalProg.Fset.File(id.Pos())] = true } } } // now start to rewrite the files var nerrs, npkgs int for _, info := range globalProg.Imported { first := true for _, f := range info.Files { tokenFile := globalProg.Fset.File(f.Pos()) if filesToUpdate[tokenFile] { if first { npkgs++ first = false } if conf.dryRun { continue } if err := rewriteFile(globalProg.Fset, f, tokenFile.Name()); err != nil { log.Println(err) nerrs++ } } } } if nidents == 0 { return nil } log.Printf("Unexported %d identifier%s in %d file%s in %d package%s.\n", nidents, plural(nidents), len(filesToUpdate), plural(len(filesToUpdate)), npkgs, plural(npkgs)) log.Println("Identifiers changed:") for obj := range objsToUpdate { log.Println("\t", obj) } log.Println("Files changed:") for f := range filesToUpdate { log.Println("\t", f.Name()) } if nerrs > 0 { return fmt.Errorf("failed to rewrite %d file%s", nerrs, plural(nerrs)) } return nil }