diff -r f4486646a233 -r 033fa34447d0 eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py --- a/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py Sat Dec 04 17:41:18 2021 +0100 +++ b/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py Sat Dec 04 18:06:17 2021 +0100 @@ -22,6 +22,9 @@ ## Imports order "I201", "I202", "I203", "I204", + + ## Various other import related + "I901", "I902", "I903", "I904", ] def __init__(self, source, filename, tree, select, ignore, expected, @@ -63,7 +66,8 @@ checkersWithCodes = [ (self.__checkLocalImports, ("I101", "I102", "I103")), - (self.__checkImportOrder, ("I201", "I202", "I203", "I204")) + (self.__checkImportOrder, ("I201", "I202", "I203", "I204")), + (self.__tidyImports, ("I901", "I902", "I903", "I904")), ] self.__checkers = [] @@ -321,3 +325,130 @@ return (node, "I204", ", ".join(expectedList)) return None + + ####################################################################### + ## Tidy imports + ## + ## adapted from: flake8-tidy-imports v4.5.0 + ####################################################################### + + def __tidyImports(self): + """ + Private method to check various other import related topics. + """ + self.__bannedModules = self.__args.get("BannedModules", []) + self.__banRelativeImports = self.__args.get("BanRelativeImports", "") + + ruleMethods = [] + if not self.__ignoreCode("I901"): + ruleMethods.append(self.__checkUnnecessaryAlias) + if ( + not self.__ignoreCode("I902") + and bool(self.__bannedModules) + ): + ruleMethods.append(self.__checkBannedImport) + if ( + (not self.__ignoreCode("I903") and + self.__banRelativeImports == "parents") or + (not self.__ignoreCode("I904") and + self.__banRelativeImports == "true") + ): + ruleMethods.append(self.__checkBannedRelativeImports) + + for node in ast.walk(self.__tree): + for method in ruleMethods: + method(node) + + def __checkUnnecessaryAlias(self, node): + """ + Private method to check unnecessary import aliases. + + @param node reference to the node to be checked + @type ast.AST + """ + if isinstance(node, ast.Import): + for alias in node.names: + if "." not in alias.name: + fromName = None + importedName = alias.name + else: + fromName, importedName = alias.name.rsplit(".", 1) + + if importedName == alias.asname: + if fromName: + rewritten = "from {0} import {1}".format( + fromName, importedName) + else: + rewritten = "import {0}".format(importedName) + + self.__error(node.lineno - 1, node.col_offset, "I901", + rewritten) + + elif isinstance(node, ast.ImportFrom): + for alias in node.names: + if alias.name == alias.asname: + rewritten = "from {0} import {1}".format( + node.module, alias.name) + + self.__error(node.lineno - 1, node.col_offset, "I901", + rewritten) + + def __checkBannedImport(self, node): + """ + Private method to check import of banned modules. + + @param node reference to the node to be checked + @type ast.AST + """ + if not bool(self.__bannedModules): + return + + if isinstance(node, ast.Import): + moduleNames = [alias.name for alias in node.names] + elif isinstance(node, ast.ImportFrom): + nodeModule = node.module or "" + moduleNames = [nodeModule] + for alias in node.names: + moduleNames.append("{0}.{1}".format(nodeModule, alias.name)) + else: + return + + # Sort from most to least specific paths. + moduleNames.sort(key=len, reverse=True) + + warned = set() + + for moduleName in moduleNames: + if moduleName in self.__bannedModules: + if any(mod.startswith(moduleName) for mod in warned): + # Do not show an error for this line if we already showed + # a more specific error. + continue + else: + warned.add(moduleName) + self.__error(node.lineno - 1, node.col_offset, "I902", + moduleName) + + def __checkBannedRelativeImports(self, node): + """ + Private method to check if relative imports are banned. + + @param node reference to the node to be checked + @type ast.AST + """ + if not self.__banRelativeImports: + return + + elif self.__banRelativeImports == "parents": + minNodeLevel = 1 + msgCode = "I903" + else: + minNodeLevel = 0 + msgCode = "I904" + + if ( + self.__banRelativeImports and + isinstance(node, ast.ImportFrom) and + node.level > minNodeLevel + ): + self.__error(node.lineno - 1, node.col_offset, msgCode)