diff -r 86fab0c74430 -r 1a7d545d3ef2 src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py --- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py Wed Jul 27 15:52:14 2022 +0200 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py Wed Jul 27 18:02:43 2022 +0200 @@ -9,6 +9,7 @@ import ast import copy +import re import sys @@ -505,16 +506,37 @@ ####################################################################### ## Tidy imports ## - ## adapted from: flake8-tidy-imports v4.5.0 + ## adapted from: flake8-tidy-imports v4.8.0 ####################################################################### - # TODO: update to v4.8.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", "") + self.__bannedModules = [] + self.__bannedStructuredPatterns = [] + self.__bannedUnstructuredPatterns = [] + for module in self.__args.get("BannedModules", []): + module = module.strip() + if "*" in module[:-1] or module == "*": + # unstructured + self.__bannedUnstructuredPatterns.append( + self.__compileUnstructuredGlob(module) + ) + elif module.endswith(".*"): + # structured + self.__bannedStructuredPatterns.append(module) + # Also check for exact matches without the wildcard + # e.g. "foo.*" matches "foo" + prefix = module[:-2] + if prefix not in self.__bannedModules: + self.__bannedModules.append(prefix) + else: + self.__bannedModules.append(module) + + # Sort the structured patterns so we match the specifc ones first. + self.__bannedStructuredPatterns.sort(key=lambda x: len(x[0]), reverse=True) ruleMethods = [] if not self.__ignoreCode("I901"): @@ -530,6 +552,26 @@ for method in ruleMethods: method(node) + def __compileUnstructuredGlob(self, module): + """ + Private method to convert a pattern to a regex such that ".*" matches zero or + more modules. + + @param module module pattern to be converted + @type str + @return compiled regex + @rtype re.regex object + """ + parts = module.split(".") + transformedParts = [ + "(\\..*)?" if p == "*" else "\\." + re.escape(p) for p in parts + ] + if parts[0] == "*": + transformedParts[0] = ".*" + else: + transformedParts[0] = re.escape(parts[0]) + return re.compile("".join(transformedParts) + "\\Z") + def __checkUnnecessaryAlias(self, node): """ Private method to check unnecessary import aliases. @@ -560,6 +602,34 @@ self.__error(node.lineno - 1, node.col_offset, "I901", rewritten) + def __isModuleBanned(self, moduleName): + """ + Private method to check, if the given module name banned. + + @param moduleName module name to be checked + @type str + @return flag indicating a banned module + @rtype bool + """ + if moduleName in self.__bannedModules: + return True + + # Check unustructed wildcards + if any( + bannedPattern.match(moduleName) + for bannedPattern in self.__bannedUnstructuredPatterns + ): + return True + + # Check structured wildcards + if any( + moduleName.startswith(bannedPrefix[:-1]) + for bannedPrefix in self.__bannedStructuredPatterns + ): + return True + + return False + def __checkBannedImport(self, node): """ Private method to check import of banned modules. @@ -586,7 +656,7 @@ warned = set() for moduleName in moduleNames: - if moduleName in self.__bannedModules: + if self.__isModuleBanned(moduleName): 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.