diff --git a/.golangci.example.yml b/.golangci.example.yml index 2a1fd19998f3..32f23a9f1aaa 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -98,6 +98,11 @@ linters-settings: # path to a file containing a list of functions to exclude from checking # see https://github.com/kisielk/errcheck#excluding-functions for details exclude: /path/to/file.txt + exhaustive: + # indicates that switch statements are to be considered exhaustive if a + # 'default' case is present, even if all enum members aren't listed in the + # switch + default-signifies-exhaustive: false funlen: lines: 60 statements: 40 @@ -174,7 +179,7 @@ linters-settings: modules: # List of blocked modules # - github.com/uudashr/go-module: # Blocked module # recommendations: # Recommended modules that should be used instead (Optional) - # - golang.org/x/mod + # - golang.org/x/mod # reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional) versions: # List of blocked module version constraints # - github.com/mitchellh/go-homedir: # Blocked module with version constraint diff --git a/.golangci.yml b/.golangci.yml index 6ea547c8b493..a592fecf0c38 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,6 +9,8 @@ linters-settings: - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log" dupl: threshold: 100 + exhaustive: + default-signifies-exhaustive: false funlen: lines: 100 statements: 50 @@ -104,6 +106,7 @@ linters: # don't enable: # - asciicheck + # - exhaustive (TODO: enable after next release; current release at time of writing is v1.27) # - gochecknoglobals # - gocognit # - godot diff --git a/go.mod b/go.mod index 4981252585ed..114ddd82d701 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-ps v1.0.0 github.com/nakabonne/nestif v0.3.0 + github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132 github.com/pkg/errors v0.9.1 github.com/ryancurrah/gomodguard v1.1.0 github.com/securego/gosec/v2 v2.3.0 @@ -52,7 +53,7 @@ require ( github.com/ultraware/whitespace v0.0.4 github.com/uudashr/gocognit v1.0.1 github.com/valyala/quicktemplate v1.5.0 - golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770 + golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a gopkg.in/yaml.v2 v2.3.0 honnef.co/go/tools v0.0.1-2020.1.4 mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed diff --git a/go.sum b/go.sum index 03548d90c83c..8f8c61c376ec 100644 --- a/go.sum +++ b/go.sum @@ -267,6 +267,8 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132 h1:NjznefjSrral0MiR4KlB41io/d3OklvhcgQUdfZTqJE= +github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= @@ -511,6 +513,8 @@ golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770 h1:M9Fif0OxNji8w+HvmhVQ8KJ golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770 h1:M9Fif0OxNji8w+HvmhVQ8KJtiZOsjU9RgslJGhn95XE= golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770 h1:M9Fif0OxNji8w+HvmhVQ8KJtiZOsjU9RgslJGhn95XE= golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a h1:gILuVKC+ZPD6g/tj6zBOdnOH1ZHI0zZ86+KLMogc6/s= +golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= diff --git a/pkg/config/config.go b/pkg/config/config.go index b29e8f607d73..4c9c447c2e0a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -242,6 +242,7 @@ type LintersSettings struct { Testpackage TestpackageSettings Nestif NestifSettings NoLintLint NoLintLintSettings + Exhaustive ExhaustiveSettings Custom map[string]CustomLinterSettings } @@ -339,6 +340,10 @@ type NestifSettings struct { MinComplexity int `mapstructure:"min-complexity"` } +type ExhaustiveSettings struct { + DefaultSignifiesExhaustive bool `mapstructure:"default-signifies-exhaustive"` +} + var defaultLintersSettings = LintersSettings{ Lll: LllSettings{ LineLength: 120, @@ -389,6 +394,9 @@ var defaultLintersSettings = LintersSettings{ Nestif: NestifSettings{ MinComplexity: 5, }, + Exhaustive: ExhaustiveSettings{ + DefaultSignifiesExhaustive: false, + }, } type CustomLinterSettings struct { diff --git a/pkg/golinters/exhaustive.go b/pkg/golinters/exhaustive.go new file mode 100644 index 000000000000..cae37ecc61d3 --- /dev/null +++ b/pkg/golinters/exhaustive.go @@ -0,0 +1,25 @@ +package golinters + +import ( + "github.com/nishanths/exhaustive" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewExhaustive(settings *config.ExhaustiveSettings) *goanalysis.Linter { + a := exhaustive.Analyzer + + var cfg map[string]map[string]interface{} + if settings != nil { + cfg = map[string]map[string]interface{}{ + a.Name: { + exhaustive.DefaultSignifiesExhaustiveFlag: settings.DefaultSignifiesExhaustive, + }, + } + } + + return goanalysis.NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, cfg). + WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/golinters/goanalysis/runner.go b/pkg/golinters/goanalysis/runner.go index d07294245ef4..db193f37bb20 100644 --- a/pkg/golinters/goanalysis/runner.go +++ b/pkg/golinters/goanalysis/runner.go @@ -935,7 +935,8 @@ func sizeOfReflectValueTreeBytes(rv reflect.Value, visitedPtrs map[uintptr]struc return rv.Len() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Uintptr, reflect.Bool, reflect.Float32, reflect.Float64, reflect.UnsafePointer: + reflect.Uintptr, reflect.Bool, reflect.Float32, reflect.Float64, + reflect.Complex64, reflect.Complex128, reflect.Func, reflect.UnsafePointer: return int(rv.Type().Size()) case reflect.Invalid: return 0 diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 89ca1755dd9e..893f5aafc7e3 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -87,9 +87,11 @@ func enableLinterConfigs(lcs []*linter.Config, isEnabled func(lc *linter.Config) func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { var govetCfg *config.GovetSettings var testpackageCfg *config.TestpackageSettings + var exhaustiveCfg *config.ExhaustiveSettings if m.cfg != nil { govetCfg = &m.cfg.LintersSettings.Govet testpackageCfg = &m.cfg.LintersSettings.Testpackage + exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive } const megacheckName = "megacheck" lcs := []*linter.Config{ @@ -275,6 +277,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { linter.NewConfig(golinters.NewExportLoopRef()). WithPresets(linter.PresetBugs). WithURL("https://github.com/kyoh86/exportloopref"), + linter.NewConfig(golinters.NewExhaustive(exhaustiveCfg)). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/nishanths/exhaustive"), // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives linter.NewConfig(golinters.NewNoLintLint()). WithPresets(linter.PresetStyle). diff --git a/test/linters_test.go b/test/linters_test.go index b53fc14c82bc..ac57f4942ac0 100644 --- a/test/linters_test.go +++ b/test/linters_test.go @@ -17,9 +17,13 @@ import ( func runGoErrchk(c *exec.Cmd, files []string, t *testing.T) { output, err := c.CombinedOutput() - assert.Error(t, err) - _, ok := err.(*exec.ExitError) - assert.True(t, ok, err) + // The returned error will be nil if the test file does not have any issues + // and thus the linter exits with exit code 0. So perform the additional + // assertions only if the error is non-nil. + if err != nil { + _, ok := err.(*exec.ExitError) + assert.True(t, ok, err) + } // TODO: uncomment after deprecating go1.11 // assert.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode()) diff --git a/test/testdata/configs/exhaustive.yml b/test/testdata/configs/exhaustive.yml new file mode 100644 index 000000000000..03f2bf902863 --- /dev/null +++ b/test/testdata/configs/exhaustive.yml @@ -0,0 +1,3 @@ +linters-settings: + exhaustive: + default-signifies-exhaustive: true diff --git a/test/testdata/exhaustive.go b/test/testdata/exhaustive.go new file mode 100644 index 000000000000..171c6e817fc8 --- /dev/null +++ b/test/testdata/exhaustive.go @@ -0,0 +1,17 @@ +//args: -Eexhaustive +package testdata + +type Direction int + +const ( + North Direction = iota + East + South + West +) + +func processDirection(d Direction) { + switch d { // ERROR "missing cases in switch of type Direction: East, West" + case North, South: + } +} diff --git a/test/testdata/exhaustive_default.go b/test/testdata/exhaustive_default.go new file mode 100644 index 000000000000..36c1dee6ed03 --- /dev/null +++ b/test/testdata/exhaustive_default.go @@ -0,0 +1,19 @@ +//args: -Eexhaustive +//config_path: testdata/configs/exhaustive.yml +package testdata + +type Direction int + +const ( + North Direction = iota + East + South + West +) + +func processDirectionDefault(d Direction) { + switch d { + case North, South: + default: + } +}