Skip to content

Instantly share code, notes, and snippets.

@elazarg
Last active October 22, 2016 13:41
Show Gist options
  • Save elazarg/bc9e5f326d718dcde827c13a3328a86b to your computer and use it in GitHub Desktop.
Save elazarg/bc9e5f326d718dcde827c13a3328a86b to your computer and use it in GitHub Desktop.
A small script that warns about functions that are not called from within the code
#!/usr/bin/python3
from contextlib import contextmanager
from ast import NodeVisitor
import ast
import sys
import glob
class Flags:
include_strings = True
def is_external_or_in(s):
def is_external(name):
prefixes = ['.__', '.test_', '.visit_']
if any(name.startswith(p) for p in prefixes):
return True
return False
return lambda x: x in s or is_external(x)
class Collector(NodeVisitor):
@classmethod
def collect(cls, modules_filenames, referenced=set()):
collector = cls()
collector.referenced = is_external_or_in(referenced)
for module, filename in modules_filenames:
collector.namespace = ('module ' + filename,)
collector.visit(module)
return collector.return_items()
@contextmanager
def enter_namespace(self, kind, name):
self.namespace += ('{} {}'.format(kind, name),)
yield
self.namespace = self.namespace[:-1]
def not_in_class(self):
return not self.namespace[-1].startswith('class ')
def visit_ClassDef(self, cd: ast.ClassDef):
with self.enter_namespace('class', cd.name):
self.generic_visit(cd)
def visit_FunctionDef(self, fd: ast.FunctionDef):
with self.enter_namespace('def', fd.name):
self.generic_visit(fd)
def visit_AsyncFunctionDef(self, fd: 'ast.AsyncFunctionDef'):
return self.visit_FunctionDef(fd)
def is_referenced(self, name):
return self.referenced('.' + name) \
or self.not_in_class() and self.referenced(name)
class Defs(Collector):
def __init__(self):
self.defs = {} # name -> namespace
def visit_FunctionDef(self, fd: ast.FunctionDef):
if not self.is_referenced(fd.name):
self.defs[fd.name] = self.namespace + (str(fd.lineno),)
super().visit_FunctionDef(fd)
def return_items(self):
return self.defs
class Refs(Collector):
def __init__(self):
self.refs = set()
def visit_FunctionDef(self, fd: ast.FunctionDef):
if self.is_referenced(fd.name) or fd.name in self.refs:
super().visit_FunctionDef(fd)
def visit_Attribute(self, attr: ast.Attribute):
self.refs.add('.' + attr.attr)
def visit_Name(self, name: ast.Name):
if isinstance(name.ctx, ast.Load):
self.refs.add(name.id)
def visit_Str(self, st: ast.Str):
if Flags.include_strings:
self.refs.add(st.s)
self.refs.add('.' + st.s)
def return_items(self):
return self.refs
def parse_modules(filenames):
for filename in filenames:
with open(filename) as f:
source = f.read()
try:
module = ast.parse(source, filename=filename)
except SyntaxError:
from sys import stderr
print('Could not parse ' + filename, file=stderr)
else:
yield module, filename
def find_unused(files):
modules_filenames = tuple(parse_modules(files))
referenced = set()
while True:
now_referenced = Refs.collect(modules_filenames,
referenced=referenced)
if now_referenced <= referenced:
break
referenced.update(now_referenced)
return Defs.collect(modules_filenames, referenced)
def print_unused(defs):
for item, (filename, *namespace, line) in sorted(defs.items(), key=lambda x:x[1]):
path = '.'.join(namespace)
print('{0}:{1}\t{2}\tat {3}'.format(filename[7:], line, item, path))
def main():
argv = sys.argv[1:] or glob.glob('*.py')
print_unused(find_unused(argv))
if __name__ == '__main__':
main()
def unused(): pass
from re import findall, IGNORECASE
import sys
def find_defs(txt):
return set([x[5:] for x in findall(' def [a-z][0-9a-z_]+', txt, IGNORECASE)])
def find_calls(txt):
return set(findall('(?<!def )[a-z][0-9a-z_]+', txt, IGNORECASE))
def main(files):
file_defs = {}
calls = set()
for file in files:
file_defs[file] = set()
with open(file) as f:
text = f.read()
file_defs[file].update(find_defs(text))
calls.update(find_calls(text))
for file in file_defs:
file_defs[file] = {x for x in file_defs[file] - calls
if 'visit' not in x and not x.startswith('test_')}
for file, defs in file_defs.items():
if not defs:
continue
print(file, ':')
for d in defs:
print('\t{}'.format(d))
if __name__ == '__main__':
main(sys.argv[1:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment