summaryrefslogtreecommitdiff
path: root/pylint/checkers/classes.py
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2015-05-14 11:23:57 +0300
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2015-05-14 11:23:57 +0300
commit62f40c65ee8c1f5283b343ca23a6efb76b283633 (patch)
treef3f3b5ee5ce5c74947db6a314aae265a589ea1b2 /pylint/checkers/classes.py
parentb76e31d092c793b4f74c09a1ae2b412478dd4f32 (diff)
downloadpylint-git-62f40c65ee8c1f5283b343ca23a6efb76b283633.tar.gz
Add a new warning 'unexpected-special-method-signature'.
This is emitted when a special method (dunder method) doesn't have the expected signature, which can lead to actual errors in the application code. Closes issue #253.
Diffstat (limited to 'pylint/checkers/classes.py')
-rw-r--r--pylint/checkers/classes.py61
1 files changed, 58 insertions, 3 deletions
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 9c5ba9f35..c63a79ac5 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -28,9 +28,11 @@ from astroid.exceptions import InconsistentMroError, DuplicateBasesError
from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker
from pylint.checkers.utils import (
- PYMETHODS, overrides_a_method, check_messages, is_attr_private,
+ PYMETHODS, SPECIAL_METHODS_PARAMS,
+ overrides_a_method, check_messages, is_attr_private,
is_attr_protected, node_frame_class, safe_infer, is_builtin_object,
- decorated_with_property, unimplemented_abstract_methods)
+ decorated_with_property, unimplemented_abstract_methods,
+ decorated_with)
from pylint.utils import deprecated_option, get_global_option
import six
@@ -891,15 +893,68 @@ class SpecialMethodsChecker(BaseChecker):
'iterable (i.e. has no `%s` method)' % NEXT_METHOD,
{'old_names': [('W0234', 'non-iterator-returned'),
('E0234', 'non-iterator-returned')]}),
+ 'E0302': ('The special method %r expects %s param(s), %d %s given',
+ 'unexpected-special-method-signature',
+ 'Emitted when a special method was defined with an '
+ 'invalid number of parameters. If it has too few or '
+ 'too many, it might not work at all.'),
}
priority = -2
- @check_messages('non-iterator-returned')
+ @check_messages('unexpected-special-method-signature',
+ 'non-iterator-returned')
def visit_function(self, node):
if not node.is_method():
return
if node.name == '__iter__':
self._check_iter(node)
+ if node.name in PYMETHODS:
+ self._check_unexpected_method_signature(node)
+
+ def _check_unexpected_method_signature(self, node):
+ expected_params = SPECIAL_METHODS_PARAMS[node.name]
+
+ if expected_params is None:
+ # This can support a variable number of parameters.
+ return
+ if not len(node.args.args) and not node.args.vararg:
+ # Method has no parameter, will be catched
+ # by no-method-argument.
+ return
+
+ if decorated_with(node, [BUILTINS + ".staticmethod"]):
+ # We expect to not take in consideration self.
+ all_args = node.args.args
+ else:
+ all_args = node.args.args[1:]
+ mandatory = len(all_args) - len(node.args.defaults)
+ optional = len(node.args.defaults)
+ current_params = mandatory + optional
+
+ if isinstance(expected_params, tuple):
+ # The expected number of parameters can be any value from this
+ # tuple, although the user should implement the method
+ # to take all of them in consideration.
+ emit = mandatory not in expected_params
+ expected_params = "between %d or %d" % expected_params
+ else:
+ # If the number of mandatory parameters doesn't
+ # suffice, the expected parameters for this
+ # function will be deduced from the optional
+ # parameters.
+ rest = expected_params - mandatory
+ if rest == 0:
+ emit = False
+ elif rest < 0:
+ emit = True
+ elif rest > 0:
+ emit = not ((optional - rest) >= 0 or node.args.vararg)
+
+ if emit:
+ verb = "was" if current_params <= 1 else "were"
+ self.add_message('unexpected-special-method-signature',
+ args=(node.name, expected_params, current_params, verb),
+ node=node)
def _check_iter(self, node):
try: