From 845abca4e3ccd90e71d02564828a909ddb514cc6 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 18 Aug 2014 22:31:56 -0400 Subject: [PATCH] go/types/typeutil: Dependencies utility returns transitive closure of import graph. (Used by forthcoming 'singlefile' command.) + Test. LGTM=bwkster, gri R=gri, bwkster CC=golang-codereviews https://golang.org/cl/130100043 --- go/types/typeutil/imports.go | 27 +++++++++++ go/types/typeutil/imports_test.go | 75 +++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 go/types/typeutil/imports.go create mode 100644 go/types/typeutil/imports_test.go diff --git a/go/types/typeutil/imports.go b/go/types/typeutil/imports.go new file mode 100644 index 0000000000..b7b8f4feb2 --- /dev/null +++ b/go/types/typeutil/imports.go @@ -0,0 +1,27 @@ +package typeutil + +import "code.google.com/p/go.tools/go/types" + +// Dependencies returns all dependencies of the specified packages. +// +// Dependent packages appear in topological order: if package P imports +// package Q, Q appears earlier than P in the result. +// The algorithm follows import statements in the order they +// appear in the source code, so the result is a total order. +// +func Dependencies(pkgs ...*types.Package) []*types.Package { + var result []*types.Package + seen := make(map[*types.Package]bool) + var visit func(pkgs []*types.Package) + visit = func(pkgs []*types.Package) { + for _, p := range pkgs { + if !seen[p] { + seen[p] = true + visit(p.Imports()) + result = append(result, p) + } + } + } + visit(pkgs) + return result +} diff --git a/go/types/typeutil/imports_test.go b/go/types/typeutil/imports_test.go new file mode 100644 index 0000000000..aca6c1a0e5 --- /dev/null +++ b/go/types/typeutil/imports_test.go @@ -0,0 +1,75 @@ +package typeutil_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "testing" + + "code.google.com/p/go.tools/go/types" + "code.google.com/p/go.tools/go/types/typeutil" +) + +func TestDependencies(t *testing.T) { + packages := make(map[string]*types.Package) + conf := types.Config{ + Packages: packages, + Import: func(_ map[string]*types.Package, path string) (*types.Package, error) { + return packages[path], nil + }, + } + fset := token.NewFileSet() + + // All edges go to the right. + // /--D--B--A + // F \_C_/ + // \__E_/ + for i, content := range []string{ + `package A`, + `package C; import (_ "A")`, + `package B; import (_ "A")`, + `package E; import (_ "C")`, + `package D; import (_ "B"; _ "C")`, + `package F; import (_ "D"; _ "E")`, + } { + f, err := parser.ParseFile(fset, fmt.Sprintf("%d.go", i), content, 0) + if err != nil { + t.Fatal(err) + } + pkg, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + packages[pkg.Path()] = pkg + } + + for _, test := range []struct { + roots, want string + }{ + {"A", "A"}, + {"B", "AB"}, + {"C", "AC"}, + {"D", "ABCD"}, + {"E", "ACE"}, + {"F", "ABCDEF"}, + + {"BE", "ABCE"}, + {"EB", "ACEB"}, + {"DE", "ABCDE"}, + {"ED", "ACEBD"}, + {"EF", "ACEBDF"}, + } { + var pkgs []*types.Package + for _, r := range test.roots { + pkgs = append(pkgs, conf.Packages[string(r)]) + } + var got string + for _, p := range typeutil.Dependencies(pkgs...) { + got += p.Path() + } + if got != test.want { + t.Errorf("Dependencies(%q) = %q, want %q", test.roots, got, test.want) + } + } +}