diff --git a/.golangci.example.yml b/.golangci.example.yml index d257b9af..82d622df 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -300,6 +300,17 @@ linters-settings: testpackage: # regexp pattern to skip files skip-regexp: (export|internal)_test\.go + thelper: + # The following configurations enable all checks. It can be omitted because all checks are enabled by default. + # You can enable only required checks deleting unnecessary checks. + test: + first: true + name: true + begin: true + benchmark: + first: true + name: true + begin: true unparam: # Inspect exported functions, default is false. Set to true if no external program/library imports your code. # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: diff --git a/go.mod b/go.mod index 683b4b9c..edd50543 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/jgautheron/goconst v0.0.0-20201117150253-ccae5bf973f3 github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3 + github.com/kulti/thelper v0.1.0 github.com/kunwardeep/paralleltest v1.0.2 github.com/kyoh86/exportloopref v0.1.8 github.com/maratori/testpackage v1.0.1 diff --git a/go.sum b/go.sum index 45f63714..a72c7c99 100644 --- a/go.sum +++ b/go.sum @@ -149,6 +149,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= @@ -168,8 +169,12 @@ github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.m github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.1.0 h1:E4c8Y1EQURbBEAHoXc/jBTK7Np14ArT8NPUiSFOl9yc= github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= +github.com/gostaticanalysis/analysisutil v0.4.1 h1:/7clKqrVfiVwiBQLM0Uke4KvXnO6JcCTS7HwF2D6wG8= +github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= github.com/gostaticanalysis/comment v1.3.0 h1:wTVgynbFu8/nz6SGgywA0TcyIoAVsYc7ai/Zp5xNGlw= github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI= +github.com/gostaticanalysis/comment v1.4.1 h1:xHopR5L2lRz6OsjH4R2HG5wRhW9ySl3FsHIvi5pcXwc= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -225,6 +230,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kulti/thelper v0.1.0 h1:ig1EW6yhDiRNN3dplbhdsW2gTvbxTz9i4q5Rr/tRfpk= +github.com/kulti/thelper v0.1.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U= github.com/kunwardeep/paralleltest v1.0.2 h1:/jJRv0TiqPoEy/Y8dQxCFJhD56uS/pnvtatgTZBHokU= github.com/kunwardeep/paralleltest v1.0.2/go.mod h1:ZPqNm1fVHPllh5LPVujzbVz1JN2GhLxSfY+oqUsvG30= github.com/kyoh86/exportloopref v0.1.8 h1:5Ry/at+eFdkX9Vsdw3qU4YkvGtzuVfzT4X7S77LoN/M= @@ -558,11 +565,13 @@ golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201011145850-ed2f50202694/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394 h1:O3VD5Fds21mB1WVRTbkiz/HTXESx6Jql5ucPZi69oiM= golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb h1:z5+u0pkAUPUWd3taoTialQ2JAMo4Wo1Z3L25U4ZV9r0= diff --git a/pkg/config/config.go b/pkg/config/config.go index 30190065..34f6ba65 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -267,6 +267,7 @@ type LintersSettings struct { Gofumpt GofumptSettings ErrorLint ErrorLintSettings Makezero MakezeroSettings + Thelper ThelperSettings Custom map[string]CustomLinterSettings } @@ -391,6 +392,19 @@ type MakezeroSettings struct { Always bool } +type ThelperSettings struct { + Test struct { + First bool `mapstructure:"first"` + Name bool `mapstructure:"name"` + Begin bool `mapstructure:"begin"` + } `mapstructure:"test"` + Benchmark struct { + First bool `mapstructure:"first"` + Name bool `mapstructure:"name"` + Begin bool `mapstructure:"begin"` + } `mapstructure:"benchmark"` +} + var defaultLintersSettings = LintersSettings{ Lll: LllSettings{ LineLength: 120, diff --git a/pkg/golinters/thelper.go b/pkg/golinters/thelper.go new file mode 100644 index 00000000..27e5ee36 --- /dev/null +++ b/pkg/golinters/thelper.go @@ -0,0 +1,51 @@ +package golinters + +import ( + "strings" + + "github.com/kulti/thelper/pkg/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewThelper(cfg *config.ThelperSettings) *goanalysis.Linter { + a := analyzer.NewAnalyzer() + + cfgMap := map[string]map[string]interface{}{} + if cfg != nil { + var opts []string + + if cfg.Test.Name { + opts = append(opts, "t_name") + } + if cfg.Test.Begin { + opts = append(opts, "t_begin") + } + if cfg.Test.First { + opts = append(opts, "t_first") + } + + if cfg.Benchmark.Name { + opts = append(opts, "b_name") + } + if cfg.Benchmark.Begin { + opts = append(opts, "b_begin") + } + if cfg.Benchmark.First { + opts = append(opts, "b_first") + } + + cfgMap[a.Name] = map[string]interface{}{ + "checks": strings.Join(opts, ","), + } + } + + return goanalysis.NewLinter( + "thelper", + "thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers", + []*analysis.Analyzer{a}, + cfgMap, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index b07cd885..f3b92a68 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -91,11 +91,13 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { var testpackageCfg *config.TestpackageSettings var exhaustiveCfg *config.ExhaustiveSettings var errorlintCfg *config.ErrorLintSettings + var thelperCfg *config.ThelperSettings if m.cfg != nil { govetCfg = &m.cfg.LintersSettings.Govet testpackageCfg = &m.cfg.LintersSettings.Testpackage exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive errorlintCfg = &m.cfg.LintersSettings.ErrorLint + thelperCfg = &m.cfg.LintersSettings.Thelper } const megacheckName = "megacheck" lcs := []*linter.Config{ @@ -313,6 +315,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithLoadForGoAnalysis(). WithURL("https://github.com/tomarrell/wrapcheck"), + linter.NewConfig(golinters.NewThelper(thelperCfg)). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/kulti/thelper"), linter.NewConfig(golinters.NewTparallel()). WithPresets(linter.PresetStyle). WithLoadForGoAnalysis(). diff --git a/test/testdata/thelper.go b/test/testdata/thelper.go new file mode 100644 index 00000000..43dfadce --- /dev/null +++ b/test/testdata/thelper.go @@ -0,0 +1,30 @@ +//args: -Ethelper +package testdata + +import "testing" + +func thelperWithHelperAfterAssignment(t *testing.T) { // ERROR "test helper function should start from t.Helper()" + _ = 0 + t.Helper() +} + +func thelperWithNotFirst(s string, t *testing.T, i int) { // ERROR "parameter \*testing.T should be the first" + t.Helper() +} + +func thelperWithIncorrectName(o *testing.T) { // ERROR "parameter \*testing.T should have name t" + o.Helper() +} + +func bhelperWithHelperAfterAssignment(b *testing.B) { // ERROR "test helper function should start from b.Helper()" + _ = 0 + b.Helper() +} + +func bhelperWithNotFirst(s string, b *testing.B, i int) { // ERROR "parameter \*testing.B should be the first" + b.Helper() +} + +func bhelperWithIncorrectName(o *testing.B) { // ERROR "parameter \*testing.B should have name b" + o.Helper() +}