63 self.__filename = filename |
57 self.__filename = filename |
64 self.__source = source[:] |
58 self.__source = source[:] |
65 self.__tree = copy.deepcopy(tree) |
59 self.__tree = copy.deepcopy(tree) |
66 self.__args = args |
60 self.__args = args |
67 |
61 |
68 # parameters for import sorting |
|
69 if args["SortOrder"] == "native": |
|
70 self.__sortingFunction = sorted |
|
71 else: |
|
72 # naturally is the default sort order |
|
73 self.__sortingFunction = self.__naturally |
|
74 self.__sortCaseSensitive = args["SortCaseSensitive"] |
|
75 |
|
76 # statistics counters |
62 # statistics counters |
77 self.counters = {} |
63 self.counters = {} |
78 |
64 |
79 # collection of detected errors |
65 # collection of detected errors |
80 self.errors = [] |
66 self.errors = [] |
81 |
67 |
82 checkersWithCodes = [ |
68 checkersWithCodes = [ |
83 (self.__checkLocalImports, ("I101", "I102", "I103")), |
69 (self.__checkLocalImports, ("I101", "I102", "I103")), |
84 (self.__checkImportOrder, ("I201", "I202", "I203", "I204")), |
|
85 (self.__tidyImports, ("I901", "I902", "I903", "I904")), |
70 (self.__tidyImports, ("I901", "I902", "I903", "I904")), |
86 ] |
71 ] |
87 |
72 |
88 self.__checkers = [] |
73 self.__checkers = [] |
89 for checker, codes in checkersWithCodes: |
74 for checker, codes in checkersWithCodes: |
395 for violation in visitor.violations: |
156 for violation in visitor.violations: |
396 if not self.__ignoreCode(violation[1]): |
157 if not self.__ignoreCode(violation[1]): |
397 node = violation[0] |
158 node = violation[0] |
398 reason = violation[1] |
159 reason = violation[1] |
399 self.__error(node.lineno - 1, node.col_offset, reason) |
160 self.__error(node.lineno - 1, node.col_offset, reason) |
400 |
|
401 ####################################################################### |
|
402 ## Import order |
|
403 ## |
|
404 ## adapted from: flake8-alphabetize v0.0.18 |
|
405 ####################################################################### |
|
406 |
|
407 def __checkImportOrder(self): |
|
408 """ |
|
409 Private method to check the order of import statements. |
|
410 """ |
|
411 from .ImportNode import ImportNode |
|
412 |
|
413 errors = [] |
|
414 imports = [] |
|
415 importNodes, listNode = self.__findNodes(self.__tree) |
|
416 |
|
417 # check for an error in '__all__' |
|
418 allError = self.__findErrorInAll(listNode) |
|
419 if allError is not None: |
|
420 errors.append(allError) |
|
421 |
|
422 for importNode in importNodes: |
|
423 if isinstance(importNode, ast.Import) and len(importNode.names) > 1: |
|
424 # skip suck imports because its already handled by pycodestyle |
|
425 continue |
|
426 |
|
427 imports.append( |
|
428 ImportNode( |
|
429 self.__args.get("ApplicationPackageNames", []), |
|
430 importNode, |
|
431 self, |
|
432 self.__args.get("SortIgnoringStyle", False), |
|
433 self.__args.get("SortFromFirst", False), |
|
434 ) |
|
435 ) |
|
436 |
|
437 lenImports = len(imports) |
|
438 if lenImports > 0: |
|
439 p = imports[0] |
|
440 if p.error is not None: |
|
441 errors.append(p.error) |
|
442 |
|
443 if lenImports > 1: |
|
444 for n in imports[1:]: |
|
445 if n.error is not None: |
|
446 errors.append(n.error) |
|
447 |
|
448 if n == p: |
|
449 if self.__args.get("CombinedAsImports", False) or ( |
|
450 not n.asImport and not p.asImport |
|
451 ): |
|
452 errors.append((n.node, "I203", str(p), str(n))) |
|
453 elif n < p: |
|
454 errors.append((n.node, "I201", str(n), str(p))) |
|
455 |
|
456 p = n |
|
457 |
|
458 for error in errors: |
|
459 if not self.__ignoreCode(error[1]): |
|
460 node = error[0] |
|
461 reason = error[1] |
|
462 args = error[2:] |
|
463 self.__error(node.lineno - 1, node.col_offset, reason, *args) |
|
464 |
|
465 def __findNodes(self, tree): |
|
466 """ |
|
467 Private method to find all import and import from nodes of the given |
|
468 tree. |
|
469 |
|
470 @param tree reference to the ast node tree to be parsed |
|
471 @type ast.AST |
|
472 @return tuple containing a list of import nodes and the '__all__' node |
|
473 @rtype tuple of (ast.Import | ast.ImportFrom, ast.List | ast.Tuple) |
|
474 """ |
|
475 importNodes = [] |
|
476 listNode = None |
|
477 |
|
478 if isinstance(tree, ast.Module): |
|
479 body = tree.body |
|
480 |
|
481 for n in body: |
|
482 if isinstance(n, (ast.Import, ast.ImportFrom)): |
|
483 importNodes.append(n) |
|
484 |
|
485 elif isinstance(n, ast.Assign): |
|
486 for t in n.targets: |
|
487 if isinstance(t, ast.Name) and t.id == "__all__": |
|
488 value = n.value |
|
489 |
|
490 if isinstance(value, (ast.List, ast.Tuple)): |
|
491 listNode = value |
|
492 |
|
493 return importNodes, listNode |
|
494 |
|
495 def __findErrorInAll(self, node): |
|
496 """ |
|
497 Private method to check the '__all__' node for errors. |
|
498 |
|
499 @param node reference to the '__all__' node |
|
500 @type ast.List or ast.Tuple |
|
501 @return tuple containing a reference to the node and an error code |
|
502 @rtype rtype tuple of (ast.List | ast.Tuple, str) |
|
503 """ |
|
504 if node is not None: |
|
505 actualList = [] |
|
506 for el in node.elts: |
|
507 if isinstance(el, ast.Constant): |
|
508 actualList.append(el.value) |
|
509 elif isinstance(el, ast.Str): |
|
510 actualList.append(el.s) |
|
511 else: |
|
512 # Can't handle anything that isn't a string literal |
|
513 return None |
|
514 |
|
515 expectedList = self.sorted( |
|
516 actualList, |
|
517 key=lambda k: self.moduleKey(k, subImports=True), |
|
518 ) |
|
519 if expectedList != actualList: |
|
520 return (node, "I204", ", ".join(expectedList)) |
|
521 |
|
522 return None |
|
523 |
|
524 def sorted(self, toSort, key=None, reverse=False): |
|
525 """ |
|
526 Public method to sort the given list of names. |
|
527 |
|
528 @param toSort list of names to be sorted |
|
529 @type list of str |
|
530 @param key function to generate keys (defaults to None) |
|
531 @type function (optional) |
|
532 @param reverse flag indicating a reverse sort (defaults to False) |
|
533 @type bool (optional) |
|
534 @return sorted list of names |
|
535 @rtype list of str |
|
536 """ |
|
537 return self.__sortingFunction(toSort, key=key, reverse=reverse) |
|
538 |
|
539 def __naturally(self, toSort, key=None, reverse=False): |
|
540 """ |
|
541 Private method to sort the given list of names naturally. |
|
542 |
|
543 Note: Natural sorting maintains the sort order of numbers (i.e. |
|
544 [Q1, Q10, Q2] is sorted as [Q1, Q2, Q10] while the Python |
|
545 standard sort would yield [Q1, Q10, Q2]. |
|
546 |
|
547 @param toSort list of names to be sorted |
|
548 @type list of str |
|
549 @param key function to generate keys (defaults to None) |
|
550 @type function (optional) |
|
551 @param reverse flag indicating a reverse sort (defaults to False) |
|
552 @type bool (optional) |
|
553 @return sorted list of names |
|
554 @rtype list of str |
|
555 """ |
|
556 if key is None: |
|
557 keyCallback = self.__naturalKeys |
|
558 else: |
|
559 |
|
560 def keyCallback(text): |
|
561 return self.__naturalKeys(key(text)) |
|
562 |
|
563 return sorted(toSort, key=keyCallback, reverse=reverse) |
|
564 |
|
565 def __atoi(self, text): |
|
566 """ |
|
567 Private method to convert the given text to an integer number. |
|
568 |
|
569 @param text text to be converted |
|
570 @type str |
|
571 @return integer number |
|
572 @rtype int |
|
573 """ |
|
574 return int(text) if text.isdigit() else text |
|
575 |
|
576 def __naturalKeys(self, text): |
|
577 """ |
|
578 Private method to generate keys for natural sorting. |
|
579 |
|
580 @param text text to generate a key for |
|
581 @type str |
|
582 @return key for natural sorting |
|
583 @rtype list of str or int |
|
584 """ |
|
585 return [self.__atoi(c) for c in re.split(r"(\d+)", text)] |
|
586 |
|
587 def moduleKey(self, moduleName, subImports=False): |
|
588 """ |
|
589 Public method to generate a key for the given module name. |
|
590 |
|
591 @param moduleName module name |
|
592 @type str |
|
593 @param subImports flag indicating a sub import like in |
|
594 'from foo import bar, baz' (defaults to False) |
|
595 @type bool (optional) |
|
596 @return generated key |
|
597 @rtype str |
|
598 """ |
|
599 prefix = "" |
|
600 |
|
601 if subImports: |
|
602 if moduleName.isupper() and len(moduleName) > 1: |
|
603 prefix = "A" |
|
604 elif moduleName[0:1].isupper(): |
|
605 prefix = "B" |
|
606 else: |
|
607 prefix = "C" |
|
608 if not self.__sortCaseSensitive: |
|
609 moduleName = moduleName.lower() |
|
610 |
|
611 return f"{prefix}{moduleName}" |
|
612 |
161 |
613 ####################################################################### |
162 ####################################################################### |
614 ## Tidy imports |
163 ## Tidy imports |
615 ## |
164 ## |
616 ## adapted from: flake8-tidy-imports v4.8.0 |
165 ## adapted from: flake8-tidy-imports v4.8.0 |