diff --git a/.golangci.example.yml b/.golangci.example.yml index 12f5c2f544d4..a7d20d071fb0 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[\d]{1,2}' + # 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 9e654f3ae67f..c601093349ca 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Antonboom/nilnil v0.1.1 github.com/BurntSushi/toml v1.1.0 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 + github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0 github.com/OpenPeeDeeP/depguard v1.1.0 github.com/alexkohler/prealloc v1.0.0 github.com/ashanbrown/forbidigo v1.3.0 diff --git a/go.sum b/go.sum index bf438564a43e..0009bb7d8aae 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ github.com/BurntSushi/toml v1.1.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/v2 v2.1.0 h1:LAPPhJ4KR5Z8aKVZF5S48csJkxL5RMKmE/98fMs1u5M= +github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0/go.mod h1:LGOGuvEgCfCQsy3JF2tRmpGDpzA53iZfyGEWSPwQ6/4= 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 088223f0db62..cb4095d03aae 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/commons.go b/pkg/golinters/commons.go new file mode 100644 index 000000000000..a4c3913f7e25 --- /dev/null +++ b/pkg/golinters/commons.go @@ -0,0 +1,6 @@ +package golinters + +import "github.com/golangci/golangci-lint/pkg/logutils" + +// linterLogger must be use only when the context logger is not available. +var linterLogger = logutils.NewStderrLog("linter") diff --git a/pkg/golinters/exhaustruct.go b/pkg/golinters/exhaustruct.go new file mode 100644 index 000000000000..8c843289dd9e --- /dev/null +++ b/pkg/golinters/exhaustruct.go @@ -0,0 +1,26 @@ +package golinters + +import ( + "github.com/GaijinEntertainment/go-exhaustruct/v2/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 { + var include, exclude []string + + if settings != nil { + include = settings.Include + exclude = settings.Exclude + } + + a, err := analyzer.NewAnalyzer(include, exclude) + if err != nil { + linterLogger.Fatalf("exhaustruct configuration: %v", err) + } + + return goanalysis.NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil). + WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 4c405c0fcf61..434d435d03c4 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("The owner seems to have abandoned the 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/configs/exhaustivestruct.yml b/test/testdata/configs/exhaustivestruct.yml new file mode 100644 index 000000000000..21d69c55dd76 --- /dev/null +++ b/test/testdata/configs/exhaustivestruct.yml @@ -0,0 +1,5 @@ +linters-settings: + exhaustivestruct: + struct-patterns: + - '*.ExhaustiveStructCustom' + - '*.ExhaustiveStructCustom2' diff --git a/test/testdata/configs/exhaustruct.yml b/test/testdata/configs/exhaustruct.yml new file mode 100644 index 000000000000..a2cb20f96528 --- /dev/null +++ b/test/testdata/configs/exhaustruct.yml @@ -0,0 +1,6 @@ +linters-settings: + exhaustruct: + include: + - .*\.ExhaustructCustom + exclude: + - .*\.ExhaustructCustom[\d]{1,2} diff --git a/test/testdata/exhaustivestruct.go b/test/testdata/exhaustivestruct.go index 49b1ea98639d..fde230dfdce1 100644 --- a/test/testdata/exhaustivestruct.go +++ b/test/testdata/exhaustivestruct.go @@ -1,9 +1,9 @@ -//args: -Eexhaustivestruct +// args: -Eexhaustivestruct --internal-cmd-test package testdata import "time" -type Test struct { +type ExhaustiveStruct struct { A string B int c bool // private field inside the same package are not ignored @@ -11,30 +11,36 @@ type Test struct { E time.Time } -var pass = Test{ - A: "a", - B: 0, - c: false, - D: 1.0, - E: time.Now(), -} +func exhaustiveStruct() { + // pass + _ = ExhaustiveStruct{ + 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(), -} + // failPrivate + _ = ExhaustiveStruct{ // ERROR "c is missing in ExhaustiveStruct" + 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(), -} + // fail + _ = ExhaustiveStruct{ // ERROR "B is missing in ExhaustiveStruct" + 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(), + // failMultiple + _ = ExhaustiveStruct{ // ERROR "B, D are missing in ExhaustiveStruct" + A: "a", + c: false, + E: time.Now(), + } } diff --git a/test/testdata/exhaustivestruct_custom.go b/test/testdata/exhaustivestruct_custom.go index f3e1c7fa6fbf..156b7b631552 100644 --- a/test/testdata/exhaustivestruct_custom.go +++ b/test/testdata/exhaustivestruct_custom.go @@ -1,10 +1,10 @@ -//args: -Eexhaustivestruct -//config: linters-settings.exhaustivestruct.struct-patterns=*.Test1,*.Test3 +// args: -Eexhaustivestruct --internal-cmd-test +// config_path: testdata/configs/exhaustivestruct.yml package testdata import "time" -type Test1 struct { +type ExhaustiveStructCustom struct { A string B int c bool // private field inside the same package are not ignored @@ -12,35 +12,42 @@ type Test1 struct { 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(), -} +func exhaustiveStructCustom() { + // pass + _ = ExhaustiveStructCustom{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), + } + + // fail + _ = ExhaustiveStructCustom{ // ERROR "B is missing in ExhaustiveStructCustom" + A: "a", + c: false, + D: 1.0, + E: time.Now(), + } + + // failMultiple + _ = ExhaustiveStructCustom{ // ERROR "B, D are missing in ExhaustiveStructCustom" + A: "a", + c: false, + E: time.Now(), + } + + // failPrivate + _ = ExhaustiveStructCustom{ // ERROR "c is missing in ExhaustiveStructCustom" + A: "a", + B: 0, + D: 1.0, + 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 { +type ExhaustiveStructCustom1 struct { A string B int c bool // private field inside the same package are not ignored @@ -48,35 +55,37 @@ type Test2 struct { 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(), +func exhaustiveStructCustom1() { + _ = ExhaustiveStructCustom1{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), + } + + _ = ExhaustiveStructCustom1{ + A: "a", + c: false, + D: 1.0, + E: time.Now(), + } + + _ = ExhaustiveStructCustom1{ + A: "a", + c: false, + E: time.Now(), + } + + _ = ExhaustiveStructCustom1{ + A: "a", + B: 0, + D: 1.0, + E: time.Now(), + } } -type Test3 struct { +type ExhaustiveStructCustom2 struct { A string B int c bool // private field inside the same package are not ignored @@ -84,30 +93,37 @@ type Test3 struct { E time.Time } -var passTest3 = Test3{ - A: "a", - B: 0, - c: false, - D: 1.0, - E: time.Now(), -} - -var failTest3 = Test3{ // ERROR "B is missing in Test" - A: "a", - c: false, - D: 1.0, - E: time.Now(), -} - -var failMultipleTest3 = Test3{ // ERROR "B, D are missing in Test" - A: "a", - c: false, - E: time.Now(), -} +func exhaustiveStructCustom2() { + // pass + _ = ExhaustiveStructCustom2{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), + } + + // fail + _ = ExhaustiveStructCustom2{ // ERROR "B is missing in ExhaustiveStructCustom2" + A: "a", + c: false, + D: 1.0, + E: time.Now(), + } + + // failMultiple + _ = ExhaustiveStructCustom2{ // ERROR "B, D are missing in ExhaustiveStructCustom2" + A: "a", + c: false, + E: time.Now(), + } + + // failPrivate + _ = ExhaustiveStructCustom2{ // ERROR "c is missing in ExhaustiveStructCustom2" + A: "a", + B: 0, + D: 1.0, + E: time.Now(), + } -var failPrivateTest3 = Test3{ // ERROR "c is missing in Test" - A: "a", - B: 0, - D: 1.0, - E: time.Now(), } diff --git a/test/testdata/exhaustruct.go b/test/testdata/exhaustruct.go new file mode 100644 index 000000000000..6860e5e40e20 --- /dev/null +++ b/test/testdata/exhaustruct.go @@ -0,0 +1,47 @@ +// args: -Eexhaustruct +package testdata + +import "time" + +type Exhaustruct struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +func exhaustruct() { + // pass + _ = Exhaustruct{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), + } + + // failPrivate + _ = Exhaustruct{ // ERROR "c is missing in Exhaustruct" + A: "a", + B: 0, + D: 1.0, + E: time.Now(), + } + + // fail + _ = Exhaustruct{ // ERROR "B is missing in Exhaustruct" + A: "a", + c: false, + D: 1.0, + E: time.Now(), + } + + // failMultiple + _ = Exhaustruct{ // ERROR "B, D are missing in Exhaustruct" + 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..27e7b6a74c22 --- /dev/null +++ b/test/testdata/exhaustruct_custom.go @@ -0,0 +1,133 @@ +// args: -Eexhaustruct +// config_path: testdata/configs/exhaustruct.yml +package testdata + +import "time" + +type ExhaustructCustom struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +func exhaustructCustom() { + // pass + _ = ExhaustructCustom{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), + } + + // fail + _ = ExhaustructCustom{ // ERROR "B is missing in ExhaustructCustom" + A: "a", + c: false, + D: 1.0, + E: time.Now(), + } + + // failMultiple + _ = ExhaustructCustom{ // ERROR "B, D are missing in ExhaustructCustom" + A: "a", + c: false, + E: time.Now(), + } + + // failPrivate + _ = ExhaustructCustom{ // ERROR "c is missing in ExhaustructCustom" + A: "a", + B: 0, + D: 1.0, + E: time.Now(), + } + +} + +type ExhaustructCustom1 struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +func exhaustructCustom1() { + // pass + _ = ExhaustructCustom{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), + } + + // fail + _ = ExhaustructCustom1{ + A: "a", + c: false, + D: 1.0, + E: time.Now(), + } + + // failMultiple + _ = ExhaustructCustom1{ + A: "a", + c: false, + E: time.Now(), + } + + // failPrivate + _ = ExhaustructCustom1{ + A: "a", + B: 0, + D: 1.0, + E: time.Now(), + } + +} + +type ExhaustructCustom2 struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +func exhaustructCustom2() { + // pass + _ = ExhaustructCustom2{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), + } + + // fail + _ = ExhaustructCustom2{ + A: "a", + c: false, + D: 1.0, + E: time.Now(), + } + + // failMultiple + _ = ExhaustructCustom2{ + A: "a", + c: false, + E: time.Now(), + } + + // failPrivate + _ = ExhaustructCustom2{ + A: "a", + B: 0, + D: 1.0, + E: time.Now(), + } +}