From 4f85cfbec5bd5576754274adb351116e164bc19a Mon Sep 17 00:00:00 2001 From: Dmitry Pribysh Date: Tue, 27 Oct 2015 18:03:15 +0300 Subject: Make iterable checker skip classes that are inferred to be abstract --- pylint/checkers/classes.py | 11 +----- pylint/checkers/typecheck.py | 6 ++-- pylint/checkers/utils.py | 11 ++++++ pylint/test/functional/iterable_context.py | 18 +++++++++- pylint/test/functional/mapping_context.py | 40 +++++++++++++++++++++- pylint/test/functional/membership_protocol.py | 46 +++++++++++++++++++++++--- pylint/test/functional/membership_protocol.txt | 14 ++++---- 7 files changed, 121 insertions(+), 25 deletions(-) diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index 86ccb19..c65e10c 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -34,7 +34,7 @@ from pylint.checkers.utils import ( overrides_a_method, check_messages, is_attr_private, is_attr_protected, node_frame_class, is_builtin_object, decorated_with_property, unimplemented_abstract_methods, - decorated_with) + decorated_with, class_is_abstract) from pylint.utils import deprecated_option, get_global_option import six @@ -102,15 +102,6 @@ def _called_in_methods(func, klass, methods): return True return False -def class_is_abstract(node): - """return true if the given class node should be considered as an abstract - class - """ - for method in node.methods(): - if method.parent.frame() is node: - if method.is_abstract(pass_is_abstract=False): - return True - return False def _is_attribute_property(name, klass): """ Check if the given attribute *name* is a property diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 35b18fb..7baaaf6 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -35,7 +35,7 @@ from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE from pylint.checkers import BaseChecker from pylint.checkers.utils import ( is_super, check_messages, decorated_with_property, - decorated_with, node_ignores_exception) + decorated_with, node_ignores_exception, class_is_abstract) from pylint import utils @@ -116,12 +116,14 @@ def _is_abstract_class_name(name): lname = name.lower() is_mixin = lname.endswith('mixin') is_abstract = lname.startswith('abstract') - is_base = lname.startswith('base') + is_base = lname.startswith('base') or lname.endswith('base') return is_mixin or is_abstract or is_base def _is_inside_abstract_class(node): while node is not None: if isinstance(node, astroid.ClassDef): + if class_is_abstract(node): + return True name = getattr(node, 'name', None) if name is not None and _is_abstract_class_name(name): return True diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 443e32d..8873d0e 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -573,6 +573,17 @@ def node_ignores_exception(node, exception): return False +def class_is_abstract(node): + """return true if the given class node should be considered as an abstract + class + """ + for method in node.methods(): + if method.parent.frame() is node: + if method.is_abstract(pass_is_abstract=False): + return True + return False + + # TODO(cpopa): deprecate these or leave them as aliases? safe_infer = astroid.helpers.safe_infer has_known_bases = astroid.helpers.has_known_bases diff --git a/pylint/test/functional/iterable_context.py b/pylint/test/functional/iterable_context.py index 9c5d1b6..fa4e617 100644 --- a/pylint/test/functional/iterable_context.py +++ b/pylint/test/functional/iterable_context.py @@ -149,7 +149,10 @@ class BaseType(object): return True else: # error should not be emitted here - return value in self.valid_values + for v in self.valid_values: + if value == v: + return True + return False class AbstractUrlMarkManager(object): def __init__(self): @@ -161,3 +164,16 @@ class AbstractUrlMarkManager(object): def _init_lineparser(self): raise NotImplementedError + +# class is not named as abstract +# but still is deduceably abstract +class UrlMarkManager(object): + def __init__(self): + self._lineparser = None + self._init_lineparser() + # error should not be emitted here + for line in self._lineparser: + print(line) + + def _init_lineparser(self): + raise NotImplementedError diff --git a/pylint/test/functional/mapping_context.py b/pylint/test/functional/mapping_context.py index cfab8dc..72457b8 100644 --- a/pylint/test/functional/mapping_context.py +++ b/pylint/test/functional/mapping_context.py @@ -36,7 +36,7 @@ class NotMapping(object): test(**NotMapping()) # [not-a-mapping] -# skip checks if statement is inside mixin class +# skip checks if statement is inside mixin/base/abstract class class SomeMixin(object): kwargs = None @@ -50,6 +50,44 @@ class SomeMixin(object): kws = self.get_kwargs() self.run(**kws) +class AbstractThing(object): + kwargs = None + + def get_kwargs(self): + return self.kwargs + + def run(self, **kwargs): + print(kwargs) + + def dispatch(self): + kws = self.get_kwargs() + self.run(**kws) + +class BaseThing(object): + kwargs = None + + def get_kwargs(self): + return self.kwargs + + def run(self, **kwargs): + print(kwargs) + + def dispatch(self): + kws = self.get_kwargs() + self.run(**kws) + +# abstract class +class Thing(object): + def get_kwargs(self): + raise NotImplementedError + + def run(self, **kwargs): + print(kwargs) + + def dispatch(self): + kwargs = self.get_kwargs() + self.run(**kwargs) + # skip uninferable instances from some_missing_module import Mapping diff --git a/pylint/test/functional/membership_protocol.py b/pylint/test/functional/membership_protocol.py index 7b3a46f..0fd4886 100644 --- a/pylint/test/functional/membership_protocol.py +++ b/pylint/test/functional/membership_protocol.py @@ -5,9 +5,9 @@ 1 in {'a': 1, 'b': 2} 1 in {1, 2, 3} 1 in (1, 2, 3) -1 in "123" -1 in u"123" -1 in bytearray(b"123") +'1' in "123" +'1' in u"123" +'1' in bytearray(b"123") 1 in frozenset([1, 2, 3]) # comprehensions @@ -59,7 +59,7 @@ class MaybeIterable(ImportedClass): 10 in MaybeIterable() -# do not emit warning inside mixins +# do not emit warning inside mixins/abstract/base classes class UsefulMixin(object): stuff = None @@ -71,6 +71,44 @@ class UsefulMixin(object): if thing in stuff: pass +class BaseThing(object): + valid_values = None + + def validate(self, value): + if self.valid_values is None: + return True + else: + # error should not be emitted here + return value in self.valid_values + +class AbstractThing(object): + valid_values = None + + def validate(self, value): + if self.valid_values is None: + return True + else: + # error should not be emitted here + return value in self.valid_values + +# class is not named as abstract +# but still is deduceably abstract +class Thing(object): + valid_values = None + + def __init__(self): + self._init_values() + + def validate(self, value): + if self.valid_values is None: + return True + else: + # error should not be emitted here + return value in self.valid_values + + def _init_values(self): + raise NotImplementedError + # error cases 42 in 42 # [unsupported-membership-test] 42 not in None # [unsupported-membership-test] diff --git a/pylint/test/functional/membership_protocol.txt b/pylint/test/functional/membership_protocol.txt index 6e9bd8e..edb2227 100644 --- a/pylint/test/functional/membership_protocol.txt +++ b/pylint/test/functional/membership_protocol.txt @@ -1,7 +1,7 @@ -unsupported-membership-test:75::Value '42' doesn't support membership test -unsupported-membership-test:76::Value 'None' doesn't support membership test -unsupported-membership-test:77::Value '8.5' doesn't support membership test -unsupported-membership-test:82::Value 'EmptyClass()' doesn't support membership test -unsupported-membership-test:83::Value 'EmptyClass' doesn't support membership test -unsupported-membership-test:84::Value 'count' doesn't support membership test -unsupported-membership-test:85::Value 'range' doesn't support membership test +unsupported-membership-test:113::Value '42' doesn't support membership test +unsupported-membership-test:114::Value 'None' doesn't support membership test +unsupported-membership-test:115::Value '8.5' doesn't support membership test +unsupported-membership-test:120::Value 'EmptyClass()' doesn't support membership test +unsupported-membership-test:121::Value 'EmptyClass' doesn't support membership test +unsupported-membership-test:122::Value 'count' doesn't support membership test +unsupported-membership-test:123::Value 'range' doesn't support membership test -- cgit v1.2.1