Skip to content

Commit cf32a7b

Browse files
authored
Add makezero linter (#1520)
makezero ensures that objects recognized as slices are initialized with length 0. By default, this is only required when we find a subsequent append to the object, but can also be enabled at all times as a way of discouraging the use of integer variables (i.e. "i") to index slices.
1 parent f7a0c3c commit cf32a7b

File tree

8 files changed

+102
-0
lines changed

8 files changed

+102
-0
lines changed

.golangci.example.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,9 @@ linters-settings:
341341
errorlint:
342342
# Report non-wrapping error creation using fmt.Errorf
343343
errorf: true
344+
makezero:
345+
# Allow only slices initialized with a length of zero. Default is false.
346+
always: false
344347

345348
# The custom section can be used to define linter plugins to be loaded at runtime. See README doc
346349
# for more info.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a
77
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5
88
github.com/OpenPeeDeeP/depguard v1.0.1
9+
github.com/ashanbrown/makezero v0.0.0-20201205152432-7b7cdbb3025a
910
github.com/bombsimon/wsl/v3 v3.1.0
1011
github.com/daixiang0/gci v0.2.4
1112
github.com/denis-tingajkin/go-header v0.3.1

go.sum

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/config/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ type LintersSettings struct {
266266
Exhaustive ExhaustiveSettings
267267
Gofumpt GofumptSettings
268268
ErrorLint ErrorLintSettings
269+
Makezero MakezeroSettings
269270

270271
Custom map[string]CustomLinterSettings
271272
}
@@ -386,6 +387,10 @@ type ErrorLintSettings struct {
386387
Errorf bool `mapstructure:"errorf"`
387388
}
388389

390+
type MakezeroSettings struct {
391+
Always bool
392+
}
393+
389394
var defaultLintersSettings = LintersSettings{
390395
Lll: LllSettings{
391396
LineLength: 120,

pkg/golinters/makezero.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package golinters
2+
3+
import (
4+
"sync"
5+
6+
"github.com/ashanbrown/makezero/makezero"
7+
"github.com/pkg/errors"
8+
"golang.org/x/tools/go/analysis"
9+
10+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
11+
"github.com/golangci/golangci-lint/pkg/lint/linter"
12+
"github.com/golangci/golangci-lint/pkg/result"
13+
)
14+
15+
const makezeroName = "makezero"
16+
17+
func NewMakezero() *goanalysis.Linter {
18+
var mu sync.Mutex
19+
var resIssues []goanalysis.Issue
20+
21+
analyzer := &analysis.Analyzer{
22+
Name: makezeroName,
23+
Doc: goanalysis.TheOnlyanalyzerDoc,
24+
}
25+
return goanalysis.NewLinter(
26+
makezeroName,
27+
"Finds slice declarations with non-zero initial length",
28+
[]*analysis.Analyzer{analyzer},
29+
nil,
30+
).WithContextSetter(func(lintCtx *linter.Context) {
31+
s := &lintCtx.Settings().Makezero
32+
33+
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
34+
var res []goanalysis.Issue
35+
linter := makezero.NewLinter(s.Always)
36+
for _, file := range pass.Files {
37+
hints, err := linter.Run(pass.Fset, pass.TypesInfo, file)
38+
if err != nil {
39+
return nil, errors.Wrapf(err, "makezero linter failed on file %q", file.Name.String())
40+
}
41+
for _, hint := range hints {
42+
res = append(res, goanalysis.NewIssue(&result.Issue{
43+
Pos: hint.Position(),
44+
Text: hint.Details(),
45+
FromLinter: makezeroName,
46+
}, pass))
47+
}
48+
}
49+
if len(res) == 0 {
50+
return nil, nil
51+
}
52+
mu.Lock()
53+
resIssues = append(resIssues, res...)
54+
mu.Unlock()
55+
return nil, nil
56+
}
57+
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
58+
return resIssues
59+
}).WithLoadMode(goanalysis.LoadModeSyntax | goanalysis.LoadModeTypesInfo)
60+
}

pkg/lint/lintersdb/manager.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,9 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
328328
WithPresets(linter.PresetStyle).
329329
WithLoadForGoAnalysis().
330330
WithURL("https://github.com/kunwardeep/paralleltest"),
331+
linter.NewConfig(golinters.NewMakezero()).
332+
WithPresets(linter.PresetStyle, linter.PresetBugs).
333+
WithURL("https://github.com/ashanbrown/makezero"),
331334

332335
// nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives
333336
linter.NewConfig(golinters.NewNoLintLint()).

test/testdata/makezero.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//args: -Emakezero
2+
package testdata
3+
4+
func Makezero() []int {
5+
x := make([]int, 5)
6+
return append(x, 1) // ERROR "append to slice `x` with non-zero initialized length"
7+
}
8+
9+
func MakezeroNolint() []int {
10+
x := make([]int, 5)
11+
return append(x, 1) //nolint:makezero // ok that we're appending to an uninitialized slice
12+
}

test/testdata/makezero_always.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//args: -Emakezero
2+
//config: linters-settings.makezero.always=true
3+
package testdata
4+
5+
func MakezeroAlways() []int {
6+
x := make([]int, 5) // ERROR "slice `x` does not have non-zero initial length"
7+
return x
8+
}
9+
10+
func MakezeroAlwaysNolint() []int {
11+
x := make([]int, 5) //nolint:makezero // ok that this is not initialized
12+
return x
13+
}

0 commit comments

Comments
 (0)