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 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/bools",
26 Requires: []*analysis.Analyzer{inspect.Analyzer},
27 Run: run,
28 }
29
30 func run(pass *analysis.Pass) (interface{}, error) {
31 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
32
33 nodeFilter := []ast.Node{
34 (*ast.BinaryExpr)(nil),
35 }
36 seen := make(map[*ast.BinaryExpr]bool)
37 inspect.Preorder(nodeFilter, func(n ast.Node) {
38 e := n.(*ast.BinaryExpr)
39 if seen[e] {
40
41 return
42 }
43
44 var op boolOp
45 switch e.Op {
46 case token.LOR:
47 op = or
48 case token.LAND:
49 op = and
50 default:
51 return
52 }
53
54 comm := op.commutativeSets(pass.TypesInfo, e, seen)
55 for _, exprs := range comm {
56 op.checkRedundant(pass, exprs)
57 op.checkSuspect(pass, exprs)
58 }
59 })
60 return nil, nil
61 }
62
63 type boolOp struct {
64 name string
65 tok token.Token
66 badEq token.Token
67 }
68
69 var (
70 or = boolOp{"or", token.LOR, token.NEQ}
71 and = boolOp{"and", token.LAND, token.EQL}
72 )
73
74
75
76
77
78
79 func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr {
80 exprs := op.split(e, seen)
81
82
83 i := 0
84 var sets [][]ast.Expr
85 for j := 0; j <= len(exprs); j++ {
86 if j == len(exprs) || analysisutil.HasSideEffects(info, exprs[j]) {
87 if i < j {
88 sets = append(sets, exprs[i:j])
89 }
90 i = j + 1
91 }
92 }
93
94 return sets
95 }
96
97
98
99
100
101
102
103 func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) {
104 seen := make(map[string]bool)
105 for _, e := range exprs {
106 efmt := analysisutil.Format(pass.Fset, e)
107 if seen[efmt] {
108 pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
109 } else {
110 seen[efmt] = true
111 }
112 }
113 }
114
115
116
117
118
119
120
121
122
123
124 func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) {
125
126 seen := make(map[string]string)
127
128 for _, e := range exprs {
129 bin, ok := e.(*ast.BinaryExpr)
130 if !ok || bin.Op != op.badEq {
131 continue
132 }
133
134
135
136
137
138
139
140
141 var x ast.Expr
142 switch {
143 case pass.TypesInfo.Types[bin.Y].Value != nil:
144 x = bin.X
145 case pass.TypesInfo.Types[bin.X].Value != nil:
146 x = bin.Y
147 default:
148 continue
149 }
150
151
152 xfmt := analysisutil.Format(pass.Fset, x)
153 efmt := analysisutil.Format(pass.Fset, e)
154 if prev, found := seen[xfmt]; found {
155
156 if efmt != prev {
157 pass.ReportRangef(e, "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
158 }
159 } else {
160 seen[xfmt] = efmt
161 }
162 }
163 }
164
165
166
167
168
169 func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
170 for {
171 e = ast.Unparen(e)
172 if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
173 seen[b] = true
174 exprs = append(exprs, op.split(b.Y, seen)...)
175 e = b.X
176 } else {
177 exprs = append(exprs, e)
178 break
179 }
180 }
181 return
182 }
183
View as plain text