summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaehoon Hwang <jaehoonhwang@users.noreply.github.com>2021-10-17 00:31:34 -0700
committerGitHub <noreply@github.com>2021-10-17 09:31:34 +0200
commit59d2b545b557a7ac47955c65935889e296da941a (patch)
treea07b8d66954e630e5b8181d175fff60c423b93c7
parent9098c6078551e2d3028fe65537d3b0e407c85724 (diff)
downloadpylint-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--ChangeLog9
-rw-r--r--doc/whatsnew/2.12.rst12
-rw-r--r--pylint/checkers/base.py6
-rw-r--r--pylint/checkers/refactoring/implicit_booleaness_checker.py95
-rw-r--r--pylint/checkers/refactoring/refactoring_checker.py2
-rw-r--r--pylint/checkers/strings.py2
-rw-r--r--pylint/checkers/utils.py18
-rw-r--r--pylint/extensions/emptystring.py8
-rw-r--r--tests/checkers/unittest_spelling.py26
-rw-r--r--tests/checkers/unittest_utils.py22
-rw-r--r--tests/functional/c/consider/consider_iterating_dictionary.py2
-rw-r--r--tests/functional/c/consider/consider_using_in.py2
-rw-r--r--tests/functional/c/consider/consider_using_in.txt28
-rw-r--r--tests/functional/l/literal_comparison.py9
-rw-r--r--tests/functional/l/literal_comparison.txt25
-rw-r--r--tests/functional/u/use/use_implicit_booleaness_not_comparison.py142
-rw-r--r--tests/functional/u/use/use_implicit_booleaness_not_comparison.txt24
-rw-r--r--tests/test_check_parallel.py14
-rw-r--r--tests/test_pragma_parser.py2
19 files changed, 384 insertions, 64 deletions
diff --git a/ChangeLog b/ChangeLog
index 10af89334..029eee735 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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: