Plugins/CheckerPlugins/Pep8/Pep8Fixer.py

changeset 2883
3c99a1db1506
parent 2882
9b97bc92fdda
child 2890
86b03a0c94bc
child 2911
ce77f0b1ee67
diff -r 9b97bc92fdda -r 3c99a1db1506 Plugins/CheckerPlugins/Pep8/Pep8Fixer.py
--- a/Plugins/CheckerPlugins/Pep8/Pep8Fixer.py	Tue Sep 03 20:28:59 2013 +0200
+++ b/Plugins/CheckerPlugins/Pep8/Pep8Fixer.py	Wed Sep 04 19:34:40 2013 +0200
@@ -59,7 +59,8 @@
         self.__origName = ""
         self.__source = sourceLines[:]  # save a copy
         self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()]
-        self.__noFixCodes = [c.strip() for c in noFixCodes.split(",") if c.strip()]
+        self.__noFixCodes = [
+            c.strip() for c in noFixCodes.split(",") if c.strip()]
         self.__maxLineLength = maxLineLength
         self.fixed = 0
         
@@ -250,7 +251,8 @@
     
     def __findLogical(self):
         """
-        Private method to extract the index of all the starts and ends of lines.
+        Private method to extract the index of all the starts and ends of
+        lines.
         
         @return tuple containing two lists of integer with start and end tuples
             of lines
@@ -342,9 +344,9 @@
         Private method to determine the line numbers that are within multi line
         strings and these which are part of a documentation string.
         
-        @return tuple of a set of line numbers belonging to a multi line string 
-            and a set of line numbers belonging to a multi line documentation
-            string (tuple of two set of integer)
+        @return tuple of a set of line numbers belonging to a multi line
+            string and a set of line numbers belonging to a multi line
+            documentation string (tuple of two set of integer)
         """
         if self.__multiLineNumbers is None:
             source = "".join(self.__source)
@@ -360,9 +362,11 @@
 
                     if (tokenType == tokenize.STRING and startRow != endRow):
                         if previousTokenType != tokenize.INDENT:
-                            self.__multiLineNumbers |= set(range(startRow, 1 + endRow))
+                            self.__multiLineNumbers |= set(
+                                range(startRow, 1 + endRow))
                         else:
-                            self.__docLineNumbers |= set(range(startRow, 1 + endRow))
+                            self.__docLineNumbers |= set(
+                                range(startRow, 1 + endRow))
 
                     previousTokenType = tokenType
             except (SyntaxError, tokenize.TokenError):
@@ -448,7 +452,8 @@
             if code in ["E101", "W191"]:
                 msg = self.trUtf8("Tab converted to 4 spaces.")
             else:
-                msg = self.trUtf8("Indentation adjusted to be a multiple of four.")
+                msg = self.trUtf8(
+                    "Indentation adjusted to be a multiple of four.")
             return (True, msg)
         else:
             return (False, self.trUtf8("Fix for {0} failed.").format(code))
@@ -481,7 +486,8 @@
     
     def __fixE122(self, code, line, pos, apply=False):
         """
-        Private method to fix a missing indentation of continuation lines (E122).
+        Private method to fix a missing indentation of continuation lines
+        (E122).
         
         @param code code of the issue (string)
         @param line line number of the issue (integer)
@@ -505,11 +511,14 @@
                         self.__indentWord + text.lstrip()
         else:
             self.__stackLogical.append((code, line, pos))
-        return (True, self.trUtf8("Missing indentation of continuation line corrected."))
+        return (
+            True,
+            self.trUtf8("Missing indentation of continuation line corrected."))
     
     def __fixE123(self, code, line, pos, apply=False):
         """
-        Private method to fix the indentation of a closing bracket lines (E123).
+        Private method to fix the indentation of a closing bracket lines
+        (E123).
         
         @param code code of the issue (string)
         @param line line number of the issue (integer)
@@ -534,7 +543,8 @@
                     self.__source[row] = newText
         else:
             self.__stackLogical.append((code, line, pos))
-        return (True, self.trUtf8("Closing bracket aligned to opening bracket."))
+        return (
+            True, self.trUtf8("Closing bracket aligned to opening bracket."))
     
     def __fixE125(self, code, line, pos, apply=False):
         """
@@ -592,7 +602,9 @@
                     self.__source[row] = newText
         else:
             self.__stackLogical.append((code, line, pos))
-        return (True, self.trUtf8("Indentation level of hanging indentation changed."))
+        return (
+            True,
+            self.trUtf8("Indentation level of hanging indentation changed."))
     
     def __fixE127(self, code, line, pos, apply=False):
         """
@@ -655,7 +667,8 @@
         text = self.__source[line]
         
         if '"""' in text or "'''" in text or text.rstrip().endswith('\\'):
