src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleFixer.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
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
69 @param backup flag indicating to create a backup before fixing 141 @param backup flag indicating to create a backup before fixing
70 anything 142 anything
71 @type bool 143 @type bool
72 """ 144 """
73 super().__init__() 145 super().__init__()
74 146
75 self.__filename = filename 147 self.__filename = filename
76 self.__origName = "" 148 self.__origName = ""
77 self.__source = sourceLines[:] # save a copy 149 self.__source = sourceLines[:] # save a copy
78 self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()] 150 self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()]
79 self.__noFixCodes = [ 151 self.__noFixCodes = [c.strip() for c in noFixCodes.split(",") if c.strip()]
80 c.strip() for c in noFixCodes.split(",") if c.strip()]
81 self.__maxLineLength = maxLineLength 152 self.__maxLineLength = maxLineLength
82 self.__blankLines = { 153 self.__blankLines = {
83 "toplevel": blankLines[0], 154 "toplevel": blankLines[0],
84 "method": blankLines[1], 155 "method": blankLines[1],
85 } 156 }
86 self.fixed = 0 157 self.fixed = 0
87 158
88 self.__reindenter = None 159 self.__reindenter = None
89 self.__indentWord = self.__getIndentWord() 160 self.__indentWord = self.__getIndentWord()
90 161
91 if inPlace: 162 if inPlace:
92 self.__createBackup = backup 163 self.__createBackup = backup
93 else: 164 else:
94 self.__origName = self.__filename 165 self.__origName = self.__filename
95 self.__filename = os.path.join( 166 self.__filename = os.path.join(
96 os.path.dirname(self.__filename), 167 os.path.dirname(self.__filename),
97 "fixed_" + os.path.basename(self.__filename)) 168 "fixed_" + os.path.basename(self.__filename),
169 )
98 self.__createBackup = False 170 self.__createBackup = False
99 self.__eol = eol 171 self.__eol = eol
100 172
101 self.__fixes = { 173 self.__fixes = {
102 "D111": self.__fixD111, 174 "D111": self.__fixD111,
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 """
502 right = line[offset:].lstrip(" \t") 578 right = line[offset:].lstrip(" \t")
503 if right.startswith("#"): 579 if right.startswith("#"):
504 return line 580 return line
505 else: 581 else:
506 return left + replacement + right 582 return left + replacement + right
507 583
508 def __fixD111(self, code, line, pos): 584 def __fixD111(self, code, line, pos):
509 """ 585 """
510 Private method to fix docstring enclosed in wrong quotes. 586 Private method to fix docstring enclosed in wrong quotes.
511 587
512 Codes: D111 588 Codes: D111
513 589
514 @param code code of the issue 590 @param code code of the issue
515 @type str 591 @type str
516 @param line line number of the issue 592 @param line line number of the issue
517 @type int 593 @type int
518 @param pos position inside line 594 @param pos position inside line
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
557 line -= 1 632 line -= 1
558 if code == "D112": 633 if code == "D112":
559 insertChar = "r" 634 insertChar = "r"
560 else: 635 else:
561 return (0, "", 0) 636 return (0, "", 0)
562 637
563 newText = ( 638 newText = (
564 self.__getIndent(self.__source[line]) + 639 self.__getIndent(self.__source[line])
565 insertChar + 640 + insertChar
566 self.__source[line].lstrip() 641 + self.__source[line].lstrip()
567 ) 642 )
568 self.__source[line] = newText 643 self.__source[line] = newText
569 # Introductory quotes corrected to be {0}""" 644 # Introductory quotes corrected to be {0}"""
570 return (1, 'FIXD112', [insertChar], 0) 645 return (1, "FIXD112", [insertChar], 0)
571 646
572 def __fixD121(self, code, line, pos, apply=False): 647 def __fixD121(self, code, line, pos, apply=False):
573 """ 648 """
574 Private method to fix a single line docstring on multiple lines. 649 Private method to fix a single line docstring on multiple lines.
575 650
576 Codes: D121 651 Codes: D121
577 652
578 @param code code of the issue 653 @param code code of the issue
579 @type str 654 @type str
580 @param line line number of the issue 655 @param line line number of the issue
581 @type int 656 @type int
582 @param pos position inside line 657 @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
689 return (1, "FIXD141", [], 0) 760 return (1, "FIXD141", [], 0)
690 else: 761 else:
691 fixId = self.__getID() 762 fixId = self.__getID()
692 self.__stack.append((fixId, code, line, pos)) 763 self.__stack.append((fixId, code, line, pos))
693 return (-1, "", [], fixId) 764 return (-1, "", [], fixId)
694 765
695 def __fixD142(self, code, line, pos, apply=False): 766 def __fixD142(self, code, line, pos, apply=False):
696 """ 767 """
697 Private method to fix a class docstring not preceded by a 768 Private method to fix a class docstring not preceded by a
698 blank line. 769 blank line.
699 770
700 Codes: D142 771 Codes: D142
701 772
702 @param code code of the issue 773 @param code code of the issue
703 @type str 774 @type str
704 @param line line number of the issue 775 @param line line number of the issue
705 @type int 776 @type int
706 @param pos position inside line 777 @param pos position inside line
719 return (1, "FIXD142", [], 0) 790 return (1, "FIXD142", [], 0)
720 else: 791 else:
721 fixId = self.__getID() 792 fixId = self.__getID()
722 self.__stack.append((fixId, code, line, pos)) 793 self.__stack.append((fixId, code, line, pos))
723 return (-1, "", [], fixId) 794 return (-1, "", [], fixId)
724 795
725 def __fixD143(self, code, line, pos, apply=False): 796 def __fixD143(self, code, line, pos, apply=False):
726 """ 797 """
727 Private method to fix a class docstring not followed by a 798 Private method to fix a class docstring not followed by a
728 blank line. 799 blank line.
729 800
730 Codes: D143 801 Codes: D143
731 802
732 @param code code of the issue 803 @param code code of the issue
733 @type str 804 @type str
734 @param line line number of the issue 805 @param line line number of the issue
735 @type int 806 @type int
736 @param pos position inside line 807 @param pos position inside line
749 return (1, "FIXD143", [], 0) 820 return (1, "FIXD143", [], 0)
750 else: 821 else:
751 fixId = self.__getID() 822 fixId = self.__getID()
752 self.__stack.append((fixId, code, line, pos)) 823 self.__stack.append((fixId, code, line, pos))
753 return (-1, "", [], fixId) 824 return (-1, "", [], fixId)
754 825
755 def __fixD144(self, code, line, pos, apply=False): 826 def __fixD144(self, code, line, pos, apply=False):
756 """ 827 """
757 Private method to fix a docstring summary not followed by a 828 Private method to fix a docstring summary not followed by a
758 blank line. 829 blank line.
759 830
760 Codes: D144 831 Codes: D144
761 832
762 @param code code of the issue 833 @param code code of the issue
763 @type str 834 @type str
764 @param line line number of the issue 835 @param line line number of the issue
765 @type int 836 @type int
766 @param pos position inside line 837 @param pos position inside line
775 if apply: 846 if apply:
776 line -= 1 847 line -= 1
777 if not self.__source[line].rstrip().endswith("."): 848 if not self.__source[line].rstrip().endswith("."):
778 # only correct summary lines can be fixed here 849 # only correct summary lines can be fixed here
779 return (0, "", 0) 850 return (0, "", 0)
780 851
781 self.__source[line] += self.__eol 852 self.__source[line] += self.__eol
782 # Blank line inserted after docstring summary. 853 # Blank line inserted after docstring summary.
783 return (1, "FIXD144", [], 0) 854 return (1, "FIXD144", [], 0)
784 else: 855 else:
785 fixId = self.__getID() 856 fixId = self.__getID()
786 self.__stack.append((fixId, code, line, pos)) 857 self.__stack.append((fixId, code, line, pos))
787 return (-1, "", [], fixId) 858 return (-1, "", [], fixId)
788 859
789 def __fixD145(self, code, line, pos, apply=False): 860 def __fixD145(self, code, line, pos, apply=False):
790 """ 861 """
791 Private method to fix the last paragraph of a multi-line docstring 862 Private method to fix the last paragraph of a multi-line docstring
792 not followed by a blank line. 863 not followed by a blank line.
793 864
794 Codes: D143 865 Codes: D143
795 866
796 @param code code of the issue 867 @param code code of the issue
797 @type str 868 @type str
798 @param line line number of the issue 869 @param line line number of the issue
799 @type int 870 @type int
800 @param pos position inside line 871 @param pos position inside line
813 return (1, "FIXD145", [], 0) 884 return (1, "FIXD145", [], 0)
814 else: 885 else:
815 fixId = self.__getID() 886 fixId = self.__getID()
816 self.__stack.append((fixId, code, line, pos)) 887 self.__stack.append((fixId, code, line, pos))
817 return (-1, "", [], fixId) 888 return (-1, "", [], fixId)
818 889
819 def __fixD221(self, code, line, pos, apply=False): 890 def __fixD221(self, code, line, pos, apply=False):
820 """ 891 """
821 Private method to fix leading and trailing quotes of docstring 892 Private method to fix leading and trailing quotes of docstring
822 not on separate lines. 893 not on separate lines.
823 894
824 Codes: D221, D222 895 Codes: D221, D222
825 896
826 @param code code of the issue 897 @param code code of the issue
827 @type str 898 @type str
828 @param line line number of the issue 899 @param line line number of the issue
829 @type int 900 @type int
830 @param pos position inside line 901 @param pos position inside line
847 else: 918 else:
848 first, second = source[:3], source[3:].strip() 919 first, second = source[:3], source[3:].strip()
849 else: 920 else:
850 # trailing 921 # trailing
851 first, second = source[:-3].strip(), source[-3:] 922 first, second = source[:-3].strip(), source[-3:]
852 newText = ( 923 newText = indent + first + self.__eol + indent + second + self.__eol
853 indent +
854 first +
855 self.__eol +
856 indent +
857 second +
858 self.__eol
859 )
860 self.__source[line] = newText 924 self.__source[line] = newText
861 if code == "D221": 925 if code == "D221":
862 # Leading quotes put on separate line. 926 # Leading quotes put on separate line.
863 msg = "FIXD221" 927 msg = "FIXD221"
864 else: 928 else:
867 return (1, msg, [], 0) 931 return (1, msg, [], 0)
868 else: 932 else:
869 fixId = self.__getID() 933 fixId = self.__getID()
870 self.__stack.append((fixId, code, line, pos)) 934 self.__stack.append((fixId, code, line, pos))
871 return (-1, "", [], fixId) 935 return (-1, "", [], fixId)
872 936
873 def __fixD242(self, code, line, pos, apply=False): 937 def __fixD242(self, code, line, pos, apply=False):
874 """ 938 """
875 Private method to fix a class or function/method docstring preceded 939 Private method to fix a class or function/method docstring preceded
876 by a blank line. 940 by a blank line.
877 941
878 Codes: D242, D244 942 Codes: D242, D244
879 943
880 @param code code of the issue 944 @param code code of the issue
881 @type str 945 @type str
882 @param line line number of the issue 946 @param line line number of the issue
883 @type int 947 @type int
884 @param pos position inside line 948 @param pos position inside line
902 return (1, msg, [], 0) 966 return (1, msg, [], 0)
903 else: 967 else:
904 fixId = self.__getID() 968 fixId = self.__getID()
905 self.__stack.append((fixId, code, line, pos)) 969 self.__stack.append((fixId, code, line, pos))
906 return (-1, "", [], fixId) 970 return (-1, "", [], fixId)
907 971
908 def __fixD243(self, code, line, pos, apply=False): 972 def __fixD243(self, code, line, pos, apply=False):
909 """ 973 """
910 Private method to fix a class or function/method docstring followed 974 Private method to fix a class or function/method docstring followed
911 by a blank line. 975 by a blank line.
912 976
913 Codes: D243, D245 977 Codes: D243, D245
914 978
915 @param code code of the issue 979 @param code code of the issue
916 @type str 980 @type str
917 @param line line number of the issue 981 @param line line number of the issue
918 @type int 982 @type int
919 @param pos position inside line 983 @param pos position inside line
937 return (1, msg, [], 0) 1001 return (1, msg, [], 0)
938 else: 1002 else:
939 fixId = self.__getID() 1003 fixId = self.__getID()
940 self.__stack.append((fixId, code, line, pos)) 1004 self.__stack.append((fixId, code, line, pos))
941 return (-1, "", [], fixId) 1005 return (-1, "", [], fixId)
942 1006
943 def __fixD247(self, code, line, pos, apply=False): 1007 def __fixD247(self, code, line, pos, apply=False):
944 """ 1008 """
945 Private method to fix a last paragraph of a docstring followed 1009 Private method to fix a last paragraph of a docstring followed
946 by a blank line. 1010 by a blank line.
947 1011
948 Codes: D247 1012 Codes: D247
949 1013
950 @param code code of the issue 1014 @param code code of the issue
951 @type str 1015 @type str
952 @param line line number of the issue 1016 @param line line number of the issue
953 @type int 1017 @type int
954 @param pos position inside line 1018 @param pos position inside line
967 return (1, "FIXD247", [], 0) 1031 return (1, "FIXD247", [], 0)
968 else: 1032 else:
969 fixId = self.__getID() 1033 fixId = self.__getID()
970 self.__stack.append((fixId, code, line, pos)) 1034 self.__stack.append((fixId, code, line, pos))
971 return (-1, "", [], fixId) 1035 return (-1, "", [], fixId)
972 1036
973 def __fixE101(self, code, line, pos): 1037 def __fixE101(self, code, line, pos):
974 """ 1038 """
975 Private method to fix obsolete tab usage and indentation errors. 1039 Private method to fix obsolete tab usage and indentation errors.
976 1040
977 Codes: E101, E111, W191 1041 Codes: E101, E111, W191
978 1042
979 @param code code of the issue 1043 @param code code of the issue
980 @type str 1044 @type str
981 @param line line number of the issue 1045 @param line line number of the issue
982 @type int 1046 @type int
983 @param pos position inside line 1047 @param pos position inside line
1000 # Indentation adjusted to be a multiple of four. 1064 # Indentation adjusted to be a multiple of four.
1001 msg = "FIXE111" 1065 msg = "FIXE111"
1002 return (1, msg, [], 0) 1066 return (1, msg, [], 0)
1003 else: 1067 else:
1004 return (0, "", [], 0) 1068 return (0, "", [], 0)
1005 1069
1006 def __fixE121(self, code, line, pos, apply=False): 1070 def __fixE121(self, code, line, pos, apply=False):
1007 """ 1071 """
1008 Private method to fix the indentation of continuation lines and 1072 Private method to fix the indentation of continuation lines and
1009 closing brackets. 1073 closing brackets.
1010 1074
1011 Codes: E121, E124 1075 Codes: E121, E124
1012 1076
1013 @param code code of the issue 1077 @param code code of the issue
1014 @type str 1078 @type str
1015 @param line line number of the issue 1079 @param line line number of the issue
1016 @type int 1080 @type int
1017 @param pos position inside line 1081 @param pos position inside line
1039 return (0, "", [], 0) 1103 return (0, "", [], 0)
1040 else: 1104 else:
1041 fixId = self.__getID() 1105 fixId = self.__getID()
1042 self.__stackLogical.append((fixId, code, line, pos)) 1106 self.__stackLogical.append((fixId, code, line, pos))
1043 return (-1, "", [], fixId) 1107 return (-1, "", [], fixId)
1044 1108
1045 def __fixE122(self, code, line, pos, apply=False): 1109 def __fixE122(self, code, line, pos, apply=False):
1046 """ 1110 """
1047 Private method to fix a missing indentation of continuation lines. 1111 Private method to fix a missing indentation of continuation lines.
1048 1112
1049 Codes: E122 1113 Codes: E122
1050 1114
1051 @param code code of the issue 1115 @param code code of the issue
1052 @type str 1116 @type str
1053 @param line line number of the issue 1117 @param line line number of the issue
1054 @type int 1118 @type int
1055 @param pos position inside line 1119 @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
1122 return (0, "", [], 0) 1185 return (0, "", [], 0)
1123 else: 1186 else:
1124 fixId = self.__getID() 1187 fixId = self.__getID()
1125 self.__stackLogical.append((fixId, code, line, pos)) 1188 self.__stackLogical.append((fixId, code, line, pos))
1126 return (-1, "", [], fixId) 1189 return (-1, "", [], fixId)
1127 1190
1128 def __fixE125(self, code, line, pos, apply=False): 1191 def __fixE125(self, code, line, pos, apply=False):
1129 """ 1192 """
1130 Private method to fix the indentation of continuation lines not 1193 Private method to fix the indentation of continuation lines not
1131 distinguishable from next logical line. 1194 distinguishable from next logical line.
1132 1195
1133 Codes: E125 1196 Codes: E125
1134 1197
1135 @param code code of the issue 1198 @param code code of the issue
1136 @type str 1199 @type str
1137 @param line line number of the issue 1200 @param line line number of the issue
1138 @type int 1201 @type int
1139 @param pos position inside line 1202 @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
1191 # Fix by deleting whitespace to the left. 1253 # Fix by deleting whitespace to the left.
1192 logicalLines = logical[2] 1254 logicalLines = logical[2]
1193 row = line - 1 1255 row = line - 1
1194 text = self.__source[row] 1256 text = self.__source[row]
1195 newText = ( 1257 newText = (
1196 self.__getIndent(logicalLines[0]) + 1258 self.__getIndent(logicalLines[0])
1197 self.__indentWord + text.lstrip() 1259 + self.__indentWord
1260 + text.lstrip()
1198 ) 1261 )
1199 if newText == text: 1262 if newText == text:
1200 # fall back to slower method 1263 # fall back to slower method
1201 changed = self.__fixReindent(line, pos, logical) 1264 changed = self.__fixReindent(line, pos, logical)
1202 else: 1265 else:
1208 return (0, "", [], 0) 1271 return (0, "", [], 0)
1209 else: 1272 else:
1210 fixId = self.__getID() 1273 fixId = self.__getID()
1211 self.__stackLogical.append((fixId, code, line, pos)) 1274 self.__stackLogical.append((fixId, code, line, pos))
1212 return (-1, "", [], fixId) 1275 return (-1, "", [], fixId)
1213 1276
1214 def __fixE127(self, code, line, pos, apply=False): 1277 def __fixE127(self, code, line, pos, apply=False):
1215 """ 1278 """
1216 Private method to fix over/under indented lines. 1279 Private method to fix over/under indented lines.
1217 1280
1218 Codes: E127, E128 1281 Codes: E127, E128
1219 1282
1220 @param code code of the issue 1283 @param code code of the issue
1221 @type str 1284 @type str
1222 @param line line number of the issue 1285 @param line line number of the issue
1223 @type int 1286 @type int
1224 @param pos position inside line 1287 @param pos position inside line
1236 # Fix by inserting/deleting whitespace to the correct level. 1299 # Fix by inserting/deleting whitespace to the correct level.
1237 logicalLines = logical[2] 1300 logicalLines = logical[2]
1238 row = line - 1 1301 row = line - 1
1239 text = self.__source[row] 1302 text = self.__source[row]
1240 newText = text 1303 newText = text
1241 1304
1242 if logicalLines[0].rstrip().endswith('\\'): 1305 if logicalLines[0].rstrip().endswith("\\"):
1243 newText = ( 1306 newText = (
1244 self.__getIndent(logicalLines[0]) + 1307 self.__getIndent(logicalLines[0])
1245 self.__indentWord + 1308 + self.__indentWord
1246 text.lstrip() 1309 + text.lstrip()
1247 ) 1310 )
1248 else: 1311 else:
1249 startIndex = None 1312 startIndex = None
1250 for symbol in '([{': 1313 for symbol in "([{":
1251 if symbol in logicalLines[0]: 1314 if symbol in logicalLines[0]:
1252 foundIndex = logicalLines[0].find(symbol) + 1 1315 foundIndex = logicalLines[0].find(symbol) + 1
1253 if startIndex is None: 1316 if startIndex is None:
1254 startIndex = foundIndex 1317 startIndex = foundIndex
1255 else: 1318 else:
1256 startIndex = min(startIndex, foundIndex) 1319 startIndex = min(startIndex, foundIndex)
1257 1320
1258 if startIndex is not None: 1321 if startIndex is not None:
1259 newText = startIndex * ' ' + text.lstrip() 1322 newText = startIndex * " " + text.lstrip()
1260 1323
1261 if newText == text: 1324 if newText == text:
1262 # fall back to slower method 1325 # fall back to slower method
1263 changed = self.__fixReindent(line, pos, logical) 1326 changed = self.__fixReindent(line, pos, logical)
1264 else: 1327 else:
1265 self.__source[row] = newText 1328 self.__source[row] = newText
1270 return (0, "", [], 0) 1333 return (0, "", [], 0)
1271 else: 1334 else:
1272 fixId = self.__getID() 1335 fixId = self.__getID()
1273 self.__stackLogical.append((fixId, code, line, pos)) 1336 self.__stackLogical.append((fixId, code, line, pos))
1274 return (-1, "", [], fixId) 1337 return (-1, "", [], fixId)
1275 1338
1276 def __fixE201(self, code, line, pos): 1339 def __fixE201(self, code, line, pos):
1277 """ 1340 """
1278 Private method to fix extraneous whitespace. 1341 Private method to fix extraneous whitespace.
1279 1342
1280 Codes: E201, E202, E203, E211 1343 Codes: E201, E202, E203, E211
1281 1344
1282 @param code code of the issue 1345 @param code code of the issue
1283 @type str 1346 @type str
1284 @param line line number of the issue 1347 @param line line number of the issue
1285 @type int 1348 @type int
1286 @param pos position inside line 1349 @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
1398 @rtype tuple of (int, str, list or int, int) 1461 @rtype tuple of (int, str, list or int, int)
1399 """ 1462 """
1400 line -= 1 1463 line -= 1
1401 pos += 1 1464 pos += 1
1402 self.__source[line] = ( 1465 self.__source[line] = (
1403 self.__source[line][:pos] + 1466 self.__source[line][:pos] + " " + self.__source[line][pos:]
1404 " " +
1405 self.__source[line][pos:]
1406 ) 1467 )
1407 # Missing whitespace added. 1468 # Missing whitespace added.
1408 return (1, "FIXE231", [], 0) 1469 return (1, "FIXE231", [], 0)
1409 1470
1410 def __fixE251(self, code, line, pos): 1471 def __fixE251(self, code, line, pos):
1411 """ 1472 """
1412 Private method to fix extraneous whitespace around keyword and 1473 Private method to fix extraneous whitespace around keyword and
1413 default parameter equals. 1474 default parameter equals.
1414 1475
1415 Codes: E251 1476 Codes: E251
1416 1477
1417 @param code code of the issue 1478 @param code code of the issue
1418 @type str 1479 @type str
1419 @param line line number of the issue 1480 @param line line number of the issue
1420 @type int 1481 @type int
1421 @param pos position inside line 1482 @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
1498 blankLinesBefore = self.__blankLines["method"] 1557 blankLinesBefore = self.__blankLines["method"]
1499 elif code == "E308": 1558 elif code == "E308":
1500 blankLinesBefore = 1 1559 blankLinesBefore = 1
1501 else: 1560 else:
1502 blankLinesBefore = self.__blankLines["toplevel"] 1561 blankLinesBefore = self.__blankLines["toplevel"]
1503 1562
1504 # count blank lines 1563 # count blank lines
1505 index = line - 1 1564 index = line - 1
1506 blanks = 0 1565 blanks = 0
1507 while index: 1566 while index:
1508 if self.__source[index - 1].strip() == "": 1567 if self.__source[index - 1].strip() == "":
1509 blanks += 1 1568 blanks += 1
1510 index -= 1 1569 index -= 1
1511 else: 1570 else:
1512 break 1571 break
1513 delta = blanks - blankLinesBefore 1572 delta = blanks - blankLinesBefore
1514 1573
1515 line -= 1 1574 line -= 1
1516 if delta < 0: 1575 if delta < 0:
1517 # insert blank lines (one or two) 1576 # insert blank lines (one or two)
1518 while delta < 0: 1577 while delta < 0:
1519 self.__source.insert(line, self.__eol) 1578 self.__source.insert(line, self.__eol)
1532 return (0, "", [], 0) 1591 return (0, "", [], 0)
1533 else: 1592 else:
1534 fixId = self.__getID() 1593 fixId = self.__getID()
1535 self.__stack.append((fixId, code, line, pos)) 1594 self.__stack.append((fixId, code, line, pos))
1536 return (-1, "", [], fixId) 1595 return (-1, "", [], fixId)
1537 1596
1538 def __fixE304(self, code, line, pos, apply=False): 1597 def __fixE304(self, code, line, pos, apply=False):
1539 """ 1598 """
1540 Private method to fix superfluous blank lines after a function 1599 Private method to fix superfluous blank lines after a function
1541 decorator. 1600 decorator.
1542 1601
1543 Codes: E304 1602 Codes: E304
1544 1603
1545 @param code code of the issue 1604 @param code code of the issue
1546 @type str 1605 @type str
1547 @param line line number of the issue 1606 @param line line number of the issue
1548 @type int 1607 @type int
1549 @param pos position inside line 1608 @param pos position inside line
1567 return (1, "FIXE304", [], 0) 1626 return (1, "FIXE304", [], 0)
1568 else: 1627 else:
1569 fixId = self.__getID() 1628 fixId = self.__getID()
1570 self.__stack.append((fixId, code, line, pos)) 1629 self.__stack.append((fixId, code, line, pos))
1571 return (-1, "", [], fixId) 1630 return (-1, "", [], fixId)
1572 1631
1573 def __fixE401(self, code, line, pos, apply=False): 1632 def __fixE401(self, code, line, pos, apply=False):
1574 """ 1633 """
1575 Private method to fix multiple imports on one line. 1634 Private method to fix multiple imports on one line.
1576 1635
1577 Codes: E401 1636 Codes: E401
1578 1637
1579 @param code code of the issue 1638 @param code code of the issue
1580 @type str 1639 @type str
1581 @param line line number of the issue 1640 @param line line number of the issue
1582 @type int 1641 @type int
1583 @param pos position inside line 1642 @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
1634 a message code for the fix, a list of arguments for the 1693 a message code for the fix, a list of arguments for the
1635 message and an ID for a deferred fix 1694 message and an ID for a deferred fix
1636 @rtype tuple of (int, str, list or int, int) 1695 @rtype tuple of (int, str, list or int, int)
1637 """ 1696 """
1638 if apply: 1697 if apply:
1639 multilineStringLines, docStringLines = ( 1698 multilineStringLines, docStringLines = self.__multilineStringLines()
1640 self.__multilineStringLines()
1641 )
1642 isDocString = line in docStringLines 1699 isDocString = line in docStringLines
1643 line -= 1 1700 line -= 1
1644 text = self.__source[line] 1701 text = self.__source[line]
1645 if line > 0: 1702 if line > 0:
1646 prevText = self.__source[line - 1] 1703 prevText = self.__source[line - 1]
1649 if line < len(self.__source) - 1: 1706 if line < len(self.__source) - 1:
1650 nextText = self.__source[line + 1] 1707 nextText = self.__source[line + 1]
1651 else: 1708 else:
1652 nextText = "" 1709 nextText = ""
1653 shortener = LineShortener( 1710 shortener = LineShortener(
1654 text, prevText, nextText, 1711 text,
1655 maxLength=self.__maxLineLength, eol=self.__eol, 1712 prevText,
1656 indentWord=self.__indentWord, isDocString=isDocString) 1713 nextText,
1714 maxLength=self.__maxLineLength,
1715 eol=self.__eol,
1716 indentWord=self.__indentWord,
1717 isDocString=isDocString,
1718 )
1657 changed, newText, newNextText = shortener.shorten() 1719 changed, newText, newNextText = shortener.shorten()
1658 if changed: 1720 if changed:
1659 if newText != text: 1721 if newText != text:
1660 self.__source[line] = newText 1722 self.__source[line] = newText
1661 if newNextText and newNextText != nextText: 1723 if newNextText and newNextText != nextText:
1668 return (0, "", [], 0) 1730 return (0, "", [], 0)
1669 else: 1731 else:
1670 fixId = self.__getID() 1732 fixId = self.__getID()
1671 self.__stack.append((fixId, code, line, pos)) 1733 self.__stack.append((fixId, code, line, pos))
1672 return (-1, "", [], fixId) 1734 return (-1, "", [], fixId)
1673 1735
1674 def __fixE502(self, code, line, pos): 1736 def __fixE502(self, code, line, pos):
1675 """ 1737 """
1676 Private method to fix redundant backslash within brackets. 1738 Private method to fix redundant backslash within brackets.
1677 1739
1678 Codes: E502 1740 Codes: E502
1679 1741
1680 @param code code of the issue 1742 @param code code of the issue
1681 @type str 1743 @type str
1682 @param line line number of the issue 1744 @param line line number of the issue
1683 @type int 1745 @type int
1684 @param pos position inside line 1746 @param pos position inside line
1687 a message code for the fix, a list of arguments for the 1749 a message code for the fix, a list of arguments for the
1688 message and an ID for a deferred fix 1750 message and an ID for a deferred fix
1689 @rtype tuple of (int, str, list or int, int) 1751 @rtype tuple of (int, str, list or int, int)
1690 """ 1752 """
1691 self.__source[line - 1] = ( 1753 self.__source[line - 1] = (
1692 self.__source[line - 1].rstrip("\n\r \t\\") + 1754 self.__source[line - 1].rstrip("\n\r \t\\") + self.__eol
1693 self.__eol
1694 ) 1755 )
1695 # Redundant backslash in brackets removed. 1756 # Redundant backslash in brackets removed.
1696 return (1, "FIXE502", [], 0) 1757 return (1, "FIXE502", [], 0)
1697 1758
1698 def __fixE701(self, code, line, pos, apply=False): 1759 def __fixE701(self, code, line, pos, apply=False):
1699 """ 1760 """
1700 Private method to fix colon-separated compound statements. 1761 Private method to fix colon-separated compound statements.
1701 1762
1702 Codes: E701 1763 Codes: E701
1703 1764
1704 @param code code of the issue 1765 @param code code of the issue
1705 @type str 1766 @type str
1706 @param line line number of the issue 1767 @param line line number of the issue
1707 @type int 1768 @type int
1708 @param pos position inside line 1769 @param pos position inside line
1716 """ 1777 """
1717 if apply: 1778 if apply:
1718 line -= 1 1779 line -= 1
1719 text = self.__source[line] 1780 text = self.__source[line]
1720 pos += 1 1781 pos += 1
1721 1782
1722 newText = ( 1783 newText = (
1723 text[:pos] + 1784 text[:pos]
1724 self.__eol + 1785 + self.__eol
1725 self.__getIndent(text) + 1786 + self.__getIndent(text)
1726 self.__indentWord + 1787 + self.__indentWord
1727 text[pos:].lstrip("\n\r \t\\") + 1788 + text[pos:].lstrip("\n\r \t\\")
1728 self.__eol 1789 + self.__eol
1729 ) 1790 )
1730 self.__source[line] = newText 1791 self.__source[line] = newText
1731 # Compound statement corrected. 1792 # Compound statement corrected.
1732 return (1, "FIXE701", [], 0) 1793 return (1, "FIXE701", [], 0)
1733 else: 1794 else:
1734 fixId = self.__getID() 1795 fixId = self.__getID()
1735 self.__stack.append((fixId, code, line, pos)) 1796 self.__stack.append((fixId, code, line, pos))
1736 return (-1, "", [], fixId) 1797 return (-1, "", [], fixId)
1737 1798
1738 def __fixE702(self, code, line, pos, apply=False): 1799 def __fixE702(self, code, line, pos, apply=False):
1739 """ 1800 """
1740 Private method to fix semicolon-separated compound statements. 1801 Private method to fix semicolon-separated compound statements.
1741 1802
1742 Codes: E702, E703 1803 Codes: E702, E703
1743 1804
1744 @param code code of the issue 1805 @param code code of the issue
1745 @type str 1806 @type str
1746 @param line line number of the issue 1807 @param line line number of the issue
1747 @type int 1808 @type int
1748 @param pos position inside line 1809 @param pos position inside line
1755 @rtype tuple of (int, str, list or int, int) 1816 @rtype tuple of (int, str, list or int, int)
1756 """ 1817 """
1757 if apply: 1818 if apply:
1758 line -= 1 1819 line -= 1
1759 text = self.__source[line] 1820 text = self.__source[line]
1760 1821
1761 if text.rstrip().endswith("\\"): 1822 if text.rstrip().endswith("\\"):
1762 # normalize '1; \\\n2' into '1; 2' 1823 # normalize '1; \\\n2' into '1; 2'
1763 self.__source[line] = text.rstrip("\n\r \t\\") 1824 self.__source[line] = text.rstrip("\n\r \t\\")
1764 self.__source[line + 1] = self.__source[line + 1].lstrip() 1825 self.__source[line + 1] = self.__source[line + 1].lstrip()
1765 elif text.rstrip().endswith(";"): 1826 elif text.rstrip().endswith(";"):
1772 return (1, "FIXE702", [], 0) 1833 return (1, "FIXE702", [], 0)
1773 else: 1834 else:
1774 fixId = self.__getID() 1835 fixId = self.__getID()
1775 self.__stack.append((fixId, code, line, pos)) 1836 self.__stack.append((fixId, code, line, pos))
1776 return (-1, "", [], fixId) 1837 return (-1, "", [], fixId)
1777 1838
1778 def __fixE711(self, code, line, pos): 1839 def __fixE711(self, code, line, pos):
1779 """ 1840 """
1780 Private method to fix comparison with None. 1841 Private method to fix comparison with None.
1781 1842
1782 Codes: E711, E712 1843 Codes: E711, E712
1783 1844
1784 @param code code of the issue 1845 @param code code of the issue
1785 @type str 1846 @type str
1786 @param line line number of the issue 1847 @param line line number of the issue
1787 @type int 1848 @type int
1788 @param pos position inside line 1849 @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
1842 text = self.__source[line] 1903 text = self.__source[line]
1843 if code == "N804": 1904 if code == "N804":
1844 arg = "cls" 1905 arg = "cls"
1845 else: 1906 else:
1846 arg = "self" 1907 arg = "self"
1847 1908
1848 if text.rstrip().endswith("("): 1909 if text.rstrip().endswith("("):
1849 newText = ( 1910 newText = (
1850 text + 1911 text
1851 self.__getIndent(text) + 1912 + self.__getIndent(text)
1852 self.__indentWord + 1913 + self.__indentWord
1853 arg + 1914 + arg
1854 "," + 1915 + ","
1855 self.__eol 1916 + self.__eol
1856 ) 1917 )
1857 else: 1918 else:
1858 index = text.find("(") + 1 1919 index = text.find("(") + 1
1859 left = text[:index] 1920 left = text[:index]
1860 right = text[index:] 1921 right = text[index:]
1868 return (1, "FIXN804", [arg], 0) 1929 return (1, "FIXN804", [arg], 0)
1869 else: 1930 else:
1870 fixId = self.__getID() 1931 fixId = self.__getID()
1871 self.__stack.append((fixId, code, line, pos)) 1932 self.__stack.append((fixId, code, line, pos))
1872 return (-1, "", [], fixId) 1933 return (-1, "", [], fixId)
1873 1934
1874 def __fixN806(self, code, line, pos, apply=False): 1935 def __fixN806(self, code, line, pos, apply=False):
1875 """ 1936 """
1876 Private method to fix a wrong first argument of static methods. 1937 Private method to fix a wrong first argument of static methods.
1877 1938
1878 Codes: N806 1939 Codes: N806
1879 1940
1880 @param code code of the issue 1941 @param code code of the issue
1881 @type str 1942 @type str
1882 @param line line number of the issue 1943 @param line line number of the issue
1883 @type int 1944 @type int
1884 @param pos position inside line 1945 @param pos position inside line
1894 line -= 1 1955 line -= 1
1895 text = self.__source[line] 1956 text = self.__source[line]
1896 index = text.find("(") + 1 1957 index = text.find("(") + 1
1897 left = text[:index] 1958 left = text[:index]
1898 right = text[index:] 1959 right = text[index:]
1899 1960
1900 if right.startswith(("cls", "self")): 1961 if right.startswith(("cls", "self")):
1901 # cls or self are on the definition line 1962 # cls or self are on the definition line
1902 if right.startswith("cls"): 1963 if right.startswith("cls"):
1903 right = right[3:] 1964 right = right[3:]
1904 arg = "cls" 1965 arg = "cls"
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
1977 @rtype tuple of (int, str, list or int, int) 2037 @rtype tuple of (int, str, list or int, int)
1978 """ 2038 """
1979 self.__source[line - 1] += self.__eol 2039 self.__source[line - 1] += self.__eol
1980 # newline added to end of file. 2040 # newline added to end of file.
1981 return (1, "FIXW292", [], 0) 2041 return (1, "FIXW292", [], 0)
1982 2042
1983 def __fixW391(self, code, line, pos): 2043 def __fixW391(self, code, line, pos):
1984 """ 2044 """
1985 Private method to fix trailing blank lines. 2045 Private method to fix trailing blank lines.
1986 2046
1987 Codes: W391 2047 Codes: W391
1988 2048
1989 @param code code of the issue 2049 @param code code of the issue
1990 @type str 2050 @type str
1991 @param line line number of the issue 2051 @param line line number of the issue
1992 @type int 2052 @type int
1993 @param pos position inside line 2053 @param pos position inside line
2004 index -= 1 2064 index -= 1
2005 else: 2065 else:
2006 break 2066 break
2007 # Superfluous trailing blank lines removed from end of file. 2067 # Superfluous trailing blank lines removed from end of file.
2008 return (1, "FIXW391", [], 0) 2068 return (1, "FIXW391", [], 0)
2009 2069
2010 def __fixW603(self, code, line, pos): 2070 def __fixW603(self, code, line, pos):
2011 """ 2071 """
2012 Private method to fix the not equal notation. 2072 Private method to fix the not equal notation.
2013 2073
2014 Codes: W603 2074 Codes: W603
2015 2075
2016 @param code code of the issue 2076 @param code code of the issue
2017 @type str 2077 @type str
2018 @param line line number of the issue 2078 @param line line number of the issue
2019 @type int 2079 @type int
2020 @param pos position inside line 2080 @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.
2111 # line got shifted. 2171 # line got shifted.
2112 for j in range(i - 1, -1, -1): 2172 for j in range(i - 1, -1, -1):
2113 jline, jlevel = stats[j] 2173 jline, jlevel = stats[j]
2114 if jlevel >= 0: 2174 if jlevel >= 0:
2115 want = ( 2175 want = (
2116 have + 2176 have
2117 self.__getlspace(after[jline - 1]) - 2177 + self.__getlspace(after[jline - 1])
2118 self.__getlspace(lines[jline]) 2178 - self.__getlspace(lines[jline])
2119 ) 2179 )
2120 break 2180 break
2121 if want < 0: 2181 if want < 0:
2122 # Still no luck -- leave it alone. 2182 # Still no luck -- leave it alone.
2123 want = have 2183 want = have
2136 after.append(" " * diff + line) 2196 after.append(" " * diff + line)
2137 else: 2197 else:
2138 remove = min(self.__getlspace(line), -diff) 2198 remove = min(self.__getlspace(line), -diff)
2139 after.append(line[remove:]) 2199 after.append(line[remove:])
2140 return self.raw != self.after 2200 return self.raw != self.after
2141 2201
2142 def fixedLine(self, line): 2202 def fixedLine(self, line):
2143 """ 2203 """
2144 Public method to get a fixed line. 2204 Public method to get a fixed line.
2145 2205
2146 @param line number of the line to retrieve (integer) 2206 @param line number of the line to retrieve (integer)
2147 @return fixed line (string) 2207 @return fixed line (string)
2148 """ 2208 """
2149 if line < len(self.after): 2209 if line < len(self.after):
2150 return self.after[line] 2210 return self.after[line]
2151 2211
2152 return "" 2212 return ""
2153 2213
2154 def getline(self): 2214 def getline(self):
2155 """ 2215 """
2156 Public method to get a line of text for tokenize. 2216 Public method to get a line of text for tokenize.
2157 2217
2158 @return line of text (string) 2218 @return line of text (string)
2159 """ 2219 """
2160 if self.index >= len(self.lines): 2220 if self.index >= len(self.lines):
2161 line = "" 2221 line = ""
2162 else: 2222 else:
2165 return line 2225 return line
2166 2226
2167 def __genStats(self, tokens): 2227 def __genStats(self, tokens):
2168 """ 2228 """
2169 Private method to generate the re-indent statistics. 2229 Private method to generate the re-indent statistics.
2170 2230
2171 @param tokens tokens generator (tokenize._tokenize) 2231 @param tokens tokens generator (tokenize._tokenize)
2172 @return reference to the generated statistics 2232 @return reference to the generated statistics
2173 """ 2233 """
2174 find_stmt = True # next token begins a fresh stmt? 2234 find_stmt = True # next token begins a fresh stmt?
2175 level = 0 # current indent level 2235 level = 0 # current indent level
2206 elif find_stmt: 2266 elif find_stmt:
2207 # This is the first "real token" following a NEWLINE, so it 2267 # This is the first "real token" following a NEWLINE, so it
2208 # must be the first token of the next program statement, or an 2268 # must be the first token of the next program statement, or an
2209 # ENDMARKER. 2269 # ENDMARKER.
2210 find_stmt = False 2270 find_stmt = False
2211 if line: # not endmarker 2271 if line: # not endmarker
2212 stats.append((sline, level)) 2272 stats.append((sline, level))
2213 2273
2214 return stats 2274 return stats
2215 2275
2216 def __getlspace(self, line): 2276 def __getlspace(self, line):
2217 """ 2277 """
2218 Private method to count number of leading blanks. 2278 Private method to count number of leading blanks.
2219 2279
2220 @param line line to check (string) 2280 @param line line to check (string)
2221 @return number of leading blanks (integer) 2281 @return number of leading blanks (integer)
2222 """ 2282 """
2223 i = 0 2283 i = 0
2224 n = len(line) 2284 n = len(line)
2231 """ 2291 """
2232 Class used by fixers dealing with indentation. 2292 Class used by fixers dealing with indentation.
2233 2293
2234 Each instance operates on a single logical line. 2294 Each instance operates on a single logical line.
2235 """ 2295 """
2236 SKIP_TOKENS = frozenset([ 2296
2237 tokenize.COMMENT, tokenize.NL, tokenize.INDENT, 2297 SKIP_TOKENS = frozenset(
2238 tokenize.DEDENT, tokenize.NEWLINE, tokenize.ENDMARKER 2298 [
2239 ]) 2299 tokenize.COMMENT,
2300 tokenize.NL,
2301 tokenize.INDENT,
2302 tokenize.DEDENT,
2303 tokenize.NEWLINE,
2304 tokenize.ENDMARKER,
2305 ]
2306 )
2240 2307
2241 def __init__(self, physical_lines): 2308 def __init__(self, physical_lines):
2242 """ 2309 """
2243 Constructor 2310 Constructor
2244 2311
2245 @param physical_lines list of physical lines to operate on 2312 @param physical_lines list of physical lines to operate on
2246 (list of strings) 2313 (list of strings)
2247 """ 2314 """
2248 self.lines = physical_lines 2315 self.lines = physical_lines
2249 self.tokens = [] 2316 self.tokens = []
2250 self.rel_indent = None 2317 self.rel_indent = None
2251 sio = StringIO(''.join(physical_lines)) 2318 sio = StringIO("".join(physical_lines))
2252 for t in tokenize.generate_tokens(sio.readline): 2319 for t in tokenize.generate_tokens(sio.readline):
2253 if not len(self.tokens) and t[0] in self.SKIP_TOKENS: 2320 if not len(self.tokens) and t[0] in self.SKIP_TOKENS:
2254 continue 2321 continue
2255 if t[0] != tokenize.ENDMARKER: 2322 if t[0] != tokenize.ENDMARKER:
2256 self.tokens.append(t) 2323 self.tokens.append(t)
2258 self.logical_line = self.__buildTokensLogical(self.tokens) 2325 self.logical_line = self.__buildTokensLogical(self.tokens)
2259 2326
2260 def __buildTokensLogical(self, tokens): 2327 def __buildTokensLogical(self, tokens):
2261 """ 2328 """
2262 Private method to build a logical line from a list of tokens. 2329 Private method to build a logical line from a list of tokens.
2263 2330
2264 @param tokens list of tokens as generated by tokenize.generate_tokens 2331 @param tokens list of tokens as generated by tokenize.generate_tokens
2265 @return logical line (string) 2332 @return logical line (string)
2266 """ 2333 """
2267 # from pycodestyle.py with minor modifications 2334 # from pycodestyle.py with minor modifications
2268 logical = [] 2335 logical = []
2274 if previous: 2341 if previous:
2275 end_line, end = previous[3] 2342 end_line, end = previous[3]
2276 start_line, start = t[2] 2343 start_line, start = t[2]
2277 if end_line != start_line: # different row 2344 if end_line != start_line: # different row
2278 prev_text = self.lines[end_line - 1][end - 1] 2345 prev_text = self.lines[end_line - 1][end - 1]
2279 if prev_text == ',' or (prev_text not in '{[(' and 2346 if prev_text == "," or (
2280 text not in '}])'): 2347 prev_text not in "{[(" and text not in "}])"
2281 logical.append(' ') 2348 ):
2349 logical.append(" ")
2282 elif end != start: # different column 2350 elif end != start: # different column
2283 fill = self.lines[end_line - 1][end:start] 2351 fill = self.lines[end_line - 1][end:start]
2284 logical.append(fill) 2352 logical.append(fill)
2285 logical.append(text) 2353 logical.append(text)
2286 previous = t 2354 previous = t
2287 logical_line = ''.join(logical) 2355 logical_line = "".join(logical)
2288 return logical_line 2356 return logical_line
2289 2357
2290 def pep8Expected(self): 2358 def pep8Expected(self):
2291 """ 2359 """
2292 Public method to replicate logic in pycodestyle.py, to know what level 2360 Public method to replicate logic in pycodestyle.py, to know what level
2312 2380
2313 if nrows == 1: 2381 if nrows == 1:
2314 # bug, really. 2382 # bug, really.
2315 return valid_indents 2383 return valid_indents
2316 2384
2317 indent_next = self.logical_line.endswith(':') 2385 indent_next = self.logical_line.endswith(":")
2318 2386
2319 row = depth = 0 2387 row = depth = 0
2320 parens = [0] * nrows 2388 parens = [0] * nrows
2321 self.rel_indent = rel_indent = [0] * nrows 2389 self.rel_indent = rel_indent = [0] * nrows
2322 indent = [indent_level] 2390 indent = [indent_level]
2326 2394
2327 for token_type, text, start, end, line in self.tokens: 2395 for token_type, text, start, end, line in self.tokens:
2328 newline = row < start[0] - first_row 2396 newline = row < start[0] - first_row
2329 if newline: 2397 if newline:
2330 row = start[0] - first_row 2398 row = start[0] - first_row
2331 newline = (not last_token_multiline and 2399 newline = not last_token_multiline and token_type not in (
2332 token_type not in (tokenize.NL, tokenize.NEWLINE)) 2400 tokenize.NL,
2401 tokenize.NEWLINE,
2402 )
2333 2403
2334 if newline: 2404 if newline:
2335 # This is where the differences start. Instead of looking at 2405 # This is where the differences start. Instead of looking at
2336 # the line and determining whether the observed indent matches 2406 # the line and determining whether the observed indent matches
2337 # our expectations, we decide which type of indentation is in 2407 # our expectations, we decide which type of indentation is in
2350 # That's all we get to work with. This code attempts to 2420 # That's all we get to work with. This code attempts to
2351 # "reverse" the below logic, and place into the valid indents 2421 # "reverse" the below logic, and place into the valid indents
2352 # list 2422 # list
2353 vi = [] 2423 vi = []
2354 add_second_chances = False 2424 add_second_chances = False
2355 if token_type == tokenize.OP and text in ']})': 2425 if token_type == tokenize.OP and text in "]})":
2356 # this line starts with a closing bracket, so it needs to 2426 # this line starts with a closing bracket, so it needs to
2357 # be closed at the same indent as the opening one. 2427 # be closed at the same indent as the opening one.
2358 if indent[depth]: 2428 if indent[depth]:
2359 # hanging indent 2429 # hanging indent
2360 vi.append(indent[depth]) 2430 vi.append(indent[depth])
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):
2446 parens[idx] -= 1 2513 parens[idx] -= 1
2447 break 2514 break
2448 if start[1] not in indent_chances: 2515 if start[1] not in indent_chances:
2449 indent_chances[start[1]] = text 2516 indent_chances[start[1]] = text
2450 2517
2451 last_token_multiline = (start[0] != end[0]) 2518 last_token_multiline = start[0] != end[0]
2452 2519
2453 return valid_indents 2520 return valid_indents
2454 2521
2455 2522
2456 class LineShortener: 2523 class LineShortener:
2457 """ 2524 """
2458 Class used to shorten lines to a given maximum of characters. 2525 Class used to shorten lines to a given maximum of characters.
2459 """ 2526 """
2460 def __init__(self, curLine, prevLine, nextLine, maxLength=88, eol="\n", 2527
2461 indentWord=" ", isDocString=False): 2528 def __init__(
2529 self,
2530 curLine,
2531 prevLine,
2532 nextLine,
2533 maxLength=88,
2534 eol="\n",
2535 indentWord=" ",
2536 isDocString=False,
2537 ):
2462 """ 2538 """
2463 Constructor 2539 Constructor
2464 2540
2465 @param curLine text to work on (string) 2541 @param curLine text to work on (string)
2466 @param prevLine line before the text to work on (string) 2542 @param prevLine line before the text to work on (string)
2467 @param nextLine line after the text to work on (string) 2543 @param nextLine line after the text to work on (string)
2468 @param maxLength maximum allowed line length (integer) 2544 @param maxLength maximum allowed line length (integer)
2469 @param eol eond-of-line marker (string) 2545 @param eol eond-of-line marker (string)
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, ""
2526 if self.__nextText.strip(): 2602 if self.__nextText.strip():
2527 if self.__nextText.lstrip().startswith("@"): 2603 if self.__nextText.lstrip().startswith("@"):
2528 # eric doc comment 2604 # eric doc comment
2529 # create a new line and indent it 2605 # create a new line and indent it
2530 newText = ( 2606 newText = (
2531 first + 2607 first
2532 self.__eol + 2608 + self.__eol
2533 self.__getIndent(first) + 2609 + self.__getIndent(first)
2534 self.__indentWord + 2610 + self.__indentWord
2535 second 2611 + second
2536 ) 2612 )
2537 newNext = "" 2613 newNext = ""
2538 else: 2614 else:
2539 newText = first + self.__eol 2615 newText = first + self.__eol
2540 newNext = ( 2616 newNext = (
2541 self.__getIndent(self.__nextText) + 2617 self.__getIndent(self.__nextText)
2542 second.rstrip() + 2618 + second.rstrip()
2543 " " + 2619 + " "
2544 self.__nextText.lstrip() 2620 + self.__nextText.lstrip()
2545 ) 2621 )
2546 else: 2622 else:
2547 # empty line, add a new line 2623 # empty line, add a new line
2548 newText = ( 2624 newText = first + self.__eol + self.__getIndent(first) + second
2549 first +
2550 self.__eol +
2551 self.__getIndent(first) +
2552 second
2553 )
2554 newNext = "" 2625 newNext = ""
2555 return True, newText, newNext 2626 return True, newText, newNext
2556 2627
2557 indent = self.__getIndent(self.__text) 2628 indent = self.__getIndent(self.__text)
2558 source = self.__text[len(indent):] 2629 source = self.__text[len(indent) :]
2559 sio = StringIO(source) 2630 sio = StringIO(source)
2560 2631
2561 # Check for multi line string. 2632 # Check for multi line string.
2562 try: 2633 try:
2563 tokens = list(tokenize.generate_tokens(sio.readline)) 2634 tokens = list(tokenize.generate_tokens(sio.readline))
2564 except (SyntaxError, tokenize.TokenError): 2635 except (SyntaxError, tokenize.TokenError):
2565 if source.rstrip().endswith("\\"): 2636 if source.rstrip().endswith("\\"):
2566 # just join the continuation line and let the next run 2637 # just join the continuation line and let the next run
2567 # handle it once it tokenizes ok 2638 # handle it once it tokenizes ok
2568 newText = ( 2639 newText = (
2569 indent + 2640 indent
2570 source.rstrip()[:-1].rstrip() + 2641 + source.rstrip()[:-1].rstrip()
2571 " " + 2642 + " "
2572 self.__nextText.lstrip() 2643 + self.__nextText.lstrip()
2573 ) 2644 )
2574 if indent: 2645 if indent:
2575 newNext = indent 2646 newNext = indent
2576 else: 2647 else:
2577 newNext = " " 2648 newNext = " "
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)
2715 # don't merge with next line 2803 # don't merge with next line
2716 newText += self.__getIndent(newText) + second + self.__eol 2804 newText += self.__getIndent(newText) + second + self.__eol
2717 newNext = "" 2805 newNext = ""
2718 else: 2806 else:
2719 newNext = ( 2807 newNext = (
2720 self.__getIndent(newNext) + 2808 self.__getIndent(newNext) + second + " " + newNext.lstrip()
2721 second +
2722 " " +
2723 newNext.lstrip()
2724 ) 2809 )
2725 else: 2810 else:
2726 # empty line, add a new line 2811 # empty line, add a new line
2727 newText = first + self.__eol 2812 newText = first + self.__eol
2728 newNext = ( 2813 newNext = (
2729 self.__getIndent(newNext) + 2814 self.__getIndent(newNext) + second + self.__eol + newNext.lstrip()
2730 second +
2731 self.__eol +
2732 newNext.lstrip()
2733 ) 2815 )
2734 return newText, newNext 2816 return newText, newNext
2735 else: 2817 else:
2736 return None 2818 return None
2737 2819
2738 def __isProbablyInsideStringOrComment(self, line, index): 2820 def __isProbablyInsideStringOrComment(self, line, index):
2739 """ 2821 """
2740 Private method to check, if the given string might be inside a string 2822 Private method to check, if the given string might be inside a string
2741 or comment. 2823 or comment.
2742 2824
2743 @param line line to check (string) 2825 @param line line to check (string)
2744 @param index position inside line to check (integer) 2826 @param index position inside line to check (integer)
2745 @return flag indicating the possibility of being inside a string 2827 @return flag indicating the possibility of being inside a string
2746 or comment 2828 or comment
2747 """ 2829 """
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

eric ide

mercurial