summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE21
-rwxr-xr-xbin/pyflakes40
-rw-r--r--pyflakes/__init__.py260
-rw-r--r--pyflakes/messages.py52
-rw-r--r--pyflakes/test/__init__.py20
-rw-r--r--pyflakes/test/test_imports.py418
-rw-r--r--pyflakes/test/test_other.py26
-rw-r--r--pyflakes/test/test_undefined_names.py103
-rwxr-xr-xsetup.py19
9 files changed, 959 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..42b8cf3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) 2005 Divmod, Inc., http://www.divmod.com/
+
+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.
diff --git a/bin/pyflakes b/bin/pyflakes
new file mode 100755
index 0000000..009bcf8
--- /dev/null
+++ b/bin/pyflakes
@@ -0,0 +1,40 @@
+#!/usr/bin/python
+
+import compiler, sys
+import os
+import pyflakes
+
+
+def check(codeString, filename):
+ try:
+ tree = compiler.parse(codeString)
+ except (SyntaxError, IndentationError):
+ value = sys.exc_info()[1]
+ (lineno, offset, line) = value[1][1:]
+ if line.endswith("\n"):
+ line = line[:-1]
+ print >> sys.stderr, 'could not compile %r:%d:' % (filename, lineno)
+ print >> sys.stderr, line
+ print >> sys.stderr, " " * (offset-2), "^"
+ else:
+ w = pyflakes.Checker(tree, filename)
+ w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
+ for warning in w.messages:
+ print warning
+
+
+def checkPath(filename):
+ return check(file(filename).read(), filename)
+
+args = sys.argv[1:]
+if args:
+ for arg in args:
+ if os.path.isdir(arg):
+ for dirpath, dirnames, filenames in os.walk(arg):
+ for filename in filenames:
+ if filename.endswith('.py'):
+ checkPath(os.path.join(dirpath, filename))
+ else:
+ checkPath(arg)
+else:
+ check(sys.stdin.read(), '<stdin>')
diff --git a/pyflakes/__init__.py b/pyflakes/__init__.py
new file mode 100644
index 0000000..a47d581
--- /dev/null
+++ b/pyflakes/__init__.py
@@ -0,0 +1,260 @@
+# (c) 2005 Divmod, Inc. See LICENSE file for details
+
+import __builtin__
+from pyflakes import messages
+
+class Binding(object):
+ def __init__(self, name, source):
+ self.name = name
+ self.source = source
+ self.used = False
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return '<Importation object %r from line %r at 0x%x>' % (self.name, self.source.lineno, id(self))
+
+class UnBinding(Binding):
+ '''Created by the 'del' operator.'''
+
+class Importation(Binding):
+ def __init__(self, name, source):
+ name = name.split('.')[0]
+ super(Importation, self).__init__(name, source)
+
+class Assignment(Binding):
+ pass
+
+
+class Scope(dict):
+ importStarred = False # set to True when import * is found
+
+ def __repr__(self):
+ return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
+
+ def __init__(self):
+ super(Scope, self).__init__()
+
+class ClassScope(Scope):
+ pass
+
+class FunctionScope(Scope):
+ pass
+
+class ModuleScope(Scope):
+ pass
+
+
+class Checker(object):
+ nodeDepth = 0
+ traceTree = False
+
+ def __init__(self, tree, filename='(none)'):
+ self.deferred = []
+ self.dead_scopes = []
+ self.messages = []
+ self.filename = filename
+ self.scopeStack = [ModuleScope()]
+
+ self.handleChildren(tree)
+ for handler, scope in self.deferred:
+ self.scopeStack = scope
+ handler()
+ del self.scopeStack[1:]
+ self.popScope()
+ self.check_dead_scopes()
+
+ def defer(self, callable):
+ '''Schedule something to be called after 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.append( (callable, self.scopeStack[:]) )
+
+ def scope(self):
+ return self.scopeStack[-1]
+ scope = property(scope)
+
+ def popScope(self):
+ self.dead_scopes.append(self.scopeStack.pop())
+
+ def check_dead_scopes(self):
+ for scope in self.dead_scopes:
+ for importation in scope.itervalues():
+ if isinstance(importation, Importation) and not importation.used:
+ self.report(messages.UnusedImport, importation.source.lineno, importation.name)
+
+ def pushFunctionScope(self):
+ self.scopeStack.append(FunctionScope())
+
+ def pushClassScope(self):
+ self.scopeStack.append(ClassScope())
+
+ def report(self, messageClass, *args, **kwargs):
+ self.messages.append(messageClass(self.filename, *args, **kwargs))
+
+ def handleChildren(self, tree):
+ for node in tree.getChildNodes():
+ self.handleNode(node)
+
+ def handleNode(self, node):
+ if self.traceTree:
+ print ' ' * self.nodeDepth + node.__class__.__name__
+ self.nodeDepth += 1
+ try:
+ handler = getattr(self, node.__class__.__name__.upper())
+ handler(node)
+ finally:
+ self.nodeDepth -= 1
+ if self.traceTree:
+ print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__
+
+ def ignore(self, node):
+ pass
+
+ STMT = PRINT = PRINTNL = TUPLE = LIST = ASSTUPLE = ASSATTR = \
+ ASSLIST = GETATTR = SLICE = SLICEOBJ = IF = CALLFUNC = DISCARD = FOR = \
+ RETURN = ADD = MOD = SUB = NOT = UNARYSUB = INVERT = ASSERT = COMPARE = \
+ SUBSCRIPT = AND = OR = TRYEXCEPT = RAISE = YIELD = DICT = LEFTSHIFT = \
+ RIGHTSHIFT = KEYWORD = TRYFINALLY = WHILE = EXEC = MUL = DIV = POWER = \
+ FLOORDIV = BITAND = BITOR = BITXOR = LISTCOMPFOR = LISTCOMPIF = \
+ AUGASSIGN = BACKQUOTE = UNARYADD = GENEXPR = GENEXPRFOR = GENEXPRIF = handleChildren
+
+ CONST = PASS = CONTINUE = BREAK = GLOBAL = ELLIPSIS = ignore
+
+ def addBinding(self, lineno, value, reportRedef=True):
+ '''Called when a binding is altered.
+
+ - `lineno` is the line of 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.
+ - iff `reportRedef` is True (default), rebinding while unused will be
+ reported.
+ '''
+ if isinstance(self.scope.get(value.name), Importation) \
+ and not self.scope[value.name].used \
+ and reportRedef:
+ self.report(messages.RedefinedWhileUnused, lineno, value.name, self.scope[value.name].source.lineno)
+
+ if isinstance(value, UnBinding):
+ try:
+ del self.scope[value.name]
+ except KeyError:
+ self.report(messages.UndefinedName, lineno, value.name)
+ else:
+ self.scope[value.name] = value
+
+
+ def LISTCOMP(self, node):
+ for qual in node.quals:
+ self.handleNode(qual)
+ self.handleNode(node.expr)
+
+ GENEXPRINNER = LISTCOMP
+
+ def NAME(self, node):
+ # try local scope
+ importStarred = self.scope.importStarred
+ try:
+ self.scope[node.name].used = True
+ except KeyError:
+ pass
+ else:
+ return
+
+ # try enclosing function scopes
+
+ for scope in self.scopeStack[-2:0:-1]:
+ importStarred = importStarred or scope.importStarred
+ if not isinstance(scope, FunctionScope):
+ continue
+ try:
+ scope[node.name].used = True
+ except KeyError:
+ pass
+ else:
+ return
+
+ # try global scope
+
+ importStarred = importStarred or self.scopeStack[0].importStarred
+ try:
+ self.scopeStack[0][node.name].used = True
+ except KeyError:
+ if (not hasattr(__builtin__, node.name)) \
+ and node.name not in ['__file__'] \
+ and not importStarred:
+ self.report(messages.UndefinedName, node.lineno, node.name)
+
+
+ def FUNCTION(self, node):
+ if getattr(node, "decorators", None) is not None:
+ map(self.NAME, node.decorators)
+ self.addBinding(node.lineno, Assignment(node.name, node))
+ self.LAMBDA(node)
+
+ def LAMBDA(self, node):
+ for default in node.defaults:
+ self.handleNode(default)
+
+ def runFunction():
+ args = []
+
+ def addArgs(arglist):
+ for arg in arglist:
+ if isinstance(arg, tuple):
+ addArgs(arg)
+ else:
+ if arg in args:
+ self.report(messages.DuplicateArgument, node.lineno, arg)
+ args.append(arg)
+
+ self.pushFunctionScope()
+ addArgs(node.argnames)
+ for name in args:
+ self.addBinding(node.lineno, Assignment(name, node), reportRedef=False)
+ self.handleNode(node.code)
+ self.popScope()
+
+ self.defer(runFunction)
+
+ def CLASS(self, node):
+ self.addBinding(node.lineno, Assignment(node.name, node))
+ for baseNode in node.bases:
+ self.handleNode(baseNode)
+ self.pushClassScope()
+ self.handleChildren(node.code)
+ self.popScope()
+
+ def ASSNAME(self, node):
+ if node.flags == 'OP_DELETE':
+ self.addBinding(node.lineno, UnBinding(node.name, node))
+ else:
+ self.addBinding(node.lineno, Assignment(node.name, node))
+
+ def ASSIGN(self, node):
+ self.handleNode(node.expr)
+ for subnode in node.nodes[::-1]:
+ self.handleNode(subnode)
+
+ def IMPORT(self, node):
+ for name, alias in node.names:
+ name = alias or name
+ importation = Importation(name, node)
+ self.addBinding(node.lineno, importation)
+
+ def FROM(self, node):
+ for name, alias in node.names:
+ if name == '*':
+ self.scope.importStarred = True
+ self.report(messages.ImportStarUsed, node.lineno, node.modname)
+ continue
+ name = alias or name
+ importation = Importation(name, node)
+ if node.modname == '__future__':
+ importation.used = True
+ self.addBinding(node.lineno, importation)
diff --git a/pyflakes/messages.py b/pyflakes/messages.py
new file mode 100644
index 0000000..ebb631b
--- /dev/null
+++ b/pyflakes/messages.py
@@ -0,0 +1,52 @@
+# (c) 2005 Divmod, Inc. See LICENSE file for details
+
+class Message(object):
+ message = ''
+ message_args = ()
+ def __init__(self, filename, lineno):
+ self.filename = filename
+ self.lineno = lineno
+ def __str__(self):
+ return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
+
+
+class UnusedImport(Message):
+ message = '%r imported but unused'
+ def __init__(self, filename, lineno, name):
+ Message.__init__(self, filename, lineno)
+ self.message_args = (name,)
+
+
+class RedefinedWhileUnused(Message):
+ message = 'redefinition of unused %r from line %r'
+ def __init__(self, filename, lineno, name, orig_lineno):
+ Message.__init__(self, filename, lineno)
+ self.message_args = (name, orig_lineno)
+
+
+class ImportStarUsed(Message):
+ message = "'from %s import *' used; unable to detect undefined names"
+ def __init__(self, filename, lineno, modname):
+ Message.__init__(self, filename, lineno)
+ self.message_args = (modname,)
+
+
+class UndefinedName(Message):
+ message = 'undefined name %r'
+ def __init__(self, filename, lineno, name):
+ Message.__init__(self, filename, lineno)
+ self.message_args = (name,)
+
+
+class DuplicateArgument(Message):
+ message = 'duplicate argument %r in function definition'
+ def __init__(self, filename, lineno, name):
+ Message.__init__(self, filename, lineno)
+ self.message_args = (name,)
+
+
+class RedefinedFunction(Message):
+ message = 'redefinition of fuction %r from line %r'
+ def __init__(self, filename, lineno, name, orig_lineno):
+ Message.__init__(self, filename, lineno)
+ self.message_args = (name, orig_lineno)
diff --git a/pyflakes/test/__init__.py b/pyflakes/test/__init__.py
new file mode 100644
index 0000000..98a5ced
--- /dev/null
+++ b/pyflakes/test/__init__.py
@@ -0,0 +1,20 @@
+import textwrap, compiler
+from twisted.trial import unittest
+
+import pyflakes
+
+class Test(unittest.TestCase):
+
+ def run(self, input, *expectedOutputs):
+ w = pyflakes.Checker(compiler.parse(textwrap.dedent(input)))
+ outputs = [type(o) for o in w.messages]
+ expectedOutputs = list(expectedOutputs)
+ outputs.sort()
+ expectedOutputs.sort()
+ self.assert_(outputs == expectedOutputs, '''\
+for input:
+%s
+expected outputs:
+%s
+but got:
+%s''' % (input, repr(expectedOutputs), '\n'.join([str(o) for o in w.messages])))
diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py
new file mode 100644
index 0000000..0843c18
--- /dev/null
+++ b/pyflakes/test/test_imports.py
@@ -0,0 +1,418 @@
+from pyflakes import messages as m, test
+
+class Test(test.Test):
+
+ def test_unusedImport(self):
+ self.run('import fu, bar', m.UnusedImport, m.UnusedImport)
+ self.run('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
+
+ def test_aliasedImport(self):
+ self.run('import fu as FU, bar as FU', m.RedefinedWhileUnused, m.UnusedImport)
+ self.run('from moo import fu as FU, bar as FU', m.RedefinedWhileUnused, m.UnusedImport)
+
+ def test_usedImport(self):
+ self.run('import fu; print fu')
+ self.run('from baz import fu; print fu')
+
+ def test_redefinedWhileUnused(self):
+ self.run('import fu; fu = 3', m.RedefinedWhileUnused)
+ self.run('import fu; del fu', m.RedefinedWhileUnused)
+ self.run('import fu; fu, bar = 3', m.RedefinedWhileUnused)
+ self.run('import fu; [fu, bar] = 3', m.RedefinedWhileUnused)
+
+ def test_redefinedByFunction(self):
+ self.run('''
+ import fu
+ def fu():
+ pass
+ ''', m.RedefinedWhileUnused)
+
+ def test_redefinedByClass(self):
+ self.run('''
+ import fu
+ class fu:
+ pass
+ ''', m.RedefinedWhileUnused)
+
+ def test_usedInFunction(self):
+ self.run('''
+ import fu
+ def fun():
+ print fu
+ ''')
+
+ def test_shadowedByParameter(self):
+ self.run('''
+ import fu
+ def fun(fu):
+ print fu
+ ''', m.UnusedImport)
+
+ self.run('''
+ import fu
+ def fun(fu):
+ print fu
+ print fu
+ ''')
+
+ def test_newAssignment(self):
+ self.run('fu = None')
+
+ def test_usedInGetattr(self):
+ self.run('import fu; fu.bar.baz')
+ self.run('import fu; "bar".fu.baz', m.UnusedImport)
+
+ def test_usedInSlice(self):
+ self.run('import fu; print fu.bar[1:]')
+
+ def test_usedInIfBody(self):
+ self.run('''
+ import fu
+ if True: print fu
+ ''')
+
+ def test_usedInIfConditional(self):
+ self.run('''
+ import fu
+ if fu: pass
+ ''')
+
+ def test_usedInElifConditional(self):
+ self.run('''
+ import fu
+ if False: pass
+ elif fu: pass
+ ''')
+
+ def test_usedInElse(self):
+ self.run('''
+ import fu
+ if False: pass
+ else: print fu
+ ''')
+
+ def test_usedInCall(self):
+ self.run('import fu; fu.bar()')
+
+ def test_usedInClass(self):
+ self.run('''
+ import fu
+ class bar:
+ bar = fu
+ ''')
+
+ def test_usedInClassBase(self):
+ self.run('''
+ import fu
+ class bar(object, fu.baz):
+ pass
+ ''')
+
+ def test_notUsedInNestedScope(self):
+ self.run('''
+ import fu
+ def bleh():
+ pass
+ print fu
+ ''')
+
+ def test_usedInFor(self):
+ self.run('''
+ import fu
+ for bar in range(9):
+ print fu
+ ''')
+
+ def test_usedInForElse(self):
+ self.run('''
+ import fu
+ for bar in range(10):
+ pass
+ else:
+ print fu
+ ''')
+
+ def test_redefinedByFor(self):
+ self.run('''
+ import fu
+ for fu in range(2):
+ pass
+ ''', m.RedefinedWhileUnused)
+
+ def test_usedInReturn(self):
+ self.run('''
+ import fu
+ def fun():
+ return fu
+ ''')
+
+ def test_usedInOperators(self):
+ self.run('import fu; 3 + fu.bar')
+ self.run('import fu; 3 % fu.bar')
+ self.run('import fu; 3 - fu.bar')
+ self.run('import fu; 3 * fu.bar')
+ self.run('import fu; 3 ** fu.bar')
+ self.run('import fu; 3 / fu.bar')
+ self.run('import fu; 3 // fu.bar')
+ self.run('import fu; -fu.bar')
+ self.run('import fu; ~fu.bar')
+ self.run('import fu; 1 == fu.bar')
+ self.run('import fu; 1 | fu.bar')
+ self.run('import fu; 1 & fu.bar')
+ self.run('import fu; 1 ^ fu.bar')
+ self.run('import fu; 1 >> fu.bar')
+ self.run('import fu; 1 << fu.bar')
+
+ def test_usedInAssert(self):
+ self.run('import fu; assert fu.bar')
+
+ def test_usedInSubscript(self):
+ self.run('import fu; fu.bar[1]')
+
+ def test_usedInLogic(self):
+ self.run('import fu; fu and False')
+ self.run('import fu; fu or False')
+ self.run('import fu; not fu.bar')
+
+ def test_usedInList(self):
+ self.run('import fu; [fu]')
+
+ def test_usedInTuple(self):
+ self.run('import fu; (fu,)')
+
+ def test_usedInTry(self):
+ self.run('''
+ import fu
+ try: fu
+ except: pass
+ ''')
+
+ def test_usedInExcept(self):
+ self.run('''
+ import fu
+ try: fu
+ except: pass
+ ''')
+
+ def test_redefinedByExcept(self):
+ self.run('''
+ import fu
+ try: pass
+ except Exception, fu: pass
+ ''', m.RedefinedWhileUnused)
+
+ def test_usedInRaise(self):
+ self.run('''
+ import fu
+ raise fu.bar
+ ''')
+
+ def test_usedInYield(self):
+ self.run('''
+ import fu
+ def gen():
+ yield fu
+ ''')
+
+ def test_usedInDict(self):
+ self.run('import fu; {fu:None}')
+ self.run('import fu; {1:fu}')
+
+ def test_usedInParameterDefault(self):
+ self.run('''
+ import fu
+ def f(bar=fu):
+ pass
+ ''')
+
+ def test_usedInAttributeAssign(self):
+ self.run('import fu; fu.bar = 1')
+
+ def test_usedInKeywordArg(self):
+ self.run('import fu; fu.bar(stuff=fu)')
+
+ def test_usedInAssignment(self):
+ self.run('import fu; bar=fu')
+ self.run('import fu; n=0; n+=fu')
+
+ def test_usedInListComp(self):
+ self.run('import fu; [fu for _ in range(1)]')
+ self.run('import fu; [1 for _ in range(1) if fu]')
+
+ def test_redefinedByListComp(self):
+ self.run('import fu; [1 for fu in range(1)]', m.RedefinedWhileUnused)
+
+ def test_usedInGenExp(self):
+ self.run('import fu; (fu for _ in range(1))')
+ self.run('import fu; (1 for _ in range(1) if fu)')
+
+ def test_redefinedByGenExp(self):
+ self.run('import fu; (1 for fu in range(1))', m.RedefinedWhileUnused)
+
+ def test_usedAsDecorator(self):
+ self.run('''
+ from interior import decorate
+ @decorate
+ def f():
+ return "hello"
+ ''')
+
+ self.run('''
+ @decorate
+ def f():
+ return "hello"
+ ''', m.UndefinedName)
+
+ def test_usedInTryFinally(self):
+ self.run('''
+ import fu
+ try: pass
+ finally: fu
+ ''')
+
+ self.run('''
+ import fu
+ try: fu
+ finally: pass
+ ''')
+
+ def test_usedInWhile(self):
+ self.run('''
+ import fu
+ while 0:
+ fu
+ ''')
+
+ self.run('''
+ import fu
+ while fu: pass
+ ''')
+
+ def test_usedInGlobal(self):
+ self.run('''
+ import fu
+ def f(): global fu
+ ''', m.UnusedImport)
+
+ def test_usedInBackquote(self):
+ self.run('import fu; `fu`')
+
+ def test_usedInExec(self):
+ self.run('import fu; exec "print 1" in fu.bar')
+
+ def test_usedInLambda(self):
+ self.run('import fu; lambda: fu')
+
+ def test_shadowedByLambda(self):
+ self.run('import fu; lambda fu: fu', m.UnusedImport)
+
+ def test_usedInSliceObj(self):
+ self.run('import fu; "meow"[::fu]')
+
+ def test_unusedInNestedScope(self):
+ self.run('''
+ def bar():
+ import fu
+ fu
+ ''', m.UnusedImport, m.UndefinedName)
+
+ def test_methodsDontUseClassScope(self):
+ self.run('''
+ class bar:
+ import fu
+ def fun(self):
+ fu
+ ''', m.UnusedImport, m.UndefinedName)
+
+ def test_nestedFunctionsNestScope(self):
+ self.run('''
+ def a():
+ def b():
+ fu
+ import fu
+ ''')
+
+ def test_nestedClassAndFunctionScope(self):
+ self.run('''
+ def a():
+ import fu
+ class b:
+ def c(self):
+ print fu
+ ''')
+
+ def test_importStar(self):
+ self.run('from fu import *', m.ImportStarUsed)
+
+ def test_packageImport(self):
+ self.run('import fu.bar; fu.bar')
+ test_packageImport.todo = "this has been hacked to treat 'import fu.bar' as just 'import fu'"
+
+ def test_assignRHSFirst(self):
+ self.run('import fu; fu = fu')
+ self.run('import fu; fu, bar = fu')
+ self.run('import fu; [fu, bar] = fu')
+ self.run('import fu; fu += fu')
+
+ def test_tryingMultipleImports(self):
+ self.run('''
+ try:
+ import fu
+ except ImportError:
+ import bar as fu
+ ''')
+ test_tryingMultipleImports.todo = ''
+
+ def test_nonGlobalDoesNotRedefine(self):
+ self.run('''
+ import fu
+ def a():
+ fu = 3
+ fu
+ ''')
+
+ def test_functionsRunLater(self):
+ self.run('''
+ def a():
+ fu
+ import fu
+ ''')
+
+ def test_functionNamesAreBoundNow(self):
+ self.run('''
+ import fu
+ def fu():
+ fu
+ fu
+ ''', m.RedefinedWhileUnused)
+
+ def test_ignoreNonImportRedefinitions(self):
+ self.run('a = 1; a = 2')
+
+ def test_importingForImportError(self):
+ self.run('''
+ try:
+ import fu
+ except ImportError:
+ pass
+ ''')
+ test_importingForImportError.todo = ''
+
+ def test_explicitlyPublic(self):
+ '''imports mentioned in __all__ are not unused'''
+ self.run('import fu; __all__ = ["fu"]')
+ test_explicitlyPublic.todo = "this would require importing the module or doing smarter parsing"
+
+ def test_importedInClass(self):
+ '''Imports in class scope can be used through self'''
+ self.run('''
+ class c:
+ import i
+ def __init__(self):
+ self.i
+ ''')
+ test_importedInClass.todo = 'requires evaluating attribute access'
+
+ def test_futureImport(self):
+ '''__future__ is special'''
+ self.run('from __future__ import division')
diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py
new file mode 100644
index 0000000..5ce8294
--- /dev/null
+++ b/pyflakes/test/test_other.py
@@ -0,0 +1,26 @@
+from pyflakes import messages as m, test
+
+class Test(test.Test):
+
+ def test_duplicateArgs(self):
+ self.run('def fu(bar, bar): pass', m.DuplicateArgument)
+
+ def test_localReferencedBeforeAssignment(self):
+ self.run('''
+ a = 1
+ def f():
+ a; a=1
+ f()
+ ''', m.UndefinedName)
+ test_localReferencedBeforeAssignment.todo = 'this requires finding all assignments in the function body first'
+
+ def test_redefinedFunction(self):
+ self.run('''
+ def a(): pass
+ def a(): pass
+ ''', m.RedefinedFunction)
+ test_redefinedFunction.todo = 'easy to implement'
+
+ def test_unaryPlus(self):
+ '''Don't die on unary +'''
+ self.run('+1')
diff --git a/pyflakes/test/test_undefined_names.py b/pyflakes/test/test_undefined_names.py
new file mode 100644
index 0000000..8b7f15c
--- /dev/null
+++ b/pyflakes/test/test_undefined_names.py
@@ -0,0 +1,103 @@
+from pyflakes import messages as m, test
+
+class Test(test.Test):
+ def test_undefined(self):
+ self.run('bar', m.UndefinedName)
+
+ def test_definedInListComp(self):
+ self.run('[a for a in range(10) if a]')
+
+ def test_definedInGenExp(self):
+ self.run('(a for a in xrange(10) if a)')
+
+ def test_functionsNeedGlobalScope(self):
+ self.run('''
+ class a:
+ def b():
+ fu
+ fu = 1
+ ''')
+
+ def test_builtins(self):
+ self.run('range(10)')
+
+ def test_magic_globals(self):
+ self.run('__file__')
+
+ def test_globalImportStar(self):
+ '''Can't find undefined names with import *'''
+ self.run('from fu import *; bar', m.ImportStarUsed)
+
+ def test_localImportStar(self):
+ '''A local import * still allows undefined names to be found in upper scopes'''
+ self.run('''
+ def a():
+ from fu import *
+ bar''', m.ImportStarUsed, m.UndefinedName)
+
+ def test_unpackedParameter(self):
+ '''Unpacked function parameters create bindings'''
+ self.run('''
+ def a((bar, baz)):
+ bar; baz
+ ''')
+
+ def test_definedByGlobal(self):
+ '''"global" can make an otherwise undefined name in another function defined'''
+ self.run('''
+ def a(): global fu; fu = 1
+ def b(): fu
+ ''')
+ test_definedByGlobal.todo = ''
+
+ def test_del(self):
+ '''del deletes bindings'''
+ self.run('a = 1; del a; a', m.UndefinedName)
+
+ def test_delGlobal(self):
+ '''del a global binding from a function'''
+ self.run('''
+ a = 1
+ def f():
+ global a
+ del a
+ a
+ ''')
+ test_delGlobal.todo = ''
+
+ def test_delUndefined(self):
+ '''del an undefined name'''
+ self.run('del a', m.UndefinedName)
+
+ def test_globalFromNestedScope(self):
+ '''global names are available from nested scopes'''
+ self.run('''
+ a = 1
+ def b():
+ def c():
+ a
+ ''')
+
+ def test_nestedClass(self):
+ '''nested classes can access enclosing scope'''
+ self.run('''
+ def f(foo):
+ class C:
+ bar = foo
+ def f(self):
+ return foo
+ return C()
+
+ f(123).f()
+ ''')
+
+ def test_badNestedClass(self):
+ '''free variables in nested classes must bind at class creation'''
+ self.run('''
+ def f():
+ class C:
+ bar = foo
+ foo = 456
+
+ f()
+ ''', m.UndefinedName)
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..3c1deb4
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,19 @@
+#!/usr/bin/python
+# (c) 2005 Divmod, Inc. See LICENSE file for details
+
+from distutils.core import setup
+
+setup(
+ name="pyflakes",
+ license="MIT",
+ version="0.2.0",
+ description="passive checker of Python programs",
+ author="Phil Frost",
+ maintainer="Moe Aboulkheir",
+ maintainer_email="moe@divmod.com",
+ url="http://www.divmod.org/projects/pyflakes",
+ packages=["pyflakes"],
+ scripts=["bin/pyflakes"],
+ long_description="""Pyflakes is program to analyze Python programs and detect various errors. It
+works by parsing the source file, not importing it, so it is safe to use on
+modules with side effects. It's also much faster.""")