Brodie Rao avatar Brodie Rao committed 62515f1 Merge

Merged experimental into default

Comments (0)

Files changed (3)

pyflakes/checker.py

 
 import _ast
 import os
+import re
 import sys
 
 from pyflakes import messages
 
+interpol = re.compile(r'%(\([a-zA-Z0-9_]+\))?[-#0 +]*([0-9]+|[*])?'
+                      r'(\.([0-9]+|[*]))?[hlL]?[diouxXeEfFgGcrs%]')
 
 # utility function to iterate over an AST node's children, adapted
 # from Python 2.6's standard ast module
         Return a list of the names referenced by this binding.
         """
         names = []
-        if isinstance(self.source, _ast.List):
+        if isinstance(self.source, (_ast.Tuple, _ast.List)):
             for node in self.source.elts:
                 if isinstance(node, _ast.Str):
                     names.append(node.s)
         self.filename = filename
         self.scopeStack = [ModuleScope()]
         self.futuresAllowed = True
+        self.noRedef = 0
         self.handleChildren(tree)
         self._runDeferred(self._deferredFunctions)
         # Set _deferredFunctions to None so that deferFunction will fail
         pass
 
     # "stmt" type nodes
-    RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \
+    DELETE = PRINT = WHILE = IF = WITH = RAISE = \
         TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren
 
     CONTINUE = BREAK = PASS = ignore
 
     # "expr" type nodes
-    BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \
-    CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren
+    BOOLOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \
+        REPR = SUBSCRIPT = LIST = TUPLE = handleChildren
 
     NUM = STR = BYTES = ELLIPSIS = ignore
 
                 if (isinstance(existing, Importation)
                         and not existing.used
                         and (not isinstance(value, Importation) or value.fullName == existing.fullName)
-                        and reportRedef):
+                        and reportRedef
+                        and self.noRedef == 0):
                     redefinedWhileUnused = True
                     self.report(messages.RedefinedWhileUnused,
                                 lineno, value.name, scope[value.name].source.lineno)
 
         self.handleChildren(node)
 
+    def BINOP(self, node):
+        if isinstance(node.op, _ast.Mod) and isinstance(node.left, _ast.Str):
+            dictfmt = '%(' in node.left.s
+            nplaces = 0
+            for m in interpol.finditer(node.left.s):
+                if m.group()[-1] != '%':
+                    nplaces += 1 + m.group().count('*')
+            if isinstance(node.right, _ast.Dict):
+                if not dictfmt:
+                    self.report(messages.StringFormattingProblem,
+                                node.lineno, 'tuple', 'dict')
+            else:
+                if isinstance(node.right, _ast.Tuple):
+                    if dictfmt:
+                        self.report(messages.StringFormattingProblem,
+                                    node.lineno, 'dict', 'tuple')
+                    else:
+                        nobjects = len(node.right.elts)
+                        if nobjects != nplaces:
+                            self.report(messages.StringFormattingProblem,
+                                        node.lineno, nplaces, nobjects)
+            self.handleNode(node.right, node)
+        else:
+            self.handleNode(node.left, node)
+            self.handleNode(node.right, node)
+
+    def CALL(self, node):
+        if isinstance(node.func, _ast.Tuple):
+            self.report(messages.TupleCall, node.lineno)
+        self.handleChildren(node)
+
+    def ATTRIBUTE(self, node):
+        if isinstance(node.value, _ast.Str) and node.attr == 'format' and \
+           isinstance(node.parent, _ast.Call) and node is node.parent.func:
+            try:
+                num = 0
+                maxnum = -1
+                kwds = set()
+                for lit, fn, fs, conv in node.value.s._formatter_parser():
+                    if lit:
+                        continue
+                    fn = fn.partition('.')[0].partition('[')[0]
+                    if not fn:
+                        num += 1
+                    elif fn.isdigit():
+                        maxnum = max(maxnum, int(fn))
+                    else:
+                        kwds.add(fn)
+            except ValueError:
+                err = sys.exc_info()[1]
+                self.report(messages.StringFormatProblem,
+                            node.lineno, str(err))
+            else:
+                callnode = node.parent
+                # can only really check if no *args or **kwds are used
+                if not (callnode.starargs or callnode.kwargs):
+                    nargs = len(node.parent.args)
+                    kwdset = set(kwd.arg for kwd in node.parent.keywords)
+                    if nargs < num:
+                        self.report(messages.StringFormatProblem, node.lineno,
+                                    'not enough positional args (need %s)' % num)
+                    elif nargs < maxnum+1:
+                        self.report(messages.StringFormatProblem, node.lineno,
+                                    'not enough positional args (need %s)' %
+                                    (maxnum+1))
+                    missing = kwds - kwdset
+                    if missing:
+                        self.report(messages.StringFormatProblem, node.lineno,
+                                    'keyword args missing: %s' % ', '.join(missing))
+        else:
+            self.handleNode(node.value, node)
+
     def NAME(self, node):
         """
         Handle occurrence of Name (which can be a load/store/delete access.)
             if node.module == '__future__':
                 importation.used = (self.scope, node.lineno)
             self.addBinding(node.lineno, importation)