-            return (False, self.trUtf8("Extraneous whitespace cannot be removed."))
+            return (
+                False, self.trUtf8("Extraneous whitespace cannot be removed."))
         
         newText = self.__fixWhitespace(text, pos, '')
         if newText == text:
@@ -680,7 +693,8 @@
         text = self.__source[line]
         
         if '"""' in text or "'''" in text or text.rstrip().endswith('\\'):
-            return (False, self.trUtf8("Extraneous whitespace cannot be removed."))
+            return (
+                False, self.trUtf8("Extraneous whitespace cannot be removed."))
         
         newText = self.__fixWhitespace(text, pos, ' ')
         if newText == text:
@@ -956,8 +970,8 @@
         @return flag indicating an applied fix (boolean) and a message for
             the fix (string)
         """
-        self.__source[line - 1] = self.__source[line - 1].rstrip("\n\r \t\\") + \
-            self.__getEol()
+        self.__source[line - 1] = \
+            self.__source[line - 1].rstrip("\n\r \t\\") + self.__getEol()
         return (True, self.trUtf8("Redundant backslash in brackets removed."))
     
     def __fixE701(self, code, line, pos, apply=False):
@@ -1113,7 +1127,8 @@
 
 class Pep8Reindenter(object):
     """
-    Class to reindent badly-indented code to uniformly use four-space indentation.
+    Class to reindent badly-indented code to uniformly use four-space
+    indentation.
 
     Released to the public domain, by Tim Peters, 03 October 2000.
     """
@@ -1192,8 +1207,10 @@
                         for j in range(i - 1, -1, -1):
                             jline, jlevel = stats[j]
                             if jlevel >= 0:
-                                want = have + self.__getlspace(after[jline - 1]) - \
-                                       self.__getlspace(lines[jline])
+                                want = \
+                                    have + \
+                                    self.__getlspace(after[jline - 1]) - \
+                                    self.__getlspace(lines[jline])
                                 break
                     if want < 0:
                         # Still no luck -- leave it alone.
@@ -1370,9 +1387,9 @@
         Public method to replicate logic in pep8.py, to know what level to
         indent things to.
 
-        @return list of lists, where each list represents valid indent levels for
-        the line in question, relative from the initial indent. However, the
-        first entry is the indent level which was expected.
+        @return list of lists, where each list represents valid indent levels
+        for the line in question, relative from the initial indent. However,
+        the first entry is the indent level which was expected.
         """
         # What follows is an adjusted version of
         # pep8.py:continuation_line_indentation. All of the comments have been
@@ -1535,13 +1552,6 @@
     """
     Class used to shorten lines to a given maximum of characters.
     """
-    ShortenOperatorGroups = frozenset([
-        frozenset([',']),
-        frozenset(['%']),
-        frozenset([',', '(', '[', '{']),
-        frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']),
-    ])
-    
     def __init__(self, curLine, prevLine, nextLine, maxLength=79, eol="\n",
                  indentWord="    ", isDocString=False):
         """
@@ -1568,8 +1578,8 @@
         """
         Public method to shorten the line wrapped by the class instance.
         
