27 "E231", "E241", "E242", "E251", "E261", "E262", |
27 "E231", "E241", "E242", "E251", "E261", "E262", |
28 "E271", "E272", "E273", "E274", "W291", "W292", |
28 "E271", "E272", "E273", "E274", "W291", "W292", |
29 "W293", "E301", "E302", "E303", "E304", "W391", |
29 "W293", "E301", "E302", "E303", "E304", "W391", |
30 "E401", "E501", "E502", "W603", "E701", "E702", |
30 "E401", "E501", "E502", "W603", "E701", "E702", |
31 "E703", "E711", "E712" |
31 "E703", "E711", "E712" |
32 ] |
32 ] |
33 |
33 |
34 |
34 |
35 class Pep8Fixer(QObject): |
35 class Pep8Fixer(QObject): |
36 """ |
36 """ |
37 Class implementing a fixer for certain PEP 8 issues. |
37 Class implementing a fixer for certain PEP 8 issues. |
68 self.__eol = "" |
68 self.__eol = "" |
69 self.__indentWord = self.__getIndentWord() |
69 self.__indentWord = self.__getIndentWord() |
70 |
70 |
71 if not inPlace: |
71 if not inPlace: |
72 self.__origName = self.__filename |
72 self.__origName = self.__filename |
73 self.__filename = os.path.join(os.path.dirname(self.__filename), |
73 self.__filename = os.path.join( |
|
74 os.path.dirname(self.__filename), |
74 "fixed_" + os.path.basename(self.__filename)) |
75 "fixed_" + os.path.basename(self.__filename)) |
75 |
76 |
76 self.__fixes = { |
77 self.__fixes = { |
77 "E101": self.__fixE101, |
78 "E101": self.__fixE101, |
78 "E111": self.__fixE101, |
79 "E111": self.__fixE101, |
151 |
152 |
152 txt = "".join(self.__source) |
153 txt = "".join(self.__source) |
153 try: |
154 try: |
154 Utilities.writeEncodedFile(self.__filename, txt, encoding) |
155 Utilities.writeEncodedFile(self.__filename, txt, encoding) |
155 except (IOError, Utilities.CodingError, UnicodeError) as err: |
156 except (IOError, Utilities.CodingError, UnicodeError) as err: |
156 E5MessageBox.critical(self, |
157 E5MessageBox.critical( |
|
158 self, |
157 self.trUtf8("Fix PEP 8 issues"), |
159 self.trUtf8("Fix PEP 8 issues"), |
158 self.trUtf8( |
160 self.trUtf8( |
159 """<p>Could not save the file <b>{0}</b>.""" |
161 """<p>Could not save the file <b>{0}</b>.""" |
160 """ Skipping it.</p><p>Reason: {1}</p>""") |
162 """ Skipping it.</p><p>Reason: {1}</p>""") |
161 .format(self.__filename, str(err)) |
163 .format(self.__filename, str(err)) |
162 ) |
164 ) |
163 return False |
165 return False |
164 |
166 |
165 return True |
167 return True |
166 |
168 |
467 """ |
469 """ |
468 if self.__reindenter is None: |
470 if self.__reindenter is None: |
469 self.__reindenter = Pep8Reindenter(self.__source) |
471 self.__reindenter = Pep8Reindenter(self.__source) |
470 self.__reindenter.run() |
472 self.__reindenter.run() |
471 fixedLine = self.__reindenter.fixedLine(line - 1) |
473 fixedLine = self.__reindenter.fixedLine(line - 1) |
472 if fixedLine is not None: |
474 if fixedLine is not None and fixedLine != self.__source[line - 1]: |
473 self.__source[line - 1] = fixedLine |
475 self.__source[line - 1] = fixedLine |
474 if code in ["E101", "W191"]: |
476 if code in ["E101", "W191"]: |
475 msg = self.trUtf8("Tab converted to 4 spaces.") |
477 msg = self.trUtf8("Tab converted to 4 spaces.") |
476 else: |
478 else: |
477 msg = self.trUtf8( |
479 msg = self.trUtf8( |
478 "Indentation adjusted to be a multiple of four.") |
480 "Indentation adjusted to be a multiple of four.") |
479 return (1, msg, 0) |
481 return (1, msg, 0) |
480 else: |
482 else: |
481 return (0, self.trUtf8("Fix for {0} failed.").format(code), 0) |
483 return (0, "", 0) |
482 |
484 |
483 def __fixE121(self, code, line, pos, apply=False): |
485 def __fixE121(self, code, line, pos, apply=False): |
484 """ |
486 """ |
485 Private method to fix the indentation of continuation lines and |
487 Private method to fix the indentation of continuation lines and |
486 closing brackets (E121,E124). |
488 closing brackets (E121,E124). |
496 """ |
498 """ |
497 if apply: |
499 if apply: |
498 logical = self.__getLogical(line, pos) |
500 logical = self.__getLogical(line, pos) |
499 if logical: |
501 if logical: |
500 # Fix by adjusting initial indent level. |
502 # Fix by adjusting initial indent level. |
501 self.__fixReindent(line, pos, logical) |
503 changed = self.__fixReindent(line, pos, logical) |
502 if code == "E121": |
504 if changed: |
503 msg = self.trUtf8("Indentation of continuation line corrected.") |
505 if code == "E121": |
504 elif code == "E124": |
506 msg = self.trUtf8( |
505 msg = self.trUtf8("Indentation of closing bracket corrected.") |
507 "Indentation of continuation line corrected.") |
506 return (1, msg, 0) |
508 elif code == "E124": |
|
509 msg = self.trUtf8( |
|
510 "Indentation of closing bracket corrected.") |
|
511 return (1, msg, 0) |
|
512 return (0, "", 0) |
507 else: |
513 else: |
508 id = self.__getID() |
514 id = self.__getID() |
509 self.__stackLogical.append((id, code, line, pos)) |
515 self.__stackLogical.append((id, code, line, pos)) |
510 return (-1, "", id) |
516 return (-1, "", id) |
511 |
517 |
533 line = line - 1 |
539 line = line - 1 |
534 text = self.__source[line] |
540 text = self.__source[line] |
535 indentation = self.__getIndent(text) |
541 indentation = self.__getIndent(text) |
536 self.__source[line] = indentation + \ |
542 self.__source[line] = indentation + \ |
537 self.__indentWord + text.lstrip() |
543 self.__indentWord + text.lstrip() |
538 return ( |
544 return ( |
539 1, |
545 1, |
540 self.trUtf8( |
546 self.trUtf8( |
541 "Missing indentation of continuation line corrected."), |
547 "Missing indentation of continuation line corrected."), |
542 0) |
548 0) |
|
549 return (0, "", 0) |
543 else: |
550 else: |
544 id = self.__getID() |
551 id = self.__getID() |
545 self.__stackLogical.append((id, code, line, pos)) |
552 self.__stackLogical.append((id, code, line, pos)) |
546 return (-1, "", id) |
553 return (-1, "", id) |
547 |
554 |
567 row = line - 1 |
574 row = line - 1 |
568 text = self.__source[row] |
575 text = self.__source[row] |
569 newText = self.__getIndent(logicalLines[0]) + text.lstrip() |
576 newText = self.__getIndent(logicalLines[0]) + text.lstrip() |
570 if newText == text: |
577 if newText == text: |
571 # fall back to slower method |
578 # fall back to slower method |
572 self.__fixReindent(line, pos, logical) |
579 changed = self.__fixReindent(line, pos, logical) |
573 else: |
580 else: |
574 self.__source[row] = newText |
581 self.__source[row] = newText |
575 return ( |
582 changed = True |
576 1, |
583 if changed: |
577 self.trUtf8("Closing bracket aligned to opening bracket."), |
584 return (1, self.trUtf8( |
578 0) |
585 "Closing bracket aligned to opening bracket."), |
|
586 0) |
|
587 return (0, "", 0) |
579 else: |
588 else: |
580 id = self.__getID() |
589 id = self.__getID() |
581 self.__stackLogical.append((id, code, line, pos)) |
590 self.__stackLogical.append((id, code, line, pos)) |
582 return (-1, "", id) |
591 return (-1, "", id) |
583 |
592 |
603 if not modified: |
612 if not modified: |
604 row = line - 1 |
613 row = line - 1 |
605 text = self.__source[row] |
614 text = self.__source[row] |
606 self.__source[row] = self.__getIndent(text) + \ |
615 self.__source[row] = self.__getIndent(text) + \ |
607 self.__indentWord + text.lstrip() |
616 self.__indentWord + text.lstrip() |
608 return (1, self.trUtf8("Indentation level changed."), 0) |
617 return (1, self.trUtf8("Indentation level changed."), 0) |
|
618 return (0, "", 0) |
609 else: |
619 else: |
610 id = self.__getID() |
620 id = self.__getID() |
611 self.__stackLogical.append((id, code, line, pos)) |
621 self.__stackLogical.append((id, code, line, pos)) |
612 return (-1, "", id) |
622 return (-1, "", id) |
613 |
623 |
634 text = self.__source[row] |
644 text = self.__source[row] |
635 newText = self.__getIndent(logicalLines[0]) + \ |
645 newText = self.__getIndent(logicalLines[0]) + \ |
636 self.__indentWord + text.lstrip() |
646 self.__indentWord + text.lstrip() |
637 if newText == text: |
647 if newText == text: |
638 # fall back to slower method |
648 # fall back to slower method |
639 self.__fixReindent(line, pos, logical) |
649 changed = self.__fixReindent(line, pos, logical) |
640 else: |
650 else: |
641 self.__source[row] = newText |
651 self.__source[row] = newText |
642 return ( |
652 changed = True |
643 1, |
653 if changed: |
644 self.trUtf8( |
654 return (1, self.trUtf8( |
645 "Indentation level of hanging indentation changed."), |
655 "Indentation level of hanging indentation changed."), |
646 0) |
656 0) |
|
657 return (0, "", 0) |
647 else: |
658 else: |
648 id = self.__getID() |
659 id = self.__getID() |
649 self.__stackLogical.append((id, code, line, pos)) |
660 self.__stackLogical.append((id, code, line, pos)) |
650 return (-1, "", id) |
661 return (-1, "", id) |
651 |
662 |
687 if startIndex is not None: |
698 if startIndex is not None: |
688 newText = startIndex * ' ' + text.lstrip() |
699 newText = startIndex * ' ' + text.lstrip() |
689 |
700 |
690 if newText == text: |
701 if newText == text: |
691 # fall back to slower method |
702 # fall back to slower method |
692 self.__fixReindent(line, pos, logical) |
703 changed = self.__fixReindent(line, pos, logical) |
693 else: |
704 else: |
694 self.__source[row] = newText |
705 self.__source[row] = newText |
695 return (1, self.trUtf8("Visual indentation corrected."), 0) |
706 changed = True |
|
707 if changed: |
|
708 return (1, self.trUtf8("Visual indentation corrected."), 0) |
|
709 return (0, "", 0) |
696 else: |
710 else: |
697 id = self.__getID() |
711 id = self.__getID() |
698 self.__stackLogical.append((id, code, line, pos)) |
712 self.__stackLogical.append((id, code, line, pos)) |
699 return (-1, "", id) |
713 return (-1, "", id) |
700 |
714 |
712 """ |
726 """ |
713 line = line - 1 |
727 line = line - 1 |
714 text = self.__source[line] |
728 text = self.__source[line] |
715 |
729 |
716 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): |
730 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): |
717 return ( |
731 return (0, "", 0) |
718 0, self.trUtf8("Extraneous whitespace cannot be removed."), 0) |
|
719 |
732 |
720 newText = self.__fixWhitespace(text, pos, '') |
733 newText = self.__fixWhitespace(text, pos, '') |
721 if newText == text: |
734 if newText == text: |
722 return (0, "", 0) |
735 return (0, "", 0) |
723 |
736 |
739 """ |
752 """ |
740 line = line - 1 |
753 line = line - 1 |
741 text = self.__source[line] |
754 text = self.__source[line] |
742 |
755 |
743 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): |
756 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): |
744 return ( |
757 return (0, "", 0) |
745 0, self.trUtf8("Extraneous whitespace cannot be removed."), 0) |
|
746 |
758 |
747 newText = self.__fixWhitespace(text, pos, ' ') |
759 newText = self.__fixWhitespace(text, pos, ' ') |
748 if newText == text: |
760 if newText == text: |
749 return (0, "", 0) |
761 return (0, "", 0) |
750 |
762 |
766 fix (integer) |
778 fix (integer) |
767 """ |
779 """ |
768 line = line - 1 |
780 line = line - 1 |
769 pos = pos + 1 |
781 pos = pos + 1 |
770 self.__source[line] = self.__source[line][:pos] + \ |
782 self.__source[line] = self.__source[line][:pos] + \ |
771 " " + \ |
783 " " + self.__source[line][pos:] |
772 self.__source[line][pos:] |
|
773 return (1, self.trUtf8("Missing whitespace added."), 0) |
784 return (1, self.trUtf8("Missing whitespace added."), 0) |
774 |
785 |
775 def __fixE251(self, code, line, pos): |
786 def __fixE251(self, code, line, pos): |
776 """ |
787 """ |
777 Private method to fix extraneous whitespace around keyword and |
788 Private method to fix extraneous whitespace around keyword and |
877 if delta < 0: |
888 if delta < 0: |
878 # insert blank lines (one or two) |
889 # insert blank lines (one or two) |
879 while delta < 0: |
890 while delta < 0: |
880 self.__source.insert(line, self.__getEol()) |
891 self.__source.insert(line, self.__getEol()) |
881 delta += 1 |
892 delta += 1 |
|
893 changed = True |
882 elif delta > 0: |
894 elif delta > 0: |
883 # delete superfluous blank lines |
895 # delete superfluous blank lines |
884 while delta > 0: |
896 while delta > 0: |
885 del self.__source[line - 1] |
897 del self.__source[line - 1] |
886 line -= 1 |
898 line -= 1 |
887 delta -= 1 |
899 delta -= 1 |
|
900 changed = True |
|
901 else: |
|
902 changed = False |
888 |
903 |
889 if delta < 0: |
904 if changed: |
890 msg = self.trUtf8("%n blank line(s) inserted.", "", -delta) |
905 if delta < 0: |
891 elif delta > 0: |
906 msg = self.trUtf8( |
892 msg = self.trUtf8("%n superfluous lines removed", "", delta) |
907 "%n blank line(s) inserted.", "", -delta) |
893 else: |
908 elif delta > 0: |
894 msg = "" |
909 msg = self.trUtf8( |
895 return (1, msg, 0) |
910 "%n superfluous lines removed", "", delta) |
|
911 else: |
|
912 msg = "" |
|
913 return (1, msg, 0) |
|
914 return (0, "", 0) |
896 else: |
915 else: |
897 id = self.__getID() |
916 id = self.__getID() |
898 self.__stack.append((id, code, line, pos)) |
917 self.__stack.append((id, code, line, pos)) |
899 return (-1, "", id) |
918 return (-1, "", id) |
900 |
919 |
945 if self.__source[index].strip() == "": |
964 if self.__source[index].strip() == "": |
946 del self.__source[index] |
965 del self.__source[index] |
947 index -= 1 |
966 index -= 1 |
948 else: |
967 else: |
949 break |
968 break |
950 return ( |
969 return (1, self.trUtf8( |
951 1, |
970 "Superfluous blank lines after function decorator removed."), |
952 self.trUtf8("Superfluous blank lines after function" |
|
953 " decorator removed."), |
|
954 0) |
971 0) |
955 else: |
972 else: |
956 id = self.__getID() |
973 id = self.__getID() |
957 self.__stack.append((id, code, line, pos)) |
974 self.__stack.append((id, code, line, pos)) |
958 return (-1, "", id) |
975 return (-1, "", id) |
1015 prevText = "" |
1032 prevText = "" |
1016 if line < len(self.__source) - 1: |
1033 if line < len(self.__source) - 1: |
1017 nextText = self.__source[line + 1] |
1034 nextText = self.__source[line + 1] |
1018 else: |
1035 else: |
1019 nextText = "" |
1036 nextText = "" |
1020 shortener = Pep8LineShortener(text, prevText, nextText, |
1037 shortener = Pep8LineShortener( |
1021 maxLength=self.__maxLineLength, eol=self.__getEol(), |
1038 text, prevText, nextText, |
1022 indentWord=self.__indentWord, isDocString=isDocString) |
1039 maxLength=self.__maxLineLength, eol=self.__getEol(), |
|
1040 indentWord=self.__indentWord, isDocString=isDocString) |
1023 changed, newText, newNextText = shortener.shorten() |
1041 changed, newText, newNextText = shortener.shorten() |
1024 if changed: |
1042 if changed: |
1025 if newText != text: |
1043 if newText != text: |
1026 self.__source[line] = newText |
1044 self.__source[line] = newText |
1027 if newNextText and newNextText != nextText: |
1045 if newNextText and newNextText != nextText: |
1049 self.__source[line - 1].rstrip("\n\r \t\\") + self.__getEol() |
1067 self.__source[line - 1].rstrip("\n\r \t\\") + self.__getEol() |
1050 return (1, self.trUtf8("Redundant backslash in brackets removed."), 0) |
1068 return (1, self.trUtf8("Redundant backslash in brackets removed."), 0) |
1051 |
1069 |
1052 def __fixE701(self, code, line, pos, apply=False): |
1070 def __fixE701(self, code, line, pos, apply=False): |
1053 """ |
1071 """ |
1054 Private method to fix colon-separated compund statements (E701). |
1072 Private method to fix colon-separated compound statements (E701). |
1055 |
1073 |
1056 @param code code of the issue (string) |
1074 @param code code of the issue (string) |
1057 @param line line number of the issue (integer) |
1075 @param line line number of the issue (integer) |
1058 @param pos position inside line (integer) |
1076 @param pos position inside line (integer) |
1059 @keyparam apply flag indicating, that the fix should be applied |
1077 @keyparam apply flag indicating, that the fix should be applied |
1782 first = source[:maxLen] |
1800 first = source[:maxLen] |
1783 second = source[maxLen:] |
1801 second = source[maxLen:] |
1784 else: |
1802 else: |
1785 first = source[:blank] |
1803 first = source[:blank] |
1786 second = source[blank + 1:] |
1804 second = source[blank + 1:] |
1787 return (True, |
1805 return ( |
|
1806 True, |
1788 first + quote + " \\" + self.__eol + |
1807 first + quote + " \\" + self.__eol + |
1789 indent + self.__indentWord + quote + second, |
1808 indent + self.__indentWord + quote + second, |
1790 "") |
1809 "") |
1791 else: |
1810 else: |
1792 # Cannot break |
1811 # Cannot break |
1811 indentation = self.__getIndent(newText) + '# ' |
1830 indentation = self.__getIndent(newText) + '# ' |
1812 maxLength = min(self.__maxLength, |
1831 maxLength = min(self.__maxLength, |
1813 len(indentation) + 72) |
1832 len(indentation) + 72) |
1814 |
1833 |
1815 MIN_CHARACTER_REPEAT = 5 |
1834 MIN_CHARACTER_REPEAT = 5 |
1816 if (len(newText) - len(newText.rstrip(newText[-1])) >= \ |
1835 if len(newText) - len(newText.rstrip(newText[-1])) >= \ |
1817 MIN_CHARACTER_REPEAT and |
1836 MIN_CHARACTER_REPEAT and \ |
1818 not newText[-1].isalnum()): |
1837 not newText[-1].isalnum(): |
1819 # Trim comments that end with things like --------- |
1838 # Trim comments that end with things like --------- |
1820 return newText[:maxLength] + self.__eol |
1839 return newText[:maxLength] + self.__eol |
1821 elif isLast and re.match(r"\s*#+\s*\w+", newText): |
1840 elif isLast and re.match(r"\s*#+\s*\w+", newText): |
1822 import textwrap |
1841 import textwrap |
1823 splitLines = textwrap.wrap(newText.lstrip(" \t#"), |
1842 splitLines = textwrap.wrap(newText.lstrip(" \t#"), |
1924 ): |
1943 ): |
1925 # Move inline comments to previous line. |
1944 # Move inline comments to previous line. |
1926 offset = tkn[2][1] |
1945 offset = tkn[2][1] |
1927 first = source[:offset] |
1946 first = source[:offset] |
1928 second = source[offset:] |
1947 second = source[offset:] |
1929 candidates.append(indent + second.strip() + self.__eol + |
1948 candidates.append( |
1930 indent + first.strip() + self.__eol) |
1949 indent + second.strip() + self.__eol + |
|
1950 indent + first.strip() + self.__eol) |
1931 elif tokenType == tokenize.OP and tokenString != '=': |
1951 elif tokenType == tokenize.OP and tokenString != '=': |
1932 # Don't break on '=' after keyword as this violates PEP 8. |
1952 # Don't break on '=' after keyword as this violates PEP 8. |
1933 |
1953 |
1934 assert tokenType != tokenize.INDENT |
1954 assert tokenType != tokenize.INDENT |
1935 |
1955 |
2033 if currentLine.startswith(badStart): |
2053 if currentLine.startswith(badStart): |
2034 rank += 100 |
2054 rank += 100 |
2035 |
2055 |
2036 for ending in '([{': |
2056 for ending in '([{': |
2037 # Avoid lonely opening. They result in longer lines. |
2057 # Avoid lonely opening. They result in longer lines. |
2038 if (currentLine.endswith(ending) and |
2058 if currentLine.endswith(ending) and \ |
2039 len(currentLine.strip()) <= \ |
2059 len(currentLine.strip()) <= len(self.__indentWord): |
2040 len(self.__indentWord)): |
|
2041 rank += 100 |
2060 rank += 100 |
2042 |
2061 |
2043 if currentLine.endswith('%'): |
2062 if currentLine.endswith('%'): |
2044 rank -= 20 |
2063 rank -= 20 |
2045 |
2064 |