summaryrefslogtreecommitdiff
path: root/pylint/checkers/classes.py
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2019-07-18 10:34:28 +0200
committerClaudiu Popa <pcmanticore@gmail.com>2019-07-18 10:34:28 +0200
commit558e5b10d14dd81522affc7695dabd36273c5b60 (patch)
treeadcfbfc1364233b79a6550a1f0329ff853d73071 /pylint/checkers/classes.py
parentf38bd0d1b5d2e601d2a6034b3f0d6ee11343e26e (diff)
downloadpylint-git-558e5b10d14dd81522affc7695dabd36273c5b60.tar.gz
Added a new check, ``invalid-overridden-method``
This check is emitted when we detect that a method is overridden as a property or a property is overridden as a method. This can indicate a bug in the application code that will trigger a runtime error. Close #2670
Diffstat (limited to 'pylint/checkers/classes.py')
-rw-r--r--pylint/checkers/classes.py38
1 files changed, 34 insertions, 4 deletions
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 31a3f574d..79724656a 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -56,6 +56,7 @@ from pylint.checkers.utils import (
is_comprehension,
is_iterable,
is_property_setter,
+ is_property_setter_or_deleter,
is_protocol_class,
node_frame_class,
overrides_a_method,
@@ -70,7 +71,7 @@ if sys.version_info >= (3, 0):
else:
NEXT_METHOD = "next"
INVALID_BASE_CLASSES = {"bool", "range", "slice", "memoryview"}
-
+BUILTIN_DECORATORS = {"builtins.property", "builtins.classmethod"}
# Dealing with useless override detection, with regard
# to parameters vs arguments
@@ -566,6 +567,13 @@ MSGS = {
"relying on super() delegation to do the same thing as another method "
"from the MRO.",
),
+ "W0236": (
+ "Method %r was expected to be %r, found it instead as %r",
+ "invalid-overridden-method",
+ "Used when we detect that a method was overridden as a property "
+ "or the other way around, which could result in potential bugs at "
+ "runtime.",
+ ),
"E0236": (
"Invalid object %r in __slots__, must contain only non empty strings",
"invalid-slots-object",
@@ -890,16 +898,18 @@ a metaclass class method.",
for overridden in klass.local_attr_ancestors(node.name):
# get astroid for the searched method
try:
- meth_node = overridden[node.name]
+ parent_function = overridden[node.name]
except KeyError:
# we have found the method but it's not in the local
# dictionary.
# This may happen with astroid build from living objects
continue
- if not isinstance(meth_node, astroid.FunctionDef):
+ if not isinstance(parent_function, astroid.FunctionDef):
continue
- self._check_signature(node, meth_node, "overridden", klass)
+ self._check_signature(node, parent_function, "overridden", klass)
+ self._check_invalid_overridden_method(node, parent_function)
break
+
if node.decorators:
for decorator in node.decorators.nodes:
if isinstance(decorator, astroid.Attribute) and decorator.attrname in (
@@ -1064,6 +1074,26 @@ a metaclass class method.",
if node.args.args and len(node.args.args) > 1 and decorated_with_property(node):
self.add_message("property-with-parameters", node=node)
+ def _check_invalid_overridden_method(self, function_node, parent_function_node):
+ parent_is_property = decorated_with_property(
+ parent_function_node
+ ) or is_property_setter_or_deleter(parent_function_node)
+ current_is_property = decorated_with_property(
+ function_node
+ ) or is_property_setter_or_deleter(function_node)
+ if parent_is_property and not current_is_property:
+ self.add_message(
+ "invalid-overridden-method",
+ args=(function_node.name, "property", function_node.type),
+ node=function_node,
+ )
+ elif not parent_is_property and current_is_property:
+ self.add_message(
+ "invalid-overridden-method",
+ args=(function_node.name, "method", "property"),
+ node=function_node,
+ )
+
def _check_slots(self, node):
if "__slots__" not in node.locals:
return