imports: rename to internal/imports

For various reasons we need an internal-facing imports API. Move imports
to internal/imports, leaving behind a small wrapper package. The wrapper
package captures the globals at time of call into the options struct.

Also converts the last goimports tests to use the test helpers, and
fixes go/packages in module mode to work with empty modules, which was
necessary to get those last tests converted.

Change-Id: Ib1212c67908741a1800b992ef1935d563c6ade32
Reviewed-on: https://go-review.googlesource.com/c/tools/+/175437
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/go/packages/packagestest/modules.go b/go/packages/packagestest/modules.go
index 0a660bb..2e06612 100644
--- a/go/packages/packagestest/modules.go
+++ b/go/packages/packagestest/modules.go
@@ -56,7 +56,13 @@
 	// other weird stuff, and will be the working dir for the go command.
 	// It depends on all the other modules.
 	primaryDir := primaryDir(exported)
+	if err := os.MkdirAll(primaryDir, 0755); err != nil {
+		return err
+	}
 	exported.Config.Dir = primaryDir
+	if exported.written[exported.primary] == nil {
+		exported.written[exported.primary] = make(map[string]string)
+	}
 	exported.written[exported.primary]["go.mod"] = filepath.Join(primaryDir, "go.mod")
 	primaryGomod := "module " + exported.primary + "\nrequire (\n"
 	for other := range exported.written {
diff --git a/imports/forward.go b/imports/forward.go
new file mode 100644
index 0000000..bb661d5
--- /dev/null
+++ b/imports/forward.go
@@ -0,0 +1,59 @@
+// Package imports implements a Go pretty-printer (like package "go/format")
+// that also adds or removes import statements as necessary.
+package imports // import "golang.org/x/tools/imports"
+
+import (
+	"go/build"
+
+	intimp "golang.org/x/tools/internal/imports"
+)
+
+// Options specifies options for processing files.
+type Options struct {
+	Fragment  bool // Accept fragment of a source file (no package statement)
+	AllErrors bool // Report all errors (not just the first 10 on different lines)
+
+	Comments  bool // Print comments (true if nil *Options provided)
+	TabIndent bool // Use tabs for indent (true if nil *Options provided)
+	TabWidth  int  // Tab width (8 if nil *Options provided)
+
+	FormatOnly bool // Disable the insertion and deletion of imports
+}
+
+// Debug controls verbose logging.
+var Debug = false
+
+// LocalPrefix is a comma-separated string of import path prefixes, which, if
+// set, instructs Process to sort the import paths with the given prefixes
+// into another group after 3rd-party packages.
+var LocalPrefix string
+
+// Process formats and adjusts imports for the provided file.
+// If opt is nil the defaults are used.
+//
+// Note that filename's directory influences which imports can be chosen,
+// so it is important that filename be accurate.
+// To process data ``as if'' it were in filename, pass the data as a non-nil src.
+func Process(filename string, src []byte, opt *Options) ([]byte, error) {
+	intopt := &intimp.Options{
+		Env: &intimp.ProcessEnv{
+			GOPATH:      build.Default.GOPATH,
+			GOROOT:      build.Default.GOROOT,
+			Debug:       Debug,
+			LocalPrefix: LocalPrefix,
+		},
+		AllErrors:  opt.AllErrors,
+		Comments:   opt.Comments,
+		FormatOnly: opt.FormatOnly,
+		Fragment:   opt.Fragment,
+		TabIndent:  opt.TabIndent,
+		TabWidth:   opt.TabWidth,
+	}
+	return intimp.Process(filename, src, intopt)
+}
+
+// VendorlessPath returns the devendorized version of the import path ipath.
+// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b".
+func VendorlessPath(ipath string) string {
+	return intimp.VendorlessPath(ipath)
+}
diff --git a/imports/fix.go b/internal/imports/fix.go
similarity index 93%
rename from imports/fix.go
rename to internal/imports/fix.go
index 777d28c..3af3a14 100644
--- a/imports/fix.go
+++ b/internal/imports/fix.go
@@ -31,39 +31,27 @@
 	"golang.org/x/tools/internal/gopathwalk"
 )
 
-// Debug controls verbose logging.
-var Debug = false
-
-// LocalPrefix is a comma-separated string of import path prefixes, which, if
-// set, instructs Process to sort the import paths with the given prefixes
-// into another group after 3rd-party packages.
-var LocalPrefix string
-
-func localPrefixes() []string {
-	if LocalPrefix != "" {
-		return strings.Split(LocalPrefix, ",")
-	}
-	return nil
-}
-
 // importToGroup is a list of functions which map from an import path to
 // a group number.
