1
#!/usr/bin/env python2.4
2
##############################################################################
4
# Copyright (c) 2003 Zope Corporation and Contributors.
7
# This software is subject to the provisions of the Zope Public License,
8
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
9
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
10
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
12
# FOR A PARTICULAR PURPOSE.
14
##############################################################################
17
This utility finds unused imports in Python modules. Its output is
18
grep-like and thus emacs-friendly.
20
$Id: importchecker.py 38688 2005-09-29 16:33:36Z fdrake $
27
def _findDottedNamesHelper(node, result):
29
name = node.__class__.__name__
32
while name == 'Getattr':
33
dotted.append(node.attrname)
35
name = node.__class__.__name__
37
dotted.append(node.name)
39
for i in range(1, len(dotted)):
40
result.append('.'.join(dotted[:i]))
41
result.append('.'.join(dotted))
44
result.append(node.name)
46
elif name == 'AssAttr':
47
# Can be on an import as well.
52
result.append(getattr(expr, 'name', ''))
54
for child in more_node.getChildNodes():
55
_findDottedNamesHelper(child, result)
58
def findDottedNames(node):
59
"""Find dotted names in an AST tree node
62
_findDottedNamesHelper(node, result)
67
"""An instance of this class will be used to walk over a compiler AST
68
tree (a module). During that operation, the appropriate methods of
69
this visitor will be called
75
def visitFrom(self, stmt):
76
"""Will be called for 'from foo import bar' statements
78
module_name, names = stmt.asList()
79
if module_name == '__future__':
80
# we don't care what's imported from the future
83
for orig_name, as_name in names:
84
# we don't care about from import *
91
names_dict[name] = orig_name
92
self._map.setdefault(module_name, {'names': names_dict,
93
'lineno': stmt.lineno})
95
def visitImport(self, stmt):
96
"""Will be called for 'import foo.bar' statements
98
for orig_name, as_name in stmt.names:
103
self._map.setdefault(orig_name, {'names': {name: orig_name},
104
'lineno': stmt.lineno})
110
def findImports(mod):
111
"""Find import statements in module and put the result in a mapping.
113
visitor = ImportFinder()
114
compiler.walk(mod, visitor)
115
return visitor.getMap()
119
"""This represents a python module.
122
def __init__(self, path):
123
mod = compiler.parseFile(path)
125
self._map = findImports(mod)
127
self._dottednames = findDottedNames(mod)
130
"""Return the path to this module's file.
134
def getImportedModuleNames(self):
135
"""Return the names of imported modules.
137
return self._map.keys()
139
def getImportNames(self):
140
"""Return the names of imports; add dottednames as well.
144
for module_name in map.keys():
145
for usedname, originalname in map[module_name]['names'].items():
146
result.append((originalname, module_name))
147
# add any other name that we could be using
148
for dottedname in self._dottednames:
149
usednamedot = usedname + '.'
150
if dottedname.startswith(usednamedot):
151
attrname = dottedname[len(usednamedot):].split('.')[0]
152
result.append((attrname, module_name))
155
def getUnusedImports(self):
156
"""Get unused imports of this module (the whole import info).
159
for value in self._map.values():
160
for usedname, originalname in value['names'].items():
161
if usedname not in self._dottednames:
162
result.append((originalname, value['lineno']))
171
def visit(self, arg, dirname, names):
172
"""This method will be called when we walk the filesystem
173
tree. It looks for python modules and stored their filenames.
176
# get all .py files that aren't weirdo emacs droppings
177
if name.endswith('.py') and not name.startswith('.#'):
178
self._files.append(os.path.join(dirname, name))
180
def getModuleFilenames(self):
184
def findModules(path):
185
"""Find python modules in the given path and return their absolute
186
filenames in a sequence.
188
finder = ModuleFinder()
189
os.path.walk(path, finder.visit, ())
190
return finder.getModuleFilenames()
193
class ImportDatabase:
194
"""This database keeps tracks of imports.
196
It allows to NOT report cases where a module imports something
197
just so that another module can import it (import dependencies).
200
def __init__(self, root_path):
201
self._root_path = root_path
205
def resolveDottedModuleName(self, dotted_name, module):
206
"""Return path to file representing module, or None if no such
207
thing. Can do this relative from module.
209
dotted_path = dotted_name.replace('.', '/')
210
# try relative import first
211
path = os.path.join(os.path.dirname(module.getPath()), dotted_path)
212
path = self._resolveHelper(path)
215
# absolute import (assumed to be from this tree)
216
if os.path.isfile(os.path.join(self._root_path, '__init__.py')):
217
startpath, dummy = os.path.split(self._root_path)
219
startpath = self._root_path
220
return self._resolveHelper(os.path.join(startpath, dotted_path))
222
def _resolveHelper(self, path):
223
if os.path.isfile(path + '.py'):
225
if os.path.isdir(path):
226
path = os.path.join(path, '__init__.py')
227
if os.path.isfile(path):
231
def findModules(self):
232
"""Find modules in the given path.
234
for modulepath in findModules(self._root_path):
235
module = Module(modulepath)
236
self.addModule(module)
238
def addModule(self, module):
239
"""Add information about a module to the database. A module in
240
this case is not a python module object, but an instance of
241
the above defined Module class.w
243
self_path = module.getPath()
244
# do nothing if we already know about it
245
if self._modules.has_key(self_path):
248
self._modules[self_path] = module
250
# add imported names to internal names mapping; this will
251
# allow us identify dependent imports later
253
for name, from_module_name in module.getImportNames():
254
path = self.resolveDottedModuleName(from_module_name, module)
256
modulepaths = names.get(t, {})
257
if not modulepaths.has_key(self_path):
258
modulepaths[self_path] = 1
259
names[t] = modulepaths
261
def getUnusedImports(self):
262
"""Get unused imports of all known modules.
265
for path, module in self._modules.items():
266
result[path] = self.getUnusedImportsInModule(module)
269
def getUnusedImportsInModule(self, module):
270
"""Get all unused imports in a module.
273
for name, lineno in module.getUnusedImports():
274
if not self.isNameImportedFrom(name, module):
275
result.append((name, lineno))
278
def isNameImportedFrom(self, name, module):
279
"""Return true if name is imported from module by another module.
281
return self._names.has_key((module.getPath(), name))
283
def getModulesImportingNameFrom(self, name, module):
284
"""Return list of known modules that import name from module.
287
for path in self._names.get((module.getPath(), name), {}).keys():
288
result.append(self._modules[path])
296
print "No path supplied"
299
path = os.path.abspath(path)
300
if not os.path.isdir(path):
301
print "Unknown path:", path
305
db = ImportDatabase(path)
307
unused_imports = db.getUnusedImports()
308
module_paths = unused_imports.keys()
310
for path in module_paths:
311
info = unused_imports[path]
316
for name, line in info:
317
names = line2names.get(line, [])
319
line2names[line] = names
320
lines = line2names.keys()
323
names = ', '.join(line2names[line])
324
print "%s:%s: %s" % (path, line, names)
326
if __name__ == '__main__':