diff --git a/.golangci.example.yml b/.golangci.example.yml index ae8a9a3f0c6d..d257b9afa14f 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -341,6 +341,9 @@ linters-settings: errorlint: # Report non-wrapping error creation using fmt.Errorf errorf: true + makezero: + # Allow only slices initialized with a length of zero. Default is false. + always: false # The custom section can be used to define linter plugins to be loaded at runtime. See README doc # for more info. diff --git a/go.mod b/go.mod index 54a695d55430..f67a8062dc1b 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( 4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5 github.com/OpenPeeDeeP/depguard v1.0.1 + github.com/ashanbrown/makezero v0.0.0-20201205152432-7b7cdbb3025a github.com/bombsimon/wsl/v3 v3.1.0 github.com/daixiang0/gci v0.2.4 github.com/denis-tingajkin/go-header v0.3.1 diff --git a/go.sum b/go.sum index 9ca26a739454..d3b9652b33c3 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,10 @@ github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/ashanbrown/makezero v0.0.0-20201122223944-6aa12ecc2e71 h1:I5vCM3EzMpsjllkzez2fjY/FXJNbtulB33LudiHuMNo= +github.com/ashanbrown/makezero v0.0.0-20201122223944-6aa12ecc2e71/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= +github.com/ashanbrown/makezero v0.0.0-20201205152432-7b7cdbb3025a h1:/U9tbJzDRof4fOR51vwzWdIBsIH6R2yU0KG1MBRM2Js= +github.com/ashanbrown/makezero v0.0.0-20201205152432-7b7cdbb3025a/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -529,6 +533,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/pkg/config/config.go b/pkg/config/config.go index 4ec378f7a687..30190065fcd6 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -266,6 +266,7 @@ type LintersSettings struct { Exhaustive ExhaustiveSettings Gofumpt GofumptSettings ErrorLint ErrorLintSettings + Makezero MakezeroSettings Custom map[string]CustomLinterSettings } @@ -386,6 +387,10 @@ type ErrorLintSettings struct { Errorf bool `mapstructure:"errorf"` } +type MakezeroSettings struct { + Always bool +} + var defaultLintersSettings = LintersSettings{ Lll: LllSettings{ LineLength: 120, diff --git a/pkg/golinters/makezero.go b/pkg/golinters/makezero.go new file mode 100644 index 000000000000..7b7ec09b9ec0 --- /dev/null +++ b/pkg/golinters/makezero.go @@ -0,0 +1,60 @@ +package golinters + +import ( + "sync" + + "github.com/ashanbrown/makezero/makezero" + "github.com/pkg/errors" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +const makezeroName = "makezero" + +func NewMakezero() *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: makezeroName, + Doc: goanalysis.TheOnlyanalyzerDoc, + } + return goanalysis.NewLinter( + makezeroName, + "Finds slice declarations with non-zero initial length", + []*analysis.Analyzer{analyzer}, + nil, + ).WithContextSetter(func(lintCtx *linter.Context) { + s := &lintCtx.Settings().Makezero + + analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { + var res []goanalysis.Issue + linter := makezero.NewLinter(s.Always) + for _, file := range pass.Files { + hints, err := linter.Run(pass.Fset, pass.TypesInfo, file) + if err != nil { + return nil, errors.Wrapf(err, "makezero linter failed on file %q", file.Name.String()) + } + for _, hint := range hints { + res = append(res, goanalysis.NewIssue(&result.Issue{ + Pos: hint.Position(), + Text: hint.Details(), + FromLinter: makezeroName, + }, pass)) + } + } + if len(res) == 0 { + return nil, nil + } + mu.Lock() + resIssues = append(resIssues, res...) + mu.Unlock() + return nil, nil + } + }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeSyntax | goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index a36ae2f57661..b07cd885c745 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -328,6 +328,9 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithLoadForGoAnalysis(). WithURL("https://github.com/kunwardeep/paralleltest"), + linter.NewConfig(golinters.NewMakezero()). + WithPresets(linter.PresetStyle, linter.PresetBugs). + WithURL("https://github.com/ashanbrown/makezero"), // 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/makezero.go b/test/testdata/makezero.go new file mode 100644 index 000000000000..ca32eda3ec3e --- /dev/null +++ b/test/testdata/makezero.go @@ -0,0 +1,12 @@ +//args: -Emakezero +package testdata + +func Makezero() []int { + x := make([]int, 5) + return append(x, 1) // ERROR "append to slice `x` with non-zero initialized length" +} + +func MakezeroNolint() []int { + x := make([]int, 5) + return append(x, 1) //nolint:makezero // ok that we're appending to an uninitialized slice +} diff --git a/test/testdata/makezero_always.go b/test/testdata/makezero_always.go new file mode 100644 index 000000000000..e6bdd87d743c --- /dev/null +++ b/test/testdata/makezero_always.go @@ -0,0 +1,13 @@ +//args: -Emakezero +//config: linters-settings.makezero.always=true +package testdata + +func MakezeroAlways() []int { + x := make([]int, 5) // ERROR "slice `x` does not have non-zero initial length" + return x +} + +func MakezeroAlwaysNolint() []int { + x := make([]int, 5) //nolint:makezero // ok that this is not initialized + return x +}