Plugins/CheckerPlugins/Pep8/Pep8Fixer.py

changeset 2882
9b97bc92fdda
parent 2880
a45c59bddc79
child 2883
3c99a1db1506
diff -r e942480a6130 -r 9b97bc92fdda Plugins/CheckerPlugins/Pep8/Pep8Fixer.py
--- a/Plugins/CheckerPlugins/Pep8/Pep8Fixer.py	Tue Sep 03 19:19:56 2013 +0200
+++ b/Plugins/CheckerPlugins/Pep8/Pep8Fixer.py	Tue Sep 03 20:28:59 2013 +0200
@@ -27,8 +27,8 @@
                      "E231", "E241", "E242", "E251", "E261", "E262",
                      "E271", "E272", "E273", "E274", "W291", "W292",
                      "W293", "E301", "E302", "E303", "E304", "W391",
-                     "E401", "E502", "W603", "E701", "E702", "E703",
-                     "E711", "E712"
+                     "E401", "E501", "E502", "W603", "E701", "E702",
+                     "E703", "E711", "E712"
                     ]
 
 
@@ -116,6 +116,7 @@
             "E304": self.__fixE304,
             "W391": self.__fixW391,
             "E401": self.__fixE401,
+            "E501": self.__fixE501,
             "E502": self.__fixE502,
             "W603": self.__fixW603,
             "E701": self.__fixE701,
@@ -130,6 +131,9 @@
                                     # fixes. These work with logical lines.
         self.__stack = []           # these need to be fixed before the file
                                     # is saved but after all inline fixes
+        
+        self.__multiLineNumbers = None
+        self.__docLineNumbers = None
     
     def saveFile(self, encoding):
         """
@@ -333,6 +337,39 @@
         """
         return line.replace(line.lstrip(), "")
     
+    def __multilineStringLines(self):
+        """
+        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)
+        """
+        if self.__multiLineNumbers is None:
+            source = "".join(self.__source)
+            sio = io.StringIO(source)
+            self.__multiLineNumbers = set()
+            self.__docLineNumbers = set()
+            previousTokenType = ''
+            try:
+                for t in tokenize.generate_tokens(sio.readline):
+                    tokenType = t[0]
+                    startRow = t[2][0]
+                    endRow = t[3][0]
+
+                    if (tokenType == tokenize.STRING and startRow != endRow):
+                        if previousTokenType != tokenize.INDENT:
+                            self.__multiLineNumbers |= set(range(startRow, 1 + endRow))
+                        else:
+                            self.__docLineNumbers |= set(range(startRow, 1 + endRow))
+
+                    previousTokenType = tokenType
+            except (SyntaxError, tokenize.TokenError):
+                pass
+        
+        return self.__multiLineNumbers, self.__docLineNumbers
+    
     def __fixReindent(self, line, pos, logical):
         """
         Private method to fix a badly indented line.
@@ -871,6 +908,44 @@
             self.__stack.append((code, line, pos))
         return (True, self.trUtf8("Imports were put on separate lines."))
     
+    def __fixE501(self, code, line, pos, apply=False):
+        """
+        Private method to fix the long lines by breaking them (E501).
+        
+        @param code code of the issue (string)
+        @param line line number of the issue (integer)
+        @param pos position inside line (integer)
+        @keyparam apply flag indicating, that the fix should be applied
+            (boolean)
+        @return flag indicating an applied fix (boolean) and a message for
+            the fix (string)
+        """
+        multilineStringLines, docStringLines = self.__multilineStringLines()
+        if apply:
+            isDocString = line in docStringLines
+            line = line - 1
+            text = self.__source[line]
+            if line > 0:
+                prevText = self.__source[line - 1]
+            else:
+                prevText = ""
+            if line < len(self.__source) - 1:
+                nextText = self.__source[line + 1]
+            else:
+                nextText = ""
+            shortener = Pep8LineShortener(text, prevText, nextText,
+                 maxLength=self.__maxLineLength, eol=self.__getEol(),
+                 indentWord=self.__indentWord, isDocString=isDocString)
+            changed, newText, newNextText = shortener.shorten()
+            if changed:
+                if newText != text:
+                    self.__source[line] = newText
+                if newNextText and newNextText != nextText:
+                    self.__source[line + 1] = newNextText
+        else:
+            self.__stack.append((code, line, pos))
+        return (True, self.trUtf8("Long lines have been shortened."))
+    
     def __fixE502(self, code, line, pos):
         """
         Private method to fix redundant backslash within brackets (E502).
@@ -1467,8 +1542,8 @@
         frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']),
     ])
     
-    def __init__(self, curLine, prevLine, nextLine,
-                 maxLength=79, eol="\n", indentWord="    "):
+    def __init__(self, curLine, prevLine, nextLine, maxLength=79, eol="\n",
+                 indentWord="    ", isDocString=False):
         """
         Constructor
         
@@ -1478,6 +1553,8 @@
         @keyparam maxLength maximum allowed line length (integer)
         @keyparam eol eond-of-line marker (string)
         @keyparam indentWord string used for indentation (string)
+        @keyparam isDocString flag indicating that the line belongs to
+            a documentation string (boolean)
         """
         self.__text = curLine
         self.__prevText = prevLine
@@ -1485,13 +1562,14 @@
         self.__maxLength = maxLength
         self.__eol = eol
         self.__indentWord = indentWord
+        self.__isDocString = isDocString
     
     def shorten(self):
         """
         Public method to shorten the line wrapped by the class instance.
         
-        @return tuple of a flag indicating successful shortening and the shortened line
-            (boolean, 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('#'):
@@ -1502,24 +1580,33 @@
             # Wrap commented lines.
             newText = self.__shortenComment(lastComment)
             if newText == self.__text:
-                return False, ""
+                return False, "", ""
             else:
-                return True, newText
+                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
+        
         indent = self.__getIndent(self.__text)
         source = self.__text[len(indent):]
         assert source.lstrip() == source
         sio = io.StringIO(source)
-
+        
         # Check for multi line string.
         try:
             tokens = list(tokenize.generate_tokens(sio.readline))
         except (SyntaxError, tokenize.TokenError):
             multilineCandidate = self.__breakMultiline()
             if multilineCandidate:
-                return True, multilineCandidate
+                return True, multilineCandidate, ""
             else:
-                return False, ""
+                return False, "", ""
 
         # Handle statements by putting the right hand side on a line by itself.
         # This should let the next pass shorten it.
@@ -1531,16 +1618,16 @@
                 indent + self.__indentWord + re.sub('^return ', '', source) +
                 indent + ')' + self.__eol
             )
-            return True, newText
+            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:
-            return True, candidates[0]
+            return True, candidates[0], ""
         
-        return False, ""
+        return False, "", ""
     
     def __shortenComment(self, isLast):
         """
@@ -1605,7 +1692,8 @@
                 return (self.__text[:index].rstrip() + self.__eol +
                         indentation + self.__indentWord +
                         self.__text[index:].lstrip())
-
+        
+        # TODO: implement the method wrapping the line (see doc strings)
         return None
     
     def __isProbablyInsideStringOrComment(self, line, index):

eric ide

mercurial