summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Sassoulas <pierre.sassoulas@gmail.com>2023-04-26 13:16:21 +0200
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2023-05-02 20:01:39 +0200
commit26686d544eab7236a92e56cc90c34b6e3a37f473 (patch)
treeca93e25911bdaa0608653d2a215d0b0fda5fbe1a
parent87fe5e58c2013b4b1949836c142993464b153237 (diff)
downloadpylint-git-26686d544eab7236a92e56cc90c34b6e3a37f473.tar.gz
Merge the compare-to-zero extensions to 'implicit_booleaness_checker'
-rw-r--r--doc/data/messages/c/compare-to-zero/bad.py8
-rw-r--r--doc/data/messages/c/compare-to-zero/good.py8
-rw-r--r--doc/data/messages/c/compare-to-zero/pylintrc2
-rw-r--r--doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/bad.py6
-rw-r--r--doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/details.rst3
-rw-r--r--doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/good.py6
-rw-r--r--doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/pylintrc2
-rw-r--r--doc/user_guide/checkers/extensions.rst15
-rw-r--r--doc/user_guide/checkers/features.rst2
-rw-r--r--doc/user_guide/configuration/all-options.rst2
-rw-r--r--doc/user_guide/messages/messages_overview.rst3
-rw-r--r--doc/whatsnew/fragments/6871.user_action12
-rw-r--r--pylint/checkers/refactoring/implicit_booleaness_checker.py59
-rw-r--r--pylint/extensions/comparetozero.py95
-rw-r--r--tests/functional/ext/comparetozero/compare_to_zero.py46
-rw-r--r--tests/functional/ext/comparetozero/compare_to_zero.rc2
-rw-r--r--tests/functional/ext/comparetozero/compare_to_zero.txt6
-rw-r--r--tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.py46
-rw-r--r--tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.rc2
-rw-r--r--tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.txt6
20 files changed, 147 insertions, 184 deletions
diff --git a/doc/data/messages/c/compare-to-zero/bad.py b/doc/data/messages/c/compare-to-zero/bad.py
deleted file mode 100644
index c987403a4..000000000
--- a/doc/data/messages/c/compare-to-zero/bad.py
+++ /dev/null
@@ -1,8 +0,0 @@
-x = 0
-y = 1
-
-if x == 0: # [compare-to-zero]
- print("x is equal to zero")
-
-if y != 0: # [compare-to-zero]
- print("y is not equal to zero")
diff --git a/doc/data/messages/c/compare-to-zero/good.py b/doc/data/messages/c/compare-to-zero/good.py
deleted file mode 100644
index bea3733e2..000000000
--- a/doc/data/messages/c/compare-to-zero/good.py
+++ /dev/null
@@ -1,8 +0,0 @@
-x = 0
-y = 1
-
-if not x:
- print("x is equal to zero")
-
-if y:
- print("y is not equal to zero")
diff --git a/doc/data/messages/c/compare-to-zero/pylintrc b/doc/data/messages/c/compare-to-zero/pylintrc
deleted file mode 100644
index 895291f84..000000000
--- a/doc/data/messages/c/compare-to-zero/pylintrc
+++ /dev/null
@@ -1,2 +0,0 @@
-[main]
-load-plugins=pylint.extensions.comparetozero
diff --git a/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/bad.py b/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/bad.py
new file mode 100644
index 000000000..2f93afd31
--- /dev/null
+++ b/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/bad.py
@@ -0,0 +1,6 @@
+def important_math(x: int, y: int) -> None:
+ if x == 0: # [use-implicit-booleaness-not-comparison-to-zero]
+ print("x is equal to zero")
+
+ if y != 0: # [use-implicit-booleaness-not-comparison-to-zero]
+ print("y is not equal to zero")
diff --git a/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/details.rst b/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/details.rst
new file mode 100644
index 000000000..670633b2a
--- /dev/null
+++ b/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/details.rst
@@ -0,0 +1,3 @@
+Following this check blindly in weakly typed code base can create hard to debug issues. If the value
+can be something else that is falsey but not an ``int`` (for example ``None``, or an empty string),
+the code will not be equivalent.
diff --git a/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/good.py b/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/good.py
new file mode 100644
index 000000000..feae7fe30
--- /dev/null
+++ b/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/good.py
@@ -0,0 +1,6 @@
+def important_math(x: int, y: int) -> None:
+ if not x:
+ print("x is equal to zero")
+
+ if y:
+ print("y is not equal to zero")
diff --git a/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/pylintrc b/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/pylintrc
new file mode 100644
index 000000000..43d8bd751
--- /dev/null
+++ b/doc/data/messages/u/use-implicit-booleaness-not-comparison-to-zero/pylintrc
@@ -0,0 +1,2 @@
+[main]
+enable=use-implicit-booleaness-not-comparison-to-zero
diff --git a/doc/user_guide/checkers/extensions.rst b/doc/user_guide/checkers/extensions.rst
index 793b3889b..f3823d715 100644
--- a/doc/user_guide/checkers/extensions.rst
+++ b/doc/user_guide/checkers/extensions.rst
@@ -10,7 +10,6 @@ Pylint provides the following optional plugins:
- :ref:`pylint.extensions.broad_try_clause`
- :ref:`pylint.extensions.check_elif`
- :ref:`pylint.extensions.code_style`
-- :ref:`pylint.extensions.comparetozero`
- :ref:`pylint.extensions.comparison_placement`
- :ref:`pylint.extensions.confusing_elif`
- :ref:`pylint.extensions.consider_refactoring_into_while_condition`
@@ -102,20 +101,6 @@ Compare-To-Empty-String checker Messages
Used when Pylint detects comparison to an empty string constant.
-.. _pylint.extensions.comparetozero:
-
-Compare-To-Zero checker
-~~~~~~~~~~~~~~~~~~~~~~~
-
-This checker is provided by ``pylint.extensions.comparetozero``.
-Verbatim name of the checker is ``compare-to-zero``.
-
-Compare-To-Zero checker Messages
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-:compare-to-zero (C2001): *"%s" can be simplified to "%s" as 0 is falsey*
- Used when Pylint detects comparison to a 0 constant.
-
-
.. _pylint.extensions.comparison_placement:
Comparison-Placement checker
diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst
index a6b08381a..e30aff544 100644
--- a/doc/user_guide/checkers/features.rst
+++ b/doc/user_guide/checkers/features.rst
@@ -893,6 +893,8 @@ Refactoring checker Messages
Emitted when a single "return" or "return None" statement is found at the end
of function or method definition. This statement can safely be removed
because Python will implicitly return None
+:use-implicit-booleaness-not-comparison-to-zero (C1804): *"%s" can be simplified to "%s" as 0 is falsey*
+ Used when Pylint detects comparison to a 0 constant.
:use-implicit-booleaness-not-comparison (C1803): *'%s' can be simplified to '%s' as an empty %s is falsey*
Used when Pylint detects that collection literal comparison is being used to
check for emptiness; Use implicit booleaness instead of a collection classes;
diff --git a/doc/user_guide/configuration/all-options.rst b/doc/user_guide/configuration/all-options.rst
index ecdfc9ef5..8ddafa104 100644
--- a/doc/user_guide/configuration/all-options.rst
+++ b/doc/user_guide/configuration/all-options.rst
@@ -233,7 +233,7 @@ Standard Checkers
confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"]
- disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "consider-using-augmented-assign"]
+ disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-implicit-booleaness-not-comparison-to-zero", "use-symbolic-message-instead", "consider-using-augmented-assign"]
enable = []
diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst
index 167802ae2..50f77d35e 100644
--- a/doc/user_guide/messages/messages_overview.rst
+++ b/doc/user_guide/messages/messages_overview.rst
@@ -391,7 +391,6 @@ All messages in the convention category:
convention/bad-mcs-classmethod-argument
convention/bad-mcs-method-argument
convention/compare-to-empty-string
- convention/compare-to-zero
convention/consider-iterating-dictionary
convention/consider-using-any-or-all
convention/consider-using-dict-items
@@ -433,6 +432,7 @@ All messages in the convention category:
convention/unnecessary-lambda-assignment
convention/unneeded-not
convention/use-implicit-booleaness-not-comparison
+ convention/use-implicit-booleaness-not-comparison-to-zero
convention/use-implicit-booleaness-not-len
convention/use-maxsplit-arg
convention/use-sequence-for-iteration
@@ -449,6 +449,7 @@ All renamed messages in the convention category:
:titlesonly:
convention/blacklisted-name
+ convention/compare-to-zero
convention/len-as-condition
convention/missing-docstring
convention/old-misplaced-comparison-constant
diff --git a/doc/whatsnew/fragments/6871.user_action b/doc/whatsnew/fragments/6871.user_action
new file mode 100644
index 000000000..9aceab4c1
--- /dev/null
+++ b/doc/whatsnew/fragments/6871.user_action
@@ -0,0 +1,12 @@
+* The compare to empty string checker (``pylint.extensions.emptystring``) has been removed and its checks are
+ now part of the implicit booleaness checker:
+
+ - ``compare-to-zero`` was renamed ``use-implicit-booleaness-not-comparison-to-zero``
+ and it now need to be enabled explicitly.
+ - The ``pylint.extensions.compare-to-zero`` extension no longer exists and
+ needs to be removed from the ``load-plugins`` option.
+
+This permits to make the likeness explicit and will provide better performance as they share most of their
+conditions to be raised.
+
+Refs #6871
diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py
index 3215d7ecd..ff1a36205 100644
--- a/pylint/checkers/refactoring/implicit_booleaness_checker.py
+++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py
@@ -4,6 +4,8 @@
from __future__ import annotations
+import itertools
+
import astroid
from astroid import bases, nodes, util
@@ -12,6 +14,14 @@ from pylint.checkers import utils
from pylint.interfaces import HIGH, INFERENCE
+def _is_constant_zero(node: str | nodes.NodeNG) -> bool:
+ # We have to check that node.value is not False because node.value == 0 is True
+ # when node.value is False
+ return (
+ isinstance(node, astroid.Const) and node.value == 0 and node.value is not False
+ )
+
+
class ImplicitBooleanessChecker(checkers.BaseChecker):
"""Checks for incorrect usage of comparisons or len() inside conditions.
@@ -70,6 +80,12 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
"used to check for emptiness; Use implicit booleaness instead "
"of a collection classes; empty collections are considered as false",
),
+ "C1804": (
+ '"%s" can be simplified to "%s" as 0 is falsey',
+ "use-implicit-booleaness-not-comparison-to-zero",
+ "Used when Pylint detects comparison to a 0 constant.",
+ {"default_enabled": False, "old_names": [("C2001", "compare-to-zero")]},
+ ),
}
options = ()
@@ -149,6 +165,49 @@ class ImplicitBooleanessChecker(checkers.BaseChecker):
@utils.only_required_for_messages("use-implicit-booleaness-not-comparison")
def visit_compare(self, node: nodes.Compare) -> None:
self._check_use_implicit_booleaness_not_comparison(node)
+ self._check_compare_to_zero(node)
+
+ @utils.only_required_for_messages("compare-to-zero")
+ def _check_compare_to_zero(self, node: nodes.Compare) -> None:
+ # pylint: disable=duplicate-code
+ _operators = ["!=", "==", "is not", "is"]
+ # note: astroid.Compare has the left most operand in node.left
+ # while the rest are a list of tuples in node.ops
+ # the format of the tuple is ('compare operator sign', node)
+ # here we squash everything into `ops` to make it easier for processing later
+ ops: list[tuple[str, nodes.NodeNG]] = [("", node.left)]
+ ops.extend(node.ops)
+ iter_ops = iter(ops)
+ all_ops = list(itertools.chain(*iter_ops))
+
+ for ops_idx in range(len(all_ops) - 2):
+ op_1 = all_ops[ops_idx]
+ op_2 = all_ops[ops_idx + 1]
+ op_3 = all_ops[ops_idx + 2]
+ error_detected = False
+
+ # 0 ?? X
+ if _is_constant_zero(op_1) and op_2 in _operators:
+ error_detected = True
+ op = op_3
+ # X ?? 0
+ elif op_2 in _operators and _is_constant_zero(op_3):
+ error_detected = True
+ op = op_1
+
+ if error_detected:
+ original = f"{op_1.as_string()} {op_2} {op_3.as_string()}"
+ suggestion = (
+ op.as_string()
+ if op_2 in {"!=", "is not"}
+ else f"not {op.as_string()}"
+ )
+ self.add_message(
+ "compare-to-zero",
+ args=(original, suggestion),
+ node=node,
+ confidence=HIGH,
+ )
def _check_use_implicit_booleaness_not_comparison(
self, node: nodes.Compare
diff --git a/pylint/extensions/comparetozero.py b/pylint/extensions/comparetozero.py
deleted file mode 100644
index 0be821be3..000000000
--- a/pylint/extensions/comparetozero.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
-# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
-# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
-
-"""Looks for comparisons to zero."""
-
-from __future__ import annotations
-
-import itertools
-from typing import TYPE_CHECKING
-
-import astroid
-from astroid import nodes
-
-from pylint import checkers
-from pylint.checkers import utils
-from pylint.interfaces import HIGH
-
-if TYPE_CHECKING:
- from pylint.lint import PyLinter
-
-
-def _is_constant_zero(node: str | nodes.NodeNG) -> bool:
- # We have to check that node.value is not False because node.value == 0 is True
- # when node.value is False
- return (
- isinstance(node, astroid.Const) and node.value == 0 and node.value is not False
- )
-
-
-class CompareToZeroChecker(checkers.BaseChecker):
- """Checks for comparisons to zero.
-
- Most of the time you should use the fact that integers with a value of 0 are false.
- An exception to this rule is when 0 is allowed in the program and has a
- different meaning than None!
- """
-
- # configuration section name
- name = "compare-to-zero"
- msgs = {
- "C2001": (
- '"%s" can be simplified to "%s" as 0 is falsey',
- "compare-to-zero",
- "Used when Pylint detects comparison to a 0 constant.",
- )
- }
-
- options = ()
-
- @utils.only_required_for_messages("compare-to-zero")
- def visit_compare(self, node: nodes.Compare) -> None:
- # pylint: disable=duplicate-code
- _operators = ["!=", "==", "is not", "is"]
- # note: astroid.Compare has the left most operand in node.left
- # while the rest are a list of tuples in node.ops
- # the format of the tuple is ('compare operator sign', node)
- # here we squash everything into `ops` to make it easier for processing later
- ops: list[tuple[str, nodes.NodeNG]] = [("", node.left)]
- ops.extend(node.ops)
- iter_ops = iter(ops)
- all_ops = list(itertools.chain(*iter_ops))
-
- for ops_idx in range(len(all_ops) - 2):
- op_1 = all_ops[ops_idx]
- op_2 = all_ops[ops_idx + 1]
- op_3 = all_ops[ops_idx + 2]
- error_detected = False
-
- # 0 ?? X
- if _is_constant_zero(op_1) and op_2 in _operators:
- error_detected = True
- op = op_3
- # X ?? 0
- elif op_2 in _operators and _is_constant_zero(op_3):
- error_detected = True
- op = op_1
-
- if error_detected:
- original = f"{op_1.as_string()} {op_2} {op_3.as_string()}"
- suggestion = (
- op.as_string()
- if op_2 in {"!=", "is not"}
- else f"not {op.as_string()}"
- )
- self.add_message(
- "compare-to-zero",
- args=(original, suggestion),
- node=node,
- confidence=HIGH,
- )
-
-
-def register(linter: PyLinter) -> None:
- linter.register_checker(CompareToZeroChecker(linter))
diff --git a/tests/functional/ext/comparetozero/compare_to_zero.py b/tests/functional/ext/comparetozero/compare_to_zero.py
deleted file mode 100644
index 6a14b8bc9..000000000
--- a/tests/functional/ext/comparetozero/compare_to_zero.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# pylint: disable=literal-comparison,missing-docstring, singleton-comparison
-
-X = 123
-Y = len('test')
-
-if X is 0: # [compare-to-zero]
- pass
-
-if X is False:
- pass
-
-if Y is not 0: # [compare-to-zero]
- pass
-
-if Y is not False:
- pass
-
-if X == 0: # [compare-to-zero]
- pass
-
-if X == False:
- pass
-
-if 0 == Y: # [compare-to-zero]
- pass
-
-if Y != 0: # [compare-to-zero]
- pass
-
-if 0 != X: # [compare-to-zero]
- pass
-
-if Y != False:
- pass
-
-if X > 0:
- pass
-
-if X < 0:
- pass
-
-if 0 < X:
- pass
-
-if 0 > X:
- pass
diff --git a/tests/functional/ext/comparetozero/compare_to_zero.rc b/tests/functional/ext/comparetozero/compare_to_zero.rc
deleted file mode 100644
index 70c6171b5..000000000
--- a/tests/functional/ext/comparetozero/compare_to_zero.rc
+++ /dev/null
@@ -1,2 +0,0 @@
-[MAIN]
-load-plugins=pylint.extensions.comparetozero,
diff --git a/tests/functional/ext/comparetozero/compare_to_zero.txt b/tests/functional/ext/comparetozero/compare_to_zero.txt
deleted file mode 100644
index a413a3268..000000000
--- a/tests/functional/ext/comparetozero/compare_to_zero.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-compare-to-zero:6:3:6:9::"""X is 0"" can be simplified to ""not X"" as 0 is falsey":HIGH
-compare-to-zero:12:3:12:13::"""Y is not 0"" can be simplified to ""Y"" as 0 is falsey":HIGH
-compare-to-zero:18:3:18:9::"""X == 0"" can be simplified to ""not X"" as 0 is falsey":HIGH
-compare-to-zero:24:3:24:9::"""0 == Y"" can be simplified to ""not Y"" as 0 is falsey":HIGH
-compare-to-zero:27:3:27:9::"""Y != 0"" can be simplified to ""Y"" as 0 is falsey":HIGH
-compare-to-zero:30:3:30:9::"""0 != X"" can be simplified to ""X"" as 0 is falsey":HIGH
diff --git a/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.py b/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.py
new file mode 100644
index 000000000..766b1f547
--- /dev/null
+++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.py
@@ -0,0 +1,46 @@
+# pylint: disable=literal-comparison,missing-docstring, singleton-comparison
+
+X = 123
+Y = len('test')
+
+if X is 0: # [use-implicit-booleaness-not-comparison-to-zero]
+ pass
+
+if X is False:
+ pass
+
+if Y is not 0: # [use-implicit-booleaness-not-comparison-to-zero]
+ pass
+
+if Y is not False:
+ pass
+
+if X == 0: # [use-implicit-booleaness-not-comparison-to-zero]
+ pass
+
+if X == False:
+ pass
+
+if 0 == Y: # [use-implicit-booleaness-not-comparison-to-zero]
+ pass
+
+if Y != 0: # [use-implicit-booleaness-not-comparison-to-zero]
+ pass
+
+if 0 != X: # [use-implicit-booleaness-not-comparison-to-zero]
+ pass
+
+if Y != False:
+ pass
+
+if X > 0:
+ pass
+
+if X < 0:
+ pass
+
+if 0 < X:
+ pass
+
+if 0 > X:
+ pass
diff --git a/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.rc b/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.rc
new file mode 100644
index 000000000..be01705b1
--- /dev/null
+++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.rc
@@ -0,0 +1,2 @@
+[MAIN]
+enable=compare-to-zero,
diff --git a/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.txt b/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.txt
new file mode 100644
index 000000000..25702682a
--- /dev/null
+++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison_to_zero.txt
@@ -0,0 +1,6 @@
+use-implicit-booleaness-not-comparison-to-zero:6:3:6:9::"""X is 0"" can be simplified to ""not X"" as 0 is falsey":HIGH
+use-implicit-booleaness-not-comparison-to-zero:12:3:12:13::"""Y is not 0"" can be simplified to ""Y"" as 0 is falsey":HIGH
+use-implicit-booleaness-not-comparison-to-zero:18:3:18:9::"""X == 0"" can be simplified to ""not X"" as 0 is falsey":HIGH
+use-implicit-booleaness-not-comparison-to-zero:24:3:24:9::"""0 == Y"" can be simplified to ""not Y"" as 0 is falsey":HIGH
+use-implicit-booleaness-not-comparison-to-zero:27:3:27:9::"""Y != 0"" can be simplified to ""Y"" as 0 is falsey":HIGH
+use-implicit-booleaness-not-comparison-to-zero:30:3:30:9::"""0 != X"" can be simplified to ""X"" as 0 is falsey":HIGH