From 918f4624d76197bec7cc54467f0281ff63c19c5a Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 26 Apr 2023 21:50:45 +0200 Subject: Merge the empty-string extensions to 'implicit_booleaness_checker' --- .../refactoring/implicit_booleaness_checker.py | 58 +++++++++++++++- pylint/extensions/emptystring.py | 78 ---------------------- 2 files changed, 56 insertions(+), 80 deletions(-) delete mode 100644 pylint/extensions/emptystring.py (limited to 'pylint') diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index ff1a36205..93ac44269 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -81,6 +81,15 @@ class ImplicitBooleanessChecker(checkers.BaseChecker): "of a collection classes; empty collections are considered as false", ), "C1804": ( + '"%s" can be simplified to "%s" as an empty string is falsey', + "use-implicit-booleaness-not-comparison-to-string", + "Used when Pylint detects comparison to an empty string constant.", + { + "default_enabled": False, + "old_names": [("C1901", "compare-to-empty-string")], + }, + ), + "C1805": ( '"%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.", @@ -162,12 +171,16 @@ class ImplicitBooleanessChecker(checkers.BaseChecker): "use-implicit-booleaness-not-len", node=node, confidence=HIGH ) - @utils.only_required_for_messages("use-implicit-booleaness-not-comparison") + @utils.only_required_for_messages( + "use-implicit-booleaness-not-comparison", + "use-implicit-booleaness-not-comparison-to-string", + "use-implicit-booleaness-not-comparison-to-zero", + ) def visit_compare(self, node: nodes.Compare) -> None: self._check_use_implicit_booleaness_not_comparison(node) self._check_compare_to_zero(node) + self._check_compare_to_string(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"] @@ -209,6 +222,47 @@ class ImplicitBooleanessChecker(checkers.BaseChecker): confidence=HIGH, ) + def _check_compare_to_string(self, node: nodes.Compare) -> None: + """Checks for comparisons to empty string. + + Most of the time you should use the fact that empty strings are false. + An exception to this rule is when an empty string value is allowed in the program + and has a different meaning than None! + """ + _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 | None]] = [("", node.left)] + ops.extend(node.ops) + iter_ops = iter(ops) + ops = list(itertools.chain(*iter_ops)) # type: ignore[arg-type] + for ops_idx in range(len(ops) - 2): + op_1: nodes.NodeNG | None = ops[ops_idx] + op_2: str = ops[ops_idx + 1] # type: ignore[assignment] + op_3: nodes.NodeNG | None = ops[ops_idx + 2] + error_detected = False + if op_1 is None or op_3 is None or op_2 not in _operators: + continue + node_name = "" + # x ?? "" + if utils.is_empty_str_literal(op_1): + error_detected = True + node_name = op_3.as_string() + # '' ?? X + elif utils.is_empty_str_literal(op_3): + error_detected = True + node_name = op_1.as_string() + if error_detected: + suggestion = f"not {node_name}" if op_2 in {"==", "is"} else node_name + self.add_message( + "compare-to-empty-string", + args=(node.as_string(), suggestion), + node=node, + confidence=HIGH, + ) + def _check_use_implicit_booleaness_not_comparison( self, node: nodes.Compare ) -> None: diff --git a/pylint/extensions/emptystring.py b/pylint/extensions/emptystring.py deleted file mode 100644 index 7053187b8..000000000 --- a/pylint/extensions/emptystring.py +++ /dev/null @@ -1,78 +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 empty string.""" - -from __future__ import annotations - -import itertools -from typing import TYPE_CHECKING - -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 - - -class CompareToEmptyStringChecker(checkers.BaseChecker): - name = "compare-to-empty-string" - msgs = { - "C1901": ( - '"%s" can be simplified to "%s" as an empty string is falsey', - "compare-to-empty-string", - "Used when Pylint detects comparison to an empty string constant.", - ) - } - - options = () - - @utils.only_required_for_messages("compare-to-empty-string") - def visit_compare(self, node: nodes.Compare) -> None: - """Checks for comparisons to empty string. - - Most of the time you should use the fact that empty strings are false. - An exception to this rule is when an empty string value is allowed in the program - and has a different meaning than None! - """ - _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 | None]] = [("", node.left)] - ops.extend(node.ops) - iter_ops = iter(ops) - ops = list(itertools.chain(*iter_ops)) # type: ignore[arg-type] - for ops_idx in range(len(ops) - 2): - op_1: nodes.NodeNG | None = ops[ops_idx] - op_2: str = ops[ops_idx + 1] # type: ignore[assignment] - op_3: nodes.NodeNG | None = ops[ops_idx + 2] - error_detected = False - if op_1 is None or op_3 is None or op_2 not in _operators: - continue - node_name = "" - # x ?? "" - if utils.is_empty_str_literal(op_1): - error_detected = True - node_name = op_3.as_string() - # '' ?? X - elif utils.is_empty_str_literal(op_3): - error_detected = True - node_name = op_1.as_string() - if error_detected: - suggestion = f"not {node_name}" if op_2 in {"==", "is"} else node_name - self.add_message( - "compare-to-empty-string", - args=(node.as_string(), suggestion), - node=node, - confidence=HIGH, - ) - - -def register(linter: PyLinter) -> None: - linter.register_checker(CompareToEmptyStringChecker(linter)) -- cgit v1.2.1