diff options
author | Jaehoon Hwang <jaehoonhwang@users.noreply.github.com> | 2021-10-17 00:31:34 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-17 09:31:34 +0200 |
commit | 59d2b545b557a7ac47955c65935889e296da941a (patch) | |
tree | a07b8d66954e630e5b8181d175fff60c423b93c7 | |
parent | 9098c6078551e2d3028fe65537d3b0e407c85724 (diff) | |
download | pylint-git-59d2b545b557a7ac47955c65935889e296da941a.tar.gz |
Add a warning ``use-implicit-booleaness-not-comparison`` for comparison with collection literals (#5120)
* Create a new checker; use-implicit-booleanness checker where it looks
for boolean evaluatiion with collection literals such as `()`, `[]`,
or `{}`
* Fixed invalid usage of comparison within pylint package
This closes #4774
* Ignore tuples when checking for `literal-comparison`
Closes #3031
* Merge len_checker with empty_literal checker
Moving empty literal checker with len_checker to avoid class without
iterators without boolean expressions (false positive on pandas)
Reference: https://github.com/PyCQA/pylint/pull/3821/files
* Update `len_checker` and its class `LenChecker` to `ComparisonChecker`
to reflect better usage after merging between `len_checker` and
`empty_literal_checker` and its tests.
* Fixed `consider_using_in` and `consider_iterating_dictionary` tests
that were failing due to new empty literal checkers.
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r-- | ChangeLog | 9 | ||||
-rw-r--r-- | doc/whatsnew/2.12.rst | 12 | ||||
-rw-r--r-- | pylint/checkers/base.py | 6 | ||||
-rw-r--r-- | pylint/checkers/refactoring/implicit_booleaness_checker.py | 95 | ||||
-rw-r--r-- | pylint/checkers/refactoring/refactoring_checker.py | 2 | ||||
-rw-r--r-- | pylint/checkers/strings.py | 2 | ||||
-rw-r--r-- | pylint/checkers/utils.py | 18 | ||||
-rw-r--r-- | pylint/extensions/emptystring.py | 8 | ||||
-rw-r--r-- | tests/checkers/unittest_spelling.py | 26 | ||||
-rw-r--r-- | tests/checkers/unittest_utils.py | 22 | ||||
-rw-r--r-- | tests/functional/c/consider/consider_iterating_dictionary.py | 2 | ||||
-rw-r--r-- | tests/functional/c/consider/consider_using_in.py | 2 | ||||
-rw-r--r-- | tests/functional/c/consider/consider_using_in.txt | 28 | ||||
-rw-r--r-- | tests/functional/l/literal_comparison.py | 9 | ||||
-rw-r--r-- | tests/functional/l/literal_comparison.txt | 25 | ||||
-rw-r--r-- | tests/functional/u/use/use_implicit_booleaness_not_comparison.py | 142 | ||||
-rw-r--r-- | tests/functional/u/use/use_implicit_booleaness_not_comparison.txt | 24 | ||||
-rw-r--r-- | tests/test_check_parallel.py | 14 | ||||
-rw-r--r-- | tests/test_pragma_parser.py | 2 |
19 files changed, 384 insertions, 64 deletions
@@ -47,6 +47,15 @@ Release date: TBA * Use ``py-version`` setting for alternative union syntax check (PEP 604), instead of the Python interpreter version. +* Added new checker ``use-implicit-booleaness-not-comparison``: Emitted when + collection literal comparison is being used to check for emptiness. + + Closes #4774 + +* Update ``literal-comparison``` checker to ignore tuple literals + + Closes #3031 + What's New in Pylint 2.11.2? ============================ diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst index ec4817eed..54214ac99 100644 --- a/doc/whatsnew/2.12.rst +++ b/doc/whatsnew/2.12.rst @@ -33,6 +33,14 @@ New checkers * Added ``using-f-string-in-unsupported-version`` checker. Issued when ``py-version`` is set to a version that does not support f-strings (< 3.6) +* Added new checker `use-implicit-booleanness``: Emitted when using collection + literals for boolean comparisons + +* Added new checker ``use-implicit-booleaness-not-comparison``: Emitted when + collection literal comparison is being used to check for emptiness. + + Closes #4774 + Removed checkers ================ @@ -53,6 +61,10 @@ Other Changes ``use-implicit-booleaness-not-len`` in order to be consistent with ``use-implicit-booleaness-not-comparison``. +* Update ``literal-comparison``` checker to ignore tuple literals + + Closes #3031 + * ``undefined-variable`` now correctly flags variables which only receive a type annotations and never get assigned a value diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 36b50186b..1c67e2fef 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -2462,11 +2462,9 @@ class ComparisonChecker(_BasicChecker): args=(f"'{root_node.as_string()}'", suggestion), ) - def _check_literal_comparison(self, literal, node): + def _check_literal_comparison(self, literal, node: nodes.Compare): """Check if we compare to a literal, which is usually what we do not want to do.""" - is_other_literal = isinstance( - literal, (nodes.List, nodes.Tuple, nodes.Dict, nodes.Set) - ) + is_other_literal = isinstance(literal, (nodes.List, nodes.Dict, nodes.Set)) is_const = False if isinstance(literal, nodes.Const): if isinstance(literal.value, bool) or literal.value is None: diff --git a/pylint/checkers/refactoring/implicit_booleaness_checker.py b/pylint/checkers/refactoring/implicit_booleaness_checker.py index 98605635c..b5d0b277f 100644 --- a/pylint/checkers/refactoring/implicit_booleaness_checker.py +++ b/pylint/checkers/refactoring/implicit_booleaness_checker.py @@ -10,7 +10,9 @@ from pylint.checkers import utils class ImplicitBooleanessChecker(checkers.BaseChecker): - """Checks for incorrect usage of len() inside conditions. + """Checks for incorrect usage of comparisons or len() inside conditions. + + Incorrect usage of len() Pep8 states: For sequences, (strings, lists, tuples), use the fact that empty sequences are false. @@ -30,6 +32,20 @@ class ImplicitBooleanessChecker(checkers.BaseChecker): * assert len(sequence): * assert not len(sequence): * bool(len(sequence)) + + Incorrect usage of empty literal sequences; (), [], {}, + + For empty sequences, (dicts, lists, tuples), use the fact that empty sequences are false. + + Yes: if variable: + if not variable + + No: if variable == empty_literal: + if variable != empty_literal: + + Problems detected: + * comparison such as variable == empty_literal: + * comparison such as variable != empty_literal: """ __implements__ = (interfaces.IAstroidChecker,) @@ -47,6 +63,13 @@ class ImplicitBooleanessChecker(checkers.BaseChecker): "compare the length against a scalar.", {"old_names": [("C1801", "len-as-condition")]}, ), + "C1803": ( + "'%s' can be simplified to '%s' as an empty sequence is falsey", + "use-implicit-booleaness-not-comparison", + "Used when Pylint detects that collection literal comparison is being " + "used to check for emptiness; Use implicit booleaness instead" + "of a collection classes; empty collections are considered as false", + ), } priority = -2 @@ -114,6 +137,76 @@ class ImplicitBooleanessChecker(checkers.BaseChecker): ): self.add_message("use-implicit-booleaness-not-len", node=node) + @utils.check_messages("use-implicit-booleaness-not-comparison") + def visit_compare(self, node: nodes.Compare) -> None: + self._check_use_implicit_booleaness_not_comparison(node) + + def _check_use_implicit_booleaness_not_comparison( + self, node: nodes.Compare + ) -> None: + """Check for left side and right side of the node for empty literals""" + is_left_empty_literal = ( + utils.is_empty_list_literal(node.left) + or utils.is_empty_tuple_literal(node.left) + or utils.is_empty_dict_literal(node.left) + ) + + # Check both left hand side and right hand side for literals + for operator, comparator in node.ops: + is_right_empty_literal = ( + utils.is_empty_list_literal(comparator) + or utils.is_empty_tuple_literal(comparator) + or utils.is_empty_dict_literal(comparator) + ) + # Using Exclusive OR (XOR) to compare between two side. + # If two sides are both literal, it should be different error. + if is_right_empty_literal ^ is_left_empty_literal: + # set target_node to opposite side of literal + target_node = node.left if is_right_empty_literal else comparator + literal_node = comparator if is_right_empty_literal else node.left + # Infer node to check + try: + target_instance = next(target_node.infer()) + except astroid.InferenceError: + # Probably undefined-variable, continue with check + continue + mother_classes = self.base_classes_of_node(target_instance) + is_base_comprehension_type = any( + t in mother_classes for t in ("tuple", "list", "dict") + ) + + # Only time we bypass check is when target_node is not inherited by + # collection literals and have its own __bool__ implementation. + if not is_base_comprehension_type and self.instance_has_bool( + target_instance + ): + continue + + # No need to check for operator when visiting compare node + if operator in ["==", "!=", ">=", ">", "<=", "<"]: + collection_literal = "{}" + if isinstance(literal_node, nodes.List): + collection_literal = "[]" + if isinstance(literal_node, nodes.Tuple): + collection_literal = "()" + variable_name = target_node.name + original_comparison = ( + f"{variable_name} {operator} {collection_literal}" + ) + suggestion = ( + f"not {variable_name}" + if operator == "!=" + else f"{variable_name}" + ) + self.add_message( + "use-implicit-booleaness-not-comparison", + args=( + original_comparison, + suggestion, + ), + node=node, + ) + @staticmethod def base_classes_of_node(instance: nodes.ClassDef) -> List[nodes.Name]: """Return all the classes names that a ClassDef inherit from including 'object'.""" diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index c6a9024fb..4c7842d8b 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -1580,7 +1580,7 @@ class RefactoringChecker(checkers.BaseTokenChecker): ) else: return - if expr_list == target_list != []: + if expr_list == target_list and expr_list: args: Optional[Tuple[str]] = None inferred = utils.safe_infer(node.iter) if isinstance(node.parent, nodes.DictComp) and isinstance( diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index 45b23eee5..cc3ecce89 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -513,7 +513,7 @@ class StringFormatChecker(BaseChecker): # num_args can be 0 if manual_pos is not. num_args = num_args or manual_pos if positional_arguments or num_args: - empty = any(True for field in named_fields if field == "") + empty = any(field == "" for field in named_fields) if named_arguments or empty: # Verify the required number of positional arguments # only if the .format got at least one keyword argument. diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 1cc74d49c..0fee05af9 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1624,3 +1624,21 @@ def is_function_body_ellipsis(node: nodes.FunctionDef) -> bool: and isinstance(node.body[0].value, nodes.Const) and node.body[0].value.value == Ellipsis ) + + +def is_empty_list_literal(node: Optional[nodes.NodeNG]) -> bool: + return isinstance(node, nodes.List) and not node.elts + + +def is_empty_tuple_literal(node: Optional[nodes.NodeNG]) -> bool: + return isinstance(node, nodes.Tuple) and not node.elts + + +def is_empty_dict_literal(node: Optional[nodes.NodeNG]) -> bool: + return isinstance(node, nodes.Dict) and not node.items + + +def is_empty_str_literal(node: Optional[nodes.NodeNG]) -> bool: + return ( + isinstance(node, nodes.Const) and isinstance(node.value, str) and not node.value + ) diff --git a/pylint/extensions/emptystring.py b/pylint/extensions/emptystring.py index b54bffdfe..2e50dd96f 100644 --- a/pylint/extensions/emptystring.py +++ b/pylint/extensions/emptystring.py @@ -20,10 +20,6 @@ from pylint import checkers, interfaces from pylint.checkers import utils -def _is_constant_empty_str(node): - return isinstance(node, nodes.Const) and node.value == "" - - class CompareToEmptyStringChecker(checkers.BaseChecker): """Checks for comparisons to empty string. Most of the times you should use the fact that empty strings are false. @@ -65,10 +61,10 @@ class CompareToEmptyStringChecker(checkers.BaseChecker): error_detected = False # x ?? "" - if _is_constant_empty_str(op_1) and op_2 in _operators: + if utils.is_empty_str_literal(op_1) and op_2 in _operators: error_detected = True # '' ?? X - elif op_2 in _operators and _is_constant_empty_str(op_3): + elif op_2 in _operators and utils.is_empty_str_literal(op_3): error_detected = True if error_detected: diff --git a/tests/checkers/unittest_spelling.py b/tests/checkers/unittest_spelling.py index 509cb502f..f9c335006 100644 --- a/tests/checkers/unittest_spelling.py +++ b/tests/checkers/unittest_spelling.py @@ -135,30 +135,30 @@ class TestSpellingChecker(CheckerTestCase): # pylint:disable=too-many-public-me @set_config(spelling_dict=spell_dict) def test_skip_shebangs(self): self.checker.process_tokens(_tokenize_str("#!/usr/bin/env python")) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) def test_skip_python_coding_comments(self): self.checker.process_tokens(_tokenize_str("# -*- coding: utf-8 -*-")) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() self.checker.process_tokens(_tokenize_str("# coding=utf-8")) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() self.checker.process_tokens(_tokenize_str("# vim: set fileencoding=utf-8 :")) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() # Now with a shebang first self.checker.process_tokens( _tokenize_str("#!/usr/bin/env python\n# -*- coding: utf-8 -*-") ) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() self.checker.process_tokens( _tokenize_str("#!/usr/bin/env python\n# coding=utf-8") ) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() self.checker.process_tokens( _tokenize_str("#!/usr/bin/env python\n# vim: set fileencoding=utf-8 :") ) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) @@ -166,13 +166,13 @@ class TestSpellingChecker(CheckerTestCase): # pylint:disable=too-many-public-me self.checker.process_tokens( _tokenize_str("# Line 1\n Line 2\n# pylint: disable=ungrouped-imports") ) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) def test_skip_words_with_numbers(self): self.checker.process_tokens(_tokenize_str("\n# 0ne\n# Thr33\n# Sh3ll")) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) @@ -244,7 +244,7 @@ class TestSpellingChecker(CheckerTestCase): # pylint:disable=too-many-public-me f'class TestClass(object):\n """{ccn} comment"""\n pass' ) self.checker.visit_classdef(stmt) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) @@ -253,19 +253,19 @@ class TestSpellingChecker(CheckerTestCase): # pylint:disable=too-many-public-me 'def fff(param_name):\n """test param_name"""\n pass' ) self.checker.visit_functiondef(stmt) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) def test_skip_email_address(self): self.checker.process_tokens(_tokenize_str("# uname@domain.tld")) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) def test_skip_urls(self): self.checker.process_tokens(_tokenize_str("# https://github.com/rfk/pyenchant")) - assert self.linter.release_messages() == [] + assert not self.linter.release_messages() @skip_on_missing_package_or_dict @set_config(spelling_dict=spell_dict) diff --git a/tests/checkers/unittest_utils.py b/tests/checkers/unittest_utils.py index 583bf3823..cb10d4fce 100644 --- a/tests/checkers/unittest_utils.py +++ b/tests/checkers/unittest_utils.py @@ -463,3 +463,25 @@ def test_if_typing_guard() -> None: assert isinstance(code[3], nodes.If) assert utils.is_typing_guard(code[3]) is False + + +def test_is_empty_literal() -> None: + list_node = astroid.extract_node("a = []") + assert utils.is_empty_list_literal(list_node.value) + not_empty_list_node = astroid.extract_node("a = [1,2,3]") + assert not utils.is_empty_list_literal(not_empty_list_node.value) + + tuple_node = astroid.extract_node("a = ()") + assert utils.is_empty_tuple_literal(tuple_node.value) + not_empty_tuple_node = astroid.extract_node("a = (1,2)") + assert not utils.is_empty_tuple_literal(not_empty_tuple_node.value) + + dict_node = astroid.extract_node("a = {}") + assert utils.is_empty_dict_literal(dict_node.value) + not_empty_dict_node = astroid.extract_node("a = {1:1}") + assert not utils.is_empty_dict_literal(not_empty_dict_node.value) + + string_node = astroid.extract_node("a = ''") + assert utils.is_empty_str_literal(string_node.value) + not_empty_string_node = astroid.extract_node("a = 'hello'") + assert not utils.is_empty_str_literal(not_empty_string_node.value) diff --git a/tests/functional/c/consider/consider_iterating_dictionary.py b/tests/functional/c/consider/consider_iterating_dictionary.py index 996fa5429..d3cc75f4d 100644 --- a/tests/functional/c/consider/consider_iterating_dictionary.py +++ b/tests/functional/c/consider/consider_iterating_dictionary.py @@ -1,6 +1,6 @@ # pylint: disable=missing-docstring, expression-not-assigned, too-few-public-methods # pylint: disable=no-member, import-error, no-self-use, line-too-long, useless-object-inheritance -# pylint: disable=unnecessary-comprehension, use-dict-literal +# pylint: disable=unnecessary-comprehension, use-dict-literal, use-implicit-booleaness-not-comparison from unknown import Unknown diff --git a/tests/functional/c/consider/consider_using_in.py b/tests/functional/c/consider/consider_using_in.py index 8d23bb093..7ce5dd0b6 100644 --- a/tests/functional/c/consider/consider_using_in.py +++ b/tests/functional/c/consider/consider_using_in.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring, invalid-name, pointless-statement, misplaced-comparison-constant, undefined-variable, literal-comparison, line-too-long, unneeded-not, too-few-public-methods +# pylint: disable=missing-docstring, invalid-name, pointless-statement, misplaced-comparison-constant, undefined-variable, literal-comparison, line-too-long, unneeded-not, too-few-public-methods, use-implicit-booleaness-not-comparison value = value1 = 1 value2 = 2 diff --git a/tests/functional/c/consider/consider_using_in.txt b/tests/functional/c/consider/consider_using_in.txt index 72c8e389d..427c0ee04 100644 --- a/tests/functional/c/consider/consider_using_in.txt +++ b/tests/functional/c/consider/consider_using_in.txt @@ -1,14 +1,14 @@ -consider-using-in:10:0::"Consider merging these comparisons with ""in"" to 'value in (1,)'" -consider-using-in:11:0::"Consider merging these comparisons with ""in"" to 'value in (1, 2)'" -consider-using-in:12:0::"Consider merging these comparisons with ""in"" to ""value in ('value',)""" -consider-using-in:13:0::"Consider merging these comparisons with ""in"" to 'value in (1, undef_value)'" -consider-using-in:14:0::"Consider merging these comparisons with ""in"" to 'value in (1, 2, 3)'" -consider-using-in:15:0::"Consider merging these comparisons with ""in"" to ""value in ('2', 1)""" -consider-using-in:16:0::"Consider merging these comparisons with ""in"" to 'value in (1, 2)'" -consider-using-in:17:0::"Consider merging these comparisons with ""in"" to 'value in (1, 2)'" -consider-using-in:18:0::"Consider merging these comparisons with ""in"" to 'value in (1, a_list)'" -consider-using-in:19:0::"Consider merging these comparisons with ""in"" to 'value in (a_set, a_list, a_str)'" -consider-using-in:20:0::"Consider merging these comparisons with ""in"" to 'value not in (1, 2)'" -consider-using-in:21:0::"Consider merging these comparisons with ""in"" to 'value1 in (value2,)'" -consider-using-in:22:0::"Consider merging these comparisons with ""in"" to 'a_list in ([1, 2, 3], [])'" -consider-using-in:53:0::"Consider merging these comparisons with ""in"" to 'A.value in (1, 2)'" +consider-using-in:10:0::"Consider merging these comparisons with ""in"" to 'value in (1,)'":HIGH +consider-using-in:11:0::"Consider merging these comparisons with ""in"" to 'value in (1, 2)'":HIGH +consider-using-in:12:0::"Consider merging these comparisons with ""in"" to ""value in ('value',)""":HIGH +consider-using-in:13:0::"Consider merging these comparisons with ""in"" to 'value in (1, undef_value)'":HIGH +consider-using-in:14:0::"Consider merging these comparisons with ""in"" to 'value in (1, 2, 3)'":HIGH +consider-using-in:15:0::"Consider merging these comparisons with ""in"" to ""value in ('2', 1)""":HIGH +consider-using-in:16:0::"Consider merging these comparisons with ""in"" to 'value in (1, 2)'":HIGH +consider-using-in:17:0::"Consider merging these comparisons with ""in"" to 'value in (1, 2)'":HIGH +consider-using-in:18:0::"Consider merging these comparisons with ""in"" to 'value in (1, a_list)'":HIGH +consider-using-in:19:0::"Consider merging these comparisons with ""in"" to 'value in (a_set, a_list, a_str)'":HIGH +consider-using-in:20:0::"Consider merging these comparisons with ""in"" to 'value not in (1, 2)'":HIGH +consider-using-in:21:0::"Consider merging these comparisons with ""in"" to 'value1 in (value2,)'":HIGH +consider-using-in:22:0::"Consider merging these comparisons with ""in"" to 'a_list in ([1, 2, 3], [])'":HIGH +consider-using-in:53:0::"Consider merging these comparisons with ""in"" to 'A.value in (1, 2)'":HIGH diff --git a/tests/functional/l/literal_comparison.py b/tests/functional/l/literal_comparison.py index 646e57eec..4e41caaa4 100644 --- a/tests/functional/l/literal_comparison.py +++ b/tests/functional/l/literal_comparison.py @@ -10,7 +10,7 @@ if "a" is b"a": # [literal-comparison] if 2.0 is 3.0: # [literal-comparison] pass -if () is (1, 2, 3): # [literal-comparison] +if () is (1, 2, 3): pass if () is {1:2, 2:3}: # [literal-comparison] @@ -48,3 +48,10 @@ if CONST is 42: # [literal-comparison] # object might be used as a sentinel. if () is CONST: pass + +def github_issue_3031(arg=()): + if arg is (): + pass + + if arg is not (): + pass diff --git a/tests/functional/l/literal_comparison.txt b/tests/functional/l/literal_comparison.txt index 541bfe515..30d54a285 100644 --- a/tests/functional/l/literal_comparison.txt +++ b/tests/functional/l/literal_comparison.txt @@ -1,13 +1,12 @@ -literal-comparison:4:3::Comparison to literal -literal-comparison:7:3::Comparison to literal -literal-comparison:10:3::Comparison to literal -literal-comparison:13:3::Comparison to literal -literal-comparison:16:3::Comparison to literal -literal-comparison:19:3::Comparison to literal -literal-comparison:22:3::Comparison to literal -literal-comparison:25:3::Comparison to literal -literal-comparison:28:3::Comparison to literal -literal-comparison:31:3::Comparison to literal -literal-comparison:38:3::Comparison to literal -literal-comparison:41:3::Comparison to literal -literal-comparison:44:3::Comparison to literal +literal-comparison:4:3::Comparison to literal:HIGH +literal-comparison:7:3::Comparison to literal:HIGH +literal-comparison:10:3::Comparison to literal:HIGH +literal-comparison:16:3::Comparison to literal:HIGH +literal-comparison:19:3::Comparison to literal:HIGH +literal-comparison:22:3::Comparison to literal:HIGH +literal-comparison:25:3::Comparison to literal:HIGH +literal-comparison:28:3::Comparison to literal:HIGH +literal-comparison:31:3::Comparison to literal:HIGH +literal-comparison:38:3::Comparison to literal:HIGH +literal-comparison:41:3::Comparison to literal:HIGH +literal-comparison:44:3::Comparison to literal:HIGH diff --git a/tests/functional/u/use/use_implicit_booleaness_not_comparison.py b/tests/functional/u/use/use_implicit_booleaness_not_comparison.py new file mode 100644 index 000000000..18ca9c370 --- /dev/null +++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison.py @@ -0,0 +1,142 @@ +# pylint: disable=missing-docstring, missing-module-docstring, invalid-name +# pylint: disable=too-few-public-methods, line-too-long, dangerous-default-value +# https://github.com/PyCQA/pylint/issues/4774 + +def github_issue_4774(): + # Test literals + # https://github.com/PyCQA/pylint/issues/4774 + good_list = [] + if not good_list: + pass + + bad_list = [] + if bad_list == []: # [use-implicit-booleaness-not-comparison] + pass + +# Testing for empty literals +empty_tuple = () +empty_list = [] +empty_dict = {} + +if empty_tuple == (): # [use-implicit-booleaness-not-comparison] + pass + +if empty_list == []: # [use-implicit-booleaness-not-comparison] + pass + +if empty_dict == {}: # [use-implicit-booleaness-not-comparison] + pass + +if () == empty_tuple: # [use-implicit-booleaness-not-comparison] + pass + +if [] == empty_list: # [use-implicit-booleaness-not-comparison] + pass + +if {} == empty_dict: # [use-implicit-booleaness-not-comparison] + pass + +def bad_tuple_return(): + t = (1, ) + return t == () # [use-implicit-booleaness-not-comparison] + +def bad_list_return(): + b = [1] + return b == [] # [use-implicit-booleaness-not-comparison] + +def bad_dict_return(): + c = {1: 1} + return c == {} # [use-implicit-booleaness-not-comparison] + +assert () == empty_tuple # [use-implicit-booleaness-not-comparison] +assert [] == empty_list # [use-implicit-booleaness-not-comparison] +assert {} != empty_dict # [use-implicit-booleaness-not-comparison] +assert () < empty_tuple # [use-implicit-booleaness-not-comparison] +assert [] <= empty_list # [use-implicit-booleaness-not-comparison] +assert () > empty_tuple # [use-implicit-booleaness-not-comparison] +assert [] >= empty_list # [use-implicit-booleaness-not-comparison] + +assert [] == [] +assert {} != {} +assert () == () + +d = {} + +if d in {}: + pass + +class NoBool: + def __init__(self): + self.a = 2 + +class YesBool: + def __init__(self): + self.a = True + + def __bool__(self): + return self.a + + +# Should be triggered +a = NoBool() +if [] == a: # [use-implicit-booleaness-not-comparison] + pass + +a = YesBool() +if a == []: + pass + +# compound test cases + +e = [] +f = {} + +if e == [] and f == {}: # [use-implicit-booleaness-not-comparison, use-implicit-booleaness-not-comparison] + pass + + +named_fields = [0, "", "42", "forty two"] +empty = any(field == "" for field in named_fields) + +something_else = NoBool() +empty_literals = [[], {}, ()] +is_empty = any(field == something_else for field in empty_literals) + +# this should work, but it doesn't since, input parameter only get the latest one, not all when inferred() +h, i, j = 1, None, [1,2,3] + +def test(k): + print(k == {}) + +def test_with_default(k={}): + print(k == {}) # [use-implicit-booleaness-not-comparison] + print(k == 1) + +test(h) +test(i) +test(j) + +test_with_default(h) +test_with_default(i) +test_with_default(j) + +# pylint: disable=import-outside-toplevel, wrong-import-position, import-error +# Numpy has its own implementation of __bool__, but base class has list, that's why the comparison check is happening +import numpy +numpy_array = numpy.array([0]) +if numpy_array == []: # [use-implicit-booleaness-not-comparison] + print('numpy_array') +if numpy_array != []: # [use-implicit-booleaness-not-comparison] + print('numpy_array') +if numpy_array >= (): # [use-implicit-booleaness-not-comparison] + print('b') + +# pandas has its own implementations of __bool__ and is not subclass of list, dict, or tuple; that's why comparison check is not happening +import pandas as pd +pandas_df = pd.DataFrame() +if pandas_df == []: + pass +if pandas_df != (): + pass +if pandas_df <= []: + print("truth value of a dataframe is ambiguous") diff --git a/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt b/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt new file mode 100644 index 000000000..1baca0b42 --- /dev/null +++ b/tests/functional/u/use/use_implicit_booleaness_not_comparison.txt @@ -0,0 +1,24 @@ +use-implicit-booleaness-not-comparison:13:7:github_issue_4774:'bad_list == []' can be simplified to 'bad_list' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:21:3::'empty_tuple == ()' can be simplified to 'empty_tuple' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:24:3::'empty_list == []' can be simplified to 'empty_list' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:27:3::'empty_dict == {}' can be simplified to 'empty_dict' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:30:3::'empty_tuple == ()' can be simplified to 'empty_tuple' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:33:3::'empty_list == []' can be simplified to 'empty_list' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:36:3::'empty_dict == {}' can be simplified to 'empty_dict' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:41:11:bad_tuple_return:'t == ()' can be simplified to 't' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:45:11:bad_list_return:'b == []' can be simplified to 'b' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:49:11:bad_dict_return:'c == {}' can be simplified to 'c' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:51:7::'empty_tuple == ()' can be simplified to 'empty_tuple' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:52:7::'empty_list == []' can be simplified to 'empty_list' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:53:7::'empty_dict != {}' can be simplified to 'not empty_dict' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:54:7::'empty_tuple < ()' can be simplified to 'empty_tuple' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:55:7::'empty_list <= []' can be simplified to 'empty_list' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:56:7::'empty_tuple > ()' can be simplified to 'empty_tuple' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:57:7::'empty_list >= []' can be simplified to 'empty_list' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:82:3::'a == []' can be simplified to 'a' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:94:3::'e == []' can be simplified to 'e' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:94:15::'f == {}' can be simplified to 'f' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:112:10:test_with_default:'k == {}' can be simplified to 'k' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:127:3::'numpy_array == []' can be simplified to 'numpy_array' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:129:3::'numpy_array != []' can be simplified to 'not numpy_array' as an empty sequence is falsey:HIGH +use-implicit-booleaness-not-comparison:131:3::'numpy_array >= ()' can be simplified to 'numpy_array' as an empty sequence is falsey:HIGH diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index be250d892..32626d902 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -197,7 +197,7 @@ class TestCheckParallelFramework: _, # mapreduce_data ) = worker_check_single_file(_gen_file_data()) assert name == "--test-file_data-name-0--" - assert [] == msgs + assert not msgs no_errors_status = 0 assert no_errors_status == msg_status assert { @@ -211,7 +211,7 @@ class TestCheckParallelFramework: "warning": 0, } } == stats.by_module - assert {} == stats.by_msg + assert not stats.by_msg assert stats.convention == 0 assert stats.error == 0 assert stats.fatal == 0 @@ -241,7 +241,7 @@ class TestCheckParallelFramework: # Ensure we return the same data as the single_file_no_checkers test assert name == "--test-file_data-name-0--" - assert [] == msgs + assert not msgs no_errors_status = 0 assert no_errors_status == msg_status assert { @@ -255,7 +255,7 @@ class TestCheckParallelFramework: "warning": 0, } } == stats.by_module - assert {} == stats.by_msg + assert not stats.by_msg assert stats.convention == 0 assert stats.error == 0 assert stats.fatal == 0 @@ -304,7 +304,7 @@ class TestCheckParallel: "warning": 0, } } == linter.stats.by_module - assert linter.stats.by_msg == {} + assert not linter.stats.by_msg assert linter.stats.convention == 0 assert linter.stats.error == 0 assert linter.stats.fatal == 0 @@ -328,7 +328,7 @@ class TestCheckParallel: "warning": 0, } } == linter.stats.by_module - assert linter.stats.by_msg == {} + assert not linter.stats.by_msg assert linter.stats.convention == 0 assert linter.stats.error == 0 assert linter.stats.fatal == 0 @@ -366,7 +366,7 @@ class TestCheckParallel: "warning": 0, } } == linter.stats.by_module - assert linter.stats.by_msg == {} + assert not linter.stats.by_msg assert linter.stats.convention == 0 assert linter.stats.error == 0 assert linter.stats.fatal == 0 diff --git a/tests/test_pragma_parser.py b/tests/test_pragma_parser.py index 473f2048b..7bf00c7a3 100644 --- a/tests/test_pragma_parser.py +++ b/tests/test_pragma_parser.py @@ -32,7 +32,7 @@ def test_simple_pragma_no_messages() -> None: assert match for pragma_repr in parse_pragma(match.group(2)): assert pragma_repr.action == "skip-file" - assert pragma_repr.messages == [] + assert not pragma_repr.messages def test_simple_pragma_multiple_messages() -> None: |