upgraded pyflakes to version 2.0.0

Sun, 17 Jun 2018 18:22:57 +0200

author
T.Rzepka <Tobias.Rzepka@gmail.com>
date
Sun, 17 Jun 2018 18:22:57 +0200
changeset 6353
6a0f3abd6878
parent 6352
4bdc6503df81
child 6354
9ec941fc1a91

upgraded pyflakes to version 2.0.0

Plugins/CheckerPlugins/SyntaxChecker/pyflakes/__init__.py file | annotate | diff | comparison | revisions
Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py file | annotate | diff | comparison | revisions
Plugins/CheckerPlugins/SyntaxChecker/pyflakes/messages.py file | annotate | diff | comparison | revisions
Plugins/CheckerPlugins/SyntaxChecker/pyflakes/translations.py file | annotate | diff | comparison | revisions
changelog file | annotate | diff | comparison | revisions
--- a/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/__init__.py	Sun Jun 17 16:56:10 2018 +0200
+++ b/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/__init__.py	Sun Jun 17 18:22:57 2018 +0200
@@ -31,7 +31,21 @@
 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 """
 
+__version__ = '2.0.0+'
+
 """ Changes
+2.0.0 (2018-05-20)
+  - Drop support for EOL Python <2.7 and 3.2-3.3
+  - Check for unused exception binding in `except:` block
+  - Handle string literal type annotations
+  - Ignore redefinitions of `_`, unless originally defined by import
+  - Support `__class__` without `self` in Python 3
+  - Issue an error for `raise NotImplemented(...)`
+
+1.6.0 (2017-08-03)
+  - Process function scope variable annotations for used names
+  - Find Python files without extensions by their shebang
+
 1.5.0 (2017-01-09)
   - Enable support for PEP 526 annotated assignments
 
@@ -209,7 +223,5 @@
   - Improve reporting of unbound locals
 """
 
-__version__ = '1.2.3+'
-
 #
 # eflag: noqa = M702
--- a/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py	Sun Jun 17 16:56:10 2018 +0200
+++ b/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py	Sun Jun 17 18:22:57 2018 +0200
@@ -12,14 +12,13 @@
 Also, it models the Bindings and Scopes.
 """
 import __future__
+import ast
 import doctest
 import os
 import sys
 
 PY2 = sys.version_info < (3, 0)
-PY32 = sys.version_info < (3, 3)    # Python 2.5 to 3.2
-PY33 = sys.version_info < (3, 4)    # Python 2.5 to 3.3
-PY34 = sys.version_info < (3, 5)    # Python 2.5 to 3.4
+PY34 = sys.version_info < (3, 5)    # Python 2.7 to 3.4
 try:
     sys.pypy_version_info
     PYPY = True
@@ -28,16 +27,6 @@
 
 builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins'))
 
-try:
-    import ast
-except ImportError:     # Python 2.5
-    import _ast as ast
-
-    if 'decorator_list' not in ast.ClassDef._fields:
-        # Patch the missing attribute 'decorator_list'
-        ast.ClassDef.decorator_list = ()
-        ast.FunctionDef.decorator_list = property(lambda s: s.decorators)
-
 from . import messages
 
 
@@ -45,12 +34,22 @@
     def getNodeType(node_class):
         # workaround str.upper() which is locale-dependent
         return str(unicode(node_class.__name__).upper())  # __IGNORE_WARNING__
+
+    def get_raise_argument(node):
+        return node.type
+
 else:
     def getNodeType(node_class):
         return node_class.__name__.upper()
 
+    def get_raise_argument(node):
+        return node.exc
+
+    # Silence `pyflakes` from reporting `undefined name 'unicode'` in Python 3.
+    unicode = str
+
 # Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally)
-if PY32:
+if PY2:
     def getAlternatives(n):
         if isinstance(n, (ast.If, ast.TryFinally)):
             return [n.body]
@@ -135,13 +134,17 @@
             result.name,
             result,
         )
-    elif (not PY33) and isinstance(item, ast.NameConstant):
+    elif (not PY2) and isinstance(item, ast.NameConstant):
         # None, True, False are nameconstants in python3, but names in 2
         return item.value
     else:
         return UnhandledKeyType()
 
 
+def is_notimplemented_name_node(node):
+    return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented'
+
+
 class Binding(object):
     """
     Represents the binding of a value to a name.
@@ -414,8 +417,8 @@
     @ivar globals: Names declared 'global' in this function.
     """
     usesLocals = False
-    alwaysUsed = set(['__tracebackhide__',
-                      '__traceback_info__', '__traceback_supplement__'])
+    alwaysUsed = {'__tracebackhide__', '__traceback_info__',
+                  '__traceback_supplement__'}
 
     def __init__(self):
         super(FunctionScope, self).__init__()
@@ -683,8 +686,9 @@
                     self.report(messages.RedefinedInListComp,
                                 node, value.name, existing.source)
                 elif not existing.used and value.redefines(existing):