-var importToGroup = []func(importPath string) (num int, ok bool){
-	func(importPath string) (num int, ok bool) {
-		for _, p := range localPrefixes() {
+var importToGroup = []func(env *ProcessEnv, importPath string) (num int, ok bool){
+	func(env *ProcessEnv, importPath string) (num int, ok bool) {
+		if env.LocalPrefix == "" {
+			return
+		}
+		for _, p := range strings.Split(env.LocalPrefix, ",") {
 			if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath {
 				return 3, true
 			}
 		}
 		return
 	},
-	func(importPath string) (num int, ok bool) {
+	func(_ *ProcessEnv, importPath string) (num int, ok bool) {
 		if strings.HasPrefix(importPath, "appengine") {
 			return 2, true
 		}
 		return
 	},
-	func(importPath string) (num int, ok bool) {
+	func(_ *ProcessEnv, importPath string) (num int, ok bool) {
 		if strings.Contains(importPath, ".") {
 			return 1, true
 		}
@@ -71,9 +59,9 @@
 	},
 }
 
-func importGroup(importPath string) int {
+func importGroup(env *ProcessEnv, importPath string) int {
 	for _, fn := range importToGroup {
-		if n, ok := fn(importPath); ok {
+		if n, ok := fn(env, importPath); ok {
 			return n
 		}
 	}
@@ -241,7 +229,7 @@
 	fset                 *token.FileSet // fset used to parse f and its siblings.
 	f                    *ast.File      // the file being fixed.
 	srcDir               string         // the directory containing f.
-	fixEnv               *fixEnv        // the environment to use for go commands, etc.
+	env                  *ProcessEnv    // the environment to use for go commands, etc.
 	loadRealPackageNames bool           // if true, load package names from disk rather than guessing them.
 	otherFiles           []*ast.File    // sibling files.
 
@@ -266,7 +254,7 @@
 		unknown = append(unknown, imp.importPath)
 	}
 
-	names, err := p.fixEnv.getResolver().loadPackageNames(unknown, p.srcDir)
+	names, err := p.env.getResolver().loadPackageNames(unknown, p.srcDir)
 	if err != nil {
 		return err
 	}
@@ -324,7 +312,7 @@
 	if p.loadRealPackageNames {
 		err := p.loadPackageNames(append(imports, p.candidates...))
 		if err != nil {
-			if Debug {
+			if p.env.Debug {
 				log.Printf("loading package names: %v", err)
 			}
 			return false
@@ -448,13 +436,13 @@
 // easily be extended by adding a file with an init function.
 var fixImports = fixImportsDefault
 
-func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *fixEnv) error {
+func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) error {
 	abs, err := filepath.Abs(filename)
 	if err != nil {
 		return err
 	}
 	srcDir := filepath.Dir(abs)
-	if Debug {
+	if env.Debug {
 		log.Printf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
 	}
 
@@ -486,7 +474,7 @@
 	// Third pass: get real package names where we had previously used
 	// the naive algorithm. This is the first step that will use the
 	// environment, so we provide it here for the first time.
-	p = &pass{fset: fset, f: f, srcDir: srcDir, fixEnv: env}
+	p = &pass{fset: fset, f: f, srcDir: srcDir, env: env}
 	p.loadRealPackageNames = true
 	p.otherFiles = otherFiles
 	if p.load() {
@@ -510,9 +498,12 @@
 	return nil
 }
 
-// fixEnv contains environment variables and settings that affect the use of
+// ProcessEnv contains environment variables and settings that affect the use of
 // the go command, the go/build package, etc.
-type fixEnv struct {
+type ProcessEnv struct {
+	LocalPrefix string
+	Debug       bool
+
 	// If non-empty, these will be used instead of the
 	// process-wide values.
 	GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS string
@@ -524,7 +515,7 @@
 	resolver resolver
 }
 
-func (e *fixEnv) env() []string {
+func (e *ProcessEnv) env() []string {
 	env := os.Environ()
 	add := func(k, v string) {
 		if v != "" {
@@ -542,7 +533,7 @@
 	return env
 }
 
-func (e *fixEnv) getResolver() resolver {
+func (e *ProcessEnv) getResolver() resolver {
 	if e.resolver != nil {
 		return e.resolver
 	}
@@ -557,7 +548,7 @@
 	return &moduleResolver{env: e}
 }
 
-func (e *fixEnv) newPackagesConfig(mode packages.LoadMode) *packages.Config {
+func (e *ProcessEnv) newPackagesConfig(mode packages.LoadMode) *packages.Config {
 	return &packages.Config{
 		Mode: mode,
 		Dir:  e.WorkingDir,
@@ -565,14 +556,14 @@
 	}
 }
 
-func (e *fixEnv) buildContext() *build.Context {
+func (e *ProcessEnv) buildContext() *build.Context {
 	ctx := build.Default
 	ctx.GOROOT = e.GOROOT
 	ctx.GOPATH = e.GOPATH
 	return &ctx
 }
 
-func (e *fixEnv) invokeGo(args ...string) (*bytes.Buffer, error) {
+func (e *ProcessEnv) invokeGo(args ...string) (*bytes.Buffer, error) {
 	cmd := exec.Command("go", args...)
 	stdout := &bytes.Buffer{}
 	stderr := &bytes.Buffer{}
@@ -581,7 +572,7 @@
 	cmd.Env = e.env()
 	cmd.Dir = e.WorkingDir
 
-	if Debug {
+	if e.Debug {
 		defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
 	}
 	if err := cmd.Run(); err != nil {
@@ -632,7 +623,7 @@
 
 // gopathResolver implements resolver for GOPATH and module workspaces using go/packages.
 type goPackagesResolver struct {
-	env *fixEnv
+	env *ProcessEnv
 }
 
 func (r *goPackagesResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
@@ -680,7 +671,7 @@
 }
 
 func addExternalCandidates(pass *pass, refs references, filename string) error {
-	dirScan, err := pass.fixEnv.getResolver().scan(refs)
+	dirScan, err := pass.env.getResolver().scan(refs)
 	if err != nil {
 		return err
 	}
@@ -707,7 +698,7 @@
 		go func(pkgName string, symbols map[string]bool) {
 			defer wg.Done()
 
-			found, err := findImport(ctx, pass.fixEnv, dirScan, pkgName, symbols, filename)
+			found, err := findImport(ctx, pass.env, dirScan, pkgName, symbols, filename)
 
 			if err != nil {
 				firstErrOnce.Do(func() {
@@ -778,7 +769,7 @@
 
 // gopathResolver implements resolver for GOPATH workspaces.
 type gopathResolver struct {
-	env *fixEnv
+	env *ProcessEnv
 }
 
 func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
@@ -791,7 +782,7 @@
 
 // importPathToNameGoPath finds out the actual package name, as declared in its .go files.
 // If there's a problem, it returns "".
-func importPathToName(env *fixEnv, importPath, srcDir string) (packageName string) {
+func importPathToName(env *ProcessEnv, importPath, srcDir string) (packageName string) {
 	// Fast path for standard library without going to disk.
 	if _, ok := stdlib[importPath]; ok {
 		return path.Base(importPath) // stdlib packages always match their paths.
@@ -927,7 +918,7 @@
 			dir:             dir,
 		})
 	}
-	gopathwalk.Walk(gopathwalk.SrcDirsRoots(r.env.buildContext()), add, gopathwalk.Options{Debug: Debug, ModulesEnabled: false})
+	gopathwalk.Walk(gopathwalk.SrcDirsRoots(r.env.buildContext()), add, gopathwalk.Options{Debug: r.env.Debug, ModulesEnabled: false})
 	return result, nil
 }
 
@@ -946,8 +937,8 @@
 
 // loadExports returns the set of exported symbols in the package at dir.
 // It returns nil on error or if the package name in dir does not match expectPackage.
-func loadExports(ctx context.Context, env *fixEnv, expectPackage string, pkg *pkg) (map[string]bool, error) {
-	if Debug {
+func loadExports(ctx context.Context, env *ProcessEnv, expectPackage string, pkg *pkg) (map[string]bool, error) {
+	if env.Debug {
 		log.Printf("loading exports in dir %s (seeking package %s)", pkg.dir, expectPackage)
 	}
 	if pkg.goPackage != nil {
@@ -1020,7 +1011,7 @@
 		}
 	}
 
-	if Debug {
+	if env.Debug {
 		exportList := make([]string, 0, len(exports))
 		for k := range exports {
 			exportList = append(exportList, k)
@@ -1033,7 +1024,7 @@
 
 // findImport searches for a package with the given symbols.
 // If no package is found, findImport returns ("", false, nil)
-func findImport(ctx context.Context, env *fixEnv, dirScan []*pkg, pkgName string, symbols map[string]bool, filename string) (*pkg, error) {
+func findImport(ctx context.Context, env *ProcessEnv, dirScan []*pkg, pkgName string, symbols map[string]bool, filename string) (*pkg, error) {
 	pkgDir, err := filepath.Abs(filename)
 	if err != nil {
 		return nil, err
@@ -1056,7 +1047,7 @@
 	// ones.  Note that this sorts by the de-vendored name, so
 	// there's no "penalty" for vendoring.
 	sort.Sort(byDistanceOrImportPathShortLength(candidates))
-	if Debug {
+	if env.Debug {
 		for i, c := range candidates {
 			log.Printf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir)
 		}
@@ -1097,7 +1088,7 @@
 
 				exports, err := loadExports(ctx, env, pkgName, c.pkg)
 				if err != nil {
-					if Debug {
+					if env.Debug {
 						log.Printf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)
 					}
 					resc <- nil
diff --git a/imports/fix_test.go b/internal/imports/fix_test.go
similarity index 93%
rename from imports/fix_test.go
rename to internal/imports/fix_test.go
index 101978c..4f10696 100644
--- a/imports/fix_test.go
+++ b/internal/imports/fix_test.go
@@ -5,6 +5,7 @@
 package imports
 
 import (
+	"flag"
 	"fmt"
 	"path/filepath"
 	"runtime"
@@ -14,6 +15,8 @@
 	"golang.org/x/tools/go/packages/packagestest"
 )
 
+var testDebug = flag.Bool("debug", false, "enable debug output")
+
 var tests = []struct {
 	name       string
 	formatOnly bool
@@ -1116,8 +1119,7 @@
 }
 
 func TestSimpleCases(t *testing.T) {
-	defer func(lp string) { LocalPrefix = lp }(LocalPrefix)
-	LocalPrefix = "local.com,github.com/local"
+	const localPrefix = "local.com,github.com/local"
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			options := &Options{
@@ -1163,7 +1165,11 @@
 						Files: fm{"bar/x.go": "package bar\nfunc Bar(){}\n"},
 					},
 				},
-			}.processTest(t, "golang.org/fake", "x.go", nil, options, tt.out)
+			}.test(t, func(t *goimportTest) {
+				t.env.LocalPrefix = localPrefix
+				t.assertProcessEquals("golang.org/fake", "x.go", nil, options, tt.out)
+			})
+
 		})
 	}
 }
@@ -1485,20 +1491,28 @@
 		for _, sym := range tt.symbols {
 			input += fmt.Sprintf("var _ = %s.%s\n", tt.pkg, sym)
 		}
-		buf, err := Process("x.go", []byte(input), &Options{})
-		if err != nil {
-			t.Fatal(err)
-		}
-		if got := string(buf); !strings.Contains(got, tt.want) {
-			t.Errorf("Process(%q) = %q, wanted it to contain %q", input, buf, tt.want)
-		}
+		testConfig{
+			module: packagestest.Module{
+				Name:  "foo.com",
+				Files: fm{"x.go": input},
+			},
+		}.test(t, func(t *goimportTest) {
+			buf, err := t.process("foo.com", "x.go", nil, nil)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if got := string(buf); !strings.Contains(got, tt.want) {
+				t.Errorf("Process(%q) = %q, wanted it to contain %q", input, buf, tt.want)
+			}
+		})
 	}
 }
 
 type testConfig struct {
-	gopathOnly bool
-	module     packagestest.Module
-	modules    []packagestest.Module
+	gopathOnly             bool
+	goPackagesIncompatible bool
+	module                 packagestest.Module
+	modules                []packagestest.Module
 }
 
 // fm is the type for a packagestest.Module's Files, abbreviated for shorter lines.
@@ -1522,6 +1536,12 @@
 
 			forceGoPackages := false
 			var exporter packagestest.Exporter
+			if c.gopathOnly && strings.HasPrefix(kind, "Modules") {
+				t.Skip("test marked GOPATH-only")
+			}
+			if c.goPackagesIncompatible && strings.HasSuffix(kind, "_GoPackages") {
+				t.Skip("test marked go/packages-incompatible")
+			}
 			switch kind {
 			case "GOPATH":
 				exporter = packagestest.GOPATH
@@ -1529,14 +1549,8 @@
 				exporter = packagestest.GOPATH
 				forceGoPackages = true
 			case "Modules":
-				if c.gopathOnly {
-					t.Skip("test marked GOPATH-only")
-				}
 				exporter = packagestest.Modules
 			case "Modules_GoPackages":
-				if c.gopathOnly {
-					t.Skip("test marked GOPATH-only")
-				}
 				exporter = packagestest.Modules
 				forceGoPackages = true
 			default:
@@ -1554,12 +1568,13 @@
 
 			it := &goimportTest{
 				T: t,
-				fixEnv: &fixEnv{
+				env: &ProcessEnv{
 					GOROOT:          env["GOROOT"],
 					GOPATH:          env["GOPATH"],
 					GO111MODULE:     env["GO111MODULE"],
 					WorkingDir:      exported.Config.Dir,
 					ForceGoPackages: forceGoPackages,
+					Debug:           *testDebug,
 				},
 				exported: exported,
 			}
@@ -1572,23 +1587,36 @@
 	t.Helper()
 	c.test(t, func(t *goimportTest) {
 		t.Helper()
-		t.process(module, file, contents, opts, want)
+		t.assertProcessEquals(module, file, contents, opts, want)
 	})
 }
 
 type goimportTest struct {
 	*testing.T
-	fixEnv   *fixEnv
+	env      *ProcessEnv
 	exported *packagestest.Exported
 }
 
-func (t *goimportTest) process(module, file string, contents []byte, opts *Options, want string) {
+func (t *goimportTest) process(module, file string, contents []byte, opts *Options) ([]byte, error) {
 	t.Helper()
 	f := t.exported.File(module, file)
 	if f == "" {
 		t.Fatalf("%v not found in exported files (typo in filename?)", file)
 	}
-	buf, err := process(f, contents, opts, t.fixEnv)
+	return t.processNonModule(f, contents, opts)
+}
+
+func (t *goimportTest) processNonModule(file string, contents []byte, opts *Options) ([]byte, error) {
+	if opts == nil {
+		opts = &Options{Comments: true, TabIndent: true, TabWidth: 8}
+	}
+	opts.Env = t.env
+	opts.Env.Debug = *testDebug
+	return Process(file, contents, opts)
+}
+
+func (t *goimportTest) assertProcessEquals(module, file string, contents []byte, opts *Options, want string) {
+	buf, err := t.process(module, file, contents, opts)
 	if err != nil {
 		t.Fatalf("Process() = %v", err)
 	}
@@ -1775,9 +1803,8 @@
 					Files: fm{"t.go": tt.src},
 				}}, tt.modules...),
 			}.test(t, func(t *goimportTest) {
-				defer func(s string) { LocalPrefix = s }(LocalPrefix)
-				LocalPrefix = tt.localPrefix
-				t.process("test.com", "t.go", nil, nil, tt.want)
+				t.env.LocalPrefix = tt.localPrefix
+				t.assertProcessEquals("test.com", "t.go", nil, nil, tt.want)
 			})
 		})
 	}
@@ -1827,7 +1854,7 @@
 		if strings.Contains(t.Name(), "GoPackages") {
 			t.Skip("go/packages does not ignore package main")
 		}
-		r := t.fixEnv.getResolver()
+		r := t.env.getResolver()
 		srcDir := filepath.Dir(t.exported.File("example.net/pkg", "z.go"))
 		names, err := r.loadPackageNames([]string{"example.net/pkg"}, srcDir)
 		if err != nil {
@@ -2220,13 +2247,19 @@
 
 // Issue 20941: this used to panic on Windows.
 func TestProcessStdin(t *testing.T) {
-	got, err := Process("<standard input>", []byte("package main\nfunc main() {\n\tfmt.Println(123)\n}\n"), nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !strings.Contains(string(got), `"fmt"`) {
-		t.Errorf("expected fmt import; got: %s", got)
-	}
+	testConfig{
+		module: packagestest.Module{
+			Name: "foo.com",
+		},
+	}.test(t, func(t *goimportTest) {
+		got, err := t.processNonModule("<standard input>", []byte("package main\nfunc main() {\n\tfmt.Println(123)\n}\n"), nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !strings.Contains(string(got), `"fmt"`) {
+			t.Errorf("expected fmt import; got: %s", got)
+		}
+	})
 }
 
 // Tests LocalPackagePromotion when there is a local package that matches, it
@@ -2324,14 +2357,21 @@
 
 var _ = &bytes.Buffer{}
 `
+	testConfig{
+		goPackagesIncompatible: true,
+		module: packagestest.Module{
+			Name: "mycompany.net",
+		},
+	}.test(t, func(t *goimportTest) {
+		buf, err := t.processNonModule("mycompany.net/tool/main.go", []byte(input), nil)
+		if err != nil {
+			t.Fatalf("Process() = %v", err)
+		}
+		if string(buf) != want {
+			t.Errorf("Got:\n%s\nWant:\n%s", buf, want)
+		}
+	})
 
-	buf, err := Process("mycompany.net/tool/main.go", []byte(input), nil)
-	if err != nil {
-		t.Fatalf("Process() = %v", err)
-	}
-	if string(buf) != want {
-		t.Errorf("Got:\n%s\nWant:\n%s", buf, want)
-	}
 }
 
 // Ensures a token as large as 500000 bytes can be handled
diff --git a/imports/imports.go b/internal/imports/imports.go
similarity index 90%
rename from imports/imports.go
rename to internal/imports/imports.go
index 07101cb..8b63908 100644
--- a/imports/imports.go
+++ b/internal/imports/imports.go
@@ -6,14 +6,13 @@
 
 // Package imports implements a Go pretty-printer (like package "go/format")
 // that also adds or removes import statements as necessary.
-package imports // import "golang.org/x/tools/imports"
+package imports
 
 import (
 	"bufio"
 	"bytes"
 	"fmt"
 	"go/ast"
-	"go/build"
 	"go/format"
 	"go/parser"
 	"go/printer"
@@ -27,8 +26,10 @@
 	"golang.org/x/tools/go/ast/astutil"
 )
 
-// Options specifies options for processing files.
+// Options is golang.org/x/tools/imports.Options with extra internal-only options.
 type Options struct {
+	Env *ProcessEnv // The environment to use. Note: this contains the cached module and filesystem state.
+
 	Fragment  bool // Accept fragment of a source file (no package statement)
 	AllErrors bool // Report all errors (not just the first 10 on different lines)
 
@@ -39,18 +40,8 @@
 	FormatOnly bool // Disable the insertion and deletion of imports
 }
 
-// Process formats and adjusts imports for the provided file.
-// If opt is nil the defaults are used.
-//
-// Note that filename's directory influences which imports can be chosen,
-// so it is important that filename be accurate.
-// To process data ``as if'' it were in filename, pass the data as a non-nil src.
+// Process implements golang.org/x/tools/imports.Process with explicit context in env.
 func Process(filename string, src []byte, opt *Options) ([]byte, error) {
-	env := &fixEnv{GOPATH: build.Default.GOPATH, GOROOT: build.Default.GOROOT}
-	return process(filename, src, opt, env)
-}
-
-func process(filename string, src []byte, opt *Options, env *fixEnv) ([]byte, error) {
 	if opt == nil {
 		opt = &Options{Comments: true, TabIndent: true, TabWidth: 8}
 	}
@@ -69,12 +60,12 @@
 	}
 
 	if !opt.FormatOnly {
-		if err := fixImports(fileSet, file, filename, env); err != nil {
+		if err := fixImports(fileSet, file, filename, opt.Env); err != nil {
 			return nil, err
 		}
 	}
 
-	sortImports(fileSet, file)
+	sortImports(opt.Env, fileSet, file)
 	imps := astutil.Imports(fileSet, file)
 	var spacesBefore []string // import paths we need spaces before
 	for _, impSection := range imps {
@@ -85,7 +76,7 @@
 		lastGroup := -1
 		for _, importSpec := range impSection {
 			importPath, _ := strconv.Unquote(importSpec.Path.Value)
-			groupNum := importGroup(importPath)
+			groupNum := importGroup(opt.Env, importPath)
 			if groupNum != lastGroup && lastGroup != -1 {
 				spacesBefore = append(spacesBefore, importPath)
 			}
diff --git a/imports/mkindex.go b/internal/imports/mkindex.go
similarity index 99%
rename from imports/mkindex.go
rename to internal/imports/mkindex.go
index 755e239..ef8c0d2 100644
--- a/imports/mkindex.go
+++ b/internal/imports/mkindex.go
@@ -8,7 +8,7 @@
 // standard library. The file is intended to be built as part of the imports
 // package, so that the package may be used in environments where a GOROOT is
 // not available (such as App Engine).
-package main
+package imports
 
 import (
 	"bytes"
diff --git a/imports/mkstdlib.go b/internal/imports/mkstdlib.go
similarity index 99%
rename from imports/mkstdlib.go
rename to internal/imports/mkstdlib.go
index c8865e5..f67b5c1 100644
--- a/imports/mkstdlib.go
+++ b/internal/imports/mkstdlib.go
@@ -3,7 +3,7 @@
 // mkstdlib generates the zstdlib.go file, containing the Go standard
 // library API symbols. It's baked into the binary to avoid scanning
 // GOPATH in the common case.
-package main
+package imports
 
 import (
 	"bufio"
diff --git a/imports/mod.go b/internal/imports/mod.go
similarity index 98%
rename from imports/mod.go
rename to internal/imports/mod.go
index 018c43c..a072214 100644
--- a/imports/mod.go
+++ b/internal/imports/mod.go
@@ -22,7 +22,7 @@
 // moduleResolver implements resolver for modules using the go command as little
 // as feasible.
 type moduleResolver struct {
-	env *fixEnv
+	env *ProcessEnv
 
 	initialized   bool
 	main          *moduleJSON
@@ -62,7 +62,7 @@
 			return err
 		}
 		if mod.Dir == "" {
-			if Debug {
+			if r.env.Debug {
 				log.Printf("module %v has not been downloaded and will be ignored", mod.Path)
 			}
 			// Can't do anything with a module that's not downloaded.
@@ -253,7 +253,7 @@
 			matches := modCacheRegexp.FindStringSubmatch(subdir)
 			modPath, err := module.DecodePath(filepath.ToSlash(matches[1]))
 			if err != nil {
-				if Debug {
+				if r.env.Debug {
 					log.Printf("decoding module cache path %q: %v", subdir, err)
 				}
 				return
@@ -303,7 +303,7 @@
 			importPathShort: VendorlessPath(importPath),
 			dir:             dir,
 		})
-	}, gopathwalk.Options{Debug: Debug, ModulesEnabled: true})
+	}, gopathwalk.Options{Debug: r.env.Debug, ModulesEnabled: true})
 	return result, nil
 }
 
diff --git a/imports/mod_112_test.go b/internal/imports/mod_112_test.go
similarity index 100%
rename from imports/mod_112_test.go
rename to internal/imports/mod_112_test.go
diff --git a/imports/mod_test.go b/internal/imports/mod_test.go
similarity index 99%
rename from imports/mod_test.go
rename to internal/imports/mod_test.go
index f2fb76e..cda4147 100644
--- a/imports/mod_test.go
+++ b/internal/imports/mod_test.go
@@ -485,7 +485,7 @@
 
 type modTest struct {
 	*testing.T
-	env      *fixEnv
+	env      *ProcessEnv
 	resolver *moduleResolver
 	cleanup  func()
 }
@@ -515,7 +515,7 @@
 		t.Fatal(err)
 	}
 
-	env := &fixEnv{
+	env := &ProcessEnv{
 		GOROOT:      build.Default.GOROOT,
 		GOPATH:      filepath.Join(dir, "gopath"),
 		GO111MODULE: "on",
diff --git a/imports/proxy_112_test.go b/internal/imports/proxy_112_test.go
similarity index 100%
rename from imports/proxy_112_test.go
rename to internal/imports/proxy_112_test.go
diff --git a/imports/proxy_113_test.go b/internal/imports/proxy_113_test.go
similarity index 100%
rename from imports/proxy_113_test.go
rename to internal/imports/proxy_113_test.go
diff --git a/imports/sortimports.go b/internal/imports/sortimports.go
similarity index 85%
rename from imports/sortimports.go
rename to internal/imports/sortimports.go
index f3dd56c..0a156fe 100644
--- a/imports/sortimports.go
+++ b/internal/imports/sortimports.go
@@ -15,7 +15,7 @@
 
 // sortImports sorts runs of consecutive import lines in import blocks in f.
 // It also removes duplicate imports when it is possible to do so without data loss.
-func sortImports(fset *token.FileSet, f *ast.File) {
+func sortImports(env *ProcessEnv, fset *token.FileSet, f *ast.File) {
 	for i, d := range f.Decls {
 		d, ok := d.(*ast.GenDecl)
 		if !ok || d.Tok != token.IMPORT {
@@ -40,11 +40,11 @@
 		for j, s := range d.Specs {
 			if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line {
 				// j begins a new run.  End this one.
-				specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...)
+				specs = append(specs, sortSpecs(env, fset, f, d.Specs[i:j])...)
 				i = j
 			}
 		}
-		specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...)
+		specs = append(specs, sortSpecs(env, fset, f, d.Specs[i:])...)
 		d.Specs = specs
 
 		// Deduping can leave a blank line before the rparen; clean that up.
@@ -95,7 +95,7 @@
 	End   token.Pos
 }
 
-func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec {
+func sortSpecs(env *ProcessEnv, fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec {
 	// Can't short-circuit here even if specs are already sorted,
 	// since they might yet need deduplication.
 	// A lone import, however, may be safely ignored.
@@ -144,7 +144,7 @@
 	// Reassign the import paths to have the same position sequence.
 	// Reassign each comment to abut the end of its spec.
 	// Sort the comments by new position.
-	sort.Sort(byImportSpec(specs))
+	sort.Sort(byImportSpec{env, specs})
 
 	// Dedup. Thanks to our sorting, we can just consider
 	// adjacent pairs of imports.
@@ -197,16 +197,19 @@
 	return specs
 }
 
-type byImportSpec []ast.Spec // slice of *ast.ImportSpec
+type byImportSpec struct {
+	env   *ProcessEnv
+	specs []ast.Spec // slice of *ast.ImportSpec
+}
 
-func (x byImportSpec) Len() int      { return len(x) }
-func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x byImportSpec) Len() int      { return len(x.specs) }
+func (x byImportSpec) Swap(i, j int) { x.specs[i], x.specs[j] = x.specs[j], x.specs[i] }
 func (x byImportSpec) Less(i, j int) bool {
-	ipath := importPath(x[i])
-	jpath := importPath(x[j])
+	ipath := importPath(x.specs[i])
+	jpath := importPath(x.specs[j])
 
-	igroup := importGroup(ipath)
-	jgroup := importGroup(jpath)
+	igroup := importGroup(x.env, ipath)
+	jgroup := importGroup(x.env, jpath)
 	if igroup != jgroup {
 		return igroup < jgroup
 	}
@@ -214,13 +217,13 @@
 	if ipath != jpath {
 		return ipath < jpath
 	}
-	iname := importName(x[i])
-	jname := importName(x[j])
+	iname := importName(x.specs[i])
+	jname := importName(x.specs[j])
 
 	if iname != jname {
 		return iname < jname
 	}
-	return importComment(x[i]) < importComment(x[j])
+	return importComment(x.specs[i]) < importComment(x.specs[j])
 }
 
 type byCommentPos []*ast.CommentGroup
diff --git a/imports/testdata/mod/example.com_v1.0.0.txt b/internal/imports/testdata/mod/example.com_v1.0.0.txt
similarity index 100%
rename from imports/testdata/mod/example.com_v1.0.0.txt
rename to internal/imports/testdata/mod/example.com_v1.0.0.txt
diff --git a/imports/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt b/internal/imports/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt
similarity index 100%
rename from imports/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt
rename to internal/imports/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt
diff --git "a/imports/testdata/mod/rsc.io_\041q\041u\041o\041t\041e_v1.5.2.txt" "b/internal/imports/testdata/mod/rsc.io_\041q\041u\041o\041t\041e_v1.5.2.txt"
similarity index 100%
rename from "imports/testdata/mod/rsc.io_\041q\041u\041o\041t\041e_v1.5.2.txt"
rename to "internal/imports/testdata/mod/rsc.io_\041q\041u\041o\041t\041e_v1.5.2.txt"
diff --git "a/imports/testdata/mod/rsc.io_\041q\041u\041o\041t\041e_v1.5.3-\041p\041r\041e.txt" "b/internal/imports/testdata/mod/rsc.io_\041q\041u\041o\041t\041e_v1.5.3-\041p\041r\041e.txt"
similarity index 100%
rename from "imports/testdata/mod/rsc.io_\041q\041u\041o\041t\041e_v1.5.3-\041p\041r\041e.txt"
rename to "internal/imports/testdata/mod/rsc.io_\041q\041u\041o\041t\041e_v1.5.3-\041p\041r\041e.txt"
diff --git a/imports/testdata/mod/rsc.io_quote_v1.5.1.txt b/internal/imports/testdata/mod/rsc.io_quote_v1.5.1.txt
similarity index 100%
rename from imports/testdata/mod/rsc.io_quote_v1.5.1.txt
rename to internal/imports/testdata/mod/rsc.io_quote_v1.5.1.txt
diff --git a/imports/testdata/mod/rsc.io_quote_v1.5.2.txt b/internal/imports/testdata/mod/rsc.io_quote_v1.5.2.txt
similarity index 100%
rename from imports/testdata/mod/rsc.io_quote_v1.5.2.txt
rename to internal/imports/testdata/mod/rsc.io_quote_v1.5.2.txt
diff --git a/imports/testdata/mod/rsc.io_quote_v2_v2.0.1.txt b/internal/imports/testdata/mod/rsc.io_quote_v2_v2.0.1.txt
similarity index 100%
rename from imports/testdata/mod/rsc.io_quote_v2_v2.0.1.txt
rename to internal/imports/testdata/mod/rsc.io_quote_v2_v2.0.1.txt
diff --git a/imports/testdata/mod/rsc.io_quote_v3_v3.0.0.txt b/internal/imports/testdata/mod/rsc.io_quote_v3_v3.0.0.txt
similarity index 100%
rename from imports/testdata/mod/rsc.io_quote_v3_v3.0.0.txt
rename to internal/imports/testdata/mod/rsc.io_quote_v3_v3.0.0.txt
diff --git a/imports/testdata/mod/rsc.io_sampler_v1.3.0.txt b/internal/imports/testdata/mod/rsc.io_sampler_v1.3.0.txt
similarity index 100%
rename from imports/testdata/mod/rsc.io_sampler_v1.3.0.txt
rename to internal/imports/testdata/mod/rsc.io_sampler_v1.3.0.txt
diff --git a/imports/testdata/mod/rsc.io_sampler_v1.3.1.txt b/internal/imports/testdata/mod/rsc.io_sampler_v1.3.1.txt
similarity index 100%
rename from imports/testdata/mod/rsc.io_sampler_v1.3.1.txt
rename to internal/imports/testdata/mod/rsc.io_sampler_v1.3.1.txt
diff --git a/imports/zstdlib.go b/internal/imports/zstdlib.go
similarity index 100%
rename from imports/zstdlib.go
rename to internal/imports/zstdlib.go