diff options
author | Ionel Cristian Maries <contact@ionelmc.ro> | 2015-02-14 18:13:20 +0200 |
---|---|---|
committer | Ionel Cristian Maries <contact@ionelmc.ro> | 2015-02-14 18:13:20 +0200 |
commit | 6d8412476a296b3a3691af1ffabcb672d9a4920f (patch) | |
tree | e358c7e886ff4d67d0efc6263f0472655efddfff /pylint/checkers/utils.py | |
parent | 0369bd6a914af3ad92ce53eac3786bf8de785f7f (diff) | |
download | pylint-6d8412476a296b3a3691af1ffabcb672d9a4920f.tar.gz |
Move all package files to a pylint package.
Diffstat (limited to 'pylint/checkers/utils.py')
-rw-r--r-- | pylint/checkers/utils.py | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py new file mode 100644 index 0000000..2cb01d5 --- /dev/null +++ b/pylint/checkers/utils.py @@ -0,0 +1,564 @@ +# pylint: disable=W0611 +# +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""some functions that may be useful for various checkers +""" + +import re +import sys +import string + +import astroid +from astroid import scoped_nodes +from logilab.common.compat import builtins + +BUILTINS_NAME = builtins.__name__ +COMP_NODE_TYPES = astroid.ListComp, astroid.SetComp, astroid.DictComp, astroid.GenExpr +PY3K = sys.version_info[0] == 3 + +if not PY3K: + EXCEPTIONS_MODULE = "exceptions" +else: + EXCEPTIONS_MODULE = "builtins" +ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod', + 'abc.abstractclassmethod', 'abc.abstractstaticmethod')) + + +class NoSuchArgumentError(Exception): + pass + +def is_inside_except(node): + """Returns true if node is inside the name of an except handler.""" + current = node + while current and not isinstance(current.parent, astroid.ExceptHandler): + current = current.parent + + return current and current is current.parent.name + + +def get_all_elements(node): + """Recursively returns all atoms in nested lists and tuples.""" + if isinstance(node, (astroid.Tuple, astroid.List)): + for child in node.elts: + for e in get_all_elements(child): + yield e + else: + yield node + + +def clobber_in_except(node): + """Checks if an assignment node in an except handler clobbers an existing + variable. + + Returns (True, args for W0623) if assignment clobbers an existing variable, + (False, None) otherwise. + """ + if isinstance(node, astroid.AssAttr): + return (True, (node.attrname, 'object %r' % (node.expr.as_string(),))) + elif isinstance(node, astroid.AssName): + name = node.name + if is_builtin(name): + return (True, (name, 'builtins')) + else: + stmts = node.lookup(name)[1] + if (stmts and not isinstance(stmts[0].ass_type(), + (astroid.Assign, astroid.AugAssign, + astroid.ExceptHandler))): + return (True, (name, 'outer scope (line %s)' % stmts[0].fromlineno)) + return (False, None) + + +def safe_infer(node): + """return the inferred value for the given node. + Return None if inference failed or if there is some ambiguity (more than + one node has been inferred) + """ + try: + inferit = node.infer() + value = next(inferit) + except astroid.InferenceError: + return + try: + next(inferit) + return # None if there is ambiguity on the inferred node + except astroid.InferenceError: + return # there is some kind of ambiguity + except StopIteration: + return value + +def is_super(node): + """return True if the node is referencing the "super" builtin function + """ + if getattr(node, 'name', None) == 'super' and \ + node.root().name == BUILTINS_NAME: + return True + return False + +def is_error(node): + """return true if the function does nothing but raising an exception""" + for child_node in node.get_children(): + if isinstance(child_node, astroid.Raise): + return True + return False + +def is_raising(body): + """return true if the given statement node raise an exception""" + for node in body: + if isinstance(node, astroid.Raise): + return True + return False + +def is_empty(body): + """return true if the given node does nothing but 'pass'""" + return len(body) == 1 and isinstance(body[0], astroid.Pass) + +builtins = builtins.__dict__.copy() +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 == BUILTINS_NAME + +def is_builtin(name): # was is_native_builtin + """return true if <name> could be considered as a builtin defined by python + """ + if name in builtins: + return True + if name in SPECIAL_BUILTINS: + return True + return False + +def is_defined_before(var_node): + """return True if the variable node is defined by a parent node (list, + set, dict, or generator comprehension, lambda) or in a previous sibling + node on the same line (statement_defining ; statement_using) + """ + varname = var_node.name + _node = var_node.parent + while _node: + if isinstance(_node, COMP_NODE_TYPES): + for ass_node in _node.nodes_of_class(astroid.AssName): + if ass_node.name == varname: + return True + elif isinstance(_node, astroid.For): + for ass_node in _node.target.nodes_of_class(astroid.AssName): + if ass_node.name == varname: + return True + elif isinstance(_node, astroid.With): + for expr, ids in _node.items: + if expr.parent_of(var_node): + break + if (ids and + isinstance(ids, astroid.AssName) and + ids.name == varname): + return True + elif isinstance(_node, (astroid.Lambda, astroid.Function)): + if _node.args.is_argument(varname): + return True + if getattr(_node, 'name', None) == varname: + return True + break + elif isinstance(_node, astroid.ExceptHandler): + if isinstance(_node.name, astroid.AssName): + ass_node = _node.name + if ass_node.name == varname: + return True + _node = _node.parent + # possibly multiple statements on the same line using semi colon separator + stmt = var_node.statement() + _node = stmt.previous_sibling() + lineno = stmt.fromlineno + while _node and _node.fromlineno == lineno: + for ass_node in _node.nodes_of_class(astroid.AssName): + if ass_node.name == varname: + return True + for imp_node in _node.nodes_of_class((astroid.From, astroid.Import)): + if varname in [name[1] or name[0] for name in imp_node.names]: + return True + _node = _node.previous_sibling() + return False + +def is_func_default(node): + """return true if the given Name node is used in function default argument's + value + """ + parent = node.scope() + if isinstance(parent, astroid.Function): + for default_node in parent.args.defaults: + for default_name_node in default_node.nodes_of_class(astroid.Name): + if default_name_node is node: + return True + return False + +def is_func_decorator(node): + """return true if the name is used in function decorator""" + parent = node.parent + while parent is not None: + if isinstance(parent, astroid.Decorators): + return True + if (parent.is_statement or + isinstance(parent, astroid.Lambda) or + isinstance(parent, (scoped_nodes.ComprehensionScope, + scoped_nodes.ListComp))): + break + parent = parent.parent + return False + +def is_ancestor_name(frame, node): + """return True if `frame` is a astroid.Class node with `node` in the + subtree of its bases attribute + """ + try: + bases = frame.bases + except AttributeError: + return False + for base in bases: + if node in base.nodes_of_class(astroid.Name): + return True + return False + +def assign_parent(node): + """return the higher parent which is not an AssName, Tuple or List node + """ + while node and isinstance(node, (astroid.AssName, + astroid.Tuple, + astroid.List)): + node = node.parent + return node + +def overrides_an_abstract_method(class_node, name): + """return True if pnode is a parent of node""" + for ancestor in class_node.ancestors(): + if name in ancestor and isinstance(ancestor[name], astroid.Function) and \ + ancestor[name].is_abstract(pass_is_abstract=False): + return True + return False + +def overrides_a_method(class_node, name): + """return True if <name> is a method overridden from an ancestor""" + for ancestor in class_node.ancestors(): + if name in ancestor and isinstance(ancestor[name], astroid.Function): + return True + return False + +PYMETHODS = set(('__new__', '__init__', '__del__', '__hash__', + '__str__', '__repr__', + '__len__', '__iter__', + '__delete__', '__get__', '__set__', + '__getitem__', '__setitem__', '__delitem__', '__contains__', + '__getattribute__', '__getattr__', '__setattr__', '__delattr__', + '__call__', + '__enter__', '__exit__', + '__cmp__', '__ge__', '__gt__', '__le__', '__lt__', '__eq__', + '__nonzero__', '__neg__', '__invert__', + '__mul__', '__imul__', '__rmul__', + '__div__', '__idiv__', '__rdiv__', + '__add__', '__iadd__', '__radd__', + '__sub__', '__isub__', '__rsub__', + '__pow__', '__ipow__', '__rpow__', + '__mod__', '__imod__', '__rmod__', + '__and__', '__iand__', '__rand__', + '__or__', '__ior__', '__ror__', + '__xor__', '__ixor__', '__rxor__', + # XXX To be continued + )) + +def check_messages(*messages): + """decorator to store messages that are handled by a checker method""" + + def store_messages(func): + func.checks_msgs = messages + return func + return store_messages + +class IncompleteFormatString(Exception): + """A format string ended in the middle of a format specifier.""" + pass + +class UnsupportedFormatCharacter(Exception): + """A format character in a format string is not one of the supported + format characters.""" + def __init__(self, index): + Exception.__init__(self, index) + self.index = index + +def parse_format_string(format_string): + """Parses a format string, returning a tuple of (keys, num_args), where keys + is the set of mapping keys in the format string, and num_args is the number + of arguments required by the format string. Raises + IncompleteFormatString or UnsupportedFormatCharacter if a + parse error occurs.""" + keys = set() + num_args = 0 + def next_char(i): + i += 1 + if i == len(format_string): + raise IncompleteFormatString + return (i, format_string[i]) + i = 0 + while i < len(format_string): + char = format_string[i] + if char == '%': + i, char = next_char(i) + # Parse the mapping key (optional). + key = None + if char == '(': + depth = 1 + i, char = next_char(i) + key_start = i + while depth != 0: + if char == '(': + depth += 1 + elif char == ')': + depth -= 1 + i, char = next_char(i) + key_end = i - 1 + key = format_string[key_start:key_end] + + # Parse the conversion flags (optional). + while char in '#0- +': + i, char = next_char(i) + # Parse the minimum field width (optional). + if char == '*': + num_args += 1 + i, char = next_char(i) + else: + while char in string.digits: + i, char = next_char(i) + # Parse the precision (optional). + if char == '.': + i, char = next_char(i) + if char == '*': + num_args += 1 + i, char = next_char(i) + else: + while char in string.digits: + i, char = next_char(i) + # Parse the length modifier (optional). + if char in 'hlL': + i, char = next_char(i) + # Parse the conversion type (mandatory). + if PY3K: + flags = 'diouxXeEfFgGcrs%a' + else: + flags = 'diouxXeEfFgGcrs%' + if char not in flags: + raise UnsupportedFormatCharacter(i) + if key: + keys.add(key) + elif char != '%': + num_args += 1 + i += 1 + return keys, num_args + + +def is_attr_protected(attrname): + """return True if attribute name is protected (start with _ and some other + details), False otherwise. + """ + return attrname[0] == '_' and not attrname == '_' and not ( + attrname.startswith('__') and attrname.endswith('__')) + +def node_frame_class(node): + """return klass node for a method node (or a staticmethod or a + classmethod), return null otherwise + """ + klass = node.frame() + + while klass is not None and not isinstance(klass, astroid.Class): + if klass.parent is None: + klass = None + else: + klass = klass.parent.frame() + + return klass + +def is_super_call(expr): + """return True if expression node is a function call and if function name + is super. Check before that you're in a method. + """ + return (isinstance(expr, astroid.CallFunc) and + isinstance(expr.func, astroid.Name) and + expr.func.name == 'super') + +def is_attr_private(attrname): + """Check that attribute name is private (at least two leading underscores, + at most one trailing underscore) + """ + regex = re.compile('^_{2,}.*[^_]+_?$') + return regex.match(attrname) + +def get_argument_from_call(callfunc_node, position=None, keyword=None): + """Returns the specified argument from a function call. + + :param callfunc_node: Node representing a function call to check. + :param int position: position of the argument. + :param str keyword: the keyword of the argument. + + :returns: The node representing the argument, None if the argument is not found. + :raises ValueError: if both position and keyword are None. + :raises NoSuchArgumentError: if no argument at the provided position or with + the provided keyword. + """ + if position is None and keyword is None: + raise ValueError('Must specify at least one of: position or keyword.') + try: + if position is not None and not isinstance(callfunc_node.args[position], astroid.Keyword): + return callfunc_node.args[position] + except IndexError as error: + raise NoSuchArgumentError(error) + if keyword: + for arg in callfunc_node.args: + if isinstance(arg, astroid.Keyword) and arg.arg == keyword: + return arg.value + raise NoSuchArgumentError + +def inherit_from_std_ex(node): + """ + Return true if the given class node is subclass of + exceptions.Exception. + """ + if node.name in ('Exception', 'BaseException') \ + and node.root().name == EXCEPTIONS_MODULE: + return True + return any(inherit_from_std_ex(parent) + for parent in node.ancestors(recurs=False)) + +def is_import_error(handler): + """ + Check if the given exception handler catches + ImportError. + + :param handler: A node, representing an ExceptHandler node. + :returns: True if the handler catches ImportError, False otherwise. + """ + names = None + if isinstance(handler.type, astroid.Tuple): + names = [name for name in handler.type.elts + if isinstance(name, astroid.Name)] + elif isinstance(handler.type, astroid.Name): + names = [handler.type] + else: + # Don't try to infer that. + return + for name in names: + try: + for infered in name.infer(): + if (isinstance(infered, astroid.Class) and + inherit_from_std_ex(infered) and + infered.name == 'ImportError'): + return True + except astroid.InferenceError: + continue + +def has_known_bases(klass): + """Returns true if all base classes of a class could be inferred.""" + try: + return klass._all_bases_known + except AttributeError: + pass + for base in klass.bases: + result = safe_infer(base) + # TODO: check for A->B->A->B pattern in class structure too? + if (not isinstance(result, astroid.Class) or + result is klass or + not has_known_bases(result)): + klass._all_bases_known = False + return False + klass._all_bases_known = True + return True + +def decorated_with_property(node): + """ Detect if the given function node is decorated with a property. """ + if not node.decorators: + return False + for decorator in node.decorators.nodes: + if not isinstance(decorator, astroid.Name): + continue + try: + for infered in decorator.infer(): + if isinstance(infered, astroid.Class): + if (infered.root().name == BUILTINS_NAME and + infered.name == 'property'): + return True + for ancestor in infered.ancestors(): + if (ancestor.name == 'property' and + ancestor.root().name == BUILTINS_NAME): + return True + except astroid.InferenceError: + pass + + +def decorated_with_abc(func): + """Determine if the `func` node is decorated with `abc` decorators.""" + if func.decorators: + for node in func.decorators.nodes: + try: + infered = next(node.infer()) + except astroid.InferenceError: + continue + if infered and infered.qname() in ABC_METHODS: + return True + + +def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc): + """ + Get the unimplemented abstract methods for the given *node*. + + A method can be considered abstract if the callback *is_abstract_cb* + returns a ``True`` value. The check defaults to verifying that + a method is decorated with abstract methods. + The function will work only for new-style classes. For old-style + classes, it will simply return an empty dictionary. + For the rest of them, it will return a dictionary of abstract method + names and their inferred objects. + """ + visited = {} + try: + mro = reversed(node.mro()) + except NotImplementedError: + # Old style class, it will not have a mro. + return {} + except astroid.ResolveError: + # Probably inconsistent hierarchy, don'try + # to figure this out here. + return {} + for ancestor in mro: + for obj in ancestor.values(): + infered = obj + if isinstance(obj, astroid.AssName): + infered = safe_infer(obj) + if not infered: + continue + if not isinstance(infered, astroid.Function): + if obj.name in visited: + del visited[obj.name] + if isinstance(infered, astroid.Function): + # It's critical to use the original name, + # since after inferring, an object can be something + # else than expected, as in the case of the + # following assignment. + # + # class A: + # def keys(self): pass + # __iter__ = keys + abstract = is_abstract_cb(infered) + if abstract: + visited[obj.name] = infered + elif not abstract and obj.name in visited: + del visited[obj.name] + return visited |