Skip to content

Commit 7274280

Browse files
committed
Add nilnil linter
1 parent 813ba7d commit 7274280

File tree

7 files changed

+272
-36
lines changed

7 files changed

+272
-36
lines changed

.golangci.example.yml

+25-16
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,6 @@ linters-settings:
172172
# minimal code complexity to report, 30 by default (but we recommend 10-20)
173173
min-complexity: 10
174174

175-
nestif:
176-
# minimal complexity of if statements to report, 5 by default
177-
min-complexity: 4
178-
179175
goconst:
180176
# minimal length of string constant, 3 by default
181177
min-len: 3
@@ -474,6 +470,31 @@ linters-settings:
474470
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
475471
max-func-lines: 30
476472

473+
nestif:
474+
# minimal complexity of if statements to report, 5 by default
475+
min-complexity: 4
476+
477+
nilnil:
478+
# By default, nilnil checks all returned types below.
479+
checked-types:
480+
- ptr
481+
- func
482+
- iface
483+
- map
484+
- chan
485+
486+
nolintlint:
487+
# Enable to ensure that nolint directives are all used. Default is true.
488+
allow-unused: false
489+
# Disable to ensure that nolint directives don't have a leading space. Default is true.
490+
allow-leading-space: true
491+
# Exclude following linters from requiring an explanation. Default is [].
492+
allow-no-explanation: [ ]
493+
# Enable to require an explanation of nonzero length after each nolint directive. Default is false.
494+
require-explanation: true
495+
# Enable to require nolint directives to mention the specific linter being suppressed. Default is false.
496+
require-specific: true
497+
477498
prealloc:
478499
# XXX: we don't recommend using this linter before doing performance profiling.
479500
# For most programs usage of prealloc will be a premature optimization.
@@ -505,18 +526,6 @@ linters-settings:
505526
# include method names and field names (i.e., qualified names) in checks
506527
q: false
507528

