Skip to content

Commit 6626fc8

Browse files
Work in progress merging of use-implicit-booleaness checker
1 parent 72e2e2a commit 6626fc8

13 files changed

+157
-217
lines changed

doc/whatsnew/2/2.15/index.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ New checkers
1919
Removed checkers
2020
================
2121

22+
* The compare to zero and compare to empty string checkers have been removed as they are now part of
23+
the implicit booleaness checker, a default check.
24+
2225

2326
Extensions
2427
==========
2528

29+
* ``compare-to-zero`` and ``compare-to-empty-string`` have been renamed to ``use-implicit-booleaness-not-comparison``.
30+
The content of the message now suggest what should be done like other implicit booleaness checks.
2631

2732
False positives fixed
2833
=====================

pylint/checkers/refactoring/implicit_booleaness_checker.py

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@
44

55
from __future__ import annotations
66

7+
import itertools
8+
from collections.abc import Iterable
9+
from typing import Any
10+
711
import astroid
812
from astroid import bases, nodes
913

1014
from pylint import checkers
1115
from pylint.checkers import utils
1216

1317

18+
def _is_constant_zero(node):
19+
return isinstance(node, astroid.Const) and node.value == 0
20+
21+
1422
class ImplicitBooleanessChecker(checkers.BaseChecker):
1523
"""Checks for incorrect usage of comparisons or len() inside conditions.
1624
@@ -50,7 +58,6 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
5058
* comparison such as variable != empty_literal:
5159
"""
5260

53-
# configuration section name
5461
name = "refactoring"
5562
msgs = {
5663
"C1802": (
@@ -64,11 +71,17 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
6471
{"old_names": [("C1801", "len-as-condition")]},
6572
),
6673
"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",
6875
"use-implicit-booleaness-not-comparison",
6976
"Used when Pylint detects that collection literal comparison is being "
7077
"used to check for emptiness; Use implicit booleaness instead"
7178
"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+
},
7285
),
7386
}
7487

@@ -139,6 +152,80 @@ def visit_unaryop(self, node: nodes.UnaryOp) -> None:
139152
@utils.only_required_for_messages("use-implicit-booleaness-not-comparison")
140153
def visit_compare(self, node: nodes.Compare) -> None:
141154
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+
)
142229

143230
def _check_use_implicit_booleaness_not_comparison(
144231
self, node: nodes.Compare
@@ -199,10 +286,7 @@ def _check_use_implicit_booleaness_not_comparison(
199286
)
200287
self.add_message(
201288
"use-implicit-booleaness-not-comparison",
202-
args=(
203-
original_comparison,
204-
suggestion,
205-
),
289+
args=(original_comparison, suggestion, "an empty sequence"),
206290
node=node,
207291
)
208292

pylint/extensions/comparetozero.py

Lines changed: 0 additions & 78 deletions
This file was deleted.

pylint/extensions/emptystring.py

Lines changed: 0 additions & 72 deletions
This file was deleted.

tests/functional/ext/comparetozero/comparetozero.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

tests/functional/ext/comparetozero/comparetozero.rc

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/functional/ext/comparetozero/comparetozero.txt

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/functional/ext/emptystring/empty_string_comparison.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

tests/functional/ext/emptystring/empty_string_comparison.rc

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/functional/ext/emptystring/empty_string_comparison.txt

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)