Skip to content

Commit 012e78a

Browse files
Work in progress merging of use-implicit-booleaness checker
1 parent b7ad68e commit 012e78a

File tree

27 files changed

+179
-207
lines changed

27 files changed

+179
-207
lines changed

doc/data/messages/c/compare-to-empty-string/bad.py

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

doc/data/messages/c/compare-to-empty-string/good.py

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

doc/data/messages/c/compare-to-empty-string/pylintrc

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

doc/data/messages/c/compare-to-zero/bad.py

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
x = 0
2+
y = 1
3+
4+
if x == 0: # [use-implicit-booleaness-not-comparison-to-zero]
5+
print("x is equal to zero")
6+
7+
if y != 0: # [use-implicit-booleaness-not-comparison-to-zero]
8+
print("y is not equal to zero")
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1+
x = ""
2+
y = "hello"
13
z = []
24

5+
if x == "": # [use-implicit-booleaness-not-comparison]
6+
print("x is an empty string")
7+
8+
if y != "": # [use-implicit-booleaness-not-comparison]
9+
print("y is not an empty string")
10+
311
if z != []: # [use-implicit-booleaness-not-comparison]
4-
print("z is not an empty sequence")
12+
print("z is not en empty container")
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1+
x = ""
2+
y = "hello"
13
z = []
24

5+
if not x:
6+
print("x is an empty string")
7+
8+
if y:
9+
print("y is not an empty string")
10+
311
if z:
4-
print("z is not an empty sequence")
12+
print("z is not en empty container")

doc/user_guide/checkers/extensions.rst

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ Pylint provides the following optional plugins:
2020
- :ref:`pylint.extensions.docstyle`
2121
- :ref:`pylint.extensions.dunder`
2222
- :ref:`pylint.extensions.empty_comment`
23-
- :ref:`pylint.extensions.emptystring`
2423
- :ref:`pylint.extensions.eq_without_hash`
2524
- :ref:`pylint.extensions.for_any_all`
2625
- :ref:`pylint.extensions.magic_value`
@@ -88,20 +87,6 @@ Code Style checker Messages
8887
to. This can be changed to be an augmented assign. Disabled by default!
8988

9089

91-
.. _pylint.extensions.emptystring:
92-
93-
Compare-To-Empty-String checker
94-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
95-
96-
This checker is provided by ``pylint.extensions.emptystring``.
97-
Verbatim name of the checker is ``compare-to-empty-string``.
98-
99-
Compare-To-Empty-String checker Messages
100-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
101-
:compare-to-empty-string (C1901): *"%s" can be simplified to "%s" as an empty string is falsey*
102-
Used when Pylint detects comparison to an empty string constant.
103-
104-
10590
.. _pylint.extensions.comparetozero:
10691

10792
Compare-To-Zero checker
@@ -112,7 +97,7 @@ Verbatim name of the checker is ``compare-to-zero``.
11297

11398
Compare-To-Zero checker Messages
11499
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
115-
:compare-to-zero (C2001): *"%s" can be simplified to "%s" as 0 is falsey*
100+
:use-implicit-booleaness-not-comparison-to-zero (C2002): *"%s" can be simplified to "%s" as 0 is falsey*
116101
Used when Pylint detects comparison to a 0 constant.
117102

118103

