diff --git a/.golangci.example.yml b/.golangci.example.yml index 49e1d9a5..3dba1f05 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -201,6 +201,9 @@ linters-settings: - NOTE - OPTIMIZE # marks code that should be optimized before merging - HACK # marks hack-arounds that should be removed before merging + dogsled: + # checks assignments with too many blank identifiers; default is 2 + max-blank-identifiers: 2 linters: enable: diff --git a/.golangci.yml b/.golangci.yml index 670992d3..f70a1af9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -49,6 +49,7 @@ linters: - bodyclose - deadcode - depguard + # - dogsled - TODO: enable it when golangci.com will support it. - dupl - errcheck # - funlen - TODO: enable it when golangci.com will support it. diff --git a/README.md b/README.md index 5e5b7e53..09da2852 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ $ golangci-lint help linters Disabled by default linters: bodyclose: checks whether HTTP response body is closed successfully [fast: false, auto-fix: false] depguard: Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false] +dogsled: Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] dupl: Tool for code clone detection [fast: true, auto-fix: false] funlen: Tool for detection of long functions [fast: true, auto-fix: false] gochecknoglobals: Checks that no globals are present in Go code [fast: true, auto-fix: false] @@ -454,6 +455,7 @@ golangci-lint help linters - [misspell](https://github.com/client9/misspell) - Finds commonly misspelled English words in comments - [lll](https://github.com/walle/lll) - Reports long lines - [unparam](https://github.com/mvdan/unparam) - Reports unused function parameters +- [dogsled](https://github.com/alexkohler/dogsled) - Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) - [nakedret](https://github.com/alexkohler/nakedret) - Finds naked returns in functions greater than a specified function length - [prealloc](https://github.com/alexkohler/prealloc) - Finds slice declarations that could potentially be preallocated - [scopelint](https://github.com/kyoh86/scopelint) - Scopelint checks for unpinned variables in go programs @@ -783,6 +785,9 @@ linters-settings: - NOTE - OPTIMIZE # marks code that should be optimized before merging - HACK # marks hack-arounds that should be removed before merging + dogsled: + # checks assignments with too many blank identifiers; default is 2 + max-blank-identifiers: 2 linters: enable: @@ -916,6 +921,7 @@ linters: - bodyclose - deadcode - depguard + # - dogsled - TODO: enable it when golangci.com will support it. - dupl - errcheck # - funlen - TODO: enable it when golangci.com will support it. diff --git a/pkg/config/config.go b/pkg/config/config.go index 4edccfa3..4b7bf7fb 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -178,6 +178,7 @@ type LintersSettings struct { Errcheck ErrcheckSettings Gocritic GocriticSettings Godox GodoxSettings + Dogsled DogsledSettings } type GovetSettings struct { @@ -234,6 +235,10 @@ type GodoxSettings struct { Keywords []string } +type DogsledSettings struct { + MaxBlankIdentifiers int `mapstructure:"max-blank-identifiers"` +} + var defaultLintersSettings = LintersSettings{ Lll: LllSettings{ LineLength: 120, @@ -256,6 +261,9 @@ var defaultLintersSettings = LintersSettings{ Godox: GodoxSettings{ Keywords: []string{}, }, + Dogsled: DogsledSettings{ + MaxBlankIdentifiers: 2, + }, } type Linters struct { diff --git a/pkg/golinters/dogsled.go b/pkg/golinters/dogsled.go new file mode 100644 index 00000000..68237cc0 --- /dev/null +++ b/pkg/golinters/dogsled.go @@ -0,0 +1,79 @@ +package golinters + +import ( + "context" + "fmt" + "go/ast" + "go/token" + + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +type Dogsled struct{} + +func (Dogsled) Name() string { + return "dogsled" +} + +func (Dogsled) Desc() string { + return "Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())" +} + +func (d Dogsled) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { + + var res []result.Issue + for _, f := range lintCtx.ASTCache.GetAllValidFiles() { + v := returnsVisitor{ + maxBlanks: lintCtx.Settings().Dogsled.MaxBlankIdentifiers, + f: f.Fset, + } + ast.Walk(&v, f.F) + res = append(res, v.issues...) + } + + return res, nil +} + +type returnsVisitor struct { + f *token.FileSet + maxBlanks int + issues []result.Issue +} + +func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor { + funcDecl, ok := node.(*ast.FuncDecl) + if !ok { + return v + } + if funcDecl.Body == nil { + return v + } + + for _, expr := range funcDecl.Body.List { + assgnStmt, ok := expr.(*ast.AssignStmt) + if !ok { + continue + } + + numBlank := 0 + for _, left := range assgnStmt.Lhs { + ident, ok := left.(*ast.Ident) + if !ok { + continue + } + if ident.Name == "_" { + numBlank++ + } + } + + if numBlank > v.maxBlanks { + v.issues = append(v.issues, result.Issue{ + FromLinter: Dogsled{}.Name(), + Text: fmt.Sprintf("declaration has %v blank identifiers", numBlank), + Pos: v.f.Position(assgnStmt.Pos()), + }) + } + } + return v +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 5a2a5788..27862a7b 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -215,6 +215,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadDepsTypeInfo(). WithSSA(). WithURL("https://github.com/mvdan/unparam"), + linter.NewConfig(golinters.Dogsled{}). + WithPresets(linter.PresetStyle). + WithSpeed(10). + WithURL("https://github.com/alexkohler/dogsled"), linter.NewConfig(golinters.Nakedret{}). WithPresets(linter.PresetComplexity). WithSpeed(10). diff --git a/test/testdata/dogsled.go b/test/testdata/dogsled.go new file mode 100644 index 00000000..8604a2d7 --- /dev/null +++ b/test/testdata/dogsled.go @@ -0,0 +1,25 @@ +//args: -Edogsled +package testdata + +func Dogsled() { + _ = ret1() + _, _ = ret2() + _, _, _ = ret3() // ERROR "declaration has 3 blank identifiers" + _, _, _, _ = ret4() // ERROR "declaration has 4 blank identifiers" +} + +func ret1() (a int) { + return 1 +} + +func ret2() (a, b int) { + return 1, 2 +} + +func ret3() (a, b, c int) { + return 1, 2, 3 +} + +func ret4() (a, b, c, d int) { + return 1, 2, 3, 4 +} diff --git a/third_party/dogsled/LICENSE b/third_party/dogsled/LICENSE new file mode 100644 index 00000000..bc3ffbd4 --- /dev/null +++ b/third_party/dogsled/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Alex Kohler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.