internal/lsp, internal/imports: use the internal goimports library

This change modifies gopls to use the internal goimports library, which
allows us to manually configure the ProcessEnv. We also add a logger to
the ProcessEnv to allow this change not to conflict with gopls's logging
mechanism.

Fixes golang/go#32585

Change-Id: Ic9aae69c7cfbc9b1f2e66aa8d812175dbc0065ce
Reviewed-on: https://go-review.googlesource.com/c/tools/+/184198
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/imports/fix.go b/internal/imports/fix.go
index d9ba3b7..76a79e1 100644
--- a/internal/imports/fix.go
+++ b/internal/imports/fix.go
@@ -13,7 +13,6 @@
 	"go/parser"
 	"go/token"
 	"io/ioutil"
-	"log"
 	"os"
 	"os/exec"
 	"path"
@@ -246,6 +245,12 @@
 
 // loadPackageNames saves the package names for everything referenced by imports.
 func (p *pass) loadPackageNames(imports []*importInfo) error {
+	if p.env.Debug {
+		p.env.Logf("loading package names for %v packages", len(imports))
+		defer func() {
+			p.env.Logf("done loading package names for %v packages", len(imports))
+		}()
+	}
 	var unknown []string
 	for _, imp := range imports {
 		if _, ok := p.knownPackages[imp.importPath]; ok {
@@ -313,7 +318,7 @@
 		err := p.loadPackageNames(append(imports, p.candidates...))
 		if err != nil {
 			if p.env.Debug {
-				log.Printf("loading package names: %v", err)
+				p.env.Logf("loading package names: %v", err)
 			}
 			return false
 		}
@@ -443,7 +448,7 @@
 	}
 	srcDir := filepath.Dir(abs)
 	if env.Debug {
-		log.Printf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
+		env.Logf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
 	}
 
 	// First pass: looking only at f, and using the naive algorithm to
@@ -512,6 +517,9 @@
 	// If true, use go/packages regardless of the environment.
 	ForceGoPackages bool
 
+	// Logf is the default logger for the ProcessEnv.
+	Logf func(format string, args ...interface{})
+
 	resolver resolver
 }
 
@@ -577,7 +585,7 @@
 	cmd.Dir = e.WorkingDir
 
 	if e.Debug {
-		defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
+		defer func(start time.Time) { e.Logf("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
 	}
 	if err := cmd.Run(); err != nil {
 		return nil, fmt.Errorf("running go: %v (stderr:\n%s)", err, stderr)
@@ -943,7 +951,7 @@
 // It returns nil on error or if the package name in dir does not match expectPackage.
 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)
+		env.Logf("loading exports in dir %s (seeking package %s)", pkg.dir, expectPackage)
 	}
 	if pkg.goPackage != nil {
 		exports := map[string]bool{}
@@ -1021,7 +1029,7 @@
 			exportList = append(exportList, k)
 		}
 		sort.Strings(exportList)
-		log.Printf("loaded exports in dir %v (package %v): %v", pkg.dir, expectPackage, strings.Join(exportList, ", "))
+		env.Logf("loaded exports in dir %v (package %v): %v", pkg.dir, expectPackage, strings.Join(exportList, ", "))
 	}
 	return exports, nil
 }
@@ -1058,7 +1066,7 @@
 	sort.Sort(byDistanceOrImportPathShortLength(candidates))
 	if pass.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)
+			pass.env.Logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir)
 		}
 	}
 
@@ -1098,7 +1106,7 @@
 				exports, err := loadExports(ctx, pass.env, pkgName, c.pkg)
 				if err != nil {
 					if pass.env.Debug {
-						log.Printf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)
+						pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)
 					}
 					resc <- nil
 					return
diff --git a/internal/imports/imports.go b/internal/imports/imports.go
index 6253337..a47a815 100644
--- a/internal/imports/imports.go
+++ b/internal/imports/imports.go
@@ -19,6 +19,7 @@
 	"go/token"
 	"io"
 	"io/ioutil"
