diff --git a/.github/workflows/gci.yml b/.github/workflows/gci.yml new file mode 100644 index 0000000..9e4c1a4 --- /dev/null +++ b/.github/workflows/gci.yml @@ -0,0 +1,82 @@ +name: gci + +on: + pull_request: + +jobs: + build: + name: Build ${{ matrix.target_os }}_${{ matrix.target_arch }} binaries + runs-on: ${{ matrix.os }} + env: + GOVER: 1.18 + GOOS: ${{ matrix.target_os }} + GOARCH: ${{ matrix.target_arch }} + GOPROXY: https://proxy.golang.org + ARCHIVE_OUTDIR: dist/archives + TEST_OUTPUT_FILE_PREFIX: test_report + strategy: + matrix: + os: [ubuntu-latest, windows-2019, macOS-latest] + target_arch: [arm, arm64, amd64] + include: + - os: ubuntu-latest + target_os: linux + - os: windows-2019 + target_os: windows + - os: macOS-latest + target_os: darwin + exclude: + - os: windows-2019 + target_arch: arm + - os: windows-2019 + target_arch: arm64 + - os: macOS-latest + target_arch: arm + steps: + - name: Set up Go ${{ env.GOVER }} + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GOVER }} + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + - name: Cache Go modules (Linux) + if: matrix.target_os == 'linux' + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-build-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-build- + - name: Cache Go modules (Windows) + if: matrix.target_os == 'windows' + uses: actions/cache@v3 + with: + path: | + ~\AppData\Local\go-build + ~\go\pkg\mod + key: ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-build-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-build- + - name: Cache Go modules (macOS) + if: matrix.target_os == 'darwin' + uses: actions/cache@v3 + with: + path: | + ~/Library/Caches/go-build + ~/go/pkg/mod + key: ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-build-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ matrix.target_os }}-${{ matrix.target_arch }}-go-${{ env.GOVER }}-build- + - name: golangci-lint + if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux' + uses: golangci/golangci-lint-action@v3.1.0 + with: + version: ${{ env.GOLANGCILINT_VER }} + - name: Run make test + env: + COVERAGE_OPTS: "-coverprofile=coverage.txt -covermode=atomic" + if: matrix.target_arch == 'amd64' + run: make test + diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..2e72bba --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,85 @@ +# options for analysis running +run: + # default concurrency is a available CPU number + concurrency: 4 + + # timeout for analysis, e.g. 30s, 5m, default is 1m + deadline: 10m + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # list of build tags, all linters use it. Default is empty list. + build-tags: + + # which dirs to skip: they won't be analyzed; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but next dirs are always skipped independently + # from this option's value: + # third_party$, testdata$, examples$, Godeps$, builtin$ + skip-dirs: + + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. + skip-files: + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" + format: tab + + # print lines of code with issue, default is true + print-issued-lines: true + + # print linter name in the end of issue text, default is true + print-linter-name: true + + +# all available settings of specific linters +linters-settings: + gci: + # Checks that no inline Comments are present. + # Default: false + no-inline-comments: false + + # Checks that no prefix Comments(comment lines above an import) are present. + # Default: false + no-prefix-comments: false + + # Section configuration to compare against. + # Section names are case-insensitive and may contain parameters in (). + # Default: ["standard", "default"] + sections: + - standard # Captures all standard packages if they do not match another section. + - default # Contains all imports that could not be matched to another section type. + - prefix(github.com/daixiang0/gci) # Groups all imports with the specified Prefix. + + # Separators that should be present between sections. + # Default: ["newLine"] + section-separators: + - newLine + + gofmt: + # simplify code: gofmt with `-s` option, true by default + simplify: true + goimports: + # put imports beginning with prefix after 3rd-party packages; + # it's a comma-separated list of prefixes + local-prefixes: github.com/daixiang0/gci + +linters: + fast: false + enable: + - gofmt + - gofumpt + - goimports + - gci + disable-all: true + +issues: + exclude: diff --git a/README.md b/README.md index a0c9cd7..085a843 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,12 @@ $ go get github.com/daixiang0/gci ``` ## Usage + +Now GCI provides two command line methods, mainly for backward compatibility. + +### New style GCI supports three modes of operation + ```shell $ gci print -h Print outputs the formatted file. If you want to apply the changes to a file use write instead! @@ -81,11 +86,29 @@ Flags: -d, --debug Enables debug output from the formatter -h, --help help for diff ``` -Support for the old CLI style is still present if you do not specify the subcommands. The only difference is that `--local` requires two dashes now. + +### Old style + +```shell +Usage: + gci [-diff | -write] [--local localPackageURLs] path... [flags] + +Flags: + -d, --diff display diffs instead of rewriting files + -h, --help help for gci + -l, --local strings put imports beginning with this string after 3rd-party packages, separate imports by comma + -v, --version version for gci + -w, --write write result to (source) file instead of stdout + +``` + +**Note**:: + +The old style is only for local tests, `golangci-lint` uses new style. ## Examples -Run `gci write --Section Standard --Section Default --Section "Prefix(github.com/daixiang0/gci)" main.go` and you will handle following cases: +Run `gci write --Section Standard --Section Default --Section "Prefix(github.com/daixiang0/gci)" main.go` and you will handle following cases: ### simple case diff --git a/cmd/gci/completion.go b/cmd/gci/completion.go index 5992105..8880f3b 100644 --- a/cmd/gci/completion.go +++ b/cmd/gci/completion.go @@ -21,6 +21,7 @@ func subCommandOrGoFileCompletion(cmd *cobra.Command, args []string, toComplete } return goFileCompletion(cmd, args, toComplete) } + func goFileCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{"go"}, cobra.ShellCompDirectiveFilterFileExt } diff --git a/cmd/gci/gcicommand.go b/cmd/gci/gcicommand.go index 4aaf076..9c4fdd5 100644 --- a/cmd/gci/gcicommand.go +++ b/cmd/gci/gcicommand.go @@ -3,13 +3,14 @@ package gci import ( "fmt" + "github.com/spf13/cobra" + "go.uber.org/zap/zapcore" + "github.com/daixiang0/gci/pkg/configuration" "github.com/daixiang0/gci/pkg/constants" "github.com/daixiang0/gci/pkg/gci" sectionsPkg "github.com/daixiang0/gci/pkg/gci/sections" - log "github.com/sirupsen/logrus" - - "github.com/spf13/cobra" + "github.com/daixiang0/gci/pkg/log" ) type processingFunc = func(args []string, gciCfg gci.GciConfiguration) error @@ -30,7 +31,7 @@ func (e *Executor) newGciCommand(use, short, long string, aliases []string, stdI return err } if *debug { - log.SetLevel(log.DebugLevel) + log.SetLevel(zapcore.DebugLevel) } return processingFunc(args, *gciCfg) }, diff --git a/cmd/gci/root.go b/cmd/gci/root.go index 9e51fc4..b61ec7e 100644 --- a/cmd/gci/root.go +++ b/cmd/gci/root.go @@ -4,22 +4,13 @@ import ( "fmt" "os" + "github.com/spf13/cobra" + "github.com/daixiang0/gci/pkg/configuration" "github.com/daixiang0/gci/pkg/gci" - log "github.com/sirupsen/logrus" - - "github.com/spf13/cobra" + "github.com/daixiang0/gci/pkg/log" ) -func init() { - log.SetFormatter(&log.TextFormatter{ - DisableLevelTruncation: true, - FullTimestamp: true, - }) - - log.SetOutput(os.Stderr) -} - type Executor struct { rootCmd *cobra.Command diffMode *bool @@ -28,9 +19,12 @@ type Executor struct { } func NewExecutor(version string) *Executor { + log.InitLogger() + defer log.L().Sync() + e := Executor{} rootCmd := cobra.Command{ - Use: "gci [-diff | -write] [-local localPackageURLs] path...", + Use: "gci [-diff | -write] [--local localPackageURLs] path...", Short: "Gci controls golang package import order and makes it always deterministic", Long: "Gci enables automatic formatting of imports in a deterministic manner" + "\n" + diff --git a/go.mod b/go.mod index ab1fae1..7345497 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/daixiang0/gci -go 1.17 +go 1.18 require ( github.com/hexops/gotextdiff v1.0.3 - github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v1.3.0 github.com/stretchr/testify v1.7.0 + go.uber.org/zap v1.17.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/tools v0.1.5 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b @@ -15,9 +15,10 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect golang.org/x/mod v0.5.0 // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect diff --git a/go.sum b/go.sum index fe49f66..fde9ec6 100644 --- a/go.sum +++ b/go.sum @@ -236,7 +236,6 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -279,6 +278,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -304,7 +304,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= @@ -343,8 +342,11 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -759,6 +761,7 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= diff --git a/main.go b/main.go index 5ef3c31..9496b2e 100644 --- a/main.go +++ b/main.go @@ -6,9 +6,7 @@ import ( "github.com/daixiang0/gci/cmd/gci" ) -var ( - version = "0.3" -) +var version = "0.3" func main() { e := gci.NewExecutor(version) diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 10818a7..6ca93e1 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -6,12 +6,13 @@ import ( "go/token" "strings" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "github.com/daixiang0/gci/pkg/configuration" "github.com/daixiang0/gci/pkg/gci" "github.com/daixiang0/gci/pkg/io" - - "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" + "github.com/daixiang0/gci/pkg/log" ) const ( @@ -19,7 +20,7 @@ const ( NoPrefixCommentsFlag = "noPrefixComments" SectionsFlag = "Sections" SectionSeparatorsFlag = "SectionSeparators" - SectionDelimiter = ";" + SectionDelimiter = "," ) var ( @@ -34,6 +35,9 @@ func init() { Analyzer.Flags.BoolVar(&noPrefixComments, NoPrefixCommentsFlag, false, "If comments above an input should be present") Analyzer.Flags.StringVar(§ionsStr, SectionsFlag, "", "Specify the Sections format that should be used to check the file formatting") Analyzer.Flags.StringVar(§ionSeparatorsStr, SectionSeparatorsFlag, "", "Specify the Sections that are inserted as Separators between Sections") + + log.InitLogger() + defer log.L().Sync() } var Analyzer = &analysis.Analyzer{ @@ -44,8 +48,6 @@ var Analyzer = &analysis.Analyzer{ } func runAnalysis(pass *analysis.Pass) (interface{}, error) { - // TODO input validation - var fileReferences []*token.File // extract file references for all files in the analyzer pass for _, pkgFile := range pass.Files { @@ -80,17 +82,7 @@ func runAnalysis(pass *analysis.Pass) (interface{}, error) { case -1: // no difference default: - diffPos := file.Position(file.Pos(diffIdx)) - // prevent invalid access to array - fileRune := "nil" - formattedRune := "nil" - if len(fileRunes)-1 >= diffIdx { - fileRune = fmt.Sprintf("%q", fileRunes[diffIdx]) - } - if len(formattedRunes)-1 >= diffIdx { - formattedRune = fmt.Sprintf("%q", formattedRunes[diffIdx]) - } - pass.Reportf(file.Pos(diffIdx), "Expected %s, Found %s at %s[line %d,col %d]", formattedRune, fileRune, filePath, diffPos.Line, diffPos.Column) + pass.Reportf(file.Pos(diffIdx), "fix by `%s %s`", generateCmdLine(*gciCfg), filePath) } } return nil, nil @@ -126,6 +118,27 @@ func parseGciConfiguration() (*gci.GciConfiguration, error) { var sectionSeparatorStrings []string if sectionSeparatorsStr != "" { sectionSeparatorStrings = strings.Split(sectionSeparatorsStr, SectionDelimiter) + fmt.Println(sectionSeparatorsStr) } return gci.GciStringConfiguration{fmtCfg, sectionStrings, sectionSeparatorStrings}.Parse() } + +func generateCmdLine(cfg gci.GciConfiguration) string { + result := "gci write" + + if cfg.FormatterConfiguration.NoInlineComments { + result += " --NoInlineComments " + } + + if cfg.FormatterConfiguration.NoPrefixComments { + result += " --NoPrefixComments " + } + + for _, s := range cfg.Sections.String() { + result += fmt.Sprintf(" --Section \"%s\" ", s) + } + for _, s := range cfg.SectionSeparators.String() { + result += fmt.Sprintf(" --SectionSeparator %s ", s) + } + return result +} diff --git a/pkg/gci/configuration.go b/pkg/gci/configuration.go index f1ef7ed..5825238 100644 --- a/pkg/gci/configuration.go +++ b/pkg/gci/configuration.go @@ -3,10 +3,10 @@ package gci import ( "io/ioutil" + "gopkg.in/yaml.v3" + "github.com/daixiang0/gci/pkg/configuration" sectionsPkg "github.com/daixiang0/gci/pkg/gci/sections" - - "gopkg.in/yaml.v3" ) type GciConfiguration struct { diff --git a/pkg/gci/errors.go b/pkg/gci/errors.go index 6fde84f..90cb7ee 100644 --- a/pkg/gci/errors.go +++ b/pkg/gci/errors.go @@ -40,7 +40,7 @@ type InvalidImportSplitError struct { } func (i InvalidImportSplitError) Error() string { - return fmt.Sprintf("seperating the inline comment from the import yielded an invalid number of segments: %v", i.segments) + return fmt.Sprintf("separating the inline comment from the import yielded an invalid number of segments: %v", i.segments) } func (i InvalidImportSplitError) Is(err error) bool { @@ -53,7 +53,7 @@ type InvalidAliasSplitError struct { } func (i InvalidAliasSplitError) Error() string { - return fmt.Sprintf("seperating the alias from the path yielded an invalid number of segments: %v", i.segments) + return fmt.Sprintf("separating the alias from the path yielded an invalid number of segments: %v", i.segments) } func (i InvalidAliasSplitError) Is(err error) bool { @@ -61,8 +61,10 @@ func (i InvalidAliasSplitError) Is(err error) bool { return ok } -var MissingImportStatementError = FileParsingError{errors.New("no import statement present in File")} -var ImportStatementNotClosedError = FileParsingError{errors.New("import statement not closed")} +var ( + MissingImportStatementError = FileParsingError{errors.New("no import statement present in File")} + ImportStatementNotClosedError = FileParsingError{errors.New("import statement not closed")} +) type FileParsingError struct { error diff --git a/pkg/gci/errors_test.go b/pkg/gci/errors_test.go index b67bddb..7e3583e 100644 --- a/pkg/gci/errors_test.go +++ b/pkg/gci/errors_test.go @@ -4,10 +4,10 @@ import ( "errors" "testing" + "github.com/stretchr/testify/assert" + importPkg "github.com/daixiang0/gci/pkg/gci/imports" sectionsPkg "github.com/daixiang0/gci/pkg/gci/sections" - - "github.com/stretchr/testify/assert" ) func TestErrorMatching(t *testing.T) { diff --git a/pkg/gci/format.go b/pkg/gci/format.go index 10f762c..2ac9fa1 100644 --- a/pkg/gci/format.go +++ b/pkg/gci/format.go @@ -9,8 +9,7 @@ import ( importPkg "github.com/daixiang0/gci/pkg/gci/imports" sectionsPkg "github.com/daixiang0/gci/pkg/gci/sections" "github.com/daixiang0/gci/pkg/gci/specificity" - - log "github.com/sirupsen/logrus" + "github.com/daixiang0/gci/pkg/log" ) // Formats the import section of a Go file @@ -38,29 +37,14 @@ func formatGoFile(input []byte, cfg GciConfiguration) ([]byte, error) { return output, nil } -// pprintImports prints the imports without quotes for logging -func pprintImports(imports []importPkg.ImportDef) string { - var sb strings.Builder - sb.WriteRune('[') - for i, imprt := range imports { - if i != 0 { - sb.WriteRune(' ') - } - sb.WriteString(imprt.UnquotedString()) - } - sb.WriteRune(']') - return sb.String() -} - // Takes unsorted imports as byte array and formats them according to the specified sections func formatImportBlock(input []byte, cfg GciConfiguration) ([]byte, error) { - //strings.ReplaceAll(input, "\r\n", linebreak) lines := strings.Split(string(input), constants.Linebreak) imports, err := parseToImportDefinitions(lines) if err != nil { - return nil, fmt.Errorf("an error occured while trying to parse imports: %w", err) + return nil, fmt.Errorf("an error occurred while trying to parse imports: %w", err) } - log.WithField("imports", pprintImports(imports)).Debug("Parsed imports in file") + log.L().Debug(fmt.Sprintf("Parsed imports in file: %v", imports)) // create mapping between sections and imports sectionMap := make(map[sectionsPkg.Section][]importPkg.ImportDef, len(cfg.Sections)) @@ -84,7 +68,7 @@ func formatImportBlock(input []byte, cfg GciConfiguration) ([]byte, error) { if bestSection == nil { return nil, NoMatchingSectionForImportError{i} } - log.WithFields(log.Fields{"import": i.UnquotedString(), "section": bestSection}).Debug("Matched import to section") + log.L().Debug(fmt.Sprintf("Matched import %s to section %s", i, bestSection)) sectionMap[bestSection] = append(sectionMap[bestSection], i) } @@ -94,7 +78,7 @@ func formatImportBlock(input []byte, cfg GciConfiguration) ([]byte, error) { sectionStr := section.Format(sectionMap[section], cfg.FormatterConfiguration) // prevent adding an empty section which would cause a separator to be inserted if sectionStr != "" { - log.WithFields(log.Fields{"imports": pprintImports(sectionMap[section]), "section": section}).Debug("Formatting section with imports") + log.L().Debug(fmt.Sprintf("Formatting section %s with imports: %v", section, sectionMap[section])) sectionStrings = append(sectionStrings, sectionStr) } } diff --git a/pkg/gci/gci.go b/pkg/gci/gci.go index 0d70f5d..eb30c11 100644 --- a/pkg/gci/gci.go +++ b/pkg/gci/gci.go @@ -5,15 +5,16 @@ import ( "errors" "fmt" "os" - - sectionsPkg "github.com/daixiang0/gci/pkg/gci/sections" - "github.com/daixiang0/gci/pkg/io" + "sync" "github.com/hexops/gotextdiff" "github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/span" - log "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" + + sectionsPkg "github.com/daixiang0/gci/pkg/gci/sections" + "github.com/daixiang0/gci/pkg/io" + "github.com/daixiang0/gci/pkg/log" ) type SectionList []sectionsPkg.Section @@ -33,6 +34,7 @@ func DefaultSections() SectionList { func DefaultSectionSeparators() SectionList { return SectionList{sectionsPkg.NewLine{}} } + func LocalFlagsToSections(localFlags []string) SectionList { sections := DefaultSections() // Add all local arguments as ImportPrefix sections @@ -52,11 +54,11 @@ func PrintFormattedFiles(paths []string, cfg GciConfiguration) error { func WriteFormattedFiles(paths []string, cfg GciConfiguration) error { return processGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error { if bytes.Equal(unmodifiedFile, formattedFile) { - log.WithField("file", filePath).Debug("Skipping correctly formatted file") + log.L().Debug(fmt.Sprintf("Skipping correctly formatted File: %s", filePath)) return nil } - log.WithField("file", filePath).Info("Writing formatted file") - return os.WriteFile(filePath, formattedFile, 0644) + log.L().Info(fmt.Sprintf("Writing formatted File: %s", filePath)) + return os.WriteFile(filePath, formattedFile, 0o644) }) } @@ -70,6 +72,20 @@ func DiffFormattedFiles(paths []string, cfg GciConfiguration) error { }) } +func DiffFormattedFilesToArray(paths []string, cfg GciConfiguration, diffs *[]string, lock *sync.Mutex) error { + log.InitLogger() + defer log.L().Sync() + return processStdInAndGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error { + fileURI := span.URIFromPath(filePath) + edits := myers.ComputeEdits(fileURI, string(unmodifiedFile), string(formattedFile)) + unifiedEdits := gotextdiff.ToUnified(filePath, filePath, string(unmodifiedFile), edits) + lock.Lock() + *diffs = append(*diffs, fmt.Sprint(unifiedEdits)) + lock.Unlock() + return nil + }) +} + type fileFormattingFunc func(filePath string, unmodifiedFile, formattedFile []byte) error func processStdInAndGoFilesInPaths(paths []string, cfg GciConfiguration, fileFunc fileFormattingFunc) error { @@ -109,7 +125,7 @@ func processingFunc(file io.FileObj, cfg GciConfiguration, formattingFunc fileFo func LoadFormatGoFile(file io.FileObj, cfg GciConfiguration) (unmodifiedFile, formattedFile []byte, err error) { unmodifiedFile, err = file.Load() - log.WithField("file", file.Path()).Debug("Loaded file") + log.L().Debug(fmt.Sprintf("Loaded File: %s", file.Path())) if err != nil { return nil, nil, err } @@ -120,7 +136,7 @@ func LoadFormatGoFile(file io.FileObj, cfg GciConfiguration) (unmodifiedFile, fo if !errors.Is(err, MissingImportStatementError) { return unmodifiedFile, nil, err } - log.WithField("file", file.Path()).Debug("File does not contain an import statement") + log.L().Debug(fmt.Sprintf("File does not contain an import statement: %s", file.Path())) formattedFile = unmodifiedFile } return unmodifiedFile, formattedFile, nil diff --git a/pkg/gci/gci_test.go b/pkg/gci/gci_test.go index 3fb34f9..15362ac 100644 --- a/pkg/gci/gci_test.go +++ b/pkg/gci/gci_test.go @@ -7,12 +7,18 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/daixiang0/gci/pkg/gci/sections" "github.com/daixiang0/gci/pkg/io" - - "github.com/stretchr/testify/assert" + "github.com/daixiang0/gci/pkg/log" ) +func init() { + log.InitLogger() + defer log.L().Sync() +} + var testFilesPath = "internal/testdata" func isTestInputFile(file os.FileInfo) bool { @@ -73,7 +79,6 @@ func TestInitGciConfigFromYAML(t *testing.T) { func TestSkippingOverIncorrectlyFormattedFiles(t *testing.T) { cfg, err := GciStringConfiguration{}.Parse() assert.NoError(t, err) - validFileProcessedChan := make(chan bool, 1) var importUnclosedCtr, noImportCtr, validCtr int var files []io.FileObj @@ -81,11 +86,12 @@ func TestSkippingOverIncorrectlyFormattedFiles(t *testing.T) { files = append(files, TestFile{io.File{"internal/skipTest/no-import.testgo"}, &noImportCtr}) files = append(files, TestFile{io.File{"internal/skipTest/valid.testgo"}, &validCtr}) + validFileProcessedChan := make(chan bool, len(files)) + generatorFunc := func() ([]io.FileObj, error) { return files, nil } fileAccessTestFunc := func(filePath string, unmodifiedFile, formattedFile []byte) error { - assert.Equal(t, "internal/skipTest/valid.testgo", filePath, "file should not have been processed") validFileProcessedChan <- true return nil } diff --git a/pkg/gci/imports/import.go b/pkg/gci/imports/import.go index 202477c..2e87ca0 100644 --- a/pkg/gci/imports/import.go +++ b/pkg/gci/imports/import.go @@ -53,11 +53,6 @@ func (i ImportDef) String() string { return i.QuotedPath } -// useful for logging statements -func (i ImportDef) UnquotedString() string { - return strings.Trim(i.QuotedPath, "\"") -} - func (i ImportDef) Format(cfg configuration.FormatterConfiguration) string { linePrefix := constants.Indent var output string diff --git a/pkg/gci/parse.go b/pkg/gci/parse.go index df31005..9ed3821 100644 --- a/pkg/gci/parse.go +++ b/pkg/gci/parse.go @@ -14,7 +14,7 @@ func parseToImportDefinitions(unformattedLines []string) ([]importPkg.ImportDef, for index, unformattedLine := range unformattedLines { line := strings.TrimSpace(unformattedLine) if line == "" { - //empty line --> starts a new import + // empty line --> starts a new import return parseToImportDefinitions(unformattedLines[index+1:]) } if strings.HasPrefix(line, constants.LineCommentFlag) { diff --git a/pkg/gci/sections/newline_test.go b/pkg/gci/sections/newline_test.go index a13b4cc..7fca8f7 100644 --- a/pkg/gci/sections/newline_test.go +++ b/pkg/gci/sections/newline_test.go @@ -14,6 +14,7 @@ func TestNewLineSpecificity(t *testing.T) { } testSpecificity(t, testCases) } + func TestNewLineParsing(t *testing.T) { testCases := []sectionTestData{ {"nl", NewLine{}, nil}, diff --git a/pkg/gci/sections/section.go b/pkg/gci/sections/section.go index cbfd467..56dd4b6 100644 --- a/pkg/gci/sections/section.go +++ b/pkg/gci/sections/section.go @@ -22,8 +22,6 @@ type Section interface { String() string } -//Unbound methods that are required until interface methods are supported - // Default method for formatting a section func inorderSectionFormat(section Section, imports []importPkg.ImportDef, cfg configuration.FormatterConfiguration) string { imports = importPkg.SortImportsByPath(imports) diff --git a/pkg/gci/sections/standardpackage_list.go b/pkg/gci/sections/standardpackage_list.go index 645233e..d11dee2 100644 --- a/pkg/gci/sections/standardpackage_list.go +++ b/pkg/gci/sections/standardpackage_list.go @@ -1,6 +1,6 @@ package sections -// Code generated based on go1.18. DO NOT EDIT. +// Code generated based on go1.18.2. DO NOT EDIT. var standardPackages = map[string]struct{}{ "archive/tar": {}, diff --git a/pkg/gci/specificity/default.go b/pkg/gci/specificity/default.go index 2d91bd8..f7ae4b8 100644 --- a/pkg/gci/specificity/default.go +++ b/pkg/gci/specificity/default.go @@ -1,11 +1,11 @@ package specificity -type Default struct { -} +type Default struct{} func (d Default) IsMoreSpecific(than MatchSpecificity) bool { return isMoreSpecific(d, than) } + func (d Default) Equal(to MatchSpecificity) bool { return equalSpecificity(d, to) } diff --git a/pkg/gci/specificity/mismatch.go b/pkg/gci/specificity/mismatch.go index 78013e3..8e87111 100644 --- a/pkg/gci/specificity/mismatch.go +++ b/pkg/gci/specificity/mismatch.go @@ -1,7 +1,6 @@ package specificity -type MisMatch struct { -} +type MisMatch struct{} func (m MisMatch) IsMoreSpecific(than MatchSpecificity) bool { return isMoreSpecific(m, than) @@ -17,5 +16,4 @@ func (m MisMatch) class() specificityClass { func (m MisMatch) String() string { return "Mismatch" - } diff --git a/pkg/gci/specificity/specificity.go b/pkg/gci/specificity/specificity.go index 32b6e28..0a7c9f8 100644 --- a/pkg/gci/specificity/specificity.go +++ b/pkg/gci/specificity/specificity.go @@ -16,8 +16,6 @@ type MatchSpecificity interface { class() specificityClass } -//Unbound methods that are required until interface methods are supported - func isMoreSpecific(this, than MatchSpecificity) bool { return this.class() > than.class() } diff --git a/pkg/gci/specificity/standard.go b/pkg/gci/specificity/standard.go index 30e8f8f..8b11d04 100644 --- a/pkg/gci/specificity/standard.go +++ b/pkg/gci/specificity/standard.go @@ -1,7 +1,6 @@ package specificity -type StandardPackageMatch struct { -} +type StandardPackageMatch struct{} func (s StandardPackageMatch) IsMoreSpecific(than MatchSpecificity) bool { return isMoreSpecific(s, than) diff --git a/pkg/io/stdin.go b/pkg/io/stdin.go index 5d92768..ccab284 100644 --- a/pkg/io/stdin.go +++ b/pkg/io/stdin.go @@ -5,8 +5,7 @@ import ( "os" ) -type stdInFile struct { -} +type stdInFile struct{} func (s stdInFile) Load() ([]byte, error) { return ioutil.ReadAll(os.Stdin) diff --git a/pkg/log/log.go b/pkg/log/log.go new file mode 100644 index 0000000..ab33739 --- /dev/null +++ b/pkg/log/log.go @@ -0,0 +1,50 @@ +package log + +import ( + "sync" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// Use L to log with Zap +var logger *zap.Logger + +// Keep the config to reference the atomicLevel for changing levels +var logConfig zap.Config + +var doOnce sync.Once + +// InitLogger sets up the logger +func InitLogger() { + doOnce.Do(func() { + logConfig = zap.NewDevelopmentConfig() + + logConfig.EncoderConfig.TimeKey = "timestamp" + logConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + logConfig.Level.SetLevel(zapcore.InfoLevel) + logConfig.OutputPaths = []string{"stderr"} + + var err error + logger, err = logConfig.Build() + if err != nil { + panic(err) + } + }) +} + +// SetLevel allows you to set the level of the default gci logger. +// This will not work if you replace the logger +func SetLevel(level zapcore.Level) { + logConfig.Level.SetLevel(level) +} + +// L returns the logger +func L() *zap.Logger { + return logger +} + +// SetLogger allows you to set the logger to whatever you want +func SetLogger(l *zap.Logger) { + logger = l +}