Commits

Brodie Rao committed c440365 Merge

Merged default into experimental

  • Participants
  • Parent commits 0fc7061, 532dabc
  • Branches experimental

Comments (0)

Files changed (11)

-\.svn/
-\.pyc$
+syntax: glob
+
+*.pyc
+.DS_Store
+build
+dist
+MANIFEST
+===================================
+pyflakes3k -- py3k port of pyflakes
+===================================
+
+This is a Python 3 port of `pyflakes <http://pypi.python.org/pypi/pyflakes>`_. It is based on Georg Brandl's `pyflake-ast <http://bitbucket.org/birkenfeld/pyflakes-ast>`_. The source repository is hosted on `bitbucket <http://bitbucket.org/hsoft/pyflakes3k>`_.

File pyflakes/checker.py

 # (c) 2005-2010 Divmod, Inc.
 # See LICENSE file for details
 
-import __builtin__
-import os.path
+try:
+    import builtins
+except ImportError:
+    import __builtin__ as builtins
+
+try:
+    [1 for a in range(1)]
+    del a
+except NameError:
+    listCompsHaveScope = True
+else:
+    listCompsHaveScope = False
+
 import _ast
+import os
 import re
+import sys
 
 from pyflakes import messages
 
                 all = []
 
             # Look for imported names that aren't used.
