cadquery-freecad-module/CadQuery/Libs/frosted/checker.py

906 lines
35 KiB
Python

"""frosted/checker.py.
The core functionality of frosted lives here. Implements the core checking capability models Bindings and Scopes
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import builtins
import doctest
import itertools
import os
import sys
from pies import ast
from pies.overrides import *
from frosted import messages
PY34_GTE = sys.version_info >= (3, 4)
FROSTED_BUILTINS = set(dir(builtins) + ['__file__', '__builtins__', '__debug__', '__name__', 'WindowsError',
'__import__'] +
os.environ.get('PYFLAKES_BUILTINS', '').split(','))
def node_name(node):
"""
Convenience function: Returns node.id, or node.name, or None
"""
return hasattr(node, 'id') and node.id or hasattr(node, 'name') and node.name
class Binding(object):
"""Represents the binding of a value to a name.
The checker uses this to keep track of which names have been bound and which names have not. See Assignment for a
special type of binding that is checked with stricter rules.
"""
__slots__ = ('name', 'source', 'used')
def __init__(self, name, source):
self.name = name
self.source = source
self.used = False
def __str__(self):
return self.name
def __repr__(self):
return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
self.name,
self.source.lineno,
id(self))
class Importation(Binding):
"""A binding created by an import statement."""
__slots__ = ('fullName', )
def __init__(self, name, source):
self.fullName = name
name = name.split('.')[0]
super(Importation, self).__init__(name, source)
class Argument(Binding):
"""Represents binding a name as an argument."""
__slots__ = ()
class Definition(Binding):
"""A binding that defines a function or a class."""
__slots__ = ()
class Assignment(Binding):
"""Represents binding a name with an explicit assignment.
The checker will raise warnings for any Assignment that isn't used. Also, the checker does not consider assignments
in tuple/list unpacking to be Assignments, rather it treats them as simple Bindings.
"""
__slots__ = ()
class FunctionDefinition(Definition):
__slots__ = ('signature', )
def __init__(self, name, source):
super(FunctionDefinition, self).__init__(name, source)
self.signature = FunctionSignature(source)
class ClassDefinition(Definition):
__slots__ = ()
class ExportBinding(Binding):
"""A binding created by an __all__ assignment. If the names in the list
can be determined statically, they will be treated as names for export and
additional checking applied to them.
The only __all__ assignment that can be recognized is one which takes
the value of a literal list containing literal strings. For example:
__all__ = ["foo", "bar"]
Names which are imported and not otherwise used but appear in the value of
__all__ will not have an unused import warning reported for them.
"""
__slots__ = ()
def names(self):
"""Return a list of the names referenced by this binding."""
names = []
if isinstance(self.source, ast.List):
for node in self.source.elts:
if isinstance(node, ast.Str):
names.append(node.s)
return names
class Scope(dict):
importStarred = False # set to True when import * is found
def __repr__(self):
scope_cls = self.__class__.__name__
return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self))
class ClassScope(Scope):
pass
class FunctionScope(Scope):
"""Represents the name scope for a function."""
uses_locals = False
always_used = set(['__tracebackhide__', '__traceback_info__', '__traceback_supplement__'])
def __init__(self):
Scope.__init__(self)
self.globals = self.always_used.copy()
def unusedAssignments(self):
"""Return a generator for the assignments which have not been used."""
for name, binding in self.items():
if (not binding.used and name not in self.globals
and not self.uses_locals
and isinstance(binding, Assignment)):
yield name, binding
class GeneratorScope(Scope):
pass
class ModuleScope(Scope):
pass
class FunctionSignature(object):
__slots__ = ('decorated', 'argument_names', 'default_count', 'kw_only_argument_names', 'default_count',
'kw_only_argument_names', 'kw_only_default_count', 'has_var_arg', 'has_kw_arg')
def __init__(self, node):
self.decorated = bool(any(node.decorator_list))
self.argument_names = ast.argument_names(node)
self.default_count = len(node.args.defaults)
self.kw_only_argument_names = ast.kw_only_argument_names(node)
self.kw_only_default_count = ast.kw_only_default_count(node)
self.has_var_arg = node.args.vararg is not None
self.has_kw_arg = node.args.kwarg is not None
def min_argument_count(self):
return len(self.argument_names) - self.default_count
def maxArgumentCount(self):
return len(self.argument_names)
def checkCall(self, call_node, reporter, name):
if self.decorated:
return
filledSlots = set()
filledKwOnlySlots = set()
for item, arg in enumerate(call_node.args):
if item >= len(self.argument_names):
if not self.has_var_arg:
return reporter.report(messages.TooManyArguments, call_node, name, self.maxArgumentCount())
break
filledSlots.add(item)
for kw in call_node.keywords:
slots = None
try:
argIndex = self.argument_names.index(kw.arg)
slots = filledSlots
except ValueError:
try:
argIndex = self.kw_only_argument_names.index(kw.arg)
slots = filledKwOnlySlots
except ValueError:
if self.has_kw_arg:
continue
else:
return reporter.report(messages.UnexpectedArgument, call_node, name, kw.arg)
if argIndex in slots:
return reporter.report(messages.MultipleValuesForArgument, call_node, name, kw.arg)
slots.add(argIndex)
filledSlots.update(range(len(self.argument_names) - self.default_count, len(self.argument_names)))
filledKwOnlySlots.update(range(len(self.kw_only_argument_names) - self.kw_only_default_count,
len(self.kw_only_argument_names)))
if (len(filledSlots) < len(self.argument_names) and not call_node.starargs and not call_node.kwargs):
return reporter.report(messages.TooFewArguments, call_node, name, self.min_argument_count())
if (len(filledKwOnlySlots) < len(self.kw_only_argument_names) and not call_node.kwargs):
missing_arguments = [repr(arg) for i, arg in enumerate(self.kw_only_argument_names)
if i not in filledKwOnlySlots]
return reporter.report(messages.NeedKwOnlyArgument, call_node, name, ', '.join(missing_arguments))
class Checker(object):
"""The core of frosted, checks the cleanliness and sanity of Python code."""
node_depth = 0
offset = None
trace_tree = False
frosted_builtins = FROSTED_BUILTINS
def __init__(self, tree, filename='(none)', builtins=None, ignore_lines=(), **settings):
self.settings = settings
self.ignore_errors = settings.get('ignore_frosted_errors', [])
self.ignore_lines = ignore_lines
file_specifc_ignores = settings.get('ignore_frosted_errors_for_' + (os.path.basename(filename) or ""), None)
if file_specifc_ignores:
self.ignore_errors += file_specifc_ignores
self._node_handlers = {}
self._deferred_functions = []
self._deferred_assignments = []
self.dead_scopes = []
self.messages = []
self.filename = filename
if builtins:
self.frosted_builtins = self.frosted_builtins.union(builtins)
self.scope_stack = [ModuleScope()]
self.except_handlers = [()]
self.futures_allowed = True
self.root = tree
self.handle_children(tree)
self.run_deferred(self._deferred_functions)
self._deferred_functions = None
self.run_deferred(self._deferred_assignments)
self._deferred_assignments = None
del self.scope_stack[1:]
self.pop_scope()
self.check_dead_scopes()
def defer_function(self, callable):
"""Schedule a function handler to be called just before completion.
This is used for handling function bodies, which must be deferred because code later in the file might modify
the global scope. When 'callable' is called, the scope at the time this is called will be restored, however it
will contain any new bindings added to it.
"""
self._deferred_functions.append((callable, self.scope_stack[:], self.offset))
def defer_assignment(self, callable):
"""Schedule an assignment handler to be called just after deferred
function handlers."""
self._deferred_assignments.append((callable, self.scope_stack[:], self.offset))
def run_deferred(self, deferred):
"""Run the callables in deferred using their associated scope stack."""
for handler, scope, offset in deferred:
self.scope_stack = scope
self.offset = offset
handler()
@property
def scope(self):
return self.scope_stack[-1]
def pop_scope(self):
self.dead_scopes.append(self.scope_stack.pop())
def check_dead_scopes(self):
"""Look at scopes which have been fully examined and report names in
them which were imported but unused."""
for scope in self.dead_scopes:
export = isinstance(scope.get('__all__'), ExportBinding)
if export:
all = scope['__all__'].names()
# Look for possible mistakes in the export list
if not scope.importStarred and os.path.basename(self.filename) != '__init__.py':
undefined = set(all) - set(scope)
for name in undefined:
self.report(messages.UndefinedExport, scope['__all__'].source, name)
else:
all = []
# Look for imported names that aren't used without checking imports in namespace definition
for importation in scope.values():
if isinstance(importation, Importation) and not importation.used and importation.name not in all:
self.report(messages.UnusedImport, importation.source, importation.name)
def push_scope(self, scope_class=FunctionScope):
self.scope_stack.append(scope_class())
def push_function_scope(self): # XXX Deprecated
self.push_scope(FunctionScope)
def push_class_scope(self): # XXX Deprecated
self.push_scope(ClassScope)
def report(self, message_class, *args, **kwargs):
error_code = message_class.error_code
if(not error_code[:2] + "00" in self.ignore_errors and not error_code in self.ignore_errors and not
str(message_class.error_number) in self.ignore_errors):
kwargs['verbose'] = self.settings.get('verbose')
message = message_class(self.filename, *args, **kwargs)
if message.lineno not in self.ignore_lines:
self.messages.append(message)
def has_parent(self, node, kind):
while hasattr(node, 'parent'):
node = node.parent
if isinstance(node, kind):
return True
def get_common_ancestor(self, lnode, rnode, stop=None):
stop = stop or self.root
if lnode is rnode:
return lnode
if stop in (lnode, rnode):
return stop
if not hasattr(lnode, 'parent') or not hasattr(rnode, 'parent'):
return
if (lnode.level > rnode.level):
return self.get_common_ancestor(lnode.parent, rnode, stop)
if (rnode.level > lnode.level):
return self.get_common_ancestor(lnode, rnode.parent, stop)
return self.get_common_ancestor(lnode.parent, rnode.parent, stop)
def descendant_of(self, node, ancestors, stop=None):
for ancestor in ancestors:
if self.get_common_ancestor(node, ancestor, stop) not in (stop, None):
return True
return False
def on_fork(self, parent, lnode, rnode, items):
return (self.descendant_of(lnode, items, parent) ^ self.descendant_of(rnode, items, parent))
def different_forks(self, lnode, rnode):
"""True, if lnode and rnode are located on different forks of
IF/TRY."""
ancestor = self.get_common_ancestor(lnode, rnode)
if isinstance(ancestor, ast.If):
for fork in (ancestor.body, ancestor.orelse):
if self.on_fork(ancestor, lnode, rnode, fork):
return True
elif isinstance(ancestor, ast.Try):
body = ancestor.body + ancestor.orelse
for fork in [body] + [[hdl] for hdl in ancestor.handlers]:
if self.on_fork(ancestor, lnode, rnode, fork):
return True
elif isinstance(ancestor, ast.TryFinally):
if self.on_fork(ancestor, lnode, rnode, ancestor.body):
return True
return False
def add_binding(self, node, value, report_redef=True):
"""Called when a binding is altered.
- `node` is the statement responsible for the change
- `value` is the optional new value, a Binding instance, associated
with the binding; if None, the binding is deleted if it exists.
- if `report_redef` is True (default), rebinding while unused will be
reported.
"""
redefinedWhileUnused = False
if not isinstance(self.scope, ClassScope):
for scope in self.scope_stack[::-1]:
existing = scope.get(value.name)
if (isinstance(existing, Importation)
and not existing.used
and (not isinstance(value, Importation) or
value.fullName == existing.fullName)
and report_redef
and not self.different_forks(node, existing.source)):
redefinedWhileUnused = True
self.report(messages.RedefinedWhileUnused,
node, value.name, existing.source)
existing = self.scope.get(value.name)
if not redefinedWhileUnused and self.has_parent(value.source, ast.ListComp):
if (existing and report_redef
and not self.has_parent(existing.source, (ast.For, ast.ListComp))
and not self.different_forks(node, existing.source)):
self.report(messages.RedefinedInListComp,
node, value.name, existing.source)
if (isinstance(existing, Definition)
and not existing.used
and not self.different_forks(node, existing.source)):
self.report(messages.RedefinedWhileUnused,
node, value.name, existing.source)
else:
self.scope[value.name] = value
def get_node_handler(self, node_class):
try:
return self._node_handlers[node_class]
except KeyError:
nodeType = str(node_class.__name__).upper()
self._node_handlers[node_class] = handler = getattr(self, nodeType)
return handler
def iter_visible_scopes(self):
outerScopes = itertools.islice(self.scope_stack, len(self.scope_stack) - 1)
scopes = [scope for scope in outerScopes
if isinstance(scope, (FunctionScope, ModuleScope))]
if (isinstance(self.scope, GeneratorScope)
and scopes[-1] != self.scope_stack[-2]):
scopes.append(self.scope_stack[-2])
scopes.append(self.scope_stack[-1])
return iter(reversed(scopes))
def handle_node_load(self, node):
name = node_name(node)
if not name:
return
importStarred = False
for scope in self.iter_visible_scopes():
importStarred = importStarred or scope.importStarred
try:
scope[name].used = (self.scope, node)
except KeyError:
pass
else:
return
# look in the built-ins
if importStarred or name in self.frosted_builtins:
return
if name == '__path__' and os.path.basename(self.filename) == '__init__.py':
# the special name __path__ is valid only in packages
return
# protected with a NameError handler?
if 'NameError' not in self.except_handlers[-1]:
self.report(messages.UndefinedName, node, name)
def handle_node_store(self, node):
name = node_name(node)
if not name:
return
# if the name hasn't already been defined in the current scope
if isinstance(self.scope, FunctionScope) and name not in self.scope:
# for each function or module scope above us
for scope in self.scope_stack[:-1]:
if not isinstance(scope, (FunctionScope, ModuleScope)):
continue
# if the name was defined in that scope, and the name has
# been accessed already in the current scope, and hasn't
# been declared global
used = name in scope and scope[name].used
if used and used[0] is self.scope and name not in self.scope.globals:
# then it's probably a mistake
self.report(messages.UndefinedLocal,
scope[name].used[1], name, scope[name].source)
break
parent = getattr(node, 'parent', None)
if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)):
binding = Binding(name, node)
elif (parent is not None and name == '__all__' and
isinstance(self.scope, ModuleScope)):
binding = ExportBinding(name, parent.value)
else:
binding = Assignment(name, node)
if name in self.scope:
binding.used = self.scope[name].used
self.add_binding(node, binding)
def handle_node_delete(self, node):
name = node_name(node)
if not name:
return
if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
self.scope.globals.remove(name)
else:
try:
del self.scope[name]
except KeyError:
self.report(messages.UndefinedName, node, name)
def handle_children(self, tree):
for node in ast.iter_child_nodes(tree):
self.handleNode(node, tree)
def is_docstring(self, node):
"""Determine if the given node is a docstring, as long as it is at the
correct place in the node tree."""
return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and
isinstance(node.value, ast.Str))
def docstring(self, node):
if isinstance(node, ast.Expr):
node = node.value
if not isinstance(node, ast.Str):
return (None, None)
# Computed incorrectly if the docstring has backslash
doctest_lineno = node.lineno - node.s.count('\n') - 1
return (node.s, doctest_lineno)
def handleNode(self, node, parent):
if node is None:
return
if self.offset and getattr(node, 'lineno', None) is not None:
node.lineno += self.offset[0]
node.col_offset += self.offset[1]
if self.trace_tree:
print(' ' * self.node_depth + node.__class__.__name__)
if self.futures_allowed and not (isinstance(node, ast.ImportFrom) or
self.is_docstring(node)):
self.futures_allowed = False
self.node_depth += 1
node.level = self.node_depth
node.parent = parent
try:
handler = self.get_node_handler(node.__class__)
handler(node)
finally:
self.node_depth -= 1
if self.trace_tree:
print(' ' * self.node_depth + 'end ' + node.__class__.__name__)
_get_doctest_examples = doctest.DocTestParser().get_examples
def handle_doctests(self, node):
try:
docstring, node_lineno = self.docstring(node.body[0])
if not docstring:
return
examples = self._get_doctest_examples(docstring)
except (ValueError, IndexError):
# e.g. line 6 of the docstring for <string> has inconsistent
# leading whitespace: ...
return
node_offset = self.offset or (0, 0)
self.push_scope()
for example in examples:
try:
tree = compile(example.source, "<doctest>", "exec", ast.PyCF_ONLY_AST)
except SyntaxError:
e = sys.exc_info()[1]
position = (node_lineno + example.lineno + e.lineno,
example.indent + 4 + (e.offset or 0))
self.report(messages.DoctestSyntaxError, node, position)
else:
self.offset = (node_offset[0] + node_lineno + example.lineno,
node_offset[1] + example.indent + 4)
self.handle_children(tree)
self.offset = node_offset
self.pop_scope()
def find_return_with_argument(self, node):
"""Finds and returns a return statment that has an argument.
Note that we should use node.returns in Python 3, but this method is never called in Python 3 so we don't bother
checking.
"""
for item in node.body:
if isinstance(item, ast.Return) and item.value:
return item
elif not isinstance(item, ast.FunctionDef) and hasattr(item, 'body'):
return_with_argument = self.find_return_with_argument(item)
if return_with_argument:
return return_with_argument
def is_generator(self, node):
"""Checks whether a function is a generator by looking for a yield
statement or expression."""
if not isinstance(node.body, list):
# lambdas can not be generators
return False
for item in node.body:
if isinstance(item, (ast.Assign, ast.Expr)):
if isinstance(item.value, ast.Yield):
return True
elif not isinstance(item, ast.FunctionDef) and hasattr(item, 'body'):
if self.is_generator(item):
return True
return False
def ignore(self, node):
pass
# "stmt" type nodes
RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = TRYFINALLY = ASSERT = EXEC = EXPR = handle_children
CONTINUE = BREAK = PASS = ignore
# "expr" type nodes
BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = COMPARE = REPR = ATTRIBUTE = SUBSCRIPT = \
LIST = TUPLE = STARRED = NAMECONSTANT = handle_children
NUM = STR = BYTES = ELLIPSIS = ignore
# "slice" type nodes
SLICE = EXTSLICE = INDEX = handle_children
# expression contexts are node instances too, though being constants
LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore
# same for operators
AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = BITOR = BITXOR = BITAND = FLOORDIV = INVERT = \
NOT = UADD = USUB = EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
# additional node types
COMPREHENSION = KEYWORD = handle_children
def GLOBAL(self, node):
"""Keep track of globals declarations."""
if isinstance(self.scope, FunctionScope):
self.scope.globals.update(node.names)
NONLOCAL = GLOBAL
def LISTCOMP(self, node):
# handle generators before element
for gen in node.generators:
self.handleNode(gen, node)
self.handleNode(node.elt, node)
def GENERATOREXP(self, node):
self.push_scope(GeneratorScope)
# handle generators before element
for gen in node.generators:
self.handleNode(gen, node)
self.handleNode(node.elt, node)
self.pop_scope()
SETCOMP = GENERATOREXP
def DICTCOMP(self, node):
self.push_scope(GeneratorScope)
for gen in node.generators:
self.handleNode(gen, node)
self.handleNode(node.key, node)
self.handleNode(node.value, node)
self.pop_scope()
def FOR(self, node):
"""Process bindings for loop variables."""
vars = []
def collectLoopVars(n):
if isinstance(n, ast.Name):
vars.append(n.id)
elif isinstance(n, ast.expr_context):
return
else:
for c in ast.iter_child_nodes(n):
collectLoopVars(c)
collectLoopVars(node.target)
for varn in vars:
if (isinstance(self.scope.get(varn), Importation)
# unused ones will get an unused import warning
and self.scope[varn].used):
self.report(messages.ImportShadowedByLoopVar,
node, varn, self.scope[varn].source)
self.handle_children(node)
def NAME(self, node):
"""Handle occurrence of Name (which can be a load/store/delete
access.)"""
# Locate the name in locals / function / globals scopes.
if isinstance(node.ctx, (ast.Load, ast.AugLoad)):
self.handle_node_load(node)
if (node.id == 'locals' and isinstance(self.scope, FunctionScope)
and isinstance(node.parent, ast.Call)):
# we are doing locals() call in current scope
self.scope.uses_locals = True
elif isinstance(node.ctx, (ast.Store, ast.AugStore)):
self.handle_node_store(node)
elif isinstance(node.ctx, ast.Del):
self.handle_node_delete(node)
else:
# must be a Param context -- this only happens for names in function
# arguments, but these aren't dispatched through here
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
def CALL(self, node):
f = node.func
if isinstance(f, ast.Name):
for scope in self.iter_visible_scopes():
definition = scope.get(f.id)
if definition:
if isinstance(definition, FunctionDefinition):
definition.signature.checkCall(node, self, f.id)
break
self.handle_children(node)
def FUNCTIONDEF(self, node):
for deco in node.decorator_list:
self.handleNode(deco, node)
self.add_binding(node, FunctionDefinition(node.name, node))
self.LAMBDA(node)
if self.settings.get('run_doctests', False):
self.defer_function(lambda: self.handle_doctests(node))
def LAMBDA(self, node):
args = []
annotations = []
if PY2:
def addArgs(arglist):
for arg in arglist:
if isinstance(arg, ast.Tuple):
addArgs(arg.elts)
else:
if arg.id in args:
self.report(messages.DuplicateArgument,
node, arg.id)
args.append(arg.id)
addArgs(node.args.args)
defaults = node.args.defaults
else:
for arg in node.args.args + node.args.kwonlyargs:
annotations.append(arg.annotation)
args.append(arg.arg)
defaults = node.args.defaults + node.args.kw_defaults
# Only for Python3 FunctionDefs
is_py3_func = hasattr(node, 'returns')
for arg_name in ('vararg', 'kwarg'):
wildcard = getattr(node.args, arg_name)
if not wildcard:
continue
args.append(getattr(wildcard, 'arg', wildcard))
if is_py3_func:
if PY34_GTE:
annotations.append(wildcard.annotation)
else:
argannotation = arg_name + 'annotation'
annotations.append(getattr(node.args, argannotation))
if is_py3_func:
annotations.append(node.returns)
if PY3:
if len(set(args)) < len(args):
for (idx, arg) in enumerate(args):
if arg in args[:idx]:
self.report(messages.DuplicateArgument, node, arg)
for child in annotations + defaults:
if child:
self.handleNode(child, node)
def runFunction():
self.push_scope()
for name in args:
self.add_binding(node, Argument(name, node), report_redef=False)
if isinstance(node.body, list):
# case for FunctionDefs
for stmt in node.body:
self.handleNode(stmt, node)
else:
# case for Lambdas
self.handleNode(node.body, node)
def checkUnusedAssignments():
"""Check to see if any assignments have not been used."""
for name, binding in self.scope.unusedAssignments():
self.report(messages.UnusedVariable, binding.source, name)
self.defer_assignment(checkUnusedAssignments)
if PY2:
def checkReturnWithArgumentInsideGenerator():
"""Check to see if there are any return statements with
arguments but the function is a generator."""
if self.is_generator(node):
stmt = self.find_return_with_argument(node)
if stmt is not None:
self.report(messages.ReturnWithArgsInsideGenerator, stmt)
self.defer_assignment(checkReturnWithArgumentInsideGenerator)
self.pop_scope()
self.defer_function(runFunction)
def CLASSDEF(self, node):
"""Check names used in a class definition, including its decorators,
base classes, and the body of its definition.
Additionally, add its name to the current scope.
"""
for deco in node.decorator_list:
self.handleNode(deco, node)
for baseNode in node.bases:
self.handleNode(baseNode, node)
if not PY2:
for keywordNode in node.keywords:
self.handleNode(keywordNode, node)
self.push_scope(ClassScope)
if self.settings.get('run_doctests', False):
self.defer_function(lambda: self.handle_doctests(node))
for stmt in node.body:
self.handleNode(stmt, node)
self.pop_scope()
self.add_binding(node, ClassDefinition(node.name, node))
def ASSIGN(self, node):
self.handleNode(node.value, node)
for target in node.targets:
self.handleNode(target, node)
def AUGASSIGN(self, node):
self.handle_node_load(node.target)
self.handleNode(node.value, node)
self.handleNode(node.target, node)
def IMPORT(self, node):
for alias in node.names:
name = alias.asname or alias.name
importation = Importation(name, node)
self.add_binding(node, importation)
def IMPORTFROM(self, node):
if node.module == '__future__':
if not self.futures_allowed:
self.report(messages.LateFutureImport,
node, [n.name for n in node.names])
else:
self.futures_allowed = False
for alias in node.names:
if alias.name == '*':
self.scope.importStarred = True
self.report(messages.ImportStarUsed, node, node.module)
continue
name = alias.asname or alias.name
importation = Importation(name, node)
if node.module == '__future__':
importation.used = (self.scope, node)
self.add_binding(node, importation)
def TRY(self, node):
handler_names = []
# List the exception handlers
for handler in node.handlers:
if isinstance(handler.type, ast.Tuple):
for exc_type in handler.type.elts:
handler_names.append(node_name(exc_type))
elif handler.type:
handler_names.append(node_name(handler.type))
# Memorize the except handlers and process the body
self.except_handlers.append(handler_names)
for child in node.body:
self.handleNode(child, node)
self.except_handlers.pop()
# Process the other nodes: "except:", "else:", "finally:"
for child in ast.iter_child_nodes(node):
if child not in node.body:
self.handleNode(child, node)
TRYEXCEPT = TRY
def EXCEPTHANDLER(self, node):
# 3.x: in addition to handling children, we must handle the name of
# the exception, which is not a Name node, but a simple string.
if node.type is None:
self.report(messages.BareExcept, node)
if isinstance(node.name, str):
self.handle_node_store(node)
self.handle_children(node)