-                    self.report(messages.RedefinedWhileUnused,
-                                node, value.name, existing.source)
+                    if value.name != '_' or isinstance(existing, Importation):
+                        self.report(messages.RedefinedWhileUnused,
+                                    node, value.name, existing.source)
 
             elif isinstance(existing, Importation) and value.redefines(existing):
                 existing.redefined.append(node)
@@ -713,10 +717,14 @@
 
         # try enclosing function scopes and global scope
         for scope in self.scopeStack[-1::-1]:
-            # only generators used in a class scope can access the names
-            # of the class. this is skipped during the first iteration
-            if in_generators is False and isinstance(scope, ClassScope):
-                continue
+            if isinstance(scope, ClassScope):
+                if not PY2 and name == '__class__':
+                    return
+                elif in_generators is False:
+                    # only generators used in a class scope can access the
+                    # names of the class. this is skipped during the first
+                    # iteration
+                    continue
 
             try:
                 scope[name].used = (self.scope, node)
@@ -877,7 +885,19 @@
 
     def handleDoctests(self, node):
         try:
-            (docstring, node_lineno) = self.getDocstring(node.body[0])
+            if hasattr(node, 'docstring'):
+                docstring = node.docstring
+
+                # This is just a reasonable guess. In Python 3.7, docstrings no
+                # longer have line numbers associated with them. This will be
+                # incorrect if there are empty lines between the beginning
+                # of the function and the docstring.
+                node_lineno = node.lineno
+                if hasattr(node, 'args'):
+                    node_lineno = max([node_lineno] +
+                                      [arg.lineno for arg in node.args.args])
+            else:
+                (docstring, node_lineno) = self.getDocstring(node.body[0])
             examples = docstring and self._getDoctestExamples(docstring)
         except (ValueError, IndexError):
             # e.g. line 6 of the docstring for <string> has inconsistent
@@ -914,12 +934,45 @@
         self.popScope()
         self.scopeStack = saved_stack
 
+    def handleAnnotation(self, annotation, node):
+        if isinstance(annotation, ast.Str):
+            # Defer handling forward annotation.
+            def handleForwardAnnotation():
+                try:
+                    tree = ast.parse(annotation.s)
+                except SyntaxError:
+                    self.report(
+                        messages.ForwardAnnotationSyntaxError,
+                        node,
+                        annotation.s,
+                    )
+                    return
+
+                body = tree.body
+                if len(body) != 1 or not isinstance(body[0], ast.Expr):
+                    self.report(
+                        messages.ForwardAnnotationSyntaxError,
+                        node,
+                        annotation.s,
+                    )
+                    return
+
+                parsed_annotation = tree.body[0].value
+                for descendant in ast.walk(parsed_annotation):
+                    ast.copy_location(descendant, annotation)
+
+                self.handleNode(parsed_annotation, node)
+
+            self.deferFunction(handleForwardAnnotation)
+        else:
+            self.handleNode(annotation, node)
+
     def ignore(self, node):
         pass
 
     # "stmt" type nodes
     DELETE = PRINT = FOR = ASYNCFOR = WHILE = IF = WITH = WITHITEM = \
-        ASYNCWITH = ASYNCWITHITEM = RAISE = TRYFINALLY = EXEC = \
+        ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \
         EXPR = ASSIGN = handleChildren
 
     PASS = ignore
@@ -943,6 +996,19 @@
         EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = \
         MATMULT = ignore
 
+    def RAISE(self, node):
+        self.handleChildren(node)
+
+        arg = get_raise_argument(node)
+
+        if isinstance(arg, ast.Call):
+            if is_notimplemented_name_node(arg.func):
+                # Handle "raise NotImplemented(...)"
+                self.report(messages.RaiseNotImplemented, node)
+        elif is_notimplemented_name_node(arg):
+            # Handle "raise NotImplemented"
+            self.report(messages.RaiseNotImplemented, node)
+
     # additional node types
     COMPREHENSION = KEYWORD = FORMATTEDVALUE = JOINEDSTR = handleChildren
 
@@ -1136,9 +1202,9 @@
             wildcard = getattr(node.args, arg_name)
             if not wildcard:
                 continue
-            args.append(wildcard if PY33 else wildcard.arg)
+            args.append(wildcard if PY2 else wildcard.arg)
             if is_py3_func:
-                if PY33:  # Python 2.5 to 3.3
+                if PY2:  # Python 2.7
                     argannotation = arg_name + 'annotation'
                     annotations.append(getattr(node.args, argannotation))
                 else:     # Python >= 3.4
@@ -1152,9 +1218,11 @@
                 if arg in args[:idx]:
                     self.report(messages.DuplicateArgument, node, arg)
 
-        for child in annotations + defaults:
-            if child:
-                self.handleNode(child, node)
+        for annotation in annotations:
+            self.handleAnnotation(annotation, node)
+
+        for default in defaults:
+            self.handleNode(default, node)
 
         def runFunction():
 
@@ -1177,7 +1245,7 @@
                     self.report(messages.UnusedVariable, binding.source, name)
             self.deferAssignment(checkUnusedAssignments)
 