-            for importation in scope.itervalues():
+            for importation in scope.values():
                 if isinstance(importation, Importation):
                     if not importation.used and importation.name not in all:
                         self.report(
     def handleNode(self, node, parent):
         node.parent = parent
         if self.traceTree:
-            print '  ' * self.nodeDepth + node.__class__.__name__
+            sys.stdout.write('  ' * self.nodeDepth + node.__class__.__name__ +
+                             '\n')
         self.nodeDepth += 1
         if self.futuresAllowed and not \
                (isinstance(node, _ast.ImportFrom) or self.isDocstring(node)):
         finally:
             self.nodeDepth -= 1
         if self.traceTree:
-            print '  ' * self.nodeDepth + 'end ' + node.__class__.__name__
+            sys.stdout.write('  ' * self.nodeDepth + 'end ' +
+                             node.__class__.__name__ + '\n')
 
     def ignore(self, node):
         pass
     BOOLOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \
         REPR = SUBSCRIPT = LIST = TUPLE = handleChildren
 
-    NUM = STR = ELLIPSIS = ignore
+    NUM = STR = BYTES = ELLIPSIS = ignore
 
     # "slice" type nodes
     SLICE = EXTSLICE = INDEX = handleChildren
     # additional node types
     COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
 
+    def hasParent(self, node, kind):
+        parent = getattr(node, 'parent', None)
+        while True:
+            if not parent:
+                return False
+            elif isinstance(parent, kind):
+                return True
+            parent = getattr(parent, 'parent', None)
+
     def addBinding(self, lineno, value, reportRedef=True):
         '''Called when a binding is altered.
 
             self.report(messages.RedefinedFunction,
                         lineno, value.name, self.scope[value.name].source.lineno)
 
+        redefinedWhileUnused = False
+
         if not isinstance(self.scope, ClassScope):
             for scope in self.scopeStack[::-1]:
                 existing = scope.get(value.name)
                         and (not isinstance(value, Importation) or value.fullName == existing.fullName)
                         and reportRedef
                         and self.noRedef == 0):
-
+                    redefinedWhileUnused = True
                     self.report(messages.RedefinedWhileUnused,
                                 lineno, value.name, scope[value.name].source.lineno)
 
+        if (not listCompsHaveScope and not redefinedWhileUnused and
+            self.hasParent(value.source, _ast.ListComp)):
+            existing = self.scope.get(value.name)
+            if (existing and
+                not self.hasParent(existing.source, (_ast.For, _ast.ListComp))
+                and reportRedef):
+                self.report(messages.RedefinedInListComp, lineno, value.name,
+                            self.scope[value.name].source.lineno)
+
         if isinstance(value, UnBinding):
             try:
                 del self.scope[value.name]
                         maxnum = max(maxnum, int(fn))
                     else:
                         kwds.add(fn)
-            except ValueError, err:
+            except ValueError:
+                err = sys.exc_info()[1]
                 self.report(messages.StringFormatProblem,
                             node.lineno, str(err))
             else:
             try:
                 self.scopeStack[0][node.id].used = (self.scope, node.lineno)
             except KeyError:
-                if ((not hasattr(__builtin__, node.id))
+                if ((not hasattr(builtins, node.id))
                         and node.id not in _MAGIC_GLOBALS
                         and not importStarred):
                     if (os.path.basename(self.filename) == '__init__.py' and
                     if isinstance(arg, _ast.Tuple):
                         addArgs(arg.elts)
                     else:
-                        if arg.id in args:
+                        try:
+                            id_ = arg.arg
+                        except AttributeError:
+                            id_ = arg.id
+                        if id_ in args:
                             self.report(messages.DuplicateArgument,
-                                        node.lineno, arg.id)
-                        args.append(arg.id)
+                                        node.lineno, id_)
+                        args.append(id_)
 
             self.pushFunctionScope()
             addArgs(node.args.args)
                 """
                 Check to see if any assignments have not been used.
                 """
-                for name, binding in self.scope.iteritems():
+                for name, binding in self.scope.items():
                     if (not binding.used and not name in self.scope.globals
                         and isinstance(binding, Assignment)):
                         self.report(messages.UnusedVariable,

File pyflakes/messages.py

         self.message_args = (name, orig_lineno)
 
 
+class RedefinedInListComp(Message):
+    message = 'list comprehension redefines %r from line %r'
+    def __init__(self, filename, lineno, name, orig_lineno):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name, orig_lineno)
+
+
 class ImportShadowedByLoopVar(Message):
     message = 'import %r from line %r shadowed by loop variable'
     def __init__(self, filename, lineno, name, orig_lineno):

File pyflakes/scripts/pyflakes.py

     # First, compile into an AST and handle syntax errors.
     try:
         tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
-    except SyntaxError, value:
+    except SyntaxError as value:
         msg = value.args[0]
 
         (lineno, offset, text) = value.lineno, value.offset, value.text
             # Avoid using msg, since for the only known case, it contains a
             # bogus message that claims the encoding the file declared was
             # unknown.
-            print >> sys.stderr, "%s: problem decoding source" % (filename, )
+            sys.stderr.write('%s: problem decoding source\n' % filename)
         else:
             line = text.splitlines()[-1]
 
             if offset is not None:
                 offset = offset - (len(text) - len(line))
 
-            print >> sys.stderr, '%s:%d: %s' % (filename, lineno, msg)
-            print >> sys.stderr, line
+            sys.stderr.write('%s:%d: %s\n' % (filename, lineno, msg))
+            sys.stderr.write(line + '\n')
 
             if offset is not None:
-                print >> sys.stderr, " " * offset, "^"
+                sys.stderr.write(' ' * offset + ' ^\n')
 
         return 1
     else:
         # Okay, it's syntactically valid.  Now check it.
         w = checker.Checker(tree, filename)
-        w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
+        w.messages.sort(key=lambda m: m.lineno)
         for warning in w.messages:
-            print warning
+            sys.stdout.write(warning + '\n')
         return len(w.messages)
 
 
     @return: the number of warnings printed
     """
     try:
-        return check(file(filename, 'U').read() + '\n', filename)
-    except IOError, msg:
-        print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
+        return check(open(filename, 'rb').read() + b'\n', filename)
+    except IOError as msg:
+        sys.stderr.write('%s: %s\n' % (filename, msg.args[1]))
         return 1
 
 

File pyflakes/test/harness.py

 import textwrap
 import _ast
 
-from twisted.trial import unittest
+import unittest
 
 from pyflakes import checker
 
         w = checker.Checker(ast, **kw)
         outputs = [type(o) for o in w.messages]
         expectedOutputs = list(expectedOutputs)
-        outputs.sort()
-        expectedOutputs.sort()
+        outputs.sort(key=lambda t: t.__name__)
+        expectedOutputs.sort(key=lambda t: t.__name__)
         self.assert_(outputs == expectedOutputs, '''\
 for input:
 %s

File pyflakes/test/test_imports.py

 
 from sys import version_info
 
+from unittest import skip
 from pyflakes import messages as m
 from pyflakes.test import harness
 
         self.flakes('from moo import fu as FU, bar as FU', m.RedefinedWhileUnused, m.UnusedImport)
 
     def test_usedImport(self):
-        self.flakes('import fu; print fu')
-        self.flakes('from baz import fu; print fu')
+        self.flakes('import fu; min(fu)')
+        self.flakes('from baz import fu; min(fu)')
 
     def test_redefinedWhileUnused(self):
         self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
         import fu
         class bar:
             fu = 1
-        print fu
+        min(fu)
         ''')
 
     def test_usedInFunction(self):
         self.flakes('''
         import fu
         def fun():
-            print fu
+            min(fu)
         ''')
 
     def test_shadowedByParameter(self):
         self.flakes('''
         import fu
         def fun(fu):
-            print fu
+            min(fu)
         ''', m.UnusedImport)
 
         self.flakes('''
         import fu
         def fun(fu):
-            print fu
-        print fu
+            min(fu)
+        min(fu)
         ''')
 
     def test_newAssignment(self):
         self.flakes('import fu; "bar".fu.baz', m.UnusedImport)
 
     def test_usedInSlice(self):
-        self.flakes('import fu; print fu.bar[1:]')
+        self.flakes('import fu; min(fu.bar[1:])')
 
     def test_usedInIfBody(self):
         self.flakes('''
         import fu
-        if True: print fu
+        if True: min(fu)
         ''')
 
     def test_usedInIfConditional(self):
         self.flakes('''
         import fu
         if False: pass
-        else: print fu
+        else: min(fu)
         ''')
 
     def test_usedInCall(self):
         import fu
         def bleh():
             pass
-        print fu
+        min(fu)
         ''')
 
     def test_usedInFor(self):
         self.flakes('''
         import fu
         for bar in range(9):
-            print fu
+            min(fu)
         ''')
 
     def test_usedInForElse(self):
         for bar in range(10):
             pass
         else:
-            print fu
+            min(fu)
         ''')
 
     def test_redefinedByFor(self):
         except: pass
         ''')
 
+    @skip("todo")
     def test_redefinedByExcept(self):
         self.flakes('''
         import fu
         try: pass
-        except Exception, fu: pass
+        except Exception as fu: pass
         ''', m.RedefinedWhileUnused)
 
     def test_usedInRaise(self):
         def f(): global fu
         ''', m.UnusedImport)
 
-    def test_usedInBackquote(self):
-        self.flakes('import fu; `fu`')
-
     def test_usedInExec(self):
-        self.flakes('import fu; exec "print 1" in fu.bar')
+        self.flakes('import fu; exec("min(1)" in fu.bar)')
 
     def test_usedInLambda(self):
         self.flakes('import fu; lambda: fu')
             import fu
             class b:
                 def c(self):
-                    print fu
+                    min(fu)
         ''')
 
     def test_importStar(self):
         self.flakes('import fu; [fu, bar] = fu')
         self.flakes('import fu; fu += fu')
 
+    @skip("todo")
     def test_tryingMultipleImports(self):
         self.flakes('''
         try:
         except ImportError:
             import bar as fu
         ''')
-    test_tryingMultipleImports.todo = ''
 
     def test_nonGlobalDoesNotRedefine(self):
         self.flakes('''
     def test_ignoreNonImportRedefinitions(self):
         self.flakes('a = 1; a = 2')
 
+    @skip("todo")
     def test_importingForImportError(self):
         self.flakes('''
         try:
         except ImportError:
             pass
         ''')
-    test_importingForImportError.todo = ''
 
+    @skip("todo: requires evaluating attribute access")
     def test_importedInClass(self):
         '''Imports in class scope can be used through self'''
         self.flakes('''
             def __init__(self):
                 self.i
         ''')
-    test_importedInClass.todo = 'requires evaluating attribute access'
 
     def test_futureImport(self):
         '''__future__ is special'''

File pyflakes/test/test_other.py

 
 from sys import version_info
 
+from unittest import skip, skipIf
 from pyflakes import messages as m
+from pyflakes.checker import listCompsHaveScope
 from pyflakes.test import harness
 
 
     def test_duplicateArgs(self):
         self.flakes('def fu(bar, bar): pass', m.DuplicateArgument)
 
+    @skip("todo: this requires finding all assignments in the function body first")
     def test_localReferencedBeforeAssignment(self):
         self.flakes('''
         a = 1
             a; a=1
         f()
         ''', m.UndefinedName)
-    test_localReferencedBeforeAssignment.todo = 'this requires finding all assignments in the function body first'
+
+    @skipIf(listCompsHaveScope, "list comprehensions have their own scope in "
+            "this version of python")
+    def test_redefinedInListComp(self):
+        """
+        Test that shadowing a variable in a list comprehension raises
+        a warning.
+        """
+        self.flakes('''
+        a = 1
+        [1 for a, b in [(1, 2)]]
+        ''', m.RedefinedInListComp)
+        self.flakes('''
+        class A:
+            a = 1
+            [1 for a, b in [(1, 2)]]
+        ''', m.RedefinedInListComp)
+        self.flakes('''
+        def f():
+            a = 1
+            [1 for a, b in [(1, 2)]]
+        ''', m.RedefinedInListComp)
+        self.flakes('''
+        [1 for a, b in [(1, 2)]]
+        [1 for a, b in [(1, 2)]]
+        ''')
+        self.flakes('''
+        for a, b in [(1, 2)]:
+            pass
+        [1 for a, b in [(1, 2)]]
+        ''')
+
 
     def test_redefinedFunction(self):
         """

File pyflakes/test/test_script.py

 Tests for L{pyflakes.scripts.pyflakes}.
 """
 
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from io import StringIO
+
+import os
 import sys
-from StringIO import StringIO
-
-from twisted.python.filepath import FilePath
-from twisted.trial.unittest import TestCase
+import tempfile
+from unittest import TestCase
 
 from pyflakes.scripts.pyflakes import checkPath
 
     finally:
         sys.stderr = outer
 
-
+def fileWithContents(contents):
+    _, fname = tempfile.mkstemp()
+    mode = 'wt' if isinstance(contents, str) else 'wb'
+    fd = open(fname, mode)
+    fd.write(contents)
+    fd.close()
+    return fname
 
 class CheckTests(TestCase):
     """
         exception to be raised nor an error indicator to be returned by
         L{check}.
         """
-        fName = self.mktemp()
-        FilePath(fName).setContent("def foo():\n\tpass\n\t")
+        fName = fileWithContents("def foo():\n\tpass\n\t")
         self.assertFalse(checkPath(fName))
 
 
         # Sanity check - SyntaxError.text should be multiple lines, if it
         # isn't, something this test was unprepared for has happened.
         def evaluate(source):
-            exec source
-        exc = self.assertRaises(SyntaxError, evaluate, source)
-        self.assertTrue(exc.text.count('\n') > 1)
+            exec(source)
+        try:
+            evaluate(source)
+        except SyntaxError as e:
+            self.assertTrue(e.text.count('\n') > 1)
+        else:
+            self.fail()
 
-        sourcePath = FilePath(self.mktemp())
-        sourcePath.setContent(source)
+        sourcePath = fileWithContents(source)
         err = StringIO()
-        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        count = withStderrTo(err, lambda: checkPath(sourcePath))
         self.assertEqual(count, 1)
 
         self.assertEqual(
 %s:8: invalid syntax
     '''quux'''
            ^
-""" % (sourcePath.path,))
+""" % (sourcePath,))
 
 
     def test_eofSyntaxError(self):
         syntax error reflects the cause for the syntax error.
         """
         source = "def foo("
-        sourcePath = FilePath(self.mktemp())
-        sourcePath.setContent(source)
+        sourcePath = fileWithContents(source)
         err = StringIO()
-        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        count = withStderrTo(err, lambda: checkPath(sourcePath))
         self.assertEqual(count, 1)
         self.assertEqual(
             err.getvalue(),
 %s:1: unexpected EOF while parsing
 def foo(
          ^
-""" % (sourcePath.path,))
+""" % (sourcePath,))
 
 
     def test_nonDefaultFollowsDefaultSyntaxError(self):
 def foo(bar=baz, bax):
     pass
 """
-        sourcePath = FilePath(self.mktemp())
-        sourcePath.setContent(source)
+        sourcePath = fileWithContents(source)
         err = StringIO()
-        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        count = withStderrTo(err, lambda: checkPath(sourcePath))
         self.assertEqual(count, 1)
         self.assertEqual(
             err.getvalue(),
             """\
 %s:1: non-default argument follows default argument
 def foo(bar=baz, bax):
-""" % (sourcePath.path,))
+""" % (sourcePath,))
 
 
     def test_nonKeywordAfterKeywordSyntaxError(self):
         source = """\
 foo(bar=baz, bax)
 """
-        sourcePath = FilePath(self.mktemp())
-        sourcePath.setContent(source)
+        sourcePath = fileWithContents(source)
         err = StringIO()
-        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        count = withStderrTo(err, lambda: checkPath(sourcePath))
         self.assertEqual(count, 1)
         self.assertEqual(
             err.getvalue(),
             """\
 %s:1: non-keyword arg after keyword arg
 foo(bar=baz, bax)
-""" % (sourcePath.path,))
+""" % (sourcePath,))
 
 
     def test_permissionDenied(self):
         If the a source file is not readable, this is reported on standard
         error.
         """
-        sourcePath = FilePath(self.mktemp())
-        sourcePath.setContent('')
-        sourcePath.chmod(0)
+        sourcePath = fileWithContents('')
+        os.chmod(sourcePath, 0)
         err = StringIO()
-        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        count = withStderrTo(err, lambda: checkPath(sourcePath))
         self.assertEquals(count, 1)
         self.assertEquals(
-            err.getvalue(), "%s: Permission denied\n" % (sourcePath.path,))
+            err.getvalue(), "%s: Permission denied\n" % (sourcePath,))
 
 
     def test_misencodedFile(self):
         If a source file contains bytes which cannot be decoded, this is
         reported on stderr.
         """
-        source = u"""\
+        try:
+            unicode
+        except NameError:
+            source = """\
 # coding: ascii
 x = "\N{SNOWMAN}"
 """.encode('utf-8')
-        sourcePath = FilePath(self.mktemp())
-        sourcePath.setContent(source)
+        else:
+            source = """\
+# coding: ascii
+x = "\xe2\x98\x83"
+"""
+        sourcePath = fileWithContents(source)
         err = StringIO()
-        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        count = withStderrTo(err, lambda: checkPath(sourcePath))
         self.assertEquals(count, 1)
         self.assertEquals(
-            err.getvalue(), "%s: problem decoding source\n" % (sourcePath.path,))
+            err.getvalue(), "%s: problem decoding source\n" % (sourcePath,))

File pyflakes/test/test_undefined_names.py

 
 from _ast import PyCF_ONLY_AST
 
-from twisted.trial.unittest import TestCase
+from unittest import skip, TestCase
 
 from pyflakes import messages as m, checker
 from pyflakes.test import harness
         bar
         ''', m.ImportStarUsed, m.UndefinedName)
 
