From ffe4e61c6496f07d2bdbb590286f0b13bd51a158 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 15 Jul 2016 18:28:37 +0000 Subject: [PATCH] imports: add configuration mechanism to exclude directories Each $GOPATH entry may have a file $GOPATH/src/.goimportsignore which may contain blank lines, #comment lines, or lines naming a directory relative to the configuration file to ignore when scanning. No globbing or regex patterns are allowed. Updates golang/go#16367 (goimports speed) Fixes golang/go#16386 (add mechanism to ignore directories) Change-Id: I8f1a88ae6c4d0ed3075444d70aec3e2228c5ce6a Reviewed-on: https://go-review.googlesource.com/24971 Reviewed-by: Rob Pike Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- cmd/goimports/doc.go | 12 ++++++++++++ imports/fix.go | 44 +++++++++++++++++++++++++++++++++++++++++++- imports/fix_test.go | 21 +++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/cmd/goimports/doc.go b/cmd/goimports/doc.go index 46b2b07dfa9..ca46d02ba19 100644 --- a/cmd/goimports/doc.go +++ b/cmd/goimports/doc.go @@ -27,6 +27,18 @@ For GoSublime, follow the steps described here: For other editors, you probably know what to do. +To exclude directories in your $GOPATH from being scanned for Go +files, goimports respects a configuration file at +$GOPATH/src/.goimportsignore which may contain blank lines, comment +lines (beginning with '#'), or lines naming a directory relative to +the configuration file to ignore when scanning. No globbing or regex +patterns are allowed. Use the "-v" verbose flag to verify it's +working and see what goimports is doing. + +File bugs or feature requests at: + + https://golang.org/issues/new?title=x/tools/cmd/goimports:+ + Happy hacking! */ diff --git a/imports/fix.go b/imports/fix.go index 1db863c69da..42f1cf920d1 100644 --- a/imports/fix.go +++ b/imports/fix.go @@ -5,6 +5,8 @@ package imports import ( + "bufio" + "bytes" "fmt" "go/ast" "go/build" @@ -311,10 +313,47 @@ var visitedSymlinks struct { m map[string]struct{} } +var ignoredDirs []os.FileInfo + +// populateIgnoredDirs reads an optional config file at /.goimportsignore +// of relative directories to ignore when scanning for go files. +// The provided path is one of the $GOPATH entries with "src" appended. +func populateIgnoredDirs(path string) { + slurp, err := ioutil.ReadFile(filepath.Join(path, ".goimportsignore")) + if err != nil { + return + } + bs := bufio.NewScanner(bytes.NewReader(slurp)) + for bs.Scan() { + line := strings.TrimSpace(bs.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + if fi, err := os.Stat(filepath.Join(path, line)); err == nil { + ignoredDirs = append(ignoredDirs, fi) + } + } +} + +func skipDir(fi os.FileInfo) bool { + for _, ignoredDir := range ignoredDirs { + if os.SameFile(fi, ignoredDir) { + return true + } + } + return false +} + // shouldTraverse checks if fi, found in dir, is a directory or a symlink to a directory. // It makes sure symlinks were never visited before to avoid symlink loops. func shouldTraverse(dir string, fi os.FileInfo) bool { if fi.IsDir() { + if skipDir(fi) { + if Debug { + log.Printf("skipping directory %q under %s", fi.Name(), dir) + } + return false + } return true } @@ -382,6 +421,9 @@ func scanGoDirs(goRoot bool) { if isGoroot != goRoot { continue } + if !goRoot { + populateIgnoredDirs(path) + } fsgate.enter() testHookScanDir(path) if Debug { @@ -554,7 +596,7 @@ func loadExportsGoPath(expectPackage, dir string) map[string]bool { exportList = append(exportList, k) } sort.Strings(exportList) - log.Printf("scanned dir %v (package %v): exports = %v", dir, expectPackage, strings.Join(exportList, ", ")) + log.Printf("loaded exports in dir %v (package %v): %v", dir, expectPackage, strings.Join(exportList, ", ")) } return exports } diff --git a/imports/fix_test.go b/imports/fix_test.go index a20e62bf2a2..d9534605b7e 100644 --- a/imports/fix_test.go +++ b/imports/fix_test.go @@ -985,6 +985,7 @@ func withEmptyGoPath(fn func()) { scanGoRootOnce = &sync.Once{} scanGoPathOnce = &sync.Once{} dirScan = nil + ignoredDirs = nil dirScanMu.Unlock() oldGOPATH := build.Default.GOPATH @@ -1304,6 +1305,26 @@ func TestImportPathToNameGoPathParse(t *testing.T) { }) } +func TestIgnoreConfiguration(t *testing.T) { + testConfig{ + gopathFiles: map[string]string{ + ".goimportsignore": "# comment line\n\n example.net", // tests comment, blank line, whitespace trimming + "example.net/pkg/pkg.go": "package pkg\nconst X = 1", + "otherwise-longer-so-worse.example.net/foo/pkg/pkg.go": "package pkg\nconst X = 1", + }, + }.test(t, func(t *goimportTest) { + const in = "package x\n\nconst _ = pkg.X\n" + const want = "package x\n\nimport \"otherwise-longer-so-worse.example.net/foo/pkg\"\n\nconst _ = pkg.X\n" + buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil) + if err != nil { + t.Fatal(err) + } + if string(buf) != want { + t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", buf, want) + } + }) +} + func strSet(ss []string) map[string]bool { m := make(map[string]bool) for _, s := range ss {