doc/user_guide/checkers/features.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -893,10 +893,15 @@ Refactoring checker Messages
893893
Emitted when a single "return" or "return None" statement is found at the end
894894
of function or method definition. This statement can safely be removed
895895
because Python will implicitly return None
896-
:use-implicit-booleaness-not-comparison (C1803): *'%s' can be simplified to '%s' as an empty %s is falsey*
896+
:use-implicit-booleaness-not-comparison (C1803): *"%s" can be simplified to "%s" as an empty %s is falsey*
897897
Used when Pylint detects that collection literal comparison is being used to
898898
check for emptiness; Use implicit booleaness instead of a collection classes;
899899
empty collections are considered as false
900+
:use-implicit-booleaness-not-len (C1802): *"%s" can be simplified to "%s" as an empty %s is falsey*
901+
Used when Pylint detects that len(sequence) is being used without explicit
902+
comparison inside a condition to determine if a sequence is empty. Instead of
903+
coercing the length to a boolean, either rely on the fact that empty
904+
sequences are false or compare the length against a scalar.
900905
:unneeded-not (C0113): *Consider changing "%s" to "%s"*
901906
Used when a boolean expression contains an unneeded negation.
902907
:consider-iterating-dictionary (C0201): *Consider iterating the dictionary directly instead of calling .keys()*
@@ -911,11 +916,6 @@ Refactoring checker Messages
911916
:consider-using-enumerate (C0200): *Consider using enumerate instead of iterating with range and len*
912917
Emitted when code that iterates with range and len is encountered. Such code
913918
can be simplified by using the enumerate builtin.
914-
:use-implicit-booleaness-not-len (C1802): *Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty*
915-
Used when Pylint detects that len(sequence) is being used without explicit
916-
comparison inside a condition to determine if a sequence is empty. Instead of
917-
coercing the length to a boolean, either rely on the fact that empty
918-
sequences are false or compare the length against a scalar.
919919
:consider-using-f-string (C0209): *Formatting a regular string which could be an f-string*
920920
Used when we detect a string that is being formatted with format() or % which
921921
could potentially be an f-string. The use of f-strings is preferred. Requires

doc/user_guide/configuration/all-options.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1285,7 +1285,7 @@ Standard Checkers
12851285

