diff options
Diffstat (limited to 'tests/functional/c')
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 |