-            if PY32:
+            if PY2:
                 def checkReturnWithArgumentInsideGenerator():
                     """
                     Check to see if there is any return statement with
@@ -1314,49 +1382,60 @@
             self.handleChildren(node)
             return
 
-        # 3.x: the name of the exception, which is not a Name node, but
-        # a simple string, creates a local that is only bound within the scope
-        # of the except: block.
+        # If the name already exists in the scope, modify state of existing
+        # binding.
+        if node.name in self.scope:
+            self.handleNodeStore(node)
+
+        # 3.x: the name of the exception, which is not a Name node, but a
+        # simple string, creates a local that is only bound within the scope of
+        # the except: block. As such, temporarily remove the existing binding
+        # to more accurately determine if the name is used in the except:
+        # block.
 
         for scope in self.scopeStack[::-1]:
-            if node.name in scope:
-                is_name_previously_defined = True
+            try:
+                binding = scope.pop(node.name)
+            except KeyError:
+                pass
+            else:
+                prev_definition = scope, binding
                 break
         else:
-            is_name_previously_defined = False
+            prev_definition = None
 
         self.handleNodeStore(node)
         self.handleChildren(node)
-        if not is_name_previously_defined:
-            # See discussion on https://github.com/PyCQA/pyflakes/pull/59
+
+        # See discussion on https://github.com/PyCQA/pyflakes/pull/59
+
+        # We're removing the local name since it's being unbound after leaving
+        # the except: block and it's always unbound if the except: block is
+        # never entered. This will cause an "undefined name" error raised if
+        # the checked code tries to use the name afterwards.
+        #
+        # Unless it's been removed already. Then do nothing.
 
-            # We're removing the local name since it's being unbound
-            # after leaving the except: block and it's always unbound
-            # if the except: block is never entered. This will cause an
-            # "undefined name" error raised if the checked code tries to
-            # use the name afterwards.
-            #
-            # Unless it's been removed already. Then do nothing.
+        try:
+            binding = self.scope.pop(node.name)
+        except KeyError:
+            pass
+        else:
+            if not binding.used:
+                self.report(messages.UnusedVariable, node, node.name)
 
-            try:
-                del self.scope[node.name]
-            except KeyError:
-                pass
+        # Restore.
+        if prev_definition:
+            scope, binding = prev_definition
+            scope[node.name] = binding
 
     def ANNASSIGN(self, node):
-        """
-        Annotated assignments don't have annotations evaluated on function
-        scope, hence the custom implementation.
-
-        See: PEP 526.
-        """
         if node.value:
             # Only bind the *targets* if the assignment has a value.
             # Otherwise it's not really ast.Store and shouldn't silence
             # UndefinedLocal warnings.
             self.handleNode(node.target, node)
-        if not isinstance(self.scope, FunctionScope):
-            self.handleNode(node.annotation, node)
+        self.handleAnnotation(node.annotation, node)
         if node.value:
             # If the assignment has value, handle the *value* now.
             self.handleNode(node.value, node)
--- a/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/messages.py	Sun Jun 17 16:56:10 2018 +0200
+++ b/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/messages.py	Sun Jun 17 18:22:57 2018 +0200
@@ -486,5 +486,36 @@
     message_id = 'F28'
     message = 'assertion is always true, perhaps remove parentheses?'
 
+
+class ForwardAnnotationSyntaxError(Message):
+    """
+    Class defining the "forward annotation syntax error" message.
+    
+    Found a syntax error in forward annotation.
+    """
+    message_id = 'F29'
+    message = 'syntax error in forward annotation %r'
+
+    def __init__(self, filename, loc, annotation):
+        """
+        Constructor
+        
+        @param filename name of the file (string)
+        @param loc location of the issue
+        @param annotation erroneous forward annotation (string)
+        """
+        Message.__init__(self, filename, loc)
+        self.message_args = (annotation,)
+
+
+class RaiseNotImplemented(Message):
+    """
+    Class defining the "raise not implemented" message.
+    
+    Use NotImplementedError instead of NotImplemented.
+    """
+    message_id = 'F30'
+    message = "'raise NotImplemented' should be 'raise NotImplementedError'"
+
 #
 # eflag: noqa = M702
--- a/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/translations.py	Sun Jun 17 16:56:10 2018 +0200
+++ b/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/translations.py	Sun Jun 17 18:22:57 2018 +0200
@@ -99,6 +99,12 @@
     'F28': QCoreApplication.translate(
         'pyFlakes',
         "Assertion is always true, perhaps remove parentheses?"),
+    'F29': QCoreApplication.translate(
+        'pyFlakes',
+        "syntax error in forward annotation {0!r}"),
+    'F30': QCoreApplication.translate(
+        'pyFlakes',
+        "'raise NotImplemented' should be 'raise NotImplementedError'"),
 }
 
 
--- a/changelog	Sun Jun 17 16:56:10 2018 +0200
+++ b/changelog	Sun Jun 17 18:22:57 2018 +0200
@@ -2,6 +2,8 @@
 ----------
 Version 18.07:
 - bug fixes
+- Checkers
+  -- upgraded pyflakes to version 2.0.0
 - Git Interface
   -- added capability to change the URL and/or user credentials for a
      remote repository

eric ide

mercurial