Skip to content

Commit 416e8f4

Browse files
committed
internal/imports: require valid options, move LocalPrefix up
CL 239754 eagerly initialized the environment. This turns out to be a problem for gopls, which calls ApplyFixes with no ProcessEnv. Reinitializing it every time seriously harmed the performance of unimported completions. Move back to lazy initialization. Working with invalid options has caused a lot of confusion; this is only the most recent. We have to maintain backwards compatibility in the externally visible API, but everywhere else we can require fully populated options. That includes the source byte slice and the options. LocalPrefix is really more of an Option than an attribute of the ProcessEnv, and it is needed in ApplyFixes where we really don't want to have to pass a ProcessEnv. Move it up to Options. Change-Id: Ib9466c375a640a521721da4587091bf93bbdaa3c Reviewed-on: https://go-review.googlesource.com/c/tools/+/241159 Run-TryBot: Heschi Kreinick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 9e0a013 commit 416e8f4

File tree

11 files changed

+179
-239
lines changed

11 files changed

+179
-239
lines changed

cmd/goimports/goimports.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"runtime/pprof"
2222
"strings"
2323

24+
"golang.org/x/tools/internal/gocommand"
2425
"golang.org/x/tools/internal/imports"
2526
)
2627

@@ -42,14 +43,16 @@ var (
4243
TabIndent: true,
4344
Comments: true,
4445
Fragment: true,
45-
Env: &imports.ProcessEnv{},
46+
Env: &imports.ProcessEnv{
47+
GocmdRunner: &gocommand.Runner{},
48+
},
4649
}
4750
exitCode = 0
4851
)
4952