-        @return tuple of a flag indicating successful shortening, the shortened line
-            and the changed next line (boolean, string, string)
+        @return tuple of a flag indicating successful shortening, the
+            shortened line and the changed next line (boolean, string, string)
         """
         # 1. check for comment
         if self.__text.lstrip().startswith('#'):
@@ -1583,15 +1593,45 @@
                 return False, "", ""
             else:
                 return True, newText, ""
+        elif '#' in self.__text:
+            pos = self.__text.rfind("#")
+            newText = self.__text[:pos].rstrip() + self.__eol + \
+                self.__getIndent(self.__text) + self.__text[pos:]
+            if newText == self.__text:
+                return False, "", ""
+            else:
+                return True, newText, ""
 
         # Do multi line doc strings
         if self.__isDocString:
             source = self.__text.rstrip()
-            while len(source) > self.__maxLength:
-                source, right = source.rsplit(None, 1)
-                self.__nextText = self.__getIndent(self.__nextText) + \
-                    right + " " + self.__nextText.lstrip()
-            return True, source + self.__eol, self.__nextText
+            blank = source.rfind(" ")
+            while blank > self.__maxLength and blank != -1:
+                blank = source.rfind(" ", 0, blank)
+            if blank == -1:
+                # Cannot break
+                return False, "", ""
+            else:
+                first = self.__text[:blank]
+                second = self.__text[blank:].lstrip()
+                if self.__nextText.strip():
+                    if self.__nextText.lstrip().startswith("@"):
+                        # eric doc comment
+                        # create a new line and indent it
+                        newText = first + self.__eol + \
+                            self.__getIndent(first) + self.__indentWord + \
+                            second
+                        newNext = ""
+                    else:
+                        newText = first + self.__eol
+                        newNext = self.__getIndent(self.__nextText) + \
+                            second.rstrip() + " " + self.__nextText.lstrip()
+                else:
+                    # empty line, add a new line
+                    newText = first + self.__eol + self.__getIndent(first) + \
+                        second
+                    newNext = ""
+            return True, newText, newNext
         
         indent = self.__getIndent(self.__text)
         source = self.__text[len(indent):]
@@ -1602,11 +1642,22 @@
         try:
             tokens = list(tokenize.generate_tokens(sio.readline))
         except (SyntaxError, tokenize.TokenError):
-            multilineCandidate = self.__breakMultiline()
-            if multilineCandidate:
-                return True, multilineCandidate, ""
+            if source.rstrip().endswith("\\"):
+                # just join the continuation line and let the next run
+                # handle it once it tokenizes ok
+                newText = indent + source.rstrip()[:-1].rstrip() + " " + \
+                    self.__nextText.lstrip()
+                if indent:
+                    newNext = indent
+                else:
+                    newNext = " "
+                return True, newText, newNext
             else:
-                return False, "", ""
+                multilineCandidate = self.__breakMultiline()
+                if multilineCandidate:
+                    return True, multilineCandidate[0], multilineCandidate[1]
+                else:
+                    return False, "", ""
 
         # Handle statements by putting the right hand side on a line by itself.
         # This should let the next pass shorten it.
@@ -1619,14 +1670,40 @@
                 indent + ')' + self.__eol
             )
             return True, newText, ""
-
+        
         candidates = self.__shortenLine(tokens, source, indent)
-        candidates = list(sorted(
-            set(candidates).union([self.__text]),
-            key=lambda x: self.__lineShorteningRank(x)))
         if candidates:
+            candidates = list(sorted(
+                set(candidates).union([self.__text]),
+                key=lambda x: self.__lineShorteningRank(x)))
             return True, candidates[0], ""
         
+        source = self.__text
+        rs = source.rstrip()
+        if rs.endswith(("'", '"')) and " " in source:
+            if rs.endswith(('"""', "'''")):
+                quote = rs[-3:]
+            else:
+                quote = rs[-1]
+            blank = source.rfind(" ")
+            maxLen = self.__maxLength - 2 - len(quote)
+            while blank > maxLen and blank != -1:
+                blank = source.rfind(" ", 0, blank)
+            if blank != -1:
+                if source[blank + 1:].startswith(quote):
+                    first = source[:maxLen]
+                    second = source[maxLen:]
+                else:
+                    first = source[:blank]
+                    second = source[blank + 1:]
+                return (True,
+                    first + quote + " \\" + self.__eol +
+                    indent + self.__indentWord + quote + second,
+                    "")
+            else:
+                # Cannot break
+                return False, "", ""
+        
         return False, "", ""
     
     def __shortenComment(self, isLast):
@@ -1648,7 +1725,8 @@
                         len(indentation) + 72)
 
         MIN_CHARACTER_REPEAT = 5
