diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..92b00d1 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,87 @@ +run: + timeout: 3m + +linters-settings: + goconst: + min-len: 3 + min-occurrences: 3 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - ifElseChain + - octalLiteral + - whyNoLint + - wrapperFunc + - importShadow + - unnamedResult + - unnecessaryBlock + settings: + rangeValCopy: + sizeThreshold: 256 + hugeParam: + sizeThreshold: 256 + gocyclo: + min-complexity: 16 + goimports: + local-prefixes: github.com/golangci/golangci-lint + golint: + min-confidence: 0 + govet: + check-shadowing: true + lll: + line-length: 300 + maligned: + suggest-new: true + misspell: + locale: US + nolintlint: + allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space) + allow-unused: false # report any unused nolint directives + require-explanation: false # don't require an explanation for nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped + +linters: + disable-all: true + enable: + - asciicheck + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + - gochecknoinits + - goconst + - gocritic + - gofmt + - goimports + - golint + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - interfacer + - lll + - maligned + - misspell + - nakedret + - nolintlint + - prealloc + - rowserrcheck + - scopelint + - staticcheck + - structcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - varcheck + - whitespace \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ff3dd13..55fe05c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,4 @@ language: go go: - - 1.12.x - -env: - - GO111MODULE=on - -before_install: - - go get golang.org/x/lint/golint - -install: -# skip - -script: - - go get -v ./... - - go vet ./... - - golint -set_exit_status ./... - - go test -v ./... + - 1.14.x \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c54c53e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.1.0] - 07-15-20 +### Added +- Added golangci-lint linter, Makefile, and fixed linter warnings + +### Changed +- go-groups now runs gofmt over the input files, unless disabled by the `-f` flag. + +### Fixed +- Fixed go-groups stripping the dot from dot imports (thanks [@jdroot](https://github.com/jdroot)) +- Fixed go-groups removing comments and other content from import blocks + +## [1.0.3] - 2019-06-20 +Initial public release diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b64c632 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +BIN_DIR ?= $(shell go env GOPATH)/bin + +default: test + +deps: ## download go modules + go mod download + +fmt: lint/install # ensure consistent code style + gofmt -s -w . + golangci-lint run --fix > /dev/null 2>&1 || true + +lint/install: + @if ! golangci-lint --version > /dev/null 2>&1; then \ + echo "Installing golangci-lint"; \ + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(BIN_DIR) v1.28.3; \ + fi + +lint: lint/install ## run golangci-lint + @if ! golangci-lint run; then \ + echo "\033[0;33mdetected fmt problems: run \`\033[0;32mmake fmt\033[0m\`"; \ + exit 1; \ + fi + +test: lint ## run go tests + go vet ./... + go test ./... -race + +help: ## displays this help message + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_\/-]+:.*?## / {printf "\033[34m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | \ + sort | \ + grep -v '#' diff --git a/README.md b/README.md index 2b4a09b..6db14c3 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ the imports. `go-groups` is similar to `goimports`, but in addition to sorting imports lexigraphically, it also separates import blocks at a per-project level. +Like `goimports`, `go-groups` also runs gofmt and fixes any style/formatting +issues. # Getting Started @@ -51,8 +53,7 @@ import ( #### Typical Workflow -1. Run `go-groups -w ./..` to rewrite and sort import groupings -2. Run `gofmt -w ./..` to format your go source file +Run `go-groups -w ./..` to rewrite and sort import groupings for go source files in a project. #### More info diff --git a/go.mod b/go.mod index d1bb49a..ce9b907 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module oss.indeed.com/go/go-groups +go 1.14 + require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.6.1 ) diff --git a/go.sum b/go.sum index 4f76e62..56d62e7 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,10 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gofmt.go b/gofmt.go index e4f5b12..9ae8bd2 100644 --- a/gofmt.go +++ b/gofmt.go @@ -33,6 +33,7 @@ package main import ( "bytes" "fmt" + "go/scanner" "io" "io/ioutil" "os" @@ -44,8 +45,14 @@ import ( // based on https://golang.org/src/cmd/gofmt/gofmt.go with a few modifications +func isGoFile(f os.FileInfo) bool { + // ignore non-Go files + name := f.Name() + return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") +} + // If in == nil, the source is the contents of the file with the given filename. -func processFile(filename string, in io.Reader, out io.Writer) error { +func processFile(filename string, in io.Reader, out io.Writer, fixFmt bool) error { var perm os.FileMode = 0644 if in == nil { f, err := os.Open(filename) @@ -66,26 +73,35 @@ func processFile(filename string, in io.Reader, out io.Writer) error { return err } - result, rewrite := parse(src) - // nothing to do, file has no imports - if !rewrite { - return nil + if fixFmt { + buffer := bytes.Buffer{} + buffer.Write(src) + cmd := exec.Command("gofmt", "--") + cmd.Stdin = &buffer + cmd.Stderr = os.Stderr + src, err = cmd.Output() + if err != nil { + return err + } } - // something to do - if !bytes.Equal(src, result) { + res, rewritten := parse(src) + if !bytes.Equal(src, res) && rewritten { + // formatting has changed if *list { - fmt.Fprintln(out, filename) + if _, err := fmt.Fprintln(out, filename); err != nil { //nolint:govet + return err + } } if *write { // make a temporary backup before overwriting original - bakname, err := backupFile(filename+".", src, perm) + bakname, err := backupFile(filename+".", src, perm) //nolint:govet if err != nil { return err } - err = ioutil.WriteFile(filename, result, perm) + err = ioutil.WriteFile(filename, res, perm) if err != nil { - os.Rename(bakname, filename) + _ = os.Rename(bakname, filename) return err } err = os.Remove(bakname) @@ -94,29 +110,33 @@ func processFile(filename string, in io.Reader, out io.Writer) error { } } if *doDiff { - data, err := diff(src, result, filename) + data, err := diff(src, res, filename) //nolint:govet if err != nil { return fmt.Errorf("computing diff: %s", err) } fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename)) - out.Write(data) + _, err = out.Write(data) + if err != nil { + return err + } } } if !*list && !*write && !*doDiff { - _, err = out.Write(result) + _, err = out.Write(res) } + return err } func visitFile(path string, f os.FileInfo, err error) error { if err == nil && isGoFile(f) { - err = processFile(path, nil, os.Stdout) + err = processFile(path, nil, os.Stdout, !*noFormat) } // Don't complain if a file was deleted in the meantime (i.e. - // the directory changed concurrently while running). + // the directory changed concurrently while running gofmt). if err != nil && !os.IsNotExist(err) { - return err + scanner.PrintError(os.Stderr, err) } return nil } @@ -125,28 +145,6 @@ func walkDir(path string) error { return filepath.Walk(path, visitFile) } -func isGoFile(f os.FileInfo) bool { - // ignore non-Go files - name := f.Name() - return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") -} - -func writeTempFile(dir, prefix string, data []byte) (string, error) { - file, err := ioutil.TempFile(dir, prefix) - if err != nil { - return "", err - } - _, err = file.Write(data) - if err1 := file.Close(); err == nil { - err = err1 - } - if err != nil { - os.Remove(file.Name()) - return "", err - } - return file.Name(), nil -} - func diff(b1, b2 []byte, filename string) (data []byte, err error) { f1, err := writeTempFile("", "go-groups", b1) if err != nil { @@ -174,6 +172,22 @@ func diff(b1, b2 []byte, filename string) (data []byte, err error) { return } +func writeTempFile(dir, prefix string, data []byte) (string, error) { + file, err := ioutil.TempFile(dir, prefix) + if err != nil { + return "", err + } + _, err = file.Write(data) + if err1 := file.Close(); err == nil { + err = err1 + } + if err != nil { + os.Remove(file.Name()) + return "", err + } + return file.Name(), nil +} + // replaceTempFilename replaces temporary filenames in diff with actual one. // // --- /tmp/gofmt316145376 2017-02-03 19:13:00.280468375 -0500 diff --git a/gofmt_test.go b/gofmt_test.go index d16f2d6..8f5a9cd 100644 --- a/gofmt_test.go +++ b/gofmt_test.go @@ -42,8 +42,6 @@ import ( ) // based on https://golang.org/src/cmd/gofmt/gofmt_test.go with a few modifications - - func TestBackupFile(t *testing.T) { dir, err := ioutil.TempDir("", "gofmt_test") if err != nil { diff --git a/import.go b/import.go index e4ca2cd..ad2ad2f 100644 --- a/import.go +++ b/import.go @@ -6,8 +6,8 @@ import ( ) // Imports represents the list of imports in a given go file. -// This helper encapsulates the logic for sorting immports based on go-groups. -type Imports []string +// This helper encapsulates the logic for sorting imports based on go-groups. +type Imports []importLine var _ sort.Interface = (*Imports)(nil) @@ -20,11 +20,11 @@ func (s Imports) Swap(i, j int) { } func (s Imports) Less(i, j int) bool { - s1 := strings.TrimSpace(s[i]) + s1 := strings.TrimSpace(s[i].line) if strings.ContainsAny(s1, " ") { s1 = strings.Join(strings.Split(s1, " ")[1:], " ") } - s2 := strings.TrimSpace(s[j]) + s2 := strings.TrimSpace(s[j].line) if strings.ContainsAny(s2, " ") { s2 = strings.Join(strings.Split(s2, " ")[1:], " ") } diff --git a/main.go b/main.go index 98293f7..f9b95e5 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ const ( exitBadStdin = 2 exitInternalError = 3 - versionStr = "go-groups version 1.0.3 (2019-06-20)" + versionStr = "go-groups version 1.1.0 (2020-07-15)" ) var ( @@ -24,13 +24,14 @@ var ( importEndRegex = regexp.MustCompile(`^\s*\)\s*$`) // any whitespace + any unicode_letter_or_underscore + any unicode_letter_or_underscore_or_unicode number + any whitespace + quote + any + quote + any - groupedImportRegex = regexp.MustCompile(`^\s*[\p{L}_]*[\s*[\p{L}_\p{N}]*\s*".*".*$`) + groupedImportRegex = regexp.MustCompile(`^\s*[\p{L}_\\.]*[\s*[\p{L}_\p{N}]*\s*".*".*$`) externalImport = regexp.MustCompile(`"([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?\/([\p{L}_\-\p{N}]*)\/?.*"`) - list = flag.Bool("l", false, "list files whose formatting differs") - write = flag.Bool("w", false, "write result to (source) file instead of stdout") - doDiff = flag.Bool("d", false, "display diffs instead of rewriting files") - version = flag.Bool("v", false, "display the version of go-groups") + list = flag.Bool("l", false, "list files whose formatting differs") + write = flag.Bool("w", false, "write result to (source) file instead of stdout") + doDiff = flag.Bool("d", false, "display diffs instead of rewriting files") + version = flag.Bool("v", false, "display the version of go-groups") + noFormat = flag.Bool("f", false, "disables the automatic gofmt style fixes") ) func main() { @@ -48,7 +49,7 @@ func main() { _, _ = fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input") os.Exit(exitBadStdin) } - if err := processFile("", os.Stdin, os.Stdout); err != nil { + if err := processFile("", os.Stdin, os.Stdout, !*noFormat); err != nil { _, _ = fmt.Fprintln(os.Stderr, "failed to parse stdin: "+err.Error()) os.Exit(exitBadFlags) } @@ -67,7 +68,7 @@ func main() { os.Exit(exitInternalError) } default: - _ = processFile(path, nil, os.Stdout) + _ = processFile(path, nil, os.Stdout, !*noFormat) } } } @@ -81,7 +82,13 @@ type importGroup struct { lineStart int lineEnd int - lines []string + lines []importLine +} + +type importLine struct { + line string + contentAbove string + contentBelow string } func parse(src []byte) (result []byte, rewritten bool) { @@ -89,10 +96,15 @@ func parse(src []byte) (result []byte, rewritten bool) { contents := make(map[int]string, 128) scanner := bufio.NewScanner(bytes.NewReader(src)) + lines := make([]string, 0, 128) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } insideImports := false var n int var group importGroup + scanner = bufio.NewScanner(bytes.NewReader(src)) for n = 0; scanner.Scan(); n++ { line := scanner.Text() if insideImports { @@ -101,7 +113,34 @@ func parse(src []byte) (result []byte, rewritten bool) { group.lineEnd = n groups = append(groups, group) } else if groupedImportRegex.MatchString(line) { - group.lines = append(group.lines, line) + importLine := importLine{ //nolint:govet + line: line, + } + var above, below int + for above = n - 1; above > 0; above-- { + if groupedImportRegex.MatchString(lines[above]) || importStartRegex.MatchString(lines[above]) { + above++ + break + } + } + for below = n + 1; below < len(lines); below++ { + if importEndRegex.MatchString(lines[below]) { + below-- + break + } + // if we hit an import beneath us, assume it owns the non-import content above itself, not us + if groupedImportRegex.MatchString(lines[below]) { + below = n + break + } + } + if above != n { + importLine.contentAbove = strings.Join(filterNewlines(lines[above:n]), "\n") + } + if below != n { + importLine.contentBelow = strings.Join(filterNewlines(lines[n+1:below+1]), "\n") + } + group.lines = append(group.lines, importLine) } } else if importStartRegex.MatchString(line) { insideImports = true @@ -127,6 +166,16 @@ func parse(src []byte) (result []byte, rewritten bool) { return fileBytes, true } +func filterNewlines(lines []string) []string { + filtered := make([]string, 0, len(lines)) + for _, line := range lines { + if strings.TrimSpace(line) != "" { + filtered = append(filtered, line) + } + } + return filtered +} + func fixupFile(contents map[int]string, numLines int, groups []importGroup) []byte { buffer := bytes.NewBufferString("") for i := 0; i < numLines; i++ { @@ -137,22 +186,31 @@ func fixupFile(contents map[int]string, numLines int, groups []importGroup) []by continue } for _, group := range groups { - if group.lineStart == i { - buffer.WriteString("import (\n") - leadingWhitespace := true - for _, line := range group.lines { - if leadingWhitespace && strings.TrimSpace(line) == "" { - // skip empty leading import lines - } else { - buffer.WriteString(line) + if group.lineStart != i { + continue + } + buffer.WriteString("import (\n") + leadingWhitespace := true + for _, importLine := range group.lines { + if leadingWhitespace && strings.TrimSpace(importLine.line) == "" { + // skip empty leading import lines + } else { + if importLine.contentAbove != "" { + buffer.WriteString(importLine.contentAbove) + buffer.WriteString("\n") + } + buffer.WriteString(importLine.line) + buffer.WriteString("\n") + if importLine.contentBelow != "" { + buffer.WriteString(importLine.contentBelow) buffer.WriteString("\n") - leadingWhitespace = false } + leadingWhitespace = false } - buffer.WriteString(")\n") - i = group.lineEnd - break } + buffer.WriteString(")\n") + i = group.lineEnd + break } } return buffer.Bytes() @@ -167,11 +225,11 @@ func regroupImportGroups(group importGroup) importGroup { standardImports := make(Imports, 0, len(group.lines)) sortedKeys := make([]string, 0) - groupNames := make(map[string]Imports, 0) + groupNames := make(map[string]Imports) for _, importLine := range group.lines { - matches := externalImport.FindStringSubmatch(importLine) + matches := externalImport.FindStringSubmatch(importLine.line) - if matches != nil && strings.ContainsAny(importLine, ".") { + if matches != nil && strings.ContainsAny(importLine.line, ".") { groupName := strings.Join(matches[1:], "") if groupNames[groupName] == nil { groupNames[groupName] = make(Imports, 0, 1) @@ -190,7 +248,7 @@ func regroupImportGroups(group importGroup) importGroup { imports := groupNames[groupName] sort.Sort(imports) - group.lines = append(group.lines, "") + group.lines = append(group.lines, importLine{}) group.lines = append(group.lines, imports...) } return group diff --git a/main_test.go b/main_test.go index 22c0a9c..e856378 100644 --- a/main_test.go +++ b/main_test.go @@ -1,280 +1,110 @@ package main import ( + "bytes" + "io/ioutil" + "strings" "testing" "github.com/stretchr/testify/require" ) -// TODO: move the import blocks to test fixtures, this file is pretty unreadable - func TestParse_NoImports(t *testing.T) { _, rewritten := parse([]byte("")) require.False(t, rewritten) } func TestParse_ValidGroupingSortImports(t *testing.T) { - str := - ` -package foo - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "strconv" - "strings" -) -` - - result, rewritten := parse([]byte(str)) + bytes := testdata(t, "valid_imports.txt") + result, rewritten := parse(bytes) require.True(t, rewritten) - require.Equal(t, str, string(result)) + require.Equal(t, string(bytes), string(result)) } func TestParse_ExtraGroups(t *testing.T) { - str := - ` -package foo - -import ( - "io" - "strings" - - "strconv" - "fmt" - - "os" - "io/ioutil" -) -` - - expected := - ` -package foo - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "strconv" - "strings" -) -` - result, rewritten := parse([]byte(str)) + bytes := testdata(t, "extra_groups.txt") + result, rewritten := parse(bytes) require.True(t, rewritten) - require.Equal(t, expected, string(result)) + require.Equal(t, testdata(t, "valid_imports.txt"), result) } func TestParse_ExternalGroups(t *testing.T) { - str := - ` -package foo - -import ( - "io" - "strings" - "strconv" - "fmt" - "os" - "io/ioutil" - - "indeed.com/devops/foobar" - - "indeed.com/gophers/quz" - - "github.com/hashicorp/vault/x" - -) -` - - expected := - ` -package foo - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "strconv" - "strings" - - "github.com/hashicorp/vault/x" - - "indeed.com/devops/foobar" - - "indeed.com/gophers/quz" -) -` - result, rewritten := parse([]byte(str)) + bytes := testdata(t, "external_groups_invalid.txt") + expected := testdata(t, "external_groups.txt") + result, rewritten := parse(bytes) require.True(t, rewritten) - require.Equal(t, expected, string(result)) + require.Equal(t, string(expected), string(result)) } func TestParse_SortingExternalGroups(t *testing.T) { - str := - ` -package foo - -import ( - "net/http" - "testing" - - "indeed.com/devops/libmarvin/gin/ginhandler" - "indeed.com/devops/libmarvin/spec" - "indeed.com/devops/marvlet/api/v2" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/suite" -) -` - - expected := - ` -package foo - -import ( - "net/http" - "testing" - - "github.com/gin-gonic/gin" - - "github.com/stretchr/testify/suite" - - "indeed.com/devops/libmarvin/gin/ginhandler" - "indeed.com/devops/libmarvin/spec" - "indeed.com/devops/marvlet/api/v2" -) -` - result, rewritten := parse([]byte(str)) + bytes := testdata(t, "sort_external_groups_invalid.txt") + expected := testdata(t, "sort_external_groups.txt") + result, rewritten := parse(bytes) require.True(t, rewritten) - require.Equal(t, expected, string(result)) + require.Equal(t, expected, result) } func TestParse_PrefixedGroups(t *testing.T) { - str := - ` -package foo - -import ( - "fmt" - "os/user" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - "indeed.com/gophers/libarc" - "indeed.com/gophers/libops" - "indeed.com/gophers/rlog" - - "indeed.com/devops/libmarvin/spec" - "indeed.com/devops/marvlet/backend" - "indeed.com/devops/marvlet/backend/indeed" - - appsv1 "k8s.io/api/apps/v1" - apiv1 "k8s.io/api/core/v1" - - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/typed/apps/v1" -) -` - expected := - ` -package foo - -import ( - "fmt" - "os/user" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - - "indeed.com/devops/libmarvin/spec" - "indeed.com/devops/marvlet/backend" - "indeed.com/devops/marvlet/backend/indeed" - - "indeed.com/gophers/libarc" - "indeed.com/gophers/libops" - "indeed.com/gophers/rlog" - - appsv1 "k8s.io/api/apps/v1" - apiv1 "k8s.io/api/core/v1" - - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/typed/apps/v1" -) -` - result, rewritten := parse([]byte(str)) + bytes := testdata(t, "prefixed_groups_invalid.txt") + expected := testdata(t, "prefixed_groups.txt") + result, rewritten := parse(bytes) require.True(t, rewritten) - require.Equal(t, expected, string(result)) + require.Equal(t, expected, result) } func TestParse_SubdomainImports(t *testing.T) { - str := - ` -package foo + bytes := testdata(t, "subdomain_imports_invalid.txt") + expected := testdata(t, "subdomain_imports.txt") + result, rewritten := parse(bytes) -import ( - "context" - "io" - "net/http" - "strconv" - "strings" - - "github.com/grpc-ecosystem/grpc-gateway/runtime" + require.True(t, rewritten) + require.Equal(t, string(expected), string(result)) +} - "github.com/shoenig/petrify/v4" +func TestParse_CommentedImports(t *testing.T) { + bytes := testdata(t, "commented_imports.txt") + result, rewritten := parse(bytes) + require.True(t, rewritten) + require.Equal(t, string(bytes), string(result)) +} - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" - "google.golang.org/grpc" -) -` +func TestParse_Multiline_CommentedImports(t *testing.T) { + bytes := testdata(t, "multiline_comments_invalid.txt") + expected := testdata(t, "multiline_comments.txt") + result, rewritten := parse(bytes) + require.True(t, rewritten) + require.Equal(t, string(expected), string(result)) +} - expected := - ` -package foo +func TestParse_gofmt(t *testing.T) { + b := testdata(t, "gofmt_invalid.txt") + expected := testdata(t, "gofmt.txt") -import ( - "context" - "io" - "net/http" - "strconv" - "strings" + var buf bytes.Buffer + err := processFile("", strings.NewReader(string(b)), &buf, true) + require.NoError(t, err) - "github.com/grpc-ecosystem/grpc-gateway/runtime" + require.Equal(t, string(expected), buf.String()) +} - "github.com/shoenig/petrify/v4" +func TestParse_gofmt_disabled(t *testing.T) { + b := testdata(t, "gofmt_invalid.txt") + expected := testdata(t, "gofmt_invalid.txt") - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" + var buf bytes.Buffer + err := processFile("", strings.NewReader(string(b)), &buf, false) + require.NoError(t, err) - "google.golang.org/grpc" -) -` - result, rewritten := parse([]byte(str)) + require.Equal(t, string(expected), buf.String()) +} - require.True(t, rewritten) - require.Equal(t, expected, string(result)) +func testdata(t *testing.T, str string) []byte { + b, err := ioutil.ReadFile("testdata/" + str) + require.NoError(t, err) + return b } diff --git a/testdata/commented_imports.txt b/testdata/commented_imports.txt new file mode 100644 index 0000000..3570927 --- /dev/null +++ b/testdata/commented_imports.txt @@ -0,0 +1,10 @@ +package main + +import ( + "fmt" + // example comment +) + +func main() { + fmt.Println("Hello world") +} diff --git a/testdata/external_groups.txt b/testdata/external_groups.txt new file mode 100644 index 0000000..c59bf44 --- /dev/null +++ b/testdata/external_groups.txt @@ -0,0 +1,16 @@ +package foo + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strconv" + "strings" + + "github.com/hashicorp/vault/x" + + "indeed.com/devops/foobar" + + "indeed.com/gophers/quz" +) diff --git a/testdata/external_groups_invalid.txt b/testdata/external_groups_invalid.txt new file mode 100644 index 0000000..acdbab9 --- /dev/null +++ b/testdata/external_groups_invalid.txt @@ -0,0 +1,17 @@ +package foo + +import ( + "io" + "strings" + "strconv" + "fmt" + "os" + "io/ioutil" + + "indeed.com/devops/foobar" + + "indeed.com/gophers/quz" + + "github.com/hashicorp/vault/x" + +) diff --git a/testdata/extra_groups.txt b/testdata/extra_groups.txt new file mode 100644 index 0000000..3a362dd --- /dev/null +++ b/testdata/extra_groups.txt @@ -0,0 +1,12 @@ +package foo + +import ( + "fmt" + "strings" + + "io" + "io/ioutil" + + "os" + "strconv" +) diff --git a/testdata/gofmt.txt b/testdata/gofmt.txt new file mode 100644 index 0000000..5565343 --- /dev/null +++ b/testdata/gofmt.txt @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" +) + +var ( + foo = "misaligned" + bar = "variables" +) + +func main() { + fmt.Println("Hello world") +} diff --git a/testdata/gofmt_invalid.txt b/testdata/gofmt_invalid.txt new file mode 100644 index 0000000..cc93141 --- /dev/null +++ b/testdata/gofmt_invalid.txt @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" +) + +var ( + foo = "misaligned" + bar = "variables" +) + +func main() { + fmt.Println("Hello world") +} diff --git a/testdata/multiline_comments.txt b/testdata/multiline_comments.txt new file mode 100644 index 0000000..4cbf819 --- /dev/null +++ b/testdata/multiline_comments.txt @@ -0,0 +1,25 @@ +package main + +import ( + "error" + "fmt" + // example comment + // comment continues + // and continues + "io" + // example comment + "log" + + // go-groups comment + "github.com/indeedeng/go-groups" + + // followup comment + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + + "google.golang.org/grpc" +) + +func main() { + fmt.Println("Hello world") +} diff --git a/testdata/multiline_comments_invalid.txt b/testdata/multiline_comments_invalid.txt new file mode 100644 index 0000000..38abaef --- /dev/null +++ b/testdata/multiline_comments_invalid.txt @@ -0,0 +1,23 @@ +package main + +import ( + // example comment + // comment continues + // and continues + "io" + "fmt" + // example comment + "log" + "error" + // go-groups comment + "github.com/indeedeng/go-groups" + // followup comment + + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + "google.golang.org/grpc" +) + +func main() { + fmt.Println("Hello world") +} diff --git a/testdata/prefixed_groups.txt b/testdata/prefixed_groups.txt new file mode 100644 index 0000000..1221162 --- /dev/null +++ b/testdata/prefixed_groups.txt @@ -0,0 +1,30 @@ +package foo + +import ( + "fmt" + "os/user" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" + + "indeed.com/devops/libmarvin/spec" + "indeed.com/devops/marvlet/backend" + "indeed.com/devops/marvlet/backend/indeed" + + "indeed.com/gophers/libarc" + "indeed.com/gophers/libops" + "indeed.com/gophers/rlog" + + appsv1 "k8s.io/api/apps/v1" + apiv1 "k8s.io/api/core/v1" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/typed/apps/v1" +) diff --git a/testdata/prefixed_groups_invalid.txt b/testdata/prefixed_groups_invalid.txt new file mode 100644 index 0000000..3d77632 --- /dev/null +++ b/testdata/prefixed_groups_invalid.txt @@ -0,0 +1,29 @@ +package foo + +import ( + "fmt" + "os/user" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" + "indeed.com/gophers/libarc" + "indeed.com/gophers/libops" + "indeed.com/gophers/rlog" + + "indeed.com/devops/libmarvin/spec" + "indeed.com/devops/marvlet/backend" + "indeed.com/devops/marvlet/backend/indeed" + + appsv1 "k8s.io/api/apps/v1" + apiv1 "k8s.io/api/core/v1" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/typed/apps/v1" +) diff --git a/testdata/sort_external_groups.txt b/testdata/sort_external_groups.txt new file mode 100644 index 0000000..c37ca6c --- /dev/null +++ b/testdata/sort_external_groups.txt @@ -0,0 +1,14 @@ +package foo + +import ( + "net/http" + "testing" + + "github.com/gin-gonic/gin" + + "github.com/stretchr/testify/suite" + + "indeed.com/devops/libmarvin/gin/ginhandler" + "indeed.com/devops/libmarvin/spec" + "indeed.com/devops/marvlet/api/v2" +) diff --git a/testdata/sort_external_groups_invalid.txt b/testdata/sort_external_groups_invalid.txt new file mode 100644 index 0000000..777def7 --- /dev/null +++ b/testdata/sort_external_groups_invalid.txt @@ -0,0 +1,13 @@ +package foo + +import ( + "net/http" + "testing" + + "indeed.com/devops/libmarvin/gin/ginhandler" + "indeed.com/devops/libmarvin/spec" + "indeed.com/devops/marvlet/api/v2" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/suite" +) diff --git a/testdata/subdomain_imports.txt b/testdata/subdomain_imports.txt new file mode 100644 index 0000000..8718422 --- /dev/null +++ b/testdata/subdomain_imports.txt @@ -0,0 +1,18 @@ +package foo + +import ( + "context" + "io" + "net/http" + "strconv" + "strings" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + + "github.com/shoenig/petrify/v4" + + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + + "google.golang.org/grpc" +) diff --git a/testdata/subdomain_imports_invalid.txt b/testdata/subdomain_imports_invalid.txt new file mode 100644 index 0000000..02b383e --- /dev/null +++ b/testdata/subdomain_imports_invalid.txt @@ -0,0 +1,17 @@ +package foo + +import ( + "context" + "io" + "net/http" + "strconv" + "strings" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + + "github.com/shoenig/petrify/v4" + + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + "google.golang.org/grpc" +) diff --git a/testdata/valid_imports.txt b/testdata/valid_imports.txt new file mode 100644 index 0000000..da6909f --- /dev/null +++ b/testdata/valid_imports.txt @@ -0,0 +1,10 @@ +package foo + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strconv" + "strings" +)