diff options
author | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-05-14 11:23:57 +0300 |
---|---|---|
committer | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-05-14 11:23:57 +0300 |
commit | 62f40c65ee8c1f5283b343ca23a6efb76b283633 (patch) | |
tree | f3f3b5ee5ce5c74947db6a314aae265a589ea1b2 /pylint/checkers/classes.py | |
parent | b76e31d092c793b4f74c09a1ae2b412478dd4f32 (diff) | |
download | pylint-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.py | 61 |
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: |