eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py

branch
eric7
changeset 8802
129a973fc33e
parent 8801
8fbb21be8579
child 8808
033fa34447d0
diff -r 8fbb21be8579 -r 129a973fc33e eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py
--- a/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py	Wed Dec 01 20:09:57 2021 +0100
+++ b/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py	Thu Dec 02 18:53:26 2021 +0100
@@ -7,6 +7,7 @@
 Module implementing a checker for import statements.
 """
 
+import ast
 import copy
 import sys
 
@@ -18,6 +19,9 @@
     Codes = [
         ## Local imports
         "I101", "I102", "I103",
+        
+        ## Imports order
+        "I201", "I202", "I203", "I204",
     ]
 
     def __init__(self, source, filename, tree, select, ignore, expected,
@@ -59,6 +63,7 @@
         
         checkersWithCodes = [
             (self.__checkLocalImports, ("I101", "I102", "I103")),
+            (self.__checkImportOrder, ("I201", "I202", "I203", "I204"))
         ]
         
         self.__checkers = []
@@ -198,6 +203,121 @@
         visitor = LocalImportVisitor(self.__args, self)
         visitor.visit(copy.deepcopy(self.__tree))
         for violation in visitor.violations:
-            node = violation[0]
-            reason = violation[1]
-            self.__error(node.lineno - 1, node.col_offset, reason)
+            if not self.__ignoreCode(violation[1]):
+                node = violation[0]
+                reason = violation[1]
+                self.__error(node.lineno - 1, node.col_offset, reason)
+    
+    #######################################################################
+    ## Import order
+    ##
+    ## adapted from: flake8-alphabetize v0.0.17
+    #######################################################################
+    
+    def __checkImportOrder(self):
+        """
+        Private method to check the order of import statements.
+        """
+        from .ImportNode import ImportNode
+        
+        errors = []
+        imports = []
+        importNodes, listNode = self.__findNodes(self.__tree)
+        
+        # check for an error in '__all__'
+        allError = self.__findErrorInAll(listNode)
+        if allError is not None:
+            errors.append(allError)
+        
+        for importNode in importNodes:
+            if (
+                isinstance(importNode, ast.Import) and
+                len(importNode.names) > 1
+            ):
+                # skip suck imports because its already handled by pycodestyle
+                continue
+            
+            imports.append(ImportNode(
+                self.__args.get("ApplicationPackageNames", []),
+                importNode, self))
+        
+        lenImports = len(imports)
+        if lenImports > 0:
+            p = imports[0]
+            if p.error is not None:
+                errors.append(p.error)
+            
+            if lenImports > 1:
+                for n in imports[1:]:
+                    if n.error is not None:
+                        errors.append(n.error)
+                    
+                    if n == p:
+                        errors.append((n.node, "I203", str(p), str(n)))
+                    elif n < p:
+                        errors.append((n.node, "I201", str(n), str(p)))
+                    
+                    p = n
+        
+        for error in errors:
+            if not self.__ignoreCode(error[1]):
+                node = error[0]
+                reason = error[1]
+                args = error[2:]
+                self.__error(node.lineno - 1, node.col_offset, reason, *args)
+    
+    def __findNodes(self, tree):
+        """
+        Private method to find all import and import from nodes of the given
+        tree.
+        
+        @param tree reference to the ast node tree to be parsed
+        @type ast.AST
+        @return tuple containing a list of import nodes and the '__all__' node
+        @rtype tuple of (ast.Import | ast.ImportFrom, ast.List | ast.Tuple)
+        """
+        importNodes = []
+        listNode = None
+        
+        if isinstance(tree, ast.Module):
+            body = tree.body
+            
+            for n in body:
+                if isinstance(n, (ast.Import, ast.ImportFrom)):
+                    importNodes.append(n)
+                
+                elif isinstance(n, ast.Assign):
+                    for t in n.targets:
+                        if isinstance(t, ast.Name) and t.id == "__all__":
+                            value = n.value
+
+                            if isinstance(value, (ast.List, ast.Tuple)):
+                                listNode = value
+        
+        return importNodes, listNode
+    
+    def __findErrorInAll(self, node):
+        """
+        Private method to check the '__all__' node for errors.
+        
+        @param node reference to the '__all__' node
+        @type ast.List or ast.Tuple
+        @return tuple containing a reference to the node and an error code
+        @rtype rtype tuple of (ast.List | ast.Tuple, str)
+        """
+        if node is not None:
+            actualList = []
+            for el in node.elts:
+                if isinstance(el, ast.Constant):
+                    actualList.append(el.value)
+                elif isinstance(el, ast.Str):
+                    actualList.append(el.s)
+                else:
+                    # Can't handle anything that isn't a string literal
+                    return None
+
+            expectedList = sorted(actualList)
+            if expectedList != actualList:
+                return (node, "I204", ", ".join(expectedList))
+        
+        return None

eric ide

mercurial