340 def __multilineStringLines(self): |
342 def __multilineStringLines(self): |
341 """ |
343 """ |
342 Private method to determine the line numbers that are within multi line |
344 Private method to determine the line numbers that are within multi line |
343 strings and these which are part of a documentation string. |
345 strings and these which are part of a documentation string. |
344 |
346 |
345 @return tuple of a set of line numbers belonging to a multi line string |
347 @return tuple of a set of line numbers belonging to a multi line |
346 and a set of line numbers belonging to a multi line documentation |
348 string and a set of line numbers belonging to a multi line |
347 string (tuple of two set of integer) |
349 documentation string (tuple of two set of integer) |
348 """ |
350 """ |
349 if self.__multiLineNumbers is None: |
351 if self.__multiLineNumbers is None: |
350 source = "".join(self.__source) |
352 source = "".join(self.__source) |
351 sio = io.StringIO(source) |
353 sio = io.StringIO(source) |
352 self.__multiLineNumbers = set() |
354 self.__multiLineNumbers = set() |
446 if fixedLine is not None: |
450 if fixedLine is not None: |
447 self.__source[line - 1] = fixedLine |
451 self.__source[line - 1] = fixedLine |
448 if code in ["E101", "W191"]: |
452 if code in ["E101", "W191"]: |
449 msg = self.trUtf8("Tab converted to 4 spaces.") |
453 msg = self.trUtf8("Tab converted to 4 spaces.") |
450 else: |
454 else: |
451 msg = self.trUtf8("Indentation adjusted to be a multiple of four.") |
455 msg = self.trUtf8( |
|
456 "Indentation adjusted to be a multiple of four.") |
452 return (True, msg) |
457 return (True, msg) |
453 else: |
458 else: |
454 return (False, self.trUtf8("Fix for {0} failed.").format(code)) |
459 return (False, self.trUtf8("Fix for {0} failed.").format(code)) |
455 |
460 |
456 def __fixE121(self, code, line, pos, apply=False): |
461 def __fixE121(self, code, line, pos, apply=False): |
479 msg = self.trUtf8("Indentation of closing bracket corrected.") |
484 msg = self.trUtf8("Indentation of closing bracket corrected.") |
480 return (True, msg) |
485 return (True, msg) |
481 |
486 |
482 def __fixE122(self, code, line, pos, apply=False): |
487 def __fixE122(self, code, line, pos, apply=False): |
483 """ |
488 """ |
484 Private method to fix a missing indentation of continuation lines (E122). |
489 Private method to fix a missing indentation of continuation lines |
|
490 (E122). |
485 |
491 |
486 @param code code of the issue (string) |
492 @param code code of the issue (string) |
487 @param line line number of the issue (integer) |
493 @param line line number of the issue (integer) |
488 @param pos position inside line (integer) |
494 @param pos position inside line (integer) |
489 @keyparam apply flag indicating, that the fix should be applied |
495 @keyparam apply flag indicating, that the fix should be applied |
503 indentation = self.__getIndent(text) |
509 indentation = self.__getIndent(text) |
504 self.__source[line] = indentation + \ |
510 self.__source[line] = indentation + \ |
505 self.__indentWord + text.lstrip() |
511 self.__indentWord + text.lstrip() |
506 else: |
512 else: |
507 self.__stackLogical.append((code, line, pos)) |
513 self.__stackLogical.append((code, line, pos)) |
508 return (True, self.trUtf8("Missing indentation of continuation line corrected.")) |
514 return ( |
|
515 True, |
|
516 self.trUtf8("Missing indentation of continuation line corrected.")) |
509 |
517 |
510 def __fixE123(self, code, line, pos, apply=False): |
518 def __fixE123(self, code, line, pos, apply=False): |
511 """ |
519 """ |
512 Private method to fix the indentation of a closing bracket lines (E123). |
520 Private method to fix the indentation of a closing bracket lines |
|
521 (E123). |
513 |
522 |
514 @param code code of the issue (string) |
523 @param code code of the issue (string) |
515 @param line line number of the issue (integer) |
524 @param line line number of the issue (integer) |
516 @param pos position inside line (integer) |
525 @param pos position inside line (integer) |
517 @keyparam apply flag indicating, that the fix should be applied |
526 @keyparam apply flag indicating, that the fix should be applied |
532 self.__fixReindent(line, pos, logical) |
541 self.__fixReindent(line, pos, logical) |
533 else: |
542 else: |
534 self.__source[row] = newText |
543 self.__source[row] = newText |
535 else: |
544 else: |
536 self.__stackLogical.append((code, line, pos)) |
545 self.__stackLogical.append((code, line, pos)) |
537 return (True, self.trUtf8("Closing bracket aligned to opening bracket.")) |
546 return ( |
|
547 True, self.trUtf8("Closing bracket aligned to opening bracket.")) |
538 |
548 |
539 def __fixE125(self, code, line, pos, apply=False): |
549 def __fixE125(self, code, line, pos, apply=False): |
540 """ |
550 """ |
541 Private method to fix the indentation of continuation lines not |
551 Private method to fix the indentation of continuation lines not |
542 distinguishable from next logical line (E125). |
552 distinguishable from next logical line (E125). |
590 self.__fixReindent(line, pos, logical) |
600 self.__fixReindent(line, pos, logical) |
591 else: |
601 else: |
592 self.__source[row] = newText |
602 self.__source[row] = newText |
593 else: |
603 else: |
594 self.__stackLogical.append((code, line, pos)) |
604 self.__stackLogical.append((code, line, pos)) |
595 return (True, self.trUtf8("Indentation level of hanging indentation changed.")) |
605 return ( |
|
606 True, |
|
607 self.trUtf8("Indentation level of hanging indentation changed.")) |
596 |
608 |
597 def __fixE127(self, code, line, pos, apply=False): |
609 def __fixE127(self, code, line, pos, apply=False): |
598 """ |
610 """ |
599 Private method to fix over/under indented lines (E127, E128). |
611 Private method to fix over/under indented lines (E127, E128). |
600 |
612 |
954 @param line line number of the issue (integer) |
968 @param line line number of the issue (integer) |
955 @param pos position inside line (integer) |
969 @param pos position inside line (integer) |
956 @return flag indicating an applied fix (boolean) and a message for |
970 @return flag indicating an applied fix (boolean) and a message for |
957 the fix (string) |
971 the fix (string) |
958 """ |
972 """ |
959 self.__source[line - 1] = self.__source[line - 1].rstrip("\n\r \t\\") + \ |
973 self.__source[line - 1] = \ |
960 self.__getEol() |
974 self.__source[line - 1].rstrip("\n\r \t\\") + self.__getEol() |
961 return (True, self.trUtf8("Redundant backslash in brackets removed.")) |
975 return (True, self.trUtf8("Redundant backslash in brackets removed.")) |
962 |
976 |
963 def __fixE701(self, code, line, pos, apply=False): |
977 def __fixE701(self, code, line, pos, apply=False): |
964 """ |
978 """ |
965 Private method to fix colon-separated compund statements (E701). |
979 Private method to fix colon-separated compund statements (E701). |
1368 def pep8Expected(self): |
1385 def pep8Expected(self): |
1369 """ |
1386 """ |
1370 Public method to replicate logic in pep8.py, to know what level to |
1387 Public method to replicate logic in pep8.py, to know what level to |
1371 indent things to. |
1388 indent things to. |
1372 |
1389 |
1373 @return list of lists, where each list represents valid indent levels for |
1390 @return list of lists, where each list represents valid indent levels |
1374 the line in question, relative from the initial indent. However, the |
1391 for the line in question, relative from the initial indent. However, |
1375 first entry is the indent level which was expected. |
1392 the first entry is the indent level which was expected. |
1376 """ |
1393 """ |
1377 # What follows is an adjusted version of |
1394 # What follows is an adjusted version of |
1378 # pep8.py:continuation_line_indentation. All of the comments have been |
1395 # pep8.py:continuation_line_indentation. All of the comments have been |
1379 # stripped and the 'yield' statements replaced with 'pass'. |
1396 # stripped and the 'yield' statements replaced with 'pass'. |
1380 if not self.tokens: |
1397 if not self.tokens: |
1533 |
1550 |
1534 class Pep8LineShortener(object): |
1551 class Pep8LineShortener(object): |
1535 """ |
1552 """ |
1536 Class used to shorten lines to a given maximum of characters. |
1553 Class used to shorten lines to a given maximum of characters. |
1537 """ |
1554 """ |
1538 ShortenOperatorGroups = frozenset([ |
|
1539 frozenset([',']), |
|
1540 frozenset(['%']), |
|
1541 frozenset([',', '(', '[', '{']), |
|
1542 frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']), |
|
1543 ]) |
|
1544 |
|
1545 def __init__(self, curLine, prevLine, nextLine, maxLength=79, eol="\n", |
1555 def __init__(self, curLine, prevLine, nextLine, maxLength=79, eol="\n", |
1546 indentWord=" ", isDocString=False): |
1556 indentWord=" ", isDocString=False): |
1547 """ |
1557 """ |
1548 Constructor |
1558 Constructor |
1549 |
1559 |
1581 newText = self.__shortenComment(lastComment) |
1591 newText = self.__shortenComment(lastComment) |
1582 if newText == self.__text: |
1592 if newText == self.__text: |
1583 return False, "", "" |
1593 return False, "", "" |
1584 else: |
1594 else: |
1585 return True, newText, "" |
1595 return True, newText, "" |
|
1596 elif '#' in self.__text: |
|
1597 pos = self.__text.rfind("#") |
|
1598 newText = self.__text[:pos].rstrip() + self.__eol + \ |
|
1599 self.__getIndent(self.__text) + self.__text[pos:] |
|
1600 if newText == self.__text: |
|
1601 return False, "", "" |
|
1602 else: |
|
1603 return True, newText, "" |
1586 |
1604 |
1587 # Do multi line doc strings |
1605 # Do multi line doc strings |
1588 if self.__isDocString: |
1606 if self.__isDocString: |
1589 source = self.__text.rstrip() |
1607 source = self.__text.rstrip() |
1590 while len(source) > self.__maxLength: |
1608 blank = source.rfind(" ") |
1591 source, right = source.rsplit(None, 1) |
1609 while blank > self.__maxLength and blank != -1: |
1592 self.__nextText = self.__getIndent(self.__nextText) + \ |
1610 blank = source.rfind(" ", 0, blank) |
1593 right + " " + self.__nextText.lstrip() |
1611 if blank == -1: |
1594 return True, source + self.__eol, self.__nextText |
1612 # Cannot break |
|
1613 return False, "", "" |
|
1614 else: |
|
1615 first = self.__text[:blank] |
|
1616 second = self.__text[blank:].lstrip() |
|
1617 if self.__nextText.strip(): |
|
1618 if self.__nextText.lstrip().startswith("@"): |
|
1619 # eric doc comment |
|
1620 # create a new line and indent it |
|
1621 newText = first + self.__eol + \ |
|
1622 self.__getIndent(first) + self.__indentWord + \ |
|
1623 second |
|
1624 newNext = "" |
|
1625 else: |
|
1626 newText = first + self.__eol |
|
1627 newNext = self.__getIndent(self.__nextText) + \ |
|
1628 second.rstrip() + " " + self.__nextText.lstrip() |
|
1629 else: |
|
1630 # empty line, add a new line |
|
1631 newText = first + self.__eol + self.__getIndent(first) + \ |
|
1632 second |
|
1633 newNext = "" |
|
1634 return True, newText, newNext |
1595 |
1635 |
1596 indent = self.__getIndent(self.__text) |
1636 indent = self.__getIndent(self.__text) |
1597 source = self.__text[len(indent):] |
1637 source = self.__text[len(indent):] |
1598 assert source.lstrip() == source |
1638 assert source.lstrip() == source |
1599 sio = io.StringIO(source) |
1639 sio = io.StringIO(source) |
1600 |
1640 |
1601 # Check for multi line string. |
1641 # Check for multi line string. |
1602 try: |
1642 try: |
1603 tokens = list(tokenize.generate_tokens(sio.readline)) |
1643 tokens = list(tokenize.generate_tokens(sio.readline)) |
1604 except (SyntaxError, tokenize.TokenError): |
1644 except (SyntaxError, tokenize.TokenError): |
1605 multilineCandidate = self.__breakMultiline() |
1645 if source.rstrip().endswith("\\"): |
1606 if multilineCandidate: |
1646 # just join the continuation line and let the next run |
1607 return True, multilineCandidate, "" |
1647 # handle it once it tokenizes ok |
|
1648 newText = indent + source.rstrip()[:-1].rstrip() + " " + \ |
|
1649 self.__nextText.lstrip() |
|
1650 if indent: |
|
1651 newNext = indent |
|
1652 else: |
|
1653 newNext = " " |
|
1654 return True, newText, newNext |
1608 else: |
1655 else: |
1609 return False, "", "" |
1656 multilineCandidate = self.__breakMultiline() |
|
1657 if multilineCandidate: |
|
1658 return True, multilineCandidate[0], multilineCandidate[1] |
|
1659 else: |
|
1660 return False, "", "" |
1610 |
1661 |
1611 # Handle statements by putting the right hand side on a line by itself. |
1662 # Handle statements by putting the right hand side on a line by itself. |
1612 # This should let the next pass shorten it. |
1663 # This should let the next pass shorten it. |
1613 if source.startswith('return '): |
1664 if source.startswith('return '): |
1614 newText = ( |
1665 newText = ( |
1617 self.__eol + |
1668 self.__eol + |
1618 indent + self.__indentWord + re.sub('^return ', '', source) + |
1669 indent + self.__indentWord + re.sub('^return ', '', source) + |
1619 indent + ')' + self.__eol |
1670 indent + ')' + self.__eol |
1620 ) |
1671 ) |
1621 return True, newText, "" |
1672 return True, newText, "" |
1622 |
1673 |
1623 candidates = self.__shortenLine(tokens, source, indent) |
1674 candidates = self.__shortenLine(tokens, source, indent) |
1624 candidates = list(sorted( |
|
1625 set(candidates).union([self.__text]), |
|
1626 key=lambda x: self.__lineShorteningRank(x))) |
|
1627 if candidates: |
1675 if candidates: |
|
1676 candidates = list(sorted( |
|
1677 set(candidates).union([self.__text]), |
|
1678 key=lambda x: self.__lineShorteningRank(x))) |
1628 return True, candidates[0], "" |
1679 return True, candidates[0], "" |
|
1680 |
|
1681 source = self.__text |
|
1682 rs = source.rstrip() |
|
1683 if rs.endswith(("'", '"')) and " " in source: |
|
1684 if rs.endswith(('"""', "'''")): |
|
1685 quote = rs[-3:] |
|
1686 else: |
|
1687 quote = rs[-1] |
|
1688 blank = source.rfind(" ") |
|
1689 maxLen = self.__maxLength - 2 - len(quote) |
|
1690 while blank > maxLen and blank != -1: |
|
1691 blank = source.rfind(" ", 0, blank) |
|
1692 if blank != -1: |
|
1693 if source[blank + 1:].startswith(quote): |
|
1694 first = source[:maxLen] |
|
1695 second = source[maxLen:] |
|
1696 else: |
|
1697 first = source[:blank] |
|
1698 second = source[blank + 1:] |
|
1699 return (True, |
|
1700 first + quote + " \\" + self.__eol + |
|
1701 indent + self.__indentWord + quote + second, |
|
1702 "") |
|
1703 else: |
|
1704 # Cannot break |
|
1705 return False, "", "" |
1629 |
1706 |
1630 return False, "", "" |
1707 return False, "", "" |
1631 |
1708 |
1632 def __shortenComment(self, isLast): |
1709 def __shortenComment(self, isLast): |
1633 """ |
1710 """ |
1646 indentation = self.__getIndent(newText) + '# ' |
1723 indentation = self.__getIndent(newText) + '# ' |
1647 maxLength = min(self.__maxLength, |
1724 maxLength = min(self.__maxLength, |
1648 len(indentation) + 72) |
1725 len(indentation) + 72) |
1649 |
1726 |
1650 MIN_CHARACTER_REPEAT = 5 |
1727 MIN_CHARACTER_REPEAT = 5 |
1651 if (len(newText) - len(newText.rstrip(newText[-1])) >= MIN_CHARACTER_REPEAT and |
1728 if (len(newText) - len(newText.rstrip(newText[-1])) >= \ |
|
1729 MIN_CHARACTER_REPEAT and |
1652 not newText[-1].isalnum()): |
1730 not newText[-1].isalnum()): |
1653 # Trim comments that end with things like --------- |
1731 # Trim comments that end with things like --------- |
1654 return newText[:maxLength] + self.__eol |
1732 return newText[:maxLength] + self.__eol |
1655 elif isLast and re.match(r"\s*#+\s*\w+", newText): |
1733 elif isLast and re.match(r"\s*#+\s*\w+", newText): |
1656 import textwrap |
1734 import textwrap |
1684 index = 1 + self.__text.find(symbol) |
1762 index = 1 + self.__text.find(symbol) |
1685 |
1763 |
1686 if index <= len(self.__indentWord) + len(indentation): |
1764 if index <= len(self.__indentWord) + len(indentation): |
1687 continue |
1765 continue |
1688 |
1766 |
1689 if self.__isProbablyInsideStringOrComment(self.__text, index - 1): |
1767 if self.__isProbablyInsideStringOrComment( |
|
1768 self.__text, index - 1): |
1690 continue |
1769 continue |
1691 |
1770 |
1692 return (self.__text[:index].rstrip() + self.__eol + |
1771 return (self.__text[:index].rstrip() + self.__eol + |
1693 indentation + self.__indentWord + |
1772 indentation + self.__indentWord + |
1694 self.__text[index:].lstrip()) |
1773 self.__text[index:].lstrip(), "") |
1695 |
1774 |
1696 # TODO: implement the method wrapping the line (see doc strings) |
1775 newText = self.__text |
1697 return None |
1776 newNext = self.__nextText |
|
1777 blank = newText.rfind(" ") |
|
1778 while blank > self.__maxLength and blank != -1: |
|
1779 blank = newText.rfind(" ", 0, blank) |
|
1780 if blank != -1: |
|
1781 first = self.__text[:blank] |
|
1782 second = self.__text[blank:].strip() |
|
1783 if newNext.strip(): |
|
1784 newText = first + self.__eol |
|
1785 newNext = self.__getIndent(newNext) + \ |
|
1786 second + " " + newNext.lstrip() |
|
1787 else: |
|
1788 # empty line, add a new line |
|
1789 newText = first + self.__eol |
|
1790 newNext = self.__getIndent(newNext) + \ |
|
1791 second + self.__eol + newNext.lstrip() |
|
1792 return newText, newNext |
1698 |
1793 |
1699 def __isProbablyInsideStringOrComment(self, line, index): |
1794 def __isProbablyInsideStringOrComment(self, line, index): |
1700 """ |
1795 """ |
1701 Private method to check, if the given string might be inside a string |
1796 Private method to check, if the given string might be inside a string |
1702 or comment. |
1797 or comment. |
1779 |
1875 |
1780 # Only fix if syntax is okay. |
1876 # Only fix if syntax is okay. |
1781 if self.__checkSyntax(self.__normalizeMultiline(newText)): |
1877 if self.__checkSyntax(self.__normalizeMultiline(newText)): |
1782 candidates.append(indent + newText) |
1878 candidates.append(indent + newText) |
1783 |
1879 |
1784 for keyTokenStrings in self.ShortenOperatorGroups: |
|
1785 shortened = self.__shortenLineAtTokens( |
|
1786 tokens, source, indent, keyTokenStrings) |
|
1787 |
|
1788 if shortened is not None and shortened != source: |
|
1789 candidates.append(shortened) |
|
1790 |
|
1791 return candidates |
1880 return candidates |
1792 |
1881 |
1793 def __normalizeMultiline(self, text): |
1882 def __normalizeMultiline(self, text): |
1794 """ |
1883 """ |
1795 Private method to remove multiline-related code that will cause syntax error. |
1884 Private method to remove multiline-related code that will cause syntax |
|
1885 error. |
1796 |
1886 |
1797 @param line code line to work on (string) |
1887 @param line code line to work on (string) |
1798 @return normalized code line (string) |
1888 @return normalized code line (string) |
1799 """ |
1889 """ |
1800 for quote in '\'"': |
1890 for quote in '\'"': |
1810 if ':' not in splitText and 'def' not in splitText: |
1900 if ':' not in splitText and 'def' not in splitText: |
1811 return text[len('def'):].strip().rstrip(':') |
1901 return text[len('def'):].strip().rstrip(':') |
1812 |
1902 |
1813 return text |
1903 return text |
1814 |
1904 |
1815 def __shortenLineAtTokens(self, tokens, source, indent, keyTokenStrings): |
|
1816 """ |
|
1817 Private method to break lines at key tokens. |
|
1818 |
|
1819 @param tokens tokens of the line as generated by tokenize (list of token) |
|
1820 @param source code string to work at (string) |
|
1821 @param indent indentation string of the code line (string) |
|
1822 @param keyTokenStrings key tokens to break at |
|
1823 @return broken code line (string) |
|
1824 """ |
|
1825 offsets = [] |
|
1826 firstParen = True |
|
1827 for tkn in tokens: |
|
1828 tokenType = tkn[0] |
|
1829 tokenString = tkn[1] |
|
1830 nextOffset = tkn[2][1] + 1 |
|
1831 |
|
1832 assert tokenType != tokenize.INDENT |
|
1833 |
|
1834 if tokenString in keyTokenStrings or (firstParen and |
|
1835 tokenString == '('): |
|
1836 # Don't split right before newline. |
|
1837 if nextOffset < len(source) - 1: |
|
1838 offsets.append(nextOffset) |
|
1839 |
|
1840 if tokenString == '(': |
|
1841 firstParen = False |
|
1842 |
|
1843 currentIndent = None |
|
1844 newText = None |
|
1845 for text in self.__splitAtOffsets(source, offsets): |
|
1846 if newText: |
|
1847 newText += self.__eol + currentIndent + text |
|
1848 |
|
1849 for symbol in '([{': |
|
1850 if text.endswith(symbol): |
|
1851 currentIndent += self.__indentWord |
|
1852 else: |
|
1853 # First line. |
|
1854 newText = text |
|
1855 assert not currentIndent |
|
1856 currentIndent = self.__indentWord |
|
1857 |
|
1858 assert newText is not None |
|
1859 |
|
1860 if self.__checkSyntax(self.__normalizeMultiline(newText)): |
|
1861 return indent + newText |
|
1862 else: |
|
1863 return None |
|
1864 |
|
1865 def __splitAtOffsets(self, line, offsets): |
|
1866 """ |
|
1867 Private method to split the line at the given offsets. |
|
1868 |
|
1869 @param line line to split (string) |
|
1870 @param offsets offsets to split at (list of integer) |
|
1871 @return split line (list of string) |
|
1872 """ |
|
1873 result = [] |
|
1874 |
|
1875 previousOffset = 0 |
|
1876 currentOffset = 0 |
|
1877 for currentOffset in sorted(offsets): |
|
1878 if currentOffset < len(line) and previousOffset != currentOffset: |
|
1879 result.append(line[previousOffset:currentOffset]) |
|
1880 previousOffset = currentOffset |
|
1881 |
|
1882 result.append(line[currentOffset:]) |
|
1883 |
|
1884 return result |
|
1885 |
|
1886 def __lineShorteningRank(self, candidate): |
1905 def __lineShorteningRank(self, candidate): |
1887 """ |
1906 """ |
1888 Private method to rank a candidate. |
1907 Private method to rank a candidate. |
1889 |
1908 |
1890 @param candidate candidate line to rank (string) |
1909 @param candidate candidate line to rank (string) |