143 lines
4.3 KiB
Python
Executable File
143 lines
4.3 KiB
Python
Executable File
"""Code for checking for local names and superfluous import statements.
|
|
|
|
This code provides searches for local symbols in the AST, assignments and such
|
|
things.
|
|
"""
|
|
# This file is part of the Snakefood open source package.
|
|
# See http://furius.ca/snakefood/ for licensing details.
|
|
|
|
# stdlib imports
|
|
import compiler
|
|
|
|
__all__ = ('get_names_from_ast', 'filter_unused_imports',
|
|
'NamesVisitor', 'AssignVisitor', 'AllVisitor')
|
|
|
|
|
|
def get_names_from_ast(ast):
|
|
"Find all the names being referenced/used."
|
|
vis = NamesVisitor()
|
|
compiler.walk(ast, vis)
|
|
dotted_names, simple_names = vis.finalize()
|
|
return (dotted_names, simple_names)
|
|
|
|
|
|
def filter_unused_imports(ast, found_imports):
|
|
"""
|
|
Given the ast and the list of found imports in the file, find out which of
|
|
the imports are not used and return two lists: a list of used imports, and a
|
|
list of unused imports.
|
|
"""
|
|
used_imports, unused_imports = [], []
|
|
|
|
# Find all the names being referenced/used.
|
|
dotted_names, simple_names = get_names_from_ast(ast)
|
|
|
|
# Find all the names being exported via __all__.
|
|
vis = AllVisitor()
|
|
compiler.walk(ast, vis)
|
|
exported = vis.finalize()
|
|
|
|
# Check that all imports have been referenced at least once.
|
|
usednames = set(x[0] for x in dotted_names)
|
|
usednames.update(x[0] for x in exported)
|
|
used_imports = []
|
|
for x in found_imports:
|
|
_, _, lname, lineno, _, _ = x
|
|
if lname is not None and lname not in usednames:
|
|
unused_imports.append(x)
|
|
else:
|
|
used_imports.append(x)
|
|
|
|
return used_imports, unused_imports
|
|
|
|
|
|
class Visitor(object):
|
|
"Base class for our visitors."
|
|
def continue_(self, node):
|
|
for child in node.getChildNodes():
|
|
self.visit(child)
|
|
|
|
|
|
class NamesVisitor(Visitor):
|
|
"""AST visitor that finds all the identifier references that are defined,
|
|
including dotted references. This includes all free names and names with
|
|
attribute references.
|
|
"""
|
|
def __init__(self):
|
|
self.dotted = []
|
|
self.simple = []
|
|
self.attributes = []
|
|
|
|
def visitName(self, node):
|
|
self.attributes.append(node.name)
|
|
self.attributes.reverse()
|
|
attribs = self.attributes
|
|
for i in xrange(1, len(attribs)+1):
|
|
self.dotted.append(('.'.join(attribs[0:i]), node.lineno))
|
|
self.simple.append((attribs[0], node.lineno))
|
|
self.attributes = []
|
|
|
|
def visitGetattr(self, node):
|
|
self.attributes.append(node.attrname)
|
|
self.continue_(node)
|
|
|
|
def finalize(self):
|
|
return self.dotted, self.simple
|
|
|
|
|
|
class AssignVisitor(Visitor):
|
|
"""AST visitor that builds a list of all potential names that are being
|
|
assigned to. This is used later to heuristically figure out if a name being
|
|
refered to is never assigned to nor in the imports."""
|
|
|
|
def __init__(self):
|
|
self.assnames = []
|
|
self.in_class = False
|
|
|
|
def visitAssName(self, node):
|
|
self.assnames.append((node.name, node.lineno))
|
|
self.continue_(node)
|
|
|
|
def visitClass(self, node):
|
|
self.assnames.append((node.name, node.lineno))
|
|
prev, self.in_class = self.in_class, True
|
|
self.continue_(node)
|
|
self.in_class = prev
|
|
|
|
def visitFunction(self, node):
|
|
# Avoid method definitions.
|
|
if not self.in_class:
|
|
self.assnames.append((node.name, node.lineno))
|
|
self.continue_(node)
|
|
|
|
def finalize(self):
|
|
return self.assnames
|
|
|
|
|
|
class AllVisitor(Visitor):
|
|
"""AST visitor that find an __all__ directive and accumulates the list of
|
|
constants in it."""
|
|
|
|
def __init__(self):
|
|
self.all = []
|
|
self.in_assign = False
|
|
self.in_all = False
|
|
|
|
def visitAssign(self, node):
|
|
prev, self.in_assign = self.in_assign, True
|
|
self.continue_(node)
|
|
self.in_assign = prev
|
|
|
|
def visitAssName(self, node):
|
|
if self.in_assign and node.name == '__all__':
|
|
self.in_all = True
|
|
self.continue_(node)
|
|
|
|
def visitConst(self, node):
|
|
if self.in_assign and self.in_all:
|
|
self.all.append((node.value, node.lineno))
|
|
self.continue_(node)
|
|
|
|
def finalize(self):
|
|
return self.all
|