diff options
author | cpopa <devnull@localhost> | 2014-01-10 16:44:37 +0200 |
---|---|---|
committer | cpopa <devnull@localhost> | 2014-01-10 16:44:37 +0200 |
commit | e22629f80ebbd2abf2f0843a56d5d77933ab4297 (patch) | |
tree | e96bb1f8893abccb2c81d06dda872c9a95b58b60 /checkers | |
parent | d0c4b03f255d6960f9ae4078e8fd24ff22b96d92 (diff) | |
parent | edbb10fb6ebe63f4bcf03fbfea7393eac9fb90ed (diff) | |
download | pylint-e22629f80ebbd2abf2f0843a56d5d77933ab4297.tar.gz |
Merge with default.
Diffstat (limited to 'checkers')
-rw-r--r-- | checkers/base.py | 68 | ||||
-rw-r--r-- | checkers/utils.py | 8 |
2 files changed, 72 insertions, 4 deletions
diff --git a/checkers/base.py b/checkers/base.py index d2be803..cf4304c 100644 --- a/checkers/base.py +++ b/checkers/base.py @@ -33,6 +33,8 @@ from pylint.checkers.utils import ( is_inside_except, overrides_a_method, safe_infer, + get_argument_from_call, + NoSuchArgumentError, ) @@ -47,6 +49,8 @@ DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$') CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$') # do not require a doc string on system methods NO_REQUIRED_DOC_RGX = re.compile('__.*__') +REVERSED_METHODS = (('__getitem__', '__len__'), + ('__reversed__', )) BAD_FUNCTIONS = ['map', 'filter', 'apply'] if sys.version_info < (3, 0): @@ -419,6 +423,16 @@ functions, methods 'C0121': ('Missing required attribute "%s"', # W0103 'missing-module-attribute', 'Used when an attribute required for modules is missing.'), + + 'E0109': ('Missing argument to reversed()', + 'missing-reversed-argument', + 'Used when reversed() builtin didn\'t receive an argument.'), + 'E0111': ('The first reversed() argument is not a sequence', + 'bad-reversed-sequence', + 'Used when the first argument to reversed() builtin ' + 'isn\'t a sequence (does not implement __reversed__, ' + 'nor __getitem__ and __len__'), + } options = (('required-attributes', @@ -608,7 +622,9 @@ functions, methods """just print a warning on exec statements""" self.add_message('exec-used', node=node) - @check_messages('bad-builtin', 'star-args', 'exec-used') + @check_messages('bad-builtin', 'star-args', + 'exec-used', 'missing-reversed-argument', + 'bad-reversed-sequence') def visit_callfunc(self, node): """visit a CallFunc node -> check if this is not a blacklisted builtin call and check for * or ** use @@ -621,6 +637,8 @@ functions, methods name in node.root()): if name == 'exec': self.add_message('exec-used', node=node) + elif name == 'reversed': + self._check_reversed(node) if name in self.config.bad_functions: self.add_message('bad-builtin', node=node, args=name) if node.starargs or node.kwargs: @@ -685,7 +703,55 @@ functions, methods return _node = _parent _parent = _node.parent + + def _check_reversed(self, node): + """ check that the argument to `reversed` is a sequence """ + try: + argument = safe_infer(get_argument_from_call(node, position=0)) + except NoSuchArgumentError: + self.add_message('missing-reversed-argument', node=node) + else: + if argument is astroid.YES: + return + if argument is None: + # nothing was infered + # try to see if we have iter() + if (isinstance(node.args[0], astroid.CallFunc) and + node.args[0].func.name == 'iter'): + func = node.args[0].func.infer().next() + if is_builtin_object(func): + self.add_message('bad-reversed-sequence', node=node) + return + if isinstance(argument, astroid.Instance): + if (argument._proxied.name == 'dict' and + is_builtin_object(argument._proxied)): + self.add_message('bad-reversed-sequence', node=node) + return + elif any(ancestor.name == 'dict' and is_builtin_object(ancestor) + for ancestor in argument._proxied.ancestors()): + # mappings aren't accepted by reversed() + self.add_message('bad-reversed-sequence', node=node) + return + + for methods in REVERSED_METHODS: + for meth in methods: + try: + argument.getattr(meth) + except astroid.NotFoundError: + break + else: + break + else: + # check if it is a .deque. It doesn't seem that + # we can retrieve special methods + # from C implemented constructs + if argument._proxied.qname().endswith(".deque"): + return + self.add_message('bad-reversed-sequence', node=node) + elif not isinstance(argument, (astroid.List, astroid.Tuple)): + # everything else is not a proper sequence for reversed() + self.add_message('bad-reversed-sequence', node=node) class NameChecker(_BasicChecker): msgs = { diff --git a/checkers/utils.py b/checkers/utils.py index 53b40a6..728893e 100644 --- a/checkers/utils.py +++ b/checkers/utils.py @@ -124,7 +124,7 @@ SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') def is_builtin_object(node): """Returns True if the given node is an object from the __builtin__ module.""" - return node and node.root().name == '__builtin__' + return node and node.root().name == BUILTINS_NAME def is_builtin(name): # was is_native_builtin """return true if <name> could be considered as a builtin defined by python @@ -154,8 +154,10 @@ def is_defined_before(var_node): elif isinstance(_node, astroid.With): for expr, vars in _node.items: if expr.parent_of(var_node): - break - if vars and vars.name == varname: + break + if (vars and + isinstance(vars, astroid.AssName) and + vars.name == varname): return True elif isinstance(_node, (astroid.Lambda, astroid.Function)): if _node.args.is_argument(varname): |