diff --git a/.golangci.reference.yml b/.golangci.reference.yml index f7950919689e..37c134271b26 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -1223,6 +1223,14 @@ linters-settings: # The position of the argument to check. arg-pos: 1 + nakedefer: + # List of regular expressions to exclude function names from check. + # Default: [] + exclude: + - 'os\.(Create|WriteFile|Chmod)' + - 'fmt\.Print.*' + - 'io\.Close' + nakedret: # Make an issue if func has more lines of code than this setting, and it has naked returns. # Default: 30 diff --git a/go.mod b/go.mod index 3bae316d165a..ff6de4a8a9e0 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 + github.com/GaijinEntertainment/go-nakedefer v1.4.0 github.com/OpenPeeDeeP/depguard v1.1.1 github.com/alexkohler/prealloc v1.0.0 github.com/alingse/asasalint v0.0.11 diff --git a/go.sum b/go.sum index 9096ff5693d5..b8ab7b24bf0b 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rW github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= +github.com/GaijinEntertainment/go-nakedefer v1.4.0 h1:Q/PUw3NGFT2BFVo6nUs0B0alGponVH6pBMx5xOBhfdc= +github.com/GaijinEntertainment/go-nakedefer v1.4.0/go.mod h1:LzRakH8F24yI23ycCqlISvWDM0Q2VV6OcWtfUvbIiRs= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index fb805f020179..40ffb63a4fa3 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -180,6 +180,7 @@ type LintersSettings struct { Maligned MalignedSettings Misspell MisspellSettings MustTag MustTagSettings + Nakedefer NakedeferSettings Nakedret NakedretSettings Nestif NestifSettings NilNil NilNilSettings @@ -548,6 +549,10 @@ type MustTagSettings struct { } `mapstructure:"functions"` } +type NakedeferSettings struct { + Exclude []string `mapstructure:"exclude"` +} + type NakedretSettings struct { MaxFuncLines int `mapstructure:"max-func-lines"` } diff --git a/pkg/golinters/nakedefer.go b/pkg/golinters/nakedefer.go new file mode 100644 index 000000000000..d0abd86257df --- /dev/null +++ b/pkg/golinters/nakedefer.go @@ -0,0 +1,29 @@ +package golinters + +import ( + "github.com/GaijinEntertainment/go-nakedefer/pkg/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewNakedefer(settings *config.NakedeferSettings) *goanalysis.Linter { + var exclude []string + + if settings != nil { + exclude = settings.Exclude + } + + a, err := analyzer.NewAnalyzer(exclude) + if err != nil { + linterLogger.Fatalf("nakedefer 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 6f406f7d26e3..3d0701c436df 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -148,6 +148,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { malignedCfg *config.MalignedSettings misspellCfg *config.MisspellSettings musttagCfg *config.MustTagSettings + nakedeferCfg *config.NakedeferSettings nakedretCfg *config.NakedretSettings nestifCfg *config.NestifSettings nilNilCfg *config.NilNilSettings @@ -226,6 +227,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { malignedCfg = &m.cfg.LintersSettings.Maligned misspellCfg = &m.cfg.LintersSettings.Misspell musttagCfg = &m.cfg.LintersSettings.MustTag + nakedeferCfg = &m.cfg.LintersSettings.Nakedefer nakedretCfg = &m.cfg.LintersSettings.Nakedret nestifCfg = &m.cfg.LintersSettings.Nestif nilNilCfg = &m.cfg.LintersSettings.NilNil @@ -645,6 +647,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle, linter.PresetBugs). WithURL("https://github.com/junk1tm/musttag"), + linter.NewConfig(golinters.NewNakedefer(nakedeferCfg)). + WithSince("v1.52.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/GaijinEntertainment/go-nakedefer"), + linter.NewConfig(golinters.NewNakedret(nakedretCfg)). WithSince("v1.19.0"). WithPresets(linter.PresetStyle). diff --git a/test/testdata/configs/nakedefer.yml b/test/testdata/configs/nakedefer.yml new file mode 100644 index 000000000000..3d61c90dee41 --- /dev/null +++ b/test/testdata/configs/nakedefer.yml @@ -0,0 +1,7 @@ +linters-settings: + nakedefer: + exclude: + - os\.(Create|WriteFile|Chmod) + - fmt\.Print.* + - io\.Close + - ignoreFunc diff --git a/test/testdata/nakedefer.go b/test/testdata/nakedefer.go new file mode 100644 index 000000000000..c77fc1c059d8 --- /dev/null +++ b/test/testdata/nakedefer.go @@ -0,0 +1,98 @@ +//golangcitest:args -Enakedefer +//golangcitest:config_path testdata/configs/nakedefer.yml +package testdata + +import ( + "bytes" + "compress/zlib" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" +) + +func funcNotReturnAnyType() { +} + +func funcReturnErr() error { + return errors.New("some error") +} + +func funcReturnFuncAndErr() (func(), error) { + return func() { + }, nil +} + +func ignoreFunc() error { + return errors.New("some error") +} + +func testCaseValid1() { + defer funcNotReturnAnyType() // ignore + + defer func() { // ignore + funcNotReturnAnyType() + }() + + defer func() { // ignore + _ = funcReturnErr() + }() +} + +func testCaseInvalid1() { + defer funcReturnErr() // want "deferred call should not return anything" + + defer funcReturnFuncAndErr() // want "deferred call should not return anything" + + defer func() error { // want "deferred call should not return anything" + return nil + }() + + defer func() func() { // want "deferred call should not return anything" + return func() {} + }() +} + +func testCase1() { + defer fmt.Errorf("some text") // want "deferred call should not return anything" + + r := new(bytes.Buffer) + defer io.LimitReader(r, 1) // want "deferred call should not return anything" + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("DONE")) + })) + defer srv.Close() // ignore + defer srv.CloseClientConnections() // ignore + defer srv.Certificate() // want "deferred call should not return anything" +} + +func testCaseExclude1() { + // exclude ignoreFunc + defer ignoreFunc() // ignore +} + +func testCaseExclude2() { + // exclude os\.(Create|WriteFile|Chmod) + defer os.Create("file_test1") // ignore + defer os.WriteFile("file_test2", []byte("data"), os.ModeAppend) // ignore + defer os.Chmod("file_test3", os.ModeAppend) // ignore + defer os.FindProcess(100500) // want "deferred call should not return anything" +} + +func testCaseExclude3() { + // exclude fmt\.Print.* + defer fmt.Println("e1") // ignore + defer fmt.Print("e1") // ignore + defer fmt.Printf("e1") // ignore + defer fmt.Sprintf("some text") // want "deferred call should not return anything" +} + +func testCaseExclude4() { + // exclude io\.Close + rc, _ := zlib.NewReader(bytes.NewReader([]byte("111"))) + defer rc.Close() // ignore +}