12861286
--spelling-dict
12871287
"""""""""""""""
1288-
*Spelling dictionary name. Available dictionaries: en (aspell), en_AU (aspell), en_CA (aspell), en_GB (aspell), en_US (aspell).*
1288+
*Spelling dictionary name. Available dictionaries: none. To make it work, install the 'python-enchant' package.*
12891289

12901290
**Default:** ``""``
12911291

doc/user_guide/messages/messages_overview.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,6 @@ All messages in the convention category:
390390
convention/bad-file-encoding
391391
convention/bad-mcs-classmethod-argument
392392
convention/bad-mcs-method-argument
393-
convention/compare-to-empty-string
394-
convention/compare-to-zero
395393
convention/consider-iterating-dictionary
396394
convention/consider-using-any-or-all
397395
convention/consider-using-dict-items
@@ -433,6 +431,7 @@ All messages in the convention category:
433431
convention/unnecessary-lambda-assignment
434432
convention/unneeded-not
435433
convention/use-implicit-booleaness-not-comparison
434+
convention/use-implicit-booleaness-not-comparison-to-zero
436435
convention/use-implicit-booleaness-not-len
437436
convention/use-maxsplit-arg
438437
convention/use-sequence-for-iteration
@@ -449,6 +448,8 @@ All renamed messages in the convention category:
449448
:titlesonly:
450449

451450
convention/blacklisted-name
451+
convention/compare-to-empty-string
452+
convention/compare-to-zero
452453
convention/len-as-condition
453454
convention/missing-docstring
454455
convention/old-misplaced-comparison-constant
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
* The compare to empty string checker (``pylint.extensions.emptystring``) has been removed
2+
as it is now part of the implicit booleaness checker, a default check.
3+
4+
- ``compare-to-zero`` was renamed ``use-implicit-booleaness-not-comparison-to-zero`` but is still an extension.
5+
- ``compare-to-empty-string`` was merged with ``use-implicit-booleaness-not-comparison``.
6+
- Messages related to implicit booleaness were made more explicit and actionable.
7+
8+
Closes #6871

pylint/checkers/refactoring/implicit_booleaness_checker.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from __future__ import annotations
66

7+
import itertools
8+
79
import astroid
810
from astroid import bases, nodes, util
911

@@ -54,7 +56,7 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
5456
name = "refactoring"
5557
msgs = {
5658
"C1802": (
57-
"Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty",
59+
'"%s" can be simplified to "%s" as an empty %s is falsey',
5860
"use-implicit-booleaness-not-len",
5961
"Used when Pylint detects that len(sequence) is being used "
6062
"without explicit comparison inside a condition to determine if a sequence is empty. "
@@ -69,6 +71,11 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
6971
"Used when Pylint detects that collection literal comparison is being "
7072
"used to check for emptiness; Use implicit booleaness instead "
7173
"of a collection classes; empty collections are considered as false",
74+
{
75+
"old_names": [
76+
("C1901", "compare-to-empty-string"),
77+
]
78+
},
7279
),
7380
}
7481

@@ -101,6 +108,7 @@ def visit_call(self, node: nodes.Call) -> None:
101108
# The node is a generator or comprehension as in len([x for x in ...])
102109
self.add_message(
103110
"use-implicit-booleaness-not-len",
111+
args=("len_arg.as_string()", "TODO", "TODO"),
104112
node=node,
105113
confidence=HIGH,
106114
)
@@ -119,6 +127,7 @@ def visit_call(self, node: nodes.Call) -> None:
119127
):
120128
self.add_message(
121129
"use-implicit-booleaness-not-len",
130+
args=("instance.as_string()", "TODO", "TODO"),
122131
node=node,
123132
confidence=INFERENCE,
124133
)
@@ -149,6 +158,48 @@ def visit_unaryop(self, node: nodes.UnaryOp) -> None:
149158
@utils.only_required_for_messages("use-implicit-booleaness-not-comparison")
150159
def visit_compare(self, node: nodes.Compare) -> None:
151160
self._check_use_implicit_booleaness_not_comparison(node)
161+
self._check_compare_to_empty_string(node)
162+
163+
def _check_compare_to_empty_string(self, node: nodes.Compare) -> None:
164+
"""Checks for comparisons to empty string.
165+
166+
Most of the time you should use the fact that empty strings are false.
167+
An exception to this rule is when an empty string value is allowed in the program
168+
and has a different meaning than None!
169+
"""
170+
_operators = {"!=", "==", "is not", "is"}
171+
# note: astroid.Compare has the left most operand in node.left while the rest
172+
# are a list of tuples in node.ops the format of the tuple is
173+
# ('compare operator sign', node) here we squash everything into `ops`
174+
# to make it easier for processing later
175+
ops: list[tuple[str, nodes.NodeNG | None]] = [("", node.left)]
176+
ops.extend(node.ops)
177+
iter_ops = iter(ops)
178+
ops = list(itertools.chain(*iter_ops)) # type: ignore[arg-type]
179+
for ops_idx in range(len(ops) - 2):
180+
op_1: nodes.NodeNG | None = ops[ops_idx]
181+
op_2: str = ops[ops_idx + 1] # type: ignore[assignment]
182+
op_3: nodes.NodeNG | None = ops[ops_idx + 2]
183+
error_detected = False
184+
if op_1 is None or op_3 is None or op_2 not in _operators:
185+
continue
186+
node_name = ""
187+
# x ?? ""
188+
if utils.is_empty_str_literal(op_1):
189+
error_detected = True
190+
node_name = op_3.as_string()
191+
# '' ?? X
192+
elif utils.is_empty_str_literal(op_3):
193+
error_detected = True
194+
node_name = op_1.as_string()
195+
if error_detected:
196+
suggestion = f"not {node_name}" if op_2 in {"==", "is"} else node_name
197+
self.add_message(
198+
"compare-to-empty-string",
199+
args=(node.as_string(), suggestion, "string"),
200+
node=node,
201+
confidence=HIGH,
202+
)
152203

153204
def _check_use_implicit_booleaness_not_comparison(
154205
self, node: nodes.Compare

pylint/extensions/comparetozero.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@ class CompareToZeroChecker(checkers.BaseChecker):
3939
# configuration section name
4040
name = "compare-to-zero"
4141
msgs = {
42-
"C2001": (
42+
"C2002": (
4343
'"%s" can be simplified to "%s" as 0 is falsey',
44-
"compare-to-zero",
44+
"use-implicit-booleaness-not-comparison-to-zero",
4545
"Used when Pylint detects comparison to a 0 constant.",
46+
{
47+
"old_names": [
48+
("C2001", "compare-to-zero"),
49+
]
50+
},
4651
)
4752
}
4853

pylint/extensions/emptystring.py

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

0 commit comments

Comments
 (0)