-        if (len(newText) - len(newText.rstrip(newText[-1])) >= MIN_CHARACTER_REPEAT and
+        if (len(newText) - len(newText.rstrip(newText[-1])) >= \
+                MIN_CHARACTER_REPEAT and
                 not newText[-1].isalnum()):
             # Trim comments that end with things like ---------
             return newText[:maxLength] + self.__eol
@@ -1668,12 +1746,12 @@
         """
         Private method to break multi line strings.
         
-        @return broken multi line string or None, if a break is not possible
-            (string or None)
+        @return tuple of the shortened line and the changed next line
+            (string, string)
         """
         indentation = self.__getIndent(self.__text)
 
-        # Handle special case only.
+        # Handle special case.
         for symbol in '([{':
             # Only valid if symbol is not on a line by itself.
             if (
@@ -1686,15 +1764,32 @@
                 if index <= len(self.__indentWord) + len(indentation):
                     continue
 
-                if self.__isProbablyInsideStringOrComment(self.__text, index - 1):
+                if self.__isProbablyInsideStringOrComment(
+                        self.__text, index - 1):
                     continue
 
                 return (self.__text[:index].rstrip() + self.__eol +
                         indentation + self.__indentWord +
-                        self.__text[index:].lstrip())
+                        self.__text[index:].lstrip(), "")
         
-        # TODO: implement the method wrapping the line (see doc strings)
-        return None
+        newText = self.__text
+        newNext = self.__nextText
+        blank = newText.rfind(" ")
+        while blank > self.__maxLength and blank != -1:
+            blank = newText.rfind(" ", 0, blank)
+        if blank != -1:
+            first = self.__text[:blank]
+            second = self.__text[blank:].strip()
+            if newNext.strip():
+                newText = first + self.__eol
+                newNext = self.__getIndent(newNext) + \
+                    second + " " + newNext.lstrip()
+            else:
+                # empty line, add a new line
+                newText = first + self.__eol
+                newNext = self.__getIndent(newNext) + \
+                    second + self.__eol + newNext.lstrip()
+        return newText, newNext
     
     def __isProbablyInsideStringOrComment(self, line, index):
         """
@@ -1723,7 +1818,8 @@
         """
         Private method to shorten a line of code at an operator.
         
-        @param tokens tokens of the line as generated by tokenize (list of token)
+        @param tokens tokens of the line as generated by tokenize
+            (list of token)
         @param source code string to work at (string)
         @param indent indentation string of the code line (string)
         @return list of candidates (list of string)
@@ -1781,18 +1877,12 @@
                 if self.__checkSyntax(self.__normalizeMultiline(newText)):
                     candidates.append(indent + newText)
         
-        for keyTokenStrings in self.ShortenOperatorGroups:
-            shortened = self.__shortenLineAtTokens(
-                tokens, source, indent, keyTokenStrings)
-
-            if shortened is not None and shortened != source:
-                candidates.append(shortened)
-        
         return candidates
     
     def __normalizeMultiline(self, text):
         """
-        Private method to remove multiline-related code that will cause syntax error.
+        Private method to remove multiline-related code that will cause syntax
+        error.
         
         @param line code line to work on (string)
         @return normalized code line (string)
@@ -1812,77 +1902,6 @@
 
         return text
     
-    def __shortenLineAtTokens(self, tokens, source, indent, keyTokenStrings):
-        """
-        Private method to break lines at key tokens.
-        
-        @param tokens tokens of the line as generated by tokenize (list of token)
-        @param source code string to work at (string)
-        @param indent indentation string of the code line (string)
-        @param keyTokenStrings key tokens to break at
-        @return broken code line (string)
-        """
-        offsets = []
-        firstParen = True
-        for tkn in tokens:
-            tokenType = tkn[0]
-            tokenString = tkn[1]
-            nextOffset = tkn[2][1] + 1
-
-            assert tokenType != tokenize.INDENT
-
-            if tokenString in keyTokenStrings or (firstParen and
-                                                  tokenString == '('):
-                # Don't split right before newline.
-                if nextOffset < len(source) - 1:
-                    offsets.append(nextOffset)
-
-                if tokenString == '(':
-                    firstParen = False
-
-        currentIndent = None
-        newText = None
-        for text in self.__splitAtOffsets(source, offsets):
-            if newText:
-                newText += self.__eol + currentIndent + text
-
-                for symbol in '([{':
-                    if text.endswith(symbol):
-                        currentIndent += self.__indentWord
-            else:
-                # First line.
-                newText = text
-                assert not currentIndent
-                currentIndent = self.__indentWord
-
-        assert newText is not None
-
-        if self.__checkSyntax(self.__normalizeMultiline(newText)):
-            return indent + newText
-        else:
-            return None
-    
-    def __splitAtOffsets(self, line, offsets):
-        """
-        Private method to split the line at the given offsets.
-        
-        @param line line to split (string)
-        @param offsets offsets to split at (list of integer)
-        @return split line (list of string)
-        """
-        result = []
-
-        previousOffset = 0
-        currentOffset = 0
-        for currentOffset in sorted(offsets):
-            if currentOffset < len(line) and previousOffset != currentOffset:
-                result.append(line[previousOffset:currentOffset])
-            previousOffset = currentOffset
-
-        result.append(line[currentOffset:])
-
-        return result
-    
     def __lineShorteningRank(self, candidate):
         """
         Private method to rank a candidate.
@@ -1925,7 +1944,8 @@
                 for ending in '([{':
                     # Avoid lonely opening. They result in longer lines.
                     if (currentLine.endswith(ending) and
-                            len(currentLine.strip()) <= len(self.__indentWord)):
+                            len(currentLine.strip()) <= \
+                            len(self.__indentWord)):
                         rank += 100
 
                 if currentLine.endswith('%'):
@@ -1938,12 +1958,13 @@
                 rank += 10 * self.__countUnbalancedBrackets(currentLine)
         else:
             rank = 100000
-
+        
         return max(0, rank)
     
     def __countUnbalancedBrackets(self, line):
         """
-        Private method to determine the number of unmatched open/close brackets.
+        Private method to determine the number of unmatched open/close
+        brackets.
         
         @param line line to work at (string)
         @return number of unmatched open/close brackets (integer)

eric ide

mercurial