508-
nolintlint:
509-
# Enable to ensure that nolint directives are all used. Default is true.
510-
allow-unused: false
511-
# Disable to ensure that nolint directives don't have a leading space. Default is true.
512-
allow-leading-space: true
513-
# Exclude following linters from requiring an explanation. Default is [].
514-
allow-no-explanation: []
515-
# Enable to require an explanation of nonzero length after each nolint directive. Default is false.
516-
require-explanation: true
517-
# Enable to require nolint directives to mention the specific linter being suppressed. Default is false.
518-
require-specific: true
519-
520529
rowserrcheck:
521530
packages:
522531
- github.com/jmoiron/sqlx

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.15
55
require (
66
4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a
77
github.com/Antonboom/errname v0.1.4
8+
github.com/Antonboom/nilnil v0.1.0
89
github.com/BurntSushi/toml v0.4.1
910
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24
1011
github.com/OpenPeeDeeP/depguard v1.0.1

go.sum

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/config/linters_settings.go

+5
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ type LintersSettings struct {
117117
Misspell MisspellSettings
118118
Nakedret NakedretSettings
119119
Nestif NestifSettings
120+
NilNil NilNilSettings
120121
NoLintLint NoLintLintSettings
121122
Prealloc PreallocSettings
122123
Predeclared PredeclaredSettings
@@ -354,6 +355,10 @@ type NestifSettings struct {
354355
MinComplexity int `mapstructure:"min-complexity"`
355356
}
356357

358+
type NilNilSettings struct {
359+
CheckedTypes []string `mapstructure:"checked-types"`
360+
}
361+
357362
type NoLintLintSettings struct {
358363
RequireExplanation bool `mapstructure:"require-explanation"`
359364
AllowLeadingSpace bool `mapstructure:"allow-leading-space"`

pkg/golinters/nilnil.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package golinters
2+
3+
import (
4+
"strings"
5+
6+
"github.com/Antonboom/nilnil/pkg/analyzer"
7+
"golang.org/x/tools/go/analysis"
8+
9+
"github.com/golangci/golangci-lint/pkg/config"
10+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
11+
)
12+
13+
func NewNilNil(cfg *config.NilNilSettings) *goanalysis.Linter {
14+
a := analyzer.New()
15+
16+
cfgMap := make(map[string]map[string]interface{})
17+
if cfg != nil && len(cfg.CheckedTypes) != 0 {
18+
cfgMap[a.Name] = map[string]interface{}{
19+
"checked-types": strings.Join(cfg.CheckedTypes, ","),
20+
}
21+
}
22+
23+
return goanalysis.NewLinter(
24+
a.Name,
25+
a.Doc,
26+
[]*analysis.Analyzer{a},
27+
cfgMap,
28+
).
29+
WithLoadMode(goanalysis.LoadModeTypesInfo)
30+
}

pkg/lint/lintersdb/manager.go

+27-20
Original file line numberDiff line numberDiff line change
@@ -99,44 +99,46 @@ func enableLinterConfigs(lcs []*linter.Config, isEnabled func(lc *linter.Config)
9999

100100
//nolint:funlen
101101
func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
102-
var govetCfg *config.GovetSettings
103-
var testpackageCfg *config.TestpackageSettings
102+
var cyclopCfg *config.Cyclop
103+
var errorlintCfg *config.ErrorLintSettings
104104
var exhaustiveCfg *config.ExhaustiveSettings
105105
var exhaustiveStructCfg *config.ExhaustiveStructSettings
106-
var errorlintCfg *config.ErrorLintSettings
107-
var thelperCfg *config.ThelperSettings
108-
var predeclaredCfg *config.PredeclaredSettings
109-
var ifshortCfg *config.IfshortSettings
110-
var reviveCfg *config.ReviveSettings
111-
var cyclopCfg *config.Cyclop
112-
var importAsCfg *config.ImportAsSettings
113106
var goModDirectivesCfg *config.GoModDirectivesSettings
114-
var tagliatelleCfg *config.TagliatelleSettings
115107
var gosecCfg *config.GoSecSettings
116108
var gosimpleCfg *config.StaticCheckSettings
109+
var govetCfg *config.GovetSettings
110+
var ifshortCfg *config.IfshortSettings
111+
var importAsCfg *config.ImportAsSettings
112+
var nilNilCfg *config.NilNilSettings
113+
var predeclaredCfg *config.PredeclaredSettings
114+
var reviveCfg *config.ReviveSettings
117115
var staticcheckCfg *config.StaticCheckSettings
118116
var stylecheckCfg *config.StaticCheckSettings
117+
var tagliatelleCfg *config.TagliatelleSettings
118+
var testpackageCfg *config.TestpackageSettings
119+
var thelperCfg *config.ThelperSettings
119120
var unusedCfg *config.StaticCheckSettings
120121
var wrapcheckCfg *config.WrapcheckSettings
121122

122123
if m.cfg != nil {
123-
govetCfg = &m.cfg.LintersSettings.Govet
124-
testpackageCfg = &m.cfg.LintersSettings.Testpackage
124+
cyclopCfg = &m.cfg.LintersSettings.Cyclop
125+
errorlintCfg = &m.cfg.LintersSettings.ErrorLint
125126
exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive
126127
exhaustiveStructCfg = &m.cfg.LintersSettings.ExhaustiveStruct
127-
errorlintCfg = &m.cfg.LintersSettings.ErrorLint
128-
thelperCfg = &m.cfg.LintersSettings.Thelper
129-
predeclaredCfg = &m.cfg.LintersSettings.Predeclared
130-
ifshortCfg = &m.cfg.LintersSettings.Ifshort
131-
reviveCfg = &m.cfg.LintersSettings.Revive
132-
cyclopCfg = &m.cfg.LintersSettings.Cyclop
133-
importAsCfg = &m.cfg.LintersSettings.ImportAs
134128
goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives
135-
tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle
136129
gosecCfg = &m.cfg.LintersSettings.Gosec
137130
gosimpleCfg = &m.cfg.LintersSettings.Gosimple
131+
govetCfg = &m.cfg.LintersSettings.Govet
132+
ifshortCfg = &m.cfg.LintersSettings.Ifshort
133+
importAsCfg = &m.cfg.LintersSettings.ImportAs
134+
nilNilCfg = &m.cfg.LintersSettings.NilNil
135+
predeclaredCfg = &m.cfg.LintersSettings.Predeclared
136+
reviveCfg = &m.cfg.LintersSettings.Revive
138137
staticcheckCfg = &m.cfg.LintersSettings.Staticcheck
139138
stylecheckCfg = &m.cfg.LintersSettings.Stylecheck
139+
tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle
140+
testpackageCfg = &m.cfg.LintersSettings.Testpackage
141+
thelperCfg = &m.cfg.LintersSettings.Thelper
140142
unusedCfg = &m.cfg.LintersSettings.Unused
141143
wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck
142144
}
@@ -506,6 +508,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
506508
WithLoadForGoAnalysis().
507509
WithURL("https://github.com/Antonboom/errname").
508510
WithSince("v1.42.0"),
511+
linter.NewConfig(golinters.NewNilNil(nilNilCfg)).
512+
WithPresets(linter.PresetStyle).
513+
WithLoadForGoAnalysis().
514+
WithURL("https://github.com/Antonboom/nilnil").
515+
WithSince("v1.43.0"),
509516

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

test/testdata/nilnil.go

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
//args: -Enilnil
2+
package testdata
3+
4+
import (
5+
"io"
6+
"unsafe"
7+
)
8+
9+
type User struct{}
10+
11+
func primitivePtr() (*int, error) {
12+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
13+
}
14+
15+
func structPtr() (*User, error) {
16+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
17+
}
18+
19+
func emptyStructPtr() (*struct{}, error) {
20+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
21+
}
22+
23+
func anonymousStructPtr() (*struct{ ID string }, error) {
24+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
25+
}
26+
27+
func chBi() (chan int, error) {
28+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
29+
}
30+
31+
func chIn() (chan<- int, error) {
32+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
33+
}
34+
35+
func chOut() (<-chan int, error) {
36+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
37+
}
38+
39+
func fun() (func(), error) {
40+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
41+
}
42+
43+
func funWithArgsAndResults() (func(a, b, c int) (int, int), error) {
44+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
45+
}
46+
47+
func iface() (interface{}, error) {
48+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
49+
}
50+
51+
func m1() (map[int]int, error) {
52+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
53+
}
54+
55+
func m2() (map[int]*User, error) {
56+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
57+
}
58+
59+
type Storage struct{}
60+
61+
func (s *Storage) GetUser() (*User, error) {
62+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
63+
}
64+
65+
func ifReturn() (*User, error) {
66+
var s Storage
67+
if _, err := s.GetUser(); err != nil {
68+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
69+
}
70+
return new(User), nil
71+
}
72+
73+
func forReturn() (*User, error) {
74+
for {
75+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
76+
}
77+
}
78+
79+
func multipleReturn() (*User, error) {
80+
var s Storage
81+
82+
if _, err := s.GetUser(); err != nil {
83+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
84+
}
85+
86+
if _, err := s.GetUser(); err != nil {
87+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
88+
}
89+
90+
if _, err := s.GetUser(); err != nil {
91+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
92+
}
93+
94+
return new(User), nil
95+
}
96+
97+
func nested() {
98+
_ = func() (*User, error) {
99+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
100+
}
101+
102+
_, _ = func() (*User, error) {
103+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
104+
}()
105+
}
106+
107+
func deeplyNested() {
108+
_ = func() {
109+
_ = func() int {
110+
_ = func() {
111+
_ = func() (*User, error) {
112+
_ = func() {}
113+
_ = func() int { return 0 }
114+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
115+
}
116+
}
117+
return 0
118+
}
119+
}
120+
}
121+
122+
type (
123+
StructPtrType *User
124+
PrimitivePtrType *int
125+
ChannelType chan int
126+
FuncType func(int) int
127+
Checker interface{ Check() }
128+
)
129+
130+
func structPtrType() (StructPtrType, error) {
131+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
132+
}
133+
134+
func primitivePtrType() (PrimitivePtrType, error) {
135+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
136+
}
137+
138+
func channelType() (ChannelType, error) {
139+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
140+
}
141+
142+
func funcType() (FuncType, error) {
143+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
144+
}
145+
146+
func ifaceType() (Checker, error) {
147+
return nil, nil // ERROR "return both the `nil` error and invalid value: use a sentinel error instead"
148+
}
149+
150+
func withoutArgs() {}
151+
func withoutError1() *User { return nil }
152+
func withoutError2() (*User, *User) { return nil, nil }
153+
func withoutError3() (*User, *User, *User) { return nil, nil, nil }
154+
func withoutError4() (*User, *User, *User, *User) { return nil, nil, nil, nil }
155+
156+
// Unsupported.
157+
158+
func invalidOrder() (error, *User) { return nil, nil }
159+
func withError3rd() (*User, bool, error) { return nil, false, nil }
160+
func withError4th() (*User, *User, *User, error) { return nil, nil, nil, nil }
161+
func unsafePtr() (unsafe.Pointer, error) { return nil, nil }
162+
func uintPtr() (uintptr, error) { return 0, nil }
163+
func slice() ([]int, error) { return nil, nil }
164+
func ifaceExtPkg() (io.Closer, error) { return nil, nil }
165+
166+
func implicitNil1() (*User, error) {
167+
err := (error)(nil)
168+
return nil, err
169+
}
170+
171+
func implicitNil2() (*User, error) {
172+
err := io.EOF
173+
err = nil
174+
return nil, err
175+
}
176+
177+
func implicitNil3() (*User, error) {
178+
return nil, wrap(nil)
179+
}
180+
func wrap(err error) error { return err }

0 commit comments

Comments
 (0)