Plugins/CheckerPlugins/Pep8/Pep8Fixer.py

changeset 2882
9b97bc92fdda
parent 2880
a45c59bddc79
child 2883
3c99a1db1506
equal deleted inserted replaced
2881:e942480a6130 2882:9b97bc92fdda
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,
128 self.__stackLogical = [] # these need to be fixed before the file 129 self.__stackLogical = [] # these need to be fixed before the file
129 # is saved but after all other inline 130 # is saved but after all other inline
130 # fixes. These work with logical lines. 131 # fixes. These work with logical lines.
131 self.__stack = [] # these need to be fixed before the file 132 self.__stack = [] # these need to be fixed before the file
132 # is saved but after all inline fixes 133 # is saved but after all inline fixes
134
135 self.__multiLineNumbers = None
136 self.__docLineNumbers = None
133 137
134 def saveFile(self, encoding): 138 def saveFile(self, encoding):
135 """ 139 """
136 Public method to save the modified file. 140 Public method to save the modified file.
137 141
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
1603 continue 1690 continue
1604 1691
1605 return (self.__text[:index].rstrip() + self.__eol + 1692 return (self.__text[:index].rstrip() + self.__eol +
1606 indentation + self.__indentWord + 1693 indentation + self.__indentWord +
1607 self.__text[index:].lstrip()) 1694 self.__text[index:].lstrip())
1608 1695
1696 # TODO: implement the method wrapping the line (see doc strings)
1609 return None 1697 return None
1610 1698
1611 def __isProbablyInsideStringOrComment(self, line, index): 1699 def __isProbablyInsideStringOrComment(self, line, index):
1612 """ 1700 """
1613 Private method to check, if the given string might be inside a string 1701 Private method to check, if the given string might be inside a string

eric ide

mercurial