5053
func init() {
5154
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
52-
flag.StringVar(&options.Env.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; comma-separated list")
55+
flag.StringVar(&options.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; comma-separated list")
5356
flag.BoolVar(&options.FormatOnly, "format-only", false, "if true, don't fix imports and only format. In this mode, goimports is effectively gofmt, with the addition that imports are grouped into sections.")
5457
}
5558

imports/forward.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
package imports // import "golang.org/x/tools/imports"
44

55
import (
6+
"io/ioutil"
67
"log"
78

9+
"golang.org/x/tools/internal/gocommand"
810
intimp "golang.org/x/tools/internal/imports"
911
)
1012

@@ -29,25 +31,34 @@ var Debug = false
2931
var LocalPrefix string
3032

3133
// Process formats and adjusts imports for the provided file.
32-
// If opt is nil the defaults are used.
34+
// If opt is nil the defaults are used, and if src is nil the source
35+
// is read from the filesystem.
3336
//
3437
// Note that filename's directory influences which imports can be chosen,
3538
// so it is important that filename be accurate.
3639
// To process data ``as if'' it were in filename, pass the data as a non-nil src.
3740
func Process(filename string, src []byte, opt *Options) ([]byte, error) {
41+
var err error
42+
if src == nil {
43+
src, err = ioutil.ReadFile(filename)
44+
if err != nil {
45+
return nil, err
46+
}
47+
}
3848
if opt == nil {
3949
opt = &Options{Comments: true, TabIndent: true, TabWidth: 8}
4050
}
4151
intopt := &intimp.Options{
4252
Env: &intimp.ProcessEnv{
43-
LocalPrefix: LocalPrefix,
53+
GocmdRunner: &gocommand.Runner{},
4454
},
45-
AllErrors: opt.AllErrors,
46-
Comments: opt.Comments,
47-
FormatOnly: opt.FormatOnly,
48-
Fragment: opt.Fragment,
49-
TabIndent: opt.TabIndent,
50-
TabWidth: opt.TabWidth,
55+
LocalPrefix: LocalPrefix,
56+
AllErrors: opt.AllErrors,
57+
Comments: opt.Comments,
58+
FormatOnly: opt.FormatOnly,
59+
Fragment: opt.Fragment,
60+
TabIndent: opt.TabIndent,
61+
TabWidth: opt.TabWidth,
5162
}
5263
if Debug {
5364
intopt.Env.Logf = log.Printf

internal/imports/fix.go

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,25 @@ import (
3232

3333
// importToGroup is a list of functions which map from an import path to
3434
// a group number.
35-
var importToGroup = []func(env *ProcessEnv, importPath string) (num int, ok bool){
36-
func(env *ProcessEnv, importPath string) (num int, ok bool) {
37-
if env.LocalPrefix == "" {
35+
var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){
36+
func(localPrefix, importPath string) (num int, ok bool) {
37+
if localPrefix == "" {
3838
return
3939
}
40-
for _, p := range strings.Split(env.LocalPrefix, ",") {
40+
for _, p := range strings.Split(localPrefix, ",") {
4141
if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath {
4242
return 3, true
4343
}
4444
}
4545
return
4646
},
47-
func(_ *ProcessEnv, importPath string) (num int, ok bool) {
47+
func(_, importPath string) (num int, ok bool) {
4848
if strings.HasPrefix(importPath, "appengine") {
4949
return 2, true
5050
}
5151
return
5252
},
53-
func(_ *ProcessEnv, importPath string) (num int, ok bool) {
53+
func(_, importPath string) (num int, ok bool) {
5454
firstComponent := strings.Split(importPath, "/")[0]
5555
if strings.Contains(firstComponent, ".") {
5656
return 1, true
@@ -59,9 +59,9 @@ var importToGroup = []func(env *ProcessEnv, importPath string) (num int, ok bool
5959
},
6060
}
6161

62-
func importGroup(env *ProcessEnv, importPath string) int {
62+
func importGroup(localPrefix, importPath string) int {
6363
for _, fn := range importToGroup {
64-
if n, ok := fn(env, importPath); ok {
64+
if n, ok := fn(localPrefix, importPath); ok {
6565
return n
6666
}
6767
}
@@ -278,7 +278,12 @@ func (p *pass) loadPackageNames(imports []*ImportInfo) error {
278278
unknown = append(unknown, imp.ImportPath)
279279
}
280280

281-
names, err := p.env.GetResolver().loadPackageNames(unknown, p.srcDir)
281+
resolver, err := p.env.GetResolver()
282+
if err != nil {
283+
return err
284+
}
285+
286+
names, err := resolver.loadPackageNames(unknown, p.srcDir)
282287
if err != nil {
283288
return err
284289
}
@@ -640,15 +645,23 @@ func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filena
640645
wrappedCallback.exportsLoaded(pkg, exports)
641646
},
642647
}
643-
return env.GetResolver().scan(ctx, scanFilter)
648+
resolver, err := env.GetResolver()
649+
if err != nil {
650+
return err
651+
}
652+
return resolver.scan(ctx, scanFilter)
644653
}
645654

646-
func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) map[string]int {
655+
func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]int, error) {
647656
result := make(map[string]int)
657+
resolver, err := env.GetResolver()
658+
if err != nil {
659+
return nil, err
660+
}
648661
for _, path := range paths {
649-
result[path] = env.GetResolver().scoreImportPath(ctx, path)
662+
result[path] = resolver.scoreImportPath(ctx, path)
650663
}
651-
return result
664+
return result, nil
652665
}
653666

654667
func PrimeCache(ctx context.Context, env *ProcessEnv) error {
@@ -674,8 +687,9 @@ func candidateImportName(pkg *pkg) string {
674687
return ""
675688
}
676689

677-
// getAllCandidates gets all of the candidates to be imported, regardless of if they are needed.
678-
func getAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error {
690+
// GetAllCandidates gets all of the packages starting with prefix that can be
691+
// imported by filename, sorted by import path.
692+
func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error {
679693
callback := &scanCallback{
680694
rootFound: func(gopathwalk.Root) bool {
681695
return true
@@ -714,7 +728,8 @@ type PackageExport struct {
714728
Exports []string
715729
}
716730

717-
func getPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error {
731+
// GetPackageExports returns all known packages with name pkg and their exports.
732+
func GetPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error {
718733
callback := &scanCallback{
719734
rootFound: func(gopathwalk.Root) bool {
720735
return true
@@ -749,8 +764,6 @@ var RequiredGoEnvVars = []string{"GO111MODULE", "GOFLAGS", "GOINSECURE", "GOMOD"
749764
// ProcessEnv contains environment variables and settings that affect the use of
750765
// the go command, the go/build package, etc.
751766
type ProcessEnv struct {
752-
LocalPrefix string
753-
754767
GocmdRunner *gocommand.Runner
755768

756769
BuildFlags []string
@@ -830,16 +843,19 @@ func (e *ProcessEnv) env() []string {
830843
return env
831844
}
832845

833-
func (e *ProcessEnv) GetResolver() Resolver {
846+
func (e *ProcessEnv) GetResolver() (Resolver, error) {
834847
if e.resolver != nil {
835-
return e.resolver
848+
return e.resolver, nil
849+
}
850+
if err := e.init(); err != nil {
851+
return nil, err
836852
}
837853
if len(e.Env["GOMOD"]) == 0 {
838854
e.resolver = newGopathResolver(e)
839-
return e.resolver
855+
return e.resolver, nil
840856
}
841857
e.resolver = newModuleResolver(e)
842-
return e.resolver
858+
return e.resolver, nil
843859
}
844860

845861
func (e *ProcessEnv) buildContext() *build.Context {
@@ -964,10 +980,13 @@ func addExternalCandidates(pass *pass, refs references, filename string) error {
964980
return false // We'll do our own loading after we sort.
965981
},
966982
}
967-
err := pass.env.GetResolver().scan(context.Background(), callback)
983+
resolver, err := pass.env.GetResolver()
968984
if err != nil {
969985
return err
970986
}
987+
if err = resolver.scan(context.Background(), callback); err != nil {
988+
return err
989+
}
971990

972991
// Search for imports matching potential package references.
973992
type result struct {
@@ -1408,6 +1427,10 @@ func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgNa
14081427
pass.env.Logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir)
14091428
}
14101429
}
1430+
resolver, err := pass.env.GetResolver()
1431+
if err != nil {
1432+
return nil, err
1433+
}
14111434

14121435
// Collect exports for packages with matching names.
14131436
rescv := make([]chan *pkg, len(candidates))
@@ -1446,7 +1469,7 @@ func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgNa
14461469
}
14471470
// If we're an x_test, load the package under test's test variant.
14481471
includeTest := strings.HasSuffix(pass.f.Name.Name, "_test") && c.pkg.dir == pass.srcDir
1449-
_, exports, err := pass.env.GetResolver().loadExports(ctx, c.pkg, includeTest)
1472+
_, exports, err := resolver.loadExports(ctx, c.pkg, includeTest)
14501473
if err != nil {
14511474
if pass.env.Logf != nil {
14521475
pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)

internal/imports/fix_test.go

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"context"
99
"flag"
1010
"fmt"
11+
"go/build"
12+
"io/ioutil"
1113
"log"
1214
"path/filepath"
1315
"reflect"
@@ -1157,13 +1159,6 @@ func TestSimpleCases(t *testing.T) {
11571159
const localPrefix = "local.com,github.com/local"
11581160
for _, tt := range tests {
11591161
t.Run(tt.name, func(t *testing.T) {
1160-
options := &Options{
1161-
TabWidth: 8,
1162-
TabIndent: true,
1163-
Comments: true,
1164-
Fragment: true,
1165-
FormatOnly: tt.formatOnly,
1166-
}
11671162
testConfig{
11681163
modules: []packagestest.Module{
11691164
{
@@ -1201,7 +1196,14 @@ func TestSimpleCases(t *testing.T) {
12011196
},
12021197
},
12031198
}.test(t, func(t *goimportTest) {
1204-
t.env.LocalPrefix = localPrefix
1199+
options := &Options{
1200+
LocalPrefix: localPrefix,
1201+
TabWidth: 8,
1202+
TabIndent: true,
1203+
Comments: true,
1204+
Fragment: true,
1205+
FormatOnly: tt.formatOnly,
1206+
}
12051207
t.assertProcessEquals("golang.org/fake", "x.go", nil, options, tt.out)
12061208
})
12071209

@@ -1638,12 +1640,13 @@ func (c testConfig) test(t *testing.T, fn func(*goimportTest)) {
16381640
},
16391641
exported: exported,
16401642
}
1641-
if err := it.env.init(); err != nil {
1642-
t.Fatal(err)
1643-
}
16441643
if *testDebug {
16451644
it.env.Logf = log.Printf
16461645
}
1646+
// packagestest clears out GOROOT to work around golang/go#32849,
1647+
// which isn't relevant here. Fill it back in so we can find the standard library.
1648+
it.env.Env["GOROOT"] = build.Default.GOROOT
1649+
16471650
fn(it)
16481651
})
16491652
}
@@ -1673,6 +1676,13 @@ func (t *goimportTest) process(module, file string, contents []byte, opts *Optio
16731676
}
16741677

16751678
func (t *goimportTest) processNonModule(file string, contents []byte, opts *Options) ([]byte, error) {
1679+
if contents == nil {
1680+
var err error
1681+
contents, err = ioutil.ReadFile(file)
1682+
if err != nil {
1683+
return nil, err
1684+
}
1685+
}
16761686
if opts == nil {
16771687
opts = &Options{Comments: true, TabIndent: true, TabWidth: 8}
16781688
}
@@ -1869,8 +1879,14 @@ const _ = runtime.GOOS
18691879
Files: fm{"t.go": tt.src},
18701880
}}, tt.modules...),
18711881
}.test(t, func(t *goimportTest) {
1872-
t.env.LocalPrefix = tt.localPrefix
1873-
t.assertProcessEquals("test.com", "t.go", nil, nil, tt.want)
1882+
options := &Options{
1883+
LocalPrefix: tt.localPrefix,
1884+
TabWidth: 8,
1885+
TabIndent: true,
1886+
Comments: true,
1887+
Fragment: true,
1888+
}
1889+
t.assertProcessEquals("test.com", "t.go", nil, options, tt.want)
18741890
})
18751891
})
18761892
}
@@ -1920,7 +1936,10 @@ func TestImportPathToNameGoPathParse(t *testing.T) {
19201936
if strings.Contains(t.Name(), "GoPackages") {
19211937
t.Skip("go/packages does not ignore package main")
19221938
}
1923-
r := t.env.GetResolver()
1939+
r, err := t.env.GetResolver()
1940+
if err != nil {
1941+
t.Fatal(err)
1942+
}
19241943
srcDir := filepath.Dir(t.exported.File("example.net/pkg", "z.go"))
19251944
names, err := r.loadPackageNames([]string{"example.net/pkg"}, srcDir)
19261945
if err != nil {
@@ -2577,7 +2596,7 @@ func TestGetCandidates(t *testing.T) {
25772596
}
25782597
}
25792598
}
2580-
if err := getAllCandidates(context.Background(), add, "", "x.go", "x", t.env); err != nil {
2599+
if err := GetAllCandidates(context.Background(), add, "", "x.go", "x", t.env); err != nil {
25812600
t.Fatalf("GetAllCandidates() = %v", err)
25822601
}
25832602
// Sort, then clear out relevance so it doesn't mess up the DeepEqual.
@@ -2628,7 +2647,7 @@ func TestGetPackageCompletions(t *testing.T) {
26282647
}
26292648
}
26302649
}
2631-
if err := getPackageExports(context.Background(), add, "rand", "x.go", "x", t.env); err != nil {
2650+
if err := GetPackageExports(context.Background(), add, "rand", "x.go", "x", t.env); err != nil {
26322651
t.Fatalf("getPackageCompletions() = %v", err)
26332652
}
26342653
// Sort, then clear out relevance so it doesn't mess up the DeepEqual.

0 commit comments

Comments
 (0)