Index: third_party/pylint/pylint/checkers/utils.py |
diff --git a/third_party/pylint/pylint/checkers/utils.py b/third_party/pylint/pylint/checkers/utils.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2cb01d558e07a37d3619da22a8de98946184df40 |
--- /dev/null |
+++ b/third_party/pylint/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 |