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