1
2
3
4
5
6
7 package bools
8
9 import (
10 "go/ast"
11 "go/token"
12 "go/types"
13
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/inspect"
16 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
17 "golang.org/x/tools/go/ast/inspector"
18 )
19
20 const Doc = "check for common mistakes involving boolean operators"
21
22 var Analyzer = &analysis.Analyzer{
23 Name: "bools",
24 Doc: Doc,
25 Requires: []*analysis.Analyzer{inspect.Analyzer},
26 Run: run,
27 }
28
29 func run(pass *analysis.Pass) (interface{}, error) {
30 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
31
32 nodeFilter := []ast.Node{
33 (*ast.BinaryExpr)(nil),
34 }
35 seen := make(map[*ast.BinaryExpr]bool)
36 inspect.Preorder(nodeFilter, func(n ast.Node) {
37 e := n.(*ast.BinaryExpr)
38 if seen[e] {
39
40 return
41 }
42
43 var op boolOp
44 switch e.Op {
45 case token.LOR:
46 op = or
47 case token.LAND:
48 op = and
49 default:
50 return
51 }
52
53 comm := op.commutativeSets(pass.TypesInfo, e, seen)
54 for _, exprs := range comm {
55 op.checkRedundant(pass, exprs)
56 op.checkSuspect(pass, exprs)
57 }
58 })
59 return nil, nil
60 }
61
62 type boolOp struct {
63 name string
64 tok token.Token
65 badEq token.Token
66 }
67
68 var (
69 or = boolOp{"or", token.LOR, token.NEQ}
70 and = boolOp{"and", token.LAND, token.EQL}
71 )
72
73
74
75
76
77
78 func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr {
79 exprs := op.split(e, seen)
80
81
82 i := 0
83 var sets [][]ast.Expr
84 for j := 0; j <= len(exprs); j++ {
85 if j == len(exprs) || hasSideEffects(info, exprs[j]) {
86 if i < j {
87 sets = append(sets, exprs[i:j])
88 }
89 i = j + 1
90 }
91 }
92
93 return sets
94 }
95
96
97
98
99
100 func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) {
101 seen := make(map[string]bool)
102 for _, e := range exprs {
103 efmt := analysisutil.Format(pass.Fset, e)
104 if seen[efmt] {
105 pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
106 } else {
107 seen[efmt] = true
108 }
109 }
110 }
111
112
113
114
115
116
117
118
119 func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) {
120
121 seen := make(map[string]string)
122
123 for _, e := range exprs {
124 bin, ok := e.(*ast.BinaryExpr)
125 if !ok || bin.Op != op.badEq {
126 continue
127 }
128
129
130
131
132
133
134
135
136 var x ast.Expr
137 switch {
138 case pass.TypesInfo.Types[bin.Y].Value != nil:
139 x = bin.X
140 case pass.TypesInfo.Types[bin.X].Value != nil:
141 x = bin.Y
142 default:
143 continue
144 }
145
146
147 xfmt := analysisutil.Format(pass.Fset, x)
148 efmt := analysisutil.Format(pass.Fset, e)
149 if prev, found := seen[xfmt]; found {
150
151 if efmt != prev {
152 pass.ReportRangef(e, "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
153 }
154 } else {
155 seen[xfmt] = efmt
156 }
157 }
158 }
159
160
161 func hasSideEffects(info *types.Info, e ast.Expr) bool {
162 safe := true
163 ast.Inspect(e, func(node ast.Node) bool {
164 switch n := node.(type) {
165 case *ast.CallExpr:
166 typVal := info.Types[n.Fun]
167 switch {
168 case typVal.IsType():
169
170 case typVal.IsBuiltin():
171
172
173 safe = false
174 return false
175 default:
176
177
178
179 safe = false
180 return false
181 }
182 case *ast.UnaryExpr:
183 if n.Op == token.ARROW {
184 safe = false
185 return false
186 }
187 }
188 return true
189 })
190 return !safe
191 }
192
193
194
195
196
197 func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
198 for {
199 e = unparen(e)
200 if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
201 seen[b] = true
202 exprs = append(exprs, op.split(b.Y, seen)...)
203 e = b.X
204 } else {
205 exprs = append(exprs, e)
206 break
207 }
208 }
209 return
210 }
211
212
213 func unparen(e ast.Expr) ast.Expr {
214 for {
215 p, ok := e.(*ast.ParenExpr)
216 if !ok {
217 return e
218 }
219 e = p.X
220 }
221 }
222
View as plain text