Index: third_party/pylint/checkers/variables.py |
diff --git a/third_party/pylint/checkers/variables.py b/third_party/pylint/checkers/variables.py |
index c75768bb66103caa6661a876d09bd22291787a1d..8f6f95743226d934f9cbc0442ed8793e8836a69e 100644 |
--- a/third_party/pylint/checkers/variables.py |
+++ b/third_party/pylint/checkers/variables.py |
@@ -17,22 +17,26 @@ |
""" |
import os |
import sys |
+import re |
from copy import copy |
import astroid |
-from astroid import are_exclusive, builtin_lookup, AstroidBuildingException |
+from astroid import are_exclusive, builtin_lookup |
+from astroid import modutils |
-from logilab.common.modutils import file_from_modpath |
- |
-from pylint.interfaces import IAstroidChecker |
+from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH |
from pylint.utils import get_global_option |
from pylint.checkers import BaseChecker |
from pylint.checkers.utils import ( |
PYMETHODS, is_ancestor_name, is_builtin, |
is_defined_before, is_error, is_func_default, is_func_decorator, |
assign_parent, check_messages, is_inside_except, clobber_in_except, |
- get_all_elements) |
+ get_all_elements, has_known_bases) |
+import six |
+ |
+SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$") |
+PY3K = sys.version_info >= (3, 0) |
def in_for_else_branch(parent, stmt): |
"""Returns True if stmt in inside the else branch for a parent For stmt.""" |
@@ -42,7 +46,7 @@ def in_for_else_branch(parent, stmt): |
def overridden_method(klass, name): |
"""get overridden method if any""" |
try: |
- parent = klass.local_attr_ancestors(name).next() |
+ parent = next(klass.local_attr_ancestors(name)) |
except (StopIteration, KeyError): |
return None |
try: |
@@ -134,6 +138,55 @@ def _detect_global_scope(node, frame, defframe): |
# and the definition of the first depends on the second. |
return frame.lineno < defframe.lineno |
+def _fix_dot_imports(not_consumed): |
+ """ Try to fix imports with multiple dots, by returning a dictionary |
+ with the import names expanded. The function unflattens root imports, |
+ like 'xml' (when we have both 'xml.etree' and 'xml.sax'), to 'xml.etree' |
+ and 'xml.sax' respectively. |
+ """ |
+ # TODO: this should be improved in issue astroid #46 |
+ names = {} |
+ for name, stmts in six.iteritems(not_consumed): |
+ if any(isinstance(stmt, astroid.AssName) |
+ and isinstance(stmt.ass_type(), astroid.AugAssign) |
+ for stmt in stmts): |
+ continue |
+ for stmt in stmts: |
+ if not isinstance(stmt, (astroid.From, astroid.Import)): |
+ continue |
+ for imports in stmt.names: |
+ second_name = None |
+ if imports[0] == "*": |
+ # In case of wildcard imports, |
+ # pick the name from inside the imported module. |
+ second_name = name |
+ else: |
+ if imports[0].find(".") > -1 or name in imports: |
+ # Most likely something like 'xml.etree', |
+ # which will appear in the .locals as 'xml'. |
+ # Only pick the name if it wasn't consumed. |
+ second_name = imports[0] |
+ if second_name and second_name not in names: |
+ names[second_name] = stmt |
+ return sorted(names.items(), key=lambda a: a[1].fromlineno) |
+ |
+def _find_frame_imports(name, frame): |
+ """ |
+ Detect imports in the frame, with the required |
+ *name*. Such imports can be considered assignments. |
+ Returns True if an import for the given name was found. |
+ """ |
+ imports = frame.nodes_of_class((astroid.Import, astroid.From)) |
+ for import_node in imports: |
+ for import_name, import_alias in import_node.names: |
+ # If the import uses an alias, check only that. |
+ # Otherwise, check only the import name. |
+ if import_alias: |
+ if import_alias == name: |
+ return True |
+ elif import_name and import_name == name: |
+ return True |
+ |
MSGS = { |
'E0601': ('Using variable %r before assignment', |
@@ -164,13 +217,13 @@ MSGS = { |
'W0603': ('Using the global statement', # W0121 |
'global-statement', |
'Used when you use the "global" statement to update a global \ |
- variable. PyLint just try to discourage this \ |
+ variable. Pylint just try to discourage this \ |
usage. That doesn\'t mean you can not use it !'), |
'W0604': ('Using the global statement at the module level', # W0103 |
'global-at-module-level', |
'Used when you use the "global" statement at the module level \ |
since it has no effect'), |
- 'W0611': ('Unused import %s', |
+ 'W0611': ('Unused %s', |
'unused-import', |
'Used when an imported module or variable is not used.'), |
'W0612': ('Unused variable %r', |
@@ -250,6 +303,13 @@ variables (i.e. expectedly not used).'}), |
'help' : 'List of additional names supposed to be defined in \ |
builtins. Remember that you should avoid to define new builtins when possible.' |
}), |
+ ("callbacks", |
+ {'default' : ('cb_', '_cb'), 'type' : 'csv', |
+ 'metavar' : '<callbacks>', |
+ 'help' : 'List of strings which can identify a callback ' |
+ 'function by name. A callback name must start or ' |
+ 'end with one of those strings.'} |
+ ) |
) |
def __init__(self, linter=None): |
BaseChecker.__init__(self, linter) |
@@ -261,12 +321,14 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
checks globals doesn't overrides builtins |
""" |
self._to_consume = [(copy(node.locals), {}, 'module')] |
- for name, stmts in node.locals.iteritems(): |
+ for name, stmts in six.iteritems(node.locals): |
if is_builtin(name) and not is_inside_except(stmts[0]): |
# do not print Redefining builtin for additional builtins |
self.add_message('redefined-builtin', args=name, node=stmts[0]) |
- @check_messages('unused-import', 'unused-wildcard-import', 'redefined-builtin', 'undefined-all-variable', 'invalid-all-object') |
+ @check_messages('unused-import', 'unused-wildcard-import', |
+ 'redefined-builtin', 'undefined-all-variable', |
+ 'invalid-all-object') |
def leave_module(self, node): |
"""leave module: check globals |
""" |
@@ -274,17 +336,18 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
not_consumed = self._to_consume.pop()[0] |
# attempt to check for __all__ if defined |
if '__all__' in node.locals: |
- assigned = node.igetattr('__all__').next() |
+ assigned = next(node.igetattr('__all__')) |
if assigned is not astroid.YES: |
for elt in getattr(assigned, 'elts', ()): |
try: |
- elt_name = elt.infer().next() |
+ elt_name = next(elt.infer()) |
except astroid.InferenceError: |
continue |
if not isinstance(elt_name, astroid.Const) \ |
- or not isinstance(elt_name.value, basestring): |
- self.add_message('invalid-all-object', args=elt.as_string(), node=elt) |
+ or not isinstance(elt_name.value, six.string_types): |
+ self.add_message('invalid-all-object', |
+ args=elt.as_string(), node=elt) |
continue |
elt_name = elt_name.value |
# If elt is in not_consumed, remove it from not_consumed |
@@ -301,7 +364,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
if os.path.basename(basename) == '__init__': |
name = node.name + "." + elt_name |
try: |
- file_from_modpath(name.split(".")) |
+ modutils.file_from_modpath(name.split(".")) |
except ImportError: |
self.add_message('undefined-all-variable', |
args=elt_name, |
@@ -314,19 +377,52 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
# don't check unused imports in __init__ files |
if not self.config.init_import and node.package: |
return |
- for name, stmts in not_consumed.iteritems(): |
- if any(isinstance(stmt, astroid.AssName) |
- and isinstance(stmt.ass_type(), astroid.AugAssign) |
- for stmt in stmts): |
- continue |
- stmt = stmts[0] |
- if isinstance(stmt, astroid.Import): |
- self.add_message('unused-import', args=name, node=stmt) |
- elif isinstance(stmt, astroid.From) and stmt.modname != '__future__': |
- if stmt.names[0][0] == '*': |
- self.add_message('unused-wildcard-import', args=name, node=stmt) |
- else: |
- self.add_message('unused-import', args=name, node=stmt) |
+ |
+ self._check_imports(not_consumed) |
+ |
+ def _check_imports(self, not_consumed): |
+ local_names = _fix_dot_imports(not_consumed) |
+ checked = set() |
+ for name, stmt in local_names: |
+ for imports in stmt.names: |
+ real_name = imported_name = imports[0] |
+ if imported_name == "*": |
+ real_name = name |
+ as_name = imports[1] |
+ if real_name in checked: |
+ continue |
+ if name not in (real_name, as_name): |
+ continue |
+ checked.add(real_name) |
+ |
+ if (isinstance(stmt, astroid.Import) or |
+ (isinstance(stmt, astroid.From) and |
+ not stmt.modname)): |
+ if (isinstance(stmt, astroid.From) and |
+ SPECIAL_OBJ.search(imported_name)): |
+ # Filter special objects (__doc__, __all__) etc., |
+ # because they can be imported for exporting. |
+ continue |
+ if as_name is None: |
+ msg = "import %s" % imported_name |
+ else: |
+ msg = "%s imported as %s" % (imported_name, as_name) |
+ self.add_message('unused-import', args=msg, node=stmt) |
+ elif isinstance(stmt, astroid.From) and stmt.modname != '__future__': |
+ if SPECIAL_OBJ.search(imported_name): |
+ # Filter special objects (__doc__, __all__) etc., |
+ # because they can be imported for exporting. |
+ continue |
+ if imported_name == '*': |
+ self.add_message('unused-wildcard-import', |
+ args=name, node=stmt) |
+ else: |
+ if as_name is None: |
+ msg = "%s imported from %s" % (imported_name, stmt.modname) |
+ else: |
+ fields = (imported_name, stmt.modname, as_name) |
+ msg = "%s imported from %s as %s" % fields |
+ self.add_message('unused-import', args=msg, node=stmt) |
del self._to_consume |
def visit_class(self, node): |
@@ -418,6 +514,10 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
klass = node.parent.frame() |
if is_method and (klass.type == 'interface' or node.is_abstract()): |
return |
+ if is_method and isinstance(klass, astroid.Class): |
+ confidence = INFERENCE if has_known_bases(klass) else INFERENCE_FAILURE |
+ else: |
+ confidence = HIGH |
authorized_rgx = self.config.dummy_variables_rgx |
called_overridden = False |
argnames = node.argnames() |
@@ -428,7 +528,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
for nonlocal_stmt in node.nodes_of_class(astroid.Nonlocal): |
nonlocal_names.update(set(nonlocal_stmt.names)) |
- for name, stmts in not_consumed.iteritems(): |
+ for name, stmts in six.iteritems(not_consumed): |
# ignore some special names specified by user configuration |
if authorized_rgx.match(name): |
continue |
@@ -468,10 +568,12 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
continue |
if node.name in PYMETHODS and node.name not in ('__init__', '__new__'): |
continue |
- # don't check callback arguments XXX should be configurable |
- if node.name.startswith('cb_') or node.name.endswith('_cb'): |
+ # don't check callback arguments |
+ if any(node.name.startswith(cb) or node.name.endswith(cb) |
+ for cb in self.config.callbacks): |
continue |
- self.add_message('unused-argument', args=name, node=stmt) |
+ self.add_message('unused-argument', args=name, node=stmt, |
+ confidence=confidence) |
else: |
if stmt.parent and isinstance(stmt.parent, astroid.Assign): |
if name in nonlocal_names: |
@@ -503,25 +605,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
# same scope level assignment |
break |
else: |
- # global but no assignment |
- # Detect imports in the current frame, with the required |
- # name. Such imports can be considered assignments. |
- imports = frame.nodes_of_class((astroid.Import, astroid.From)) |
- for import_node in imports: |
- found = False |
- for import_name, import_alias in import_node.names: |
- # If the import uses an alias, check only that. |
- # Otherwise, check only the import name. |
- if import_alias: |
- if import_alias == name: |
- found = True |
- break |
- elif import_name and import_name == name: |
- found = True |
- break |
- if found: |
- break |
- else: |
+ if not _find_frame_imports(name, frame): |
self.add_message('global-variable-not-assigned', |
args=name, node=node) |
default_message = False |
@@ -541,7 +625,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
if default_message: |
self.add_message('global-statement', node=node) |
- def _check_late_binding_closure(self, node, assignment_node, scope_type): |
+ def _check_late_binding_closure(self, node, assignment_node): |
def _is_direct_lambda_call(): |
return (isinstance(node_scope.parent, astroid.CallFunc) |
and node_scope.parent.func is node_scope) |
@@ -651,18 +735,34 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
base_scope_type == 'comprehension' and i == start_index-1): |
# Detect if we are in a local class scope, as an assignment. |
# For example, the following is fair game. |
+ # |
# class A: |
# b = 1 |
# c = lambda b=b: b * b |
- class_assignment = (isinstance(frame, astroid.Class) and |
- name in frame.locals) |
- if not class_assignment: |
+ # |
+ # class B: |
+ # tp = 1 |
+ # def func(self, arg: tp): |
+ # ... |
+ |
+ in_annotation = ( |
+ PY3K and isinstance(frame, astroid.Function) |
+ and node.statement() is frame and |
+ (node in frame.args.annotations |
+ or node is frame.args.varargannotation |
+ or node is frame.args.kwargannotation)) |
+ if in_annotation: |
+ frame_locals = frame.parent.scope().locals |
+ else: |
+ frame_locals = frame.locals |
+ if not ((isinstance(frame, astroid.Class) or in_annotation) |
+ and name in frame_locals): |
continue |
# the name has already been consumed, only check it's not a loop |
# variable used outside the loop |
if name in consumed: |
defnode = assign_parent(consumed[name][0]) |
- self._check_late_binding_closure(node, defnode, scope_type) |
+ self._check_late_binding_closure(node, defnode) |
self._loopvar_name(node, name) |
break |
# mark the name as consumed if it's defined in this scope |
@@ -674,7 +774,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
# checks for use before assignment |
defnode = assign_parent(to_consume[name][0]) |
if defnode is not None: |
- self._check_late_binding_closure(node, defnode, scope_type) |
+ self._check_late_binding_closure(node, defnode) |
defstmt = defnode.statement() |
defframe = defstmt.frame() |
maybee0601 = True |
@@ -696,10 +796,35 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
maybee0601 = not any(isinstance(child, astroid.Nonlocal) |
and name in child.names |
for child in defframe.get_children()) |
+ |
+ # Handle a couple of class scoping issues. |
+ annotation_return = False |
+ # The class reuses itself in the class scope. |
+ recursive_klass = (frame is defframe and |
+ defframe.parent_of(node) and |
+ isinstance(defframe, astroid.Class) and |
+ node.name == defframe.name) |
if (self._to_consume[-1][-1] == 'lambda' and |
isinstance(frame, astroid.Class) |
and name in frame.locals): |
maybee0601 = True |
+ elif (isinstance(defframe, astroid.Class) and |
+ isinstance(frame, astroid.Function)): |
+ # Special rule for function return annotations, |
+ # which uses the same name as the class where |
+ # the function lives. |
+ if (PY3K and node is frame.returns and |
+ defframe.parent_of(frame.returns)): |
+ maybee0601 = annotation_return = True |
+ |
+ if (maybee0601 and defframe.name in defframe.locals and |
+ defframe.locals[name][0].lineno < frame.lineno): |
+ # Detect class assignments with the same |
+ # name as the class. In this case, no warning |
+ # should be raised. |
+ maybee0601 = False |
+ elif recursive_klass: |
+ maybee0601 = True |
else: |
maybee0601 = maybee0601 and stmt.fromlineno <= defstmt.fromlineno |
@@ -708,8 +833,11 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
and not are_exclusive(stmt, defstmt, ('NameError', |
'Exception', |
'BaseException'))): |
- if defstmt is stmt and isinstance(node, (astroid.DelName, |
- astroid.AssName)): |
+ if recursive_klass or (defstmt is stmt and |
+ isinstance(node, (astroid.DelName, |
+ astroid.AssName))): |
+ self.add_message('undefined-variable', args=name, node=node) |
+ elif annotation_return: |
self.add_message('undefined-variable', args=name, node=node) |
elif self._to_consume[-1][-1] != 'lambda': |
# E0601 may *not* occurs in lambda scope. |
@@ -753,7 +881,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
for name, _ in node.names: |
parts = name.split('.') |
try: |
- module = node.infer_name_module(parts[0]).next() |
+ module = next(node.infer_name_module(parts[0])) |
except astroid.ResolveError: |
continue |
self._check_module_attrs(node, module, parts[1:]) |
@@ -765,10 +893,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
level = getattr(node, 'level', None) |
try: |
module = node.root().import_module(name_parts[0], level=level) |
- except AstroidBuildingException: |
- return |
- except Exception, exc: |
- print 'Unhandled exception in VariablesChecker:', exc |
+ except Exception: # pylint: disable=broad-except |
return |
module = self._check_module_attrs(node, module, name_parts[1:]) |
if not module: |
@@ -799,6 +924,11 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
""" |
if infered is astroid.YES: |
return |
+ if (isinstance(infered.parent, astroid.Arguments) and |
+ isinstance(node.value, astroid.Name) and |
+ node.value.name == infered.parent.vararg): |
+ # Variable-length argument, we can't determine the length. |
+ return |
if isinstance(infered, (astroid.Tuple, astroid.List)): |
# attempt to check unpacking is properly balanced |
values = infered.itered() |
@@ -841,7 +971,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' |
module = None |
break |
try: |
- module = module.getattr(name)[0].infer().next() |
+ module = next(module.getattr(name)[0].infer()) |
if module is astroid.YES: |
return None |
except astroid.NotFoundError: |