diff options
-rw-r--r-- | LICENSE | 21 | ||||
-rwxr-xr-x | bin/pyflakes | 40 | ||||
-rw-r--r-- | pyflakes/__init__.py | 260 | ||||
-rw-r--r-- | pyflakes/messages.py | 52 | ||||
-rw-r--r-- | pyflakes/test/__init__.py | 20 | ||||
-rw-r--r-- | pyflakes/test/test_imports.py | 418 | ||||
-rw-r--r-- | pyflakes/test/test_other.py | 26 | ||||
-rw-r--r-- | pyflakes/test/test_undefined_names.py | 103 | ||||
-rwxr-xr-x | setup.py | 19 |
9 files changed, 959 insertions, 0 deletions
@@ -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.""") |