Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleFixer.py

branch
Py2 comp.
changeset 3057
10516539f238
parent 3056
9986ec0e559a
parent 2982
556adfe76ba7
child 3145
a9de05d4a22f
equal deleted inserted replaced
3056:9986ec0e559a 3057:10516539f238
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 - 2013 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a class to fix certain code style issues.
8 """
9
10 from __future__ import unicode_literals # __IGNORE_WARNING__
11
12 import os
13 import re
14 import tokenize
15 import io
16
17 from PyQt4.QtCore import QObject
18
19 from E5Gui import E5MessageBox
20
21 from . import pep8
22
23 import Utilities
24
25 FixableCodeStyleIssues = [
26 "D111", "D112", "D113", "D121", "D131", "D141",
27 "D142", "D143", "D144", "D145",
28 "D221", "D222", "D231", "D242", "D243", "D244",
29 "D245", "D246", "D247",
30 "E101", "E111", "E121", "E122", "E123", "E124",
31 "E125", "E126", "E127", "E128", "E133", "E201",
32 "E202", "E203", "E211", "E221", "E222", "E223",
33 "E224", "E225", "E226", "E227", "E228", "E231",
34 "E241", "E242", "E251", "E261", "E262", "E271",
35 "E272", "E273", "E274", "E301", "E302", "E303",
36 "E304", "E401", "E501", "E502", "E701", "E702",
37 "E703", "E711", "E712",
38 "N804", "N805", "N806",
39 "W191", "W291", "W292", "W293", "W391", "W603",
40 ]
41
42
43 class CodeStyleFixer(QObject):
44 """
45 Class implementing a fixer for certain code style issues.
46 """
47 def __init__(self, project, filename, sourceLines, fixCodes, noFixCodes,
48 maxLineLength, inPlace):
49 """
50 Constructor
51
52 @param project reference to the project object (Project)
53 @param filename name of the file to be fixed (string)
54 @param sourceLines list of source lines including eol marker
55 (list of string)
56 @param fixCodes list of codes to be fixed as a comma separated
57 string (string)
58 @param noFixCodes list of codes not to be fixed as a comma
59 separated string (string)
60 @param maxLineLength maximum allowed line length (integer)
61 @param inPlace flag indicating to modify the file in place (boolean)
62 """
63 super(CodeStyleFixer, self).__init__()
64
65 self.__project = project
66 self.__filename = filename
67 self.__origName = ""
68 self.__source = sourceLines[:] # save a copy
69 self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()]
70 self.__noFixCodes = [
71 c.strip() for c in noFixCodes.split(",") if c.strip()]
72 self.__maxLineLength = maxLineLength
73 self.fixed = 0
74
75 self.__reindenter = None
76 self.__eol = ""
77 self.__indentWord = self.__getIndentWord()
78
79 if not inPlace:
80 self.__origName = self.__filename
81 self.__filename = os.path.join(
82 os.path.dirname(self.__filename),
83 "fixed_" + os.path.basename(self.__filename))
84
85 self.__fixes = {
86 "D111": self.__fixD111,
87 "D112": self.__fixD112,
88 "D113": self.__fixD112,
89 "D121": self.__fixD121,
90 "D131": self.__fixD131,
91 "D141": self.__fixD141,
92 "D142": self.__fixD142,
93 "D143": self.__fixD143,
94 "D144": self.__fixD144,
95 "D145": self.__fixD145,
96 "D221": self.__fixD221,
97 "D222": self.__fixD221,
98 "D231": self.__fixD131,
99 "D242": self.__fixD242,
100 "D243": self.__fixD243,
101 "D244": self.__fixD242,
102 "D245": self.__fixD243,
103 "D246": self.__fixD144,
104 "D247": self.__fixD247,
105 "E101": self.__fixE101,
106 "E111": self.__fixE101,
107 "E121": self.__fixE121,
108 "E122": self.__fixE122,
109 "E123": self.__fixE123,
110 "E124": self.__fixE121,
111 "E125": self.__fixE125,
112 "E126": self.__fixE126,
113 "E127": self.__fixE127,
114 "E128": self.__fixE127,
115 "E133": self.__fixE126,
116 "E201": self.__fixE201,
117 "E202": self.__fixE201,
118 "E203": self.__fixE201,
119 "E211": self.__fixE201,
120 "E221": self.__fixE221,
121 "E222": self.__fixE221,
122 "E223": self.__fixE221,
123 "E224": self.__fixE221,
124 "E225": self.__fixE221,
125 "E226": self.__fixE221,
126 "E227": self.__fixE221,
127 "E228": self.__fixE221,
128 "E231": self.__fixE231,
129 "E241": self.__fixE221,
130 "E242": self.__fixE221,
131 "E251": self.__fixE251,
132 "E261": self.__fixE261,
133 "E262": self.__fixE261,
134 "E271": self.__fixE221,
135 "E272": self.__fixE221,
136 "E273": self.__fixE221,
137 "E274": self.__fixE221,
138 "E301": self.__fixE301,
139 "E302": self.__fixE302,
140 "E303": self.__fixE303,
141 "E304": self.__fixE304,
142 "E401": self.__fixE401,
143 "E501": self.__fixE501,
144 "E502": self.__fixE502,
145 "E701": self.__fixE701,
146 "E702": self.__fixE702,
147 "E703": self.__fixE702,
148 "E711": self.__fixE711,
149 "E712": self.__fixE711,
150 "N804": self.__fixN804,
151 "N805": self.__fixN804,
152 "N806": self.__fixN806,
153 "W191": self.__fixE101,
154 "W291": self.__fixW291,
155 "W292": self.__fixW292,
156 "W293": self.__fixW291,
157 "W391": self.__fixW391,
158 "W603": self.__fixW603,
159 }
160 self.__modified = False
161 self.__stackLogical = [] # these need to be fixed before the file
162 # is saved but after all other inline
163 # fixes. These work with logical lines.
164 self.__stack = [] # these need to be fixed before the file
165 # is saved but after all inline fixes
166
167 self.__multiLineNumbers = None
168 self.__docLineNumbers = None
169
170 self.__lastID = 0
171
172 def saveFile(self, encoding):
173 """
174 Public method to save the modified file.
175
176 @param encoding encoding of the source file (string)
177 @return flag indicating success (boolean)
178 """
179 if not self.__modified:
180 # no need to write
181 return True
182
183 txt = "".join(self.__source)
184 try:
185 Utilities.writeEncodedFile(self.__filename, txt, encoding)
186 except (IOError, Utilities.CodingError, UnicodeError) as err:
187 E5MessageBox.critical(
188 self,
189 self.trUtf8("Fix Code Style Issues"),
190 self.trUtf8(
191 """<p>Could not save the file <b>{0}</b>."""
192 """ Skipping it.</p><p>Reason: {1}</p>""")
193 .format(self.__filename, str(err))
194 )
195 return False
196
197 return True
198
199 def __codeMatch(self, code):
200 """
201 Private method to check, if the code should be fixed.
202
203 @param code to check (string)
204 @return flag indicating it should be fixed (boolean)
205 """
206 def mutualStartswith(a, b):
207 """
208 Local helper method to compare the beginnings of two strings
209 against each other.
210
211 @return flag indicating that one string starts with the other
212 (boolean)
213 """
214 return b.startswith(a) or a.startswith(b)
215
216 if self.__noFixCodes:
217 for noFixCode in [c.strip() for c in self.__noFixCodes]:
218 if mutualStartswith(code.lower(), noFixCode.lower()):
219 return False
220
221 if self.__fixCodes:
222 for fixCode in [c.strip() for c in self.__fixCodes]:
223 if mutualStartswith(code.lower(), fixCode.lower()):
224 return True
225 return False
226
227 return True
228
229 def fixIssue(self, line, pos, message):
230 """
231 Public method to fix the fixable issues.
232
233 @param line line number of issue (integer)
234 @param pos character position of issue (integer)
235 @param message message text (string)
236 @return value indicating an applied/deferred fix (-1, 0, 1),
237 a message for the fix (string) and an ID for a deferred
238 fix (integer)
239 """
240 code = message.split(None, 1)[0].strip()
241
242 if line <= len(self.__source) and \
243 self.__codeMatch(code) and \
244 code in self.__fixes:
245 res = self.__fixes[code](code, line, pos)
246 if res[0] == 1:
247 self.__modified = True
248 self.fixed += 1
249 else:
250 res = (0, "", 0)
251
252 return res
253
254 def finalize(self):
255 """
256 Public method to apply all deferred fixes.
257
258 @return dictionary containing the fix results
259 """
260 results = {}
261
262 # step 1: do fixes operating on logical lines first
263 for id_, code, line, pos in self.__stackLogical:
264 res, msg, _ = self.__fixes[code](code, line, pos, apply=True)
265 if res == 1:
266 self.__modified = True
267 self.fixed += 1
268 results[id_] = (res, msg)
269
270 # step 2: do fixes that change the number of lines
271 for id_, code, line, pos in reversed(self.__stack):
272 res, msg, _ = self.__fixes[code](code, line, pos, apply=True)
273 if res == 1:
274 self.__modified = True
275 self.fixed += 1
276 results[id_] = (res, msg)
277
278 return results
279
280 def __getID(self):
281 """
282 Private method to get the ID for a deferred fix.
283
284 @return ID for a deferred fix (integer)
285 """
286 self.__lastID += 1
287 return self.__lastID
288
289 def __getEol(self):
290 """
291 Private method to get the applicable eol string.
292
293 @return eol string (string)
294 """
295 if not self.__eol:
296 if self.__origName:
297 fn = self.__origName
298 else:
299 fn = self.__filename
300
301 if self.__project.isOpen() and self.__project.isProjectFile(fn):
302 self.__eol = self.__project.getEolString()
303 else:
304 self.__eol = Utilities.linesep()
305 return self.__eol
306
307 def __findLogical(self):
308 """
309 Private method to extract the index of all the starts and ends of
310 lines.
311
312 @return tuple containing two lists of integer with start and end tuples
313 of lines
314 """
315 logical_start = []
316 logical_end = []
317 last_newline = True
318 sio = io.StringIO("".join(self.__source))
319 parens = 0
320 for t in tokenize.generate_tokens(sio.readline):
321 if t[0] in [tokenize.COMMENT, tokenize.DEDENT,
322 tokenize.INDENT, tokenize.NL,
323 tokenize.ENDMARKER]:
324 continue
325 if not parens and t[0] in [tokenize.NEWLINE, tokenize.SEMI]:
326 last_newline = True
327 logical_end.append((t[3][0] - 1, t[2][1]))
328 continue
329 if last_newline and not parens:
330 logical_start.append((t[2][0] - 1, t[2][1]))
331 last_newline = False
332 if t[0] == tokenize.OP:
333 if t[1] in '([{':
334 parens += 1
335 elif t[1] in '}])':
336 parens -= 1
337 return logical_start, logical_end
338
339 def __getLogical(self, line, pos):
340 """
341 Private method to get the logical line corresponding to the given
342 position.
343
344 @param line line number of the issue (integer)
345 @param pos position inside line (integer)
346 @return tuple of a tuple of two integers giving the start of the
347 logical line, another tuple of two integers giving the end
348 of the logical line and a list of strings with the original
349 source lines
350 """
351 try:
352 (logical_start, logical_end) = self.__findLogical()
353 except (SyntaxError, tokenize.TokenError):
354 return None
355
356 line = line - 1
357 ls = None
358 le = None
359 for i in range(0, len(logical_start)):
360 x = logical_end[i]
361 if x[0] > line or (x[0] == line and x[1] > pos):
362 le = x
363 ls = logical_start[i]
364 break
365 if ls is None:
366 return None
367
368 original = self.__source[ls[0]:le[0] + 1]
369 return ls, le, original
370
371 def __getIndentWord(self):
372 """
373 Private method to determine the indentation type.
374
375 @return string to be used for an indentation (string)
376 """
377 sio = io.StringIO("".join(self.__source))
378 indentWord = " " # default in case of failure
379 try:
380 for token in tokenize.generate_tokens(sio.readline):
381 if token[0] == tokenize.INDENT:
382 indentWord = token[1]
383 break
384 except (SyntaxError, tokenize.TokenError):
385 pass
386 return indentWord
387
388 def __getIndent(self, line):
389 """
390 Private method to get the indentation string.
391
392 @param line line to determine the indentation string from (string)
393 @return indentation string (string)
394 """
395 return line.replace(line.lstrip(), "")
396
397 def __multilineStringLines(self):
398 """
399 Private method to determine the line numbers that are within multi line
400 strings and these which are part of a documentation string.
401
402 @return tuple of a set of line numbers belonging to a multi line
403 string and a set of line numbers belonging to a multi line
404 documentation string (tuple of two set of integer)
405 """
406 if self.__multiLineNumbers is None:
407 source = "".join(self.__source)
408 sio = io.StringIO(source)
409 self.__multiLineNumbers = set()
410 self.__docLineNumbers = set()
411 previousTokenType = ''
412 try:
413 for t in tokenize.generate_tokens(sio.readline):
414 tokenType = t[0]
415 startRow = t[2][0]
416 endRow = t[3][0]
417
418 if (tokenType == tokenize.STRING and startRow != endRow):
419 if previousTokenType != tokenize.INDENT:
420 self.__multiLineNumbers |= set(
421 range(startRow, 1 + endRow))
422 else:
423 self.__docLineNumbers |= set(
424 range(startRow, 1 + endRow))
425
426 previousTokenType = tokenType
427 except (SyntaxError, tokenize.TokenError):
428 pass
429
430 return self.__multiLineNumbers, self.__docLineNumbers
431
432 def __fixReindent(self, line, pos, logical):
433 """
434 Private method to fix a badly indented line.
435
436 This is done by adding or removing from its initial indent only.
437
438 @param line line number of the issue (integer)
439 @param pos position inside line (integer)
440 @param logical logical line structure
441 @return flag indicating a change was done (boolean)
442 """
443 assert logical
444 ls, _, original = logical
445
446 rewrapper = IndentationWrapper(original)
447 valid_indents = rewrapper.pep8Expected()
448 if not rewrapper.rel_indent:
449 return False
450
451 if line > ls[0]:
452 # got a valid continuation line number
453 row = line - ls[0] - 1
454 # always pick the first option for this
455 valid = valid_indents[row]
456 got = rewrapper.rel_indent[row]
457 else:
458 return False
459
460 line1 = ls[0] + row
461 # always pick the expected indent, for now.
462 indent_to = valid[0]
463
464 if got != indent_to:
465 orig_line = self.__source[line1]
466 new_line = ' ' * (indent_to) + orig_line.lstrip()
467 if new_line == orig_line:
468 return False
469 else:
470 self.__source[line1] = new_line
471 return True
472 else:
473 return False
474
475 def __fixWhitespace(self, line, offset, replacement):
476 """
477 Private method to correct whitespace at the given offset.
478
479 @param line line to be corrected (string)
480 @param offset offset within line (integer)
481 @param replacement replacement string (string)
482 @return corrected line
483 """
484 left = line[:offset].rstrip(" \t")
485 right = line[offset:].lstrip(" \t")
486 if right.startswith("#"):
487 return line
488 else:
489 return left + replacement + right
490
491 def __fixD111(self, code, line, pos):
492 """
493 Private method to fix docstring enclosed in wrong quotes.
494
495 Codes: D111
496
497 @param code code of the issue (string)
498 @param line line number of the issue (integer)
499 @param pos position inside line (integer)
500 @return value indicating an applied/deferred fix (-1, 0, 1),
501 a message for the fix (string) and an ID for a deferred
502 fix (integer)
503 """
504 line = line - 1
505 left, right = self.__source[line].split("'''", 1)
506 self.__source[line] = left + '"""' + right
507 while line < len(self.__source):
508 if self.__source[line].rstrip().endswith("'''"):
509 left, right = self.__source[line].rsplit("'''", 1)
510 self.__source[line] = left + '"""' + right
511 break
512 line += 1
513
514 return (
515 1,
516 self.trUtf8(
517 "Triple single quotes converted to triple double quotes."),
518 0)
519
520 def __fixD112(self, code, line, pos):
521 """
522 Private method to fix docstring 'r' or 'u' in leading quotes.
523
524 Codes: D112, D113
525
526 @param code code of the issue (string)
527 @param line line number of the issue (integer)
528 @param pos position inside line (integer)
529 @return value indicating an applied/deferred fix (-1, 0, 1),
530 a message for the fix (string) and an ID for a deferred
531 fix (integer)
532 """
533 line = line - 1
534 if code == "D112":
535 insertChar = "r"
536 elif code == "D113":
537 insertChar = "u"
538 else:
539 return (0, "", 0)
540
541 newText = self.__getIndent(self.__source[line]) + \
542 insertChar + self.__source[line].lstrip()
543 self.__source[line] = newText
544 return (
545 1,
546 self.trUtf8('Introductory quotes corrected to be {0}"""')
547 .format(insertChar),
548 0)
549
550 def __fixD121(self, code, line, pos, apply=False):
551 """
552 Private method to fix a single line docstring on multiple lines.
553
554 Codes: D121
555
556 @param code code of the issue (string)
557 @param line line number of the issue (integer)
558 @param pos position inside line (integer)
559 @keyparam apply flag indicating, that the fix should be applied
560 (boolean)
561 @return value indicating an applied/deferred fix (-1, 0, 1),
562 a message for the fix (string) and an ID for a deferred
563 fix (integer)
564 """
565 if apply:
566 line = line - 1
567 if not self.__source[line].lstrip().startswith(
568 ('"""', 'r"""', 'u"""')):
569 # only correctly formatted docstrings will be fixed
570 return (0, "", 0)
571
572 docstring = self.__source[line].rstrip() + \
573 self.__source[line + 1].strip()
574 if docstring.endswith('"""'):
575 docstring += self.__getEol()
576 else:
577 docstring += self.__source[line + 2].lstrip()
578 self.__source[line + 2] = ""
579
580 self.__source[line] = docstring
581 self.__source[line + 1] = ""
582 return (
583 1,
584 self.trUtf8("Single line docstring put on one line."),
585 0)
586 else:
587 id = self.__getID()
588 self.__stack.append((id, code, line, pos))
589 return (-1, "", id)
590
591 def __fixD131(self, code, line, pos):
592 """
593 Private method to fix a docstring summary not ending with a
594 period.
595
596 Codes: D131
597
598 @param code code of the issue (string)
599 @param line line number of the issue (integer)
600 @param pos position inside line (integer)
601 @return value indicating an applied/deferred fix (-1, 0, 1),
602 a message for the fix (string) and an ID for a deferred
603 fix (integer)
604 """
605 line = line - 1
606 newText = ""
607 if self.__source[line].rstrip().endswith(('"""', "'''")) and \
608 self.__source[line].lstrip().startswith(('"""', 'r"""', 'u"""')):
609 # it is a one-liner
610 newText = self.__source[line].rstrip()[:-3].rstrip() + "." + \
611 self.__source[line].rstrip()[-3:] + self.__getEol()
612 else:
613 if line < len(self.__source) - 1 and \
614 (not self.__source[line + 1].strip() or
615 self.__source[line + 1].lstrip().startswith("@") or
616 (self.__source[line + 1].strip() in ('"""', "'''") and
617 not self.__source[line].lstrip().startswith("@"))):
618 newText = self.__source[line].rstrip() + "." + self.__getEol()
619
620 if newText:
621 self.__source[line] = newText
622 return (1, self.trUtf8("Period added to summary line."), 0)
623 else:
624 return (0, "", 0)
625
626 def __fixD141(self, code, line, pos, apply=False):
627 """
628 Private method to fix a function/method docstring preceded by a
629 blank line.
630
631 Codes: D141
632
633 @param code code of the issue (string)
634 @param line line number of the issue (integer)
635 @param pos position inside line (integer)
636 @keyparam apply flag indicating, that the fix should be applied
637 (boolean)
638 @return value indicating an applied/deferred fix (-1, 0, 1),
639 a message for the fix (string) and an ID for a deferred
640 fix (integer)
641 """
642 if apply:
643 line = line - 1
644 self.__source[line - 1] = ""
645 return (
646 1,
647 self.trUtf8(
648 "Blank line before function/method docstring removed."),
649 0)
650 else:
651 id = self.__getID()
652 self.__stack.append((id, code, line, pos))
653 return (-1, "", id)
654
655 def __fixD142(self, code, line, pos, apply=False):
656 """
657 Private method to fix a class docstring not preceded by a
658 blank line.
659
660 Codes: D142
661
662 @param code code of the issue (string)
663 @param line line number of the issue (integer)
664 @param pos position inside line (integer)
665 @keyparam apply flag indicating, that the fix should be applied
666 (boolean)
667 @return value indicating an applied/deferred fix (-1, 0, 1),
668 a message for the fix (string) and an ID for a deferred
669 fix (integer)
670 """
671 if apply:
672 line = line - 1
673 self.__source[line] = self.__getEol() + self.__source[line]
674 return (
675 1,
676 self.trUtf8("Blank line inserted before class docstring."),
677 0)
678 else:
679 id = self.__getID()
680 self.__stack.append((id, code, line, pos))
681 return (-1, "", id)
682
683 def __fixD143(self, code, line, pos, apply=False):
684 """
685 Private method to fix a class docstring not followed by a
686 blank line.
687
688 Codes: D143
689
690 @param code code of the issue (string)
691 @param line line number of the issue (integer)
692 @param pos position inside line (integer)
693 @keyparam apply flag indicating, that the fix should be applied
694 (boolean)
695 @return value indicating an applied/deferred fix (-1, 0, 1),
696 a message for the fix (string) and an ID for a deferred
697 fix (integer)
698 """
699 if apply:
700 line = line - 1
701 self.__source[line] += self.__getEol()
702 return (
703 1,
704 self.trUtf8("Blank line inserted after class docstring."),
705 0)
706 else:
707 id = self.__getID()
708 self.__stack.append((id, code, line, pos))
709 return (-1, "", id)
710
711 def __fixD144(self, code, line, pos, apply=False):
712 """
713 Private method to fix a docstring summary not followed by a
714 blank line.
715
716 Codes: D144
717
718 @param code code of the issue (string)
719 @param line line number of the issue (integer)
720 @param pos position inside line (integer)
721 @keyparam apply flag indicating, that the fix should be applied
722 (boolean)
723 @return value indicating an applied/deferred fix (-1, 0, 1),
724 a message for the fix (string) and an ID for a deferred
725 fix (integer)
726 """
727 if apply:
728 line = line - 1
729 if not self.__source[line].rstrip().endswith("."):
730 # only correct summary lines can be fixed here
731 return (0, "", 0)
732
733 self.__source[line] += self.__getEol()
734 return (
735 1,
736 self.trUtf8("Blank line inserted after docstring summary."),
737 0)
738 else:
739 id = self.__getID()
740 self.__stack.append((id, code, line, pos))
741 return (-1, "", id)
742
743 def __fixD145(self, code, line, pos, apply=False):
744 """
745 Private method to fix the last paragraph of a multi-line docstring
746 not followed by a blank line.
747
748 Codes: D143
749
750 @param code code of the issue (string)
751 @param line line number of the issue (integer)
752 @param pos position inside line (integer)
753 @keyparam apply flag indicating, that the fix should be applied
754 (boolean)
755 @return value indicating an applied/deferred fix (-1, 0, 1),
756 a message for the fix (string) and an ID for a deferred
757 fix (integer)
758 """
759 if apply:
760 line = line - 1
761 self.__source[line] = self.__getEol() + self.__source[line]
762 return (
763 1,
764 self.trUtf8("Blank line inserted after last paragraph"
765 " of docstring."),
766 0)
767 else:
768 id = self.__getID()
769 self.__stack.append((id, code, line, pos))
770 return (-1, "", id)
771
772 def __fixD221(self, code, line, pos, apply=False):
773 """
774 Private method to fix leading and trailing quotes of docstring
775 not on separate lines.
776
777 Codes: D221, D222
778
779 @param code code of the issue (string)
780 @param line line number of the issue (integer)
781 @param pos position inside line (integer)
782 @keyparam apply flag indicating, that the fix should be applied
783 (boolean)
784 @return value indicating an applied/deferred fix (-1, 0, 1),
785 a message for the fix (string) and an ID for a deferred
786 fix (integer)
787 """
788 if apply:
789 line = line - 1
790 indent = self.__getIndent(self.__source[line])
791 source = self.__source[line].strip()
792 if code == "D221":
793 # leading
794 if source.startswith(("r", "u")):
795 first, second = source[:4], source[4:].strip()
796 else:
797 first, second = source[:3], source[3:].strip()
798 else:
799 # trailing
800 first, second = source[:-3].strip(), source[-3:]
801 newText = indent + first + self.__getEol() + \
802 indent + second + self.__getEol()
803 self.__source[line] = newText
804 if code == "D221":
805 msg = self.trUtf8("Leading quotes put on separate line.")
806 else:
807 msg = self.trUtf8("Trailing quotes put on separate line.")
808 return (1, msg, 0)
809 else:
810 id = self.__getID()
811 self.__stack.append((id, code, line, pos))
812 return (-1, "", id)
813
814 def __fixD242(self, code, line, pos, apply=False):
815 """
816 Private method to fix a class or function/method docstring preceded
817 by a blank line.
818
819 Codes: D242, D244
820
821 @param code code of the issue (string)
822 @param line line number of the issue (integer)
823 @param pos position inside line (integer)
824 @keyparam apply flag indicating, that the fix should be applied
825 (boolean)
826 @return value indicating an applied/deferred fix (-1, 0, 1),
827 a message for the fix (string) and an ID for a deferred
828 fix (integer)
829 """
830 if apply:
831 line = line - 1
832 self.__source[line - 1] = ""
833 if code == "D242":
834 msg = self.trUtf8("Blank line before class docstring removed.")
835 else:
836 msg = self.trUtf8(
837 "Blank line before function/method docstring removed.")
838 return (1, msg, 0)
839 else:
840 id = self.__getID()
841 self.__stack.append((id, code, line, pos))
842 return (-1, "", id)
843
844 def __fixD243(self, code, line, pos, apply=False):
845 """
846 Private method to fix a class or function/method docstring followed
847 by a blank line.
848
849 Codes: D243, D245
850
851 @param code code of the issue (string)
852 @param line line number of the issue (integer)
853 @param pos position inside line (integer)
854 @keyparam apply flag indicating, that the fix should be applied
855 (boolean)
856 @return value indicating an applied/deferred fix (-1, 0, 1),
857 a message for the fix (string) and an ID for a deferred
858 fix (integer)
859 """
860 if apply:
861 line = line - 1
862 self.__source[line + 1] = ""
863 if code == "D243":
864 msg = self.trUtf8("Blank line after class docstring removed.")
865 else:
866 msg = self.trUtf8(
867 "Blank line after function/method docstring removed.")
868 return (1, msg, 0)
869 else:
870 id = self.__getID()
871 self.__stack.append((id, code, line, pos))
872 return (-1, "", id)
873
874 def __fixD247(self, code, line, pos, apply=False):
875 """
876 Private method to fix a last paragraph of a docstring followed
877 by a blank line.
878
879 Codes: D247
880
881 @param code code of the issue (string)
882 @param line line number of the issue (integer)
883 @param pos position inside line (integer)
884 @keyparam apply flag indicating, that the fix should be applied
885 (boolean)
886 @return value indicating an applied/deferred fix (-1, 0, 1),
887 a message for the fix (string) and an ID for a deferred
888 fix (integer)
889 """
890 if apply:
891 line = line - 1
892 self.__source[line - 1] = ""
893 return (
894 1,
895 self.trUtf8("Blank line after last paragraph removed."),
896 0)
897 else:
898 id = self.__getID()
899 self.__stack.append((id, code, line, pos))
900 return (-1, "", id)
901
902 def __fixE101(self, code, line, pos):
903 """
904 Private method to fix obsolete tab usage and indentation errors.
905
906 Codes: E101, E111, W191
907
908 @param code code of the issue (string)
909 @param line line number of the issue (integer)
910 @param pos position inside line (integer)
911 @return value indicating an applied/deferred fix (-1, 0, 1),
912 a message for the fix (string) and an ID for a deferred
913 fix (integer)
914 """
915 if self.__reindenter is None:
916 self.__reindenter = Reindenter(self.__source)
917 self.__reindenter.run()
918 fixedLine = self.__reindenter.fixedLine(line - 1)
919 if fixedLine is not None and fixedLine != self.__source[line - 1]:
920 self.__source[line - 1] = fixedLine
921 if code in ["E101", "W191"]:
922 msg = self.trUtf8("Tab converted to 4 spaces.")
923 else:
924 msg = self.trUtf8(
925 "Indentation adjusted to be a multiple of four.")
926 return (1, msg, 0)
927 else:
928 return (0, "", 0)
929
930 def __fixE121(self, code, line, pos, apply=False):
931 """
932 Private method to fix the indentation of continuation lines and
933 closing brackets.
934
935 Codes: E121, E124
936
937 @param code code of the issue (string)
938 @param line line number of the issue (integer)
939 @param pos position inside line (integer)
940 @keyparam apply flag indicating, that the fix should be applied
941 (boolean)
942 @return value indicating an applied/deferred fix (-1, 0, 1),
943 a message for the fix (string) and an ID for a deferred
944 fix (integer)
945 """
946 if apply:
947 logical = self.__getLogical(line, pos)
948 if logical:
949 # Fix by adjusting initial indent level.
950 changed = self.__fixReindent(line, pos, logical)
951 if changed:
952 if code == "E121":
953 msg = self.trUtf8(
954 "Indentation of continuation line corrected.")
955 elif code == "E124":
956 msg = self.trUtf8(
957 "Indentation of closing bracket corrected.")
958 return (1, msg, 0)
959 return (0, "", 0)
960 else:
961 id = self.__getID()
962 self.__stackLogical.append((id, code, line, pos))
963 return (-1, "", id)
964
965 def __fixE122(self, code, line, pos, apply=False):
966 """
967 Private method to fix a missing indentation of continuation lines.
968
969 Codes: E122
970
971 @param code code of the issue (string)
972 @param line line number of the issue (integer)
973 @param pos position inside line (integer)
974 @keyparam apply flag indicating, that the fix should be applied
975 (boolean)
976 @return value indicating an applied/deferred fix (-1, 0, 1),
977 a message for the fix (string) and an ID for a deferred
978 fix (integer)
979 """
980 if apply:
981 logical = self.__getLogical(line, pos)
982 if logical:
983 # Fix by adding an initial indent.
984 modified = self.__fixReindent(line, pos, logical)
985 if not modified:
986 # fall back to simple method
987 line = line - 1
988 text = self.__source[line]
989 indentation = self.__getIndent(text)
990 self.__source[line] = indentation + \
991 self.__indentWord + text.lstrip()
992 return (
993 1,
994 self.trUtf8(
995 "Missing indentation of continuation line corrected."),
996 0)
997 return (0, "", 0)
998 else:
999 id = self.__getID()
1000 self.__stackLogical.append((id, code, line, pos))
1001 return (-1, "", id)
1002
1003 def __fixE123(self, code, line, pos, apply=False):
1004 """
1005 Private method to fix the indentation of a closing bracket lines.
1006
1007 Codes: E123
1008
1009 @param code code of the issue (string)
1010 @param line line number of the issue (integer)
1011 @param pos position inside line (integer)
1012 @keyparam apply flag indicating, that the fix should be applied
1013 (boolean)
1014 @return value indicating an applied/deferred fix (-1, 0, 1),
1015 a message for the fix (string) and an ID for a deferred
1016 fix (integer)
1017 """
1018 if apply:
1019 logical = self.__getLogical(line, pos)
1020 if logical:
1021 # Fix by deleting whitespace to the correct level.
1022 logicalLines = logical[2]
1023 row = line - 1
1024 text = self.__source[row]
1025 newText = self.__getIndent(logicalLines[0]) + text.lstrip()
1026 if newText == text:
1027 # fall back to slower method
1028 changed = self.__fixReindent(line, pos, logical)
1029 else:
1030 self.__source[row] = newText
1031 changed = True
1032 if changed:
1033 return (1, self.trUtf8(
1034 "Closing bracket aligned to opening bracket."),
1035 0)
1036 return (0, "", 0)
1037 else:
1038 id = self.__getID()
1039 self.__stackLogical.append((id, code, line, pos))
1040 return (-1, "", id)
1041
1042 def __fixE125(self, code, line, pos, apply=False):
1043 """
1044 Private method to fix the indentation of continuation lines not
1045 distinguishable from next logical line.
1046
1047 Codes: E125
1048
1049 @param code code of the issue (string)
1050 @param line line number of the issue (integer)
1051 @param pos position inside line (integer)
1052 @keyparam apply flag indicating, that the fix should be applied
1053 (boolean)
1054 @return value indicating an applied/deferred fix (-1, 0, 1),
1055 a message for the fix (string) and an ID for a deferred
1056 fix (integer)
1057 """
1058 if apply:
1059 logical = self.__getLogical(line, pos)
1060 if logical:
1061 # Fix by adjusting initial indent level.
1062 modified = self.__fixReindent(line, pos, logical)
1063 if not modified:
1064 row = line - 1
1065 text = self.__source[row]
1066 self.__source[row] = self.__getIndent(text) + \
1067 self.__indentWord + text.lstrip()
1068 return (1, self.trUtf8("Indentation level changed."), 0)
1069 return (0, "", 0)
1070 else:
1071 id = self.__getID()
1072 self.__stackLogical.append((id, code, line, pos))
1073 return (-1, "", id)
1074
1075 def __fixE126(self, code, line, pos, apply=False):
1076 """
1077 Private method to fix over-indented/under-indented hanging
1078 indentation.
1079
1080 Codes: E126, E133
1081
1082 @param code code of the issue (string)
1083 @param line line number of the issue (integer)
1084 @param pos position inside line (integer)
1085 @keyparam apply flag indicating, that the fix should be applied
1086 (boolean)
1087 @return value indicating an applied/deferred fix (-1, 0, 1),
1088 a message for the fix (string) and an ID for a deferred
1089 fix (integer)
1090 """
1091 if apply:
1092 logical = self.__getLogical(line, pos)
1093 if logical:
1094 # Fix by deleting whitespace to the left.
1095 logicalLines = logical[2]
1096 row = line - 1
1097 text = self.__source[row]
1098 newText = self.__getIndent(logicalLines[0]) + \
1099 self.__indentWord + text.lstrip()
1100 if newText == text:
1101 # fall back to slower method
1102 changed = self.__fixReindent(line, pos, logical)
1103 else:
1104 self.__source[row] = newText
1105 changed = True
1106 if changed:
1107 return (1, self.trUtf8(
1108 "Indentation level of hanging indentation changed."),
1109 0)
1110 return (0, "", 0)
1111 else:
1112 id = self.__getID()
1113 self.__stackLogical.append((id, code, line, pos))
1114 return (-1, "", id)
1115
1116 def __fixE127(self, code, line, pos, apply=False):
1117 """
1118 Private method to fix over/under indented lines.
1119
1120 Codes: E127, E128
1121
1122 @param code code of the issue (string)
1123 @param line line number of the issue (integer)
1124 @param pos position inside line (integer)
1125 @keyparam apply flag indicating, that the fix should be applied
1126 (boolean)
1127 @return value indicating an applied/deferred fix (-1, 0, 1),
1128 a message for the fix (string) and an ID for a deferred
1129 fix (integer)
1130 """
1131 if apply:
1132 logical = self.__getLogical(line, pos)
1133 if logical:
1134 # Fix by inserting/deleting whitespace to the correct level.
1135 logicalLines = logical[2]
1136 row = line - 1
1137 text = self.__source[row]
1138 newText = text
1139
1140 if logicalLines[0].rstrip().endswith('\\'):
1141 newText = self.__getIndent(logicalLines[0]) + \
1142 self.__indentWord + text.lstrip()
1143 else:
1144 startIndex = None
1145 for symbol in '([{':
1146 if symbol in logicalLines[0]:
1147 foundIndex = logicalLines[0].find(symbol) + 1
1148 if startIndex is None:
1149 startIndex = foundIndex
1150 else:
1151 startIndex = min(startIndex, foundIndex)
1152
1153 if startIndex is not None:
1154 newText = startIndex * ' ' + text.lstrip()
1155
1156 if newText == text:
1157 # fall back to slower method
1158 changed = self.__fixReindent(line, pos, logical)
1159 else:
1160 self.__source[row] = newText
1161 changed = True
1162 if changed:
1163 return (1, self.trUtf8("Visual indentation corrected."), 0)
1164 return (0, "", 0)
1165 else:
1166 id = self.__getID()
1167 self.__stackLogical.append((id, code, line, pos))
1168 return (-1, "", id)
1169
1170 def __fixE201(self, code, line, pos):
1171 """
1172 Private method to fix extraneous whitespace.
1173
1174 Codes: E201, E202, E203, E211
1175
1176 @param code code of the issue (string)
1177 @param line line number of the issue (integer)
1178 @param pos position inside line (integer)
1179 @return value indicating an applied/deferred fix (-1, 0, 1),
1180 a message for the fix (string) and an ID for a deferred
1181 fix (integer)
1182 """
1183 line = line - 1
1184 text = self.__source[line]
1185
1186 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'):
1187 return (0, "", 0)
1188
1189 newText = self.__fixWhitespace(text, pos, '')
1190 if newText == text:
1191 return (0, "", 0)
1192
1193 self.__source[line] = newText
1194 return (1, self.trUtf8("Extraneous whitespace removed."), 0)
1195
1196 def __fixE221(self, code, line, pos):
1197 """
1198 Private method to fix extraneous whitespace around operator or
1199 keyword.
1200
1201 Codes: E221, E222, E223, E224, E225, E226, E227, E228, E241,
1202 E242, E271, E272, E273, E274).
1203
1204 @param code code of the issue (string)
1205 @param line line number of the issue (integer)
1206 @param pos position inside line (integer)
1207 @return value indicating an applied/deferred fix (-1, 0, 1),
1208 a message for the fix (string) and an ID for a deferred
1209 fix (integer)
1210 """
1211 line = line - 1
1212 text = self.__source[line]
1213
1214 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'):
1215 return (0, "", 0)
1216
1217 newText = self.__fixWhitespace(text, pos, ' ')
1218 if newText == text:
1219 return (0, "", 0)
1220
1221 self.__source[line] = newText
1222 if code in ["E225", "E226", "E227", "E228"]:
1223 return (1, self.trUtf8("Missing whitespace added."), 0)
1224 else:
1225 return (1, self.trUtf8("Extraneous whitespace removed."), 0)
1226
1227 def __fixE231(self, code, line, pos):
1228 """
1229 Private method to fix missing whitespace after ',;:'.
1230
1231 Codes: E231
1232
1233 @param code code of the issue (string)
1234 @param line line number of the issue (integer)
1235 @param pos position inside line (integer)
1236 @return value indicating an applied/deferred fix (-1, 0, 1),
1237 a message for the fix (string) and an ID for a deferred
1238 fix (integer)
1239 """
1240 line = line - 1
1241 pos = pos + 1
1242 self.__source[line] = self.__source[line][:pos] + \
1243 " " + self.__source[line][pos:]
1244 return (1, self.trUtf8("Missing whitespace added."), 0)
1245
1246 def __fixE251(self, code, line, pos):
1247 """
1248 Private method to fix extraneous whitespace around keyword and
1249 default parameter equals.
1250
1251 Codes: E251
1252
1253 @param code code of the issue (string)
1254 @param line line number of the issue (integer)
1255 @param pos position inside line (integer)
1256 @return value indicating an applied/deferred fix (-1, 0, 1),
1257 a message for the fix (string) and an ID for a deferred
1258 fix (integer)
1259 """
1260 line = line - 1
1261 text = self.__source[line]
1262
1263 # This is necessary since pep8 sometimes reports columns that goes
1264 # past the end of the physical line. This happens in cases like,
1265 # foo(bar\n=None)
1266 col = min(pos, len(text) - 1)
1267 if text[col].strip():
1268 newText = text
1269 else:
1270 newText = text[:col].rstrip() + text[col:].lstrip()
1271
1272 # There could be an escaped newline
1273 #
1274 # def foo(a=\
1275 # 1)
1276 if newText.endswith(('=\\\n', '=\\\r\n', '=\\\r')):
1277 self.__source[line] = newText.rstrip("\n\r \t\\")
1278 self.__source[line + 1] = self.__source[line + 1].lstrip()
1279 else:
1280 self.__source[line] = newText
1281 return (1, self.trUtf8("Extraneous whitespace removed."), 0)
1282
1283 def __fixE261(self, code, line, pos):
1284 """
1285 Private method to fix whitespace before or after inline comment.
1286
1287 Codes: E261, E262
1288
1289 @param code code of the issue (string)
1290 @param line line number of the issue (integer)
1291 @param pos position inside line (integer)
1292 @return value indicating an applied/deferred fix (-1, 0, 1),
1293 a message for the fix (string) and an ID for a deferred
1294 fix (integer)
1295 """
1296 line = line - 1
1297 text = self.__source[line]
1298 left = text[:pos].rstrip(' \t#')
1299 right = text[pos:].lstrip(' \t#')
1300 newText = left + (" # " + right if right.strip() else right)
1301 self.__source[line] = newText
1302 return (1, self.trUtf8("Whitespace around comment sign corrected."), 0)
1303
1304 def __fixE301(self, code, line, pos, apply=False):
1305 """
1306 Private method to fix the need for one blank line.
1307
1308 Codes: E301
1309
1310 @param code code of the issue (string)
1311 @param line line number of the issue (integer)
1312 @param pos position inside line (integer)
1313 @keyparam apply flag indicating, that the fix should be applied
1314 (boolean)
1315 @return value indicating an applied/deferred fix (-1, 0, 1),
1316 a message for the fix (string) and an ID for a deferred
1317 fix (integer)
1318 """
1319 if apply:
1320 self.__source.insert(line - 1, self.__getEol())
1321 return (1, self.trUtf8("One blank line inserted."), 0)
1322 else:
1323 id = self.__getID()
1324 self.__stack.append((id, code, line, pos))
1325 return (-1, "", id)
1326
1327 def __fixE302(self, code, line, pos, apply=False):
1328 """
1329 Private method to fix the need for two blank lines.
1330
1331 Codes: E302
1332
1333 @param code code of the issue (string)
1334 @param line line number of the issue (integer)
1335 @param pos position inside line (integer)
1336 @keyparam apply flag indicating, that the fix should be applied
1337 (boolean)
1338 @return value indicating an applied/deferred fix (-1, 0, 1),
1339 a message for the fix (string) and an ID for a deferred
1340 fix (integer)
1341 """
1342 if apply:
1343 # count blank lines
1344 index = line - 1
1345 blanks = 0
1346 while index:
1347 if self.__source[index - 1].strip() == "":
1348 blanks += 1
1349 index -= 1
1350 else:
1351 break
1352 delta = blanks - 2
1353
1354 line -= 1
1355 if delta < 0:
1356 # insert blank lines (one or two)
1357 while delta < 0:
1358 self.__source.insert(line, self.__getEol())
1359 delta += 1
1360 changed = True
1361 elif delta > 0:
1362 # delete superfluous blank lines
1363 while delta > 0:
1364 del self.__source[line - 1]
1365 line -= 1
1366 delta -= 1
1367 changed = True
1368 else:
1369 changed = False
1370
1371 if changed:
1372 if delta < 0:
1373 msg = self.trUtf8(
1374 "%n blank line(s) inserted.", "", -delta)
1375 elif delta > 0:
1376 msg = self.trUtf8(
1377 "%n superfluous lines removed", "", delta)
1378 else:
1379 msg = ""
1380 return (1, msg, 0)
1381 return (0, "", 0)
1382 else:
1383 id = self.__getID()
1384 self.__stack.append((id, code, line, pos))
1385 return (-1, "", id)
1386
1387 def __fixE303(self, code, line, pos, apply=False):
1388 """
1389 Private method to fix superfluous blank lines.
1390
1391 Codes: E303
1392
1393 @param code code of the issue (string)
1394 @param line line number of the issue (integer)
1395 @param pos position inside line (integer)
1396 @keyparam apply flag indicating, that the fix should be applied
1397 (boolean)
1398 @return value indicating an applied/deferred fix (-1, 0, 1),
1399 a message for the fix (string) and an ID for a deferred
1400 fix (integer)
1401 """
1402 if apply:
1403 index = line - 3
1404 while index:
1405 if self.__source[index].strip() == "":
1406 del self.__source[index]
1407 index -= 1
1408 else:
1409 break
1410 return (1, self.trUtf8("Superfluous blank lines removed."), 0)
1411 else:
1412 id = self.__getID()
1413 self.__stack.append((id, code, line, pos))
1414 return (-1, "", id)
1415
1416 def __fixE304(self, code, line, pos, apply=False):
1417 """
1418 Private method to fix superfluous blank lines after a function
1419 decorator.
1420
1421 Codes: E304
1422
1423 @param code code of the issue (string)
1424 @param line line number of the issue (integer)
1425 @param pos position inside line (integer)
1426 @keyparam apply flag indicating, that the fix should be applied
1427 (boolean)
1428 @return value indicating an applied/deferred fix (-1, 0, 1),
1429 a message for the fix (string) and an ID for a deferred
1430 fix (integer)
1431 """
1432 if apply:
1433 index = line - 2
1434 while index:
1435 if self.__source[index].strip() == "":
1436 del self.__source[index]
1437 index -= 1
1438 else:
1439 break
1440 return (1, self.trUtf8(
1441 "Superfluous blank lines after function decorator removed."),
1442 0)
1443 else:
1444 id = self.__getID()
1445 self.__stack.append((id, code, line, pos))
1446 return (-1, "", id)
1447
1448 def __fixE401(self, code, line, pos, apply=False):
1449 """
1450 Private method to fix multiple imports on one line.
1451
1452 Codes: E401
1453
1454 @param code code of the issue (string)
1455 @param line line number of the issue (integer)
1456 @param pos position inside line (integer)
1457 @keyparam apply flag indicating, that the fix should be applied
1458 (boolean)
1459 @return value indicating an applied/deferred fix (-1, 0, 1),
1460 a message for the fix (string) and an ID for a deferred
1461 fix (integer)
1462 """
1463 if apply:
1464 line = line - 1
1465 text = self.__source[line]
1466 if not text.lstrip().startswith("import"):
1467 return (0, "", 0)
1468
1469 # pep8 (1.3.1) reports false positive if there is an import
1470 # statement followed by a semicolon and some unrelated
1471 # statement with commas in it.
1472 if ';' in text:
1473 return (0, "", 0)
1474
1475 newText = text[:pos].rstrip("\t ,") + self.__getEol() + \
1476 self.__getIndent(text) + "import " + text[pos:].lstrip("\t ,")
1477 self.__source[line] = newText
1478 return (1, self.trUtf8("Imports were put on separate lines."), 0)
1479 else:
1480 id = self.__getID()
1481 self.__stack.append((id, code, line, pos))
1482 return (-1, "", id)
1483
1484 def __fixE501(self, code, line, pos, apply=False):
1485 """
1486 Private method to fix the long lines by breaking them.
1487
1488 Codes: E501
1489
1490 @param code code of the issue (string)
1491 @param line line number of the issue (integer)
1492 @param pos position inside line (integer)
1493 @keyparam apply flag indicating, that the fix should be applied
1494 (boolean)
1495 @return value indicating an applied/deferred fix (-1, 0, 1),
1496 a message for the fix (string) and an ID for a deferred
1497 fix (integer)
1498 """
1499 if apply:
1500 multilineStringLines, docStringLines = \
1501 self.__multilineStringLines()
1502 isDocString = line in docStringLines
1503 line = line - 1
1504 text = self.__source[line]
1505 if line > 0:
1506 prevText = self.__source[line - 1]
1507 else:
1508 prevText = ""
1509 if line < len(self.__source) - 1:
1510 nextText = self.__source[line + 1]
1511 else:
1512 nextText = ""
1513 shortener = LineShortener(
1514 text, prevText, nextText,
1515 maxLength=self.__maxLineLength, eol=self.__getEol(),
1516 indentWord=self.__indentWord, isDocString=isDocString)
1517 changed, newText, newNextText = shortener.shorten()
1518 if changed:
1519 if newText != text:
1520 self.__source[line] = newText
1521 if newNextText and newNextText != nextText:
1522 if newNextText == " ":
1523 newNextText = ""
1524 self.__source[line + 1] = newNextText
1525 return (1, self.trUtf8("Long lines have been shortened."), 0)
1526 else:
1527 return (0, "", 0)
1528 else:
1529 id = self.__getID()
1530 self.__stack.append((id, code, line, pos))
1531 return (-1, "", id)
1532
1533 def __fixE502(self, code, line, pos):
1534 """
1535 Private method to fix redundant backslash within brackets.
1536
1537 Codes: E502
1538
1539 @param code code of the issue (string)
1540 @param line line number of the issue (integer)
1541 @param pos position inside line (integer)
1542 @return value indicating an applied/deferred fix (-1, 0, 1),
1543 a message for the fix (string) and an ID for a deferred
1544 fix (integer)
1545 """
1546 self.__source[line - 1] = \
1547 self.__source[line - 1].rstrip("\n\r \t\\") + self.__getEol()
1548 return (1, self.trUtf8("Redundant backslash in brackets removed."), 0)
1549
1550 def __fixE701(self, code, line, pos, apply=False):
1551 """
1552 Private method to fix colon-separated compound statements.
1553
1554 Codes: E701
1555
1556 @param code code of the issue (string)
1557 @param line line number of the issue (integer)
1558 @param pos position inside line (integer)
1559 @keyparam apply flag indicating, that the fix should be applied
1560 (boolean)
1561 @return value indicating an applied/deferred fix (-1, 0, 1),
1562 a message for the fix (string) and an ID for a deferred
1563 fix (integer)
1564 """
1565 if apply:
1566 line = line - 1
1567 text = self.__source[line]
1568 pos = pos + 1
1569
1570 newText = text[:pos] + self.__getEol() + self.__getIndent(text) + \
1571 self.__indentWord + text[pos:].lstrip("\n\r \t\\") + \
1572 self.__getEol()
1573 self.__source[line] = newText
1574 return (1, self.trUtf8("Compound statement corrected."), 0)
1575 else:
1576 id = self.__getID()
1577 self.__stack.append((id, code, line, pos))
1578 return (-1, "", id)
1579
1580 def __fixE702(self, code, line, pos, apply=False):
1581 """
1582 Private method to fix semicolon-separated compound statements.
1583
1584 Codes: E702, E703
1585
1586 @param code code of the issue (string)
1587 @param line line number of the issue (integer)
1588 @param pos position inside line (integer)
1589 @keyparam apply flag indicating, that the fix should be applied
1590 (boolean)
1591 @return value indicating an applied/deferred fix (-1, 0, 1),
1592 a message for the fix (string) and an ID for a deferred
1593 fix (integer)
1594 """
1595 if apply:
1596 line = line - 1
1597 text = self.__source[line]
1598
1599 if text.rstrip().endswith("\\"):
1600 # normalize '1; \\\n2' into '1; 2'
1601 self.__source[line] = text.rstrip("\n\r \t\\")
1602 self.__source[line + 1] = self.__source[line + 1].lstrip()
1603 elif text.rstrip().endswith(";"):
1604 self.__source[line] = text.rstrip("\n\r \t;") + self.__getEol()
1605 else:
1606 first = text[:pos].rstrip("\n\r \t;") + self.__getEol()
1607 second = text[pos:].lstrip("\n\r \t;")
1608 self.__source[line] = first + self.__getIndent(text) + second
1609 return (1, self.trUtf8("Compound statement corrected."), 0)
1610 else:
1611 id = self.__getID()
1612 self.__stack.append((id, code, line, pos))
1613 return (-1, "", id)
1614
1615 def __fixE711(self, code, line, pos):
1616 """
1617 Private method to fix comparison with None.
1618
1619 Codes: E711, E712
1620
1621 @param code code of the issue (string)
1622 @param line line number of the issue (integer)
1623 @param pos position inside line (integer)
1624 @return value indicating an applied/deferred fix (-1, 0, 1),
1625 a message for the fix (string) and an ID for a deferred
1626 fix (integer)
1627 """
1628 line = line - 1
1629 text = self.__source[line]
1630
1631 rightPos = pos + 2
1632 if rightPos >= len(text):
1633 return (0, "", 0)
1634
1635 left = text[:pos].rstrip()
1636 center = text[pos:rightPos]
1637 right = text[rightPos:].lstrip()
1638
1639 if not right.startswith(("None", "True", "False")):
1640 return (0, "", 0)
1641
1642 if center.strip() == "==":
1643 center = "is"
1644 elif center.strip() == "!=":
1645 center = "is not"
1646 else:
1647 return (0, "", 0)
1648
1649 self.__source[line] = " ".join([left, center, right])
1650 return (1, self.trUtf8("Comparison to None/True/False corrected."), 0)
1651
1652 def __fixN804(self, code, line, pos, apply=False):
1653 """
1654 Private method to fix a wrong first argument of normal and
1655 class methods.
1656
1657 Codes: N804, N805
1658
1659 @param code code of the issue (string)
1660 @param line line number of the issue (integer)
1661 @param pos position inside line (integer)
1662 @keyparam apply flag indicating, that the fix should be applied
1663 (boolean)
1664 @return value indicating an applied/deferred fix (-1, 0, 1),
1665 a message for the fix (string) and an ID for a deferred
1666 fix (integer)
1667 """
1668 if apply:
1669 line = line - 1
1670 text = self.__source[line]
1671 if code == "N804":
1672 arg = "cls"
1673 else:
1674 arg = "self"
1675
1676 if text.rstrip().endswith("("):
1677 newText = text + self.__getIndent(text) + \
1678 self.__indentWord + arg + "," + self.__getEol()
1679 else:
1680 index = text.find("(") + 1
1681 left = text[:index]
1682 right = text[index:]
1683 if right.startswith(")"):
1684 center = arg
1685 else:
1686 center = arg + ", "
1687 newText = left + center + right
1688 self.__source[line] = newText
1689 return (1, self.trUtf8("'{0}' argument added.").format(arg), 0)
1690 else:
1691 id = self.__getID()
1692 self.__stack.append((id, code, line, pos))
1693 return (-1, "", id)
1694
1695 def __fixN806(self, code, line, pos, apply=False):
1696 """
1697 Private method to fix a wrong first argument of static methods.
1698
1699 Codes: N806
1700
1701 @param code code of the issue (string)
1702 @param line line number of the issue (integer)
1703 @param pos position inside line (integer)
1704 @keyparam apply flag indicating, that the fix should be applied
1705 (boolean)
1706 @return value indicating an applied/deferred fix (-1, 0, 1),
1707 a message for the fix (string) and an ID for a deferred
1708 fix (integer)
1709 """
1710 if apply:
1711 line = line - 1
1712 text = self.__source[line]
1713 index = text.find("(") + 1
1714 left = text[:index]
1715 right = text[index:]
1716
1717 if right.startswith(("cls", "self")):
1718 # cls or self are on the definition line
1719 if right.startswith("cls"):
1720 right = right[3:]
1721 arg = "cls"
1722 else:
1723 right = right[4:]
1724 arg = "self"
1725 right = right.lstrip(", ")
1726 newText = left + right
1727 self.__source[line] = newText
1728 else:
1729 # they are on the next line
1730 line = line + 1
1731 text = self.__source[line]
1732 indent = self.__getIndent(text)
1733 right = text.lstrip()
1734 if right.startswith("cls"):
1735 right = right[3:]
1736 arg = "cls"
1737 else:
1738 right = right[4:]
1739 arg = "self"
1740 right = right.lstrip(", ")
1741 if right.startswith("):"):
1742 # merge with previous line
1743 self.__source[line - 1] = \
1744 self.__source[line - 1].rstrip() + right
1745 self.__source[line] = ""
1746 else:
1747 self.__source[line] = indent + right
1748
1749 return (1, self.trUtf8("'{0}' argument removed.").format(arg), 0)
1750 else:
1751 id = self.__getID()
1752 self.__stack.append((id, code, line, pos))
1753 return (-1, "", id)
1754
1755 def __fixW291(self, code, line, pos):
1756 """
1757 Private method to fix trailing whitespace.
1758
1759 Codes: W291, W293
1760
1761 @param code code of the issue (string)
1762 @param line line number of the issue (integer)
1763 @param pos position inside line (integer)
1764 @return value indicating an applied/deferred fix (-1, 0, 1),
1765 a message for the fix (string) and an ID for a deferred
1766 fix (integer)
1767 """
1768 self.__source[line - 1] = re.sub(r'[\t ]+(\r?)$', r"\1",
1769 self.__source[line - 1])
1770 return (1, self.trUtf8("Whitespace stripped from end of line."), 0)
1771
1772 def __fixW292(self, code, line, pos):
1773 """
1774 Private method to fix a missing newline at the end of file.
1775
1776 Codes: W292
1777
1778 @param code code of the issue (string)
1779 @param line line number of the issue (integer)
1780 @param pos position inside line (integer)
1781 @return value indicating an applied/deferred fix (-1, 0, 1),
1782 a message for the fix (string) and an ID for a deferred
1783 fix (integer)
1784 """
1785 self.__source[line - 1] += self.__getEol()
1786 return (1, self.trUtf8("newline added to end of file."), 0)
1787
1788 def __fixW391(self, code, line, pos):
1789 """
1790 Private method to fix trailing blank lines.
1791
1792 Codes: W391
1793
1794 @param code code of the issue (string)
1795 @param line line number of the issue (integer)
1796 @param pos position inside line (integer)
1797 @return value indicating an applied/deferred fix (-1, 0, 1),
1798 a message for the fix (string) and an ID for a deferred
1799 fix (integer)
1800 """
1801 index = line - 1
1802 while index:
1803 if self.__source[index].strip() == "":
1804 del self.__source[index]
1805 index -= 1
1806 else:
1807 break
1808 return (1, self.trUtf8(
1809 "Superfluous trailing blank lines removed from end of file."), 0)
1810
1811 def __fixW603(self, code, line, pos):
1812 """
1813 Private method to fix the not equal notation.
1814
1815 Codes: W603
1816
1817 @param code code of the issue (string)
1818 @param line line number of the issue (integer)
1819 @param pos position inside line (integer)
1820 @return value indicating an applied/deferred fix (-1, 0, 1),
1821 a message for the fix (string) and an ID for a deferred
1822 fix (integer)
1823 """
1824 self.__source[line - 1] = self.__source[line - 1].replace("<>", "!=")
1825 return (1, self.trUtf8("'<>' replaced by '!='."), 0)
1826
1827
1828 class Reindenter(object):
1829 """
1830 Class to reindent badly-indented code to uniformly use four-space
1831 indentation.
1832
1833 Released to the public domain, by Tim Peters, 03 October 2000.
1834 """
1835 def __init__(self, sourceLines):
1836 """
1837 Constructor
1838
1839 @param sourceLines list of source lines including eol marker
1840 (list of string)
1841 """
1842 # Raw file lines.
1843 self.raw = sourceLines
1844 self.after = []
1845
1846 # File lines, rstripped & tab-expanded. Dummy at start is so
1847 # that we can use tokenize's 1-based line numbering easily.
1848 # Note that a line is all-blank iff it's "\n".
1849 self.lines = [line.rstrip().expandtabs() + "\n"
1850 for line in self.raw]
1851 self.lines.insert(0, None)
1852 self.index = 1 # index into self.lines of next line
1853
1854 # List of (lineno, indentlevel) pairs, one for each stmt and
1855 # comment line. indentlevel is -1 for comment lines, as a
1856 # signal that tokenize doesn't know what to do about them;
1857 # indeed, they're our headache!
1858 self.stats = []
1859
1860 def run(self):
1861 """
1862 Public method to run the re-indenter.
1863
1864 @return flag indicating that a change was done (boolean)
1865 """
1866 try:
1867 stats = self.__genStats(tokenize.generate_tokens(self.getline))
1868 except (SyntaxError, tokenize.TokenError):
1869 return False
1870
1871 # Remove trailing empty lines.
1872 lines = self.lines
1873 while lines and lines[-1] == "\n":
1874 lines.pop()
1875 # Sentinel.
1876 stats.append((len(lines), 0))
1877 # Map count of leading spaces to # we want.
1878 have2want = {}
1879 # Program after transformation.
1880 after = self.after = []
1881 # Copy over initial empty lines -- there's nothing to do until
1882 # we see a line with *something* on it.
1883 i = stats[0][0]
1884 after.extend(lines[1:i])
1885 for i in range(len(stats) - 1):
1886 thisstmt, thislevel = stats[i]
1887 nextstmt = stats[i + 1][0]
1888 have = self.__getlspace(lines[thisstmt])
1889 want = thislevel * 4
1890 if want < 0:
1891 # A comment line.
1892 if have:
1893 # An indented comment line. If we saw the same
1894 # indentation before, reuse what it most recently
1895 # mapped to.
1896 want = have2want.get(have, -1)
1897 if want < 0:
1898 # Then it probably belongs to the next real stmt.
1899 for j in range(i + 1, len(stats) - 1):
1900 jline, jlevel = stats[j]
1901 if jlevel >= 0:
1902 if have == self.__getlspace(lines[jline]):
1903 want = jlevel * 4
1904 break
1905 if want < 0: # Maybe it's a hanging
1906 # comment like this one,
1907 # in which case we should shift it like its base
1908 # line got shifted.
1909 for j in range(i - 1, -1, -1):
1910 jline, jlevel = stats[j]
1911 if jlevel >= 0:
1912 want = \
1913 have + \
1914 self.__getlspace(after[jline - 1]) - \
1915 self.__getlspace(lines[jline])
1916 break
1917 if want < 0:
1918 # Still no luck -- leave it alone.
1919 want = have
1920 else:
1921 want = 0
1922 assert want >= 0
1923 have2want[have] = want
1924 diff = want - have
1925 if diff == 0 or have == 0:
1926 after.extend(lines[thisstmt:nextstmt])
1927 else:
1928 for line in lines[thisstmt:nextstmt]:
1929 if diff > 0:
1930 if line == "\n":
1931 after.append(line)
1932 else:
1933 after.append(" " * diff + line)
1934 else:
1935 remove = min(self.__getlspace(line), -diff)
1936 after.append(line[remove:])
1937 return self.raw != self.after
1938
1939 def fixedLine(self, line):
1940 """
1941 Public method to get a fixed line.
1942
1943 @param line number of the line to retrieve (integer)
1944 @return fixed line (string)
1945 """
1946 if line < len(self.after):
1947 return self.after[line]
1948
1949 def getline(self):
1950 """
1951 Public method to get a line of text for tokenize.
1952
1953 @return line of text (string)
1954 """
1955 if self.index >= len(self.lines):
1956 line = ""
1957 else:
1958 line = self.lines[self.index]
1959 self.index += 1
1960 return line
1961
1962 def __genStats(self, tokens):
1963 """
1964 Private method to generate the re-indent statistics.
1965
1966 @param tokens tokens generator (tokenize._tokenize)
1967 @return reference to the generated statistics
1968 """
1969 find_stmt = True # next token begins a fresh stmt?
1970 level = 0 # current indent level
1971 stats = []
1972
1973 for t in tokens:
1974 token_type = t[0]
1975 sline = t[2][0]
1976 line = t[4]
1977
1978 if token_type == tokenize.NEWLINE:
1979 # A program statement, or ENDMARKER, will eventually follow,
1980 # after some (possibly empty) run of tokens of the form
1981 # (NL | COMMENT)* (INDENT | DEDENT+)?
1982 self.find_stmt = True
1983
1984 elif token_type == tokenize.INDENT:
1985 find_stmt = True
1986 level += 1
1987
1988 elif token_type == tokenize.DEDENT:
1989 find_stmt = True
1990 level -= 1
1991
1992 elif token_type == tokenize.COMMENT:
1993 if find_stmt:
1994 stats.append((sline, -1))
1995 # but we're still looking for a new stmt, so leave
1996 # find_stmt alone
1997
1998 elif token_type == tokenize.NL:
1999 pass
2000
2001 elif find_stmt:
2002 # This is the first "real token" following a NEWLINE, so it
2003 # must be the first token of the next program statement, or an
2004 # ENDMARKER.
2005 find_stmt = False
2006 if line: # not endmarker
2007 stats.append((sline, level))
2008
2009 return stats
2010
2011 def __getlspace(self, line):
2012 """
2013 Private method to count number of leading blanks.
2014
2015 @param line line to check (string)
2016 @return number of leading blanks (integer)
2017 """
2018 i = 0
2019 n = len(line)
2020 while i < n and line[i] == " ":
2021 i += 1
2022 return i
2023
2024
2025 class IndentationWrapper(object):
2026 """
2027 Class used by fixers dealing with indentation.
2028
2029 Each instance operates on a single logical line.
2030 """
2031 SKIP_TOKENS = frozenset([
2032 tokenize.COMMENT, tokenize.NL, tokenize.INDENT,
2033 tokenize.DEDENT, tokenize.NEWLINE, tokenize.ENDMARKER
2034 ])
2035
2036 def __init__(self, physical_lines):
2037 """
2038 Constructor
2039
2040 @param physical_lines list of physical lines to operate on
2041 (list of strings)
2042 """
2043 self.lines = physical_lines
2044 self.tokens = []
2045 self.rel_indent = None
2046 sio = io.StringIO(''.join(physical_lines))
2047 for t in tokenize.generate_tokens(sio.readline):
2048 if not len(self.tokens) and t[0] in self.SKIP_TOKENS:
2049 continue
2050 if t[0] != tokenize.ENDMARKER:
2051 self.tokens.append(t)
2052
2053 self.logical_line = self.__buildTokensLogical(self.tokens)
2054
2055 def __buildTokensLogical(self, tokens):
2056 """
2057 Private method to build a logical line from a list of tokens.
2058
2059 @param tokens list of tokens as generated by tokenize.generate_tokens
2060 @return logical line (string)
2061 """
2062 # from pep8.py with minor modifications
2063 logical = []
2064 previous = None
2065 for t in tokens:
2066 token_type, text = t[0:2]
2067 if token_type in self.SKIP_TOKENS:
2068 continue
2069 if previous:
2070 end_line, end = previous[3]
2071 start_line, start = t[2]
2072 if end_line != start_line: # different row
2073 prev_text = self.lines[end_line - 1][end - 1]
2074 if prev_text == ',' or (prev_text not in '{[('
2075 and text not in '}])'):
2076 logical.append(' ')
2077 elif end != start: # different column
2078 fill = self.lines[end_line - 1][end:start]
2079 logical.append(fill)
2080 logical.append(text)
2081 previous = t
2082 logical_line = ''.join(logical)
2083 assert logical_line.lstrip() == logical_line
2084 assert logical_line.rstrip() == logical_line
2085 return logical_line
2086
2087 def pep8Expected(self):
2088 """
2089 Public method to replicate logic in pep8.py, to know what level to
2090 indent things to.
2091
2092 @return list of lists, where each list represents valid indent levels
2093 for the line in question, relative from the initial indent. However,
2094 the first entry is the indent level which was expected.
2095 """
2096 # What follows is an adjusted version of
2097 # pep8.py:continuation_line_indentation. All of the comments have been
2098 # stripped and the 'yield' statements replaced with 'pass'.
2099 if not self.tokens:
2100 return
2101
2102 first_row = self.tokens[0][2][0]
2103 nrows = 1 + self.tokens[-1][2][0] - first_row
2104
2105 # here are the return values
2106 valid_indents = [list()] * nrows
2107 indent_level = self.tokens[0][2][1]
2108 valid_indents[0].append(indent_level)
2109
2110 if nrows == 1:
2111 # bug, really.
2112 return valid_indents
2113
2114 indent_next = self.logical_line.endswith(':')
2115
2116 row = depth = 0
2117 parens = [0] * nrows
2118 self.rel_indent = rel_indent = [0] * nrows
2119 indent = [indent_level]
2120 indent_chances = {}
2121 last_indent = (0, 0)
2122 last_token_multiline = None
2123
2124 for token_type, text, start, end, line in self.tokens:
2125 newline = row < start[0] - first_row
2126 if newline:
2127 row = start[0] - first_row
2128 newline = (not last_token_multiline and
2129 token_type not in (tokenize.NL, tokenize.NEWLINE))
2130
2131 if newline:
2132 # This is where the differences start. Instead of looking at
2133 # the line and determining whether the observed indent matches
2134 # our expectations, we decide which type of indentation is in
2135 # use at the given indent level, and return the offset. This
2136 # algorithm is susceptible to "carried errors", but should
2137 # through repeated runs eventually solve indentation for
2138 # multiline expressions.
2139
2140 if depth:
2141 for open_row in range(row - 1, -1, -1):
2142 if parens[open_row]:
2143 break
2144 else:
2145 open_row = 0
2146
2147 # That's all we get to work with. This code attempts to
2148 # "reverse" the below logic, and place into the valid indents
2149 # list
2150 vi = []
2151 add_second_chances = False
2152 if token_type == tokenize.OP and text in ']})':
2153 # this line starts with a closing bracket, so it needs to
2154 # be closed at the same indent as the opening one.
2155 if indent[depth]:
2156 # hanging indent
2157 vi.append(indent[depth])
2158 else:
2159 # visual indent
2160 vi.append(indent_level + rel_indent[open_row])
2161 elif depth and indent[depth]:
2162 # visual indent was previously confirmed.
2163 vi.append(indent[depth])
2164 add_second_chances = True
2165 elif depth and True in indent_chances.values():
2166 # visual indent happened before, so stick to
2167 # visual indent this time.
2168 if depth > 1 and indent[depth - 1]:
2169 vi.append(indent[depth - 1])
2170 else:
2171 # stupid fallback
2172 vi.append(indent_level + 4)
2173 add_second_chances = True
2174 elif not depth:
2175 vi.append(indent_level + 4)
2176 else:
2177 # must be in hanging indent
2178 hang = rel_indent[open_row] + 4
2179 vi.append(indent_level + hang)
2180
2181 # about the best we can do without look-ahead
2182 if (indent_next and vi[0] == indent_level + 4 and
2183 nrows == row + 1):
2184 vi[0] += 4
2185
2186 if add_second_chances:
2187 # visual indenters like to line things up.
2188 min_indent = vi[0]
2189 for col, what in indent_chances.items():
2190 if col > min_indent and (
2191 what is True or
2192 (what == str and token_type == tokenize.STRING) or
2193 (what == text and token_type == tokenize.OP)
2194 ):
2195 vi.append(col)
2196 vi = sorted(vi)
2197
2198 valid_indents[row] = vi
2199
2200 # Returning to original continuation_line_indentation() from
2201 # pep8.
2202 visual_indent = indent_chances.get(start[1])
2203 last_indent = start
2204 rel_indent[row] = pep8.expand_indent(line) - indent_level
2205 hang = rel_indent[row] - rel_indent[open_row]
2206
2207 if token_type == tokenize.OP and text in ']})':
2208 pass
2209 elif visual_indent is True:
2210 if not indent[depth]:
2211 indent[depth] = start[1]
2212
2213 # line altered: comments shouldn't define a visual indent
2214 if parens[row] and not indent[depth] and token_type not in (
2215 tokenize.NL, tokenize.COMMENT
2216 ):
2217 indent[depth] = start[1]
2218 indent_chances[start[1]] = True
2219 elif token_type == tokenize.STRING or text in (
2220 'u', 'ur', 'b', 'br'
2221 ):
2222 indent_chances[start[1]] = str
2223
2224 if token_type == tokenize.OP:
2225 if text in '([{':
2226 depth += 1
2227 indent.append(0)
2228 parens[row] += 1
2229 elif text in ')]}' and depth > 0:
2230 prev_indent = indent.pop() or last_indent[1]
2231 for d in range(depth):
2232 if indent[d] > prev_indent:
2233 indent[d] = 0
2234 for ind in list(indent_chances):
2235 if ind >= prev_indent:
2236 del indent_chances[ind]
2237 depth -= 1
2238 if depth and indent[depth]: # modified
2239 indent_chances[indent[depth]] = True
2240 for idx in range(row, -1, -1):
2241 if parens[idx]:
2242 parens[idx] -= 1
2243 break
2244 assert len(indent) == depth + 1
2245 if start[1] not in indent_chances:
2246 indent_chances[start[1]] = text
2247
2248 last_token_multiline = (start[0] != end[0])
2249
2250 return valid_indents
2251
2252
2253 class LineShortener(object):
2254 """
2255 Class used to shorten lines to a given maximum of characters.
2256 """
2257 def __init__(self, curLine, prevLine, nextLine, maxLength=79, eol="\n",
2258 indentWord=" ", isDocString=False):
2259 """
2260 Constructor
2261
2262 @param curLine text to work on (string)
2263 @param prevLine line before the text to work on (string)
2264 @param nextLine line after the text to work on (string)
2265 @keyparam maxLength maximum allowed line length (integer)
2266 @keyparam eol eond-of-line marker (string)
2267 @keyparam indentWord string used for indentation (string)
2268 @keyparam isDocString flag indicating that the line belongs to
2269 a documentation string (boolean)
2270 """
2271 self.__text = curLine
2272 self.__prevText = prevLine
2273 self.__nextText = nextLine
2274 self.__maxLength = maxLength
2275 self.__eol = eol
2276 self.__indentWord = indentWord
2277 self.__isDocString = isDocString
2278
2279 def shorten(self):
2280 """
2281 Public method to shorten the line wrapped by the class instance.
2282
2283 @return tuple of a flag indicating successful shortening, the
2284 shortened line and the changed next line (boolean, string, string)
2285 """
2286 # 1. check for comment
2287 if self.__text.lstrip().startswith('#'):
2288 lastComment = True
2289 if self.__nextText.lstrip().startswith('#'):
2290 lastComment = False
2291
2292 # Wrap commented lines.
2293 newText = self.__shortenComment(lastComment)
2294 if newText == self.__text:
2295 return False, "", ""
2296 else:
2297 return True, newText, ""
2298 elif '#' in self.__text:
2299 pos = self.__text.rfind("#")
2300 newText = self.__text[:pos].rstrip() + self.__eol + \
2301 self.__getIndent(self.__text) + self.__text[pos:]
2302 if newText == self.__text:
2303 return False, "", ""
2304 else:
2305 return True, newText, ""
2306
2307 # Do multi line doc strings
2308 if self.__isDocString:
2309 source = self.__text.rstrip()
2310 blank = source.rfind(" ")
2311 while blank > self.__maxLength and blank != -1:
2312 blank = source.rfind(" ", 0, blank)
2313 if blank == -1:
2314 # Cannot break
2315 return False, "", ""
2316 else:
2317 first = self.__text[:blank]
2318 second = self.__text[blank:].lstrip()
2319 if self.__nextText.strip():
2320 if self.__nextText.lstrip().startswith("@"):
2321 # eric doc comment
2322 # create a new line and indent it
2323 newText = first + self.__eol + \
2324 self.__getIndent(first) + self.__indentWord + \
2325 second
2326 newNext = ""
2327 else:
2328 newText = first + self.__eol
2329 newNext = self.__getIndent(self.__nextText) + \
2330 second.rstrip() + " " + self.__nextText.lstrip()
2331 else:
2332 # empty line, add a new line
2333 newText = first + self.__eol + self.__getIndent(first) + \
2334 second
2335 newNext = ""
2336 return True, newText, newNext
2337
2338 indent = self.__getIndent(self.__text)
2339 source = self.__text[len(indent):]
2340 assert source.lstrip() == source
2341 sio = io.StringIO(source)
2342
2343 # Check for multi line string.
2344 try:
2345 tokens = list(tokenize.generate_tokens(sio.readline))
2346 except (SyntaxError, tokenize.TokenError):
2347 if source.rstrip().endswith("\\"):
2348 # just join the continuation line and let the next run
2349 # handle it once it tokenizes ok
2350 newText = indent + source.rstrip()[:-1].rstrip() + " " + \
2351 self.__nextText.lstrip()
2352 if indent:
2353 newNext = indent
2354 else:
2355 newNext = " "
2356 return True, newText, newNext
2357 else:
2358 multilineCandidate = self.__breakMultiline()
2359 if multilineCandidate:
2360 return True, multilineCandidate[0], multilineCandidate[1]
2361 else:
2362 return False, "", ""
2363
2364 # Handle statements by putting the right hand side on a line by itself.
2365 # This should let the next pass shorten it.
2366 if source.startswith('return '):
2367 newText = (
2368 indent +
2369 'return (' +
2370 self.__eol +
2371 indent + self.__indentWord + re.sub('^return ', '', source) +
2372 indent + ')' + self.__eol
2373 )
2374 return True, newText, ""
2375
2376 candidates = self.__shortenLine(tokens, source, indent)
2377 if candidates:
2378 candidates = list(sorted(
2379 set(candidates).union([self.__text]),
2380 key=lambda x: self.__lineShorteningRank(x)))
2381 if candidates[0] == self.__text:
2382 return False, "", ""
2383 return True, candidates[0], ""
2384
2385 source = self.__text
2386 rs = source.rstrip()
2387 if rs.endswith(("'", '"')) and " " in source:
2388 if rs.endswith(('"""', "'''")):
2389 quote = rs[-3:]
2390 else:
2391 quote = rs[-1]
2392 blank = source.rfind(" ")
2393 maxLen = self.__maxLength - 2 - len(quote)
2394 while blank > maxLen and blank != -1:
2395 blank = source.rfind(" ", 0, blank)
2396 if blank != -1:
2397 if source[blank + 1:].startswith(quote):
2398 first = source[:maxLen]
2399 second = source[maxLen:]
2400 else:
2401 first = source[:blank]
2402 second = source[blank + 1:]
2403 return (
2404 True,
2405 first + quote + " \\" + self.__eol +
2406 indent + self.__indentWord + quote + second,
2407 "")
2408 else:
2409 # Cannot break
2410 return False, "", ""
2411
2412 return False, "", ""
2413
2414 def __shortenComment(self, isLast):
2415 """
2416 Private method to shorten a comment line.
2417
2418 @param isLast flag indicating, that the line is the last comment line
2419 (boolean)
2420 @return shortened comment line (string)
2421 """
2422 if len(self.__text) <= self.__maxLength:
2423 return self.__text
2424
2425 newText = self.__text.rstrip()
2426
2427 # PEP 8 recommends 72 characters for comment text.
2428 indentation = self.__getIndent(newText) + '# '
2429 maxLength = min(self.__maxLength,
2430 len(indentation) + 72)
2431
2432 MIN_CHARACTER_REPEAT = 5
2433 if len(newText) - len(newText.rstrip(newText[-1])) >= \
2434 MIN_CHARACTER_REPEAT and \
2435 not newText[-1].isalnum():
2436 # Trim comments that end with things like ---------
2437 return newText[:maxLength] + self.__eol
2438 elif isLast and re.match(r"\s*#+\s*\w+", newText):
2439 import textwrap
2440 splitLines = textwrap.wrap(newText.lstrip(" \t#"),
2441 initial_indent=indentation,
2442 subsequent_indent=indentation,
2443 width=maxLength,
2444 break_long_words=False,
2445 break_on_hyphens=False)
2446 return self.__eol.join(splitLines) + self.__eol
2447 else:
2448 return newText + self.__eol
2449
2450 def __breakMultiline(self):
2451 """
2452 Private method to break multi line strings.
2453
2454 @return tuple of the shortened line and the changed next line
2455 (string, string)
2456 """
2457 indentation = self.__getIndent(self.__text)
2458
2459 # Handle special case.
2460 for symbol in '([{':
2461 # Only valid if symbol is not on a line by itself.
2462 if (
2463 symbol in self.__text and
2464 self.__text.strip() != symbol and
2465 self.__text.rstrip().endswith((',', '%'))
2466 ):
2467 index = 1 + self.__text.find(symbol)
2468
2469 if index <= len(self.__indentWord) + len(indentation):
2470 continue
2471
2472 if self.__isProbablyInsideStringOrComment(
2473 self.__text, index - 1):
2474 continue
2475
2476 return (self.__text[:index].rstrip() + self.__eol +
2477 indentation + self.__indentWord +
2478 self.__text[index:].lstrip(), "")
2479
2480 newText = self.__text
2481 newNext = self.__nextText
2482 blank = newText.rfind(" ")
2483 while blank > self.__maxLength and blank != -1:
2484 blank = newText.rfind(" ", 0, blank)
2485 if blank != -1:
2486 first = self.__text[:blank]
2487 second = self.__text[blank:].strip()
2488 if newNext.strip():
2489 newText = first + self.__eol
2490 if second.endswith(")"):
2491 # don't merge with next line
2492 newText += self.__getIndent(newText) + second + self.__eol
2493 newNext = ""
2494 else:
2495 newNext = self.__getIndent(newNext) + \
2496 second + " " + newNext.lstrip()
2497 else:
2498 # empty line, add a new line
2499 newText = first + self.__eol
2500 newNext = self.__getIndent(newNext) + \
2501 second + self.__eol + newNext.lstrip()
2502 return newText, newNext
2503 else:
2504 return None
2505
2506 def __isProbablyInsideStringOrComment(self, line, index):
2507 """
2508 Private method to check, if the given string might be inside a string
2509 or comment.
2510
2511 @param line line to check (string)
2512 @param index position inside line to check (integer)
2513 @return flag indicating the possibility of being inside a string
2514 or comment
2515 """
2516 # Check against being in a string.
2517 for quote in ['"', "'"]:
2518 pos = line.find(quote)
2519 if pos != -1 and pos <= index:
2520 return True
2521
2522 # Check against being in a comment.
2523 pos = line.find('#')
2524 if pos != -1 and pos <= index:
2525 return True
2526
2527 return False
2528
2529 def __shortenLine(self, tokens, source, indent):
2530 """
2531 Private method to shorten a line of code at an operator.
2532
2533 @param tokens tokens of the line as generated by tokenize
2534 (list of token)
2535 @param source code string to work at (string)
2536 @param indent indentation string of the code line (string)
2537 @return list of candidates (list of string)
2538 """
2539 candidates = []
2540
2541 for tkn in tokens:
2542 tokenType = tkn[0]
2543 tokenString = tkn[1]
2544
2545 if (
2546 tokenType == tokenize.COMMENT and
2547 not self.__prevText.rstrip().endswith('\\')
2548 ):
2549 # Move inline comments to previous line.
2550 offset = tkn[2][1]
2551 first = source[:offset]
2552 second = source[offset:]
2553 candidates.append(
2554 indent + second.strip() + self.__eol +
2555 indent + first.strip() + self.__eol)
2556 elif tokenType == tokenize.OP and tokenString != '=':
2557 # Don't break on '=' after keyword as this violates PEP 8.
2558
2559 assert tokenType != tokenize.INDENT
2560
2561 offset = tkn[2][1] + 1
2562 first = source[:offset]
2563
2564 secondIndent = indent
2565 if first.rstrip().endswith('('):
2566 secondIndent += self.__indentWord
2567 elif '(' in first:
2568 secondIndent += ' ' * (1 + first.find('('))
2569 else:
2570 secondIndent += self.__indentWord
2571
2572 second = (secondIndent + source[offset:].lstrip())
2573 if not second.strip():
2574 continue
2575
2576 # Do not begin a line with a comma
2577 if second.lstrip().startswith(','):
2578 continue
2579
2580 # Do end a line with a dot
2581 if first.rstrip().endswith('.'):
2582 continue
2583
2584 if tokenString in '+-*/,':
2585 newText = first + ' \\' + self.__eol + second
2586 else:
2587 newText = first + self.__eol + second
2588
2589 # Only fix if syntax is okay.
2590 if self.__checkSyntax(self.__normalizeMultiline(newText)):
2591 candidates.append(indent + newText)
2592
2593 return candidates
2594
2595 def __normalizeMultiline(self, text):
2596 """
2597 Private method to remove multiline-related code that will cause syntax
2598 error.
2599
2600 @param text code line to work on (string)
2601 @return normalized code line (string)
2602 """
2603 for quote in '\'"':
2604 dictPattern = r"^{q}[^{q}]*{q} *: *".format(q=quote)
2605 if re.match(dictPattern, text):
2606 if not text.strip().endswith('}'):
2607 text += '}'
2608 return '{' + text
2609
2610 if text.startswith('def ') and text.rstrip().endswith(':'):
2611 # Do not allow ':' to be alone. That is invalid.
2612 splitText = [item.strip() for item in text.split(self.__eol)]
2613 if ':' not in splitText and 'def' not in splitText:
2614 return text[len('def'):].strip().rstrip(':')
2615
2616 return text
2617
2618 def __lineShorteningRank(self, candidate):
2619 """
2620 Private method to rank a candidate.
2621
2622 @param candidate candidate line to rank (string)
2623 @return rank of the candidate (integer)
2624 """
2625 rank = 0
2626 if candidate.strip():
2627 if candidate == self.__text:
2628 # give the original a disadvantage
2629 rank += 50
2630
2631 lines = candidate.split(self.__eol)
2632
2633 offset = 0
2634 if lines[0].rstrip()[-1] not in '([{':
2635 for symbol in '([{':
2636 offset = max(offset, 1 + lines[0].find(symbol))
2637
2638 maxLength = max([offset + len(x.strip()) for x in lines])
2639 rank += maxLength
2640 rank += len(lines)
2641
2642 badStartingSymbol = {
2643 '(': ')',
2644 '[': ']',
2645 '{': '}'}.get(lines[0][-1], None)
2646
2647 if len(lines) > 1:
2648 if (badStartingSymbol and
2649 lines[1].lstrip().startswith(badStartingSymbol)):
2650 rank += 20
2651
2652 if re.match(r".*[+\-\*/] \($", lines[0]):
2653 # "1 * (\n" is ugly as hell.
2654 rank += 100
2655
2656 for currentLine in lines:
2657 for badStart in ['.', '%', '+', '-', '/']:
2658 if currentLine.startswith(badStart):
2659 rank += 100
2660
2661 for ending in '([{':
2662 # Avoid lonely opening. They result in longer lines.
2663 if currentLine.endswith(ending) and \
2664 len(currentLine.strip()) <= len(self.__indentWord):
2665 rank += 100
2666
2667 if currentLine.endswith('%'):
2668 rank -= 20
2669
2670 # Try to break list comprehensions at the "for".
2671 if currentLine.lstrip().startswith('for'):
2672 rank -= 50
2673
2674 rank += 10 * self.__countUnbalancedBrackets(currentLine)
2675 else:
2676 rank = 100000
2677
2678 return max(0, rank)
2679
2680 def __countUnbalancedBrackets(self, line):
2681 """
2682 Private method to determine the number of unmatched open/close
2683 brackets.
2684
2685 @param line line to work at (string)
2686 @return number of unmatched open/close brackets (integer)
2687 """
2688 count = 0
2689 for opening, closing in ['()', '[]', '{}']:
2690 count += abs(line.count(opening) - line.count(closing))
2691
2692 return count
2693
2694 def __getIndent(self, line):
2695 """
2696 Private method to get the indentation string.
2697
2698 @param line line to determine the indentation string from (string)
2699 @return indentation string (string)
2700 """
2701 # copied from CodeStyleFixer
2702 return line.replace(line.lstrip(), "")
2703
2704 def __checkSyntax(self, code):
2705 """
2706 Private method to check the syntax of the given code fragment.
2707
2708 @param code code fragment to check (string)
2709 @return flag indicating syntax is ok (boolean)
2710 """
2711 code = code.replace("\r\n", "\n").replace("\r", "\n")
2712 try:
2713 return compile(code, '<string>', 'exec')
2714 except (SyntaxError, TypeError, UnicodeDecodeError):
2715 return False

eric ide

mercurial