From 2ea496f22b997490dd43bbb1536f28908b4389c6 Mon Sep 17 00:00:00 2001 From: Oleg Butuzov Date: Thu, 16 Sep 2021 12:47:56 +0300 Subject: [PATCH] new-linter: ireturn (checks for function return type) (#2219) --- .golangci.example.yml | 23 ++++++++++++++ go.mod | 1 + go.sum | 2 ++ pkg/config/linters_settings.go | 6 ++++ pkg/golinters/ireturn.go | 30 +++++++++++++++++++ pkg/lint/lintersdb/manager.go | 7 +++++ test/testdata/configs/ireturn.yml | 4 +++ .../configs/ireturn_stdlib_reject.yml | 4 +++ test/testdata/ireturn_allow.go | 13 ++++++++ test/testdata/ireturn_default.go | 12 ++++++++ test/testdata/ireturn_reject_stdlib.go | 28 +++++++++++++++++ 11 files changed, 130 insertions(+) create mode 100644 pkg/golinters/ireturn.go create mode 100644 test/testdata/configs/ireturn.yml create mode 100644 test/testdata/configs/ireturn_stdlib_reject.yml create mode 100644 test/testdata/ireturn_allow.go create mode 100644 test/testdata/ireturn_default.go create mode 100644 test/testdata/ireturn_reject_stdlib.go diff --git a/.golangci.example.yml b/.golangci.example.yml index ac0577c6ad7a..8c4c43ca764a 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -447,6 +447,29 @@ linters-settings: - pkg: knative.dev/serving/pkg/apis/(\w+)/(v[\w\d]+) alias: $1$2 + ireturn: + # ireturn allows using `allow` and `reject` settings at the same time. + # Both settings are lists of the keywords and regular expressions matched to interface or package names. + # keywords: + # - `empty` for `interface{}` + # - `error` for errors + # - `stdlib` for standard library + # - `anon` for anonymous interfaces + + # By default, it allows using errors, empty interfaces, anonymous interfaces, + # and interfaces provided by the standard library. + allow: + - anon + - error + - empty + - stdlib + # You can specify idiomatic endings for interface + - (or|er)$ + + # Reject patterns + reject: + - github.com\/user\/package\/v4\.Type + lll: # max line length, lines longer will be reported. Default is 120. # '\t' is counted as 1 character by default, and can be changed with the tab-width option diff --git a/go.mod b/go.mod index 90e8ba0f75e1..a80493dcf2b9 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde github.com/bkielbasa/cyclop v1.2.0 github.com/bombsimon/wsl/v3 v3.3.0 + github.com/butuzov/ireturn v0.1.0 github.com/charithe/durationcheck v0.0.8 github.com/daixiang0/gci v0.2.9 github.com/denis-tingajkin/go-header v0.4.2 diff --git a/go.sum b/go.sum index 06a205315c9c..9be25685c54d 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,8 @@ github.com/bkielbasa/cyclop v1.2.0 h1:7Jmnh0yL2DjKfw28p86YTd/B4lRGcNuu12sKE35sM7 github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= github.com/bombsimon/wsl/v3 v3.3.0 h1:Mka/+kRLoQJq7g2rggtgQsjuI/K5Efd87WX96EWFxjM= github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/butuzov/ireturn v0.1.0 h1:fMNgwuKMwsV9qtPNFgI7/NUOF3+CfbdLPGX6ZhDaMgA= +github.com/butuzov/ireturn v0.1.0/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index fd5f413181ca..201a57e2538d 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -110,6 +110,7 @@ type LintersSettings struct { Gosimple StaticCheckSettings Govet GovetSettings Ifshort IfshortSettings + Ireturn IreturnSettings ImportAs ImportAsSettings Lll LllSettings Makezero MakezeroSettings @@ -186,6 +187,11 @@ type ExhaustiveStructSettings struct { StructPatterns []string `mapstructure:"struct-patterns"` } +type IreturnSettings struct { + Allow []string `mapstructure:"allow"` + Reject []string `mapstructure:"reject"` +} + type ForbidigoSettings struct { Forbid []string `mapstructure:"forbid"` ExcludeGodocExamples bool `mapstructure:"exclude-godoc-examples"` diff --git a/pkg/golinters/ireturn.go b/pkg/golinters/ireturn.go new file mode 100644 index 000000000000..3b5df66dae81 --- /dev/null +++ b/pkg/golinters/ireturn.go @@ -0,0 +1,30 @@ +package golinters + +import ( + "strings" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + + "github.com/butuzov/ireturn/analyzer" + "golang.org/x/tools/go/analysis" +) + +func NewIreturn(settings *config.IreturnSettings) *goanalysis.Linter { + a := analyzer.NewAnalyzer() + + cfg := map[string]map[string]interface{}{} + if settings != nil { + cfg[a.Name] = map[string]interface{}{ + "allow": strings.Join(settings.Allow, ","), + "reject": strings.Join(settings.Reject, ","), + } + } + + 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 a69a6ec38261..93abb3bb1f30 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -110,6 +110,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { var reviveCfg *config.ReviveSettings var cyclopCfg *config.Cyclop var importAsCfg *config.ImportAsSettings + var ireturnCfg *config.IreturnSettings var goModDirectivesCfg *config.GoModDirectivesSettings var tagliatelleCfg *config.TagliatelleSettings var gosecCfg *config.GoSecSettings @@ -131,6 +132,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { reviveCfg = &m.cfg.LintersSettings.Revive cyclopCfg = &m.cfg.LintersSettings.Cyclop importAsCfg = &m.cfg.LintersSettings.ImportAs + ireturnCfg = &m.cfg.LintersSettings.Ireturn goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle gosecCfg = &m.cfg.LintersSettings.Gosec @@ -506,6 +508,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/Antonboom/errname"). WithSince("v1.42.0"), + linter.NewConfig(golinters.NewIreturn(ireturnCfg)). + WithSince("v1.43.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/butuzov/ireturn"), // nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives linter.NewConfig(golinters.NewNoLintLint()). diff --git a/test/testdata/configs/ireturn.yml b/test/testdata/configs/ireturn.yml new file mode 100644 index 000000000000..05459dd7af73 --- /dev/null +++ b/test/testdata/configs/ireturn.yml @@ -0,0 +1,4 @@ +linters-settings: + ireturn: + allow: + - IreturnAllowDoer diff --git a/test/testdata/configs/ireturn_stdlib_reject.yml b/test/testdata/configs/ireturn_stdlib_reject.yml new file mode 100644 index 000000000000..c5e687f7c3d4 --- /dev/null +++ b/test/testdata/configs/ireturn_stdlib_reject.yml @@ -0,0 +1,4 @@ +linters-settings: + ireturn: + reject: + - stdlib diff --git a/test/testdata/ireturn_allow.go b/test/testdata/ireturn_allow.go new file mode 100644 index 000000000000..d6b968a2e244 --- /dev/null +++ b/test/testdata/ireturn_allow.go @@ -0,0 +1,13 @@ +// args: -Eireturn +// config_path: testdata/configs/ireturn.yml +package testdata + +type ( + IreturnAllowDoer interface{ Do() } + ireturnAllowDoer struct{} +) + +func NewAllowDoer() IreturnAllowDoer { return new(ireturnAllowDoer) } +func (d *ireturnAllowDoer) Do() { /*...*/ } + +func NewerAllowDoer() *ireturnAllowDoer { return new(ireturnAllowDoer) } diff --git a/test/testdata/ireturn_default.go b/test/testdata/ireturn_default.go new file mode 100644 index 000000000000..0f9afd48f3a8 --- /dev/null +++ b/test/testdata/ireturn_default.go @@ -0,0 +1,12 @@ +// args: -Eireturn +package testdata + +type ( + IreturnDoer interface{ Do() } + ireturnDoer struct{} +) + +func New() IreturnDoer { return new(ireturnDoer) } // ERROR `New returns interface \(command-line-arguments.IreturnDoer\)` +func (d *ireturnDoer) Do() { /*...*/ } + +func Newer() *ireturnDoer { return new(ireturnDoer) } diff --git a/test/testdata/ireturn_reject_stdlib.go b/test/testdata/ireturn_reject_stdlib.go new file mode 100644 index 000000000000..d57d81639904 --- /dev/null +++ b/test/testdata/ireturn_reject_stdlib.go @@ -0,0 +1,28 @@ +// args: -Eireturn +// config_path: testdata/configs/ireturn_stdlib_reject.yml +package testdata + +import ( + "bytes" + "io" +) + +func NewWriter() io.Writer { // ERROR `NewWriter returns interface \(io.Writer\)` + var buf bytes.Buffer + return &buf +} + +func TestError() error { + return nil +} + +type Foo interface { + Foo() +} +type foo int + +func (f foo) Foo() {} + +func NewFoo() Foo { + return foo(1) +}