+
+    def RETURN(self, node):
+        if not node.value:
+            return
+        self.handleNode(node.value, node)
+        if isinstance(node.value, _ast.Name):
+            name = node.value.id
+        elif isinstance(node.value, _ast.Call) and \
+           isinstance(node.value.func, _ast.Name):
+            name = node.value.func.id
+        else:
+            return
+        if name.endswith('Error') or name.endswith('Exception'):
+            self.report(messages.ExceptionReturn, node.lineno, name)
+
+    def TRYEXCEPT(self, node):
+        """
+        Handle C{try}-C{except}.  In particular, do not report redefinitions
+        when occurring in an "except ImportError" block.
+        """
+        for stmt in node.body:
+            self.handleNode(stmt, node)
+
+        for handler in node.handlers:
+            isImport = (handler.type and isinstance(handler.type, _ast.Name) and
+                        handler.type.id == 'ImportError')
+            if isImport:
+                self.noRedef += 1
+            if handler.type:
+                self.handleNode(handler.type, node)
+                if handler.name:
+                    self.handleNode(handler.name, node)
+            for stmt in handler.body:
+                self.handleNode(stmt, node)
+            if isImport:
+                self.noRedef -= 1
+
+        for stmt in node.orelse:
+            self.handleNode(stmt, node)

pyflakes/messages.py

     def __init__(self, filename, lineno, names):
         Message.__init__(self, filename, lineno)
         self.message_args = (names,)
+
+
+class StringFormattingProblem(Message):
+    message = 'string formatting arguments: should have %s, has %s'
+    def __init__(self, filename, lineno, nshould, nhave):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (nshould, nhave)
+
+
+class StringFormatProblem(Message):
+    message = 'string.format(): %s'
+    def __init__(self, filename, lineno, msg):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (msg,)
+
+
+class ExceptionReturn(Message):
+    """
+    Indicates that an Error or Exception is returned instead of raised.
+    """
+
+    message = 'exception %r is returned'
+    def __init__(self, filename, lineno, name):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name,)
+
+
+class TupleCall(Message):
+    """
+    Indicates that an Error or Exception is returned instead of raised.
+    """
+
+    message = 'calling tuple literal, forgot a comma?'
+    def __init__(self, filename, lineno):
+        Message.__init__(self, filename, lineno)

pyflakes/scripts/pyflakes.py

         for arg in args:
             if os.path.isdir(arg):
                 for dirpath, dirnames, filenames in os.walk(arg):
-                    for filename in filenames:
+                    for filename in sorted(filenames):
                         if filename.endswith('.py'):
                             warnings += checkPath(os.path.join(dirpath, filename))
             else:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.