+	"log"
 	"regexp"
 	"strconv"
 	"strings"
@@ -50,6 +51,11 @@
 		src = b
 	}
 
+	// Set the logger if the user has not provided it.
+	if opt.Env.Logf == nil {
+		opt.Env.Logf = log.Printf
+	}
+
 	fileSet := token.NewFileSet()
 	file, adjust, err := parse(fileSet, filename, src, opt)
 	if err != nil {
diff --git a/internal/imports/mod.go b/internal/imports/mod.go
index a072214..a0ebd07 100644
--- a/internal/imports/mod.go
+++ b/internal/imports/mod.go
@@ -4,7 +4,6 @@
 	"bytes"
 	"encoding/json"
 	"io/ioutil"
-	"log"
 	"os"
 	"path"
 	"path/filepath"
@@ -63,7 +62,7 @@
 		}
 		if mod.Dir == "" {
 			if r.env.Debug {
-				log.Printf("module %v has not been downloaded and will be ignored", mod.Path)
+				r.env.Logf("module %v has not been downloaded and will be ignored", mod.Path)
 			}
 			// Can't do anything with a module that's not downloaded.
 			continue
@@ -254,7 +253,7 @@
 			modPath, err := module.DecodePath(filepath.ToSlash(matches[1]))
 			if err != nil {
 				if r.env.Debug {
-					log.Printf("decoding module cache path %q: %v", subdir, err)
+					r.env.Logf("decoding module cache path %q: %v", subdir, err)
 				}
 				return
 			}
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 6877650..2b6661e 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -84,7 +84,7 @@
 		return nil, nil, ctx.Err()
 	}
 
