25 "E201", "E202", "E203", "E211", "E221", "E222", |
25 "E201", "E202", "E203", "E211", "E221", "E222", |
26 "E223", "E224", "E225", "E226", "E227", "E228", |
26 "E223", "E224", "E225", "E226", "E227", "E228", |
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", "E502", "W603", "E701", "E702", "E703", |
30 "E401", "E501", "E502", "W603", "E701", "E702", |
31 "E711", "E712" |
31 "E703", "E711", "E712" |
32 ] |
32 ] |
33 |
33 |
34 |
34 |
35 class Pep8Fixer(QObject): |
35 class Pep8Fixer(QObject): |
36 """ |
36 """ |
114 "E302": self.__fixE302, |
114 "E302": self.__fixE302, |
115 "E303": self.__fixE303, |
115 "E303": self.__fixE303, |
116 "E304": self.__fixE304, |
116 "E304": self.__fixE304, |
117 "W391": self.__fixW391, |
117 "W391": self.__fixW391, |
118 "E401": self.__fixE401, |
118 "E401": self.__fixE401, |
|
119 "E501": self.__fixE501, |
119 "E502": self.__fixE502, |
120 "E502": self.__fixE502, |
120 "W603": self.__fixW603, |
121 "W603": self.__fixW603, |
121 "E701": self.__fixE701, |
122 "E701": self.__fixE701, |
122 "E702": self.__fixE702, |
123 "E702": self.__fixE702, |
123 "E703": self.__fixE702, |
124 "E703": self.__fixE702, |
331 @param line line to determine the indentation string from (string) |
335 @param line line to determine the indentation string from (string) |
332 @return indentation string (string) |
336 @return indentation string (string) |
333 """ |
337 """ |
334 return line.replace(line.lstrip(), "") |
338 return line.replace(line.lstrip(), "") |
335 |
339 |
|
340 def __multilineStringLines(self): |
|
341 """ |
|
342 Private method to determine the line numbers that are within multi line |
|
343 strings and these which are part of a documentation string. |
|
344 |
|
345 @return tuple of a set of line numbers belonging to a multi line string |
|
346 and a set of line numbers belonging to a multi line documentation |
|
347 string (tuple of two set of integer) |
|
348 """ |
|
349 if self.__multiLineNumbers is None: |
|
350 source = "".join(self.__source) |
|
351 sio = io.StringIO(source) |
|
352 self.__multiLineNumbers = set() |
|
353 self.__docLineNumbers = set() |
|
354 previousTokenType = '' |
|
355 try: |
|
356 for t in tokenize.generate_tokens(sio.readline): |
|
357 tokenType = t[0] |
|
358 startRow = t[2][0] |
|
359 endRow = t[3][0] |
|
360 |
|
361 if (tokenType == tokenize.STRING and startRow != endRow): |
|
362 if previousTokenType != tokenize.INDENT: |
|
363 self.__multiLineNumbers |= set(range(startRow, 1 + endRow)) |
|
364 else: |
|
365 self.__docLineNumbers |= set(range(startRow, 1 + endRow)) |
|
366 |
|
367 previousTokenType = tokenType |
|
368 except (SyntaxError, tokenize.TokenError): |
|
369 pass |
|
370 |
|
371 return self.__multiLineNumbers, self.__docLineNumbers |
|
372 |
336 def __fixReindent(self, line, pos, logical): |
373 def __fixReindent(self, line, pos, logical): |
337 """ |
374 """ |
338 Private method to fix a badly indented line. |
375 Private method to fix a badly indented line. |
339 |
376 |
340 This is done by adding or removing from its initial indent only. |
377 This is done by adding or removing from its initial indent only. |
868 self.__getIndent(text) + "import " + text[pos:].lstrip("\t ,") |
905 self.__getIndent(text) + "import " + text[pos:].lstrip("\t ,") |
869 self.__source[line] = newText |
906 self.__source[line] = newText |
870 else: |
907 else: |
871 self.__stack.append((code, line, pos)) |
908 self.__stack.append((code, line, pos)) |
872 return (True, self.trUtf8("Imports were put on separate lines.")) |
909 return (True, self.trUtf8("Imports were put on separate lines.")) |
|
910 |
|
911 def __fixE501(self, code, line, pos, apply=False): |
|
912 """ |
|
913 Private method to fix the long lines by breaking them (E501). |
|
914 |
|
915 @param code code of the issue (string) |
|
916 @param line line number of the issue (integer) |
|
917 @param pos position inside line (integer) |
|
918 @keyparam apply flag indicating, that the fix should be applied |
|
919 (boolean) |
|
920 @return flag indicating an applied fix (boolean) and a message for |
|
921 the fix (string) |
|
922 """ |
|
923 multilineStringLines, docStringLines = self.__multilineStringLines() |
|
924 if apply: |
|
925 isDocString = line in docStringLines |
|
926 line = line - 1 |
|
927 text = self.__source[line] |
|
928 if line > 0: |
|
929 prevText = self.__source[line - 1] |
|
930 else: |
|
931 prevText = "" |
|
932 if line < len(self.__source) - 1: |
|
933 nextText = self.__source[line + 1] |
|
934 else: |
|
935 nextText = "" |
|
936 shortener = Pep8LineShortener(text, prevText, nextText, |
|
937 maxLength=self.__maxLineLength, eol=self.__getEol(), |
|
938 indentWord=self.__indentWord, isDocString=isDocString) |
|
939 changed, newText, newNextText = shortener.shorten() |
|
940 if changed: |
|
941 if newText != text: |
|
942 self.__source[line] = newText |
|
943 if newNextText and newNextText != nextText: |
|
944 self.__source[line + 1] = newNextText |
|
945 else: |
|
946 self.__stack.append((code, line, pos)) |
|
947 return (True, self.trUtf8("Long lines have been shortened.")) |
873 |
948 |
874 def __fixE502(self, code, line, pos): |
949 def __fixE502(self, code, line, pos): |
875 """ |
950 """ |
876 Private method to fix redundant backslash within brackets (E502). |
951 Private method to fix redundant backslash within brackets (E502). |
877 |
952 |
1465 frozenset(['%']), |
1540 frozenset(['%']), |
1466 frozenset([',', '(', '[', '{']), |
1541 frozenset([',', '(', '[', '{']), |
1467 frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']), |
1542 frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']), |
1468 ]) |
1543 ]) |
1469 |
1544 |
1470 def __init__(self, curLine, prevLine, nextLine, |
1545 def __init__(self, curLine, prevLine, nextLine, maxLength=79, eol="\n", |
1471 maxLength=79, eol="\n", indentWord=" "): |
1546 indentWord=" ", isDocString=False): |
1472 """ |
1547 """ |
1473 Constructor |
1548 Constructor |
1474 |
1549 |
1475 @param curLine text to work on (string) |
1550 @param curLine text to work on (string) |
1476 @param prevLine line before the text to work on (string) |
1551 @param prevLine line before the text to work on (string) |
1477 @param nextLine line after the text to work on (string) |
1552 @param nextLine line after the text to work on (string) |
1478 @keyparam maxLength maximum allowed line length (integer) |
1553 @keyparam maxLength maximum allowed line length (integer) |
1479 @keyparam eol eond-of-line marker (string) |
1554 @keyparam eol eond-of-line marker (string) |
1480 @keyparam indentWord string used for indentation (string) |
1555 @keyparam indentWord string used for indentation (string) |
|
1556 @keyparam isDocString flag indicating that the line belongs to |
|
1557 a documentation string (boolean) |
1481 """ |
1558 """ |
1482 self.__text = curLine |
1559 self.__text = curLine |
1483 self.__prevText = prevLine |
1560 self.__prevText = prevLine |
1484 self.__nextText = nextLine |
1561 self.__nextText = nextLine |
1485 self.__maxLength = maxLength |
1562 self.__maxLength = maxLength |
1486 self.__eol = eol |
1563 self.__eol = eol |
1487 self.__indentWord = indentWord |
1564 self.__indentWord = indentWord |
|
1565 self.__isDocString = isDocString |
1488 |
1566 |
1489 def shorten(self): |
1567 def shorten(self): |
1490 """ |
1568 """ |
1491 Public method to shorten the line wrapped by the class instance. |
1569 Public method to shorten the line wrapped by the class instance. |
1492 |
1570 |
1493 @return tuple of a flag indicating successful shortening and the shortened line |
1571 @return tuple of a flag indicating successful shortening, the shortened line |
1494 (boolean, string) |
1572 and the changed next line (boolean, string, string) |
1495 """ |
1573 """ |
1496 # 1. check for comment |
1574 # 1. check for comment |
1497 if self.__text.lstrip().startswith('#'): |
1575 if self.__text.lstrip().startswith('#'): |
1498 lastComment = True |
1576 lastComment = True |
1499 if self.__nextText.lstrip().startswith('#'): |
1577 if self.__nextText.lstrip().startswith('#'): |
1500 lastComment = False |
1578 lastComment = False |
1501 |
1579 |
1502 # Wrap commented lines. |
1580 # Wrap commented lines. |
1503 newText = self.__shortenComment(lastComment) |
1581 newText = self.__shortenComment(lastComment) |
1504 if newText == self.__text: |
1582 if newText == self.__text: |
1505 return False, "" |
1583 return False, "", "" |
1506 else: |
1584 else: |
1507 return True, newText |
1585 return True, newText, "" |
1508 |
1586 |
|
1587 # Do multi line doc strings |
|
1588 if self.__isDocString: |
|
1589 source = self.__text.rstrip() |
|
1590 while len(source) > self.__maxLength: |
|
1591 source, right = source.rsplit(None, 1) |
|
1592 self.__nextText = self.__getIndent(self.__nextText) + \ |
|
1593 right + " " + self.__nextText.lstrip() |
|
1594 return True, source + self.__eol, self.__nextText |
|
1595 |
1509 indent = self.__getIndent(self.__text) |
1596 indent = self.__getIndent(self.__text) |
1510 source = self.__text[len(indent):] |
1597 source = self.__text[len(indent):] |
1511 assert source.lstrip() == source |
1598 assert source.lstrip() == source |
1512 sio = io.StringIO(source) |
1599 sio = io.StringIO(source) |
1513 |
1600 |
1514 # Check for multi line string. |
1601 # Check for multi line string. |
1515 try: |
1602 try: |
1516 tokens = list(tokenize.generate_tokens(sio.readline)) |
1603 tokens = list(tokenize.generate_tokens(sio.readline)) |
1517 except (SyntaxError, tokenize.TokenError): |
1604 except (SyntaxError, tokenize.TokenError): |
1518 multilineCandidate = self.__breakMultiline() |
1605 multilineCandidate = self.__breakMultiline() |
1519 if multilineCandidate: |
1606 if multilineCandidate: |
1520 return True, multilineCandidate |
1607 return True, multilineCandidate, "" |
1521 else: |
1608 else: |
1522 return False, "" |
1609 return False, "", "" |
1523 |
1610 |
1524 # Handle statements by putting the right hand side on a line by itself. |
1611 # Handle statements by putting the right hand side on a line by itself. |
1525 # This should let the next pass shorten it. |
1612 # This should let the next pass shorten it. |
1526 if source.startswith('return '): |
1613 if source.startswith('return '): |
1527 newText = ( |
1614 newText = ( |
1529 'return (' + |
1616 'return (' + |
1530 self.__eol + |
1617 self.__eol + |
1531 indent + self.__indentWord + re.sub('^return ', '', source) + |
1618 indent + self.__indentWord + re.sub('^return ', '', source) + |
1532 indent + ')' + self.__eol |
1619 indent + ')' + self.__eol |
1533 ) |
1620 ) |
1534 return True, newText |
1621 return True, newText, "" |
1535 |
1622 |
1536 candidates = self.__shortenLine(tokens, source, indent) |
1623 candidates = self.__shortenLine(tokens, source, indent) |
1537 candidates = list(sorted( |
1624 candidates = list(sorted( |
1538 set(candidates).union([self.__text]), |
1625 set(candidates).union([self.__text]), |
1539 key=lambda x: self.__lineShorteningRank(x))) |
1626 key=lambda x: self.__lineShorteningRank(x))) |
1540 if candidates: |
1627 if candidates: |
1541 return True, candidates[0] |
1628 return True, candidates[0], "" |
1542 |
1629 |
1543 return False, "" |
1630 return False, "", "" |
1544 |
1631 |
1545 def __shortenComment(self, isLast): |
1632 def __shortenComment(self, isLast): |
1546 """ |
1633 """ |
1547 Private method to shorten a comment line. |
1634 Private method to shorten a comment line. |
1548 |
1635 |