diff options
author | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2023-04-26 13:16:21 +0200 |
---|---|---|
committer | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2023-05-02 20:01:39 +0200 |
commit | 26686d544eab7236a92e56cc90c34b6e3a37f473 (patch) | |
tree | ca93e25911bdaa0608653d2a215d0b0fda5fbe1a | |
parent | 87fe5e58c2013b4b1949836c142993464b153237 (diff) | |
download | pylint-git-26686d544eab7236a92e56cc90c34b6e3a37f473.tar.gz |
Merge the compare-to-zero extensions to 'implicit_booleaness_checker'
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 |