503 return None |
504 return None |
504 |
505 |
505 ####################################################################### |
506 ####################################################################### |
506 ## Tidy imports |
507 ## Tidy imports |
507 ## |
508 ## |
508 ## adapted from: flake8-tidy-imports v4.5.0 |
509 ## adapted from: flake8-tidy-imports v4.8.0 |
509 ####################################################################### |
510 ####################################################################### |
510 # TODO: update to v4.8.0 |
|
511 |
511 |
512 def __tidyImports(self): |
512 def __tidyImports(self): |
513 """ |
513 """ |
514 Private method to check various other import related topics. |
514 Private method to check various other import related topics. |
515 """ |
515 """ |
516 self.__bannedModules = self.__args.get("BannedModules", []) |
|
517 self.__banRelativeImports = self.__args.get("BanRelativeImports", "") |
516 self.__banRelativeImports = self.__args.get("BanRelativeImports", "") |
|
517 self.__bannedModules = [] |
|
518 self.__bannedStructuredPatterns = [] |
|
519 self.__bannedUnstructuredPatterns = [] |
|
520 for module in self.__args.get("BannedModules", []): |
|
521 module = module.strip() |
|
522 if "*" in module[:-1] or module == "*": |
|
523 # unstructured |
|
524 self.__bannedUnstructuredPatterns.append( |
|
525 self.__compileUnstructuredGlob(module) |
|
526 ) |
|
527 elif module.endswith(".*"): |
|
528 # structured |
|
529 self.__bannedStructuredPatterns.append(module) |
|
530 # Also check for exact matches without the wildcard |
|
531 # e.g. "foo.*" matches "foo" |
|
532 prefix = module[:-2] |
|
533 if prefix not in self.__bannedModules: |
|
534 self.__bannedModules.append(prefix) |
|
535 else: |
|
536 self.__bannedModules.append(module) |
|
537 |
|
538 # Sort the structured patterns so we match the specifc ones first. |
|
539 self.__bannedStructuredPatterns.sort(key=lambda x: len(x[0]), reverse=True) |
518 |
540 |
519 ruleMethods = [] |
541 ruleMethods = [] |
520 if not self.__ignoreCode("I901"): |
542 if not self.__ignoreCode("I901"): |
521 ruleMethods.append(self.__checkUnnecessaryAlias) |
543 ruleMethods.append(self.__checkUnnecessaryAlias) |
522 if not self.__ignoreCode("I902") and bool(self.__bannedModules): |
544 if not self.__ignoreCode("I902") and bool(self.__bannedModules): |
528 |
550 |
529 for node in ast.walk(self.__tree): |
551 for node in ast.walk(self.__tree): |
530 for method in ruleMethods: |
552 for method in ruleMethods: |
531 method(node) |
553 method(node) |
532 |
554 |
|
555 def __compileUnstructuredGlob(self, module): |
|
556 """ |
|
557 Private method to convert a pattern to a regex such that ".*" matches zero or |
|
558 more modules. |
|
559 |
|
560 @param module module pattern to be converted |
|
561 @type str |
|
562 @return compiled regex |
|
563 @rtype re.regex object |
|
564 """ |
|
565 parts = module.split(".") |
|
566 transformedParts = [ |
|
567 "(\\..*)?" if p == "*" else "\\." + re.escape(p) for p in parts |
|
568 ] |
|
569 if parts[0] == "*": |
|
570 transformedParts[0] = ".*" |
|
571 else: |
|
572 transformedParts[0] = re.escape(parts[0]) |
|
573 return re.compile("".join(transformedParts) + "\\Z") |
|
574 |
533 def __checkUnnecessaryAlias(self, node): |
575 def __checkUnnecessaryAlias(self, node): |
534 """ |
576 """ |
535 Private method to check unnecessary import aliases. |
577 Private method to check unnecessary import aliases. |
536 |
578 |
537 @param node reference to the node to be checked |
579 @param node reference to the node to be checked |
557 for alias in node.names: |
599 for alias in node.names: |
558 if alias.name == alias.asname: |
600 if alias.name == alias.asname: |
559 rewritten = "from {0} import {1}".format(node.module, alias.name) |
601 rewritten = "from {0} import {1}".format(node.module, alias.name) |
560 |
602 |
561 self.__error(node.lineno - 1, node.col_offset, "I901", rewritten) |
603 self.__error(node.lineno - 1, node.col_offset, "I901", rewritten) |
|
604 |
|
605 def __isModuleBanned(self, moduleName): |
|
606 """ |
|
607 Private method to check, if the given module name banned. |
|
608 |
|
609 @param moduleName module name to be checked |
|
610 @type str |
|
611 @return flag indicating a banned module |
|
612 @rtype bool |
|
613 """ |
|
614 if moduleName in self.__bannedModules: |
|
615 return True |
|
616 |
|
617 # Check unustructed wildcards |
|
618 if any( |
|
619 bannedPattern.match(moduleName) |
|
620 for bannedPattern in self.__bannedUnstructuredPatterns |
|
621 ): |
|
622 return True |
|
623 |
|
624 # Check structured wildcards |
|
625 if any( |
|
626 moduleName.startswith(bannedPrefix[:-1]) |
|
627 for bannedPrefix in self.__bannedStructuredPatterns |
|
628 ): |
|
629 return True |
|
630 |
|
631 return False |
562 |
632 |
563 def __checkBannedImport(self, node): |
633 def __checkBannedImport(self, node): |
564 """ |
634 """ |
565 Private method to check import of banned modules. |
635 Private method to check import of banned modules. |
566 |
636 |
584 moduleNames.sort(key=len, reverse=True) |
654 moduleNames.sort(key=len, reverse=True) |
585 |
655 |
586 warned = set() |
656 warned = set() |
587 |
657 |
588 for moduleName in moduleNames: |
658 for moduleName in moduleNames: |
589 if moduleName in self.__bannedModules: |
659 if self.__isModuleBanned(moduleName): |
590 if any(mod.startswith(moduleName) for mod in warned): |
660 if any(mod.startswith(moduleName) for mod in warned): |
591 # Do not show an error for this line if we already showed |
661 # Do not show an error for this line if we already showed |
592 # a more specific error. |
662 # a more specific error. |
593 continue |
663 continue |
594 else: |
664 else: |