diff --git a/.golangci.reference.yml b/.golangci.reference.yml index f8c2ae148274..8e6390932ef8 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -1882,6 +1882,35 @@ linters-settings: # Default: false all: false + testifylint: + # Enable all checkers. + # Default: false + enable-all: true + # Enable specific checkers. + # https://github.com/Antonboom/testifylint#checkers + # Default: ["bool-compare", "compares", "empty", "error-is-as", "error-nil", "expected-actual", "float-compare", "len", "require-error", "suite-dont-use-pkg", "suite-extra-assert-call"] + enable: + - bool-compare + - compares + - empty + - error-is-as + - error-nil + - expected-actual + - float-compare + - len + - require-error + - suite-dont-use-pkg + - suite-extra-assert-call + - suite-thelper + expected-actual: + # Regexp for expected variable name. + # Default: (^(exp(ected)?|want(ed)?)([A-Z]\w*)?$)|(^(\w*[a-z])?(Exp(ected)?|Want(ed)?)$) + pattern: ^expected + suite-extra-assert-call: + # To require or remove extra Assert() call? + # Default: remove + mode: require + testpackage: # Regexp pattern to skip files. # Default: "(export|internal)_test\\.go" @@ -2242,6 +2271,7 @@ linters: - tagliatelle - tenv - testableexamples + - testifylint - testpackage - thelper - tparallel @@ -2356,6 +2386,7 @@ linters: - tagliatelle - tenv - testableexamples + - testifylint - testpackage - thelper - tparallel diff --git a/go.mod b/go.mod index 49266d662aa5..5df9161522c4 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/Abirdcfly/dupword v0.0.13 github.com/Antonboom/errname v0.1.12 github.com/Antonboom/nilnil v0.1.7 + github.com/Antonboom/testifylint v0.2.3 github.com/BurntSushi/toml v1.3.2 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 diff --git a/go.sum b/go.sum index a292f87374e1..edee8426da4b 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClD github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= +github.com/Antonboom/testifylint v0.2.3 h1:MFq9zyL+rIVpsvLX4vDPLojgN7qODzWsrnftNX2Qh60= +github.com/Antonboom/testifylint v0.2.3/go.mod h1:IYaXaOX9NbfAyO+Y04nfjGI8wDemC1rUyM/cYolz018= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 9c2b2ad37db7..d1ad89f79b34 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -218,6 +218,7 @@ type LintersSettings struct { Stylecheck StaticCheckSettings TagAlign TagAlignSettings Tagliatelle TagliatelleSettings + Testifylint TestifylintSettings Tenv TenvSettings Testpackage TestpackageSettings Thelper ThelperSettings @@ -737,6 +738,19 @@ type TagliatelleSettings struct { } } +type TestifylintSettings struct { + EnableAll bool `mapstructure:"enable-all"` + EnabledCheckers []string `mapstructure:"enable"` + + ExpectedActual struct { + ExpVarPattern string `mapstructure:"pattern"` + } `mapstructure:"expected-actual"` + + SuiteExtraAssertCall struct { + Mode string `mapstructure:"mode"` + } `mapstructure:"suite-extra-assert-call"` +} + type TestpackageSettings struct { SkipRegexp string `mapstructure:"skip-regexp"` AllowPackages []string `mapstructure:"allow-packages"` diff --git a/pkg/golinters/testifylint.go b/pkg/golinters/testifylint.go new file mode 100644 index 000000000000..83bae2868a5b --- /dev/null +++ b/pkg/golinters/testifylint.go @@ -0,0 +1,36 @@ +package golinters + +import ( + "github.com/Antonboom/testifylint/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewTestifylint(settings *config.TestifylintSettings) *goanalysis.Linter { + a := analyzer.New() + + cfg := make(map[string]map[string]any) + if settings != nil { + cfg[a.Name] = map[string]any{ + "enable-all": settings.EnableAll, + } + if len(settings.EnabledCheckers) > 0 { + cfg[a.Name]["enable"] = settings.EnabledCheckers + } + if p := settings.ExpectedActual.ExpVarPattern; p != "" { + cfg[a.Name]["expected-actual.pattern"] = p + } + if m := settings.SuiteExtraAssertCall.Mode; m != "" { + cfg[a.Name]["suite-extra-assert-call.mode"] = m + } + } + + 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 4de3a1116f58..59f6ef30c0c2 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -133,6 +133,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { tagalignCfg *config.TagAlignSettings tagliatelleCfg *config.TagliatelleSettings tenvCfg *config.TenvSettings + testifylintCfg *config.TestifylintSettings testpackageCfg *config.TestpackageSettings thelperCfg *config.ThelperSettings unparamCfg *config.UnparamSettings @@ -213,6 +214,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { tagalignCfg = &m.cfg.LintersSettings.TagAlign tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle tenvCfg = &m.cfg.LintersSettings.Tenv + testifylintCfg = &m.cfg.LintersSettings.Testifylint testpackageCfg = &m.cfg.LintersSettings.Testpackage thelperCfg = &m.cfg.LintersSettings.Thelper unparamCfg = &m.cfg.LintersSettings.Unparam @@ -788,6 +790,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetTest). WithURL("https://github.com/maratori/testableexamples"), + linter.NewConfig(golinters.NewTestifylint(testifylintCfg)). + WithSince("v1.55.0"). + WithPresets(linter.PresetTest, linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/Antonboom/testifylint"), + linter.NewConfig(golinters.NewTestpackage(testpackageCfg)). WithSince("v1.25.0"). WithPresets(linter.PresetStyle, linter.PresetTest). diff --git a/test/testdata/testifylint.go b/test/testdata/testifylint.go new file mode 100644 index 000000000000..dc6d96de735e --- /dev/null +++ b/test/testdata/testifylint.go @@ -0,0 +1,62 @@ +//golangcitest:args -Etestifylint +package testdata + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +func TestTestifylint(t *testing.T) { + var ( + predicate bool + resultInt int + resultFloat float64 + arr []string + err error + ) + + assert.Equal(t, predicate, true) // want "bool-compare: use assert\\.True" + assert.True(t, resultInt == 1) // want "compares: use assert\\.Equal" + assert.Equal(t, len(arr), 0) // want "empty: use assert\\.Empty" + assert.Error(t, err, io.EOF) // want "error-is-as: invalid usage of assert\\.Error, use assert\\.ErrorIs instead" + assert.Nil(t, err) // want "error-nil: use assert\\.NoError" + assert.Equal(t, resultInt, 42) // want "expected-actual: need to reverse actual and expected values" + assert.Equal(t, resultFloat, 42.42) // want "float-compare: use assert\\.InEpsilon \\(or InDelta\\)" + assert.Equal(t, len(arr), 10) // want "len: use assert\\.Len" + + assert.True(t, predicate) + assert.Equal(t, resultInt, 1) // want "expected-actual: need to reverse actual and expected values" + assert.Empty(t, arr) + assert.ErrorIs(t, err, io.EOF) // want "require-error: for error assertions use require" + assert.NoError(t, err) // want "require-error: for error assertions use require" + assert.Equal(t, 42, resultInt) + assert.InEpsilon(t, 42.42, resultFloat, 0.0001) + assert.Len(t, arr, 10) + + require.ErrorIs(t, err, io.EOF) + require.NoError(t, err) + + t.Run("formatted", func(t *testing.T) { + assert.Equal(t, predicate, true, "message") // want "bool-compare: use assert\\.True" + assert.Equal(t, predicate, true, "message %d", 42) // want "bool-compare: use assert\\.True" + assert.Equalf(t, predicate, true, "message") // want "bool-compare: use assert\\.Truef" + assert.Equalf(t, predicate, true, "message %d", 42) // want "bool-compare: use assert\\.Truef" + }) +} + +type SuiteExample struct { + suite.Suite +} + +func TestSuiteExample(t *testing.T) { + suite.Run(t, new(SuiteExample)) +} + +func (s *SuiteExample) TestAll() { + var b bool + s.Assert().True(b) // want "suite-extra-assert-call: need to simplify the assertion to s\\.True" +}