Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(155)

Unified Diff: third_party/pylint/checkers/variables.py

Issue 739393004: Revert "Revert "pylint: upgrade to 1.3.1"" (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools/
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/pylint/checkers/utils.py ('k') | third_party/pylint/config.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/pylint/checkers/variables.py
===================================================================
--- third_party/pylint/checkers/variables.py (revision 293047)
+++ third_party/pylint/checkers/variables.py (working copy)
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2014 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
@@ -12,27 +12,32 @@
#
# 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.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""variables checkers for Python code
"""
-
+import os
import sys
from copy import copy
-from logilab import astng
-from logilab.astng import are_exclusive, builtin_lookup, ASTNGBuildingException
+import astroid
+from astroid import are_exclusive, builtin_lookup, AstroidBuildingException
-from pylint.interfaces import IASTNGChecker
+from logilab.common.modutils import file_from_modpath
+
+from pylint.interfaces import IAstroidChecker
+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)
+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)
def in_for_else_branch(parent, stmt):
- """Returns True if stmt in inside the else branch for a parent For stmt."""
- return (isinstance(parent, astng.For) and
- any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse))
+ """Returns True if stmt in inside the else branch for a parent For stmt."""
+ return (isinstance(parent, astroid.For) and
+ any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse))
def overridden_method(klass, name):
"""get overridden method if any"""
@@ -44,58 +49,176 @@
meth_node = parent[name]
except KeyError:
# We have found an ancestor defining <name> but it's not in the local
- # dictionary. This may happen with astng built from living objects.
+ # dictionary. This may happen with astroid built from living objects.
return None
- if isinstance(meth_node, astng.Function):
+ if isinstance(meth_node, astroid.Function):
return meth_node
return None
+def _get_unpacking_extra_info(node, infered):
+ """return extra information to add to the message for unpacking-non-sequence
+ and unbalanced-tuple-unpacking errors
+ """
+ more = ''
+ infered_module = infered.root().name
+ if node.root().name == infered_module:
+ if node.lineno == infered.lineno:
+ more = ' %s' % infered.as_string()
+ elif infered.lineno:
+ more = ' defined at line %s' % infered.lineno
+ elif infered.lineno:
+ more = ' defined at line %s of %s' % (infered.lineno, infered_module)
+ return more
+def _detect_global_scope(node, frame, defframe):
+ """ Detect that the given frames shares a global
+ scope.
+
+ Two frames shares a global scope when neither
+ of them are hidden under a function scope, as well
+ as any of parent scope of them, until the root scope.
+ In this case, depending from something defined later on
+ will not work, because it is still undefined.
+
+ Example:
+ class A:
+ # B has the same global scope as `C`, leading to a NameError.
+ class B(C): ...
+ class C: ...
+
+ """
+ def_scope = scope = None
+ if frame and frame.parent:
+ scope = frame.parent.scope()
+ if defframe and defframe.parent:
+ def_scope = defframe.parent.scope()
+ if isinstance(frame, astroid.Function):
+ # If the parent of the current node is a
+ # function, then it can be under its scope
+ # (defined in, which doesn't concern us) or
+ # the `->` part of annotations. The same goes
+ # for annotations of function arguments, they'll have
+ # their parent the Arguments node.
+ if not isinstance(node.parent,
+ (astroid.Function, astroid.Arguments)):
+ return False
+ elif any(not isinstance(f, (astroid.Class, astroid.Module))
+ for f in (frame, defframe)):
+ # Not interested in other frames, since they are already
+ # not in a global scope.
+ return False
+
+ break_scopes = []
+ for s in (scope, def_scope):
+ # Look for parent scopes. If there is anything different
+ # than a module or a class scope, then they frames don't
+ # share a global scope.
+ parent_scope = s
+ while parent_scope:
+ if not isinstance(parent_scope, (astroid.Class, astroid.Module)):
+ break_scopes.append(parent_scope)
+ break
+ if parent_scope.parent:
+ parent_scope = parent_scope.parent.scope()
+ else:
+ break
+ if break_scopes and len(set(break_scopes)) != 1:
+ # Store different scopes than expected.
+ # If the stored scopes are, in fact, the very same, then it means
+ # that the two frames (frame and defframe) shares the same scope,
+ # and we could apply our lineno analysis over them.
+ # For instance, this works when they are inside a function, the node
+ # that uses a definition and the definition itself.
+ return False
+ # At this point, we are certain that frame and defframe shares a scope
+ # and the definition of the first depends on the second.
+ return frame.lineno < defframe.lineno
+
+
MSGS = {
'E0601': ('Using variable %r before assignment',
+ 'used-before-assignment',
'Used when a local variable is accessed before it\'s \
assignment.'),
'E0602': ('Undefined variable %r',
+ 'undefined-variable',
'Used when an undefined variable is accessed.'),
+ 'E0603': ('Undefined variable name %r in __all__',
+ 'undefined-all-variable',
+ 'Used when an undefined variable name is referenced in __all__.'),
+ 'E0604': ('Invalid object %r in __all__, must contain only strings',
+ 'invalid-all-object',
+ 'Used when an invalid (non-string) object occurs in __all__.'),
'E0611': ('No name %r in module %r',
+ 'no-name-in-module',
'Used when a name cannot be found in a module.'),
'W0601': ('Global variable %r undefined at the module level',
+ 'global-variable-undefined',
'Used when a variable is defined through the "global" statement \
but the variable is not defined in the module scope.'),
'W0602': ('Using global for %r but no assignment is done',
+ 'global-variable-not-assigned',
'Used when a variable is defined through the "global" statement \
but no assignment to this variable is done.'),
'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 \
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',
+ 'unused-import',
'Used when an imported module or variable is not used.'),
'W0612': ('Unused variable %r',
+ 'unused-variable',
'Used when a variable is defined but not used.'),
'W0613': ('Unused argument %r',
+ 'unused-argument',
'Used when a function or method argument is not used.'),
'W0614': ('Unused import %s from wildcard import',
+ 'unused-wildcard-import',
'Used when an imported module or variable is not used from a \
\'from X import *\' style import.'),
'W0621': ('Redefining name %r from outer scope (line %s)',
+ 'redefined-outer-name',
'Used when a variable\'s name hide a name defined in the outer \
scope.'),
'W0622': ('Redefining built-in %r',
+ 'redefined-builtin',
'Used when a variable or function override a built-in.'),
'W0623': ('Redefining name %r from %s in exception handler',
+ 'redefine-in-handler',
'Used when an exception handler assigns the exception \
to an existing name'),
'W0631': ('Using possibly undefined loop variable %r',
+ 'undefined-loop-variable',
'Used when an loop variable (i.e. defined by a for loop or \
a list comprehension or a generator expression) is used outside \
the loop.'),
+
+ 'W0632': ('Possible unbalanced tuple unpacking with '
+ 'sequence%s: '
+ 'left side has %d label(s), right side has %d value(s)',
+ 'unbalanced-tuple-unpacking',
+ 'Used when there is an unbalanced tuple unpacking in assignment'),
+
+ 'W0633': ('Attempting to unpack a non-sequence%s',
+ 'unpacking-non-sequence',
+ 'Used when something which is not '
+ 'a sequence is used in an unpack assignment'),
+
+ 'W0640': ('Cell variable %s defined in loop',
+ 'cell-var-from-loop',
+ 'A variable used in a closure is defined in a loop. '
+ 'This will result in all closures using the same value for '
+ 'the closed-over variable.'),
+
}
class VariablesChecker(BaseChecker):
@@ -104,35 +227,34 @@
* undefined variables
* redefinition of variable from builtins or from an outer scope
* use of variable before assignment
+ * __all__ consistency
"""
- __implements__ = IASTNGChecker
+ __implements__ = IAstroidChecker
name = 'variables'
msgs = MSGS
priority = -1
- options = (
- ("init-import",
+ options = (("init-import",
{'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
'help' : 'Tells whether we should check for unused import in \
__init__ files.'}),
("dummy-variables-rgx",
- {'default': ('_|dummy'),
+ {'default': ('_$|dummy'),
'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'A regular expression matching the beginning of \
- the name of dummy variables (i.e. not used).'}),
+ 'help' : 'A regular expression matching the name of dummy \
+variables (i.e. expectedly not used).'}),
("additional-builtins",
{'default': (), 'type' : 'csv',
'metavar' : '<comma separated list>',
'help' : 'List of additional names supposed to be defined in \
builtins. Remember that you should avoid to define new builtins when possible.'
- }),
- )
+ }),
+ )
def __init__(self, linter=None):
BaseChecker.__init__(self, linter)
self._to_consume = None
self._checking_mod_attr = None
- self._vars = None
def visit_module(self, node):
"""visit module : update consumption analysis variable
@@ -139,32 +261,73 @@
checks globals doesn't overrides builtins
"""
self._to_consume = [(copy(node.locals), {}, 'module')]
- self._vars = []
- for name, stmts in node.locals.items():
+ for name, stmts in node.locals.iteritems():
if is_builtin(name) and not is_inside_except(stmts[0]):
# do not print Redefining builtin for additional builtins
- self.add_message('W0622', args=name, node=stmts[0])
+ self.add_message('redefined-builtin', args=name, node=stmts[0])
- @check_messages('W0611', 'W0614')
+ @check_messages('unused-import', 'unused-wildcard-import', 'redefined-builtin', 'undefined-all-variable', 'invalid-all-object')
def leave_module(self, node):
"""leave module: check globals
"""
assert len(self._to_consume) == 1
not_consumed = self._to_consume.pop()[0]
+ # attempt to check for __all__ if defined
+ if '__all__' in node.locals:
+ assigned = node.igetattr('__all__').next()
+ if assigned is not astroid.YES:
+ for elt in getattr(assigned, 'elts', ()):
+ try:
+ elt_name = elt.infer().next()
+ 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)
+ continue
+ elt_name = elt_name.value
+ # If elt is in not_consumed, remove it from not_consumed
+ if elt_name in not_consumed:
+ del not_consumed[elt_name]
+ continue
+ if elt_name not in node.locals:
+ if not node.package:
+ self.add_message('undefined-all-variable',
+ args=elt_name,
+ node=elt)
+ else:
+ basename = os.path.splitext(node.file)[0]
+ if os.path.basename(basename) == '__init__':
+ name = node.name + "." + elt_name
+ try:
+ file_from_modpath(name.split("."))
+ except ImportError:
+ self.add_message('undefined-all-variable',
+ args=elt_name,
+ node=elt)
+ except SyntaxError:
+ # don't yield an syntax-error warning,
+ # because it will be later yielded
+ # when the file will be checked
+ pass
# don't check unused imports in __init__ files
if not self.config.init_import and node.package:
return
- for name, stmts in not_consumed.items():
+ 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, astng.Import):
- self.add_message('W0611', args=name, node=stmt)
- elif isinstance(stmt, astng.From) and stmt.modname != '__future__':
+ 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('W0614', args=name, node=stmt)
+ self.add_message('unused-wildcard-import', args=name, node=stmt)
else:
- self.add_message('W0611', args=name, node=stmt)
+ self.add_message('unused-import', args=name, node=stmt)
del self._to_consume
- del self._vars
def visit_class(self, node):
"""visit class: update consumption analysis variable
@@ -225,25 +388,27 @@
"""visit function: update consumption analysis variable and check locals
"""
self._to_consume.append((copy(node.locals), {}, 'function'))
- self._vars.append({})
- if not set(('W0621', 'W0622')) & self.active_msgs:
+ if not (self.linter.is_message_enabled('redefined-outer-name') or
+ self.linter.is_message_enabled('redefined-builtin')):
return
globs = node.root().globals
for name, stmt in node.items():
if is_inside_except(stmt):
continue
- if name in globs and not isinstance(stmt, astng.Global):
- line = globs[name][0].lineno
- self.add_message('W0621', args=(name, line), node=stmt)
+ if name in globs and not isinstance(stmt, astroid.Global):
+ line = globs[name][0].fromlineno
+ dummy_rgx = self.config.dummy_variables_rgx
+ if not dummy_rgx.match(name):
+ self.add_message('redefined-outer-name', args=(name, line), node=stmt)
elif is_builtin(name):
# do not print Redefining builtin for additional builtins
- self.add_message('W0622', args=name, node=stmt)
+ self.add_message('redefined-builtin', args=name, node=stmt)
def leave_function(self, node):
"""leave function: check function's locals are consumed"""
not_consumed = self._to_consume.pop()[0]
- self._vars.pop(0)
- if not set(('W0612', 'W0613')) & self.active_msgs:
+ if not (self.linter.is_message_enabled('unused-variable') or
+ self.linter.is_message_enabled('unused-argument')):
return
# don't check arguments of function which are only raising an exception
if is_error(node):
@@ -256,6 +421,13 @@
authorized_rgx = self.config.dummy_variables_rgx
called_overridden = False
argnames = node.argnames()
+ global_names = set()
+ nonlocal_names = set()
+ for global_stmt in node.nodes_of_class(astroid.Global):
+ global_names.update(set(global_stmt.names))
+ for nonlocal_stmt in node.nodes_of_class(astroid.Nonlocal):
+ nonlocal_names.update(set(nonlocal_stmt.names))
+
for name, stmts in not_consumed.iteritems():
# ignore some special names specified by user configuration
if authorized_rgx.match(name):
@@ -263,8 +435,25 @@
# ignore names imported by the global statement
# FIXME: should only ignore them if it's assigned latter
stmt = stmts[0]
- if isinstance(stmt, astng.Global):
+ if isinstance(stmt, astroid.Global):
continue
+ if isinstance(stmt, (astroid.Import, astroid.From)):
+ # Detect imports, assigned to global statements.
+ if global_names:
+ skip = False
+ for import_name, import_alias in stmt.names:
+ # If the import uses an alias, check only that.
+ # Otherwise, check only the import name.
+ if import_alias:
+ if import_alias in global_names:
+ skip = True
+ break
+ elif import_name in global_names:
+ skip = True
+ break
+ if skip:
+ continue
+
# care about functions with unknown argument (builtins)
if name in argnames:
if is_method:
@@ -282,16 +471,20 @@
# don't check callback arguments XXX should be configurable
if node.name.startswith('cb_') or node.name.endswith('_cb'):
continue
- self.add_message('W0613', args=name, node=stmt)
+ self.add_message('unused-argument', args=name, node=stmt)
else:
- self.add_message('W0612', args=name, node=stmt)
+ if stmt.parent and isinstance(stmt.parent, astroid.Assign):
+ if name in nonlocal_names:
+ continue
+ self.add_message('unused-variable', args=name, node=stmt)
- @check_messages('W0601', 'W0602', 'W0603', 'W0604', 'W0622')
+ @check_messages('global-variable-undefined', 'global-variable-not-assigned', 'global-statement',
+ 'global-at-module-level', 'redefined-builtin')
def visit_global(self, node):
"""check names imported exists in the global scope"""
frame = node.frame()
- if isinstance(frame, astng.Module):
- self.add_message('W0604', node=node)
+ if isinstance(frame, astroid.Module):
+ self.add_message('global-at-module-level', node=node)
return
module = frame.root()
default_message = True
@@ -298,7 +491,7 @@
for name in node.names:
try:
assign_nodes = module.getattr(name)
- except astng.NotFoundError:
+ except astroid.NotFoundError:
# unassigned global, skip
assign_nodes = []
for anode in assign_nodes:
@@ -311,13 +504,32 @@
break
else:
# global but no assignment
- self.add_message('W0602', args=name, node=node)
+ # 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:
+ self.add_message('global-variable-not-assigned',
+ args=name, node=node)
default_message = False
if not assign_nodes:
continue
for anode in assign_nodes:
if anode.parent is None:
- self.add_message('W0622', args=name, node=node)
+ self.add_message('redefined-builtin', args=name, node=node)
break
if anode.frame() is module:
# module level assignment
@@ -324,11 +536,38 @@
break
else:
# global undefined at the module scope
- self.add_message('W0601', args=name, node=node)
+ self.add_message('global-variable-undefined', args=name, node=node)
default_message = False
if default_message:
- self.add_message('W0603', node=node)
+ self.add_message('global-statement', node=node)
+ def _check_late_binding_closure(self, node, assignment_node, scope_type):
+ def _is_direct_lambda_call():
+ return (isinstance(node_scope.parent, astroid.CallFunc)
+ and node_scope.parent.func is node_scope)
+
+ node_scope = node.scope()
+ if not isinstance(node_scope, (astroid.Lambda, astroid.Function)):
+ return
+ if isinstance(node.parent, astroid.Arguments):
+ return
+
+ if isinstance(assignment_node, astroid.Comprehension):
+ if assignment_node.parent.parent_of(node.scope()):
+ self.add_message('cell-var-from-loop', node=node, args=node.name)
+ else:
+ assign_scope = assignment_node.scope()
+ maybe_for = assignment_node
+ while not isinstance(maybe_for, astroid.For):
+ if maybe_for is assign_scope:
+ break
+ maybe_for = maybe_for.parent
+ else:
+ if (maybe_for.parent_of(node_scope)
+ and not _is_direct_lambda_call()
+ and not isinstance(node_scope.statement(), astroid.Return)):
+ self.add_message('cell-var-from-loop', node=node, args=node.name)
+
def _loopvar_name(self, node, name):
# filter variables according to node's scope
# XXX used to filter parents but don't remember why, and removing this
@@ -337,7 +576,7 @@
#astmts = [stmt for stmt in node.lookup(name)[1]
# if hasattr(stmt, 'ass_type')] and
# not stmt.statement().parent_of(node)]
- if 'W0631' not in self.active_msgs:
+ if not self.linter.is_message_enabled('undefined-loop-variable'):
return
astmts = [stmt for stmt in node.lookup(name)[1]
if hasattr(stmt, 'ass_type')]
@@ -355,28 +594,31 @@
_astmts = astmts[:1]
for i, stmt in enumerate(astmts[1:]):
if (astmts[i].statement().parent_of(stmt)
- and not in_for_else_branch(astmts[i].statement(), stmt)):
+ and not in_for_else_branch(astmts[i].statement(), stmt)):
continue
_astmts.append(stmt)
astmts = _astmts
if len(astmts) == 1:
ass = astmts[0].ass_type()
- if isinstance(ass, (astng.For, astng.Comprehension, astng.GenExpr)) \
+ if isinstance(ass, (astroid.For, astroid.Comprehension, astroid.GenExpr)) \
and not ass.statement() is node.statement():
- self.add_message('W0631', args=name, node=node)
+ self.add_message('undefined-loop-variable', args=name, node=node)
+ @check_messages('redefine-in-handler')
def visit_excepthandler(self, node):
- clobbering, args = clobber_in_except(node.name)
- if clobbering:
- self.add_message('W0623', args=args, node=node)
+ for name in get_all_elements(node.name):
+ clobbering, args = clobber_in_except(name)
+ if clobbering:
+ self.add_message('redefine-in-handler', args=args, node=name)
def visit_assname(self, node):
- if isinstance(node.ass_type(), astng.AugAssign):
+ if isinstance(node.ass_type(), astroid.AugAssign):
self.visit_name(node)
def visit_delname(self, node):
self.visit_name(node)
+ @check_messages(*(MSGS.keys()))
def visit_name(self, node):
"""check that a name is defined if the current scope and doesn't
redefine a built-in
@@ -383,7 +625,7 @@
"""
stmt = node.statement()
if stmt.fromlineno is None:
- # name node from a astng built from live code, skip
+ # name node from a astroid built from live code, skip
assert not stmt.root().file.endswith('.py')
return
name = node.name
@@ -392,7 +634,7 @@
# a decorator, then start from the parent frame of the function instead
# of the function frame - and thus open an inner class scope
if (is_func_default(node) or is_func_decorator(node)
- or is_ancestor_name(frame, node)):
+ or is_ancestor_name(frame, node)):
start_index = len(self._to_consume) - 2
else:
start_index = len(self._to_consume) - 1
@@ -406,12 +648,21 @@
# names. The only exception is when the starting scope is a
# comprehension and its direct outer scope is a class
if scope_type == 'class' and i != start_index and not (
- base_scope_type == 'comprehension' and i == start_index-1):
- # XXX find a way to handle class scope in a smoother way
- continue
+ 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:
+ 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._loopvar_name(node, name)
break
# mark the name as consumed if it's defined in this scope
@@ -423,11 +674,12 @@
# 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)
defstmt = defnode.statement()
defframe = defstmt.frame()
maybee0601 = True
if not frame is defframe:
- maybee0601 = False
+ maybee0601 = _detect_global_scope(node, frame, defframe)
elif defframe.parent is None:
# we are at the module level, check the name is not
# defined in builtins
@@ -438,20 +690,53 @@
# defined in global or builtin scope
if defframe.root().lookup(name)[1]:
maybee0601 = False
+ else:
+ # check if we have a nonlocal
+ if name in defframe.locals:
+ maybee0601 = not any(isinstance(child, astroid.Nonlocal)
+ and name in child.names
+ for child in defframe.get_children())
+ if (self._to_consume[-1][-1] == 'lambda' and
+ isinstance(frame, astroid.Class)
+ and name in frame.locals):
+ maybee0601 = True
+ else:
+ maybee0601 = maybee0601 and stmt.fromlineno <= defstmt.fromlineno
+
if (maybee0601
- and stmt.fromlineno <= defstmt.fromlineno
- and not is_defined_before(node)
- and not are_exclusive(stmt, defstmt, ('NameError', 'Exception', 'BaseException'))):
- if defstmt is stmt and isinstance(node, (astng.DelName,
- astng.AssName)):
- self.add_message('E0602', args=name, node=node)
+ and not is_defined_before(node)
+ and not are_exclusive(stmt, defstmt, ('NameError',
+ 'Exception',
+ 'BaseException'))):
+ if defstmt is stmt and isinstance(node, (astroid.DelName,
+ astroid.AssName)):
+ self.add_message('undefined-variable', args=name, node=node)
elif self._to_consume[-1][-1] != 'lambda':
- # E0601 may *not* occurs in lambda scope
- self.add_message('E0601', args=name, node=node)
- if not isinstance(node, astng.AssName): # Aug AssName
+ # E0601 may *not* occurs in lambda scope.
+ self.add_message('used-before-assignment', args=name, node=node)
+ elif self._to_consume[-1][-1] == 'lambda':
+ # E0601 can occur in class-level scope in lambdas, as in
+ # the following example:
+ # class A:
+ # x = lambda attr: f + attr
+ # f = 42
+ if isinstance(frame, astroid.Class) and name in frame.locals:
+ if isinstance(node.parent, astroid.Arguments):
+ # Doing the following is fine:
+ # class A:
+ # x = 42
+ # y = lambda attr=x: attr
+ if stmt.fromlineno <= defstmt.fromlineno:
+ self.add_message('used-before-assignment',
+ args=name, node=node)
+ else:
+ self.add_message('undefined-variable',
+ args=name, node=node)
+
+ if isinstance(node, astroid.AssName): # Aug AssName
+ del consumed[name]
+ else:
del to_consume[name]
- else:
- del consumed[name]
# check it's not a loop variable used outside the loop
self._loopvar_name(node, name)
break
@@ -458,11 +743,11 @@
else:
# we have not found the name, if it isn't a builtin, that's an
# undefined name !
- if not (name in astng.Module.scope_attrs or is_builtin(name)
+ if not (name in astroid.Module.scope_attrs or is_builtin(name)
or name in self.config.additional_builtins):
- self.add_message('E0602', args=name, node=node)
+ self.add_message('undefined-variable', args=name, node=node)
- @check_messages('E0611')
+ @check_messages('no-name-in-module')
def visit_import(self, node):
"""check modules attribute accesses"""
for name, _ in node.names:
@@ -469,11 +754,11 @@
parts = name.split('.')
try:
module = node.infer_name_module(parts[0]).next()
- except astng.ResolveError:
+ except astroid.ResolveError:
continue
self._check_module_attrs(node, module, parts[1:])
- @check_messages('E0611')
+ @check_messages('no-name-in-module')
def visit_from(self, node):
"""check modules attribute accesses"""
name_parts = node.modname.split('.')
@@ -480,7 +765,7 @@
level = getattr(node, 'level', None)
try:
module = node.root().import_module(name_parts[0], level=level)
- except ASTNGBuildingException:
+ except AstroidBuildingException:
return
except Exception, exc:
print 'Unhandled exception in VariablesChecker:', exc
@@ -493,12 +778,63 @@
continue
self._check_module_attrs(node, module, name.split('.'))
+ @check_messages('unbalanced-tuple-unpacking', 'unpacking-non-sequence')
+ def visit_assign(self, node):
+ """Check unbalanced tuple unpacking for assignments
+ and unpacking non-sequences.
+ """
+ if not isinstance(node.targets[0], (astroid.Tuple, astroid.List)):
+ return
+
+ targets = node.targets[0].itered()
+ try:
+ for infered in node.value.infer():
+ self._check_unpacking(infered, node, targets)
+ except astroid.InferenceError:
+ return
+
+ def _check_unpacking(self, infered, node, targets):
+ """ Check for unbalanced tuple unpacking
+ and unpacking non sequences.
+ """
+ if infered is astroid.YES:
+ return
+ if isinstance(infered, (astroid.Tuple, astroid.List)):
+ # attempt to check unpacking is properly balanced
+ values = infered.itered()
+ if len(targets) != len(values):
+ # Check if we have starred nodes.
+ if any(isinstance(target, astroid.Starred)
+ for target in targets):
+ return
+ self.add_message('unbalanced-tuple-unpacking', node=node,
+ args=(_get_unpacking_extra_info(node, infered),
+ len(targets),
+ len(values)))
+ # attempt to check unpacking may be possible (ie RHS is iterable)
+ elif isinstance(infered, astroid.Instance):
+ for meth in ('__iter__', '__getitem__'):
+ try:
+ infered.getattr(meth)
+ break
+ except astroid.NotFoundError:
+ continue
+ else:
+ self.add_message('unpacking-non-sequence', node=node,
+ args=(_get_unpacking_extra_info(node, infered),))
+ else:
+ self.add_message('unpacking-non-sequence', node=node,
+ args=(_get_unpacking_extra_info(node, infered),))
+
+
def _check_module_attrs(self, node, module, module_names):
"""check that module_names (list of string) are accessible through the
given module
if the latest access name corresponds to a module, return it
"""
- assert isinstance(module, astng.Module), module
+ assert isinstance(module, astroid.Module), module
+ ignored_modules = get_global_option(self, 'ignored-modules',
+ default=[])
while module_names:
name = module_names.pop(0)
if name == '__dict__':
@@ -506,21 +842,24 @@
break
try:
module = module.getattr(name)[0].infer().next()
- if module is astng.YES:
+ if module is astroid.YES:
return None
- except astng.NotFoundError:
- self.add_message('E0611', args=(name, module.name), node=node)
+ except astroid.NotFoundError:
+ if module.name in ignored_modules:
+ return None
+ self.add_message('no-name-in-module',
+ args=(name, module.name), node=node)
return None
- except astng.InferenceError:
+ except astroid.InferenceError:
return None
if module_names:
# FIXME: other message if name is not the latest part of
# module_names ?
modname = module and module.name or '__dict__'
- self.add_message('E0611', node=node,
+ self.add_message('no-name-in-module', node=node,
args=('.'.join(module_names), modname))
return None
- if isinstance(module, astng.Module):
+ if isinstance(module, astroid.Module):
return module
return None
@@ -540,6 +879,57 @@
# do not check for not used locals here
self._to_consume.pop()
+ def leave_module(self, node):
+ """ Update consumption analysis variable
+ for metaclasses.
+ """
+ module_locals = self._to_consume[0][0]
+ module_imports = self._to_consume[0][1]
+ consumed = {}
+
+ for klass in node.nodes_of_class(astroid.Class):
+ found = metaclass = name = None
+ if not klass._metaclass:
+ # Skip if this class doesn't use
+ # explictly a metaclass, but inherits it from ancestors
+ continue
+
+ metaclass = klass.metaclass()
+
+ # Look the name in the already found locals.
+ # If it's not found there, look in the module locals
+ # and in the imported modules.
+ if isinstance(klass._metaclass, astroid.Name):
+ name = klass._metaclass.name
+ elif metaclass:
+ # if it uses a `metaclass=module.Class`
+ name = metaclass.root().name
+
+ if name:
+ found = consumed.setdefault(
+ name, module_locals.get(name, module_imports.get(name)))
+
+ if found is None and not metaclass:
+ name = None
+ if isinstance(klass._metaclass, astroid.Name):
+ name = klass._metaclass.name
+ elif isinstance(klass._metaclass, astroid.Getattr):
+ name = klass._metaclass.as_string()
+
+ if name is not None:
+ if not (name in astroid.Module.scope_attrs or
+ is_builtin(name) or
+ name in self.config.additional_builtins or
+ name in node.locals):
+ self.add_message('undefined-variable',
+ node=klass,
+ args=(name, ))
+ # Pop the consumed items, in order to
+ # avoid having unused-import false positives
+ for name in consumed:
+ module_locals.pop(name, None)
+ super(VariablesChecker3k, self).leave_module(node)
+
if sys.version_info >= (3, 0):
VariablesChecker = VariablesChecker3k
« no previous file with comments | « third_party/pylint/checkers/utils.py ('k') | third_party/pylint/config.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698