4
4
5
5
from __future__ import annotations
6
6
7
+ import itertools
8
+ from collections .abc import Iterable
9
+ from typing import Any
10
+
7
11
import astroid
8
12
from astroid import bases , nodes
9
13
10
14
from pylint import checkers
11
15
from pylint .checkers import utils
12
16
13
17
18
+ def _is_constant_zero (node ):
19
+ return isinstance (node , astroid .Const ) and node .value == 0
20
+
21
+
14
22
class ImplicitBooleanessChecker (checkers .BaseChecker ):
15
23
"""Checks for incorrect usage of comparisons or len() inside conditions.
16
24
@@ -50,7 +58,6 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
50
58
* comparison such as variable != empty_literal:
51
59
"""
52
60
53
- # configuration section name
54
61
name = "refactoring"
55
62
msgs = {
56
63
"C1802" : (
@@ -64,11 +71,17 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
64
71
{"old_names" : [("C1801" , "len-as-condition" )]},
65
72
),
66
73
"C1803" : (
67
- "'%s' can be simplified to '%s' as an empty sequence is falsey" ,
74
+ "'%s' can be simplified to '%s' as %s is falsey" ,
68
75
"use-implicit-booleaness-not-comparison" ,
69
76
"Used when Pylint detects that collection literal comparison is being "
70
77
"used to check for emptiness; Use implicit booleaness instead"
71
78
"of a collection classes; empty collections are considered as false" ,
79
+ {
80
+ "old_names" : [
81
+ ("C1901" , "compare-to-empty-string" ),
82
+ ("C2001" , "compare-to-zero" ),
83
+ ]
84
+ },
72
85
),
73
86
}
74
87
@@ -139,6 +152,80 @@ def visit_unaryop(self, node: nodes.UnaryOp) -> None:
139
152
@utils .only_required_for_messages ("use-implicit-booleaness-not-comparison" )
140
153
def visit_compare (self , node : nodes .Compare ) -> None :
141
154
self ._check_use_implicit_booleaness_not_comparison (node )
155
+ self ._check_compare_to_empty_string (node )
156
+ self ._check_compare_to_zero (node )
157
+
158
+ def _check_compare_to_zero (self , node : nodes .Compare ) -> None :
159
+ """Checks for comparisons to zero.
160
+
161
+ Most of the time you should use the fact that integers with a value of 0 are false.
162
+ An exception to this rule is when 0 is allowed in the program and has a
163
+ different meaning than None!
164
+ """
165
+ # pylint: disable=duplicate-code
166
+ _operators = ["!=" , "==" , "is not" , "is" ]
167
+ # note: astroid.Compare has the left most operand in node.left
168
+ # while the rest are a list of tuples in node.ops
169
+ # the format of the tuple is ('compare operator sign', node)
170
+ # here we squash everything into `ops` to make it easier for processing later
171
+ ops = [("" , node .left )]
172
+ ops .extend (node .ops )
173
+ iter_ops : Iterable [Any ] = iter (ops )
174
+ ops = list (itertools .chain (* iter_ops ))
175
+ for ops_idx in range (len (ops ) - 2 ):
176
+ op_1 = ops [ops_idx ]
177
+ op_2 = ops [ops_idx + 1 ]
178
+ op_3 = ops [ops_idx + 2 ]
179
+ error_detected = False
180
+
181
+ # 0 ?? X
182
+ if _is_constant_zero (op_1 ) and op_2 in _operators :
183
+ error_detected = True
184
+ # X ?? 0
185
+ elif op_2 in _operators and _is_constant_zero (op_3 ):
186
+ error_detected = True
187
+
188
+ if error_detected :
189
+ self .add_message (
190
+ "compare-to-zero" , args = ("TODO" , "TODO" , "'0'" ), node = node
191
+ )
192
+
193
+ def _check_compare_to_empty_string (self , node : nodes .Compare ) -> None :
194
+ """Checks for comparisons to empty string.
195
+
196
+ Most of the time you should use the fact that empty strings are false.
197
+ An exception to this rule is when an empty string value is allowed in the program
198
+ and has a different meaning than None!
199
+ """
200
+ _operators = ["!=" , "==" , "is not" , "is" ]
201
+ # note: astroid.Compare has the left most operand in node.left
202
+ # while the rest are a list of tuples in node.ops
203
+ # the format of the tuple is ('compare operator sign', node)
204
+ # here we squash everything into `ops` to make it easier for processing later
205
+ ops = [("" , node .left )]
206
+ ops .extend (node .ops )
207
+ iter_ops : Iterable [Any ] = iter (ops )
208
+ ops = list (itertools .chain (* iter_ops ))
209
+ for ops_idx in range (len (ops ) - 2 ):
210
+ op_1 = ops [ops_idx ]
211
+ op_2 = ops [ops_idx + 1 ]
212
+ op_3 = ops [ops_idx + 2 ]
213
+ error_detected = False
214
+
215
+ # x ?? ""
216
+ if utils .is_empty_str_literal (op_1 ) and op_2 in _operators :
217
+ error_detected = True
218
+ suggestion = op_3 .name
219
+ # '' ?? X
220
+ elif op_2 in _operators and utils .is_empty_str_literal (op_3 ):
221
+ error_detected = True
222
+ suggestion = op_1 .name
223
+ if error_detected :
224
+ self .add_message (
225
+ "compare-to-empty-string" ,
226
+ args = (node .as_string (), suggestion , "an empty string" ),
227
+ node = node ,
228
+ )
142
229
143
230
def _check_use_implicit_booleaness_not_comparison (
144
231
self , node : nodes .Compare
@@ -199,10 +286,7 @@ def _check_use_implicit_booleaness_not_comparison(
199
286
)
200
287
self .add_message (
201
288
"use-implicit-booleaness-not-comparison" ,
202
- args = (
203
- original_comparison ,
204
- suggestion ,
205
- ),
289
+ args = (original_comparison , suggestion , "an empty sequence" ),
206
290
node = node ,
207
291
)
208
292
0 commit comments