Plugins/CheckerPlugins/Pep8/Pep8Fixer.py

changeset 2868
8d30ec21e9c7
parent 2302
f29e9405c851
child 2875
1267f0663801
equal deleted inserted replaced
2867:eb325d7f7335 2868:8d30ec21e9c7
7 Module implementing a class to fix certain PEP 8 issues. 7 Module implementing a class to fix certain PEP 8 issues.
8 """ 8 """
9 9
10 import os 10 import os
11 import re 11 import re
12 import tokenize
13 import io
12 14
13 from PyQt4.QtCore import QObject 15 from PyQt4.QtCore import QObject
14 16
15 from E5Gui import E5MessageBox 17 from E5Gui import E5MessageBox
16 18
17 import Utilities 19 import Utilities
18 20
19 Pep8FixableIssues = ["E101", "W191", "E201", "E202", "E203", "E211", "E221", 21 Pep8FixableIssues = ["E101", "E111", "W191", "E201", "E202", "E203",
20 "E222", "E225", "E231", "E241", "E251", "E261", "E262", 22 "E211", "E221", "E222", "E223", "E224", "E225",
21 "W291", "W292", "W293", "E301", "E302", "E303", "E304", 23 "E226", "E227", "E228", "E231", "E241", "E242",
22 "W391", "W603"] 24 "E251", "E261", "E262", "E271", "E272", "E273",
25 "E274", "W291", "W292", "W293", "E301", "E302",
26 "E303", "E304", "W391", "E401", "E502", "W603",
27 "E701", "E702", "E703", "E711", "E712"
28 ]
23 29
24 30
25 class Pep8Fixer(QObject): 31 class Pep8Fixer(QObject):
26 """ 32 """
27 Class implementing a fixer for certain PEP 8 issues. 33 Class implementing a fixer for certain PEP 8 issues.
45 self.__origName = "" 51 self.__origName = ""
46 self.__source = sourceLines[:] # save a copy 52 self.__source = sourceLines[:] # save a copy
47 self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()] 53 self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()]
48 self.fixed = 0 54 self.fixed = 0
49 55
56 self.__reindenter = None
57 self.__eol = ""
58 self.__indentWord = self.__getIndentWord()
59
50 if not inPlace: 60 if not inPlace:
51 self.__origName = self.__filename 61 self.__origName = self.__filename
52 self.__filename = os.path.join(os.path.dirname(self.__filename), 62 self.__filename = os.path.join(os.path.dirname(self.__filename),
53 "fixed_" + os.path.basename(self.__filename)) 63 "fixed_" + os.path.basename(self.__filename))
54 64
55 self.__fixes = { 65 self.__fixes = {
56 "E101": self.__fixTabs, 66 "E101": self.__fixE101,
57 "W191": self.__fixTabs, 67 "E111": self.__fixE101,
58 "E201": self.__fixWhitespaceAfter, 68 "W191": self.__fixE101,
59 "E202": self.__fixWhitespaceBefore, 69 "E201": self.__fixE201,
60 "E203": self.__fixWhitespaceBefore, 70 "E202": self.__fixE201,
61 "E211": self.__fixWhitespaceBefore, 71 "E203": self.__fixE201,
62 "E221": self.__fixWhitespaceAroundOperator, 72 "E211": self.__fixE201,
63 "E222": self.__fixWhitespaceAroundOperator, 73 "E221": self.__fixE221,
64 "E225": self.__fixMissingWhitespaceAroundOperator, 74 "E222": self.__fixE221,
65 "E231": self.__fixMissingWhitespaceAfter, 75 "E223": self.__fixE221,
66 "E241": self.__fixWhitespaceAroundOperator, 76 "E224": self.__fixE221,
67 "E251": self.__fixWhitespaceAroundEquals, 77 "E225": self.__fixE221,
68 "E261": self.__fixWhitespaceBeforeInline, 78 "E226": self.__fixE221,
69 "E262": self.__fixWhitespaceAfterInline, 79 "E227": self.__fixE221,
70 "W291": self.__fixWhitespace, 80 "E228": self.__fixE221,
71 "W292": self.__fixNewline, 81 "E231": self.__fixE231,
72 "W293": self.__fixWhitespace, 82 "E241": self.__fixE221,
73 "E301": self.__fixOneBlankLine, 83 "E242": self.__fixE221,
74 "E302": self.__fixTwoBlankLines, 84 "E251": self.__fixE251,
75 "E303": self.__fixTooManyBlankLines, 85 "E261": self.__fixE261,
76 "E304": self.__fixBlankLinesAfterDecorator, 86 "E262": self.__fixE261,
77 "W391": self.__fixTrailingBlankLines, 87 "E271": self.__fixE221,
78 "W603": self.__fixNotEqual, 88 "E272": self.__fixE221,
89 "E273": self.__fixE221,
90 "E274": self.__fixE221,
91 "W291": self.__fixW291,
92 "W292": self.__fixW292,
93 "W293": self.__fixW291,
94 "E301": self.__fixE301,
95 "E302": self.__fixE302,
96 "E303": self.__fixE303,
97 "E304": self.__fixE304,
98 "W391": self.__fixW391,
99 "E401": self.__fixE401,
100 "E502": self.__fixE502,
101 "W603": self.__fixW603,
102 "E701": self.__fixE701,
103 "E702": self.__fixE702,
104 "E703": self.__fixE702,
105 "E711": self.__fixE711,
106 "E712": self.__fixE711,
79 } 107 }
80 self.__modified = False 108 self.__modified = False
81 self.__stack = [] # these need to be fixed before the file is saved 109 self.__stack = [] # these need to be fixed before the file is saved
82 # but after all inline fixes 110 # but after all inline fixes
83 111
145 """ 173 """
146 Private method to get the applicable eol string. 174 Private method to get the applicable eol string.
147 175
148 @return eol string (string) 176 @return eol string (string)
149 """ 177 """
150 if self.__origName: 178 if not self.__eol:
151 fn = self.__origName 179 if self.__origName:
152 else: 180 fn = self.__origName
153 fn = self.__filename
154
155 if self.__project.isOpen() and self.__project.isProjectFile(fn):
156 eol = self.__project.getEolString()
157 else:
158 eol = Utilities.linesep()
159 return eol
160
161 def __fixTabs(self, code, line, pos):
162 """
163 Private method to fix obsolete tab usage.
164
165 @param code code of the issue (string)
166 @param line line number of the issue (integer)
167 @param pos position inside line (integer)
168 @return flag indicating an applied fix (boolean) and a message for
169 the fix (string)
170 """
171 self.__source[line - 1] = self.__source[line - 1].replace("\t", " ")
172 return (True, self.trUtf8("Tab converted to 4 spaces."))
173
174 def __fixWhitespace(self, code, line, pos):
175 """
176 Private method to fix trailing whitespace.
177
178 @param code code of the issue (string)
179 @param line line number of the issue (integer)
180 @param pos position inside line (integer)
181 @return flag indicating an applied fix (boolean) and a message for
182 the fix (string)
183 """
184 self.__source[line - 1] = re.sub(r'[\t ]+(\r?)$', r"\1",
185 self.__source[line - 1])
186 return (True, self.trUtf8("Whitespace stripped from end of line."))
187
188 def __fixNewline(self, code, line, pos):
189 """
190 Private method to fix a missing newline at the end of file.
191
192 @param code code of the issue (string)
193 @param line line number of the issue (integer)
194 @param pos position inside line (integer)
195 @return flag indicating an applied fix (boolean) and a message for
196 the fix (string)
197 """
198 self.__source[line - 1] += self.__getEol()
199 return (True, self.trUtf8("newline added to end of file."))
200
201 def __fixTrailingBlankLines(self, code, line, pos):
202 """
203 Private method to fix trailing blank lines.
204
205 @param code code of the issue (string)
206 @param line line number of the issue (integer)
207 @param pos position inside line (integer)
208 @return flag indicating an applied fix (boolean) and a message for
209 the fix (string)
210 """
211 index = line - 1
212 while index:
213 if self.__source[index].strip() == "":
214 del self.__source[index]
215 index -= 1
216 else: 181 else:
217 break 182 fn = self.__filename
218 return (True, self.trUtf8( 183
219 "Superfluous trailing blank lines removed from end of file.")) 184 if self.__project.isOpen() and self.__project.isProjectFile(fn):
220 185 self.__eol = self.__project.getEolString()
221 def __fixNotEqual(self, code, line, pos): 186 else:
222 """ 187 self.__eol = Utilities.linesep()
223 Private method to fix the not equal notation. 188 return self.__eol
224 189
225 @param code code of the issue (string) 190 def __getIndentWord(self):
226 @param line line number of the issue (integer) 191 """
227 @param pos position inside line (integer) 192 Private method to determine the indentation type.
228 @return flag indicating an applied fix (boolean) and a message for 193
229 the fix (string) 194 @return string to be used for an indentation (string)
230 """ 195 """
231 self.__source[line - 1] = self.__source[line - 1].replace("<>", "!=") 196 sio = io.StringIO("".join(self.__source))
232 return (True, self.trUtf8("'<>' replaced by '!='.")) 197 indentWord = " " # default in case of failure
233 198 try:
234 def __fixBlankLinesAfterDecorator(self, code, line, pos, apply=False): 199 for token in tokenize.generate_tokens(sio.readline):
235 """ 200 if token[0] == tokenize.INDENT:
236 Private method to fix superfluous blank lines after a function 201 indentWord = token[1]
237 decorator. 202 break
203 except (SyntaxError, tokenize.TokenError):
204 pass
205 return indentWord
206
207 def __getIndent(self, line):
208 """
209 Private method to get the indentation string.
210
211 @param line line to determine the indentation string from (string)
212 @return indentation string (string)
213 """
214 return line.replace(line.lstrip(), "")
215
216 def __fixWhitespace(self, line, offset, replacement):
217 """
218 Private method to correct whitespace at the given offset.
219
220 @param line line to be corrected (string)
221 @param offset offset within line (integer)
222 @param replacement replacement string (string)
223 @return corrected line
224 """
225 left = line[:offset].rstrip(" \t")
226 right = line[offset:].lstrip(" \t")
227 if right.startswith("#"):
228 return line
229 else:
230 return left + replacement + right
231
232 def __fixE101(self, code, line, pos):
233 """
234 Private method to fix obsolete tab usage and indentation errors
235 (E101, E111, W191).
236
237 @param code code of the issue (string)
238 @param line line number of the issue (integer)
239 @param pos position inside line (integer)
240 @return flag indicating an applied fix (boolean) and a message for
241 the fix (string)
242 """
243 if self.__reindenter is None:
244 self.__reindenter = Pep8Reindenter(self.__source)
245 self.__reindenter.run()
246 fixedLine = self.__reindenter.fixedLine(line - 1)
247 if fixedLine is not None:
248 self.__source[line - 1] = fixedLine
249 if code in ["E101", "W191"]:
250 msg = self.trUtf8("Tab converted to 4 spaces.")
251 else:
252 msg = self.trUtf8("Indentation adjusted to be a multiple of four.")
253 return (True, msg)
254 else:
255 return (False, self.trUtf8("Fix for {0} failed.").format(code))
256
257 def __fixE201(self, code, line, pos):
258 """
259 Private method to fix extraneous whitespace (E201, E202,
260 E203, E211).
261
262 @param code code of the issue (string)
263 @param line line number of the issue (integer)
264 @param pos position inside line (integer)
265 @return flag indicating an applied fix (boolean) and a message for
266 the fix (string)
267 """
268 line = line - 1
269 text = self.__source[line]
270
271 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'):
272 return (False, self.trUtf8("Extraneous whitespace cannot be removed."))
273
274 newText = self.__fixWhitespace(text, pos, '')
275 if newText == text:
276 return (False, "")
277
278 self.__source[line] = newText
279 return (True, self.trUtf8("Extraneous whitespace removed."))
280
281 def __fixE221(self, code, line, pos):
282 """
283 Private method to fix extraneous whitespace around operator or
284 keyword (E221, E222, E223, E224, E225, E226, E227, E228, E241,
285 E242, E271, E272, E273, E274).
286
287 @param code code of the issue (string)
288 @param line line number of the issue (integer)
289 @param pos position inside line (integer)
290 @return flag indicating an applied fix (boolean) and a message for
291 the fix (string)
292 """
293 line = line - 1
294 text = self.__source[line]
295
296 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'):
297 return (False, self.trUtf8("Extraneous whitespace cannot be removed."))
298
299 newText = self.__fixWhitespace(text, pos, ' ')
300 if newText == text:
301 return (False, "")
302
303 self.__source[line] = newText
304 if code in ["E225", "E226", "E227", "E228"]:
305 return (True, self.trUtf8("Missing whitespace added."))
306 else:
307 return (True, self.trUtf8("Extraneous whitespace removed."))
308
309 def __fixE231(self, code, line, pos):
310 """
311 Private method to fix missing whitespace after ',;:'.
312
313 @param code code of the issue (string)
314 @param line line number of the issue (integer)
315 @param pos position inside line (integer)
316 @return flag indicating an applied fix (boolean) and a message for
317 the fix (string)
318 """
319 line = line - 1
320 pos = pos + 1
321 self.__source[line] = self.__source[line][:pos] + \
322 " " + \
323 self.__source[line][pos:]
324 return (True, self.trUtf8("Missing whitespace added."))
325
326 def __fixE251(self, code, line, pos):
327 """
328 Private method to fix extraneous whitespace around keyword and
329 default parameter equals (E251).
330
331 @param code code of the issue (string)
332 @param line line number of the issue (integer)
333 @param pos position inside line (integer)
334 @return flag indicating an applied fix (boolean) and a message for
335 the fix (string)
336 """
337 line = line - 1
338 text = self.__source[line]
339
340 # This is necessary since pep8 sometimes reports columns that goes
341 # past the end of the physical line. This happens in cases like,
342 # foo(bar\n=None)
343 col = min(pos, len(text) - 1)
344 if text[col].strip():
345 newText = text
346 else:
347 newText = text[:col].rstrip() + text[col:].lstrip()
348
349 # There could be an escaped newline
350 #
351 # def foo(a=\
352 # 1)
353 if newText.endswith(('=\\\n', '=\\\r\n', '=\\\r')):
354 self.__source[line] = newText.rstrip("\n\r \t\\")
355 self.__source[line + 1] = self.__source[line + 1].lstrip()
356 else:
357 self.__source[line] = newText
358 return (True, self.trUtf8("Extraneous whitespace removed."))
359
360 def __fixE261(self, code, line, pos):
361 """
362 Private method to fix whitespace before or after inline comment
363 (E261, E262).
364
365 @param code code of the issue (string)
366 @param line line number of the issue (integer)
367 @param pos position inside line (integer)
368 @return flag indicating an applied fix (boolean) and a message for
369 the fix (string)
370 """
371 line = line - 1
372 text = self.__source[line]
373 left = text[:pos].rstrip(' \t#')
374 right = text[pos:].lstrip(' \t#')
375 newText = left + (" # " + right if right.strip() else right)
376 self.__source[line] = newText
377 return (True, self.trUtf8("Whitespace around comment sign corrected."))
378
379 def __fixE301(self, code, line, pos, apply=False):
380 """
381 Private method to fix the need for one blank line (E301).
238 382
239 @param code code of the issue (string) 383 @param code code of the issue (string)
240 @param line line number of the issue (integer) 384 @param line line number of the issue (integer)
241 @param pos position inside line (integer) 385 @param pos position inside line (integer)
242 @keyparam apply flag indicating, that the fix should be applied 386 @keyparam apply flag indicating, that the fix should be applied
243 (boolean) 387 (boolean)
244 @return flag indicating an applied fix (boolean) and a message for 388 @return flag indicating an applied fix (boolean) and a message for
245 the fix (string) 389 the fix (string)
246 """ 390 """
247 if apply: 391 if apply:
248 index = line - 2
249 while index:
250 if self.__source[index].strip() == "":
251 del self.__source[index]
252 index -= 1
253 else:
254 break
255 else:
256 self.__stack.append((code, line, pos))
257 return (True, self.trUtf8(
258 "Superfluous blank lines after function decorator removed."))
259
260 def __fixTooManyBlankLines(self, code, line, pos, apply=False):
261 """
262 Private method to fix superfluous blank lines.
263
264 @param code code of the issue (string)
265 @param line line number of the issue (integer)
266 @param pos position inside line (integer)
267 @keyparam apply flag indicating, that the fix should be applied
268 (boolean)
269 @return flag indicating an applied fix (boolean) and a message for
270 the fix (string)
271 """
272 if apply:
273 index = line - 3
274 while index:
275 if self.__source[index].strip() == "":
276 del self.__source[index]
277 index -= 1
278 else:
279 break
280 else:
281 self.__stack.append((code, line, pos))
282 return (True, self.trUtf8("Superfluous blank lines removed."))
283
284 def __fixOneBlankLine(self, code, line, pos, apply=False):
285 """
286 Private method to fix the need for one blank line.
287
288 @param code code of the issue (string)
289 @param line line number of the issue (integer)
290 @param pos position inside line (integer)
291 @keyparam apply flag indicating, that the fix should be applied
292 (boolean)
293 @return flag indicating an applied fix (boolean) and a message for
294 the fix (string)
295 """
296 if apply:
297 self.__source.insert(line - 1, self.__getEol()) 392 self.__source.insert(line - 1, self.__getEol())
298 else: 393 else:
299 self.__stack.append((code, line, pos)) 394 self.__stack.append((code, line, pos))
300 return (True, self.trUtf8("One blank line inserted.")) 395 return (True, self.trUtf8("One blank line inserted."))
301 396
302 def __fixTwoBlankLines(self, code, line, pos, apply=False): 397 def __fixE302(self, code, line, pos, apply=False):
303 """ 398 """
304 Private method to fix the need for two blank lines. 399 Private method to fix the need for two blank lines (E302).
305 """ 400 """
306 # count blank lines 401 # count blank lines
307 index = line - 1 402 index = line - 1
308 blanks = 0 403 blanks = 0
309 while index: 404 while index:
336 msg = self.trUtf8("%n superfluous lines removed", "", delta) 431 msg = self.trUtf8("%n superfluous lines removed", "", delta)
337 else: 432 else:
338 msg = "" 433 msg = ""
339 return (True, msg) 434 return (True, msg)
340 435
341 def __fixWhitespaceAfter(self, code, line, pos, apply=False): 436 def __fixE303(self, code, line, pos, apply=False):
342 """ 437 """
343 Private method to fix superfluous whitespace after '([{'. 438 Private method to fix superfluous blank lines (E303).
344 439
345 @param code code of the issue (string) 440 @param code code of the issue (string)
346 @param line line number of the issue (integer) 441 @param line line number of the issue (integer)
347 @param pos position inside line (integer) 442 @param pos position inside line (integer)
348 @keyparam apply flag indicating, that the fix should be applied 443 @keyparam apply flag indicating, that the fix should be applied
349 (boolean) 444 (boolean)
350 @return flag indicating an applied fix (boolean) and a message for 445 @return flag indicating an applied fix (boolean) and a message for
351 the fix (string) 446 the fix (string)
352 """ 447 """
353 line = line - 1 448 if apply:
354 pos = pos - 1 449 index = line - 3
355 while self.__source[line][pos] in [" ", "\t"]: 450 while index:
356 self.__source[line] = self.__source[line][:pos] + \ 451 if self.__source[index].strip() == "":
357 self.__source[line][pos + 1:] 452 del self.__source[index]
358 return (True, self.trUtf8("Superfluous whitespace removed.")) 453 index -= 1
359 454 else:
360 def __fixWhitespaceBefore(self, code, line, pos, apply=False): 455 break
361 """ 456 else:
362 Private method to fix superfluous whitespace before '}])', 457 self.__stack.append((code, line, pos))
363 ',;:' and '(['. 458 return (True, self.trUtf8("Superfluous blank lines removed."))
459
460 def __fixE304(self, code, line, pos, apply=False):
461 """
462 Private method to fix superfluous blank lines after a function
463 decorator (E304).
364 464
365 @param code code of the issue (string) 465 @param code code of the issue (string)
366 @param line line number of the issue (integer) 466 @param line line number of the issue (integer)
367 @param pos position inside line (integer) 467 @param pos position inside line (integer)
368 @keyparam apply flag indicating, that the fix should be applied 468 @keyparam apply flag indicating, that the fix should be applied
369 (boolean) 469 (boolean)
370 @return flag indicating an applied fix (boolean) and a message for 470 @return flag indicating an applied fix (boolean) and a message for
371 the fix (string) 471 the fix (string)
372 """ 472 """
373 line = line - 1 473 if apply:
374 pos = pos - 1 474 index = line - 2
375 while self.__source[line][pos] in [" ", "\t"]: 475 while index:
376 self.__source[line] = self.__source[line][:pos] + \ 476 if self.__source[index].strip() == "":
377 self.__source[line][pos + 1:] 477 del self.__source[index]
378 pos -= 1 478 index -= 1
379 return (True, self.trUtf8("Superfluous whitespace removed.")) 479 else:
380 480 break
381 def __fixMissingWhitespaceAfter(self, code, line, pos, apply=False): 481 else:
382 """ 482 self.__stack.append((code, line, pos))
383 Private method to fix missing whitespace after ',;:'. 483 return (True, self.trUtf8(
484 "Superfluous blank lines after function decorator removed."))
485
486 def __fixE401(self, code, line, pos, apply=False):
487 """
488 Private method to fix multiple imports on one line (E401).
384 489
385 @param code code of the issue (string) 490 @param code code of the issue (string)
386 @param line line number of the issue (integer) 491 @param line line number of the issue (integer)
387 @param pos position inside line (integer) 492 @param pos position inside line (integer)
388 @keyparam apply flag indicating, that the fix should be applied 493 @keyparam apply flag indicating, that the fix should be applied
389 (boolean) 494 (boolean)
390 @return flag indicating an applied fix (boolean) and a message for 495 @return flag indicating an applied fix (boolean) and a message for
391 the fix (string) 496 the fix (string)
392 """ 497 """
393 line = line - 1 498 if apply:
394 self.__source[line] = self.__source[line][:pos] + \ 499 line = line - 1
395 " " + \ 500 text = self.__source[line]
396 self.__source[line][pos:] 501 if not text.lstrip().startswith("import"):
397 return (True, self.trUtf8("Missing whitespace added.")) 502 return (False, "")
398 503
399 def __fixWhitespaceAroundOperator(self, code, line, pos, apply=False): 504 # pep8 (1.3.1) reports false positive if there is an import
400 """ 505 # statement followed by a semicolon and some unrelated
401 Private method to fix extraneous whitespace around operator. 506 # statement with commas in it.
507 if ';' in text:
508 return (False, "")
509
510 newText = text[:pos].rstrip("\t ,") + self.__getEol() + \
511 self.__getIndent(text) + "import " + text[pos:].lstrip("\t ,")
512 self.__source[line] = newText
513 else:
514 self.__stack.append((code, line, pos))
515 return (True, self.trUtf8("Imports were put on separate lines."))
516
517 def __fixE502(self, code, line, pos):
518 """
519 Private method to fix redundant backslash within brackets (E502).
520
521 @param code code of the issue (string)
522 @param line line number of the issue (integer)
523 @param pos position inside line (integer)
524 @return flag indicating an applied fix (boolean) and a message for
525 the fix (string)
526 """
527 self.__source[line - 1] = self.__source[line - 1].rstrip("\n\r \t\\") + \
528 self.__getEol()
529 return (True, self.trUtf8("Redundant backslash in brackets removed."))
530
531 def __fixE701(self, code, line, pos, apply=False):
532 """
533 Private method to fix colon-separated compund statements (E701).
402 534
403 @param code code of the issue (string) 535 @param code code of the issue (string)
404 @param line line number of the issue (integer) 536 @param line line number of the issue (integer)
405 @param pos position inside line (integer) 537 @param pos position inside line (integer)
406 @keyparam apply flag indicating, that the fix should be applied 538 @keyparam apply flag indicating, that the fix should be applied
407 (boolean) 539 (boolean)
408 @return flag indicating an applied fix (boolean) and a message for 540 @return flag indicating an applied fix (boolean) and a message for
409 the fix (string) 541 the fix (string)
410 """ 542 """
411 line = line - 1 543 if apply:
412 while self.__source[line][pos - 1] in [" ", "\t"]: 544 line = line - 1
413 self.__source[line] = self.__source[line][:pos - 1] + \ 545 text = self.__source[line]
414 self.__source[line][pos:] 546 pos = pos + 1
415 pos -= 1 547
416 return (True, self.trUtf8("Extraneous whitespace removed.")) 548 newText = text[:pos] + self.__getEol() + self.__getIndent(text) + \
417 549 self.__indentWord + text[pos:].lstrip("\n\r \t\\") + \
418 def __fixMissingWhitespaceAroundOperator(self, code, line, pos, 550 self.__getEol()
419 apply=False): 551 self.__source[line] = newText
420 """ 552 else:
421 Private method to fix missing whitespace after ',;:'. 553 self.__stack.append((code, line, pos))
554 return (True, self.trUtf8("Compound statement corrected."))
555
556 def __fixE702(self, code, line, pos, apply=False):
557 """
558 Private method to fix semicolon-separated compound statements
559 (E702, E703).
422 560
423 @param code code of the issue (string) 561 @param code code of the issue (string)
424 @param line line number of the issue (integer) 562 @param line line number of the issue (integer)
425 @param pos position inside line (integer) 563 @param pos position inside line (integer)
426 @keyparam apply flag indicating, that the fix should be applied 564 @keyparam apply flag indicating, that the fix should be applied
427 (boolean) 565 (boolean)
428 @return flag indicating an applied fix (boolean) and a message for 566 @return flag indicating an applied fix (boolean) and a message for
429 the fix (string) 567 the fix (string)
430 """ 568 """
569 if apply:
570 line = line - 1
571 text = self.__source[line]
572
573 if text.rstrip().endswith("\\"):
574 # normalize '1; \\\n2' into '1; 2'
575 self.__source[line] = text.rstrip("\n\r \t\\")
576 self.__source[line + 1] = self.__source[line + 1].lstrip()
577 elif text.rstrip().endswith(";"):
578 self.__source[line] = text.rstrip("\n\r \t;") + self.__getEol()
579 else:
580 first = text[:pos].rstrip("\n\r \t;") + self.__getEol()
581 second = text[pos:].lstrip("\n\r \t;")
582 self.__source[line] = first + self.__getIndent(text) + second
583 else:
584 self.__stack.append((code, line, pos))
585 return (True, self.trUtf8("Compound statement corrected."))
586
587 def __fixE711(self, code, line, pos):
588 """
589 Private method to fix comparison with None (E711, E712).
590
591 @param code code of the issue (string)
592 @param line line number of the issue (integer)
593 @param pos position inside line (integer)
594 @return flag indicating an applied fix (boolean) and a message for
595 the fix (string)
596 """
431 line = line - 1 597 line = line - 1
432 pos = pos - 1 598 text = self.__source[line]
433 self.__source[line] = self.__source[line][:pos] + \ 599
434 " " + \ 600 rightPos = pos + 2
435 self.__source[line][pos:] 601 if rightPos >= len(text):
436 return (True, self.trUtf8("Missing whitespace added.")) 602 return (False, "")
437 603
438 def __fixWhitespaceAroundEquals(self, code, line, pos, apply=False): 604 left = text[:pos].rstrip()
439 """ 605 center = text[pos:rightPos]
440 Private method to fix extraneous whitespace around keyword and 606 right = text[rightPos:].lstrip()
441 default parameter equals. 607
442 608 if not right.startswith(("None", "True", "False")):
443 @param code code of the issue (string) 609 return (False, "")
444 @param line line number of the issue (integer) 610
445 @param pos position inside line (integer) 611 if center.strip() == "==":
446 @keyparam apply flag indicating, that the fix should be applied 612 center = "is"
447 (boolean) 613 elif center.strip() == "!=":
448 @return flag indicating an applied fix (boolean) and a message for 614 center = "is not"
449 the fix (string) 615 else:
450 """ 616 return (False, "")
451 line = line - 1 617
452 if self.__source[line][pos + 1] == " ": 618 self.__source[line] = " ".join([left, center, right])
453 self.__source[line] = self.__source[line][:pos + 1] + \ 619 return (True, self.trUtf8("Comparison to None/True/False corrected."))
454 self.__source[line][pos + 2:] 620
455 if self.__source[line][pos - 1] == " ": 621 def __fixW291(self, code, line, pos):
456 self.__source[line] = self.__source[line][:pos - 1] + \ 622 """
457 self.__source[line][pos:] 623 Private method to fix trailing whitespace (W291, W293).
458 return (True, self.trUtf8("Extraneous whitespace removed.")) 624
459 625 @param code code of the issue (string)
460 def __fixWhitespaceBeforeInline(self, code, line, pos, apply=False): 626 @param line line number of the issue (integer)
461 """ 627 @param pos position inside line (integer)
462 Private method to fix missing whitespace before inline comment. 628 @return flag indicating an applied fix (boolean) and a message for
463 629 the fix (string)
464 @param code code of the issue (string) 630 """
465 @param line line number of the issue (integer) 631 self.__source[line - 1] = re.sub(r'[\t ]+(\r?)$', r"\1",
466 @param pos position inside line (integer) 632 self.__source[line - 1])
467 @keyparam apply flag indicating, that the fix should be applied 633 return (True, self.trUtf8("Whitespace stripped from end of line."))
468 (boolean) 634
469 @return flag indicating an applied fix (boolean) and a message for 635 def __fixW292(self, code, line, pos):
470 the fix (string) 636 """
471 """ 637 Private method to fix a missing newline at the end of file (W292).
472 line = line - 1 638
473 pos = pos - 1 639 @param code code of the issue (string)
474 if self.__source[line][pos] == " ": 640 @param line line number of the issue (integer)
475 count = 1 641 @param pos position inside line (integer)
476 else: 642 @return flag indicating an applied fix (boolean) and a message for
477 count = 2 643 the fix (string)
478 self.__source[line] = self.__source[line][:pos] + \ 644 """
479 count * " " + \ 645 self.__source[line - 1] += self.__getEol()
480 self.__source[line][pos:] 646 return (True, self.trUtf8("newline added to end of file."))
481 return (True, self.trUtf8("Missing whitespace added.")) 647
482 648 def __fixW391(self, code, line, pos):
483 def __fixWhitespaceAfterInline(self, code, line, pos, apply=False): 649 """
484 """ 650 Private method to fix trailing blank lines (W391).
485 Private method to fix whitespace after inline comment. 651
486 652 @param code code of the issue (string)
487 @param code code of the issue (string) 653 @param line line number of the issue (integer)
488 @param line line number of the issue (integer) 654 @param pos position inside line (integer)
489 @param pos position inside line (integer) 655 @return flag indicating an applied fix (boolean) and a message for
490 @keyparam apply flag indicating, that the fix should be applied 656 the fix (string)
491 (boolean) 657 """
492 @return flag indicating an applied fix (boolean) and a message for 658 index = line - 1
493 the fix (string) 659 while index:
494 """ 660 if self.__source[index].strip() == "":
495 line = line - 1 661 del self.__source[index]
496 if self.__source[line][pos] == " ": 662 index -= 1
497 pos += 1 663 else:
498 while self.__source[line][pos] == " ": 664 break
499 self.__source[line] = self.__source[line][:pos] + \
500 self.__source[line][pos + 1:]
501 else:
502 self.__source[line] = self.__source[line][:pos] + \
503 " " + \
504 self.__source[line][pos:]
505 return (True, self.trUtf8( 665 return (True, self.trUtf8(
506 "Whitespace after inline comment sign corrected.")) 666 "Superfluous trailing blank lines removed from end of file."))
667
668 def __fixW603(self, code, line, pos):
669 """
670 Private method to fix the not equal notation (W603).
671
672 @param code code of the issue (string)
673 @param line line number of the issue (integer)
674 @param pos position inside line (integer)
675 @return flag indicating an applied fix (boolean) and a message for
676 the fix (string)
677 """
678 self.__source[line - 1] = self.__source[line - 1].replace("<>", "!=")
679 return (True, self.trUtf8("'<>' replaced by '!='."))
680
681
682 class Pep8Reindenter(object):
683 """
684 Class to reindent badly-indented code to uniformly use four-space indentation.
685
686 Released to the public domain, by Tim Peters, 03 October 2000.
687 """
688 def __init__(self, sourceLines):
689 """
690 Constructor
691
692 @param sourceLines list of source lines including eol marker
693 (list of string)
694 """
695 # Raw file lines.
696 self.raw = sourceLines
697 self.after = []
698
699 # File lines, rstripped & tab-expanded. Dummy at start is so
700 # that we can use tokenize's 1-based line numbering easily.
701 # Note that a line is all-blank iff it's "\n".
702 self.lines = [line.rstrip().expandtabs() + "\n"
703 for line in self.raw]
704 self.lines.insert(0, None)
705 self.index = 1 # index into self.lines of next line
706
707 # List of (lineno, indentlevel) pairs, one for each stmt and
708 # comment line. indentlevel is -1 for comment lines, as a
709 # signal that tokenize doesn't know what to do about them;
710 # indeed, they're our headache!
711 self.stats = []
712
713 def run(self):
714 """
715 Public method to run the re-indenter.
716 """
717 try:
718 stats = self.__genStats(tokenize.generate_tokens(self.getline))
719 except (SyntaxError, tokenize.TokenError):
720 return False
721
722 # Remove trailing empty lines.
723 lines = self.lines
724 while lines and lines[-1] == "\n":
725 lines.pop()
726 # Sentinel.
727 stats.append((len(lines), 0))
728 # Map count of leading spaces to # we want.
729 have2want = {}
730 # Program after transformation.
731 after = self.after = []
732 # Copy over initial empty lines -- there's nothing to do until
733 # we see a line with *something* on it.
734 i = stats[0][0]
735 after.extend(lines[1:i])
736 for i in range(len(stats)-1):
737 thisstmt, thislevel = stats[i]
738 nextstmt = stats[i+1][0]
739 have = self.__getlspace(lines[thisstmt])
740 want = thislevel * 4
741 if want < 0:
742 # A comment line.
743 if have:
744 # An indented comment line. If we saw the same
745 # indentation before, reuse what it most recently
746 # mapped to.
747 want = have2want.get(have, -1)
748 if want < 0:
749 # Then it probably belongs to the next real stmt.
750 for j in range(i + 1, len(stats) - 1):
751 jline, jlevel = stats[j]
752 if jlevel >= 0:
753 if have == self.__getlspace(lines[jline]):
754 want = jlevel * 4
755 break
756 if want < 0: # Maybe it's a hanging
757 # comment like this one,
758 # in which case we should shift it like its base
759 # line got shifted.
760 for j in range(i - 1, -1, -1):
761 jline, jlevel = stats[j]
762 if jlevel >= 0:
763 want = have + self.__getlspace(after[jline-1]) - \
764 self.__getlspace(lines[jline])
765 break
766 if want < 0:
767 # Still no luck -- leave it alone.
768 want = have
769 else:
770 want = 0
771 assert want >= 0
772 have2want[have] = want
773 diff = want - have
774 if diff == 0 or have == 0:
775 after.extend(lines[thisstmt:nextstmt])
776 else:
777 for line in lines[thisstmt:nextstmt]:
778 if diff > 0:
779 if line == "\n":
780 after.append(line)
781 else:
782 after.append(" " * diff + line)
783 else:
784 remove = min(self.__getlspace(line), -diff)
785 after.append(line[remove:])
786 return self.raw != self.after
787
788 def fixedLine(self, line):
789 """
790 Public method to get a fixed line.
791
792 @param line number of the line to retrieve (integer)
793 @return fixed line (string)
794 """
795 if line < len(self.after):
796 return self.after[line]
797
798 def getline(self):
799 """
800 Public method to get a line of text for tokenize.
801
802 @return line of text (string)
803 """
804 if self.index >= len(self.lines):
805 line = ""
806 else:
807 line = self.lines[self.index]
808 self.index += 1
809 return line
810
811 def __genStats(self, tokens):
812 """
813 Private method to generate the re-indent statistics.
814
815 @param tokens tokens generator (tokenize._tokenize)
816 """
817 find_stmt = True # next token begins a fresh stmt?
818 level = 0 # current indent level
819 stats = []
820
821 for t in tokens:
822 token_type = t[0]
823 sline = t[2][0]
824 line = t[4]
825
826 if token_type == tokenize.NEWLINE:
827 # A program statement, or ENDMARKER, will eventually follow,
828 # after some (possibly empty) run of tokens of the form
829 # (NL | COMMENT)* (INDENT | DEDENT+)?
830 self.find_stmt = True
831
832 elif token_type == tokenize.INDENT:
833 find_stmt = True
834 level += 1
835
836 elif token_type == tokenize.DEDENT:
837 find_stmt = True
838 level -= 1
839
840 elif token_type == tokenize.COMMENT:
841 if find_stmt:
842 stats.append((sline, -1))
843 # but we're still looking for a new stmt, so leave
844 # find_stmt alone
845
846 elif token_type == tokenize.NL:
847 pass
848
849 elif find_stmt:
850 # This is the first "real token" following a NEWLINE, so it
851 # must be the first token of the next program statement, or an
852 # ENDMARKER.
853 find_stmt = False
854 if line: # not endmarker
855 stats.append((sline, level))
856
857 return stats
858
859 def __getlspace(self, line):
860 """
861 Private method to count number of leading blanks.
862
863 @param line line to check (string)
864 @return number of leading blanks (integer)
865 """
866 i = 0
867 n = len(line)
868 while i < n and line[i] == " ":
869 i += 1
870 return i

eric ide

mercurial