-	pkgs, err := packages.Load(v.buildConfig(), fmt.Sprintf("file=%s", f.filename()))
+	pkgs, err := packages.Load(v.Config(), fmt.Sprintf("file=%s", f.filename()))
 	if len(pkgs) == 0 {
 		if err == nil {
 			err = fmt.Errorf("go/packages.Load: no packages found for %s", f.filename())
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index a56dcbb..189a8d1 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -111,7 +111,7 @@
 
 // Config returns the configuration used for the view's interaction with the
 // go/packages API. It is shared across all views.
-func (v *view) buildConfig() *packages.Config {
+func (v *view) Config() *packages.Config {
 	// TODO: Should we cache the config and/or overlay somewhere?
 	return &packages.Config{
 		Dir:        v.folder.Filename(),
@@ -187,7 +187,7 @@
 // It assumes that the view is not active yet,
 // i.e. it has not been added to the session's list of views.
 func (v *view) buildBuiltinPkg() {
-	cfg := *v.buildConfig()
+	cfg := *v.Config()
 	pkgs, _ := packages.Load(&cfg, "builtin")
 	if len(pkgs) != 1 {
 		v.builtinPkg, _ = ast.NewPackage(cfg.Fset, nil, nil, nil)
diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go
index e5195e7..e1b0643 100644
--- a/internal/lsp/code_action.go
+++ b/internal/lsp/code_action.go
@@ -100,7 +100,7 @@
 	if err != nil {
 		return nil, err
 	}
-	edits, err := source.Imports(ctx, f, rng)
+	edits, err := source.Imports(ctx, view, f, rng)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index aa485a5..07200a3 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -10,10 +10,11 @@
 	"context"
 	"fmt"
 	"go/format"
+	"strings"
 
 	"golang.org/x/tools/go/ast/astutil"
 	"golang.org/x/tools/go/packages"
-	"golang.org/x/tools/imports"
+	"golang.org/x/tools/internal/imports"
 	"golang.org/x/tools/internal/lsp/diff"
 	"golang.org/x/tools/internal/span"
 )
@@ -33,12 +34,14 @@
 		return nil, fmt.Errorf("no exact AST node matching the specified range")
 	}
 	node := path[0]
+
+	fset := f.FileSet()
+	buf := &bytes.Buffer{}
+
 	// format.Node changes slightly from one release to another, so the version
 	// of Go used to build the LSP server will determine how it formats code.
 	// This should be acceptable for all users, who likely be prompted to rebuild
 	// the LSP server on each Go release.
-	fset := f.FileSet()
-	buf := &bytes.Buffer{}
 	if err := format.Node(buf, fset, node); err != nil {
 		return nil, err
 	}
@@ -46,7 +49,7 @@
 }
 
 // Imports formats a file using the goimports tool.
-func Imports(ctx context.Context, f GoFile, rng span.Range) ([]TextEdit, error) {
+func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]TextEdit, error) {
 	data, _, err := f.Handle(ctx).Read(ctx)
 	if err != nil {
 		return nil, err
@@ -58,7 +61,17 @@
 	if hasListErrors(pkg.GetErrors()) {
 		return nil, fmt.Errorf("%s has list errors, not running goimports", f.URI())
 	}
-	formatted, err := imports.Process(f.URI().Filename(), data, nil)
+	options := &imports.Options{
+		Env: buildProcessEnv(ctx, view),
+		// Defaults.
+		AllErrors:  true,
+		Comments:   true,
+		Fragment:   true,
+		FormatOnly: false,
+		TabIndent:  true,
+		TabWidth:   8,
+	}
+	formatted, err := imports.Process(f.URI().Filename(), data, options)
 	if err != nil {
 		return nil, err
 	}
@@ -83,6 +96,37 @@
 	return false
 }
 
+func buildProcessEnv(ctx context.Context, view View) *imports.ProcessEnv {
+	cfg := view.Config()
+	env := &imports.ProcessEnv{
+		WorkingDir: cfg.Dir,
+		Logf: func(format string, v ...interface{}) {
+			view.Session().Logger().Infof(ctx, format, v...)
+		},
+	}
+	for _, kv := range cfg.Env {
+		split := strings.Split(kv, "=")
+		if len(split) < 2 {
+			continue
+		}
+		switch split[0] {
+		case "GOPATH":
+			env.GOPATH = split[1]
+		case "GOROOT":
+			env.GOROOT = split[1]
+		case "GO111MODULE":
+			env.GO111MODULE = split[1]
+		case "GOPROXY":
+			env.GOROOT = split[1]
+		case "GOFLAGS":
+			env.GOFLAGS = split[1]
+		case "GOSUMDB":
+			env.GOSUMDB = split[1]
+		}
+	}
+	return env
+}
+
 func computeTextEdits(ctx context.Context, file File, formatted string) (edits []TextEdit) {
 	data, _, err := file.Handle(ctx).Read(ctx)
 	if err != nil {
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 641b0ba..ff84348 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -343,7 +343,7 @@
 		if err != nil {
 			t.Fatalf("failed for %v: %v", spn, err)
 		}
-		edits, err := source.Imports(ctx, f.(source.GoFile), rng)
+		edits, err := source.Imports(ctx, r.view, f.(source.GoFile), rng)
 		if err != nil {
 			if goimported != "" {
 				t.Error(err)
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 872e463..62a281a 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -209,6 +209,8 @@
 
 	// Ignore returns true if this file should be ignored by this view.
 	Ignore(span.URI) bool
+
+	Config() *packages.Config
 }
 
 // File represents a source file of any type.
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 447cfd7..6d659a5 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -25,7 +25,7 @@
 // We hardcode the expected number of test cases to ensure that all tests
 // are being executed. If a test is added, this number must be changed.
 const (
-	ExpectedCompletionsCount       = 137
+	ExpectedCompletionsCount       = 138
 	ExpectedCompletionSnippetCount = 15
 	ExpectedDiagnosticsCount       = 17
 	ExpectedFormatCount            = 5