From 6791ae18a5f8dc99d0e64e40965939e0962385d8 Mon Sep 17 00:00:00 2001 From: xobotyi Date: Sun, 20 Mar 2022 23:27:07 +0300 Subject: [PATCH] feat: add `exhaustruct` linter This linter can be called a successor of `exhaustivestruct`, and: - it is at least **2.5+ times faster**, due to better algorithm; - can receive `include` and/or `exclude` patterns; - expects received patterns to be RegExp, therefore this package is not api-compatible with `exhaustivestruct`. Also: deprecate `exhaustivestruct` linter --- .golangci.example.yml | 10 +++ go.mod | 2 + go.sum | 4 + pkg/config/linters_settings.go | 6 ++ pkg/golinters/exhaustruct.go | 32 ++++++++ pkg/lint/lintersdb/manager.go | 11 ++- test/testdata/exhaustruct.go | 40 ++++++++++ test/testdata/exhaustruct_custom.go | 114 ++++++++++++++++++++++++++++ 8 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 pkg/golinters/exhaustruct.go create mode 100644 test/testdata/exhaustruct.go create mode 100644 test/testdata/exhaustruct_custom.go diff --git a/.golangci.example.yml b/.golangci.example.yml index 9edde4f472d9..183b3eef765e 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -285,6 +285,16 @@ linters-settings: - '*.Test' - 'example.com/package.ExampleStruct' + exhaustruct: + # List of regular expressions to match struct packages and names. + # If this list is empty, all structs are tested. + include: + - '.*\.Test' + - 'example\.com/package\.ExampleStruct' + # List of regular expressions to exclude struct packages and names from check. + exclude: + - 'cobra\.Command$' + forbidigo: # Forbid the following identifiers (list of regexp). forbid: diff --git a/go.mod b/go.mod index d8c8af618a7c..e5844abde1fb 100644 --- a/go.mod +++ b/go.mod @@ -104,6 +104,8 @@ require ( mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 ) +require github.com/GaijinEntertainment/go-exhaustruct v1.0.0 + require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index c0b709bdbb4c..5fd9d74bd679 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,10 @@ github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GaijinEntertainment/go-exhaustruct v0.0.0-20220320194800-c73fb34375ae h1:vcGb2Pu07VvKQRgoEM6vq2i48lSFHZHs18lOLGYK+fk= +github.com/GaijinEntertainment/go-exhaustruct v0.0.0-20220320194800-c73fb34375ae/go.mod h1:jXk57htv8oR5kz8IClFCxBykWOwS2VWHRuRT4V+a5Xc= +github.com/GaijinEntertainment/go-exhaustruct v1.0.0 h1:z+MQsgpeFDwmVZqB78E1nig4TX8IAGXr/LczFNbqRPU= +github.com/GaijinEntertainment/go-exhaustruct v1.0.0/go.mod h1:jXk57htv8oR5kz8IClFCxBykWOwS2VWHRuRT4V+a5Xc= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 97c2065d4bf5..3ad8c56eea3f 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -123,6 +123,7 @@ type LintersSettings struct { ErrorLint ErrorLintSettings Exhaustive ExhaustiveSettings ExhaustiveStruct ExhaustiveStructSettings + Exhaustruct ExhaustructSettings Forbidigo ForbidigoSettings Funlen FunlenSettings Gci GciSettings @@ -255,6 +256,11 @@ type ExhaustiveStructSettings struct { StructPatterns []string `mapstructure:"struct-patterns"` } +type ExhaustructSettings struct { + Include []string `mapstructure:"include"` + Exclude []string `mapstructure:"exclude"` +} + type ForbidigoSettings struct { Forbid []string `mapstructure:"forbid"` ExcludeGodocExamples bool `mapstructure:"exclude-godoc-examples"` diff --git a/pkg/golinters/exhaustruct.go b/pkg/golinters/exhaustruct.go new file mode 100644 index 000000000000..50c065310808 --- /dev/null +++ b/pkg/golinters/exhaustruct.go @@ -0,0 +1,32 @@ +package golinters + +import ( + "strings" + + "github.com/GaijinEntertainment/go-exhaustruct/pkg/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewExhaustruct(settings *config.ExhaustructSettings) *goanalysis.Linter { + a := analyzer.Analyzer + + var cfg map[string]map[string]interface{} + if settings != nil { + cfg = map[string]map[string]interface{}{ + a.Name: { + "include": strings.Join(settings.Include, ","), + "exclude": strings.Join(settings.Exclude, ","), + }, + } + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + cfg, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 6178e0bec2c7..ea63924e966e 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -107,6 +107,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { var errorlintCfg *config.ErrorLintSettings var exhaustiveCfg *config.ExhaustiveSettings var exhaustiveStructCfg *config.ExhaustiveStructSettings + var exhaustructCfg *config.ExhaustructSettings var gciCfg *config.GciSettings var goModDirectivesCfg *config.GoModDirectivesSettings var goMndCfg *config.GoMndSettings @@ -140,6 +141,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { errorlintCfg = &m.cfg.LintersSettings.ErrorLint exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive exhaustiveStructCfg = &m.cfg.LintersSettings.ExhaustiveStruct + exhaustructCfg = &m.cfg.LintersSettings.Exhaustruct gciCfg = &m.cfg.LintersSettings.Gci goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives goMndCfg = &m.cfg.LintersSettings.Gomnd @@ -281,7 +283,14 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithSince("v1.32.0"). WithPresets(linter.PresetStyle, linter.PresetTest). WithLoadForGoAnalysis(). - WithURL("https://github.com/mbilski/exhaustivestruct"), + WithURL("https://github.com/mbilski/exhaustivestruct"). + Deprecated("Owner seems to abandon linter.", "v1.46.0", "exhaustruct"), + + linter.NewConfig(golinters.NewExhaustruct(exhaustructCfg)). + WithSince("v1.46.0"). + WithPresets(linter.PresetStyle, linter.PresetTest). + WithLoadForGoAnalysis(). + WithURL("https://github.com/GaijinEntertainment/go-exhaustruct"), linter.NewConfig(golinters.NewExportLoopRef()). WithSince("v1.28.0"). diff --git a/test/testdata/exhaustruct.go b/test/testdata/exhaustruct.go new file mode 100644 index 000000000000..2f5d2432ceb7 --- /dev/null +++ b/test/testdata/exhaustruct.go @@ -0,0 +1,40 @@ +//args: -Eexhaustruct +package testdata + +import "time" + +type Test struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +var pass = Test{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), +} + +var failPrivate = Test{ // ERROR "c is missing in Test" + A: "a", + B: 0, + D: 1.0, + E: time.Now(), +} + +var fail = Test{ // ERROR "B is missing in Test" + A: "a", + c: false, + D: 1.0, + E: time.Now(), +} + +var failMultiple = Test{ // ERROR "B, D are missing in Test" + A: "a", + c: false, + E: time.Now(), +} diff --git a/test/testdata/exhaustruct_custom.go b/test/testdata/exhaustruct_custom.go new file mode 100644 index 000000000000..52d966511322 --- /dev/null +++ b/test/testdata/exhaustruct_custom.go @@ -0,0 +1,114 @@ +//args: -Eexhaustruct +//config: linters-settings.exhaustruct.include=.*\.Test1$ +//config: linters-settings.exhaustruct.exclude=.*\.Test3$ +package testdata + +import "time" + +type Test1 struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +var passTest1 = Test1{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), +} + +var failTest1 = Test1{ // ERROR "B is missing in Test" + A: "a", + c: false, + D: 1.0, + E: time.Now(), +} + +var failMultipleTest1 = Test1{ // ERROR "B, D are missing in Test" + A: "a", + c: false, + E: time.Now(), +} + +var failPrivateTest1 = Test1{ // ERROR "c is missing in Test" + A: "a", + B: 0, + D: 1.0, + E: time.Now(), +} + +type Test2 struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +var passTest2 = Test1{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), +} + +var failTest2 = Test2{ + A: "a", + c: false, + D: 1.0, + E: time.Now(), +} + +var failMultipleTest2 = Test2{ + A: "a", + c: false, + E: time.Now(), +} + +var failPrivateTest2 = Test2{ + A: "a", + B: 0, + D: 1.0, + E: time.Now(), +} + +type Test3 struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +var passTest3 = Test3{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), +} + +var failTest3 = Test3{ + A: "a", + c: false, + D: 1.0, + E: time.Now(), +} + +var failMultipleTest3 = Test3{ + A: "a", + c: false, + E: time.Now(), +} + +var failPrivateTest3 = Test3{ + A: "a", + B: 0, + D: 1.0, + E: time.Now(), +}