aboutsummaryrefslogtreecommitdiff
path: root/tests/functional/c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/functional/c')
-rw-r--r--tests/functional/c/__init__.py0
-rw-r--r--tests/functional/c/cached_property.py23
-rw-r--r--tests/functional/c/cached_property.rc2
-rw-r--r--tests/functional/c/cached_property.txt1
-rw-r--r--tests/functional/c/cellvar_escaping_loop.py219
-rw-r--r--tests/functional/c/cellvar_escaping_loop.txt13
-rw-r--r--tests/functional/c/class_attributes.py32
-rw-r--r--tests/functional/c/class_members.py13
-rw-r--r--tests/functional/c/class_members_py30.py68
-rw-r--r--tests/functional/c/class_members_py30.txt7
-rw-r--r--tests/functional/c/class_scope.py43
-rw-r--r--tests/functional/c/class_scope.txt7
-rw-r--r--tests/functional/c/class_variable_slots_conflict_exempted.py4
-rw-r--r--tests/functional/c/classes_meth_could_be_a_function.py33
-rw-r--r--tests/functional/c/classes_protected_member_access.py26
-rw-r--r--tests/functional/c/comparison_with_callable.py60
-rw-r--r--tests/functional/c/comparison_with_callable.txt4
-rw-r--r--tests/functional/c/condition_evals_to_constant.py46
-rw-r--r--tests/functional/c/condition_evals_to_constant.txt15
-rw-r--r--tests/functional/c/confidence_filter.py16
-rw-r--r--tests/functional/c/confidence_filter.rc3
-rw-r--r--tests/functional/c/confidence_filter.txt1
-rw-r--r--tests/functional/c/confusing_with_statement.py27
-rw-r--r--tests/functional/c/confusing_with_statement.txt1
-rw-r--r--tests/functional/c/consider/consider_iterating_dictionary.py68
-rw-r--r--tests/functional/c/consider/consider_iterating_dictionary.txt18
-rw-r--r--tests/functional/c/consider/consider_join.py126
-rw-r--r--tests/functional/c/consider/consider_join.txt11
-rw-r--r--tests/functional/c/consider/consider_merging_isinstance.py37
-rw-r--r--tests/functional/c/consider/consider_merging_isinstance.txt7
-rw-r--r--tests/functional/c/consider/consider_swap_variables.py25
-rw-r--r--tests/functional/c/consider/consider_swap_variables.txt2
-rw-r--r--tests/functional/c/consider/consider_using_dict_comprehension.py12
-rw-r--r--tests/functional/c/consider/consider_using_dict_comprehension.txt1
-rw-r--r--tests/functional/c/consider/consider_using_dict_items.py105
-rw-r--r--tests/functional/c/consider/consider_using_dict_items.txt16
-rw-r--r--tests/functional/c/consider/consider_using_enumerate.py85
-rw-r--r--tests/functional/c/consider/consider_using_enumerate.txt5
-rw-r--r--tests/functional/c/consider/consider_using_f_string.py107
-rw-r--r--tests/functional/c/consider/consider_using_f_string.txt30
-rw-r--r--tests/functional/c/consider/consider_using_generator.py11
-rw-r--r--tests/functional/c/consider/consider_using_generator.txt2
-rw-r--r--tests/functional/c/consider/consider_using_get.py90
-rw-r--r--tests/functional/c/consider/consider_using_get.txt4
-rw-r--r--tests/functional/c/consider/consider_using_in.py53
-rw-r--r--tests/functional/c/consider/consider_using_in.txt14
-rw-r--r--tests/functional/c/consider/consider_using_min_max_builtin.py107
-rw-r--r--tests/functional/c/consider/consider_using_min_max_builtin.txt11
-rw-r--r--tests/functional/c/consider/consider_using_set_comprehension.py9
-rw-r--r--tests/functional/c/consider/consider_using_set_comprehension.txt1
-rw-r--r--tests/functional/c/consider/consider_using_sys_exit.py14
-rw-r--r--tests/functional/c/consider/consider_using_sys_exit.txt3
-rw-r--r--tests/functional/c/consider/consider_using_sys_exit_exempted.py5
-rw-r--r--tests/functional/c/consider/consider_using_sys_exit_local_scope.py5
-rw-r--r--tests/functional/c/consider/consider_using_with.py231
-rw-r--r--tests/functional/c/consider/consider_using_with.txt26
-rw-r--r--tests/functional/c/consider/consider_using_with_open.py161
-rw-r--r--tests/functional/c/consider/consider_using_with_open.rc2
-rw-r--r--tests/functional/c/consider/consider_using_with_open.txt6
-rw-r--r--tests/functional/c/continue_in_finally.py23
-rw-r--r--tests/functional/c/continue_in_finally.rc2
-rw-r--r--tests/functional/c/continue_in_finally.txt1
-rw-r--r--tests/functional/c/control_pragmas.py19
-rw-r--r--tests/functional/c/crash_missing_module_type.py18
-rw-r--r--tests/functional/c/ctor_arguments.py104
-rw-r--r--tests/functional/c/ctor_arguments.txt24
66 files changed, 2265 insertions, 0 deletions
diff --git a/tests/functional/c/__init__.py b/tests/functional/c/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/functional/c/__init__.py
diff --git a/tests/functional/c/cached_property.py b/tests/functional/c/cached_property.py
new file mode 100644
index 000000000..880015eb1
--- /dev/null
+++ b/tests/functional/c/cached_property.py
@@ -0,0 +1,23 @@
+# pylint: disable=missing-docstring,invalid-name,no-self-use
+from functools import cached_property
+
+
+# https://github.com/PyCQA/pylint/issues/4023
+# False-positive 'invalid-overridden-method' with 'cached_property'
+class Parent:
+ @property
+ def value(self):
+ return 42
+
+ def func(self):
+ return False
+
+
+class Child(Parent):
+ @cached_property
+ def value(self):
+ return 2**6
+
+ @cached_property
+ def func(self): # [invalid-overridden-method]
+ return 42
diff --git a/tests/functional/c/cached_property.rc b/tests/functional/c/cached_property.rc
new file mode 100644
index 000000000..85fc502b3
--- /dev/null
+++ b/tests/functional/c/cached_property.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.8
diff --git a/tests/functional/c/cached_property.txt b/tests/functional/c/cached_property.txt
new file mode 100644
index 000000000..6c3a6d107
--- /dev/null
+++ b/tests/functional/c/cached_property.txt
@@ -0,0 +1 @@
+invalid-overridden-method:22:4:Child.func:Method 'func' was expected to be 'method', found it instead as 'property'
diff --git a/tests/functional/c/cellvar_escaping_loop.py b/tests/functional/c/cellvar_escaping_loop.py
new file mode 100644
index 000000000..5520fe949
--- /dev/null
+++ b/tests/functional/c/cellvar_escaping_loop.py
@@ -0,0 +1,219 @@
+# pylint: disable= unnecessary-comprehension,missing-docstring,too-few-public-methods
+"""Tests for loopvar-in-closure."""
+from __future__ import print_function
+
+from enum import Enum
+
+
+def good_case():
+ """No problems here."""
+ lst = []
+ for i in range(10):
+ lst.append(i)
+
+
+def good_case2():
+ """No problems here."""
+ return [i for i in range(10)]
+
+
+def good_case3():
+ """No problems here."""
+ lst = []
+ for i in range(10):
+ lst.append(lambda i=i: i)
+
+
+def good_case4():
+ """No problems here."""
+ lst = []
+ for i in range(10):
+ print(i)
+ lst.append(lambda i: i)
+
+
+def good_case5():
+ """No problems here."""
+ return (i for i in range(10))
+
+
+def good_case6():
+ """Accept use of the variable inside return."""
+ for i in range(10):
+ if i == 8:
+ return lambda: i
+ return lambda: -1
+
+
+def good_case7():
+ """Lambda defined and called in loop."""
+ for i in range(10):
+ print((lambda x: i + x)(1))
+
+
+def good_case8():
+ """Another eager binding of the cell variable."""
+ funs = []
+ for i in range(10):
+ def func(bound_i=i):
+ """Ignore."""
+ return bound_i
+ funs.append(func)
+ return funs
+
+
+def good_case9():
+ """Ignore when the cell var is not defined in a loop"""
+ i = 10
+ lst = []
+ for _ in range(10):
+ lst.append(lambda: i)
+ return lst
+
+
+def good_case10():
+ """Ignore when a loop variable is showdowed by an inner function"""
+ lst = []
+ for i in range(10): # pylint: disable=unused-variable
+ def func():
+ i = 100
+ def func2(arg=i):
+ return arg
+
+ return func2
+
+ lst.append(func)
+ return lst
+
+
+def good_case_issue3107():
+ """Eager binding of cell variable when used in a non-trivial default argument expression.
+ """
+ for i in [[2], [3]]:
+ next(filter(lambda j, ix=i[0]: j == ix, [1, 3]))
+
+
+def bad_case():
+ """Closing over a loop variable."""
+ lst = []
+ for i in range(10):
+ print(i)
+ lst.append(lambda: i) # [cell-var-from-loop]
+
+
+def bad_case2():
+ """Closing over a loop variable."""
+ return [lambda: i for i in range(10)] # [cell-var-from-loop]
+
+
+def bad_case3():
+ """Closing over variable defined in loop."""
+ lst = []
+ for i in range(10):
+ j = i * i
+ lst.append(lambda: j) # [cell-var-from-loop]
+ return lst
+
+
+def bad_case4():
+ """Closing over variable defined in loop."""
+ lst = []
+ for i in range(10):
+ def nested():
+ """Nested function."""
+ return i**2 # [cell-var-from-loop]
+ lst.append(nested)
+ return lst
+
+
+def bad_case5():
+ """Problematic case.
+
+ If this function is used as
+
+ >>> [x() for x in bad_case5()]
+
+ it behaves 'as expected', i.e. the result is range(10).
+
+ If it's used with
+
+ >>> lst = list(bad_case5())
+ >>> [x() for x in lst]
+
+ the result is [9] * 10 again.
+ """
+ return (lambda: i for i in range(10)) # [cell-var-from-loop]
+
+
+def bad_case6():
+ """Closing over variable defined in loop."""
+ lst = []
+ for i, j in zip(range(10), range(10, 20)):
+ print(j)
+ lst.append(lambda: i) # [cell-var-from-loop]
+ return lst
+
+
+def bad_case7():
+ """Multiple variables unpacked in comprehension."""
+ return [
+ lambda: (
+ x # [cell-var-from-loop]
+ + y) # [cell-var-from-loop]
+ for x, y in ((1, 2), (3, 4), (5, 6))
+ ]
+
+
+def bad_case8():
+ """Closing over variable defined in loop below the function."""
+ lst = []
+ for i in range(10):
+ lst.append(lambda: j) # [cell-var-from-loop]
+ j = i * i
+ return lst
+
+
+def bad_case9():
+ """Detect when loop variable shadows an outer assignment."""
+ lst = []
+ i = 100
+ for i in range(10):
+ lst.append(lambda: i) # [cell-var-from-loop]
+ return lst
+
+
+def bad_case10():
+ """Detect when a loop variable is the default argument for a nested function"""
+ lst = []
+ for i in range(10):
+ def func():
+ def func2(arg=i): # [cell-var-from-loop]
+ return arg
+
+ return func2
+
+ lst.append(func)
+ return lst
+
+
+def bad_case_issue2846():
+ """Closing over variable that is used within a comprehension in the function body."""
+ lst_a = [
+ (lambda: n) # [cell-var-from-loop]
+ for n in range(3)
+ ]
+
+ lst_b = [
+ (lambda: [n for _ in range(3)]) # [cell-var-from-loop]
+ for n in range(3)
+ ]
+
+ return lst_a, lst_b
+
+
+class Test(Enum):
+ TEST = (40, 160)
+
+ @staticmethod
+ def new_test(minimum=TEST[0], maximum=TEST[1]):
+ return minimum, maximum
diff --git a/tests/functional/c/cellvar_escaping_loop.txt b/tests/functional/c/cellvar_escaping_loop.txt
new file mode 100644
index 000000000..4513d833f
--- /dev/null
+++ b/tests/functional/c/cellvar_escaping_loop.txt
@@ -0,0 +1,13 @@
+cell-var-from-loop:101:27:bad_case.<lambda>:Cell variable i defined in loop:HIGH
+cell-var-from-loop:106:20:bad_case2.<lambda>:Cell variable i defined in loop:HIGH
+cell-var-from-loop:114:27:bad_case3.<lambda>:Cell variable j defined in loop:HIGH
+cell-var-from-loop:124:19:bad_case4.nested:Cell variable i defined in loop:HIGH
+cell-var-from-loop:145:20:bad_case5.<lambda>:Cell variable i defined in loop:HIGH
+cell-var-from-loop:153:27:bad_case6.<lambda>:Cell variable i defined in loop:HIGH
+cell-var-from-loop:161:12:bad_case7.<lambda>:Cell variable x defined in loop:HIGH
+cell-var-from-loop:162:14:bad_case7.<lambda>:Cell variable y defined in loop:HIGH
+cell-var-from-loop:171:27:bad_case8.<lambda>:Cell variable j defined in loop:HIGH
+cell-var-from-loop:181:27:bad_case9.<lambda>:Cell variable i defined in loop:HIGH
+cell-var-from-loop:190:26:bad_case10.func.func2:Cell variable i defined in loop:HIGH
+cell-var-from-loop:202:17:bad_case_issue2846.<lambda>:Cell variable n defined in loop:HIGH
+cell-var-from-loop:207:18:bad_case_issue2846.<lambda>:Cell variable n defined in loop:HIGH
diff --git a/tests/functional/c/class_attributes.py b/tests/functional/c/class_attributes.py
new file mode 100644
index 000000000..1d41f9d7a
--- /dev/null
+++ b/tests/functional/c/class_attributes.py
@@ -0,0 +1,32 @@
+"""Test that valid class attribute doesn't trigger errors"""
+__revision__ = 'sponge bob'
+# pylint: disable=useless-object-inheritance,missing-docstring,too-few-public-methods
+
+class Clazz(object):
+ "dummy class"
+
+ def __init__(self):
+ self.topic = 5
+ self._data = 45
+
+ def change_type(self, new_class):
+ """Change type"""
+ self.__class__ = new_class
+
+ def do_nothing(self):
+ "I do nothing useful"
+ return self.topic + 56
+
+
+class Base:
+ _class_prop: int
+
+
+class Child(Base):
+ _class_prop = 42
+
+ def method(self):
+ print(self._class_prop)
+
+
+Child().method()
diff --git a/tests/functional/c/class_members.py b/tests/functional/c/class_members.py
new file mode 100644
index 000000000..e43ab57ba
--- /dev/null
+++ b/tests/functional/c/class_members.py
@@ -0,0 +1,13 @@
+# pylint: disable=missing-docstring, too-few-public-methods
+
+
+class Class:
+ attr: int
+ ...
+
+
+# `bar` definitely does not exist here, but in a complex scenario,
+# it might. We simply exclude PEP 526 class and instance variables
+# from `no-member`.
+print(Class().attr)
+print(Class.attr)
diff --git a/tests/functional/c/class_members_py30.py b/tests/functional/c/class_members_py30.py
new file mode 100644
index 000000000..cb7267ce5
--- /dev/null
+++ b/tests/functional/c/class_members_py30.py
@@ -0,0 +1,68 @@
+""" Various tests for class members access. """
+# pylint: disable=too-few-public-methods,import-error,no-init,missing-docstring, wrong-import-position,wrong-import-order, useless-object-inheritance
+from missing import Missing
+class MyClass(object):
+ """class docstring"""
+
+ def __init__(self):
+ """init"""
+ self.correct = 1
+
+ def test(self):
+ """test"""
+ self.correct += 2
+ self.incorrect += 2 # [no-member]
+ del self.havenot # [no-member]
+ self.nonexistent1.truc() # [no-member]
+ self.nonexistent2[1] = 'hehe' # [no-member]
+
+class XYZMixin(object):
+ """access to undefined members should be ignored in mixin classes by
+ default
+ """
+ def __init__(self):
+ print(self.nonexistent)
+
+
+class NewClass(object):
+ """use object.__setattr__"""
+ def __init__(self):
+ self.__setattr__('toto', 'tutu')
+
+from abc import ABCMeta
+
+class TestMetaclass(object, metaclass=ABCMeta):
+ """ Test attribute access for metaclasses. """
+
+class Metaclass(type):
+ """ metaclass """
+ @classmethod
+ def test(cls):
+ """ classmethod """
+
+class UsingMetaclass(object, metaclass=Metaclass):
+ """ empty """
+
+TestMetaclass.register(int)
+UsingMetaclass.test()
+TestMetaclass().register(int) # [no-member]
+UsingMetaclass().test() # [no-member]
+
+
+class NoKnownBases(Missing):
+ """Don't emit no-member if we don't know the bases of a class."""
+
+NoKnownBases().lalala()
+
+
+class MetaClass(object):
+ """Look some methods in the implicit metaclass."""
+
+ @classmethod
+ def whatever(cls):
+ return cls.mro() + cls.missing() # [no-member]
+
+from collections import namedtuple
+
+Tuple = namedtuple("Tuple", "field other")
+Tuple.field.__doc__ = "A doc for the field."
diff --git a/tests/functional/c/class_members_py30.txt b/tests/functional/c/class_members_py30.txt
new file mode 100644
index 000000000..c0fab25a7
--- /dev/null
+++ b/tests/functional/c/class_members_py30.txt
@@ -0,0 +1,7 @@
+no-member:14:8:MyClass.test:Instance of 'MyClass' has no 'incorrect' member:INFERENCE
+no-member:15:12:MyClass.test:Instance of 'MyClass' has no 'havenot' member:INFERENCE
+no-member:16:8:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member:INFERENCE
+no-member:17:8:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member:INFERENCE
+no-member:48:0::Instance of 'TestMetaclass' has no 'register' member:INFERENCE
+no-member:49:0::Instance of 'UsingMetaclass' has no 'test' member:INFERENCE
+no-member:63:27:MetaClass.whatever:Class 'MetaClass' has no 'missing' member:INFERENCE
diff --git a/tests/functional/c/class_scope.py b/tests/functional/c/class_scope.py
new file mode 100644
index 000000000..a22ba721b
--- /dev/null
+++ b/tests/functional/c/class_scope.py
@@ -0,0 +1,43 @@
+# pylint: disable=too-few-public-methods,no-init, useless-object-inheritance
+"""check for scope problems"""
+
+__revision__ = None
+
+class Well(object):
+ """well"""
+ attr = 42
+ get_attr = lambda arg=attr: arg * 24
+ # +1: [undefined-variable, used-before-assignment]
+ get_attr_bad = lambda arg=revattr: revattr * 42
+ revattr = 24
+ bad_lambda = lambda: get_attr_bad # [undefined-variable]
+ bad_gen = list(attr + i for i in range(10)) # [undefined-variable]
+
+ class Data(object):
+ """base hidden class"""
+ class Sub(Data):
+ """whaou, is Data found???"""
+ attr = Data() # [undefined-variable]
+ def func(self):
+ """check Sub is not defined here"""
+ return Sub(), self # [undefined-variable]
+
+
+class Right:
+ """right"""
+ class Result1:
+ """result one"""
+ OK = 0
+ def work(self) -> Result1:
+ """good type hint"""
+ return self.Result1.OK
+
+
+class Wrong:
+ """wrong"""
+ class Result2:
+ """result two"""
+ OK = 0
+ def work(self) -> self.Result2: # [undefined-variable]
+ """bad type hint"""
+ return self.Result2.OK
diff --git a/tests/functional/c/class_scope.txt b/tests/functional/c/class_scope.txt
new file mode 100644
index 000000000..a45d23263
--- /dev/null
+++ b/tests/functional/c/class_scope.txt
@@ -0,0 +1,7 @@
+undefined-variable:11:39:Well.<lambda>:Undefined variable 'revattr'
+used-before-assignment:11:30:Well.<lambda>:Using variable 'revattr' before assignment
+undefined-variable:13:25:Well.<lambda>:Undefined variable 'get_attr_bad'
+undefined-variable:14:19:Well:Undefined variable 'attr'
+undefined-variable:20:15:Well.Sub:Undefined variable 'Data'
+undefined-variable:23:15:Well.func:Undefined variable 'Sub'
+undefined-variable:41:22:Wrong.work:Undefined variable 'self'
diff --git a/tests/functional/c/class_variable_slots_conflict_exempted.py b/tests/functional/c/class_variable_slots_conflict_exempted.py
new file mode 100644
index 000000000..b6a0edf44
--- /dev/null
+++ b/tests/functional/c/class_variable_slots_conflict_exempted.py
@@ -0,0 +1,4 @@
+# pylint: disable=missing-docstring,too-few-public-methods
+class Example:
+ __slots__ = ["field"]
+ field: int
diff --git a/tests/functional/c/classes_meth_could_be_a_function.py b/tests/functional/c/classes_meth_could_be_a_function.py
new file mode 100644
index 000000000..94103a324
--- /dev/null
+++ b/tests/functional/c/classes_meth_could_be_a_function.py
@@ -0,0 +1,33 @@
+# pylint: disable=missing-docstring,too-few-public-methods,no-init,useless-object-inheritance
+"""
+#2479
+
+R0201 (formely W0212), Method could be a function shouldn't be emitted in case
+like factory method pattern
+"""
+__revision__ = 1
+
+class XAsub(object):
+ pass
+class XBsub(XAsub):
+ pass
+class XCsub(XAsub):
+ pass
+
+class Aimpl(object):
+ # disable "method could be a function" on classes which are not overriding
+ # the factory method because in that case the usage of polymorphism is not
+ # detected
+ # pylint: disable=no-self-use
+ def makex(self):
+ return XAsub()
+
+class Bimpl(Aimpl):
+
+ def makex(self):
+ return XBsub()
+
+class Cimpl(Aimpl):
+
+ def makex(self):
+ return XCsub()
diff --git a/tests/functional/c/classes_protected_member_access.py b/tests/functional/c/classes_protected_member_access.py
new file mode 100644
index 000000000..516efd7d4
--- /dev/null
+++ b/tests/functional/c/classes_protected_member_access.py
@@ -0,0 +1,26 @@
+"""
+#3123: W0212 false positive on static method
+"""
+__revision__ = 1
+
+# pylint: disable=no-classmethod-decorator, no-staticmethod-decorator, useless-object-inheritance
+class A3123(object):
+ """oypuee"""
+ _protected = 1
+ def __init__(self):
+ pass
+
+
+ def cmeth(cls, val):
+ """set protected member"""
+ cls._protected = +val
+
+ cmeth = classmethod(cmeth)
+
+ def smeth(val):
+ """set protected member"""
+ A3123._protected += val
+
+ smeth = staticmethod(smeth)
+
+ prop = property(lambda self: self._protected)
diff --git a/tests/functional/c/comparison_with_callable.py b/tests/functional/c/comparison_with_callable.py
new file mode 100644
index 000000000..b676844ae
--- /dev/null
+++ b/tests/functional/c/comparison_with_callable.py
@@ -0,0 +1,60 @@
+# pylint: disable = disallowed-name, missing-docstring, useless-return, misplaced-comparison-constant, invalid-name, no-self-use, line-too-long, useless-object-inheritance
+def foo():
+ return None
+
+def goo():
+ return None
+
+if foo == 786: # [comparison-with-callable]
+ pass
+
+if 666 == goo: # [comparison-with-callable]
+ pass
+
+if foo == goo:
+ pass
+
+if foo() == goo():
+ pass
+
+
+class FakeClass(object):
+ def __init__(self):
+ self._fake_prop = 'fake it till you make it!!'
+
+ def fake_method(self):
+ return '666 - The Number of the Beast'
+
+ @property
+ def fake_property(self):
+ return self._fake_prop
+
+ @fake_property.setter
+ def fake_property(self, prop):
+ self._fake_prop = prop
+
+obj1 = FakeClass()
+obj2 = FakeClass()
+
+if obj1.fake_method == obj2.fake_method:
+ pass
+
+if obj1.fake_property != obj2.fake_property: # property although is function but is called without parenthesis
+ pass
+
+if obj1.fake_method != foo:
+ pass
+
+if obj1.fake_method != 786: # [comparison-with-callable]
+ pass
+
+if obj1.fake_method != obj2.fake_property: # [comparison-with-callable]
+ pass
+
+if 666 == 786:
+ pass
+
+a = 666
+b = 786
+if a == b:
+ pass
diff --git a/tests/functional/c/comparison_with_callable.txt b/tests/functional/c/comparison_with_callable.txt
new file mode 100644
index 000000000..a87f0b57e
--- /dev/null
+++ b/tests/functional/c/comparison_with_callable.txt
@@ -0,0 +1,4 @@
+comparison-with-callable:8:3::Comparing against a callable, did you omit the parenthesis?
+comparison-with-callable:11:3::Comparing against a callable, did you omit the parenthesis?
+comparison-with-callable:48:3::Comparing against a callable, did you omit the parenthesis?
+comparison-with-callable:51:3::Comparing against a callable, did you omit the parenthesis?
diff --git a/tests/functional/c/condition_evals_to_constant.py b/tests/functional/c/condition_evals_to_constant.py
new file mode 100644
index 000000000..cfd0b00f4
--- /dev/null
+++ b/tests/functional/c/condition_evals_to_constant.py
@@ -0,0 +1,46 @@
+"""Test that boolean conditions simplify to a constant value"""
+# pylint: disable=pointless-statement
+from unknown import Unknown # pylint: disable=import-error
+
+
+def func(_):
+ """Pointless function"""
+
+
+CONSTANT = 100
+OTHER = 200
+
+# Simplifies any boolean expression that is coerced into a True/False value
+bool(CONSTANT or True) # [condition-evals-to-constant]
+assert CONSTANT or True # [condition-evals-to-constant]
+if CONSTANT and False: # [condition-evals-to-constant]
+ pass
+elif CONSTANT and False: # [condition-evals-to-constant]
+ pass
+while CONSTANT and False: # [condition-evals-to-constant]
+ break
+1 if CONSTANT or True else 2 # [condition-evals-to-constant]
+z = [x for x in range(10) if x or True] # [condition-evals-to-constant]
+
+# Simplifies recursively
+assert True or CONSTANT or OTHER # [condition-evals-to-constant]
+assert (CONSTANT or True) or (CONSTANT or True) # [condition-evals-to-constant]
+
+# Will try to infer the truthiness of an expression as long as it doesn't contain any variables
+assert 3 + 4 or CONSTANT # [condition-evals-to-constant]
+assert Unknown or True # [condition-evals-to-constant]
+
+assert True or True # [condition-evals-to-constant]
+assert False or False # [condition-evals-to-constant]
+assert True and True # [condition-evals-to-constant]
+assert False and False # [condition-evals-to-constant]
+
+
+# A bare constant that's not inside of a boolean operation will emit `using-constant-test` instead
+if True: # pylint: disable=using-constant-test
+ pass
+
+# Expressions not in one of the above situations will not emit a message
+CONSTANT or True
+bool(CONSTANT or OTHER)
+bool(func(CONSTANT or True))
diff --git a/tests/functional/c/condition_evals_to_constant.txt b/tests/functional/c/condition_evals_to_constant.txt
new file mode 100644
index 000000000..04d8deb8c
--- /dev/null
+++ b/tests/functional/c/condition_evals_to_constant.txt
@@ -0,0 +1,15 @@
+condition-evals-to-constant:14:5::Boolean condition 'CONSTANT or True' will always evaluate to 'True'
+condition-evals-to-constant:15:7::Boolean condition 'CONSTANT or True' will always evaluate to 'True'
+condition-evals-to-constant:16:3::Boolean condition 'CONSTANT and False' will always evaluate to 'False'
+condition-evals-to-constant:18:5::Boolean condition 'CONSTANT and False' will always evaluate to 'False'
+condition-evals-to-constant:20:6::Boolean condition 'CONSTANT and False' will always evaluate to 'False'
+condition-evals-to-constant:22:5::Boolean condition 'CONSTANT or True' will always evaluate to 'True'
+condition-evals-to-constant:23:29::Boolean condition 'x or True' will always evaluate to 'True'
+condition-evals-to-constant:26:7::Boolean condition 'True or CONSTANT or OTHER' will always evaluate to 'True'
+condition-evals-to-constant:27:7::Boolean condition 'CONSTANT or True or CONSTANT or True' will always evaluate to 'True'
+condition-evals-to-constant:30:7::Boolean condition '3 + 4 or CONSTANT' will always evaluate to '3 + 4'
+condition-evals-to-constant:31:7::Boolean condition 'Unknown or True' will always evaluate to 'True'
+condition-evals-to-constant:33:7::Boolean condition 'True or True' will always evaluate to 'True'
+condition-evals-to-constant:34:7::Boolean condition 'False or False' will always evaluate to 'False'
+condition-evals-to-constant:35:7::Boolean condition 'True and True' will always evaluate to 'True'
+condition-evals-to-constant:36:7::Boolean condition 'False and False' will always evaluate to 'False'
diff --git a/tests/functional/c/confidence_filter.py b/tests/functional/c/confidence_filter.py
new file mode 100644
index 000000000..42351998d
--- /dev/null
+++ b/tests/functional/c/confidence_filter.py
@@ -0,0 +1,16 @@
+"""Test for the confidence filter."""
+from __future__ import print_function
+# pylint: disable=useless-object-inheritance
+
+class Client(object):
+ """use provider class"""
+
+ def __init__(self):
+ self.set_later = 0
+
+ def set_set_later(self, value):
+ """set set_later attribute (introduce an inference ambiguity)"""
+ self.set_later = value
+
+print(Client().set_later.lower())
+print(Client().foo) # [no-member]
diff --git a/tests/functional/c/confidence_filter.rc b/tests/functional/c/confidence_filter.rc
new file mode 100644
index 000000000..5d21cb56e
--- /dev/null
+++ b/tests/functional/c/confidence_filter.rc
@@ -0,0 +1,3 @@
+[Messages Control]
+disable=no-init,too-few-public-methods,undefined-variable
+confidence=INFERENCE,HIGH,UNDEFINED
diff --git a/tests/functional/c/confidence_filter.txt b/tests/functional/c/confidence_filter.txt
new file mode 100644
index 000000000..1c2cd8380
--- /dev/null
+++ b/tests/functional/c/confidence_filter.txt
@@ -0,0 +1 @@
+no-member:16:6::Instance of 'Client' has no 'foo' member:INFERENCE
diff --git a/tests/functional/c/confusing_with_statement.py b/tests/functional/c/confusing_with_statement.py
new file mode 100644
index 000000000..7b8d494e4
--- /dev/null
+++ b/tests/functional/c/confusing_with_statement.py
@@ -0,0 +1,27 @@
+# pylint: disable=undefined-variable,not-context-manager,missing-docstring
+
+# both context managers are named
+with one as first, two as second:
+ pass
+
+# first matched as tuple; this is ok
+with one as (first, second), third:
+ pass
+
+# the first context manager doesn't have as name
+with one, two as second:
+ pass
+
+# the second is a function call; this is ok
+with one as first, two():
+ pass
+
+# nested with statements; make sure no message is emitted
+# this could be a false positive on Py2
+with one as first:
+ with two:
+ pass
+
+# ambiguous looking with statement
+with one as first, two: # [confusing-with-statement]
+ pass
diff --git a/tests/functional/c/confusing_with_statement.txt b/tests/functional/c/confusing_with_statement.txt
new file mode 100644
index 000000000..5ac2e7a50
--- /dev/null
+++ b/tests/functional/c/confusing_with_statement.txt
@@ -0,0 +1 @@
+confusing-with-statement:26:0::"Following ""as"" with another context manager looks like a tuple."
diff --git a/tests/functional/c/consider/consider_iterating_dictionary.py b/tests/functional/c/consider/consider_iterating_dictionary.py
new file mode 100644
index 000000000..996fa5429
--- /dev/null
+++ b/tests/functional/c/consider/consider_iterating_dictionary.py
@@ -0,0 +1,68 @@
+# 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
+
+from unknown import Unknown
+
+
+class CustomClass(object):
+ def keys(self):
+ return []
+
+for key in Unknown().keys():
+ pass
+for key in Unknown.keys():
+ pass
+for key in dict.keys():
+ pass
+for key in {}.values():
+ pass
+for key in {}.key():
+ pass
+for key in CustomClass().keys():
+ pass
+
+[key for key in {}.keys()] # [consider-iterating-dictionary]
+(key for key in {}.keys()) # [consider-iterating-dictionary]
+{key for key in {}.keys()} # [consider-iterating-dictionary]
+{key: key for key in {}.keys()} # [consider-iterating-dictionary]
+COMP1 = [key for key in {}.keys()] # [consider-iterating-dictionary]
+COMP2 = (key for key in {}.keys()) # [consider-iterating-dictionary]
+COMP3 = {key for key in {}.keys()} # [consider-iterating-dictionary]
+COMP4 = {key: key for key in {}.keys()} # [consider-iterating-dictionary]
+for key in {}.keys(): # [consider-iterating-dictionary]
+ pass
+
+# Issue #1247
+DICT = {'a': 1, 'b': 2}
+COMP1 = [k * 2 for k in DICT.keys()] + [k * 3 for k in DICT.keys()] # [consider-iterating-dictionary,consider-iterating-dictionary]
+COMP2, COMP3 = [k * 2 for k in DICT.keys()], [k * 3 for k in DICT.keys()] # [consider-iterating-dictionary,consider-iterating-dictionary]
+SOME_TUPLE = ([k * 2 for k in DICT.keys()], [k * 3 for k in DICT.keys()]) # [consider-iterating-dictionary,consider-iterating-dictionary]
+
+# Checks for membership checks
+if 1 in dict().keys(): # [consider-iterating-dictionary]
+ pass
+if 1 in {}.keys(): # [consider-iterating-dictionary]
+ pass
+if 1 in Unknown().keys():
+ pass
+if 1 in Unknown.keys():
+ pass
+if 1 in CustomClass().keys():
+ pass
+if 1 in dict():
+ pass
+if 1 in dict().values():
+ pass
+if (1, 1) in dict().items():
+ pass
+if [1] == {}.keys():
+ pass
+if [1] == {}:
+ pass
+if [1] == dict():
+ pass
+VAR = 1 in {}.keys() # [consider-iterating-dictionary]
+VAR = 1 in {}
+VAR = 1 in dict()
+VAR = [1, 2] == {}.keys() in {False}
diff --git a/tests/functional/c/consider/consider_iterating_dictionary.txt b/tests/functional/c/consider/consider_iterating_dictionary.txt
new file mode 100644
index 000000000..a3ebbac2a
--- /dev/null
+++ b/tests/functional/c/consider/consider_iterating_dictionary.txt
@@ -0,0 +1,18 @@
+consider-iterating-dictionary:25:16::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:26:16::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:27:16::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:28:21::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:29:24::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:30:24::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:31:24::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:32:29::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:33:11::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:38:24::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:38:55::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:39:31::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:39:61::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:40:30::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:40:60::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:43:8::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:45:8::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-iterating-dictionary:65:11::Consider iterating the dictionary directly instead of calling .keys():HIGH
diff --git a/tests/functional/c/consider/consider_join.py b/tests/functional/c/consider/consider_join.py
new file mode 100644
index 000000000..24cd6dd49
--- /dev/null
+++ b/tests/functional/c/consider/consider_join.py
@@ -0,0 +1,126 @@
+# pylint: disable=missing-docstring,invalid-name,undefined-variable,multiple-statements
+
+# Variations of 'result'
+result = ''
+for number in ['1', '2', '3']:
+ result += number # [consider-using-join]
+
+result = 'header'
+for number in ['1', '2', '3']:
+ result += number # [consider-using-join]
+
+result = another_result = ''
+for number in ['1', '2', '3']:
+ result += number # [consider-using-join]
+
+another_result = result = ''
+for number in ['1', '2', '3']:
+ result += number # [consider-using-join]
+
+result = 0 # result is not a string
+for number in ['1', '2', '3']:
+ result += number
+
+RESULT = '' # wrong name / initial variable missing
+for number in ['1', '2', '3']:
+ result += [number]
+
+string_variable = ''
+result = string_variable # type of 'result' not obviously a string
+for number in ['1', '2', '3']:
+ result += number
+
+result = ''
+another_result = '' # result defined too early
+for number in ['1', '2', '3']:
+ result += [number]
+
+for number in ['1', '2', '3']: # 'result'-definition missing
+ result += number
+
+
+# Variations of 'number'
+result = '' # no concatenation (iterator-name differs)
+for name in ['1', '2', '3']:
+ result += number
+
+result = '' # no concatenation (iterator-name differs)
+for _ in ['1', '2', '3']:
+ result += number
+# 'exprlist' is not a single name
+for index, number in ['1', '2', '3']:
+ result += number
+
+
+# Variations of 'iterable'
+result = ''
+for number in []:
+ result += number # [consider-using-join]
+
+result = ''
+for number in "a text":
+ result += number # [consider-using-join]
+
+result = ''
+for number in [1, 2, 3]:
+ result += number # [consider-using-join]
+
+a_list = [1, 2, 3]
+result = ''
+for number in a_list:
+ result += number # [consider-using-join]
+
+result = ''
+for number in ['1', '2', '3']:
+ result += number # [consider-using-join]
+
+result = ''
+for number in undefined_iterable:
+ result += number # [consider-using-join]
+
+
+# Variations of loop-body
+result = '' # addition is not the only part of the body
+for number in ['1', '2', '3']:
+ print(number)
+ result += number
+
+result = '' # addition is not the only part of the body
+for number in ['1', '2', '3']:
+ result += number
+ print(number)
+
+result = '' # augmented addition is not a simple one
+for number in ['1', '2', '3']:
+ result += '4' + number
+
+result = '' # assignment is not augmented
+for number in ['1', '2', '3']:
+ result = number
+
+result = '' # augmented assignment is not an addition
+for number in ['1', '2', '3']:
+ result -= number
+
+result = '' # addition is not the 'number'-iterable
+for number in ['1', '2', '3']:
+ result += another_number
+
+result = ''
+for number in ['1', '2', '3']: result += number # [consider-using-join]
+
+result = ''
+for number in ['1']:
+ result.result += number
+
+# Does not emit if the body is more complex
+result = {'context': 1}
+result['context'] = 0
+for number in ['1']:
+ result1 = 42 + int(number)
+ result['context'] += result1 * 24
+
+# Does not crash if the previous sibling does not have AssignNames
+result['context'] = 0
+for number in ['1']:
+ result['context'] += 24
diff --git a/tests/functional/c/consider/consider_join.txt b/tests/functional/c/consider/consider_join.txt
new file mode 100644
index 000000000..f544e55b8
--- /dev/null
+++ b/tests/functional/c/consider/consider_join.txt
@@ -0,0 +1,11 @@
+consider-using-join:6:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:10:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:14:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:18:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:58:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:62:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:66:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:71:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:75:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:79:4::Consider using str.join(sequence) for concatenating strings from an iterable
+consider-using-join:110:31::Consider using str.join(sequence) for concatenating strings from an iterable
diff --git a/tests/functional/c/consider/consider_merging_isinstance.py b/tests/functional/c/consider/consider_merging_isinstance.py
new file mode 100644
index 000000000..d3387bd5c
--- /dev/null
+++ b/tests/functional/c/consider/consider_merging_isinstance.py
@@ -0,0 +1,37 @@
+"""Checks use of consider-merging-isinstance"""
+# pylint:disable=line-too-long, simplifiable-condition
+
+
+def isinstances():
+ "Examples of isinstances"
+ var = range(10)
+
+ # merged
+ if isinstance(var[1], (int, float)):
+ pass
+ result = isinstance(var[2], (int, float))
+
+ # not merged
+ if isinstance(var[3], int) or isinstance(var[3], float) or isinstance(var[3], list) and True: # [consider-merging-isinstance]
+ pass
+ result = isinstance(var[4], int) or isinstance(var[4], float) or isinstance(var[5], list) and False # [consider-merging-isinstance]
+
+ result = isinstance(var[5], int) or True or isinstance(var[5], float) # [consider-merging-isinstance]
+
+ infered_isinstance = isinstance
+ result = infered_isinstance(var[6], int) or infered_isinstance(var[6], float) or infered_isinstance(var[6], list) and False # [consider-merging-isinstance]
+ result = isinstance(var[10], str) or isinstance(var[10], int) and var[8] * 14 or isinstance(var[10], float) and var[5] * 14.4 or isinstance(var[10], list) # [consider-merging-isinstance]
+ result = isinstance(var[11], int) or isinstance(var[11], int) or isinstance(var[11], float) # [consider-merging-isinstance]
+
+ result = isinstance(var[20])
+ result = isinstance()
+
+ # Combination merged and not merged
+ result = isinstance(var[12], (int, float)) or isinstance(var[12], list) # [consider-merging-isinstance]
+
+ # not merged but valid
+ result = isinstance(var[5], int) and var[5] * 14 or isinstance(var[5], float) and var[5] * 14.4
+ result = isinstance(var[7], int) or not isinstance(var[7], float)
+ result = isinstance(var[6], int) or isinstance(var[7], float)
+ result = isinstance(var[6], int) or isinstance(var[7], int)
+ return result
diff --git a/tests/functional/c/consider/consider_merging_isinstance.txt b/tests/functional/c/consider/consider_merging_isinstance.txt
new file mode 100644
index 000000000..9728f1e15
--- /dev/null
+++ b/tests/functional/c/consider/consider_merging_isinstance.txt
@@ -0,0 +1,7 @@
+consider-merging-isinstance:15:7:isinstances:Consider merging these isinstance calls to isinstance(var[3], (float, int))
+consider-merging-isinstance:17:13:isinstances:Consider merging these isinstance calls to isinstance(var[4], (float, int))
+consider-merging-isinstance:19:13:isinstances:Consider merging these isinstance calls to isinstance(var[5], (float, int))
+consider-merging-isinstance:22:13:isinstances:Consider merging these isinstance calls to isinstance(var[6], (float, int))
+consider-merging-isinstance:23:13:isinstances:Consider merging these isinstance calls to isinstance(var[10], (list, str))
+consider-merging-isinstance:24:13:isinstances:Consider merging these isinstance calls to isinstance(var[11], (float, int))
+consider-merging-isinstance:30:13:isinstances:Consider merging these isinstance calls to isinstance(var[12], (float, int, list))
diff --git a/tests/functional/c/consider/consider_swap_variables.py b/tests/functional/c/consider/consider_swap_variables.py
new file mode 100644
index 000000000..e3320773f
--- /dev/null
+++ b/tests/functional/c/consider/consider_swap_variables.py
@@ -0,0 +1,25 @@
+# pylint: disable=missing-docstring,invalid-name,using-constant-test
+
+a, b, c, d = 'a b c d'.split()
+
+temp = a # [consider-swap-variables]
+a = b
+b = temp
+
+temp = a # only simple swaps are reported
+a = b
+if True:
+ b = a
+
+temp = a # this is no swap
+a = b
+b = a
+
+temp = a, b # complex swaps are ignored
+a, b = c, d
+c, d = temp
+
+temp = a # [consider-swap-variables]
+a = b # longer swap circles are only reported once
+b = temp
+temp = a
diff --git a/tests/functional/c/consider/consider_swap_variables.txt b/tests/functional/c/consider/consider_swap_variables.txt
new file mode 100644
index 000000000..3e099d671
--- /dev/null
+++ b/tests/functional/c/consider/consider_swap_variables.txt
@@ -0,0 +1,2 @@
+consider-swap-variables:5:0::Consider using tuple unpacking for swapping variables
+consider-swap-variables:22:0::Consider using tuple unpacking for swapping variables
diff --git a/tests/functional/c/consider/consider_using_dict_comprehension.py b/tests/functional/c/consider/consider_using_dict_comprehension.py
new file mode 100644
index 000000000..c9d740e18
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_dict_comprehension.py
@@ -0,0 +1,12 @@
+# pylint: disable=missing-docstring, invalid-name, use-dict-literal
+
+numbers = [1, 2, 3, 4, 5, 6]
+
+dict()
+
+dict([])
+
+dict([(number, number*2) for number in numbers]) # [consider-using-dict-comprehension]
+
+# Cannot emit as this cannot be written as a comprehension
+dict([value.split("=") for value in ["a=b", "c=d"]])
diff --git a/tests/functional/c/consider/consider_using_dict_comprehension.txt b/tests/functional/c/consider/consider_using_dict_comprehension.txt
new file mode 100644
index 000000000..ea2bb31ae
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_dict_comprehension.txt
@@ -0,0 +1 @@
+consider-using-dict-comprehension:9:0::Consider using a dictionary comprehension
diff --git a/tests/functional/c/consider/consider_using_dict_items.py b/tests/functional/c/consider/consider_using_dict_items.py
new file mode 100644
index 000000000..32e92e803
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_dict_items.py
@@ -0,0 +1,105 @@
+"""Emit a message for iteration through dict keys and subscripting dict with key."""
+# pylint: disable=line-too-long,missing-docstring,unsubscriptable-object,too-few-public-methods,redefined-outer-name,use-dict-literal
+
+def bad():
+ a_dict = {1: 1, 2: 2, 3: 3}
+ for k in a_dict: # [consider-using-dict-items]
+ print(a_dict[k])
+ another_dict = dict()
+ for k in another_dict: # [consider-using-dict-items]
+ print(another_dict[k])
+
+
+def good():
+ a_dict = {1: 1, 2: 2, 3: 3}
+ for k in a_dict:
+ print(k)
+
+out_of_scope_dict = dict()
+
+def another_bad():
+ for k in out_of_scope_dict: # [consider-using-dict-items]
+ print(out_of_scope_dict[k])
+
+def another_good():
+ for k in out_of_scope_dict:
+ k = 1
+ k = 2
+ k = 3
+ print(out_of_scope_dict[k])
+
+
+b_dict = {}
+for k2 in b_dict: # Should not emit warning, key access necessary
+ b_dict[k2] = 2
+
+for k2 in b_dict: # Should not emit warning, key access necessary (AugAssign)
+ b_dict[k2] += 2
+
+# Warning should be emitted in this case
+for k6 in b_dict: # [consider-using-dict-items]
+ val = b_dict[k6]
+ b_dict[k6] = 2
+
+for k3 in b_dict: # [consider-using-dict-items]
+ val = b_dict[k3]
+
+for k4 in b_dict.keys(): # [consider-iterating-dictionary,consider-using-dict-items]
+ val = b_dict[k4]
+
+class Foo:
+ c_dict = {}
+
+# Should emit warning when iterating over a dict attribute of a class
+for k5 in Foo.c_dict: # [consider-using-dict-items]
+ val = Foo.c_dict[k5]
+
+c_dict = {}
+
+# Should NOT emit warning whey key used to access a different dict
+for k5 in Foo.c_dict: # This is fine
+ val = b_dict[k5]
+
+for k5 in Foo.c_dict: # This is fine
+ val = c_dict[k5]
+
+# Should emit warning within a list/dict comprehension
+val = {k9: b_dict[k9] for k9 in b_dict} # [consider-using-dict-items]
+val = [(k7, b_dict[k7]) for k7 in b_dict] # [consider-using-dict-items]
+
+# Should emit warning even when using dict attribute of a class within comprehension
+val = [(k7, Foo.c_dict[k7]) for k7 in Foo.c_dict] # [consider-using-dict-items]
+val = any(True for k8 in Foo.c_dict if Foo.c_dict[k8]) # [consider-using-dict-items]
+
+# Should emit warning when dict access done in ``if`` portion of comprehension
+val = any(True for k8 in b_dict if b_dict[k8]) # [consider-using-dict-items]
+
+# Should NOT emit warning whey key used to access a different dict
+val = [(k7, b_dict[k7]) for k7 in Foo.c_dict]
+val = any(True for k8 in Foo.c_dict if b_dict[k8])
+
+# Should NOT emit warning, essentially same check as above
+val = [(k7, c_dict[k7]) for k7 in Foo.c_dict]
+val = any(True for k8 in Foo.c_dict if c_dict[k8])
+
+# Should emit warning, using .keys() of Foo.c_dict
+val = any(True for k8 in Foo.c_dict.keys() if Foo.c_dict[k8]) # [consider-iterating-dictionary,consider-using-dict-items]
+
+# Test false positive described in #4630
+# (https://github.com/PyCQA/pylint/issues/4630)
+
+d = {'key': 'value'}
+
+for k in d: # this is fine, with the reassignment of d[k], d[k] is necessary
+ d[k] += '123'
+ if '1' in d[k]: # index lookup necessary here, do not emit error
+ print('found 1')
+
+for k in d: # if this gets rewritten to d.items(), we are back to the above problem
+ d[k] = d[k] + 1
+ if '1' in d[k]: # index lookup necessary here, do not emit error
+ print('found 1')
+
+for k in d: # [consider-using-dict-items]
+ if '1' in d[k]: # index lookup necessary here, do not emit error
+ print('found 1')
diff --git a/tests/functional/c/consider/consider_using_dict_items.txt b/tests/functional/c/consider/consider_using_dict_items.txt
new file mode 100644
index 000000000..9951d6848
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_dict_items.txt
@@ -0,0 +1,16 @@
+consider-using-dict-items:6:4:bad:Consider iterating with .items():HIGH
+consider-using-dict-items:9:4:bad:Consider iterating with .items():HIGH
+consider-using-dict-items:21:4:another_bad:Consider iterating with .items():HIGH
+consider-using-dict-items:40:0::Consider iterating with .items():HIGH
+consider-using-dict-items:44:0::Consider iterating with .items():HIGH
+consider-iterating-dictionary:47:10::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-using-dict-items:47:0::Consider iterating with .items():HIGH
+consider-using-dict-items:54:0::Consider iterating with .items():HIGH
+consider-using-dict-items:67:0::Consider iterating with .items():HIGH
+consider-using-dict-items:68:0::Consider iterating with .items():HIGH
+consider-using-dict-items:71:0::Consider iterating with .items():HIGH
+consider-using-dict-items:72:0::Consider iterating with .items():HIGH
+consider-using-dict-items:75:0::Consider iterating with .items():HIGH
+consider-iterating-dictionary:86:25::Consider iterating the dictionary directly instead of calling .keys():HIGH
+consider-using-dict-items:86:0::Consider iterating with .items():HIGH
+consider-using-dict-items:103:0::Consider iterating with .items():HIGH
diff --git a/tests/functional/c/consider/consider_using_enumerate.py b/tests/functional/c/consider/consider_using_enumerate.py
new file mode 100644
index 000000000..bf29d6885
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_enumerate.py
@@ -0,0 +1,85 @@
+"""Emit a message for iteration through range and len is encountered."""
+
+# pylint: disable=missing-docstring, import-error, useless-object-inheritance, unsubscriptable-object, too-few-public-methods
+
+def bad():
+ iterable = [1, 2, 3]
+ for obj in range(len(iterable)): # [consider-using-enumerate]
+ yield iterable[obj]
+ for obj in range(0, len(iterable)): # [consider-using-enumerate]
+ yield iterable[obj]
+
+
+class Bad(object):
+
+ def __iter__(self):
+ iterable = [1, 2, 3]
+ for i in range(len(iterable)): # [consider-using-enumerate]
+ yield iterable[i]
+
+ def test(self):
+ for i in range(len(self)): # [consider-using-enumerate]
+ yield self[i]
+
+
+def good():
+ iterable = other_obj = [1, 2, 3]
+ total = 0
+ for obj in range(len(iterable)):
+ total += obj
+ yield total
+ yield iterable[obj + 1: 2]
+ yield iterable[len(obj)]
+ for obj in iterable:
+ yield iterable[obj - 1]
+
+ for index, obj in enumerate(iterable):
+ yield iterable[index]
+ for index in range(0, 10):
+ yield iterable[index + 1]
+ for index in range(10):
+ yield iterable[index]
+ for index in range(len([1, 2, 3, 4])):
+ yield index
+ for index in range(1, len(iterable)):
+ yield index
+ for index in range(len(iterable)):
+ yield [1, 2, 3][index]
+ yield len([1, 2, 3])
+ for index in range(len(iterable)):
+ yield other_obj[index]
+
+ # pylint: disable=import-outside-toplevel
+ from unknown import unknown
+ for index in range(unknown(iterable)):
+ yield iterable[index]
+
+ for index in range(len(iterable)):
+ def test(iterable):
+ return iterable[index] # pylint: disable=cell-var-from-loop
+ yield test([1, 2, 3])
+
+
+class Good(object):
+
+ def __iter__(self):
+ # Should not suggest enumerate on self
+ for i in range(len(self)):
+ yield self[i]
+
+
+def does_not_crash_on_range_without_args():
+ for elem in range():
+ print(elem)
+
+# False negative described in #3657
+# https://github.com/PyCQA/pylint/issues/3657
+class MyClass(object):
+ def __init__(self):
+ self.my_list = []
+
+my_obj = MyClass()
+def my_function(instance: MyClass):
+ for i in range(len(instance.my_list)): # [consider-using-enumerate]
+ var = instance.my_list[i]
+ print(var)
diff --git a/tests/functional/c/consider/consider_using_enumerate.txt b/tests/functional/c/consider/consider_using_enumerate.txt
new file mode 100644
index 000000000..4a4738838
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_enumerate.txt
@@ -0,0 +1,5 @@
+consider-using-enumerate:7:4:bad:Consider using enumerate instead of iterating with range and len
+consider-using-enumerate:9:4:bad:Consider using enumerate instead of iterating with range and len
+consider-using-enumerate:17:8:Bad.__iter__:Consider using enumerate instead of iterating with range and len
+consider-using-enumerate:21:8:Bad.test:Consider using enumerate instead of iterating with range and len
+consider-using-enumerate:83:4:my_function:Consider using enumerate instead of iterating with range and len
diff --git a/tests/functional/c/consider/consider_using_f_string.py b/tests/functional/c/consider/consider_using_f_string.py
new file mode 100644
index 000000000..825f3517c
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_f_string.py
@@ -0,0 +1,107 @@
+"""Test to see if a f-string would be possible and consider-using-f-string should be raised"""
+# pylint: disable=unused-variable, invalid-name, missing-function-docstring, pointless-statement
+# pylint: disable=expression-not-assigned, repeated-keyword
+
+PARAM_1 = PARAM_2 = PARAM_3 = 1
+PARAM_LIST = [PARAM_1, PARAM_2, PARAM_3]
+PARAM_LIST_SINGLE = [PARAM_1]
+PARAM_DICT = {"Param_1": PARAM_1, "Param_2": PARAM_2, "Param_3": PARAM_3}
+PARAM_DICT_SINGLE = {"Param_1": PARAM_1}
+
+
+def return_parameter():
+ return PARAM_1
+
+
+def return_list():
+ return PARAM_LIST
+
+
+def return_dict():
+ return PARAM_DICT
+
+
+def print_good():
+ print("String {}, {} or {}".format(*PARAM_LIST))
+ print("String {}, {}, {} or {}".format(*PARAM_LIST_SINGLE, *PARAM_LIST))
+ print("String {Param}, {}, {} or {}".format(Param=PARAM_1, *PARAM_LIST))
+ print("String {Param} {Param}".format(Param=PARAM_1))
+ print("{Param_1} {Param_2}".format(**PARAM_DICT))
+ print("{Param_1} {Param_2} {Param_3}".format(**PARAM_DICT_SINGLE, **PARAM_DICT))
+ print("{Param_1} {Param_2} {Param_3}".format(Param_1=PARAM_1, **PARAM_DICT))
+ print("{Param_1} {Param_2}".format(**PARAM_DICT))
+ print("{Param_1} {Param_2}".format(**return_dict()))
+ print("%(Param_1)s %(Param_2)s" % PARAM_LIST)
+ print("%(Param_1)s %(Param_2)s" % PARAM_DICT)
+ print("%(Param_1)s %(Param_2)s" % return_dict())
+ print("{a[Param_1]}{a[Param_2]}".format(a=PARAM_DICT))
+
+def print_bad():
+ print("String %f" % PARAM_1) # [consider-using-f-string]
+ print("String {}".format(PARAM_1)) # [consider-using-f-string]
+ print("String {Param_1}".format(Param_1=PARAM_1)) # [consider-using-f-string]
+ print("{} {}".format(PARAM_1, PARAM_2)) # [consider-using-f-string]
+ print("{Par_1}{Par_2}".format(Par_1=PARAM_1, Par_2=PARAM_2)) # [consider-using-f-string]
+ print("{Param_1}".format(*PARAM_LIST_SINGLE)) # [consider-using-f-string]
+ print("{Param_1}".format(**PARAM_DICT_SINGLE)) # [consider-using-f-string]
+ print("String %s" % (PARAM_1)) # [consider-using-f-string]
+ print("String %s %s" % (PARAM_1, PARAM_2)) # [consider-using-f-string]
+ print("String %s" % (PARAM_LIST_SINGLE)) # [consider-using-f-string]
+
+
+def statement_good():
+ "String {}, {} or {}".format(*PARAM_LIST)
+ "String {}, {}, {} or {}".format(*PARAM_LIST_SINGLE, *PARAM_LIST)
+ "String {Param}, {}, {} or {}".format(Param=PARAM_1, *PARAM_LIST)
+ "String {Param} {Param}".format(Param=PARAM_1)
+ "{Param_1} {Param_2}".format(**PARAM_DICT)
+ "{Param_1} {Param_2} {Param_3}".format(**PARAM_DICT_SINGLE, **PARAM_DICT)
+ "{Param_1} {Param_2} {Param_3}".format(Param_1=PARAM_1, **PARAM_DICT)
+ "{Param_1} {Param_2}".format(**PARAM_DICT)
+ "{Param_1} {Param_2}".format(**return_dict())
+ "%(Param_1)s %(Param_2)s" % PARAM_LIST
+ "%(Param_1)s %(Param_2)s" % PARAM_DICT
+ "%(Param_1)s %(Param_2)s" % return_dict()
+ "{a[Param_1]}{a[Param_2]}".format(a=PARAM_DICT)
+
+def statement_bad():
+ "String %f" % PARAM_1 # [consider-using-f-string]
+ "String {}".format(PARAM_1) # [consider-using-f-string]
+ "String {Param_1}".format(Param_1=PARAM_1) # [consider-using-f-string]
+ "{} {}".format(PARAM_1, PARAM_2) # [consider-using-f-string]
+ "{Par_1}{Par_2}".format(Par_1=PARAM_1, Par_2=PARAM_2) # [consider-using-f-string]
+ "{Param_1}".format(*PARAM_LIST_SINGLE) # [consider-using-f-string]
+ "{Param_1}".format(**PARAM_DICT_SINGLE) # [consider-using-f-string]
+ "String %s" % (PARAM_1) # [consider-using-f-string]
+ "String %s %s" % (PARAM_1, PARAM_2) # [consider-using-f-string]
+ "String %s" % (PARAM_LIST_SINGLE) # [consider-using-f-string]
+
+
+def assignment_good():
+ A = "String {}, {} or {}".format(*PARAM_LIST)
+ B = "String {}, {}, {} or {}".format(*PARAM_LIST_SINGLE, *PARAM_LIST)
+ C = "String {Param}, {}, {} or {}".format(Param=PARAM_1, *PARAM_LIST)
+ D = "String {Param} {Param}".format(Param=PARAM_1)
+ E = "{Param_1} {Param_2}".format(**PARAM_DICT)
+ F = "{Param_1} {Param_2} {Param_3}".format(**PARAM_DICT_SINGLE, **PARAM_DICT)
+ G = "{Param_1} {Param_2} {Param_3}".format(Param_1=PARAM_1, **PARAM_DICT)
+ H = "{Param_1} {Param_2}".format(**PARAM_DICT)
+ I = "{Param_1} {Param_2}".format(**return_dict())
+ J = "%(Param_1)s %(Param_2)s" % PARAM_LIST
+ K = "%(Param_1)s %(Param_2)s" % PARAM_DICT
+ L = "%(Param_1)s %(Param_2)s" % return_dict()
+ M = "{a[Param_1]}{a[Param_2]}".format(a=PARAM_DICT)
+ N = "{Param}".format
+
+
+def assignment_bad():
+ a = "String %f" % PARAM_1 # [consider-using-f-string]
+ b = "String {}".format(PARAM_1) # [consider-using-f-string]
+ c = "String {Param_1}".format(Param_1=PARAM_1) # [consider-using-f-string]
+ d = "{} {}".format(PARAM_1, PARAM_2) # [consider-using-f-string]
+ e = "{Par_1}{Par_2}".format(Par_1=PARAM_1, Par_2=PARAM_2) # [consider-using-f-string]
+ f = "{Param_1}".format(*PARAM_LIST_SINGLE) # [consider-using-f-string]
+ g = "{Param_1}".format(**PARAM_DICT_SINGLE) # [consider-using-f-string]
+ h = "String %s" % (PARAM_1) # [consider-using-f-string]
+ i = "String %s %s" % (PARAM_1, PARAM_2) # [consider-using-f-string]
+ j = "String %s" % (PARAM_LIST_SINGLE) # [consider-using-f-string]
diff --git a/tests/functional/c/consider/consider_using_f_string.txt b/tests/functional/c/consider/consider_using_f_string.txt
new file mode 100644
index 000000000..db8f98112
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_f_string.txt
@@ -0,0 +1,30 @@
+consider-using-f-string:40:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:41:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:42:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:43:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:44:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:45:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:46:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:47:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:48:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:49:10:print_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:68:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:69:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:70:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:71:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:72:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:73:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:74:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:75:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:76:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:77:4:statement_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:98:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:99:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:100:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:101:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:102:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:103:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:104:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:105:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:106:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
+consider-using-f-string:107:8:assignment_bad:Formatting a regular string which could be a f-string:HIGH
diff --git a/tests/functional/c/consider/consider_using_generator.py b/tests/functional/c/consider/consider_using_generator.py
new file mode 100644
index 000000000..eadb34a56
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_generator.py
@@ -0,0 +1,11 @@
+# pylint: disable=missing-docstring, invalid-name
+# https://github.com/PyCQA/pylint/issues/3165
+
+list([])
+tuple([])
+
+list([0 for y in list(range(10))]) # [consider-using-generator]
+tuple([0 for y in list(range(10))]) # [consider-using-generator]
+
+list(0 for y in list(range(10)))
+tuple(0 for y in list(range(10)))
diff --git a/tests/functional/c/consider/consider_using_generator.txt b/tests/functional/c/consider/consider_using_generator.txt
new file mode 100644
index 000000000..3d3c5a494
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_generator.txt
@@ -0,0 +1,2 @@
+consider-using-generator:7:0::Consider using a generator instead 'list(0 for y in list(range(10)))'
+consider-using-generator:8:0::Consider using a generator instead 'tuple(0 for y in list(range(10)))'
diff --git a/tests/functional/c/consider/consider_using_get.py b/tests/functional/c/consider/consider_using_get.py
new file mode 100644
index 000000000..0b71431d0
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_get.py
@@ -0,0 +1,90 @@
+# pylint: disable=missing-docstring,invalid-name,using-constant-test,invalid-sequence-index,undefined-variable
+dictionary = {}
+key = 'key'
+
+if 'key' in dictionary: # [consider-using-get]
+ variable = dictionary['key']
+
+if 'key' in dictionary: # [consider-using-get]
+ variable = dictionary['key']
+else:
+ variable = 'default'
+
+if key in dictionary: # [consider-using-get]
+ variable = dictionary[key]
+
+if 'key' in dictionary: # not accessing the dictionary in assignment
+ variable = "string"
+
+if 'key' in dictionary: # is a match, but not obvious and we ignore it for now
+ variable = dictionary[key]
+
+if 'key1' in dictionary: # dictionary querried for wrong key
+ variable = dictionary['key2']
+
+if 'key' in dictionary: # body is not pure
+ variable = dictionary['key']
+ print('found')
+
+if 'key' in dictionary: # body is not pure
+ variable = dictionary['key']
+ print('found')
+else:
+ variable = 'default'
+
+if 'key' in dictionary1: # different dictionaries
+ variable = dictionary2['key']
+else:
+ variable = 'default'
+
+if 'key' in dictionary: # body is not pure
+ variable = dictionary['key']
+else:
+ variable = 'default'
+ print('found')
+
+if 'key' in dictionary: # different variables
+ variable1 = dictionary['key']
+else:
+ variable2 = 'default'
+
+if 'key' in dictionary: # assignment is not simple
+ variable1 = variable2 = dictionary['key']
+
+if 'key' in dictionary: # assignment is not simple
+ variable1 = dictionary['key']
+else:
+ variable1 = variable2 = "default"
+
+if 'word' in 'text':
+ variable = 'text'['word'] # already bogus, but to assert that this only works with dictionaries
+
+if 'word' in dictionary:
+ variable = 'dictionary'['word']
+
+if 'key1' in dictionary: # not the simple case
+ variable = dictionary['key1']
+elif 'key2' in dictionary: # [consider-using-get]
+ variable = dictionary['key2']
+else:
+ variable = 'default'
+
+if 'key' in dictionary and bool(key): # not a simple compare
+ variable = dictionary['key1']
+else:
+ variable = 'default'
+
+if bool(key) and 'key' in dictionary: # not a simple compare
+ variable = dictionary['key1']
+else:
+ variable = 'default'
+
+
+d1 = {'foo': None}
+d2 = {}
+# Cannot be represented as using .get()
+if 'foo' in d1:
+ d2['bar'] = d1['foo']
+
+if 'key' in dictionary:
+ variable = dictionary[1:]
diff --git a/tests/functional/c/consider/consider_using_get.txt b/tests/functional/c/consider/consider_using_get.txt
new file mode 100644
index 000000000..d57b4a269
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_get.txt
@@ -0,0 +1,4 @@
+consider-using-get:5:0::Consider using dict.get for getting values from a dict if a key is present or a default if not
+consider-using-get:8:0::Consider using dict.get for getting values from a dict if a key is present or a default if not
+consider-using-get:13:0::Consider using dict.get for getting values from a dict if a key is present or a default if not
+consider-using-get:67:0::Consider using dict.get for getting values from a dict if a key is present or a default if not
diff --git a/tests/functional/c/consider/consider_using_in.py b/tests/functional/c/consider/consider_using_in.py
new file mode 100644
index 000000000..8d23bb093
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_in.py
@@ -0,0 +1,53 @@
+# pylint: disable=missing-docstring, invalid-name, pointless-statement, misplaced-comparison-constant, undefined-variable, literal-comparison, line-too-long, unneeded-not, too-few-public-methods
+
+value = value1 = 1
+value2 = 2
+a_set = {1, 2, 3}
+a_list = [1, 2, 3]
+a_str = '1'
+
+# Positive
+value == 1 or value == 1 # [consider-using-in]
+value == 1 or value == 2 # [consider-using-in]
+'value' == value or 'value' == value # [consider-using-in]
+value == 1 or value == undef_value # [consider-using-in]
+value == 1 or value == 2 or value == 3 # [consider-using-in]
+value == '2' or value == 1 # [consider-using-in]
+1 == value or 2 == value # [consider-using-in]
+1 == value or value == 2 # [consider-using-in]
+value == 1 or value == a_list # [consider-using-in]
+value == a_set or value == a_list or value == a_str # [consider-using-in]
+value != 1 and value != 2 # [consider-using-in]
+value1 == value2 or value2 == value1 # [consider-using-in]
+a_list == [1, 2, 3] or a_list == [] # [consider-using-in]
+
+# Negative
+value != 1 or value != 2 # not a "not in"-case because of "or"
+value == 1 and value == 2 # not a "in"-case because of "and"
+value == 1 and value == 2 or value == 3 # not only 'or's
+value == 1 or value == 2 or 3 < value < 4 # not only '=='
+value == 1 # value not checked against multiple values
+value == 1 or value == 2 or value == 3 and value == 4 # not all checks are concatenated with 'or'
+value == 1 or 2 < value < 3 # length of 'ops' != 1 for second check
+value is 1 or value is 2 # 'in' compares using '==' not 'is' and therefore not considered
+not value == 1 and not value == 2
+value1 == 1 or value2 == 2 # different variables and only one comparison for each
+value1 == value2 == 1 or value1 == 2 # not checking multi-compares for '=='
+value1 != 1 == value2 and value2 != value1 != 2 # not checking multi-compares for '!='
+value1 == 1 or value1 == value2 or value2 == 3 # value1 or value2 do not occur in every check
+value1 == 1 or value1 == 2 or value2 == 1 or value2 == 2 # value1 or value2 do not occur in every check
+'value' == 1 or 'value' == 2 # only detect variables for now
+
+
+def oops():
+ return 5 / 0
+
+
+some_value = value == 4 or value == 5 or value == oops() # We only look for names and constants
+
+
+# With attribute nodes
+class A:
+ value = 2
+
+A.value == 1 or A.value == 2 # [consider-using-in]
diff --git a/tests/functional/c/consider/consider_using_in.txt b/tests/functional/c/consider/consider_using_in.txt
new file mode 100644
index 000000000..72c8e389d
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_in.txt
@@ -0,0 +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)'"
diff --git a/tests/functional/c/consider/consider_using_min_max_builtin.py b/tests/functional/c/consider/consider_using_min_max_builtin.py
new file mode 100644
index 000000000..6e7c7b886
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_min_max_builtin.py
@@ -0,0 +1,107 @@
+# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, redefined-outer-name
+
+value = 10
+value2 = 0
+value3 = 3
+
+# Positive
+if value < 10: # [consider-using-max-builtin]
+ value = 10
+
+if value >= 10: # [consider-using-min-builtin]
+ value = 10
+
+if value <= 10: # [consider-using-max-builtin]
+ value = 10
+
+if value > 10: # [consider-using-min-builtin]
+ value = 10
+
+if value < value2: # [consider-using-max-builtin]
+ value = value2
+
+if value > value2: # [consider-using-min-builtin]
+ value = value2
+
+
+class A:
+ def __init__(self):
+ self.value = 13
+
+
+A1 = A()
+if A1.value > 10: # [consider-using-min-builtin]
+ A1.value = 10
+
+
+class AA:
+ def __init__(self, value):
+ self.value = value
+
+ def __gt__(self, b):
+ return self.value > b
+
+ def __ge__(self, b):
+ return self.value >= b
+
+ def __lt__(self, b):
+ return self.value < b
+
+ def __le__(self, b):
+ return self.value <= b
+
+
+A1 = AA(0)
+A2 = AA(3)
+
+if A1 > A2: # [consider-using-min-builtin]
+ A1 = A2
+
+if A2 < A1: # [consider-using-max-builtin]
+ A2 = A1
+
+if A1 >= A2: # [consider-using-min-builtin]
+ A1 = A2
+
+if A2 <= A1: # [consider-using-max-builtin]
+ A2 = A1
+
+# Negative
+if value > 10:
+ value = 2
+
+if value > 10:
+ value = 2
+ value2 = 3
+
+if value > value2:
+ value = value3
+
+if value > 5:
+ value = value3
+
+if 2 < value <= 3:
+ value = 1
+
+if value <= 3:
+ value = 5
+
+if value <= 3:
+ value = 5
+elif value == 3:
+ value = 2
+
+if value > 10:
+ value = 10
+else:
+ value = 3
+
+
+# https://github.com/PyCQA/pylint/issues/4379
+var = 1
+if var == -1:
+ var = None
+
+var2 = 1
+if var2 in [1, 2]:
+ var2 = None
diff --git a/tests/functional/c/consider/consider_using_min_max_builtin.txt b/tests/functional/c/consider/consider_using_min_max_builtin.txt
new file mode 100644
index 000000000..9ed5e0559
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_min_max_builtin.txt
@@ -0,0 +1,11 @@
+consider-using-max-builtin:8:0::"Consider using 'value = max(value, 10)' instead of unnecessary if block"
+consider-using-min-builtin:11:0::"Consider using 'value = min(value, 10)' instead of unnecessary if block"
+consider-using-max-builtin:14:0::"Consider using 'value = max(value, 10)' instead of unnecessary if block"
+consider-using-min-builtin:17:0::"Consider using 'value = min(value, 10)' instead of unnecessary if block"
+consider-using-max-builtin:20:0::"Consider using 'value = max(value, value2)' instead of unnecessary if block"
+consider-using-min-builtin:23:0::"Consider using 'value = min(value, value2)' instead of unnecessary if block"
+consider-using-min-builtin:33:0::"Consider using 'value = min(value, 10)' instead of unnecessary if block"
+consider-using-min-builtin:57:0::"Consider using 'A1 = min(A1, A2)' instead of unnecessary if block"
+consider-using-max-builtin:60:0::"Consider using 'A2 = max(A2, A1)' instead of unnecessary if block"
+consider-using-min-builtin:63:0::"Consider using 'A1 = min(A1, A2)' instead of unnecessary if block"
+consider-using-max-builtin:66:0::"Consider using 'A2 = max(A2, A1)' instead of unnecessary if block"
diff --git a/tests/functional/c/consider/consider_using_set_comprehension.py b/tests/functional/c/consider/consider_using_set_comprehension.py
new file mode 100644
index 000000000..345989c69
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_set_comprehension.py
@@ -0,0 +1,9 @@
+# pylint: disable=missing-docstring, invalid-name, unnecessary-comprehension
+
+numbers = [1, 2, 3, 4, 5, 6]
+
+set()
+
+set([])
+
+set([number for number in numbers]) # [consider-using-set-comprehension]
diff --git a/tests/functional/c/consider/consider_using_set_comprehension.txt b/tests/functional/c/consider/consider_using_set_comprehension.txt
new file mode 100644
index 000000000..4839a7a62
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_set_comprehension.txt
@@ -0,0 +1 @@
+consider-using-set-comprehension:9:0::Consider using a set comprehension
diff --git a/tests/functional/c/consider/consider_using_sys_exit.py b/tests/functional/c/consider/consider_using_sys_exit.py
new file mode 100644
index 000000000..524cf004d
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_sys_exit.py
@@ -0,0 +1,14 @@
+# pylint: disable=missing-docstring, invalid-name, disallowed-name, redefined-builtin, unused-variable
+import sys
+
+def foo():
+ exit() # [consider-using-sys-exit]
+
+def foo_1():
+ quit() # [consider-using-sys-exit]
+
+def foo_2():
+ quit = 'abc'
+ sys.exit()
+
+quit() # [consider-using-sys-exit]
diff --git a/tests/functional/c/consider/consider_using_sys_exit.txt b/tests/functional/c/consider/consider_using_sys_exit.txt
new file mode 100644
index 000000000..c429c652e
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_sys_exit.txt
@@ -0,0 +1,3 @@
+consider-using-sys-exit:5:4:foo:Consider using sys.exit()
+consider-using-sys-exit:8:4:foo_1:Consider using sys.exit()
+consider-using-sys-exit:14:0::Consider using sys.exit()
diff --git a/tests/functional/c/consider/consider_using_sys_exit_exempted.py b/tests/functional/c/consider/consider_using_sys_exit_exempted.py
new file mode 100644
index 000000000..ba66989cb
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_sys_exit_exempted.py
@@ -0,0 +1,5 @@
+# pylint: disable=missing-docstring,redefined-builtin
+
+from sys import exit
+
+exit(0)
diff --git a/tests/functional/c/consider/consider_using_sys_exit_local_scope.py b/tests/functional/c/consider/consider_using_sys_exit_local_scope.py
new file mode 100644
index 000000000..05b57ae41
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_sys_exit_local_scope.py
@@ -0,0 +1,5 @@
+# pylint: disable=missing-docstring,import-outside-toplevel,redefined-builtin
+
+def run():
+ from sys import exit
+ exit()
diff --git a/tests/functional/c/consider/consider_using_with.py b/tests/functional/c/consider/consider_using_with.py
new file mode 100644
index 000000000..d05866aaa
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_with.py
@@ -0,0 +1,231 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring, invalid-name, import-outside-toplevel
+import codecs
+import contextlib
+import multiprocessing
+import subprocess
+import tarfile
+import tempfile
+import threading
+import urllib
+import zipfile
+from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
+
+
+def test_codecs_open():
+ fh = codecs.open("test.txt", "utf8") # [consider-using-with]
+ fh.close()
+
+
+def test_urlopen():
+ _ = urllib.request.urlopen("http://www.python.org") # [consider-using-with]
+
+
+def test_temporary_file():
+ _ = tempfile.TemporaryFile("r") # [consider-using-with]
+
+
+def test_named_temporary_file():
+ _ = tempfile.NamedTemporaryFile("r") # [consider-using-with]
+
+
+def test_spooled_temporary_file():
+ _ = tempfile.SpooledTemporaryFile("r") # [consider-using-with]
+
+
+def test_temporary_directory():
+ _ = tempfile.TemporaryDirectory() # [consider-using-with]
+
+
+def test_zipfile():
+ myzip = zipfile.ZipFile("spam.zip", "w") # [consider-using-with]
+ _ = myzip.open("eggs.txt") # [consider-using-with]
+
+
+def test_pyzipfile():
+ myzip = zipfile.PyZipFile("spam.zip", "w") # [consider-using-with]
+
+ with zipfile.PyZipFile("spam.zip", "w"): # must not trigger
+ pass
+
+ _ = myzip.open("eggs.txt") # [consider-using-with]
+
+ with myzip.open("eggs.txt"): # must not trigger
+ pass
+
+
+def test_tarfile():
+ tf = tarfile.open("/tmp/test.tar", "w") # [consider-using-with]
+ tf.close()
+
+ with tarfile.open("/tmp/test.tar", "w"): # must not trigger
+ pass
+
+ tf = tarfile.TarFile("/tmp/test2.tar", "w") # [consider-using-with]
+ tf.close()
+
+ with tarfile.TarFile("/tmp/test2.tar", "w"): # must not trigger
+ pass
+
+
+def test_lock_acquisition():
+ lock = threading.Lock()
+ lock.acquire() # [consider-using-with]
+ lock.release()
+
+ with lock: # must not trigger
+ pass
+
+ rlock = threading.RLock()
+ rlock.acquire() # [consider-using-with]
+ rlock.release()
+
+ with rlock: # must not trigger
+ pass
+
+ sema = threading.Semaphore()
+ sema.acquire() # [consider-using-with]
+ sema.release()
+
+ with sema: # must not trigger
+ pass
+
+ bounded_sema = threading.BoundedSemaphore()
+ bounded_sema.acquire() # [consider-using-with]
+ bounded_sema.release()
+
+ with bounded_sema: # must not trigger
+ pass
+
+
+@contextlib.contextmanager
+def test_lock_acquisition_in_context_manager1():
+ """
+ The message must not be triggered if the resource allocation is done inside a context manager.
+ """
+ lock = threading.Lock()
+ lock.acquire() # must not trigger
+ yield
+ lock.release()
+
+
+class MyLockContext:
+ """
+ The message must not be triggered if the resource allocation is done inside a context manager.
+ """
+
+ def __init__(self):
+ self.lock = threading.Lock()
+
+ def __enter__(self):
+ self.lock.acquire() # must not trigger
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.lock.release()
+
+
+def test_multiprocessing():
+ # the different Locks provided by multiprocessing would be candidates
+ # for consider-using-with as well, but they lead to InferenceErrors.
+ _ = multiprocessing.Pool() # [consider-using-with]
+ with multiprocessing.Pool():
+ pass
+
+ manager = multiprocessing.managers.BaseManager()
+ manager.start() # [consider-using-with]
+ with multiprocessing.managers.BaseManager():
+ pass
+
+ manager = multiprocessing.managers.SyncManager()
+ manager.start() # [consider-using-with]
+ with multiprocessing.managers.SyncManager():
+ pass
+
+
+def test_popen():
+ _ = subprocess.Popen("sh") # [consider-using-with]
+ with subprocess.Popen("sh"):
+ pass
+
+
+def test_suppress_in_exit_stack():
+ """Regression test for issue #4654 (false positive)"""
+ with contextlib.ExitStack() as stack:
+ _ = stack.enter_context(
+ open("/sys/firmware/devicetree/base/hwid,location", "r", encoding="utf-8")
+ ) # must not trigger
+
+
+def test_futures():
+ """
+ Regression test for issue #4689.
+ ThreadPoolExecutor and ProcessPoolExecutor were formerly part of the callables that raised
+ the R1732 message if used outside a with block, but there are legitimate use cases where
+ Executor instances are used e.g. as a persistent background worker pool throughout the program.
+ """
+ thread_executor = ThreadPoolExecutor()
+ thread_executor.submit(print, 1)
+ process_executor = ProcessPoolExecutor()
+ process_executor.submit(print, 2)
+ thread_executor.shutdown()
+ process_executor.shutdown()
+
+
+pool = multiprocessing.Pool() # must not trigger, as it is used later on
+with pool:
+ pass
+
+
+global_pool = (
+ multiprocessing.Pool()
+) # must not trigger, will be used in nested scope
+
+
+def my_nested_function():
+ with global_pool:
+ pass
+
+
+# this must also work for tuple unpacking
+pool1, pool2 = (
+ multiprocessing.Pool(), # must not trigger
+ multiprocessing.Pool(), # must not trigger
+)
+
+with pool1:
+ pass
+
+with pool2:
+ pass
+
+unused_pool1, unused_pool2 = (
+ multiprocessing.Pool(), # [consider-using-with]
+ multiprocessing.Pool(), # [consider-using-with]
+)
+
+used_pool, unused_pool = (
+ multiprocessing.Pool(), # must not trigger
+ multiprocessing.Pool(), # [consider-using-with]
+)
+with used_pool:
+ pass
+
+unused_pool, used_pool = (
+ multiprocessing.Pool(), # [consider-using-with]
+ multiprocessing.Pool(), # must not trigger
+)
+with used_pool:
+ pass
+
+
+def test_subscript_assignment():
+ """
+ Regression test for issue https://github.com/PyCQA/pylint/issues/4732.
+ If a context manager is assigned to a list or dict, we are not able to
+ tell if / how the context manager is used later on, as it is not assigned
+ to a variable or attribute directly.
+ In this case we can only emit the message directly.
+ """
+ job_list = [None, None]
+ job_list[0] = subprocess.Popen("ls") # [consider-using-with]
+ job_dict = {}
+ job_dict["myjob"] = subprocess.Popen("ls") # [consider-using-with]
diff --git a/tests/functional/c/consider/consider_using_with.txt b/tests/functional/c/consider/consider_using_with.txt
new file mode 100644
index 000000000..45311b15b
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_with.txt
@@ -0,0 +1,26 @@
+consider-using-with:15:9:test_codecs_open:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:20:8:test_urlopen:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:24:8:test_temporary_file:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:28:8:test_named_temporary_file:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:32:8:test_spooled_temporary_file:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:36:8:test_temporary_directory:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:40:12:test_zipfile:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:41:8:test_zipfile:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:45:12:test_pyzipfile:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:50:8:test_pyzipfile:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:57:9:test_tarfile:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:63:9:test_tarfile:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:72:4:test_lock_acquisition:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:79:4:test_lock_acquisition:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:86:4:test_lock_acquisition:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:93:4:test_lock_acquisition:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:129:8:test_multiprocessing:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:134:4:test_multiprocessing:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:139:4:test_multiprocessing:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:145:8:test_popen:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:201:4::Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:202:4::Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:207:4::Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:213:4::Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:229:18:test_subscript_assignment:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:231:24:test_subscript_assignment:Consider using 'with' for resource-allocating operations:HIGH
diff --git a/tests/functional/c/consider/consider_using_with_open.py b/tests/functional/c/consider/consider_using_with_open.py
new file mode 100644
index 000000000..7c584d8f5
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_with_open.py
@@ -0,0 +1,161 @@
+# pylint: disable=missing-function-docstring, missing-module-docstring, invalid-name, import-outside-toplevel, no-self-use
+# pylint: disable=missing-class-docstring, too-few-public-methods, unused-variable, multiple-statements, line-too-long
+"""
+The functional test for the standard ``open()`` function has to be moved in a separate file,
+because PyPy has to be excluded for the tests as the ``open()`` function is uninferable in PyPy.
+However, all remaining checks for consider-using-with work in PyPy, so we do not want to exclude
+PyPy from ALL functional tests.
+"""
+from contextlib import contextmanager
+from pathlib import Path
+
+myfile = open("test.txt", encoding="utf-8") # [consider-using-with]
+
+
+def test_open():
+ fh = open("test.txt", encoding="utf-8") # [consider-using-with]
+ fh.close()
+
+ with open("test.txt", encoding="utf-8") as fh: # must not trigger
+ fh.read()
+
+
+def test_open_in_enter():
+ """Message must not trigger if the resource is allocated in a context manager."""
+
+ class MyContextManager:
+ def __init__(self):
+ self.file_handle = None
+
+ def __enter__(self):
+ self.file_handle = open("foo.txt", "w", encoding="utf-8") # must not trigger
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.file_handle.close()
+
+
+@contextmanager
+def test_open_in_with_contextlib():
+ """Message must not trigger if the resource is allocated in a context manager."""
+ file_handle = open("foo.txt", "w", encoding="utf-8") # must not trigger
+ yield file_handle
+ file_handle.close()
+
+
+def test_open_outside_assignment():
+ open("foo", encoding="utf-8").read() # [consider-using-with]
+ content = open("foo", encoding="utf-8").read() # [consider-using-with]
+
+
+def test_open_inside_with_block():
+ with open("foo", encoding="utf-8") as fh:
+ open("bar", encoding="utf-8") # [consider-using-with]
+
+
+def test_ternary_if_in_with_block(file1, file2, which):
+ """Regression test for issue #4676 (false positive)"""
+ with (open(file1, encoding="utf-8") if which else open(file2, encoding="utf-8")) as input_file: # must not trigger
+ return input_file.read()
+
+
+def test_single_line_with(file1):
+ with open(file1, encoding="utf-8"): return file1.read() # must not trigger
+
+
+def test_multiline_with_items(file1, file2, which):
+ with (open(file1, encoding="utf-8") if which
+ else open(file2, encoding="utf-8")) as input_file: return input_file.read()
+
+
+def test_suppress_on_return():
+ return open("foo", encoding="utf8") # must not trigger
+
+
+class TestControlFlow:
+ """
+ The message is triggered if a context manager is assigned to a variable, which name is later
+ reassigned without the variable being used inside a ``with`` first.
+ E.g. the following would trigger the message:
+
+ a = open("foo") # <-- would trigger here
+ a = "something new"
+
+ But it must not happen that the logic which checks if the same variable is assigned multiple
+ times in different code branches where only one of those assign statements is hit at runtime.
+ For example, the variable could be assigned in an if-else construct.
+
+ These tests check that the message is not triggered in those circumstances.
+ """
+
+ def test_defined_in_if_and_else(self, predicate):
+ if predicate:
+ file_handle = open("foo", encoding="utf8") # must not trigger
+ else:
+ file_handle = open("bar", encoding="utf8") # must not trigger
+ with file_handle:
+ return file_handle.read()
+
+ def test_defined_in_else_only(self, predicate):
+ if predicate:
+ result = "shiny watermelon"
+ else:
+ file_handle = open("foo", encoding="utf8") # must not trigger
+ with file_handle:
+ result = file_handle.read()
+ return result
+
+ def test_defined_in_if_only(self, predicate):
+ if predicate:
+ file_handle = open("foo", encoding="utf8") # must not trigger
+ with file_handle:
+ result = file_handle.read()
+ else:
+ result = "shiny watermelon"
+ return result
+
+ def test_triggers_if_reassigned_after_if_else(self, predicate):
+ if predicate:
+ file_handle = open("foo", encoding="utf8")
+ else:
+ file_handle = open( # [consider-using-with]
+ "bar", encoding="utf8"
+ )
+ file_handle = None
+ return file_handle
+
+ def test_defined_in_try_and_except(self):
+ try:
+ file_handle = open("foo", encoding="utf8") # must not trigger
+ except FileNotFoundError:
+ file_handle = open("bar", encoding="utf8") # must not trigger
+ with file_handle:
+ return file_handle.read()
+
+ def test_defined_in_try_and_finally(self):
+ try:
+ file_handle = open("foo", encoding="utf8") # must not trigger
+ except FileNotFoundError:
+ Path("foo").touch()
+ finally:
+ file_handle.open("foo", encoding="utf") # must not trigger
+ with file_handle:
+ return file_handle.read()
+
+ def test_defined_in_different_except_handlers(self, a, b):
+ try:
+ result = a/b
+ except ZeroDivisionError:
+ logfile = open("math_errors.txt", encoding="utf8") # must not trigger
+ result = "Can't divide by zero"
+ except TypeError:
+ logfile = open("type_errors.txt", encoding="utf8") # must not trigger
+ result = "Wrong types"
+ else:
+ logfile = open("results.txt", encoding="utf8") # must not trigger
+ with logfile:
+ logfile.write(result)
+
+ def test_multiple_return_statements(self, predicate):
+ if predicate:
+ return open("foo", encoding="utf8") # must not trigger
+ return open("bar", encoding="utf8") # must not trigger
diff --git a/tests/functional/c/consider/consider_using_with_open.rc b/tests/functional/c/consider/consider_using_with_open.rc
new file mode 100644
index 000000000..b47a74525
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_with_open.rc
@@ -0,0 +1,2 @@
+[testoptions]
+except_implementations=PyPy
diff --git a/tests/functional/c/consider/consider_using_with_open.txt b/tests/functional/c/consider/consider_using_with_open.txt
new file mode 100644
index 000000000..cf3fdc1c8
--- /dev/null
+++ b/tests/functional/c/consider/consider_using_with_open.txt
@@ -0,0 +1,6 @@
+consider-using-with:12:9::Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:16:9:test_open:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:46:4:test_open_outside_assignment:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:47:14:test_open_outside_assignment:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:52:8:test_open_inside_with_block:Consider using 'with' for resource-allocating operations:HIGH
+consider-using-with:120:26:TestControlFlow.test_triggers_if_reassigned_after_if_else:Consider using 'with' for resource-allocating operations:HIGH
diff --git a/tests/functional/c/continue_in_finally.py b/tests/functional/c/continue_in_finally.py
new file mode 100644
index 000000000..bb1fcbab1
--- /dev/null
+++ b/tests/functional/c/continue_in_finally.py
@@ -0,0 +1,23 @@
+"""Test that `continue` is catched when met inside a `finally` clause."""
+
+# pylint: disable=missing-docstring, lost-exception, broad-except
+
+while True:
+ try:
+ pass
+ finally:
+ continue # [continue-in-finally]
+
+while True:
+ try:
+ pass
+ finally:
+ break
+
+while True:
+ try:
+ pass
+ except Exception:
+ pass
+ else:
+ continue
diff --git a/tests/functional/c/continue_in_finally.rc b/tests/functional/c/continue_in_finally.rc
new file mode 100644
index 000000000..67a28a36a
--- /dev/null
+++ b/tests/functional/c/continue_in_finally.rc
@@ -0,0 +1,2 @@
+[testoptions]
+max_pyver=3.8
diff --git a/tests/functional/c/continue_in_finally.txt b/tests/functional/c/continue_in_finally.txt
new file mode 100644
index 000000000..3a5e542e1
--- /dev/null
+++ b/tests/functional/c/continue_in_finally.txt
@@ -0,0 +1 @@
+continue-in-finally:9:::'continue' not supported inside 'finally' clause
diff --git a/tests/functional/c/control_pragmas.py b/tests/functional/c/control_pragmas.py
new file mode 100644
index 000000000..993f9a5dd
--- /dev/null
+++ b/tests/functional/c/control_pragmas.py
@@ -0,0 +1,19 @@
+# pylint: disable=missing-docstring
+
+
+def test_pragma():
+ """Test that the control pragmas are not too eager to consume the entire line
+
+ We should stop either at:
+ - ; or #
+ - or at the end of line
+ """
+ # noqa: E501 # pylint: disable=unused-variable #nosec
+ variable = 1
+
+ # noqa # pylint: disable=undefined-variable,no-member; don't trigger
+ other_variable = some_variable + variable.member
+
+ # noqa # pylint: disable=unbalanced-tuple-unpacking,no-member # no trigger
+ first, second = some_other_variable
+ return first + other_variable.method()
diff --git a/tests/functional/c/crash_missing_module_type.py b/tests/functional/c/crash_missing_module_type.py
new file mode 100644
index 000000000..308606ff4
--- /dev/null
+++ b/tests/functional/c/crash_missing_module_type.py
@@ -0,0 +1,18 @@
+""" Test for a crash found in
+https://bitbucket.org/logilab/astroid/issue/45/attributeerror-module-object-has-no#comment-11944673
+"""
+# pylint: disable=no-init, invalid-name, too-few-public-methods, redefined-outer-name, useless-object-inheritance
+def decor(trop):
+ """ decorator """
+ return trop
+
+class Foo(object):
+ """ Class """
+ @decor
+ def prop(self):
+ """ method """
+ return self
+
+if __name__ == '__main__':
+ trop = Foo()
+ trop.prop = 42
diff --git a/tests/functional/c/ctor_arguments.py b/tests/functional/c/ctor_arguments.py
new file mode 100644
index 000000000..ee10413e3
--- /dev/null
+++ b/tests/functional/c/ctor_arguments.py
@@ -0,0 +1,104 @@
+"""Test function argument checker on __init__
+
+Based on tests/functional/a/arguments.py
+"""
+# pylint: disable=missing-docstring,too-few-public-methods,super-init-not-called,useless-object-inheritance
+
+
+class Class1Arg(object):
+ def __init__(self, first_argument):
+ """one argument function"""
+
+class Class3Arg(object):
+ def __init__(self, first_argument, second_argument, third_argument):
+ """three arguments function"""
+
+class ClassDefaultArg(object):
+ def __init__(self, one=1, two=2):
+ """function with default value"""
+
+class Subclass1Arg(Class1Arg):
+ pass
+
+class ClassAllArgs(Class1Arg):
+ def __init__(self, *args, **kwargs):
+ pass
+
+class ClassMultiInheritance(Class1Arg, Class3Arg):
+ pass
+
+class ClassNew(object):
+ def __new__(cls, first_argument, kwarg=None):
+ return first_argument, kwarg
+
+Class1Arg(420)
+Class1Arg() # [no-value-for-parameter]
+Class1Arg(1337, 347) # [too-many-function-args]
+
+Class3Arg(420, 789) # [no-value-for-parameter]
+# +1:[no-value-for-parameter,no-value-for-parameter,no-value-for-parameter]
+Class3Arg()
+Class3Arg(1337, 347, 456)
+Class3Arg('bab', 'bebe', None, 5.6) # [too-many-function-args]
+
+ClassDefaultArg(1, two=5)
+ClassDefaultArg(two=5)
+
+Class1Arg(bob=4) # [no-value-for-parameter,unexpected-keyword-arg]
+ClassDefaultArg(1, 4, coin="hello") # [unexpected-keyword-arg]
+
+ClassDefaultArg(1, one=5) # [redundant-keyword-arg]
+
+Subclass1Arg(420)
+Subclass1Arg() # [no-value-for-parameter]
+Subclass1Arg(1337, 347) # [too-many-function-args]
+
+ClassAllArgs()
+ClassAllArgs(1, 2, 3, even=4, more=5)
+
+ClassMultiInheritance(1)
+ClassMultiInheritance(1, 2, 3) # [too-many-function-args]
+
+ClassNew(1, kwarg=1)
+ClassNew(1, 2, 3) # [too-many-function-args]
+ClassNew(one=2) # [no-value-for-parameter,unexpected-keyword-arg]
+
+
+class Metaclass(type):
+ def __new__(cls, name, bases, namespace):
+ return type.__new__(cls, name, bases, namespace)
+
+def with_metaclass(meta, base=object):
+ """Create a new type that can be used as a metaclass."""
+ return meta("NewBase", (base, ), {})
+
+class ClassWithMeta(with_metaclass(Metaclass)):
+ pass
+
+ClassWithMeta()
+
+
+class BuiltinExc(Exception):
+ def __init__(self, val=True):
+ self.val = val
+
+BuiltinExc(42, 24, badarg=1) # [too-many-function-args,unexpected-keyword-arg]
+
+
+class Clsmethod(object):
+ def __init__(self, first, second):
+ self.first = first
+ self.second = second
+
+ @classmethod
+ def from_nothing(cls):
+ return cls(1, 2, 3, 4) # [too-many-function-args]
+
+ @classmethod
+ def from_nothing1(cls):
+ return cls() # [no-value-for-parameter,no-value-for-parameter]
+
+ @classmethod
+ def from_nothing2(cls):
+ # +1: [no-value-for-parameter,unexpected-keyword-arg]
+ return cls(1, not_argument=2)
diff --git a/tests/functional/c/ctor_arguments.txt b/tests/functional/c/ctor_arguments.txt
new file mode 100644
index 000000000..781715c0a
--- /dev/null
+++ b/tests/functional/c/ctor_arguments.txt
@@ -0,0 +1,24 @@
+no-value-for-parameter:35:0::No value for argument 'first_argument' in constructor call
+too-many-function-args:36:0::Too many positional arguments for constructor call
+no-value-for-parameter:38:0::No value for argument 'third_argument' in constructor call
+no-value-for-parameter:40:0::No value for argument 'first_argument' in constructor call
+no-value-for-parameter:40:0::No value for argument 'second_argument' in constructor call
+no-value-for-parameter:40:0::No value for argument 'third_argument' in constructor call
+too-many-function-args:42:0::Too many positional arguments for constructor call
+no-value-for-parameter:47:0::No value for argument 'first_argument' in constructor call
+unexpected-keyword-arg:47:0::Unexpected keyword argument 'bob' in constructor call
+unexpected-keyword-arg:48:0::Unexpected keyword argument 'coin' in constructor call
+redundant-keyword-arg:50:0::Argument 'one' passed by position and keyword in constructor call
+no-value-for-parameter:53:0::No value for argument 'first_argument' in constructor call
+too-many-function-args:54:0::Too many positional arguments for constructor call
+too-many-function-args:60:0::Too many positional arguments for constructor call
+too-many-function-args:63:0::Too many positional arguments for constructor call
+no-value-for-parameter:64:0::No value for argument 'first_argument' in constructor call
+unexpected-keyword-arg:64:0::Unexpected keyword argument 'one' in constructor call
+too-many-function-args:85:0::Too many positional arguments for constructor call
+unexpected-keyword-arg:85:0::Unexpected keyword argument 'badarg' in constructor call
+too-many-function-args:95:15:Clsmethod.from_nothing:Too many positional arguments for constructor call
+no-value-for-parameter:99:15:Clsmethod.from_nothing1:No value for argument 'first' in constructor call
+no-value-for-parameter:99:15:Clsmethod.from_nothing1:No value for argument 'second' in constructor call
+no-value-for-parameter:104:15:Clsmethod.from_nothing2:No value for argument 'second' in constructor call
+unexpected-keyword-arg:104:15:Clsmethod.from_nothing2:Unexpected keyword argument 'not_argument' in constructor call