diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index 72b2d158ff9c..23cb852ec0fd 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -324,6 +324,11 @@ linters-settings: # Default: false report-no-exported: false + errgroupcheck: + # Check if any sync.errgroup.Group instance is missing a call to the Wait() func. + # Default: true + require-wait: true + errorlint: # Check whether fmt.Errorf uses the %w verb for formatting errors. # See the https://github.com/polyfloyd/go-errorlint for caveats. diff --git a/go.mod b/go.mod index f103663f94d5..5f41f029170f 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 github.com/OpenPeeDeeP/depguard/v2 v2.2.0 github.com/alecthomas/go-check-sumtype v0.1.4 + github.com/alexbagnolini/errgroupcheck v0.1.2 github.com/alexkohler/nakedret/v2 v2.0.4 github.com/alexkohler/prealloc v1.0.0 github.com/alingse/asasalint v0.0.11 diff --git a/go.sum b/go.sum index e76f2950ea43..846924b8fa4b 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexbagnolini/errgroupcheck v0.1.2 h1:cq9YqSkdVDsYDGAUEK5qerSH08BVaWB0XSPx6ZyfA2Q= +github.com/alexbagnolini/errgroupcheck v0.1.2/go.mod h1:lKmMzcO/EElqmb8q0tmSDmm+zMm5i/DdMWVjTzFMpzo= github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index 16c7f557e940..28d01e8c948d 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -312,6 +312,7 @@ "durationcheck", "errcheck", "errchkjson", + "errgroupcheck", "errname", "errorlint", "execinquery", @@ -865,6 +866,17 @@ } } }, + "errgroupcheck": { + "type": "object", + "additionalProperties": false, + "properties": { + "require-wait": { + "description": "Check if any sync.errgroup.Group instance is missing a call to the Wait() func.", + "type": "boolean", + "default": true + } + } + }, "errorlint": { "type": "object", "additionalProperties": false, diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 267efe2cf6e7..8a81a440da44 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -208,6 +208,7 @@ type LintersSettings struct { DupWord DupWordSettings Errcheck ErrcheckSettings ErrChkJSON ErrChkJSONSettings + ErrGroupCheck ErrGroupCheckSettings ErrorLint ErrorLintSettings Exhaustive ExhaustiveSettings Exhaustruct ExhaustructSettings @@ -382,6 +383,10 @@ type ErrChkJSONSettings struct { ReportNoExported bool `mapstructure:"report-no-exported"` } +type ErrGroupCheckSettings struct { + RequireWait bool `mapstructure:"require-wait"` +} + type ErrorLintSettings struct { Errorf bool `mapstructure:"errorf"` ErrorfMulti bool `mapstructure:"errorf-multi"` diff --git a/pkg/golinters/errgroupcheck/errgroupcheck.go b/pkg/golinters/errgroupcheck/errgroupcheck.go new file mode 100644 index 000000000000..b77406e3818e --- /dev/null +++ b/pkg/golinters/errgroupcheck/errgroupcheck.go @@ -0,0 +1,33 @@ +package errgroupcheck + +import ( + "github.com/alexbagnolini/errgroupcheck" + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goanalysis" + "golang.org/x/tools/go/analysis" +) + +func New(cfg *config.ErrGroupCheckSettings) *goanalysis.Linter { + var setts = errgroupcheck.DefaultSettings() + + if cfg != nil { + setts.RequireWait = cfg.RequireWait + } + + cfgMap := map[string]map[string]any{} + + analyzer := errgroupcheck.NewAnalyzer(setts) + + if cfg != nil { + cfgMap[analyzer.Name] = map[string]any{ + "require-wait": cfg.RequireWait, + } + } + + return goanalysis.NewLinter( + analyzer.Name, + analyzer.Doc, + []*analysis.Analyzer{analyzer}, + nil, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/golinters/errgroupcheck/errgroupcheck_test.go b/pkg/golinters/errgroupcheck/errgroupcheck_test.go new file mode 100644 index 000000000000..e9ef06fe82a6 --- /dev/null +++ b/pkg/golinters/errgroupcheck/errgroupcheck_test.go @@ -0,0 +1,11 @@ +package errgroupcheck + +import ( + "testing" + + "github.com/golangci/golangci-lint/test/testshared/integration" +) + +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} diff --git a/pkg/golinters/errgroupcheck/testdata/errgroupcheck_wait.go b/pkg/golinters/errgroupcheck/testdata/errgroupcheck_wait.go new file mode 100644 index 000000000000..67b720594fa8 --- /dev/null +++ b/pkg/golinters/errgroupcheck/testdata/errgroupcheck_wait.go @@ -0,0 +1,101 @@ +//golangcitest:args -Eerrgroupcheck +//golangcitest:config_path testdata/errgroupcheck_wait.yml +package testdata + +import ( + "context" + + "golang.org/x/sync/errgroup" +) + +func ErrgroupWithWait() { + eg := errgroup.Group{} + + eg.Go(func() error { + return nil + }) + + eg.Go(func() error { + return nil + }) + + _ = eg.Wait() +} + +func ErrgroupMissingWait() { + eg := errgroup.Group{} // want "errgroup 'eg' does not have Wait called" + + eg.Go(func() error { + return nil + }) + + eg.Go(func() error { + return nil + }) +} + +func ErrgroupContextWithWait() { + eg, _ := errgroup.WithContext(context.Background()) + + eg.Go(func() error { + return nil + }) + + eg.Go(func() error { + return nil + }) + + _ = eg.Wait() +} + +func ErrgroupContextMissingWait() { + eg, _ := errgroup.WithContext(context.Background()) // want "errgroup 'eg' does not have Wait called" + + eg.Go(func() error { + return nil + }) + + eg.Go(func() error { + return nil + }) +} + +func ErrgroupMultipleScopesWithWait() { + eg := errgroup.Group{} + + eg.Go(func() error { + return nil + }) + + eg.Go(func() error { + eg2 := errgroup.Group{} + + eg2.Go(func() error { + return nil + }) + + return eg2.Wait() + }) + + _ = eg.Wait() +} + +func ErrgroupMultipleScopesMissingWait() { + eg := errgroup.Group{} + + eg.Go(func() error { + return nil + }) + + eg.Go(func() error { + eg2 := errgroup.Group{} // want "errgroup 'eg2' does not have Wait called" + + eg2.Go(func() error { + return nil + }) + + return nil + }) + + _ = eg.Wait() +} diff --git a/pkg/golinters/errgroupcheck/testdata/errgroupcheck_wait.yml b/pkg/golinters/errgroupcheck/testdata/errgroupcheck_wait.yml new file mode 100644 index 000000000000..89679cb913a1 --- /dev/null +++ b/pkg/golinters/errgroupcheck/testdata/errgroupcheck_wait.yml @@ -0,0 +1,3 @@ +linters-settings: + errgroupcheck: + require-wait: true diff --git a/pkg/golinters/errgroupcheck/testdata/go.mod b/pkg/golinters/errgroupcheck/testdata/go.mod new file mode 100644 index 000000000000..191cc1ae4df4 --- /dev/null +++ b/pkg/golinters/errgroupcheck/testdata/go.mod @@ -0,0 +1,5 @@ +module wait + +go 1.19 + +require golang.org/x/sync v0.7.0 diff --git a/pkg/golinters/errgroupcheck/testdata/go.sum b/pkg/golinters/errgroupcheck/testdata/go.sum new file mode 100644 index 000000000000..e8ef4a360a14 --- /dev/null +++ b/pkg/golinters/errgroupcheck/testdata/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index 7b83a81530a7..62de9b7d5b48 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -21,6 +21,7 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/err113" "github.com/golangci/golangci-lint/pkg/golinters/errcheck" "github.com/golangci/golangci-lint/pkg/golinters/errchkjson" + "github.com/golangci/golangci-lint/pkg/golinters/errgroupcheck" "github.com/golangci/golangci-lint/pkg/golinters/errname" "github.com/golangci/golangci-lint/pkg/golinters/errorlint" "github.com/golangci/golangci-lint/pkg/golinters/execinquery" @@ -236,6 +237,12 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithLoadForGoAnalysis(). WithURL("https://github.com/breml/errchkjson"), + linter.NewConfig(errgroupcheck.New(&cfg.LintersSettings.ErrGroupCheck)). + WithSince("1.60.0"). + WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). + WithURL("https://github.com/alexbagnolini/errgroupcheck"), + linter.NewConfig(errname.New()). WithSince("v1.42.0"). WithPresets(linter.PresetStyle).