-    def test_unpackedParameter(self):
-        '''Unpacked function parameters create bindings'''
-        self.flakes('''
-        def a((bar, baz)):
-            bar; baz
-        ''')
-
+    @skip("todo")
     def test_definedByGlobal(self):
         '''"global" can make an otherwise undefined name in another function defined'''
         self.flakes('''
         def a(): global fu; fu = 1
         def b(): fu
         ''')
-    test_definedByGlobal.todo = ''
 
     def test_globalInGlobalScope(self):
         """
         self.flakes('''
         global x
         def foo():
-            print x
+            min(x)
         ''', m.UndefinedName)
 
     def test_del(self):
                 def h(self):
                     a = x
                     x = None
-                    print x, a
-            print x
+                    min(x, a)
+            min(x)
         ''', m.UndefinedLocal)
 
 
         '''star and double-star arg names are defined'''
         self.flakes('''
         def f(a, *b, **c):
-            print a, b, c
+            min(a, b, c)
         ''')
 
     def test_definedInGenExp(self):
         Using the loop variable of a generator expression results in no
         warnings.
         """
-        self.flakes('(a for a in xrange(10) if a)')
+        self.flakes('(a for a in range(10) if a)')
 
 
 
 from distutils.core import setup
 
 setup(
-    name="pyflakes",
+    name="pyflakes3k",
     license="MIT",
     version="0.4.0",
-    description="passive checker of Python programs",
-    author="Phil Frost",
-    maintainer="Moe Aboulkheir",
-    maintainer_email="moe@divmod.com",
-    url="http://www.divmod.org/trac/wiki/DivmodPyflakes",
+    description="passive checker of Python programs (py3k port)",
+    maintainer="Virgil Dupras",
+    maintainer_email="hsoft@hardcoded.net",
+    url="http://bitbucket.org/hsoft/pyflakes3k",
     packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"],
     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.""",
+    long_description=open('README').read(),
     classifiers=[
         "Development Status :: 6 - Mature",
         "Environment :: Console",
         "Intended Audience :: Developers",
         "License :: OSI Approved :: MIT License",
-        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
         "Topic :: Software Development",
         "Topic :: Utilities",
         ])