17 # under Python3. So ignore it. |
17 # under Python3. So ignore it. |
18 with contextlib.suppress(ImportError): |
18 with contextlib.suppress(ImportError): |
19 import pycodestyle |
19 import pycodestyle |
20 |
20 |
21 FixableCodeStyleIssues = [ |
21 FixableCodeStyleIssues = [ |
22 "D111", "D112", "D121", "D131", "D141", "D142", |
22 "D111", |
23 "D143", "D144", "D145", |
23 "D112", |
24 "D221", "D222", "D231", "D242", "D243", "D244", |
24 "D121", |
25 "D245", "D246", "D247", |
25 "D131", |
26 "E101", "E111", "E121", "E122", "E123", "E124", |
26 "D141", |
27 "E125", "E126", "E127", "E128", "E133", "E201", |
27 "D142", |
28 "E202", "E203", "E211", "E221", "E222", "E223", |
28 "D143", |
29 "E224", "E225", "E226", "E227", "E228", "E231", |
29 "D144", |
30 "E241", "E242", "E251", "E261", "E262", "E271", |
30 "D145", |
31 "E272", "E273", "E274", "E301", "E302", "E303", |
31 "D221", |
32 "E304", "E305", "E306", "E307", "E308", "E401", |
32 "D222", |
33 "E501", "E502", "E701", "E702", "E703", "E711", |
33 "D231", |
|
34 "D242", |
|
35 "D243", |
|
36 "D244", |
|
37 "D245", |
|
38 "D246", |
|
39 "D247", |
|
40 "E101", |
|
41 "E111", |
|
42 "E121", |
|
43 "E122", |
|
44 "E123", |
|
45 "E124", |
|
46 "E125", |
|
47 "E126", |
|
48 "E127", |
|
49 "E128", |
|
50 "E133", |
|
51 "E201", |
|
52 "E202", |
|
53 "E203", |
|
54 "E211", |
|
55 "E221", |
|
56 "E222", |
|
57 "E223", |
|
58 "E224", |
|
59 "E225", |
|
60 "E226", |
|
61 "E227", |
|
62 "E228", |
|
63 "E231", |
|
64 "E241", |
|
65 "E242", |
|
66 "E251", |
|
67 "E261", |
|
68 "E262", |
|
69 "E271", |
|
70 "E272", |
|
71 "E273", |
|
72 "E274", |
|
73 "E301", |
|
74 "E302", |
|
75 "E303", |
|
76 "E304", |
|
77 "E305", |
|
78 "E306", |
|
79 "E307", |
|
80 "E308", |
|
81 "E401", |
|
82 "E501", |
|
83 "E502", |
|
84 "E701", |
|
85 "E702", |
|
86 "E703", |
|
87 "E711", |
34 "E712", |
88 "E712", |
35 "N804", "N805", "N806", |
89 "N804", |
36 "W191", "W291", "W292", "W293", "W391", "W603", |
90 "N805", |
|
91 "N806", |
|
92 "W191", |
|
93 "W291", |
|
94 "W292", |
|
95 "W293", |
|
96 "W391", |
|
97 "W603", |
37 ] |
98 ] |
38 |
99 |
39 |
100 |
40 class CodeStyleFixer: |
101 class CodeStyleFixer: |
41 """ |
102 """ |
42 Class implementing a fixer for certain code style issues. |
103 Class implementing a fixer for certain code style issues. |
43 """ |
104 """ |
44 def __init__(self, filename, sourceLines, fixCodes, noFixCodes, |
105 |
45 maxLineLength, blankLines, inPlace, eol, backup=False): |
106 def __init__( |
|
107 self, |
|
108 filename, |
|
109 sourceLines, |
|
110 fixCodes, |
|
111 noFixCodes, |
|
112 maxLineLength, |
|
113 blankLines, |
|
114 inPlace, |
|
115 eol, |
|
116 backup=False, |
|
117 ): |
46 """ |
118 """ |
47 Constructor |
119 Constructor |
48 |
120 |
49 @param filename name of the file to be fixed |
121 @param filename name of the file to be fixed |
50 @type str |
122 @type str |
51 @param sourceLines list of source lines including eol marker |
123 @param sourceLines list of source lines including eol marker |
52 @type list of str |
124 @type list of str |
53 @param fixCodes list of codes to be fixed as a comma separated |
125 @param fixCodes list of codes to be fixed as a comma separated |
181 # These need to be fixed before the file is saved but after all |
253 # These need to be fixed before the file is saved but after all |
182 # other inline fixes. These work with logical lines. |
254 # other inline fixes. These work with logical lines. |
183 self.__stack = [] |
255 self.__stack = [] |
184 # These need to be fixed before the file is saved but after all |
256 # These need to be fixed before the file is saved but after all |
185 # inline fixes. |
257 # inline fixes. |
186 |
258 |
187 self.__multiLineNumbers = None |
259 self.__multiLineNumbers = None |
188 self.__docLineNumbers = None |
260 self.__docLineNumbers = None |
189 |
261 |
190 self.__lastID = 0 |
262 self.__lastID = 0 |
191 |
263 |
192 def saveFile(self, encoding): |
264 def saveFile(self, encoding): |
193 """ |
265 """ |
194 Public method to save the modified file. |
266 Public method to save the modified file. |
195 |
267 |
196 @param encoding encoding of the source file (string) |
268 @param encoding encoding of the source file (string) |
197 @return error message on failure (tuple of str) |
269 @return error message on failure (tuple of str) |
198 """ |
270 """ |
199 import codecs |
271 import codecs |
200 |
272 |
201 if not self.__modified: |
273 if not self.__modified: |
202 # no need to write |
274 # no need to write |
203 return None |
275 return None |
204 |
276 |
205 if self.__createBackup: |
277 if self.__createBackup: |
206 # create a backup file before writing any changes |
278 # create a backup file before writing any changes |
207 if os.path.islink(self.__filename): |
279 if os.path.islink(self.__filename): |
208 bfn = '{0}~'.format(os.path.realpath(self.__filename)) |
280 bfn = "{0}~".format(os.path.realpath(self.__filename)) |
209 else: |
281 else: |
210 bfn = '{0}~'.format(self.__filename) |
282 bfn = "{0}~".format(self.__filename) |
211 with contextlib.suppress(OSError): |
283 with contextlib.suppress(OSError): |
212 os.remove(bfn) |
284 os.remove(bfn) |
213 with contextlib.suppress(OSError): |
285 with contextlib.suppress(OSError): |
214 os.rename(self.__filename, bfn) |
286 os.rename(self.__filename, bfn) |
215 |
287 |
216 txt = "".join(self.__source) |
288 txt = "".join(self.__source) |
217 try: |
289 try: |
218 enc = 'utf-8' if encoding == 'utf-8-bom' else encoding |
290 enc = "utf-8" if encoding == "utf-8-bom" else encoding |
219 txt = txt.encode(enc) |
291 txt = txt.encode(enc) |
220 if encoding == 'utf-8-bom': |
292 if encoding == "utf-8-bom": |
221 txt = codecs.BOM_UTF8 + txt |
293 txt = codecs.BOM_UTF8 + txt |
222 |
294 |
223 with open(self.__filename, "wb") as fp: |
295 with open(self.__filename, "wb") as fp: |
224 fp.write(txt) |
296 fp.write(txt) |
225 except (OSError, UnicodeError) as err: |
297 except (OSError, UnicodeError) as err: |
226 # Could not save the file! Skipping it. Reason: {0} |
298 # Could not save the file! Skipping it. Reason: {0} |
227 return ("FIXWRITE_ERROR", [str(err)]) |
299 return ("FIXWRITE_ERROR", [str(err)]) |
228 |
300 |
229 return None |
301 return None |
230 |
302 |
231 def __codeMatch(self, code): |
303 def __codeMatch(self, code): |
232 """ |
304 """ |
233 Private method to check, if the code should be fixed. |
305 Private method to check, if the code should be fixed. |
234 |
306 |
235 @param code to check (string) |
307 @param code to check (string) |
236 @return flag indicating it should be fixed (boolean) |
308 @return flag indicating it should be fixed (boolean) |
237 """ |
309 """ |
|
310 |
238 def mutualStartswith(a, b): |
311 def mutualStartswith(a, b): |
239 """ |
312 """ |
240 Local helper method to compare the beginnings of two strings |
313 Local helper method to compare the beginnings of two strings |
241 against each other. |
314 against each other. |
242 |
315 |
243 @return flag indicating that one string starts with the other |
316 @return flag indicating that one string starts with the other |
244 (boolean) |
317 (boolean) |
245 """ |
318 """ |
246 return b.startswith(a) or a.startswith(b) |
319 return b.startswith(a) or a.startswith(b) |
247 |
320 |
248 if ( |
321 if self.__noFixCodes and any( |
249 self.__noFixCodes and |
322 mutualStartswith(code.lower(), noFixCode.lower()) |
250 any(mutualStartswith(code.lower(), noFixCode.lower()) |
323 for noFixCode in [c.strip() for c in self.__noFixCodes] |
251 for noFixCode in [c.strip() for c in self.__noFixCodes]) |
|
252 ): |
324 ): |
253 return False |
325 return False |
254 |
326 |
255 if self.__fixCodes: |
327 if self.__fixCodes: |
256 return any(mutualStartswith(code.lower(), fixCode.lower()) |
328 return any( |
257 for fixCode in [c.strip() for c in self.__fixCodes]) |
329 mutualStartswith(code.lower(), fixCode.lower()) |
|
330 for fixCode in [c.strip() for c in self.__fixCodes] |
|
331 ) |
258 |
332 |
259 return True |
333 return True |
260 |
334 |
261 def fixIssue(self, line, pos, code): |
335 def fixIssue(self, line, pos, code): |
262 """ |
336 """ |
263 Public method to fix the fixable issues. |
337 Public method to fix the fixable issues. |
264 |
338 |
265 @param line line number of the issue |
339 @param line line number of the issue |
266 @type int |
340 @type int |
267 @param pos position inside line |
341 @param pos position inside line |
268 @type int |
342 @type int |
269 @param code code of the issue |
343 @param code code of the issue |
272 a message code for the fix, arguments list for the message |
346 a message code for the fix, arguments list for the message |
273 and an ID for a deferred fix |
347 and an ID for a deferred fix |
274 @rtype tuple of (int, str, list, int) |
348 @rtype tuple of (int, str, list, int) |
275 """ |
349 """ |
276 if ( |
350 if ( |
277 line <= len(self.__source) and |
351 line <= len(self.__source) |
278 self.__codeMatch(code) and |
352 and self.__codeMatch(code) |
279 code in self.__fixes |
353 and code in self.__fixes |
280 ): |
354 ): |
281 res = self.__fixes[code](code, line, pos) |
355 res = self.__fixes[code](code, line, pos) |
282 if res[0] == 1: |
356 if res[0] == 1: |
283 self.__modified = True |
357 self.__modified = True |
284 self.fixed += 1 |
358 self.fixed += 1 |
285 else: |
359 else: |
286 res = (0, "", [], 0) |
360 res = (0, "", [], 0) |
287 |
361 |
288 return res |
362 return res |
289 |
363 |
290 def finalize(self): |
364 def finalize(self): |
291 """ |
365 """ |
292 Public method to apply all deferred fixes. |
366 Public method to apply all deferred fixes. |
293 |
367 |
294 @return dictionary containing the fix results |
368 @return dictionary containing the fix results |
295 """ |
369 """ |
296 results = {} |
370 results = {} |
297 |
371 |
298 # step 1: do fixes operating on logical lines first |
372 # step 1: do fixes operating on logical lines first |
299 for id_, code, line, pos in self.__stackLogical: |
373 for id_, code, line, pos in self.__stackLogical: |
300 res, msg, args, _ = self.__fixes[code](code, line, pos, apply=True) |
374 res, msg, args, _ = self.__fixes[code](code, line, pos, apply=True) |
301 if res == 1: |
375 if res == 1: |
302 self.__modified = True |
376 self.__modified = True |
303 self.fixed += 1 |
377 self.fixed += 1 |
304 results[id_] = (res, msg, args) |
378 results[id_] = (res, msg, args) |
305 |
379 |
306 # step 2: do fixes that change the number of lines |
380 # step 2: do fixes that change the number of lines |
307 for id_, code, line, pos in reversed(self.__stack): |
381 for id_, code, line, pos in reversed(self.__stack): |
308 res, msg, args, _ = self.__fixes[code](code, line, pos, apply=True) |
382 res, msg, args, _ = self.__fixes[code](code, line, pos, apply=True) |
309 if res == 1: |
383 if res == 1: |
310 self.__modified = True |
384 self.__modified = True |
311 self.fixed += 1 |
385 self.fixed += 1 |
312 results[id_] = (res, msg, args) |
386 results[id_] = (res, msg, args) |
313 |
387 |
314 return results |
388 return results |
315 |
389 |
316 def __getID(self): |
390 def __getID(self): |
317 """ |
391 """ |
318 Private method to get the ID for a deferred fix. |
392 Private method to get the ID for a deferred fix. |
319 |
393 |
320 @return ID for a deferred fix (integer) |
394 @return ID for a deferred fix (integer) |
321 """ |
395 """ |
322 self.__lastID += 1 |
396 self.__lastID += 1 |
323 return self.__lastID |
397 return self.__lastID |
324 |
398 |
325 def __findLogical(self): |
399 def __findLogical(self): |
326 """ |
400 """ |
327 Private method to extract the index of all the starts and ends of |
401 Private method to extract the index of all the starts and ends of |
328 lines. |
402 lines. |
329 |
403 |
330 @return tuple containing two lists of integer with start and end tuples |
404 @return tuple containing two lists of integer with start and end tuples |
331 of lines |
405 of lines |
332 """ |
406 """ |
333 logical_start = [] |
407 logical_start = [] |
334 logical_end = [] |
408 logical_end = [] |
335 last_newline = True |
409 last_newline = True |
336 sio = StringIO("".join(self.__source)) |
410 sio = StringIO("".join(self.__source)) |
337 parens = 0 |
411 parens = 0 |
338 for t in tokenize.generate_tokens(sio.readline): |
412 for t in tokenize.generate_tokens(sio.readline): |
339 if t[0] in [tokenize.COMMENT, tokenize.DEDENT, |
413 if t[0] in [ |
340 tokenize.INDENT, tokenize.NL, |
414 tokenize.COMMENT, |
341 tokenize.ENDMARKER]: |
415 tokenize.DEDENT, |
|
416 tokenize.INDENT, |
|
417 tokenize.NL, |
|
418 tokenize.ENDMARKER, |
|
419 ]: |
342 continue |
420 continue |
343 if not parens and t[0] in [tokenize.NEWLINE, tokenize.SEMI]: |
421 if not parens and t[0] in [tokenize.NEWLINE, tokenize.SEMI]: |
344 last_newline = True |
422 last_newline = True |
345 logical_end.append((t[3][0] - 1, t[2][1])) |
423 logical_end.append((t[3][0] - 1, t[2][1])) |
346 continue |
424 continue |
347 if last_newline and not parens: |
425 if last_newline and not parens: |
348 logical_start.append((t[2][0] - 1, t[2][1])) |
426 logical_start.append((t[2][0] - 1, t[2][1])) |
349 last_newline = False |
427 last_newline = False |
350 if t[0] == tokenize.OP: |
428 if t[0] == tokenize.OP: |
351 if t[1] in '([{': |
429 if t[1] in "([{": |
352 parens += 1 |
430 parens += 1 |
353 elif t[1] in '}])': |
431 elif t[1] in "}])": |
354 parens -= 1 |
432 parens -= 1 |
355 return logical_start, logical_end |
433 return logical_start, logical_end |
356 |
434 |
357 def __getLogical(self, line, pos): |
435 def __getLogical(self, line, pos): |
358 """ |
436 """ |
359 Private method to get the logical line corresponding to the given |
437 Private method to get the logical line corresponding to the given |
360 position. |
438 position. |
361 |
439 |
362 @param line line number of the issue (integer) |
440 @param line line number of the issue (integer) |
363 @param pos position inside line (integer) |
441 @param pos position inside line (integer) |
364 @return tuple of a tuple of two integers giving the start of the |
442 @return tuple of a tuple of two integers giving the start of the |
365 logical line, another tuple of two integers giving the end |
443 logical line, another tuple of two integers giving the end |
366 of the logical line and a list of strings with the original |
444 of the logical line and a list of strings with the original |
380 le = x |
458 le = x |
381 ls = logical_start[i] |
459 ls = logical_start[i] |
382 break |
460 break |
383 if ls is None: |
461 if ls is None: |
384 return None |
462 return None |
385 |
463 |
386 original = self.__source[ls[0]:le[0] + 1] |
464 original = self.__source[ls[0] : le[0] + 1] |
387 return ls, le, original |
465 return ls, le, original |
388 |
466 |
389 def __getIndentWord(self): |
467 def __getIndentWord(self): |
390 """ |
468 """ |
391 Private method to determine the indentation type. |
469 Private method to determine the indentation type. |
392 |
470 |
393 @return string to be used for an indentation (string) |
471 @return string to be used for an indentation (string) |
394 """ |
472 """ |
395 sio = StringIO("".join(self.__source)) |
473 sio = StringIO("".join(self.__source)) |
396 indentWord = " " # default in case of failure |
474 indentWord = " " # default in case of failure |
397 with contextlib.suppress(SyntaxError, tokenize.TokenError): |
475 with contextlib.suppress(SyntaxError, tokenize.TokenError): |
398 for token in tokenize.generate_tokens(sio.readline): |
476 for token in tokenize.generate_tokens(sio.readline): |
399 if token[0] == tokenize.INDENT: |
477 if token[0] == tokenize.INDENT: |
400 indentWord = token[1] |
478 indentWord = token[1] |
401 break |
479 break |
402 return indentWord |
480 return indentWord |
403 |
481 |
404 def __getIndent(self, line): |
482 def __getIndent(self, line): |
405 """ |
483 """ |
406 Private method to get the indentation string. |
484 Private method to get the indentation string. |
407 |
485 |
408 @param line line to determine the indentation string from (string) |
486 @param line line to determine the indentation string from (string) |
409 @return indentation string (string) |
487 @return indentation string (string) |
410 """ |
488 """ |
411 return line.replace(line.lstrip(), "") |
489 return line.replace(line.lstrip(), "") |
412 |
490 |
413 def __multilineStringLines(self): |
491 def __multilineStringLines(self): |
414 """ |
492 """ |
415 Private method to determine the line numbers that are within multi line |
493 Private method to determine the line numbers that are within multi line |
416 strings and these which are part of a documentation string. |
494 strings and these which are part of a documentation string. |
417 |
495 |
418 @return tuple of a set of line numbers belonging to a multi line |
496 @return tuple of a set of line numbers belonging to a multi line |
419 string and a set of line numbers belonging to a multi line |
497 string and a set of line numbers belonging to a multi line |
420 documentation string (tuple of two set of integer) |
498 documentation string (tuple of two set of integer) |
421 """ |
499 """ |
422 if self.__multiLineNumbers is None: |
500 if self.__multiLineNumbers is None: |
423 source = "".join(self.__source) |
501 source = "".join(self.__source) |
424 sio = StringIO(source) |
502 sio = StringIO(source) |
425 self.__multiLineNumbers = set() |
503 self.__multiLineNumbers = set() |
426 self.__docLineNumbers = set() |
504 self.__docLineNumbers = set() |
427 previousTokenType = '' |
505 previousTokenType = "" |
428 with contextlib.suppress(SyntaxError, tokenize.TokenError): |
506 with contextlib.suppress(SyntaxError, tokenize.TokenError): |
429 for t in tokenize.generate_tokens(sio.readline): |
507 for t in tokenize.generate_tokens(sio.readline): |
430 tokenType = t[0] |
508 tokenType = t[0] |
431 startRow = t[2][0] |
509 startRow = t[2][0] |
432 endRow = t[3][0] |
510 endRow = t[3][0] |
433 |
511 |
434 if (tokenType == tokenize.STRING and startRow != endRow): |
512 if tokenType == tokenize.STRING and startRow != endRow: |
435 if previousTokenType != tokenize.INDENT: |
513 if previousTokenType != tokenize.INDENT: |
436 self.__multiLineNumbers |= set( |
514 self.__multiLineNumbers |= set(range(startRow, 1 + endRow)) |
437 range(startRow, 1 + endRow)) |
|
438 else: |
515 else: |
439 self.__docLineNumbers |= set( |
516 self.__docLineNumbers |= set(range(startRow, 1 + endRow)) |
440 range(startRow, 1 + endRow)) |
|
441 |
517 |
442 previousTokenType = tokenType |
518 previousTokenType = tokenType |
443 |
519 |
444 return self.__multiLineNumbers, self.__docLineNumbers |
520 return self.__multiLineNumbers, self.__docLineNumbers |
445 |
521 |
446 def __fixReindent(self, line, pos, logical): |
522 def __fixReindent(self, line, pos, logical): |
447 """ |
523 """ |
448 Private method to fix a badly indented line. |
524 Private method to fix a badly indented line. |
449 |
525 |
450 This is done by adding or removing from its initial indent only. |
526 This is done by adding or removing from its initial indent only. |
451 |
527 |
452 @param line line number of the issue (integer) |
528 @param line line number of the issue (integer) |
453 @param pos position inside line (integer) |
529 @param pos position inside line (integer) |
454 @param logical logical line structure |
530 @param logical logical line structure |
455 @return flag indicating a change was done (boolean) |
531 @return flag indicating a change was done (boolean) |
456 @exception ValueError raised to indicate a bad 'logical' parameter |
532 @exception ValueError raised to indicate a bad 'logical' parameter |
457 """ |
533 """ |
458 if not logical: |
534 if not logical: |
459 raise ValueError("Bad value for 'logical' parameter.") |
535 raise ValueError("Bad value for 'logical' parameter.") |
460 |
536 |
461 ls, _, original = logical |
537 ls, _, original = logical |
462 |
538 |
463 rewrapper = IndentationWrapper(original) |
539 rewrapper = IndentationWrapper(original) |
464 valid_indents = rewrapper.pep8Expected() |
540 valid_indents = rewrapper.pep8Expected() |
465 if not rewrapper.rel_indent: |
541 if not rewrapper.rel_indent: |
466 return False |
542 return False |
467 |
543 |
468 if line > ls[0]: |
544 if line > ls[0]: |
469 # got a valid continuation line number |
545 # got a valid continuation line number |
470 row = line - ls[0] - 1 |
546 row = line - ls[0] - 1 |
471 # always pick the first option for this |
547 # always pick the first option for this |
472 valid = valid_indents[row] |
548 valid = valid_indents[row] |
473 got = rewrapper.rel_indent[row] |
549 got = rewrapper.rel_indent[row] |
474 else: |
550 else: |
475 return False |
551 return False |
476 |
552 |
477 line1 = ls[0] + row |
553 line1 = ls[0] + row |
478 # always pick the expected indent, for now. |
554 # always pick the expected indent, for now. |
479 indent_to = valid[0] |
555 indent_to = valid[0] |
480 |
556 |
481 if got != indent_to: |
557 if got != indent_to: |
482 orig_line = self.__source[line1] |
558 orig_line = self.__source[line1] |
483 new_line = ' ' * (indent_to) + orig_line.lstrip() |
559 new_line = " " * (indent_to) + orig_line.lstrip() |
484 if new_line == orig_line: |
560 if new_line == orig_line: |
485 return False |
561 return False |
486 else: |
562 else: |
487 self.__source[line1] = new_line |
563 self.__source[line1] = new_line |
488 return True |
564 return True |
489 else: |
565 else: |
490 return False |
566 return False |
491 |
567 |
492 def __fixWhitespace(self, line, offset, replacement): |
568 def __fixWhitespace(self, line, offset, replacement): |
493 """ |
569 """ |
494 Private method to correct whitespace at the given offset. |
570 Private method to correct whitespace at the given offset. |
495 |
571 |
496 @param line line to be corrected (string) |
572 @param line line to be corrected (string) |
497 @param offset offset within line (integer) |
573 @param offset offset within line (integer) |
498 @param replacement replacement string (string) |
574 @param replacement replacement string (string) |
499 @return corrected line |
575 @return corrected line |
500 """ |
576 """ |
521 a message code for the fix, a list of arguments for the |
597 a message code for the fix, a list of arguments for the |
522 message and an ID for a deferred fix |
598 message and an ID for a deferred fix |
523 @rtype tuple of (int, str, list or int, int) |
599 @rtype tuple of (int, str, list or int, int) |
524 """ |
600 """ |
525 line -= 1 |
601 line -= 1 |
526 quotes = re.match(r"""\s*[ru]?('''|'|\")""", |
602 quotes = re.match(r"""\s*[ru]?('''|'|\")""", self.__source[line]).group(1) |
527 self.__source[line]).group(1) |
|
528 left, right = self.__source[line].split(quotes, 1) |
603 left, right = self.__source[line].split(quotes, 1) |
529 self.__source[line] = left + '"""' + right |
604 self.__source[line] = left + '"""' + right |
530 while line < len(self.__source): |
605 while line < len(self.__source): |
531 if self.__source[line].rstrip().endswith(quotes): |
606 if self.__source[line].rstrip().endswith(quotes): |
532 left, right = self.__source[line].rsplit(quotes, 1) |
607 left, right = self.__source[line].rsplit(quotes, 1) |
533 self.__source[line] = left + '"""' + right |
608 self.__source[line] = left + '"""' + right |
534 break |
609 break |
535 line += 1 |
610 line += 1 |
536 |
611 |
537 # Triple single quotes converted to triple double quotes. |
612 # Triple single quotes converted to triple double quotes. |
538 return (1, "FIXD111", [], 0) |
613 return (1, "FIXD111", [], 0) |
539 |
614 |
540 def __fixD112(self, code, line, pos): |
615 def __fixD112(self, code, line, pos): |
541 """ |
616 """ |
542 Private method to fix docstring 'r' in leading quotes. |
617 Private method to fix docstring 'r' in leading quotes. |
543 |
618 |
544 Codes: D112 |
619 Codes: D112 |
545 |
620 |
546 @param code code of the issue |
621 @param code code of the issue |
547 @type str |
622 @type str |
548 @param line line number of the issue |
623 @param line line number of the issue |
549 @type int |
624 @type int |
550 @param pos position inside line |
625 @param pos position inside line |
588 message and an ID for a deferred fix |
663 message and an ID for a deferred fix |
589 @rtype tuple of (int, str, list or int, int) |
664 @rtype tuple of (int, str, list or int, int) |
590 """ |
665 """ |
591 if apply: |
666 if apply: |
592 line -= 1 |
667 line -= 1 |
593 if not self.__source[line].lstrip().startswith( |
668 if not self.__source[line].lstrip().startswith(('"""', 'r"""', 'u"""')): |
594 ('"""', 'r"""', 'u"""')): |
|
595 # only correctly formatted docstrings will be fixed |
669 # only correctly formatted docstrings will be fixed |
596 return (0, "", [], 0) |
670 return (0, "", [], 0) |
597 |
671 |
598 docstring = ( |
672 docstring = self.__source[line].rstrip() + self.__source[line + 1].strip() |
599 self.__source[line].rstrip() + |
|
600 self.__source[line + 1].strip() |
|
601 ) |
|
602 if docstring.endswith('"""'): |
673 if docstring.endswith('"""'): |
603 docstring += self.__eol |
674 docstring += self.__eol |
604 else: |
675 else: |
605 docstring += self.__source[line + 2].lstrip() |
676 docstring += self.__source[line + 2].lstrip() |
606 self.__source[line + 2] = "" |
677 self.__source[line + 2] = "" |
607 |
678 |
608 self.__source[line] = docstring |
679 self.__source[line] = docstring |
609 self.__source[line + 1] = "" |
680 self.__source[line + 1] = "" |
610 # Single line docstring put on one line. |
681 # Single line docstring put on one line. |
611 return (1, "FIXD121", [], 0) |
682 return (1, "FIXD121", [], 0) |
612 else: |
683 else: |
613 fixId = self.__getID() |
684 fixId = self.__getID() |
614 self.__stack.append((fixId, code, line, pos)) |
685 self.__stack.append((fixId, code, line, pos)) |
615 return (-1, "", [], fixId) |
686 return (-1, "", [], fixId) |
616 |
687 |
617 def __fixD131(self, code, line, pos): |
688 def __fixD131(self, code, line, pos): |
618 """ |
689 """ |
619 Private method to fix a docstring summary not ending with a |
690 Private method to fix a docstring summary not ending with a |
620 period. |
691 period. |
621 |
692 |
622 Codes: D131 |
693 Codes: D131 |
623 |
694 |
624 @param code code of the issue |
695 @param code code of the issue |
625 @type str |
696 @type str |
626 @param line line number of the issue |
697 @param line line number of the issue |
627 @type int |
698 @type int |
628 @param pos position inside line |
699 @param pos position inside line |
632 message and an ID for a deferred fix |
703 message and an ID for a deferred fix |
633 @rtype tuple of (int, str, list or int, int) |
704 @rtype tuple of (int, str, list or int, int) |
634 """ |
705 """ |
635 line -= 1 |
706 line -= 1 |
636 newText = "" |
707 newText = "" |
637 if ( |
708 if self.__source[line].rstrip().endswith(('"""', "'''")) and self.__source[ |
638 self.__source[line].rstrip().endswith(('"""', "'''")) and |
709 line |
639 self.__source[line].lstrip().startswith(('"""', 'r"""', 'u"""')) |
710 ].lstrip().startswith(('"""', 'r"""', 'u"""')): |
640 ): |
|
641 # it is a one-liner |
711 # it is a one-liner |
642 newText = ( |
712 newText = ( |
643 self.__source[line].rstrip()[:-3].rstrip() + |
713 self.__source[line].rstrip()[:-3].rstrip() |
644 "." + |
714 + "." |
645 self.__source[line].rstrip()[-3:] + |
715 + self.__source[line].rstrip()[-3:] |
646 self.__eol |
716 + self.__eol |
647 ) |
717 ) |
648 else: |
718 else: |
649 if ( |
719 if line < len(self.__source) - 1 and ( |
650 line < len(self.__source) - 1 and |
720 not self.__source[line + 1].strip() |
651 (not self.__source[line + 1].strip() or |
721 or self.__source[line + 1].lstrip().startswith("@") |
652 self.__source[line + 1].lstrip().startswith("@") or |
722 or ( |
653 (self.__source[line + 1].strip() in ('"""', "'''") and |
723 self.__source[line + 1].strip() in ('"""', "'''") |
654 not self.__source[line].lstrip().startswith("@"))) |
724 and not self.__source[line].lstrip().startswith("@") |
|
725 ) |
655 ): |
726 ): |
656 newText = self.__source[line].rstrip() + "." + self.__eol |
727 newText = self.__source[line].rstrip() + "." + self.__eol |
657 |
728 |
658 if newText: |
729 if newText: |
659 self.__source[line] = newText |
730 self.__source[line] = newText |
660 # Period added to summary line. |
731 # Period added to summary line. |
661 return (1, "FIXD131", [], 0) |
732 return (1, "FIXD131", [], 0) |
662 else: |
733 else: |
663 return (0, "", [], 0) |
734 return (0, "", [], 0) |
664 |
735 |
665 def __fixD141(self, code, line, pos, apply=False): |
736 def __fixD141(self, code, line, pos, apply=False): |
666 """ |
737 """ |
667 Private method to fix a function/method docstring preceded by a |
738 Private method to fix a function/method docstring preceded by a |
668 blank line. |
739 blank line. |
669 |
740 |
670 Codes: D141 |
741 Codes: D141 |
671 |
742 |
672 @param code code of the issue |
743 @param code code of the issue |
673 @type str |
744 @type str |
674 @param line line number of the issue |
745 @param line line number of the issue |
675 @type int |
746 @type int |
676 @param pos position inside line |
747 @param pos position inside line |
1070 # fall back to simple method |
1134 # fall back to simple method |
1071 line -= 1 |
1135 line -= 1 |
1072 text = self.__source[line] |
1136 text = self.__source[line] |
1073 indentation = self.__getIndent(text) |
1137 indentation = self.__getIndent(text) |
1074 self.__source[line] = ( |
1138 self.__source[line] = ( |
1075 indentation + |
1139 indentation + self.__indentWord + text.lstrip() |
1076 self.__indentWord + text.lstrip() |
|
1077 ) |
1140 ) |
1078 # Missing indentation of continuation line corrected. |
1141 # Missing indentation of continuation line corrected. |
1079 return (1, "FIXE122", [], 0) |
1142 return (1, "FIXE122", [], 0) |
1080 return (0, "", [], 0) |
1143 return (0, "", [], 0) |
1081 else: |
1144 else: |
1082 fixId = self.__getID() |
1145 fixId = self.__getID() |
1083 self.__stackLogical.append((fixId, code, line, pos)) |
1146 self.__stackLogical.append((fixId, code, line, pos)) |
1084 return (-1, "", [], fixId) |
1147 return (-1, "", [], fixId) |
1085 |
1148 |
1086 def __fixE123(self, code, line, pos, apply=False): |
1149 def __fixE123(self, code, line, pos, apply=False): |
1087 """ |
1150 """ |
1088 Private method to fix the indentation of a closing bracket lines. |
1151 Private method to fix the indentation of a closing bracket lines. |
1089 |
1152 |
1090 Codes: E123 |
1153 Codes: E123 |
1091 |
1154 |
1092 @param code code of the issue |
1155 @param code code of the issue |
1093 @type str |
1156 @type str |
1094 @param line line number of the issue |
1157 @param line line number of the issue |
1095 @type int |
1158 @type int |
1096 @param pos position inside line |
1159 @param pos position inside line |
1152 modified = self.__fixReindent(line, pos, logical) |
1215 modified = self.__fixReindent(line, pos, logical) |
1153 if not modified: |
1216 if not modified: |
1154 row = line - 1 |
1217 row = line - 1 |
1155 text = self.__source[row] |
1218 text = self.__source[row] |
1156 self.__source[row] = ( |
1219 self.__source[row] = ( |
1157 self.__getIndent(text) + |
1220 self.__getIndent(text) + self.__indentWord + text.lstrip() |
1158 self.__indentWord + text.lstrip() |
|
1159 ) |
1221 ) |
1160 # Indentation level changed. |
1222 # Indentation level changed. |
1161 return (1, "FIXE125", [], 0) |
1223 return (1, "FIXE125", [], 0) |
1162 return (0, "", [], 0) |
1224 return (0, "", [], 0) |
1163 else: |
1225 else: |
1164 fixId = self.__getID() |
1226 fixId = self.__getID() |
1165 self.__stackLogical.append((fixId, code, line, pos)) |
1227 self.__stackLogical.append((fixId, code, line, pos)) |
1166 return (-1, "", [], fixId) |
1228 return (-1, "", [], fixId) |
1167 |
1229 |
1168 def __fixE126(self, code, line, pos, apply=False): |
1230 def __fixE126(self, code, line, pos, apply=False): |
1169 """ |
1231 """ |
1170 Private method to fix over-indented/under-indented hanging |
1232 Private method to fix over-indented/under-indented hanging |
1171 indentation. |
1233 indentation. |
1172 |
1234 |
1173 Codes: E126, E133 |
1235 Codes: E126, E133 |
1174 |
1236 |
1175 @param code code of the issue |
1237 @param code code of the issue |
1176 @type str |
1238 @type str |
1177 @param line line number of the issue |
1239 @param line line number of the issue |
1178 @type int |
1240 @type int |
1179 @param pos position inside line |
1241 @param pos position inside line |
1290 message and an ID for a deferred fix |
1353 message and an ID for a deferred fix |
1291 @rtype tuple of (int, str, list or int, int) |
1354 @rtype tuple of (int, str, list or int, int) |
1292 """ |
1355 """ |
1293 line -= 1 |
1356 line -= 1 |
1294 text = self.__source[line] |
1357 text = self.__source[line] |
1295 |
1358 |
1296 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): |
1359 if '"""' in text or "'''" in text or text.rstrip().endswith("\\"): |
1297 return (0, "", [], 0) |
1360 return (0, "", [], 0) |
1298 |
1361 |
1299 newText = self.__fixWhitespace(text, pos, '') |
1362 newText = self.__fixWhitespace(text, pos, "") |
1300 if newText == text: |
1363 if newText == text: |
1301 return (0, "", [], 0) |
1364 return (0, "", [], 0) |
1302 |
1365 |
1303 self.__source[line] = newText |
1366 self.__source[line] = newText |
1304 # Extraneous whitespace removed. |
1367 # Extraneous whitespace removed. |
1305 return (1, "FIXE201", [], 0) |
1368 return (1, "FIXE201", [], 0) |
1306 |
1369 |
1307 def __fixE221(self, code, line, pos): |
1370 def __fixE221(self, code, line, pos): |
1308 """ |
1371 """ |
1309 Private method to fix extraneous whitespace around operator or |
1372 Private method to fix extraneous whitespace around operator or |
1310 keyword. |
1373 keyword. |
1311 |
1374 |
1312 Codes: E221, E222, E223, E224, E241, E242, E271, E272, E273, E274 |
1375 Codes: E221, E222, E223, E224, E241, E242, E271, E272, E273, E274 |
1313 |
1376 |
1314 @param code code of the issue |
1377 @param code code of the issue |
1315 @type str |
1378 @type str |
1316 @param line line number of the issue |
1379 @param line line number of the issue |
1317 @type int |
1380 @type int |
1318 @param pos position inside line |
1381 @param pos position inside line |
1322 message and an ID for a deferred fix |
1385 message and an ID for a deferred fix |
1323 @rtype tuple of (int, str, list or int, int) |
1386 @rtype tuple of (int, str, list or int, int) |
1324 """ |
1387 """ |
1325 line -= 1 |
1388 line -= 1 |
1326 text = self.__source[line] |
1389 text = self.__source[line] |
1327 |
1390 |
1328 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): |
1391 if '"""' in text or "'''" in text or text.rstrip().endswith("\\"): |
1329 return (0, "", [], 0) |
1392 return (0, "", [], 0) |
1330 |
1393 |
1331 newText = self.__fixWhitespace(text, pos, ' ') |
1394 newText = self.__fixWhitespace(text, pos, " ") |
1332 if newText == text: |
1395 if newText == text: |
1333 return (0, "", [], 0) |
1396 return (0, "", [], 0) |
1334 |
1397 |
1335 self.__source[line] = newText |
1398 self.__source[line] = newText |
1336 return (1, "FIXE221", [], 0) |
1399 return (1, "FIXE221", [], 0) |
1337 |
1400 |
1338 def __fixE225(self, code, line, pos): |
1401 def __fixE225(self, code, line, pos): |
1339 """ |
1402 """ |
1340 Private method to fix extraneous whitespaces around operator. |
1403 Private method to fix extraneous whitespaces around operator. |
1341 |
1404 |
1342 Codes: E225, E226, E227, E228 |
1405 Codes: E225, E226, E227, E228 |
1343 |
1406 |
1344 @param code code of the issue |
1407 @param code code of the issue |
1345 @type str |
1408 @type str |
1346 @param line line number of the issue |
1409 @param line line number of the issue |
1347 @type int |
1410 @type int |
1348 @param pos position inside line |
1411 @param pos position inside line |
1352 message and an ID for a deferred fix |
1415 message and an ID for a deferred fix |
1353 @rtype tuple of (int, str, list or int, int) |
1416 @rtype tuple of (int, str, list or int, int) |
1354 """ |
1417 """ |
1355 line -= 1 |
1418 line -= 1 |
1356 text = self.__source[line] |
1419 text = self.__source[line] |
1357 |
1420 |
1358 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): |
1421 if '"""' in text or "'''" in text or text.rstrip().endswith("\\"): |
1359 return (0, "", [], 0) |
1422 return (0, "", [], 0) |
1360 |
1423 |
1361 newText = text |
1424 newText = text |
1362 # determine length of operator |
1425 # determine length of operator |
1363 tokens = '<>*/=^&|%!+-' |
1426 tokens = "<>*/=^&|%!+-" |
1364 pos2 = pos |
1427 pos2 = pos |
1365 token_delimiter = len(tokens) |
1428 token_delimiter = len(tokens) |
1366 for _ in range(3): |
1429 for _ in range(3): |
1367 if pos2 < len(text) and text[pos2] in tokens[:token_delimiter]: |
1430 if pos2 < len(text) and text[pos2] in tokens[:token_delimiter]: |
1368 pos2 += 1 |
1431 pos2 += 1 |
1369 # only the first five could be repeated |
1432 # only the first five could be repeated |
1370 token_delimiter = 5 |
1433 token_delimiter = 5 |
1371 else: |
1434 else: |
1372 break |
1435 break |
1373 if pos2 < len(text) and text[pos2] not in ' \t': |
1436 if pos2 < len(text) and text[pos2] not in " \t": |
1374 newText = self.__fixWhitespace(newText, pos2, ' ') |
1437 newText = self.__fixWhitespace(newText, pos2, " ") |
1375 newText = self.__fixWhitespace(newText, pos, ' ') |
1438 newText = self.__fixWhitespace(newText, pos, " ") |
1376 if newText == text: |
1439 if newText == text: |
1377 return (0, "", [], 0) |
1440 return (0, "", [], 0) |
1378 |
1441 |
1379 self.__source[line] = newText |
1442 self.__source[line] = newText |
1380 # Missing whitespaces added. |
1443 # Missing whitespaces added. |
1381 return (1, "FIXE225", [], 0) |
1444 return (1, "FIXE225", [], 0) |
1382 |
1445 |
1383 def __fixE231(self, code, line, pos): |
1446 def __fixE231(self, code, line, pos): |
1384 """ |
1447 """ |
1385 Private method to fix missing whitespace after ',;:'. |
1448 Private method to fix missing whitespace after ',;:'. |
1386 |
1449 |
1387 Codes: E231 |
1450 Codes: E231 |
1388 |
1451 |
1389 @param code code of the issue |
1452 @param code code of the issue |
1390 @type str |
1453 @type str |
1391 @param line line number of the issue |
1454 @param line line number of the issue |
1392 @type int |
1455 @type int |
1393 @param pos position inside line |
1456 @param pos position inside line |
1425 message and an ID for a deferred fix |
1486 message and an ID for a deferred fix |
1426 @rtype tuple of (int, str, list or int, int) |
1487 @rtype tuple of (int, str, list or int, int) |
1427 """ |
1488 """ |
1428 line -= 1 |
1489 line -= 1 |
1429 text = self.__source[line] |
1490 text = self.__source[line] |
1430 |
1491 |
1431 # This is necessary since pycodestyle sometimes reports columns that |
1492 # This is necessary since pycodestyle sometimes reports columns that |
1432 # goes past the end of the physical line. This happens in cases like, |
1493 # goes past the end of the physical line. This happens in cases like, |
1433 # foo(bar\n=None) |
1494 # foo(bar\n=None) |
1434 col = min(pos, len(text) - 1) |
1495 col = min(pos, len(text) - 1) |
1435 newText = ( |
1496 newText = ( |
1436 text |
1497 text if text[col].strip() else text[:col].rstrip() + text[col:].lstrip() |
1437 if text[col].strip() else |
|
1438 text[:col].rstrip() + text[col:].lstrip() |
|
1439 ) |
1498 ) |
1440 |
1499 |
1441 # There could be an escaped newline |
1500 # There could be an escaped newline |
1442 if newText.endswith(('=\\\n', '=\\\r\n', '=\\\r')): |
1501 if newText.endswith(("=\\\n", "=\\\r\n", "=\\\r")): |
1443 self.__source[line] = newText.rstrip("\n\r \t\\") |
1502 self.__source[line] = newText.rstrip("\n\r \t\\") |
1444 self.__source[line + 1] = self.__source[line + 1].lstrip() |
1503 self.__source[line + 1] = self.__source[line + 1].lstrip() |
1445 else: |
1504 else: |
1446 self.__source[line] = newText |
1505 self.__source[line] = newText |
1447 # Extraneous whitespace removed. |
1506 # Extraneous whitespace removed. |
1448 return (1, "FIXE251", [], 0) |
1507 return (1, "FIXE251", [], 0) |
1449 |
1508 |
1450 def __fixE261(self, code, line, pos): |
1509 def __fixE261(self, code, line, pos): |
1451 """ |
1510 """ |
1452 Private method to fix whitespace before or after inline comment. |
1511 Private method to fix whitespace before or after inline comment. |
1453 |
1512 |
1454 Codes: E261, E262 |
1513 Codes: E261, E262 |
1455 |
1514 |
1456 @param code code of the issue |
1515 @param code code of the issue |
1457 @type str |
1516 @type str |
1458 @param line line number of the issue |
1517 @param line line number of the issue |
1459 @type int |
1518 @type int |
1460 @param pos position inside line |
1519 @param pos position inside line |
1464 message and an ID for a deferred fix |
1523 message and an ID for a deferred fix |
1465 @rtype tuple of (int, str, list or int, int) |
1524 @rtype tuple of (int, str, list or int, int) |
1466 """ |
1525 """ |
1467 line -= 1 |
1526 line -= 1 |
1468 text = self.__source[line] |
1527 text = self.__source[line] |
1469 left = text[:pos].rstrip(' \t#') |
1528 left = text[:pos].rstrip(" \t#") |
1470 right = text[pos:].lstrip(' \t#') |
1529 right = text[pos:].lstrip(" \t#") |
1471 newText = left + (" # " + right if right.strip() else right) |
1530 newText = left + (" # " + right if right.strip() else right) |
1472 self.__source[line] = newText |
1531 self.__source[line] = newText |
1473 # Whitespace around comment sign corrected. |
1532 # Whitespace around comment sign corrected. |
1474 return (1, "FIXE261", [], 0) |
1533 return (1, "FIXE261", [], 0) |
1475 |
1534 |
1476 def __fixBlankLinesBefore(self, code, line, pos, apply=False): |
1535 def __fixBlankLinesBefore(self, code, line, pos, apply=False): |
1477 """ |
1536 """ |
1478 Private method to fix the need for blank lines before class, function |
1537 Private method to fix the need for blank lines before class, function |
1479 and method definitions. |
1538 and method definitions. |
1480 |
1539 |
1481 Codes: E301, E302, E303, E305, E306, E307, E308 |
1540 Codes: E301, E302, E303, E305, E306, E307, E308 |
1482 |
1541 |
1483 @param code code of the issue |
1542 @param code code of the issue |
1484 @type str |
1543 @type str |
1485 @param line line number of the issue |
1544 @param line line number of the issue |
1486 @type int |
1545 @type int |
1487 @param pos position inside line |
1546 @param pos position inside line |
1592 if apply: |
1651 if apply: |
1593 line -= 1 |
1652 line -= 1 |
1594 text = self.__source[line] |
1653 text = self.__source[line] |
1595 if not text.lstrip().startswith("import"): |
1654 if not text.lstrip().startswith("import"): |
1596 return (0, "", [], 0) |
1655 return (0, "", [], 0) |
1597 |
1656 |
1598 # pycodestyle (1.3.1) reports false positive if there is an import |
1657 # pycodestyle (1.3.1) reports false positive if there is an import |
1599 # statement followed by a semicolon and some unrelated |
1658 # statement followed by a semicolon and some unrelated |
1600 # statement with commas in it. |
1659 # statement with commas in it. |
1601 if ';' in text: |
1660 if ";" in text: |
1602 return (0, "", [], 0) |
1661 return (0, "", [], 0) |
1603 |
1662 |
1604 newText = ( |
1663 newText = ( |
1605 text[:pos].rstrip("\t ,") + |
1664 text[:pos].rstrip("\t ,") |
1606 self.__eol + |
1665 + self.__eol |
1607 self.__getIndent(text) + |
1666 + self.__getIndent(text) |
1608 "import " + |
1667 + "import " |
1609 text[pos:].lstrip("\t ,") |
1668 + text[pos:].lstrip("\t ,") |
1610 ) |
1669 ) |
1611 self.__source[line] = newText |
1670 self.__source[line] = newText |
1612 # Imports were put on separate lines. |
1671 # Imports were put on separate lines. |
1613 return (1, "FIXE401", [], 0) |
1672 return (1, "FIXE401", [], 0) |
1614 else: |
1673 else: |
1615 fixId = self.__getID() |
1674 fixId = self.__getID() |
1616 self.__stack.append((fixId, code, line, pos)) |
1675 self.__stack.append((fixId, code, line, pos)) |
1617 return (-1, "", [], fixId) |
1676 return (-1, "", [], fixId) |
1618 |
1677 |
1619 def __fixE501(self, code, line, pos, apply=False): |
1678 def __fixE501(self, code, line, pos, apply=False): |
1620 """ |
1679 """ |
1621 Private method to fix the long lines by breaking them. |
1680 Private method to fix the long lines by breaking them. |
1622 |
1681 |
1623 Codes: E501 |
1682 Codes: E501 |
1624 |
1683 |
1625 @param code code of the issue |
1684 @param code code of the issue |
1626 @type str |
1685 @type str |
1627 @param line line number of the issue |
1686 @param line line number of the issue |
1628 @type int |
1687 @type int |
1629 @param pos position inside line |
1688 @param pos position inside line |
1792 message and an ID for a deferred fix |
1853 message and an ID for a deferred fix |
1793 @rtype tuple of (int, str, list or int, int) |
1854 @rtype tuple of (int, str, list or int, int) |
1794 """ |
1855 """ |
1795 line -= 1 |
1856 line -= 1 |
1796 text = self.__source[line] |
1857 text = self.__source[line] |
1797 |
1858 |
1798 rightPos = pos + 2 |
1859 rightPos = pos + 2 |
1799 if rightPos >= len(text): |
1860 if rightPos >= len(text): |
1800 return (0, "", 0) |
1861 return (0, "", 0) |
1801 |
1862 |
1802 left = text[:pos].rstrip() |
1863 left = text[:pos].rstrip() |
1803 center = text[pos:rightPos] |
1864 center = text[pos:rightPos] |
1804 right = text[rightPos:].lstrip() |
1865 right = text[rightPos:].lstrip() |
1805 |
1866 |
1806 if not right.startswith(("None", "True", "False")): |
1867 if not right.startswith(("None", "True", "False")): |
1807 return (0, "", [], 0) |
1868 return (0, "", [], 0) |
1808 |
1869 |
1809 if center.strip() == "==": |
1870 if center.strip() == "==": |
1810 center = "is" |
1871 center = "is" |
1811 elif center.strip() == "!=": |
1872 elif center.strip() == "!=": |
1812 center = "is not" |
1873 center = "is not" |
1813 else: |
1874 else: |
1814 return (0, "", [], 0) |
1875 return (0, "", [], 0) |
1815 |
1876 |
1816 self.__source[line] = " ".join([left, center, right]) |
1877 self.__source[line] = " ".join([left, center, right]) |
1817 # Comparison to None/True/False corrected. |
1878 # Comparison to None/True/False corrected. |
1818 return (1, "FIXE711", [], 0) |
1879 return (1, "FIXE711", [], 0) |
1819 |
1880 |
1820 def __fixN804(self, code, line, pos, apply=False): |
1881 def __fixN804(self, code, line, pos, apply=False): |
1821 """ |
1882 """ |
1822 Private method to fix a wrong first argument of normal and |
1883 Private method to fix a wrong first argument of normal and |
1823 class methods. |
1884 class methods. |
1824 |
1885 |
1825 Codes: N804, N805 |
1886 Codes: N804, N805 |
1826 |
1887 |
1827 @param code code of the issue |
1888 @param code code of the issue |
1828 @type str |
1889 @type str |
1829 @param line line number of the issue |
1890 @param line line number of the issue |
1830 @type int |
1891 @type int |
1831 @param pos position inside line |
1892 @param pos position inside line |
1921 right = right[4:] |
1982 right = right[4:] |
1922 arg = "self" |
1983 arg = "self" |
1923 right = right.lstrip(", ") |
1984 right = right.lstrip(", ") |
1924 if right.startswith("):"): |
1985 if right.startswith("):"): |
1925 # merge with previous line |
1986 # merge with previous line |
1926 self.__source[line - 1] = ( |
1987 self.__source[line - 1] = self.__source[line - 1].rstrip() + right |
1927 self.__source[line - 1].rstrip() + right |
|
1928 ) |
|
1929 self.__source[line] = "" |
1988 self.__source[line] = "" |
1930 else: |
1989 else: |
1931 self.__source[line] = indent + right |
1990 self.__source[line] = indent + right |
1932 |
1991 |
1933 # '{0}' argument removed. |
1992 # '{0}' argument removed. |
1934 return (1, "FIXN806", [arg], 0) |
1993 return (1, "FIXN806", [arg], 0) |
1935 else: |
1994 else: |
1936 fixId = self.__getID() |
1995 fixId = self.__getID() |
1937 self.__stack.append((fixId, code, line, pos)) |
1996 self.__stack.append((fixId, code, line, pos)) |
1938 return (-1, "", [], fixId) |
1997 return (-1, "", [], fixId) |
1939 |
1998 |
1940 def __fixW291(self, code, line, pos): |
1999 def __fixW291(self, code, line, pos): |
1941 """ |
2000 """ |
1942 Private method to fix trailing whitespace. |
2001 Private method to fix trailing whitespace. |
1943 |
2002 |
1944 Codes: W291, W293 |
2003 Codes: W291, W293 |
1945 |
2004 |
1946 @param code code of the issue |
2005 @param code code of the issue |
1947 @type str |
2006 @type str |
1948 @param line line number of the issue |
2007 @param line line number of the issue |
1949 @type int |
2008 @type int |
1950 @param pos position inside line |
2009 @param pos position inside line |
1951 @type int |
2010 @type int |
1952 @return value indicating an applied/deferred fix (-1, 0, 1), |
2011 @return value indicating an applied/deferred fix (-1, 0, 1), |
1953 a message code for the fix, a list of arguments for the |
2012 a message code for the fix, a list of arguments for the |
1954 message and an ID for a deferred fix |
2013 message and an ID for a deferred fix |
1955 @rtype tuple of (int, str, list or int, int) |
2014 @rtype tuple of (int, str, list or int, int) |
1956 """ |
2015 """ |
1957 self.__source[line - 1] = re.sub(r'[\t ]+(\r?)$', r"\1", |
2016 self.__source[line - 1] = re.sub( |
1958 self.__source[line - 1]) |
2017 r"[\t ]+(\r?)$", r"\1", self.__source[line - 1] |
|
2018 ) |
1959 # Whitespace stripped from end of line. |
2019 # Whitespace stripped from end of line. |
1960 return (1, "FIXW291", [], 0) |
2020 return (1, "FIXW291", [], 0) |
1961 |
2021 |
1962 def __fixW292(self, code, line, pos): |
2022 def __fixW292(self, code, line, pos): |
1963 """ |
2023 """ |
1964 Private method to fix a missing newline at the end of file. |
2024 Private method to fix a missing newline at the end of file. |
1965 |
2025 |
1966 Codes: W292 |
2026 Codes: W292 |
1967 |
2027 |
1968 @param code code of the issue |
2028 @param code code of the issue |
1969 @type str |
2029 @type str |
1970 @param line line number of the issue |
2030 @param line line number of the issue |
1971 @type int |
2031 @type int |
1972 @param pos position inside line |
2032 @param pos position inside line |
2034 Class to reindent badly-indented code to uniformly use four-space |
2094 Class to reindent badly-indented code to uniformly use four-space |
2035 indentation. |
2095 indentation. |
2036 |
2096 |
2037 Released to the public domain, by Tim Peters, 03 October 2000. |
2097 Released to the public domain, by Tim Peters, 03 October 2000. |
2038 """ |
2098 """ |
|
2099 |
2039 def __init__(self, sourceLines): |
2100 def __init__(self, sourceLines): |
2040 """ |
2101 """ |
2041 Constructor |
2102 Constructor |
2042 |
2103 |
2043 @param sourceLines list of source lines including eol marker |
2104 @param sourceLines list of source lines including eol marker |
2044 (list of string) |
2105 (list of string) |
2045 """ |
2106 """ |
2046 # Raw file lines. |
2107 # Raw file lines. |
2047 self.raw = sourceLines |
2108 self.raw = sourceLines |
2048 self.after = [] |
2109 self.after = [] |
2049 |
2110 |
2050 # File lines, rstripped & tab-expanded. Dummy at start is so |
2111 # File lines, rstripped & tab-expanded. Dummy at start is so |
2051 # that we can use tokenize's 1-based line numbering easily. |
2112 # that we can use tokenize's 1-based line numbering easily. |
2052 # Note that a line is all-blank iff it's "\n". |
2113 # Note that a line is all-blank iff it's "\n". |
2053 self.lines = [line.rstrip().expandtabs() + "\n" |
2114 self.lines = [line.rstrip().expandtabs() + "\n" for line in self.raw] |
2054 for line in self.raw] |
|
2055 self.lines.insert(0, None) |
2115 self.lines.insert(0, None) |
2056 self.index = 1 # index into self.lines of next line |
2116 self.index = 1 # index into self.lines of next line |
2057 |
2117 |
2058 # List of (lineno, indentlevel) pairs, one for each stmt and |
2118 # List of (lineno, indentlevel) pairs, one for each stmt and |
2059 # comment line. indentlevel is -1 for comment lines, as a |
2119 # comment line. indentlevel is -1 for comment lines, as a |
2060 # signal that tokenize doesn't know what to do about them; |
2120 # signal that tokenize doesn't know what to do about them; |
2061 # indeed, they're our headache! |
2121 # indeed, they're our headache! |
2062 self.stats = [] |
2122 self.stats = [] |
2063 |
2123 |
2064 def run(self): |
2124 def run(self): |
2065 """ |
2125 """ |
2066 Public method to run the re-indenter. |
2126 Public method to run the re-indenter. |
2067 |
2127 |
2068 @return flag indicating that a change was done (boolean) |
2128 @return flag indicating that a change was done (boolean) |
2069 """ |
2129 """ |
2070 try: |
2130 try: |
2071 stats = self.__genStats(tokenize.generate_tokens(self.getline)) |
2131 stats = self.__genStats(tokenize.generate_tokens(self.getline)) |
2072 except (SyntaxError, tokenize.TokenError): |
2132 except (SyntaxError, tokenize.TokenError): |
2073 return False |
2133 return False |
2074 |
2134 |
2075 # Remove trailing empty lines. |
2135 # Remove trailing empty lines. |
2076 lines = self.lines |
2136 lines = self.lines |
2077 while lines and lines[-1] == "\n": |
2137 while lines and lines[-1] == "\n": |
2078 lines.pop() |
2138 lines.pop() |
2079 # Sentinel. |
2139 # Sentinel. |
2380 # must be in hanging indent |
2450 # must be in hanging indent |
2381 hang = rel_indent[open_row] + 4 |
2451 hang = rel_indent[open_row] + 4 |
2382 vi.append(indent_level + hang) |
2452 vi.append(indent_level + hang) |
2383 |
2453 |
2384 # about the best we can do without look-ahead |
2454 # about the best we can do without look-ahead |
2385 if (indent_next and vi[0] == indent_level + 4 and |
2455 if indent_next and vi[0] == indent_level + 4 and nrows == row + 1: |
2386 nrows == row + 1): |
|
2387 vi[0] += 4 |
2456 vi[0] += 4 |
2388 |
2457 |
2389 if add_second_chances: |
2458 if add_second_chances: |
2390 # visual indenters like to line things up. |
2459 # visual indenters like to line things up. |
2391 min_indent = vi[0] |
2460 min_indent = vi[0] |
2392 for col, what in indent_chances.items(): |
2461 for col, what in indent_chances.items(): |
2393 if col > min_indent and ( |
2462 if col > min_indent and ( |
2394 what is True or |
2463 what is True |
2395 (what == str and token_type == tokenize.STRING) or |
2464 or (what == str and token_type == tokenize.STRING) |
2396 (what == text and token_type == tokenize.OP) |
2465 or (what == text and token_type == tokenize.OP) |
2397 ): |
2466 ): |
2398 vi.append(col) |
2467 vi.append(col) |
2399 vi = sorted(vi) |
2468 vi = sorted(vi) |
2400 |
2469 |
2401 valid_indents[row] = vi |
2470 valid_indents[row] = vi |
2402 |
2471 |
2403 # Returning to original continuation_line_indentation() from |
2472 # Returning to original continuation_line_indentation() from |
2404 # pycodestyle. |
2473 # pycodestyle. |
2405 visual_indent = indent_chances.get(start[1]) |
2474 visual_indent = indent_chances.get(start[1]) |
2406 last_indent = start |
2475 last_indent = start |
2407 rel_indent[row] = ( |
2476 rel_indent[row] = pycodestyle.expand_indent(line) - indent_level |
2408 pycodestyle.expand_indent(line) - indent_level |
|
2409 ) |
|
2410 hang = rel_indent[row] - rel_indent[open_row] |
2477 hang = rel_indent[row] - rel_indent[open_row] |
2411 |
2478 |
2412 if token_type == tokenize.OP and text in ']})': |
2479 if token_type == tokenize.OP and text in "]})": |
2413 pass |
2480 pass |
2414 elif visual_indent is True and not indent[depth]: |
2481 elif visual_indent is True and not indent[depth]: |
2415 indent[depth] = start[1] |
2482 indent[depth] = start[1] |
2416 |
2483 |
2417 # line altered: comments shouldn't define a visual indent |
2484 # line altered: comments shouldn't define a visual indent |
2418 if parens[row] and not indent[depth] and token_type not in ( |
2485 if ( |
2419 tokenize.NL, tokenize.COMMENT |
2486 parens[row] |
|
2487 and not indent[depth] |
|
2488 and token_type not in (tokenize.NL, tokenize.COMMENT) |
2420 ): |
2489 ): |
2421 indent[depth] = start[1] |
2490 indent[depth] = start[1] |
2422 indent_chances[start[1]] = True |
2491 indent_chances[start[1]] = True |
2423 elif token_type == tokenize.STRING or text in ( |
2492 elif token_type == tokenize.STRING or text in ("u", "ur", "b", "br"): |
2424 'u', 'ur', 'b', 'br' |
|
2425 ): |
|
2426 indent_chances[start[1]] = str |
2493 indent_chances[start[1]] = str |
2427 |
2494 |
2428 if token_type == tokenize.OP: |
2495 if token_type == tokenize.OP: |
2429 if text in '([{': |
2496 if text in "([{": |
2430 depth += 1 |
2497 depth += 1 |
2431 indent.append(0) |
2498 indent.append(0) |
2432 parens[row] += 1 |
2499 parens[row] += 1 |
2433 elif text in ')]}' and depth > 0: |
2500 elif text in ")]}" and depth > 0: |
2434 prev_indent = indent.pop() or last_indent[1] |
2501 prev_indent = indent.pop() or last_indent[1] |
2435 for d in range(depth): |
2502 for d in range(depth): |
2436 if indent[d] > prev_indent: |
2503 if indent[d] > prev_indent: |
2437 indent[d] = 0 |
2504 indent[d] = 0 |
2438 for ind in list(indent_chances): |
2505 for ind in list(indent_chances): |
2476 self.__nextText = nextLine |
2552 self.__nextText = nextLine |
2477 self.__maxLength = maxLength |
2553 self.__maxLength = maxLength |
2478 self.__eol = eol |
2554 self.__eol = eol |
2479 self.__indentWord = indentWord |
2555 self.__indentWord = indentWord |
2480 self.__isDocString = isDocString |
2556 self.__isDocString = isDocString |
2481 |
2557 |
2482 def shorten(self): |
2558 def shorten(self): |
2483 """ |
2559 """ |
2484 Public method to shorten the line wrapped by the class instance. |
2560 Public method to shorten the line wrapped by the class instance. |
2485 |
2561 |
2486 @return tuple of a flag indicating successful shortening, the |
2562 @return tuple of a flag indicating successful shortening, the |
2487 shortened line and the changed next line (boolean, string, string) |
2563 shortened line and the changed next line (boolean, string, string) |
2488 """ |
2564 """ |
2489 # 1. check for comment |
2565 # 1. check for comment |
2490 if self.__text.lstrip().startswith('#'): |
2566 if self.__text.lstrip().startswith("#"): |
2491 lastComment = True |
2567 lastComment = True |
2492 if self.__nextText.lstrip().startswith('#'): |
2568 if self.__nextText.lstrip().startswith("#"): |
2493 lastComment = False |
2569 lastComment = False |
2494 |
2570 |
2495 # Wrap commented lines. |
2571 # Wrap commented lines. |
2496 newText = self.__shortenComment(lastComment) |
2572 newText = self.__shortenComment(lastComment) |
2497 if newText == self.__text: |
2573 if newText == self.__text: |
2498 return False, "", "" |
2574 return False, "", "" |
2499 else: |
2575 else: |
2500 return True, newText, "" |
2576 return True, newText, "" |
2501 elif '#' in self.__text: |
2577 elif "#" in self.__text: |
2502 pos = self.__text.rfind("#") |
2578 pos = self.__text.rfind("#") |
2503 newText = ( |
2579 newText = ( |
2504 self.__text[:pos].rstrip() + |
2580 self.__text[:pos].rstrip() |
2505 self.__eol + |
2581 + self.__eol |
2506 self.__getIndent(self.__text) + |
2582 + self.__getIndent(self.__text) |
2507 self.__text[pos:] |
2583 + self.__text[pos:] |
2508 ) |
2584 ) |
2509 if newText == self.__text: |
2585 if newText == self.__text: |
2510 return False, "", "" |
2586 return False, "", "" |
2511 else: |
2587 else: |
2512 return True, newText, "" |
2588 return True, newText, "" |
2583 else: |
2654 else: |
2584 return False, "", "" |
2655 return False, "", "" |
2585 |
2656 |
2586 # Handle statements by putting the right hand side on a line by itself. |
2657 # Handle statements by putting the right hand side on a line by itself. |
2587 # This should let the next pass shorten it. |
2658 # This should let the next pass shorten it. |
2588 if source.startswith('return '): |
2659 if source.startswith("return "): |
2589 newText = ( |
2660 newText = ( |
2590 indent + |
2661 indent |
2591 'return (' + |
2662 + "return (" |
2592 self.__eol + |
2663 + self.__eol |
2593 indent + self.__indentWord + re.sub('^return ', '', source) + |
2664 + indent |
2594 indent + ')' + self.__eol |
2665 + self.__indentWord |
|
2666 + re.sub("^return ", "", source) |
|
2667 + indent |
|
2668 + ")" |
|
2669 + self.__eol |
2595 ) |
2670 ) |
2596 return True, newText, "" |
2671 return True, newText, "" |
2597 |
2672 |
2598 candidates = self.__shortenLine(tokens, source, indent) |
2673 candidates = self.__shortenLine(tokens, source, indent) |
2599 if candidates: |
2674 if candidates: |
2600 candidates = sorted( |
2675 candidates = sorted( |
2601 set(candidates).union([self.__text]), |
2676 set(candidates).union([self.__text]), |
2602 key=lambda x: self.__lineShorteningRank(x)) |
2677 key=lambda x: self.__lineShorteningRank(x), |
|
2678 ) |
2603 if candidates[0] == self.__text: |
2679 if candidates[0] == self.__text: |
2604 return False, "", "" |
2680 return False, "", "" |
2605 return True, candidates[0], "" |
2681 return True, candidates[0], "" |
2606 |
2682 |
2607 source = self.__text |
2683 source = self.__text |
2608 rs = source.rstrip() |
2684 rs = source.rstrip() |
2609 if rs.endswith(("'", '"')) and " " in source: |
2685 if rs.endswith(("'", '"')) and " " in source: |
2610 if rs.endswith(('"""', "'''")): |
2686 if rs.endswith(('"""', "'''")): |
2611 quote = rs[-3:] |
2687 quote = rs[-3:] |
2614 blank = source.rfind(" ") |
2690 blank = source.rfind(" ") |
2615 maxLen = self.__maxLength - 2 - len(quote) |
2691 maxLen = self.__maxLength - 2 - len(quote) |
2616 while blank > maxLen and blank != -1: |
2692 while blank > maxLen and blank != -1: |
2617 blank = source.rfind(" ", 0, blank) |
2693 blank = source.rfind(" ", 0, blank) |
2618 if blank != -1: |
2694 if blank != -1: |
2619 if source[blank + 1:].startswith(quote): |
2695 if source[blank + 1 :].startswith(quote): |
2620 first = source[:maxLen] |
2696 first = source[:maxLen] |
2621 second = source[maxLen:] |
2697 second = source[maxLen:] |
2622 else: |
2698 else: |
2623 first = source[:blank] |
2699 first = source[:blank] |
2624 second = source[blank + 1:] |
2700 second = source[blank + 1 :] |
2625 return ( |
2701 return ( |
2626 True, |
2702 True, |
2627 first + quote + " \\" + self.__eol + |
2703 first |
2628 indent + self.__indentWord + quote + second, |
2704 + quote |
2629 "") |
2705 + " \\" |
|
2706 + self.__eol |
|
2707 + indent |
|
2708 + self.__indentWord |
|
2709 + quote |
|
2710 + second, |
|
2711 "", |
|
2712 ) |
2630 else: |
2713 else: |
2631 # Cannot break |
2714 # Cannot break |
2632 return False, "", "" |
2715 return False, "", "" |
2633 |
2716 |
2634 return False, "", "" |
2717 return False, "", "" |
2635 |
2718 |
2636 def __shortenComment(self, isLast): |
2719 def __shortenComment(self, isLast): |
2637 """ |
2720 """ |
2638 Private method to shorten a comment line. |
2721 Private method to shorten a comment line. |
2639 |
2722 |
2640 @param isLast flag indicating, that the line is the last comment line |
2723 @param isLast flag indicating, that the line is the last comment line |
2641 (boolean) |
2724 (boolean) |
2642 @return shortened comment line (string) |
2725 @return shortened comment line (string) |
2643 """ |
2726 """ |
2644 if len(self.__text) <= self.__maxLength: |
2727 if len(self.__text) <= self.__maxLength: |
2645 return self.__text |
2728 return self.__text |
2646 |
2729 |
2647 newText = self.__text.rstrip() |
2730 newText = self.__text.rstrip() |
2648 |
2731 |
2649 # PEP 8 recommends 72 characters for comment text. |
2732 # PEP 8 recommends 72 characters for comment text. |
2650 indentation = self.__getIndent(newText) + '# ' |
2733 indentation = self.__getIndent(newText) + "# " |
2651 maxLength = min(self.__maxLength, |
2734 maxLength = min(self.__maxLength, len(indentation) + 72) |
2652 len(indentation) + 72) |
|
2653 |
2735 |
2654 MIN_CHARACTER_REPEAT = 5 |
2736 MIN_CHARACTER_REPEAT = 5 |
2655 if ( |
2737 if ( |
2656 len(newText) - len(newText.rstrip(newText[-1])) >= |
2738 len(newText) - len(newText.rstrip(newText[-1])) >= MIN_CHARACTER_REPEAT |
2657 MIN_CHARACTER_REPEAT and |
2739 and not newText[-1].isalnum() |
2658 not newText[-1].isalnum() |
|
2659 ): |
2740 ): |
2660 # Trim comments that end with things like --------- |
2741 # Trim comments that end with things like --------- |
2661 return newText[:maxLength] + self.__eol |
2742 return newText[:maxLength] + self.__eol |
2662 elif isLast and re.match(r"\s*#+\s*\w+", newText): |
2743 elif isLast and re.match(r"\s*#+\s*\w+", newText): |
2663 import textwrap |
2744 import textwrap |
2664 splitLines = textwrap.wrap(newText.lstrip(" \t#"), |
2745 |
2665 initial_indent=indentation, |
2746 splitLines = textwrap.wrap( |
2666 subsequent_indent=indentation, |
2747 newText.lstrip(" \t#"), |
2667 width=maxLength, |
2748 initial_indent=indentation, |
2668 break_long_words=False, |
2749 subsequent_indent=indentation, |
2669 break_on_hyphens=False) |
2750 width=maxLength, |
|
2751 break_long_words=False, |
|
2752 break_on_hyphens=False, |
|
2753 ) |
2670 return self.__eol.join(splitLines) + self.__eol |
2754 return self.__eol.join(splitLines) + self.__eol |
2671 else: |
2755 else: |
2672 return newText + self.__eol |
2756 return newText + self.__eol |
2673 |
2757 |
2674 def __breakMultiline(self): |
2758 def __breakMultiline(self): |
2675 """ |
2759 """ |
2676 Private method to break multi line strings. |
2760 Private method to break multi line strings. |
2677 |
2761 |
2678 @return tuple of the shortened line and the changed next line |
2762 @return tuple of the shortened line and the changed next line |
2679 (string, string) |
2763 (string, string) |
2680 """ |
2764 """ |
2681 indentation = self.__getIndent(self.__text) |
2765 indentation = self.__getIndent(self.__text) |
2682 |
2766 |
2683 # Handle special case. |
2767 # Handle special case. |
2684 for symbol in '([{': |
2768 for symbol in "([{": |
2685 # Only valid if symbol is not on a line by itself. |
2769 # Only valid if symbol is not on a line by itself. |
2686 if ( |
2770 if ( |
2687 symbol in self.__text and |
2771 symbol in self.__text |
2688 self.__text.strip() != symbol and |
2772 and self.__text.strip() != symbol |
2689 self.__text.rstrip().endswith((',', '%')) |
2773 and self.__text.rstrip().endswith((",", "%")) |
2690 ): |
2774 ): |
2691 index = 1 + self.__text.find(symbol) |
2775 index = 1 + self.__text.find(symbol) |
2692 |
2776 |
2693 if index <= len(self.__indentWord) + len(indentation): |
2777 if index <= len(self.__indentWord) + len(indentation): |
2694 continue |
2778 continue |
2695 |
2779 |
2696 if self.__isProbablyInsideStringOrComment( |
2780 if self.__isProbablyInsideStringOrComment(self.__text, index - 1): |
2697 self.__text, index - 1): |
|
2698 continue |
2781 continue |
2699 |
2782 |
2700 return (self.__text[:index].rstrip() + self.__eol + |
2783 return ( |
2701 indentation + self.__indentWord + |
2784 self.__text[:index].rstrip() |
2702 self.__text[index:].lstrip(), "") |
2785 + self.__eol |
2703 |
2786 + indentation |
|
2787 + self.__indentWord |
|
2788 + self.__text[index:].lstrip(), |
|
2789 "", |
|
2790 ) |
|
2791 |
2704 newText = self.__text |
2792 newText = self.__text |
2705 newNext = self.__nextText |
2793 newNext = self.__nextText |
2706 blank = newText.rfind(" ") |
2794 blank = newText.rfind(" ") |
2707 while blank > self.__maxLength and blank != -1: |
2795 while blank > self.__maxLength and blank != -1: |
2708 blank = newText.rfind(" ", 0, blank) |
2796 blank = newText.rfind(" ", 0, blank) |
2750 pos = line.find(quote) |
2832 pos = line.find(quote) |
2751 if pos != -1 and pos <= index: |
2833 if pos != -1 and pos <= index: |
2752 return True |
2834 return True |
2753 |
2835 |
2754 # Check against being in a comment. |
2836 # Check against being in a comment. |
2755 pos = line.find('#') |
2837 pos = line.find("#") |
2756 if pos != -1 and pos <= index: |
2838 if pos != -1 and pos <= index: |
2757 return True |
2839 return True |
2758 |
2840 |
2759 return False |
2841 return False |
2760 |
2842 |
2761 def __shortenLine(self, tokens, source, indent): |
2843 def __shortenLine(self, tokens, source, indent): |
2762 """ |
2844 """ |
2763 Private method to shorten a line of code at an operator. |
2845 Private method to shorten a line of code at an operator. |
2764 |
2846 |
2765 @param tokens tokens of the line as generated by tokenize |
2847 @param tokens tokens of the line as generated by tokenize |
2766 (list of token) |
2848 (list of token) |
2767 @param source code string to work at (string) |
2849 @param source code string to work at (string) |
2768 @param indent indentation string of the code line (string) |
2850 @param indent indentation string of the code line (string) |
2769 @return list of candidates (list of string) |
2851 @return list of candidates (list of string) |
2770 """ |
2852 """ |
2771 candidates = [] |
2853 candidates = [] |
2772 |
2854 |
2773 for tkn in tokens: |
2855 for tkn in tokens: |
2774 tokenType = tkn[0] |
2856 tokenType = tkn[0] |
2775 tokenString = tkn[1] |
2857 tokenString = tkn[1] |
2776 |
2858 |
2777 if ( |
2859 if tokenType == tokenize.COMMENT and not self.__prevText.rstrip().endswith( |
2778 tokenType == tokenize.COMMENT and |
2860 "\\" |
2779 not self.__prevText.rstrip().endswith('\\') |
|
2780 ): |
2861 ): |
2781 # Move inline comments to previous line. |
2862 # Move inline comments to previous line. |
2782 offset = tkn[2][1] |
2863 offset = tkn[2][1] |
2783 first = source[:offset] |
2864 first = source[:offset] |
2784 second = source[offset:] |
2865 second = source[offset:] |
2785 candidates.append( |
2866 candidates.append( |
2786 indent + second.strip() + self.__eol + |
2867 indent |
2787 indent + first.strip() + self.__eol) |
2868 + second.strip() |
2788 elif tokenType == tokenize.OP and tokenString != '=': |
2869 + self.__eol |
|
2870 + indent |
|
2871 + first.strip() |
|
2872 + self.__eol |
|
2873 ) |
|
2874 elif tokenType == tokenize.OP and tokenString != "=": |
2789 # Don't break on '=' after keyword as this violates PEP 8. |
2875 # Don't break on '=' after keyword as this violates PEP 8. |
2790 offset = tkn[2][1] + 1 |
2876 offset = tkn[2][1] + 1 |
2791 first = source[:offset] |
2877 first = source[:offset] |
2792 |
2878 |
2793 secondIndent = indent |
2879 secondIndent = indent |
2794 if first.rstrip().endswith('('): |
2880 if first.rstrip().endswith("("): |
2795 secondIndent += self.__indentWord |
2881 secondIndent += self.__indentWord |
2796 elif '(' in first: |
2882 elif "(" in first: |
2797 secondIndent += ' ' * (1 + first.find('(')) |
2883 secondIndent += " " * (1 + first.find("(")) |
2798 else: |
2884 else: |
2799 secondIndent += self.__indentWord |
2885 secondIndent += self.__indentWord |
2800 |
2886 |
2801 second = (secondIndent + source[offset:].lstrip()) |
2887 second = secondIndent + source[offset:].lstrip() |
2802 if not second.strip(): |
2888 if not second.strip(): |
2803 continue |
2889 continue |
2804 |
2890 |
2805 # Do not begin a line with a comma |
2891 # Do not begin a line with a comma |
2806 if second.lstrip().startswith(','): |
2892 if second.lstrip().startswith(","): |
2807 continue |
2893 continue |
2808 |
2894 |
2809 # Do end a line with a dot |
2895 # Do end a line with a dot |
2810 if first.rstrip().endswith('.'): |
2896 if first.rstrip().endswith("."): |
2811 continue |
2897 continue |
2812 |
2898 |
2813 if tokenString in '+-*/,': |
2899 if tokenString in "+-*/,": |
2814 newText = first + ' \\' + self.__eol + second |
2900 newText = first + " \\" + self.__eol + second |
2815 else: |
2901 else: |
2816 newText = first + self.__eol + second |
2902 newText = first + self.__eol + second |
2817 |
2903 |
2818 # Only fix if syntax is okay. |
2904 # Only fix if syntax is okay. |
2819 if self.__checkSyntax(self.__normalizeMultiline(newText)): |
2905 if self.__checkSyntax(self.__normalizeMultiline(newText)): |
2820 candidates.append(indent + newText) |
2906 candidates.append(indent + newText) |
2821 |
2907 |
2822 return candidates |
2908 return candidates |
2823 |
2909 |
2824 def __normalizeMultiline(self, text): |
2910 def __normalizeMultiline(self, text): |
2825 """ |
2911 """ |
2826 Private method to remove multiline-related code that will cause syntax |
2912 Private method to remove multiline-related code that will cause syntax |
2827 error. |
2913 error. |
2828 |
2914 |
2829 @param text code line to work on (string) |
2915 @param text code line to work on (string) |
2830 @return normalized code line (string) |
2916 @return normalized code line (string) |
2831 """ |
2917 """ |
2832 for quote in '\'"': |
2918 for quote in "'\"": |
2833 dictPattern = r"^{q}[^{q}]*{q} *: *".format(q=quote) |
2919 dictPattern = r"^{q}[^{q}]*{q} *: *".format(q=quote) |
2834 if re.match(dictPattern, text): |
2920 if re.match(dictPattern, text): |
2835 if not text.strip().endswith('}'): |
2921 if not text.strip().endswith("}"): |
2836 text += '}' |
2922 text += "}" |
2837 return '{' + text |
2923 return "{" + text |
2838 |
2924 |
2839 if text.startswith('def ') and text.rstrip().endswith(':'): |
2925 if text.startswith("def ") and text.rstrip().endswith(":"): |
2840 # Do not allow ':' to be alone. That is invalid. |
2926 # Do not allow ':' to be alone. That is invalid. |
2841 splitText = [item.strip() for item in text.split(self.__eol)] |
2927 splitText = [item.strip() for item in text.split(self.__eol)] |
2842 if ':' not in splitText and 'def' not in splitText: |
2928 if ":" not in splitText and "def" not in splitText: |
2843 return text[len('def'):].strip().rstrip(':') |
2929 return text[len("def") :].strip().rstrip(":") |
2844 |
2930 |
2845 return text |
2931 return text |
2846 |
2932 |
2847 def __lineShorteningRank(self, candidate): |
2933 def __lineShorteningRank(self, candidate): |
2848 """ |
2934 """ |
2849 Private method to rank a candidate. |
2935 Private method to rank a candidate. |
2850 |
2936 |
2851 @param candidate candidate line to rank (string) |
2937 @param candidate candidate line to rank (string) |
2852 @return rank of the candidate (integer) |
2938 @return rank of the candidate (integer) |
2853 """ |
2939 """ |
2854 rank = 0 |
2940 rank = 0 |
2855 if candidate.strip(): |
2941 if candidate.strip(): |
2856 if candidate == self.__text: |
2942 if candidate == self.__text: |
2857 # give the original a disadvantage |
2943 # give the original a disadvantage |
2858 rank += 50 |
2944 rank += 50 |
2859 |
2945 |
2860 lines = candidate.split(self.__eol) |
2946 lines = candidate.split(self.__eol) |
2861 |
2947 |
2862 offset = 0 |
2948 offset = 0 |
2863 if lines[0].rstrip()[-1] not in '([{': |
2949 if lines[0].rstrip()[-1] not in "([{": |
2864 for symbol in '([{': |
2950 for symbol in "([{": |
2865 offset = max(offset, 1 + lines[0].find(symbol)) |
2951 offset = max(offset, 1 + lines[0].find(symbol)) |
2866 |
2952 |
2867 maxLength = max(offset + len(x.strip()) for x in lines) |
2953 maxLength = max(offset + len(x.strip()) for x in lines) |
2868 rank += maxLength |
2954 rank += maxLength |
2869 rank += len(lines) |
2955 rank += len(lines) |
2870 |
2956 |
2871 badStartingSymbol = { |
2957 badStartingSymbol = {"(": ")", "[": "]", "{": "}"}.get(lines[0][-1], None) |
2872 '(': ')', |
|
2873 '[': ']', |
|
2874 '{': '}'}.get(lines[0][-1], None) |
|
2875 |
2958 |
2876 if ( |
2959 if ( |
2877 len(lines) > 1 and |
2960 len(lines) > 1 |
2878 badStartingSymbol and |
2961 and badStartingSymbol |
2879 lines[1].lstrip().startswith(badStartingSymbol) |
2962 and lines[1].lstrip().startswith(badStartingSymbol) |
2880 ): |
2963 ): |
2881 rank += 20 |
2964 rank += 20 |
2882 |
2965 |
2883 if re.match(r".*[+\-\*/] \($", lines[0]): |
2966 if re.match(r".*[+\-\*/] \($", lines[0]): |
2884 # "1 * (\n" is ugly as hell. |
2967 # "1 * (\n" is ugly as hell. |
2885 rank += 100 |
2968 rank += 100 |
2886 |
2969 |
2887 for currentLine in lines: |
2970 for currentLine in lines: |
2888 for badStart in ['.', '%', '+', '-', '/']: |
2971 for badStart in [".", "%", "+", "-", "/"]: |
2889 if currentLine.startswith(badStart): |
2972 if currentLine.startswith(badStart): |
2890 rank += 100 |
2973 rank += 100 |
2891 |
2974 |
2892 for ending in '([{': |
2975 for ending in "([{": |
2893 # Avoid lonely opening. They result in longer lines. |
2976 # Avoid lonely opening. They result in longer lines. |
2894 if ( |
2977 if currentLine.endswith(ending) and len(currentLine.strip()) <= len( |
2895 currentLine.endswith(ending) and |
2978 self.__indentWord |
2896 len(currentLine.strip()) <= len(self.__indentWord) |
|
2897 ): |
2979 ): |
2898 rank += 100 |
2980 rank += 100 |
2899 |
2981 |
2900 if currentLine.endswith('%'): |
2982 if currentLine.endswith("%"): |
2901 rank -= 20 |
2983 rank -= 20 |
2902 |
2984 |
2903 # Try to break list comprehensions at the "for". |
2985 # Try to break list comprehensions at the "for". |
2904 if currentLine.lstrip().startswith('for'): |
2986 if currentLine.lstrip().startswith("for"): |
2905 rank -= 50 |
2987 rank -= 50 |
2906 |
2988 |
2907 rank += 10 * self.__countUnbalancedBrackets(currentLine) |
2989 rank += 10 * self.__countUnbalancedBrackets(currentLine) |
2908 else: |
2990 else: |
2909 rank = 100000 |
2991 rank = 100000 |
2910 |
2992 |
2911 return max(0, rank) |
2993 return max(0, rank) |
2912 |
2994 |
2913 def __countUnbalancedBrackets(self, line): |
2995 def __countUnbalancedBrackets(self, line): |
2914 """ |
2996 """ |
2915 Private method to determine the number of unmatched open/close |
2997 Private method to determine the number of unmatched open/close |
2916 brackets. |
2998 brackets. |
2917 |
2999 |
2918 @param line line to work at (string) |
3000 @param line line to work at (string) |
2919 @return number of unmatched open/close brackets (integer) |
3001 @return number of unmatched open/close brackets (integer) |
2920 """ |
3002 """ |
2921 count = 0 |
3003 count = 0 |
2922 for opening, closing in ['()', '[]', '{}']: |
3004 for opening, closing in ["()", "[]", "{}"]: |
2923 # __IGNORE_WARNING_M613__ |
3005 # __IGNORE_WARNING_M613__ |
2924 count += abs(line.count(opening) - line.count(closing)) |
3006 count += abs(line.count(opening) - line.count(closing)) |
2925 |
3007 |
2926 return count |
3008 return count |
2927 |
3009 |
2928 def __getIndent(self, line): |
3010 def __getIndent(self, line): |
2929 """ |
3011 """ |
2930 Private method to get the indentation string. |
3012 Private method to get the indentation string. |
2931 |
3013 |
2932 @param line line to determine the indentation string from (string) |
3014 @param line line to determine the indentation string from (string) |
2933 @return indentation string (string) |
3015 @return indentation string (string) |
2934 """ |
3016 """ |
2935 # copied from CodeStyleFixer |
3017 # copied from CodeStyleFixer |
2936 return line.replace(line.lstrip(), "") |
3018 return line.replace(line.lstrip(), "") |
2937 |
3019 |
2938 def __checkSyntax(self, code): |
3020 def __checkSyntax(self, code): |
2939 """ |
3021 """ |
2940 Private method to check the syntax of the given code fragment. |
3022 Private method to check the syntax of the given code fragment. |
2941 |
3023 |
2942 @param code code fragment to check (string) |
3024 @param code code fragment to check (string) |
2943 @return flag indicating syntax is ok (boolean) |
3025 @return flag indicating syntax is ok (boolean) |
2944 """ |
3026 """ |
2945 code = code.replace("\r\n", "\n").replace("\r", "\n") |
3027 code = code.replace("\r\n", "\n").replace("\r", "\n") |
2946 try: |
3028 try: |
2947 return compile(code, '<string>', 'exec') |
3029 return compile(code, "<string>", "exec") |
2948 except (SyntaxError, TypeError, UnicodeDecodeError): |
3030 except (SyntaxError, TypeError, UnicodeDecodeError): |
2949 return False |
3031 return False |