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