Skip to content

Commit 37b21b7

Browse files
AntonboomSeigeC
authored andcommitted
Add nilnil linter (golangci#2236)
1 parent 90314ed commit 37b21b7

File tree

7 files changed

+274
-38
lines changed

7 files changed

+274
-38
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
@@ -502,6 +498,31 @@ linters-settings:
502498
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
503499
max-func-lines: 30
504500

501+
nestif:
502+
# minimal complexity of if statements to report, 5 by default
503+
min-complexity: 4
504+
505+
nilnil:
506+
# By default, nilnil checks all returned types below.
507+
checked-types:
508+
- ptr
509+
- func
510+
- iface
511+
- map
512+
- chan
513+
514+
nolintlint:
515+
# Enable to ensure that nolint directives are all used. Default is true.
516+
allow-unused: false
517+
# Disable to ensure that nolint directives don't have a leading space. Default is true.
518+
allow-leading-space: true
519+
# Exclude following linters from requiring an explanation. Default is [].
520+
allow-no-explanation: [ ]
521+
# Enable to require an explanation of nonzero length after each nolint directive. Default is false.
522+
require-explanation: true
523+
# Enable to require nolint directives to mention the specific linter being suppressed. Default is false.
524+
require-specific: true
525+
505526
prealloc:
506527
# XXX: we don't recommend using this linter before doing performance profiling.
507528
# For most programs usage of prealloc will be a premature optimization.
@@ -533,18 +554,6 @@ linters-settings:
533554
# include method names and field names (i.e., qualified names) in checks
534555
q: false
535556

536-
nolintlint:
537-
# Enable to ensure that nolint directives are all used. Default is true.
538-
allow-unused: false
539-
# Disable to ensure that nolint directives don't have a leading space. Default is true.
540-
allow-leading-space: true
541-
# Exclude following linters from requiring an explanation. Default is [].
542-
allow-no-explanation: []
543-
# Enable to require an explanation of nonzero length after each nolint directive. Default is false.
544-
require-explanation: true
545-
# Enable to require nolint directives to mention the specific linter being suppressed. Default is false.
546-
require-specific: true
547-
548557
rowserrcheck:
549558
packages:
550559
- 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
@@ -118,6 +118,7 @@ type LintersSettings struct {
118118
Misspell MisspellSettings
119119
Nakedret NakedretSettings
120120
Nestif NestifSettings
121+
NilNil NilNilSettings
121122
NoLintLint NoLintLintSettings
122123
Prealloc PreallocSettings
123124
Predeclared PredeclaredSettings
@@ -360,6 +361,10 @@ type NestifSettings struct {
360361
MinComplexity int `mapstructure:"min-complexity"`
361362
}
362363

364+
type NilNilSettings struct {
365+
CheckedTypes []string `mapstructure:"checked-types"`
366+
}
367+
363368
type NoLintLintSettings struct {
364369
RequireExplanation bool `mapstructure:"require-explanation"`
365370
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

+29-22
Original file line numberDiff line numberDiff line change
@@ -99,46 +99,48 @@ 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
113-
var ireturnCfg *config.IreturnSettings
114106
var goModDirectivesCfg *config.GoModDirectivesSettings
115-
var tagliatelleCfg *config.TagliatelleSettings
116107
var gosecCfg *config.GoSecSettings
117108
var gosimpleCfg *config.StaticCheckSettings
109+
var govetCfg *config.GovetSettings
110+
var ifshortCfg *config.IfshortSettings
111+
var importAsCfg *config.ImportAsSettings
112+
var ireturnCfg *config.IreturnSettings
113+
var nilNilCfg *config.NilNilSettings
114+
var predeclaredCfg *config.PredeclaredSettings
115+
var reviveCfg *config.ReviveSettings
118116
var staticcheckCfg *config.StaticCheckSettings
119117
var stylecheckCfg *config.StaticCheckSettings
118+
var tagliatelleCfg *config.TagliatelleSettings
119+
var testpackageCfg *config.TestpackageSettings
120+
var thelperCfg *config.ThelperSettings
120121
var unusedCfg *config.StaticCheckSettings
121122
var wrapcheckCfg *config.WrapcheckSettings
122123

123124
if m.cfg != nil {
124-
govetCfg = &m.cfg.LintersSettings.Govet
125-
testpackageCfg = &m.cfg.LintersSettings.Testpackage
125+
cyclopCfg = &m.cfg.LintersSettings.Cyclop
126+
errorlintCfg = &m.cfg.LintersSettings.ErrorLint
126127
exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive
127128
exhaustiveStructCfg = &m.cfg.LintersSettings.ExhaustiveStruct
128-
errorlintCfg = &m.cfg.LintersSettings.ErrorLint
129-
thelperCfg = &m.cfg.LintersSettings.Thelper
130-
predeclaredCfg = &m.cfg.LintersSettings.Predeclared
131-
ifshortCfg = &m.cfg.LintersSettings.Ifshort
132-
reviveCfg = &m.cfg.LintersSettings.Revive
133-
cyclopCfg = &m.cfg.LintersSettings.Cyclop
134-
importAsCfg = &m.cfg.LintersSettings.ImportAs
135-
ireturnCfg = &m.cfg.LintersSettings.Ireturn
136129
goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives
137-
tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle
138130
gosecCfg = &m.cfg.LintersSettings.Gosec
139131
gosimpleCfg = &m.cfg.LintersSettings.Gosimple
132+
govetCfg = &m.cfg.LintersSettings.Govet
133+
ifshortCfg = &m.cfg.LintersSettings.Ifshort
134+
importAsCfg = &m.cfg.LintersSettings.ImportAs
135+
ireturnCfg = &m.cfg.LintersSettings.Ireturn
136+
nilNilCfg = &m.cfg.LintersSettings.NilNil
137+
predeclaredCfg = &m.cfg.LintersSettings.Predeclared
138+
reviveCfg = &m.cfg.LintersSettings.Revive
140139
staticcheckCfg = &m.cfg.LintersSettings.Staticcheck
141140
stylecheckCfg = &m.cfg.LintersSettings.Stylecheck
141+
tagliatelleCfg = &m.cfg.LintersSettings.Tagliatelle
142+
testpackageCfg = &m.cfg.LintersSettings.Testpackage
143+
thelperCfg = &m.cfg.LintersSettings.Thelper
142144
unusedCfg = &m.cfg.LintersSettings.Unused
143145
wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck
144146
}
@@ -513,6 +515,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
513515
WithPresets(linter.PresetStyle).
514516
WithLoadForGoAnalysis().
515517
WithURL("https://github.com/butuzov/ireturn"),
518+
linter.NewConfig(golinters.NewNilNil(nilNilCfg)).
519+
WithPresets(linter.PresetStyle).
520+
WithLoadForGoAnalysis().
521+
WithURL("https://github.com/Antonboom/nilnil").
522+
WithSince("v1.43.0"),
516523

517524
linter.NewConfig(golinters.NewCheckBannedFunc()).
518525
WithPresets(linter.PresetStyle),

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)