s1031.go raw
1 package s1031
2
3 import (
4 "go/ast"
5 "go/types"
6
7 "honnef.co/go/tools/analysis/code"
8 "honnef.co/go/tools/analysis/facts/generated"
9 "honnef.co/go/tools/analysis/lint"
10 "honnef.co/go/tools/analysis/report"
11 "honnef.co/go/tools/go/types/typeutil"
12 "honnef.co/go/tools/pattern"
13
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/inspect"
16 )
17
18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
19 Analyzer: &analysis.Analyzer{
20 Name: "S1031",
21 Run: run,
22 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
23 },
24 Doc: &lint.RawDocumentation{
25 Title: `Omit redundant nil check around loop`,
26 Text: `You can use range on nil slices and maps, the loop will simply never
27 execute. This makes an additional nil check around the loop
28 unnecessary.`,
29 Before: `
30 if s != nil {
31 for _, x := range s {
32 ...
33 }
34 }`,
35 After: `
36 for _, x := range s {
37 ...
38 }`,
39 Since: "2017.1",
40 // MergeIfAll because x might be a channel under some build tags.
41 // you shouldn't write code like that…
42 MergeIf: lint.MergeIfAll,
43 },
44 })
45
46 var Analyzer = SCAnalyzer.Analyzer
47
48 var checkNilCheckAroundRangeQ = pattern.MustParse(`
49 (IfStmt
50 nil
51 (BinaryExpr x@(Object _) "!=" (Builtin "nil"))
52 [(RangeStmt _ _ _ x _)]
53 nil)`)
54
55 func run(pass *analysis.Pass) (interface{}, error) {
56 fn := func(node ast.Node) {
57 m, ok := code.Match(pass, checkNilCheckAroundRangeQ, node)
58 if !ok {
59 return
60 }
61 ok = typeutil.All(m.State["x"].(types.Object).Type(), func(term *types.Term) bool {
62 switch term.Type().Underlying().(type) {
63 case *types.Slice, *types.Map:
64 return true
65 case *types.TypeParam, *types.Chan, *types.Pointer, *types.Signature:
66 return false
67 default:
68 lint.ExhaustiveTypeSwitch(term.Type().Underlying())
69 return false
70 }
71 })
72 if !ok {
73 return
74 }
75 report.Report(pass, node, "unnecessary nil check around range", report.ShortRange(), report.FilterGenerated())
76 }
77 code.Preorder(pass, fn, (*ast.IfStmt)(nil))
78 return nil, nil
79 }
80