Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/autopep8.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

2474 statements  

1#!/usr/bin/env python 

2 

3# Copyright (C) 2010-2011 Hideo Hattori 

4# Copyright (C) 2011-2013 Hideo Hattori, Steven Myint 

5# Copyright (C) 2013-2016 Hideo Hattori, Steven Myint, Bill Wendling 

6# 

7# Permission is hereby granted, free of charge, to any person obtaining 

8# a copy of this software and associated documentation files (the 

9# "Software"), to deal in the Software without restriction, including 

10# without limitation the rights to use, copy, modify, merge, publish, 

11# distribute, sublicense, and/or sell copies of the Software, and to 

12# permit persons to whom the Software is furnished to do so, subject to 

13# the following conditions: 

14# 

15# The above copyright notice and this permission notice shall be 

16# included in all copies or substantial portions of the Software. 

17# 

18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 

19# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 

20# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 

21# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 

22# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 

23# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 

24# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 

25# SOFTWARE. 

26 

27# Copyright (C) 2006-2009 Johann C. Rocholl <johann@rocholl.net> 

28# Copyright (C) 2009-2013 Florent Xicluna <florent.xicluna@gmail.com> 

29# 

30# Permission is hereby granted, free of charge, to any person 

31# obtaining a copy of this software and associated documentation files 

32# (the "Software"), to deal in the Software without restriction, 

33# including without limitation the rights to use, copy, modify, merge, 

34# publish, distribute, sublicense, and/or sell copies of the Software, 

35# and to permit persons to whom the Software is furnished to do so, 

36# subject to the following conditions: 

37# 

38# The above copyright notice and this permission notice shall be 

39# included in all copies or substantial portions of the Software. 

40# 

41# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 

42# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 

43# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 

44# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 

45# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 

46# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 

47# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 

48# SOFTWARE. 

49 

50"""Automatically formats Python code to conform to the PEP 8 style guide. 

51 

52Fixes that only need be done once can be added by adding a function of the form 

53"fix_<code>(source)" to this module. They should return the fixed source code. 

54These fixes are picked up by apply_global_fixes(). 

55 

56Fixes that depend on pycodestyle should be added as methods to FixPEP8. See the 

57class documentation for more information. 

58 

59""" 

60 

61from __future__ import absolute_import 

62from __future__ import division 

63from __future__ import print_function 

64from __future__ import unicode_literals 

65 

66import argparse 

67import codecs 

68import collections 

69import copy 

70import difflib 

71import fnmatch 

72import importlib 

73import inspect 

74import io 

75import itertools 

76import keyword 

77import locale 

78import os 

79import re 

80import signal 

81import sys 

82import textwrap 

83import token 

84import tokenize 

85import warnings 

86import ast 

87from configparser import ConfigParser as SafeConfigParser, Error 

88 

89import pycodestyle 

90 

91 

92__version__ = '2.3.2' 

93 

94 

95CR = '\r' 

96LF = '\n' 

97CRLF = '\r\n' 

98 

99 

100PYTHON_SHEBANG_REGEX = re.compile(r'^#!.*\bpython[23]?\b\s*$') 

101LAMBDA_REGEX = re.compile(r'([\w.]+)\s=\slambda\s*([)(=\w,\s.]*):') 

102COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+([^][)(}{]+?)\s+(in|is)\s') 

103COMPARE_NEGATIVE_REGEX_THROUGH = re.compile(r'\b(not\s+in|is\s+not)\s') 

104BARE_EXCEPT_REGEX = re.compile(r'except\s*:') 

105STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\s.*\):') 

106DOCSTRING_START_REGEX = re.compile(r'^u?r?(?P<kind>["\']{3})') 

107ENABLE_REGEX = re.compile(r'# *(fmt|autopep8): *on') 

108DISABLE_REGEX = re.compile(r'# *(fmt|autopep8): *off') 

109ENCODING_MAGIC_COMMENT = re.compile( 

110 r'^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)' 

111) 

112COMPARE_TYPE_REGEX = re.compile( 

113 r'([=!]=)\s+type(?:\s*\(\s*([^)]*[^ )])\s*\))' 

114 r'|\btype(?:\s*\(\s*([^)]*[^ )])\s*\))\s+([=!]=)' 

115) 

116TYPE_REGEX = re.compile(r'(type\s*\(\s*[^)]*?[^\s)]\s*\))') 

117 

118EXIT_CODE_OK = 0 

119EXIT_CODE_ERROR = 1 

120EXIT_CODE_EXISTS_DIFF = 2 

121EXIT_CODE_ARGPARSE_ERROR = 99 

122 

123# For generating line shortening candidates. 

124SHORTEN_OPERATOR_GROUPS = frozenset([ 

125 frozenset([',']), 

126 frozenset(['%']), 

127 frozenset([',', '(', '[', '{']), 

128 frozenset(['%', '(', '[', '{']), 

129 frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']), 

130 frozenset(['%', '+', '-', '*', '/', '//']), 

131]) 

132 

133 

134DEFAULT_IGNORE = 'E226,E24,W50,W690' # TODO: use pycodestyle.DEFAULT_IGNORE 

135DEFAULT_INDENT_SIZE = 4 

136# these fixes conflict with each other, if the `--ignore` setting causes both 

137# to be enabled, disable both of them 

138CONFLICTING_CODES = ('W503', 'W504') 

139 

140if sys.platform == 'win32': # pragma: no cover 

141 DEFAULT_CONFIG = os.path.expanduser(r'~\.pycodestyle') 

142else: 

143 DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or 

144 os.path.expanduser('~/.config'), 

145 'pycodestyle') 

146# fallback, use .pep8 

147if not os.path.exists(DEFAULT_CONFIG): # pragma: no cover 

148 if sys.platform == 'win32': 

149 DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8') 

150 else: 

151 DEFAULT_CONFIG = os.path.join(os.path.expanduser('~/.config'), 'pep8') 

152PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8', '.flake8') 

153 

154 

155MAX_PYTHON_FILE_DETECTION_BYTES = 1024 

156 

157IS_SUPPORT_TOKEN_FSTRING = False 

158if sys.version_info >= (3, 12): # pgrama: no cover 

159 IS_SUPPORT_TOKEN_FSTRING = True 

160 

161 

162def _custom_formatwarning(message, category, _, __, line=None): 

163 return f"{category.__name__}: {message}\n" 

164 

165 

166def open_with_encoding(filename, mode='r', encoding=None, limit_byte_check=-1): 

167 """Return opened file with a specific encoding.""" 

168 if not encoding: 

169 encoding = detect_encoding(filename, limit_byte_check=limit_byte_check) 

170 

171 return io.open(filename, mode=mode, encoding=encoding, 

172 newline='') # Preserve line endings 

173 

174 

175def _detect_encoding_from_file(filename: str): 

176 try: 

177 with open(filename) as input_file: 

178 for idx, line in enumerate(input_file): 

179 if idx == 0 and line[0] == '\ufeff': 

180 return "utf-8-sig" 

181 if idx >= 2: 

182 break 

183 match = ENCODING_MAGIC_COMMENT.search(line) 

184 if match: 

185 return match.groups()[0] 

186 except Exception: 

187 pass 

188 # Python3's default encoding 

189 return 'utf-8' 

190 

191 

192def detect_encoding(filename, limit_byte_check=-1): 

193 """Return file encoding.""" 

194 encoding = _detect_encoding_from_file(filename) 

195 if encoding == "utf-8-sig": 

196 return encoding 

197 try: 

198 with open_with_encoding(filename, encoding=encoding) as test_file: 

199 test_file.read(limit_byte_check) 

200 return encoding 

201 except (LookupError, SyntaxError, UnicodeDecodeError): 

202 return 'latin-1' 

203 

204 

205def readlines_from_file(filename): 

206 """Return contents of file.""" 

207 with open_with_encoding(filename) as input_file: 

208 return input_file.readlines() 

209 

210 

211def extended_blank_lines(logical_line, 

212 blank_lines, 

213 blank_before, 

214 indent_level, 

215 previous_logical): 

216 """Check for missing blank lines after class declaration.""" 

217 if previous_logical.startswith(('def ', 'async def ')): 

218 if blank_lines and pycodestyle.DOCSTRING_REGEX.match(logical_line): 

219 yield (0, 'E303 too many blank lines ({})'.format(blank_lines)) 

220 elif pycodestyle.DOCSTRING_REGEX.match(previous_logical): 

221 # Missing blank line between class docstring and method declaration. 

222 if ( 

223 indent_level and 

224 not blank_lines and 

225 not blank_before and 

226 logical_line.startswith(('def ', 'async def ')) and 

227 '(self' in logical_line 

228 ): 

229 yield (0, 'E301 expected 1 blank line, found 0') 

230 

231 

232def continued_indentation(logical_line, tokens, indent_level, hang_closing, 

233 indent_char, noqa): 

234 """Override pycodestyle's function to provide indentation information.""" 

235 first_row = tokens[0][2][0] 

236 nrows = 1 + tokens[-1][2][0] - first_row 

237 if noqa or nrows == 1: 

238 return 

239 

240 # indent_next tells us whether the next block is indented. Assuming 

241 # that it is indented by 4 spaces, then we should not allow 4-space 

242 # indents on the final continuation line. In turn, some other 

243 # indents are allowed to have an extra 4 spaces. 

244 indent_next = logical_line.endswith(':') 

245 

246 row = depth = 0 

247 valid_hangs = ( 

248 (DEFAULT_INDENT_SIZE,) 

249 if indent_char != '\t' else (DEFAULT_INDENT_SIZE, 

250 2 * DEFAULT_INDENT_SIZE) 

251 ) 

252 

253 # Remember how many brackets were opened on each line. 

254 parens = [0] * nrows 

255 

256 # Relative indents of physical lines. 

257 rel_indent = [0] * nrows 

258 

259 # For each depth, collect a list of opening rows. 

260 open_rows = [[0]] 

261 # For each depth, memorize the hanging indentation. 

262 hangs = [None] 

263 

264 # Visual indents. 

265 indent_chances = {} 

266 last_indent = tokens[0][2] 

267 indent = [last_indent[1]] 

268 

269 last_token_multiline = None 

270 line = None 

271 last_line = '' 

272 last_line_begins_with_multiline = False 

273 for token_type, text, start, end, line in tokens: 

274 

275 newline = row < start[0] - first_row 

276 if newline: 

277 row = start[0] - first_row 

278 newline = (not last_token_multiline and 

279 token_type not in (tokenize.NL, tokenize.NEWLINE)) 

280 last_line_begins_with_multiline = last_token_multiline 

281 

282 if newline: 

283 # This is the beginning of a continuation line. 

284 last_indent = start 

285 

286 # Record the initial indent. 

287 rel_indent[row] = pycodestyle.expand_indent(line) - indent_level 

288 

289 # Identify closing bracket. 

290 close_bracket = (token_type == tokenize.OP and text in ']})') 

291 

292 # Is the indent relative to an opening bracket line? 

293 for open_row in reversed(open_rows[depth]): 

294 hang = rel_indent[row] - rel_indent[open_row] 

295 hanging_indent = hang in valid_hangs 

296 if hanging_indent: 

297 break 

298 if hangs[depth]: 

299 hanging_indent = (hang == hangs[depth]) 

300 

301 visual_indent = (not close_bracket and hang > 0 and 

302 indent_chances.get(start[1])) 

303 

304 if close_bracket and indent[depth]: 

305 # Closing bracket for visual indent. 

306 if start[1] != indent[depth]: 

307 yield (start, 'E124 {}'.format(indent[depth])) 

308 elif close_bracket and not hang: 

309 # closing bracket matches indentation of opening bracket's line 

310 if hang_closing: 

311 yield (start, 'E133 {}'.format(indent[depth])) 

312 elif indent[depth] and start[1] < indent[depth]: 

313 if visual_indent is not True: 

314 # Visual indent is broken. 

315 yield (start, 'E128 {}'.format(indent[depth])) 

316 elif (hanging_indent or 

317 (indent_next and 

318 rel_indent[row] == 2 * DEFAULT_INDENT_SIZE)): 

319 # Hanging indent is verified. 

320 if close_bracket and not hang_closing: 

321 yield (start, 'E123 {}'.format(indent_level + 

322 rel_indent[open_row])) 

323 hangs[depth] = hang 

324 elif visual_indent is True: 

325 # Visual indent is verified. 

326 indent[depth] = start[1] 

327 elif visual_indent in (text, str): 

328 # Ignore token lined up with matching one from a previous line. 

329 pass 

330 else: 

331 one_indented = (indent_level + rel_indent[open_row] + 

332 DEFAULT_INDENT_SIZE) 

333 # Indent is broken. 

334 if hang <= 0: 

335 error = ('E122', one_indented) 

336 elif indent[depth]: 

337 error = ('E127', indent[depth]) 

338 elif not close_bracket and hangs[depth]: 

339 error = ('E131', one_indented) 

340 elif hang > DEFAULT_INDENT_SIZE: 

341 error = ('E126', one_indented) 

342 else: 

343 hangs[depth] = hang 

344 error = ('E121', one_indented) 

345 

346 yield (start, '{} {}'.format(*error)) 

347 

348 # Look for visual indenting. 

349 if ( 

350 parens[row] and 

351 token_type not in (tokenize.NL, tokenize.COMMENT) and 

352 not indent[depth] 

353 ): 

354 indent[depth] = start[1] 

355 indent_chances[start[1]] = True 

356 # Deal with implicit string concatenation. 

357 elif (token_type in (tokenize.STRING, tokenize.COMMENT) or 

358 text in ('u', 'ur', 'b', 'br')): 

359 indent_chances[start[1]] = str 

360 # Special case for the "if" statement because len("if (") is equal to 

361 # 4. 

362 elif not indent_chances and not row and not depth and text == 'if': 

363 indent_chances[end[1] + 1] = True 

364 elif text == ':' and line[end[1]:].isspace(): 

365 open_rows[depth].append(row) 

366 

367 # Keep track of bracket depth. 

368 if token_type == tokenize.OP: 

369 if text in '([{': 

370 depth += 1 

371 indent.append(0) 

372 hangs.append(None) 

373 if len(open_rows) == depth: 

374 open_rows.append([]) 

375 open_rows[depth].append(row) 

376 parens[row] += 1 

377 elif text in ')]}' and depth > 0: 

378 # Parent indents should not be more than this one. 

379 prev_indent = indent.pop() or last_indent[1] 

380 hangs.pop() 

381 for d in range(depth): 

382 if indent[d] > prev_indent: 

383 indent[d] = 0 

384 for ind in list(indent_chances): 

385 if ind >= prev_indent: 

386 del indent_chances[ind] 

387 del open_rows[depth + 1:] 

388 depth -= 1 

389 if depth: 

390 indent_chances[indent[depth]] = True 

391 for idx in range(row, -1, -1): 

392 if parens[idx]: 

393 parens[idx] -= 1 

394 break 

395 assert len(indent) == depth + 1 

396 if ( 

397 start[1] not in indent_chances and 

398 # This is for purposes of speeding up E121 (GitHub #90). 

399 not last_line.rstrip().endswith(',') 

400 ): 

401 # Allow to line up tokens. 

402 indent_chances[start[1]] = text 

403 

404 last_token_multiline = (start[0] != end[0]) 

405 if last_token_multiline: 

406 rel_indent[end[0] - first_row] = rel_indent[row] 

407 

408 last_line = line 

409 

410 if ( 

411 indent_next and 

412 not last_line_begins_with_multiline and 

413 pycodestyle.expand_indent(line) == indent_level + DEFAULT_INDENT_SIZE 

414 ): 

415 pos = (start[0], indent[0] + 4) 

416 desired_indent = indent_level + 2 * DEFAULT_INDENT_SIZE 

417 if visual_indent: 

418 yield (pos, 'E129 {}'.format(desired_indent)) 

419 else: 

420 yield (pos, 'E125 {}'.format(desired_indent)) 

421 

422 

423# NOTE: need reload with runpy and call twice 

424# see: https://github.com/hhatto/autopep8/issues/625 

425importlib.reload(pycodestyle) 

426del pycodestyle._checks['logical_line'][pycodestyle.continued_indentation] 

427pycodestyle.register_check(extended_blank_lines) 

428pycodestyle.register_check(continued_indentation) 

429 

430 

431class FixPEP8(object): 

432 

433 """Fix invalid code. 

434 

435 Fixer methods are prefixed "fix_". The _fix_source() method looks for these 

436 automatically. 

437 

438 The fixer method can take either one or two arguments (in addition to 

439 self). The first argument is "result", which is the error information from 

440 pycodestyle. The second argument, "logical", is required only for 

441 logical-line fixes. 

442 

443 The fixer method can return the list of modified lines or None. An empty 

444 list would mean that no changes were made. None would mean that only the 

445 line reported in the pycodestyle error was modified. Note that the modified 

446 line numbers that are returned are indexed at 1. This typically would 

447 correspond with the line number reported in the pycodestyle error 

448 information. 

449 

450 [fixed method list] 

451 - e111,e114,e115,e116 

452 - e121,e122,e123,e124,e125,e126,e127,e128,e129 

453 - e201,e202,e203 

454 - e211 

455 - e221,e222,e223,e224,e225 

456 - e231 

457 - e251,e252 

458 - e261,e262 

459 - e271,e272,e273,e274,e275 

460 - e301,e302,e303,e304,e305,e306 

461 - e401,e402 

462 - e502 

463 - e701,e702,e703,e704 

464 - e711,e712,e713,e714 

465 - e721,e722 

466 - e731 

467 - w291 

468 - w503,504 

469 

470 """ 

471 

472 def __init__(self, filename, 

473 options, 

474 contents=None, 

475 long_line_ignore_cache=None): 

476 self.filename = filename 

477 if contents is None: 

478 self.source = readlines_from_file(filename) 

479 else: 

480 sio = io.StringIO(contents) 

481 self.source = sio.readlines() 

482 self.options = options 

483 self.indent_word = _get_indentword(''.join(self.source)) 

484 self.original_source = copy.copy(self.source) 

485 

486 # collect imports line 

487 self.imports = {} 

488 for i, line in enumerate(self.source): 

489 if (line.find("import ") == 0 or line.find("from ") == 0) and \ 

490 line not in self.imports: 

491 # collect only import statements that first appeared 

492 self.imports[line] = i 

493 

494 self.long_line_ignore_cache = ( 

495 set() if long_line_ignore_cache is None 

496 else long_line_ignore_cache) 

497 

498 # Many fixers are the same even though pycodestyle categorizes them 

499 # differently. 

500 self.fix_e115 = self.fix_e112 

501 self.fix_e121 = self._fix_reindent 

502 self.fix_e122 = self._fix_reindent 

503 self.fix_e123 = self._fix_reindent 

504 self.fix_e124 = self._fix_reindent 

505 self.fix_e126 = self._fix_reindent 

506 self.fix_e127 = self._fix_reindent 

507 self.fix_e128 = self._fix_reindent 

508 self.fix_e129 = self._fix_reindent 

509 self.fix_e133 = self.fix_e131 

510 self.fix_e202 = self.fix_e201 

511 self.fix_e203 = self.fix_e201 

512 self.fix_e204 = self.fix_e201 

513 self.fix_e211 = self.fix_e201 

514 self.fix_e221 = self.fix_e271 

515 self.fix_e222 = self.fix_e271 

516 self.fix_e223 = self.fix_e271 

517 self.fix_e226 = self.fix_e225 

518 self.fix_e227 = self.fix_e225 

519 self.fix_e228 = self.fix_e225 

520 self.fix_e241 = self.fix_e271 

521 self.fix_e242 = self.fix_e224 

522 self.fix_e252 = self.fix_e225 

523 self.fix_e261 = self.fix_e262 

524 self.fix_e272 = self.fix_e271 

525 self.fix_e273 = self.fix_e271 

526 self.fix_e274 = self.fix_e271 

527 self.fix_e275 = self.fix_e271 

528 self.fix_e306 = self.fix_e301 

529 self.fix_e501 = ( 

530 self.fix_long_line_logically if 

531 options and (options.aggressive >= 2 or options.experimental) else 

532 self.fix_long_line_physically) 

533 self.fix_e703 = self.fix_e702 

534 self.fix_w292 = self.fix_w291 

535 self.fix_w293 = self.fix_w291 

536 

537 def _check_affected_anothers(self, result) -> bool: 

538 """Check if the fix affects the number of lines of another remark.""" 

539 line_index = result['line'] - 1 

540 target = self.source[line_index] 

541 original_target = self.original_source[line_index] 

542 return target != original_target 

543 

544 def _fix_source(self, results): 

545 try: 

546 (logical_start, logical_end) = _find_logical(self.source) 

547 logical_support = True 

548 except (SyntaxError, tokenize.TokenError): # pragma: no cover 

549 logical_support = False 

550 

551 completed_lines = set() 

552 for result in sorted(results, key=_priority_key): 

553 if result['line'] in completed_lines: 

554 continue 

555 

556 fixed_methodname = 'fix_' + result['id'].lower() 

557 if hasattr(self, fixed_methodname): 

558 fix = getattr(self, fixed_methodname) 

559 

560 line_index = result['line'] - 1 

561 original_line = self.source[line_index] 

562 

563 is_logical_fix = len(_get_parameters(fix)) > 2 

564 if is_logical_fix: 

565 logical = None 

566 if logical_support: 

567 logical = _get_logical(self.source, 

568 result, 

569 logical_start, 

570 logical_end) 

571 if logical and set(range( 

572 logical[0][0] + 1, 

573 logical[1][0] + 1)).intersection( 

574 completed_lines): 

575 continue 

576 

577 if self._check_affected_anothers(result): 

578 continue 

579 modified_lines = fix(result, logical) 

580 else: 

581 if self._check_affected_anothers(result): 

582 continue 

583 modified_lines = fix(result) 

584 

585 if modified_lines is None: 

586 # Force logical fixes to report what they modified. 

587 assert not is_logical_fix 

588 

589 if self.source[line_index] == original_line: 

590 modified_lines = [] 

591 

592 if modified_lines: 

593 completed_lines.update(modified_lines) 

594 elif modified_lines == []: # Empty list means no fix 

595 if self.options.verbose >= 2: 

596 print( 

597 '---> Not fixing {error} on line {line}'.format( 

598 error=result['id'], line=result['line']), 

599 file=sys.stderr) 

600 else: # We assume one-line fix when None. 

601 completed_lines.add(result['line']) 

602 else: 

603 if self.options.verbose >= 3: 

604 print( 

605 "---> '{}' is not defined.".format(fixed_methodname), 

606 file=sys.stderr) 

607 

608 info = result['info'].strip() 

609 print('---> {}:{}:{}:{}'.format(self.filename, 

610 result['line'], 

611 result['column'], 

612 info), 

613 file=sys.stderr) 

614 

615 def fix(self): 

616 """Return a version of the source code with PEP 8 violations fixed.""" 

617 pep8_options = { 

618 'ignore': self.options.ignore, 

619 'select': self.options.select, 

620 'max_line_length': self.options.max_line_length, 

621 'hang_closing': self.options.hang_closing, 

622 } 

623 results = _execute_pep8(pep8_options, self.source) 

624 

625 if self.options.verbose: 

626 progress = {} 

627 for r in results: 

628 if r['id'] not in progress: 

629 progress[r['id']] = set() 

630 progress[r['id']].add(r['line']) 

631 print('---> {n} issue(s) to fix {progress}'.format( 

632 n=len(results), progress=progress), file=sys.stderr) 

633 

634 if self.options.line_range: 

635 start, end = self.options.line_range 

636 results = [r for r in results 

637 if start <= r['line'] <= end] 

638 

639 self._fix_source(filter_results(source=''.join(self.source), 

640 results=results, 

641 aggressive=self.options.aggressive)) 

642 

643 if self.options.line_range: 

644 # If number of lines has changed then change line_range. 

645 count = sum(sline.count('\n') 

646 for sline in self.source[start - 1:end]) 

647 self.options.line_range[1] = start + count - 1 

648 

649 return ''.join(self.source) 

650 

651 def _fix_reindent(self, result): 

652 """Fix a badly indented line. 

653 

654 This is done by adding or removing from its initial indent only. 

655 

656 """ 

657 num_indent_spaces = int(result['info'].split()[1]) 

658 line_index = result['line'] - 1 

659 target = self.source[line_index] 

660 

661 self.source[line_index] = ' ' * num_indent_spaces + target.lstrip() 

662 

663 def fix_e112(self, result): 

664 """Fix under-indented comments.""" 

665 line_index = result['line'] - 1 

666 target = self.source[line_index] 

667 

668 if not target.lstrip().startswith('#'): 

669 # Don't screw with invalid syntax. 

670 return [] 

671 

672 self.source[line_index] = self.indent_word + target 

673 

674 def fix_e113(self, result): 

675 """Fix unexpected indentation.""" 

676 line_index = result['line'] - 1 

677 target = self.source[line_index] 

678 indent = _get_indentation(target) 

679 stripped = target.lstrip() 

680 self.source[line_index] = indent[1:] + stripped 

681 

682 def fix_e116(self, result): 

683 """Fix over-indented comments.""" 

684 line_index = result['line'] - 1 

685 target = self.source[line_index] 

686 

687 indent = _get_indentation(target) 

688 stripped = target.lstrip() 

689 

690 if not stripped.startswith('#'): 

691 # Don't screw with invalid syntax. 

692 return [] 

693 

694 self.source[line_index] = indent[1:] + stripped 

695 

696 def fix_e117(self, result): 

697 """Fix over-indented.""" 

698 line_index = result['line'] - 1 

699 target = self.source[line_index] 

700 

701 indent = _get_indentation(target) 

702 if indent == '\t': 

703 return [] 

704 

705 stripped = target.lstrip() 

706 

707 self.source[line_index] = indent[1:] + stripped 

708 

709 def fix_e125(self, result): 

710 """Fix indentation undistinguish from the next logical line.""" 

711 num_indent_spaces = int(result['info'].split()[1]) 

712 line_index = result['line'] - 1 

713 target = self.source[line_index] 

714 

715 spaces_to_add = num_indent_spaces - len(_get_indentation(target)) 

716 indent = len(_get_indentation(target)) 

717 modified_lines = [] 

718 

719 while len(_get_indentation(self.source[line_index])) >= indent: 

720 self.source[line_index] = (' ' * spaces_to_add + 

721 self.source[line_index]) 

722 modified_lines.append(1 + line_index) # Line indexed at 1. 

723 line_index -= 1 

724 

725 return modified_lines 

726 

727 def fix_e131(self, result): 

728 """Fix indentation undistinguish from the next logical line.""" 

729 num_indent_spaces = int(result['info'].split()[1]) 

730 line_index = result['line'] - 1 

731 target = self.source[line_index] 

732 

733 spaces_to_add = num_indent_spaces - len(_get_indentation(target)) 

734 

735 indent_length = len(_get_indentation(target)) 

736 spaces_to_add = num_indent_spaces - indent_length 

737 if num_indent_spaces == 0 and indent_length == 0: 

738 spaces_to_add = 4 

739 

740 if spaces_to_add >= 0: 

741 self.source[line_index] = (' ' * spaces_to_add + 

742 self.source[line_index]) 

743 else: 

744 offset = abs(spaces_to_add) 

745 self.source[line_index] = self.source[line_index][offset:] 

746 

747 def fix_e201(self, result): 

748 """Remove extraneous whitespace.""" 

749 line_index = result['line'] - 1 

750 target = self.source[line_index] 

751 offset = result['column'] - 1 

752 

753 fixed = fix_whitespace(target, 

754 offset=offset, 

755 replacement='') 

756 

757 self.source[line_index] = fixed 

758 

759 def fix_e224(self, result): 

760 """Remove extraneous whitespace around operator.""" 

761 target = self.source[result['line'] - 1] 

762 offset = result['column'] - 1 

763 fixed = target[:offset] + target[offset:].replace('\t', ' ') 

764 self.source[result['line'] - 1] = fixed 

765 

766 def fix_e225(self, result): 

767 """Fix missing whitespace around operator.""" 

768 target = self.source[result['line'] - 1] 

769 offset = result['column'] - 1 

770 fixed = target[:offset] + ' ' + target[offset:] 

771 

772 # Only proceed if non-whitespace characters match. 

773 # And make sure we don't break the indentation. 

774 if ( 

775 fixed.replace(' ', '') == target.replace(' ', '') and 

776 _get_indentation(fixed) == _get_indentation(target) 

777 ): 

778 self.source[result['line'] - 1] = fixed 

779 error_code = result.get('id', 0) 

780 try: 

781 ts = generate_tokens(fixed) 

782 except (SyntaxError, tokenize.TokenError): 

783 return 

784 if not check_syntax(fixed.lstrip()): 

785 return 

786 try: 

787 _missing_whitespace = ( 

788 pycodestyle.missing_whitespace_around_operator 

789 ) 

790 except AttributeError: 

791 # pycodestyle >= 2.11.0 

792 _missing_whitespace = pycodestyle.missing_whitespace 

793 errors = list(_missing_whitespace(fixed, ts)) 

794 for e in reversed(errors): 

795 if error_code != e[1].split()[0]: 

796 continue 

797 offset = e[0][1] 

798 fixed = fixed[:offset] + ' ' + fixed[offset:] 

799 self.source[result['line'] - 1] = fixed 

800 else: 

801 return [] 

802 

803 def fix_e231(self, result): 

804 """Add missing whitespace.""" 

805 line_index = result['line'] - 1 

806 target = self.source[line_index] 

807 offset = result['column'] 

808 fixed = target[:offset].rstrip() + ' ' + target[offset:].lstrip() 

809 self.source[line_index] = fixed 

810 

811 def fix_e251(self, result): 

812 """Remove whitespace around parameter '=' sign.""" 

813 line_index = result['line'] - 1 

814 target = self.source[line_index] 

815 

816 # This is necessary since pycodestyle sometimes reports columns that 

817 # goes past the end of the physical line. This happens in cases like, 

818 # foo(bar\n=None) 

819 c = min(result['column'] - 1, 

820 len(target) - 1) 

821 

822 if target[c].strip(): 

823 fixed = target 

824 else: 

825 fixed = target[:c].rstrip() + target[c:].lstrip() 

826 

827 # There could be an escaped newline 

828 # 

829 # def foo(a=\ 

830 # 1) 

831 if fixed.endswith(('=\\\n', '=\\\r\n', '=\\\r')): 

832 self.source[line_index] = fixed.rstrip('\n\r \t\\') 

833 self.source[line_index + 1] = self.source[line_index + 1].lstrip() 

834 return [line_index + 1, line_index + 2] # Line indexed at 1 

835 

836 self.source[result['line'] - 1] = fixed 

837 

838 def fix_e262(self, result): 

839 """Fix spacing after inline comment hash.""" 

840 target = self.source[result['line'] - 1] 

841 offset = result['column'] 

842 

843 code = target[:offset].rstrip(' \t#') 

844 comment = target[offset:].lstrip(' \t#') 

845 

846 fixed = code + (' # ' + comment if comment.strip() else '\n') 

847 

848 self.source[result['line'] - 1] = fixed 

849 

850 def fix_e265(self, result): 

851 """Fix spacing after block comment hash.""" 

852 target = self.source[result['line'] - 1] 

853 

854 indent = _get_indentation(target) 

855 line = target.lstrip(' \t') 

856 pos = next((index for index, c in enumerate(line) if c != '#')) 

857 hashes = line[:pos] 

858 comment = line[pos:].lstrip(' \t') 

859 

860 # Ignore special comments, even in the middle of the file. 

861 if comment.startswith('!'): 

862 return 

863 

864 fixed = indent + hashes + (' ' + comment if comment.strip() else '\n') 

865 

866 self.source[result['line'] - 1] = fixed 

867 

868 def fix_e266(self, result): 

869 """Fix too many block comment hashes.""" 

870 target = self.source[result['line'] - 1] 

871 

872 # Leave stylistic outlined blocks alone. 

873 if target.strip().endswith('#'): 

874 return 

875 

876 indentation = _get_indentation(target) 

877 fixed = indentation + '# ' + target.lstrip('# \t') 

878 

879 self.source[result['line'] - 1] = fixed 

880 

881 def fix_e271(self, result): 

882 """Fix extraneous whitespace around keywords.""" 

883 line_index = result['line'] - 1 

884 target = self.source[line_index] 

885 offset = result['column'] - 1 

886 

887 fixed = fix_whitespace(target, 

888 offset=offset, 

889 replacement=' ') 

890 

891 if fixed == target: 

892 return [] 

893 else: 

894 self.source[line_index] = fixed 

895 

896 def fix_e301(self, result): 

897 """Add missing blank line.""" 

898 cr = '\n' 

899 self.source[result['line'] - 1] = cr + self.source[result['line'] - 1] 

900 

901 def fix_e302(self, result): 

902 """Add missing 2 blank lines.""" 

903 add_linenum = 2 - int(result['info'].split()[-1]) 

904 offset = 1 

905 if self.source[result['line'] - 2].strip() == "\\": 

906 offset = 2 

907 cr = '\n' * add_linenum 

908 self.source[result['line'] - offset] = ( 

909 cr + self.source[result['line'] - offset] 

910 ) 

911 

912 def fix_e303(self, result): 

913 """Remove extra blank lines.""" 

914 delete_linenum = int(result['info'].split('(')[1].split(')')[0]) - 2 

915 delete_linenum = max(1, delete_linenum) 

916 

917 # We need to count because pycodestyle reports an offset line number if 

918 # there are comments. 

919 cnt = 0 

920 line = result['line'] - 2 

921 modified_lines = [] 

922 while cnt < delete_linenum and line >= 0: 

923 if not self.source[line].strip(): 

924 self.source[line] = '' 

925 modified_lines.append(1 + line) # Line indexed at 1 

926 cnt += 1 

927 line -= 1 

928 

929 return modified_lines 

930 

931 def fix_e304(self, result): 

932 """Remove blank line following function decorator.""" 

933 line = result['line'] - 2 

934 if not self.source[line].strip(): 

935 self.source[line] = '' 

936 

937 def fix_e305(self, result): 

938 """Add missing 2 blank lines after end of function or class.""" 

939 add_delete_linenum = 2 - int(result['info'].split()[-1]) 

940 cnt = 0 

941 offset = result['line'] - 2 

942 modified_lines = [] 

943 if add_delete_linenum < 0: 

944 # delete cr 

945 add_delete_linenum = abs(add_delete_linenum) 

946 while cnt < add_delete_linenum and offset >= 0: 

947 if not self.source[offset].strip(): 

948 self.source[offset] = '' 

949 modified_lines.append(1 + offset) # Line indexed at 1 

950 cnt += 1 

951 offset -= 1 

952 else: 

953 # add cr 

954 cr = '\n' 

955 # check comment line 

956 while True: 

957 if offset < 0: 

958 break 

959 line = self.source[offset].lstrip() 

960 if not line: 

961 break 

962 if line[0] != '#': 

963 break 

964 offset -= 1 

965 offset += 1 

966 self.source[offset] = cr + self.source[offset] 

967 modified_lines.append(1 + offset) # Line indexed at 1. 

968 return modified_lines 

969 

970 def fix_e401(self, result): 

971 """Put imports on separate lines.""" 

972 line_index = result['line'] - 1 

973 target = self.source[line_index] 

974 offset = result['column'] - 1 

975 

976 if not target.lstrip().startswith('import'): 

977 return [] 

978 

979 indentation = re.split(pattern=r'\bimport\b', 

980 string=target, maxsplit=1)[0] 

981 fixed = (target[:offset].rstrip('\t ,') + '\n' + 

982 indentation + 'import ' + target[offset:].lstrip('\t ,')) 

983 self.source[line_index] = fixed 

984 

985 def fix_e402(self, result): 

986 (line_index, offset, target) = get_index_offset_contents(result, 

987 self.source) 

988 for i in range(1, 100): 

989 line = "".join(self.source[line_index:line_index+i]) 

990 try: 

991 generate_tokens("".join(line)) 

992 except (SyntaxError, tokenize.TokenError): 

993 continue 

994 break 

995 if not (target in self.imports and self.imports[target] != line_index): 

996 mod_offset = get_module_imports_on_top_of_file(self.source, 

997 line_index) 

998 self.source[mod_offset] = line + self.source[mod_offset] 

999 for offset in range(i): 

1000 self.source[line_index+offset] = '' 

1001 

1002 def fix_long_line_logically(self, result, logical): 

1003 """Try to make lines fit within --max-line-length characters.""" 

1004 if ( 

1005 not logical or 

1006 len(logical[2]) == 1 or 

1007 self.source[result['line'] - 1].lstrip().startswith('#') 

1008 ): 

1009 return self.fix_long_line_physically(result) 

1010 

1011 start_line_index = logical[0][0] 

1012 end_line_index = logical[1][0] 

1013 logical_lines = logical[2] 

1014 

1015 previous_line = get_item(self.source, start_line_index - 1, default='') 

1016 next_line = get_item(self.source, end_line_index + 1, default='') 

1017 

1018 single_line = join_logical_line(''.join(logical_lines)) 

1019 

1020 try: 

1021 fixed = self.fix_long_line( 

1022 target=single_line, 

1023 previous_line=previous_line, 

1024 next_line=next_line, 

1025 original=''.join(logical_lines)) 

1026 except (SyntaxError, tokenize.TokenError): 

1027 return self.fix_long_line_physically(result) 

1028 

1029 if fixed: 

1030 for line_index in range(start_line_index, end_line_index + 1): 

1031 self.source[line_index] = '' 

1032 self.source[start_line_index] = fixed 

1033 return range(start_line_index + 1, end_line_index + 1) 

1034 

1035 return [] 

1036 

1037 def fix_long_line_physically(self, result): 

1038 """Try to make lines fit within --max-line-length characters.""" 

1039 line_index = result['line'] - 1 

1040 target = self.source[line_index] 

1041 

1042 previous_line = get_item(self.source, line_index - 1, default='') 

1043 next_line = get_item(self.source, line_index + 1, default='') 

1044 

1045 try: 

1046 fixed = self.fix_long_line( 

1047 target=target, 

1048 previous_line=previous_line, 

1049 next_line=next_line, 

1050 original=target) 

1051 except (SyntaxError, tokenize.TokenError): 

1052 return [] 

1053 

1054 if fixed: 

1055 self.source[line_index] = fixed 

1056 return [line_index + 1] 

1057 

1058 return [] 

1059 

1060 def fix_long_line(self, target, previous_line, 

1061 next_line, original): 

1062 cache_entry = (target, previous_line, next_line) 

1063 if cache_entry in self.long_line_ignore_cache: 

1064 return [] 

1065 

1066 if target.lstrip().startswith('#'): 

1067 if self.options.aggressive: 

1068 # Wrap commented lines. 

1069 return shorten_comment( 

1070 line=target, 

1071 max_line_length=self.options.max_line_length, 

1072 last_comment=not next_line.lstrip().startswith('#')) 

1073 return [] 

1074 

1075 fixed = get_fixed_long_line( 

1076 target=target, 

1077 previous_line=previous_line, 

1078 original=original, 

1079 indent_word=self.indent_word, 

1080 max_line_length=self.options.max_line_length, 

1081 aggressive=self.options.aggressive, 

1082 experimental=self.options.experimental, 

1083 verbose=self.options.verbose) 

1084 

1085 if fixed and not code_almost_equal(original, fixed): 

1086 return fixed 

1087 

1088 self.long_line_ignore_cache.add(cache_entry) 

1089 return None 

1090 

1091 def fix_e502(self, result): 

1092 """Remove extraneous escape of newline.""" 

1093 (line_index, _, target) = get_index_offset_contents(result, 

1094 self.source) 

1095 self.source[line_index] = target.rstrip('\n\r \t\\') + '\n' 

1096 

1097 def fix_e701(self, result): 

1098 """Put colon-separated compound statement on separate lines.""" 

1099 line_index = result['line'] - 1 

1100 target = self.source[line_index] 

1101 c = result['column'] 

1102 

1103 fixed_source = (target[:c] + '\n' + 

1104 _get_indentation(target) + self.indent_word + 

1105 target[c:].lstrip('\n\r \t\\')) 

1106 self.source[result['line'] - 1] = fixed_source 

1107 return [result['line'], result['line'] + 1] 

1108 

1109 def fix_e702(self, result, logical): 

1110 """Put semicolon-separated compound statement on separate lines.""" 

1111 if not logical: 

1112 return [] # pragma: no cover 

1113 logical_lines = logical[2] 

1114 

1115 # Avoid applying this when indented. 

1116 # https://docs.python.org/reference/compound_stmts.html 

1117 for line in logical_lines: 

1118 if ( 

1119 result['id'] == 'E702' 

1120 and ':' in line 

1121 and pycodestyle.STARTSWITH_INDENT_STATEMENT_REGEX.match(line) 

1122 ): 

1123 if self.options.verbose: 

1124 print( 

1125 '---> avoid fixing {error} with ' 

1126 'other compound statements'.format(error=result['id']), 

1127 file=sys.stderr 

1128 ) 

1129 return [] 

1130 

1131 line_index = result['line'] - 1 

1132 target = self.source[line_index] 

1133 

1134 if target.rstrip().endswith('\\'): 

1135 # Normalize '1; \\\n2' into '1; 2'. 

1136 self.source[line_index] = target.rstrip('\n \r\t\\') 

1137 self.source[line_index + 1] = self.source[line_index + 1].lstrip() 

1138 return [line_index + 1, line_index + 2] 

1139 

1140 if target.rstrip().endswith(';'): 

1141 self.source[line_index] = target.rstrip('\n \r\t;') + '\n' 

1142 return [line_index + 1] 

1143 

1144 offset = result['column'] - 1 

1145 first = target[:offset].rstrip(';').rstrip() 

1146 second = (_get_indentation(logical_lines[0]) + 

1147 target[offset:].lstrip(';').lstrip()) 

1148 

1149 # Find inline comment. 

1150 inline_comment = None 

1151 if target[offset:].lstrip(';').lstrip()[:2] == '# ': 

1152 inline_comment = target[offset:].lstrip(';') 

1153 

1154 if inline_comment: 

1155 self.source[line_index] = first + inline_comment 

1156 else: 

1157 self.source[line_index] = first + '\n' + second 

1158 return [line_index + 1] 

1159 

1160 def fix_e704(self, result): 

1161 """Fix multiple statements on one line def""" 

1162 (line_index, _, target) = get_index_offset_contents(result, 

1163 self.source) 

1164 match = STARTSWITH_DEF_REGEX.match(target) 

1165 if match: 

1166 self.source[line_index] = '{}\n{}{}'.format( 

1167 match.group(0), 

1168 _get_indentation(target) + self.indent_word, 

1169 target[match.end(0):].lstrip()) 

1170 

1171 def fix_e711(self, result): 

1172 """Fix comparison with None.""" 

1173 (line_index, offset, target) = get_index_offset_contents(result, 

1174 self.source) 

1175 

1176 right_offset = offset + 2 

1177 if right_offset >= len(target): 

1178 return [] 

1179 

1180 left = target[:offset].rstrip() 

1181 center = target[offset:right_offset] 

1182 right = target[right_offset:].lstrip() 

1183 

1184 if center.strip() == '==': 

1185 new_center = 'is' 

1186 elif center.strip() == '!=': 

1187 new_center = 'is not' 

1188 else: 

1189 return [] 

1190 

1191 self.source[line_index] = ' '.join([left, new_center, right]) 

1192 

1193 def fix_e712(self, result): 

1194 """Fix (trivial case of) comparison with boolean.""" 

1195 (line_index, offset, target) = get_index_offset_contents(result, 

1196 self.source) 

1197 

1198 # Handle very easy "not" special cases. 

1199 if re.match(r'^\s*if [\w."\'\[\]]+ == False:$', target): 

1200 self.source[line_index] = re.sub(r'if ([\w."\'\[\]]+) == False:', 

1201 r'if not \1:', target, count=1) 

1202 elif re.match(r'^\s*if [\w."\'\[\]]+ != True:$', target): 

1203 self.source[line_index] = re.sub(r'if ([\w."\'\[\]]+) != True:', 

1204 r'if not \1:', target, count=1) 

1205 else: 

1206 right_offset = offset + 2 

1207 if right_offset >= len(target): 

1208 return [] 

1209 

1210 left = target[:offset].rstrip() 

1211 center = target[offset:right_offset] 

1212 right = target[right_offset:].lstrip() 

1213 

1214 # Handle simple cases only. 

1215 new_right = None 

1216 if center.strip() == '==': 

1217 if re.match(r'\bTrue\b', right): 

1218 new_right = re.sub(r'\bTrue\b *', '', right, count=1) 

1219 elif center.strip() == '!=': 

1220 if re.match(r'\bFalse\b', right): 

1221 new_right = re.sub(r'\bFalse\b *', '', right, count=1) 

1222 

1223 if new_right is None: 

1224 return [] 

1225 

1226 if new_right[0].isalnum(): 

1227 new_right = ' ' + new_right 

1228 

1229 self.source[line_index] = left + new_right 

1230 

1231 def fix_e713(self, result): 

1232 """Fix (trivial case of) non-membership check.""" 

1233 (line_index, offset, target) = get_index_offset_contents(result, 

1234 self.source) 

1235 

1236 # to convert once 'not in' -> 'in' 

1237 before_target = target[:offset] 

1238 target = target[offset:] 

1239 match_notin = COMPARE_NEGATIVE_REGEX_THROUGH.search(target) 

1240 notin_pos_start, notin_pos_end = 0, 0 

1241 if match_notin: 

1242 notin_pos_start = match_notin.start(1) 

1243 notin_pos_end = match_notin.end() 

1244 target = '{}{} {}'.format( 

1245 target[:notin_pos_start], 'in', target[notin_pos_end:]) 

1246 

1247 # fix 'not in' 

1248 match = COMPARE_NEGATIVE_REGEX.search(target) 

1249 if match: 

1250 if match.group(3) == 'in': 

1251 pos_start = match.start(1) 

1252 new_target = '{5}{0}{1} {2} {3} {4}'.format( 

1253 target[:pos_start], match.group(2), match.group(1), 

1254 match.group(3), target[match.end():], before_target) 

1255 if match_notin: 

1256 # revert 'in' -> 'not in' 

1257 pos_start = notin_pos_start + offset 

1258 pos_end = notin_pos_end + offset - 4 # len('not ') 

1259 new_target = '{}{} {}'.format( 

1260 new_target[:pos_start], 'not in', new_target[pos_end:]) 

1261 self.source[line_index] = new_target 

1262 

1263 def fix_e714(self, result): 

1264 """Fix object identity should be 'is not' case.""" 

1265 (line_index, offset, target) = get_index_offset_contents(result, 

1266 self.source) 

1267 

1268 # to convert once 'is not' -> 'is' 

1269 before_target = target[:offset] 

1270 target = target[offset:] 

1271 match_isnot = COMPARE_NEGATIVE_REGEX_THROUGH.search(target) 

1272 isnot_pos_start, isnot_pos_end = 0, 0 

1273 if match_isnot: 

1274 isnot_pos_start = match_isnot.start(1) 

1275 isnot_pos_end = match_isnot.end() 

1276 target = '{}{} {}'.format( 

1277 target[:isnot_pos_start], 'in', target[isnot_pos_end:]) 

1278 

1279 match = COMPARE_NEGATIVE_REGEX.search(target) 

1280 if match: 

1281 if match.group(3).startswith('is'): 

1282 pos_start = match.start(1) 

1283 new_target = '{5}{0}{1} {2} {3} {4}'.format( 

1284 target[:pos_start], match.group(2), match.group(3), 

1285 match.group(1), target[match.end():], before_target) 

1286 if match_isnot: 

1287 # revert 'is' -> 'is not' 

1288 pos_start = isnot_pos_start + offset 

1289 pos_end = isnot_pos_end + offset - 4 # len('not ') 

1290 new_target = '{}{} {}'.format( 

1291 new_target[:pos_start], 'is not', new_target[pos_end:]) 

1292 self.source[line_index] = new_target 

1293 

1294 def fix_e721(self, result): 

1295 """fix comparison type""" 

1296 (line_index, _, target) = get_index_offset_contents(result, 

1297 self.source) 

1298 match = COMPARE_TYPE_REGEX.search(target) 

1299 if match: 

1300 # NOTE: match objects 

1301 # * type(a) == type(b) -> (None, None, 'a', '==') 

1302 # * str == type(b) -> ('==', 'b', None, None) 

1303 # * type(b) == str -> (None, None, 'b', '==') 

1304 # * type("") != type(b) -> (None, None, '""', '!=') 

1305 start = match.start() 

1306 end = match.end() 

1307 _prefix = "" 

1308 _suffix = "" 

1309 first_match_type_obj = match.groups()[1] 

1310 if first_match_type_obj is None: 

1311 _target_obj = match.groups()[2] 

1312 else: 

1313 _target_obj = match.groups()[1] 

1314 _suffix = target[end:] 

1315 

1316 isinstance_stmt = " isinstance" 

1317 is_not_condition = ( 

1318 match.groups()[0] == "!=" or match.groups()[3] == "!=" 

1319 ) 

1320 if is_not_condition: 

1321 isinstance_stmt = " not isinstance" 

1322 

1323 _type_comp = f"{_target_obj}, {target[:start]}" 

1324 indent_match = re.match(r'^\s+', target) 

1325 indent = "" 

1326 if indent_match: 

1327 indent = indent_match.group() 

1328 

1329 _prefix_tmp = target[:start].split() 

1330 if len(_prefix_tmp) >= 1: 

1331 _type_comp = f"{_target_obj}, {target[:start]}" 

1332 if first_match_type_obj is not None: 

1333 _prefix = " ".join(_prefix_tmp[:-1]) 

1334 _type_comp = f"{_target_obj}, {_prefix_tmp[-1]}" 

1335 else: 

1336 _prefix = " ".join(_prefix_tmp) 

1337 

1338 _suffix_tmp = target[end:] 

1339 _suffix_type_match = TYPE_REGEX.search(_suffix_tmp) 

1340 if _suffix_type_match: 

1341 if len(_suffix_tmp.split()) >= 1: 

1342 type_match_end = _suffix_type_match.end() 

1343 _suffix = _suffix_tmp[type_match_end:] 

1344 cmp_b = _suffix_type_match.groups()[0] 

1345 _type_comp = f"{_target_obj}, {cmp_b}" 

1346 else: 

1347 _else_suffix_match = re.match( 

1348 r"^\s*([^\s:]+)(.*)$", 

1349 _suffix_tmp, 

1350 ) 

1351 if _else_suffix_match: 

1352 _else_suffix = _else_suffix_match.group(1) 

1353 _else_suffix_other = _else_suffix_match.group(2) 

1354 _type_comp = f"{_target_obj}, {_else_suffix}" 

1355 _else_suffix_end = _suffix_tmp[_else_suffix_match.end():] 

1356 _suffix = f"{_else_suffix_other}{_else_suffix_end}" 

1357 # `else` route is not care 

1358 

1359 fix_line = ( 

1360 f"{indent}{_prefix}{isinstance_stmt}({_type_comp}){_suffix}" 

1361 ) 

1362 self.source[line_index] = fix_line 

1363 

1364 def fix_e722(self, result): 

1365 """fix bare except""" 

1366 (line_index, _, target) = get_index_offset_contents(result, 

1367 self.source) 

1368 match = BARE_EXCEPT_REGEX.search(target) 

1369 if match: 

1370 self.source[line_index] = '{}{}{}'.format( 

1371 target[:result['column'] - 1], "except BaseException:", 

1372 target[match.end():]) 

1373 

1374 def fix_e731(self, result): 

1375 """Fix do not assign a lambda expression check.""" 

1376 (line_index, _, target) = get_index_offset_contents(result, 

1377 self.source) 

1378 match = LAMBDA_REGEX.search(target) 

1379 if match: 

1380 end = match.end() 

1381 self.source[line_index] = '{}def {}({}): return {}'.format( 

1382 target[:match.start(0)], match.group(1), match.group(2), 

1383 target[end:].lstrip()) 

1384 

1385 def fix_w291(self, result): 

1386 """Remove trailing whitespace.""" 

1387 fixed_line = self.source[result['line'] - 1].rstrip() 

1388 self.source[result['line'] - 1] = fixed_line + '\n' 

1389 

1390 def fix_w391(self, _): 

1391 """Remove trailing blank lines.""" 

1392 blank_count = 0 

1393 for line in reversed(self.source): 

1394 line = line.rstrip() 

1395 if line: 

1396 break 

1397 else: 

1398 blank_count += 1 

1399 

1400 original_length = len(self.source) 

1401 self.source = self.source[:original_length - blank_count] 

1402 return range(1, 1 + original_length) 

1403 

1404 def fix_w503(self, result): 

1405 (line_index, _, target) = get_index_offset_contents(result, 

1406 self.source) 

1407 one_string_token = target.split()[0] 

1408 try: 

1409 ts = generate_tokens(one_string_token) 

1410 except (SyntaxError, tokenize.TokenError): 

1411 return 

1412 if not _is_binary_operator(ts[0][0], one_string_token): 

1413 return 

1414 # find comment 

1415 comment_index = 0 

1416 found_not_comment_only_line = False 

1417 comment_only_linenum = 0 

1418 for i in range(5): 

1419 # NOTE: try to parse code in 5 times 

1420 if (line_index - i) < 0: 

1421 break 

1422 from_index = line_index - i - 1 

1423 if from_index < 0 or len(self.source) <= from_index: 

1424 break 

1425 to_index = line_index + 1 

1426 strip_line = self.source[from_index].lstrip() 

1427 if ( 

1428 not found_not_comment_only_line and 

1429 strip_line and strip_line[0] == '#' 

1430 ): 

1431 comment_only_linenum += 1 

1432 continue 

1433 found_not_comment_only_line = True 

1434 try: 

1435 ts = generate_tokens("".join(self.source[from_index:to_index])) 

1436 except (SyntaxError, tokenize.TokenError): 

1437 continue 

1438 newline_count = 0 

1439 newline_index = [] 

1440 for index, t in enumerate(ts): 

1441 if t[0] in (tokenize.NEWLINE, tokenize.NL): 

1442 newline_index.append(index) 

1443 newline_count += 1 

1444 if newline_count > 2: 

1445 tts = ts[newline_index[-3]:] 

1446 else: 

1447 tts = ts 

1448 old = [] 

1449 for t in tts: 

1450 if t[0] in (tokenize.NEWLINE, tokenize.NL): 

1451 newline_count -= 1 

1452 if newline_count <= 1: 

1453 break 

1454 if tokenize.COMMENT == t[0] and old and old[0] != tokenize.NL: 

1455 comment_index = old[3][1] 

1456 break 

1457 old = t 

1458 break 

1459 i = target.index(one_string_token) 

1460 fix_target_line = line_index - 1 - comment_only_linenum 

1461 self.source[line_index] = '{}{}'.format( 

1462 target[:i], target[i + len(one_string_token):].lstrip()) 

1463 nl = find_newline(self.source[fix_target_line:line_index]) 

1464 before_line = self.source[fix_target_line] 

1465 bl = before_line.index(nl) 

1466 if comment_index: 

1467 self.source[fix_target_line] = '{} {} {}'.format( 

1468 before_line[:comment_index], one_string_token, 

1469 before_line[comment_index + 1:]) 

1470 else: 

1471 if before_line[:bl].endswith("#"): 

1472 # special case 

1473 # see: https://github.com/hhatto/autopep8/issues/503 

1474 self.source[fix_target_line] = '{}{} {}'.format( 

1475 before_line[:bl-2], one_string_token, before_line[bl-2:]) 

1476 else: 

1477 self.source[fix_target_line] = '{} {}{}'.format( 

1478 before_line[:bl], one_string_token, before_line[bl:]) 

1479 

1480 def fix_w504(self, result): 

1481 (line_index, _, target) = get_index_offset_contents(result, 

1482 self.source) 

1483 # NOTE: is not collect pointed out in pycodestyle==2.4.0 

1484 comment_index = 0 

1485 operator_position = None # (start_position, end_position) 

1486 for i in range(1, 6): 

1487 to_index = line_index + i 

1488 try: 

1489 ts = generate_tokens("".join(self.source[line_index:to_index])) 

1490 except (SyntaxError, tokenize.TokenError): 

1491 continue 

1492 newline_count = 0 

1493 newline_index = [] 

1494 for index, t in enumerate(ts): 

1495 if _is_binary_operator(t[0], t[1]): 

1496 if t[2][0] == 1 and t[3][0] == 1: 

1497 operator_position = (t[2][1], t[3][1]) 

1498 elif t[0] == tokenize.NAME and t[1] in ("and", "or"): 

1499 if t[2][0] == 1 and t[3][0] == 1: 

1500 operator_position = (t[2][1], t[3][1]) 

1501 elif t[0] in (tokenize.NEWLINE, tokenize.NL): 

1502 newline_index.append(index) 

1503 newline_count += 1 

1504 if newline_count > 2: 

1505 tts = ts[:newline_index[-3]] 

1506 else: 

1507 tts = ts 

1508 old = [] 

1509 for t in tts: 

1510 if tokenize.COMMENT == t[0] and old: 

1511 comment_row, comment_index = old[3] 

1512 break 

1513 old = t 

1514 break 

1515 if not operator_position: 

1516 return 

1517 target_operator = target[operator_position[0]:operator_position[1]] 

1518 

1519 if comment_index and comment_row == 1: 

1520 self.source[line_index] = '{}{}'.format( 

1521 target[:operator_position[0]].rstrip(), 

1522 target[comment_index:]) 

1523 else: 

1524 self.source[line_index] = '{}{}{}'.format( 

1525 target[:operator_position[0]].rstrip(), 

1526 target[operator_position[1]:].lstrip(), 

1527 target[operator_position[1]:]) 

1528 

1529 next_line = self.source[line_index + 1] 

1530 next_line_indent = 0 

1531 m = re.match(r'\s*', next_line) 

1532 if m: 

1533 next_line_indent = m.span()[1] 

1534 self.source[line_index + 1] = '{}{} {}'.format( 

1535 next_line[:next_line_indent], target_operator, 

1536 next_line[next_line_indent:]) 

1537 

1538 def fix_w605(self, result): 

1539 (line_index, offset, target) = get_index_offset_contents(result, 

1540 self.source) 

1541 self.source[line_index] = '{}\\{}'.format( 

1542 target[:offset + 1], target[offset + 1:]) 

1543 

1544 

1545def get_module_imports_on_top_of_file(source, import_line_index): 

1546 """return import or from keyword position 

1547 

1548 example: 

1549 > 0: import sys 

1550 1: import os 

1551 2: 

1552 3: def function(): 

1553 """ 

1554 def is_string_literal(line): 

1555 if line[0] in 'uUbB': 

1556 line = line[1:] 

1557 if line and line[0] in 'rR': 

1558 line = line[1:] 

1559 return line and (line[0] == '"' or line[0] == "'") 

1560 

1561 def is_future_import(line): 

1562 nodes = ast.parse(line) 

1563 for n in nodes.body: 

1564 if isinstance(n, ast.ImportFrom) and n.module == '__future__': 

1565 return True 

1566 return False 

1567 

1568 def has_future_import(source): 

1569 offset = 0 

1570 line = '' 

1571 for _, next_line in source: 

1572 for line_part in next_line.strip().splitlines(True): 

1573 line = line + line_part 

1574 try: 

1575 return is_future_import(line), offset 

1576 except SyntaxError: 

1577 continue 

1578 offset += 1 

1579 return False, offset 

1580 

1581 allowed_try_keywords = ('try', 'except', 'else', 'finally') 

1582 in_docstring = False 

1583 docstring_kind = '"""' 

1584 source_stream = iter(enumerate(source)) 

1585 for cnt, line in source_stream: 

1586 if not in_docstring: 

1587 m = DOCSTRING_START_REGEX.match(line.lstrip()) 

1588 if m is not None: 

1589 in_docstring = True 

1590 docstring_kind = m.group('kind') 

1591 remain = line[m.end(): m.endpos].rstrip() 

1592 if remain[-3:] == docstring_kind: # one line doc 

1593 in_docstring = False 

1594 continue 

1595 if in_docstring: 

1596 if line.rstrip()[-3:] == docstring_kind: 

1597 in_docstring = False 

1598 continue 

1599 

1600 if not line.rstrip(): 

1601 continue 

1602 elif line.startswith('#'): 

1603 continue 

1604 

1605 if line.startswith('import '): 

1606 if cnt == import_line_index: 

1607 continue 

1608 return cnt 

1609 elif line.startswith('from '): 

1610 if cnt == import_line_index: 

1611 continue 

1612 hit, offset = has_future_import( 

1613 itertools.chain([(cnt, line)], source_stream) 

1614 ) 

1615 if hit: 

1616 # move to the back 

1617 return cnt + offset + 1 

1618 return cnt 

1619 elif pycodestyle.DUNDER_REGEX.match(line): 

1620 return cnt 

1621 elif any(line.startswith(kw) for kw in allowed_try_keywords): 

1622 continue 

1623 elif is_string_literal(line): 

1624 return cnt 

1625 else: 

1626 return cnt 

1627 return 0 

1628 

1629 

1630def get_index_offset_contents(result, source): 

1631 """Return (line_index, column_offset, line_contents).""" 

1632 line_index = result['line'] - 1 

1633 return (line_index, 

1634 result['column'] - 1, 

1635 source[line_index]) 

1636 

1637 

1638def get_fixed_long_line(target, previous_line, original, 

1639 indent_word=' ', max_line_length=79, 

1640 aggressive=0, experimental=False, verbose=False): 

1641 """Break up long line and return result. 

1642 

1643 Do this by generating multiple reformatted candidates and then 

1644 ranking the candidates to heuristically select the best option. 

1645 

1646 """ 

1647 indent = _get_indentation(target) 

1648 source = target[len(indent):] 

1649 assert source.lstrip() == source 

1650 assert not target.lstrip().startswith('#') 

1651 

1652 # Check for partial multiline. 

1653 tokens = list(generate_tokens(source)) 

1654 

1655 candidates = shorten_line( 

1656 tokens, source, indent, 

1657 indent_word, 

1658 max_line_length, 

1659 aggressive=aggressive, 

1660 experimental=experimental, 

1661 previous_line=previous_line) 

1662 

1663 # Also sort alphabetically as a tie breaker (for determinism). 

1664 candidates = sorted( 

1665 sorted(set(candidates).union([target, original])), 

1666 key=lambda x: line_shortening_rank( 

1667 x, 

1668 indent_word, 

1669 max_line_length, 

1670 experimental=experimental)) 

1671 

1672 if verbose >= 4: 

1673 print(('-' * 79 + '\n').join([''] + candidates + ['']), 

1674 file=wrap_output(sys.stderr, 'utf-8')) 

1675 

1676 if candidates: 

1677 best_candidate = candidates[0] 

1678 

1679 # Don't allow things to get longer. 

1680 if longest_line_length(best_candidate) > longest_line_length(original): 

1681 return None 

1682 

1683 return best_candidate 

1684 

1685 

1686def longest_line_length(code): 

1687 """Return length of longest line.""" 

1688 if len(code) == 0: 

1689 return 0 

1690 return max(len(line) for line in code.splitlines()) 

1691 

1692 

1693def join_logical_line(logical_line): 

1694 """Return single line based on logical line input.""" 

1695 indentation = _get_indentation(logical_line) 

1696 

1697 return indentation + untokenize_without_newlines( 

1698 generate_tokens(logical_line.lstrip())) + '\n' 

1699 

1700 

1701def untokenize_without_newlines(tokens): 

1702 """Return source code based on tokens.""" 

1703 text = '' 

1704 last_row = 0 

1705 last_column = -1 

1706 

1707 for t in tokens: 

1708 token_string = t[1] 

1709 (start_row, start_column) = t[2] 

1710 (end_row, end_column) = t[3] 

1711 

1712 if start_row > last_row: 

1713 last_column = 0 

1714 if ( 

1715 (start_column > last_column or token_string == '\n') and 

1716 not text.endswith(' ') 

1717 ): 

1718 text += ' ' 

1719 

1720 if token_string != '\n': 

1721 text += token_string 

1722 

1723 last_row = end_row 

1724 last_column = end_column 

1725 

1726 return text.rstrip() 

1727 

1728 

1729def _find_logical(source_lines): 

1730 # Make a variable which is the index of all the starts of lines. 

1731 logical_start = [] 

1732 logical_end = [] 

1733 last_newline = True 

1734 parens = 0 

1735 for t in generate_tokens(''.join(source_lines)): 

1736 if t[0] in [tokenize.COMMENT, tokenize.DEDENT, 

1737 tokenize.INDENT, tokenize.NL, 

1738 tokenize.ENDMARKER]: 

1739 continue 

1740 if not parens and t[0] in [tokenize.NEWLINE, tokenize.SEMI]: 

1741 last_newline = True 

1742 logical_end.append((t[3][0] - 1, t[2][1])) 

1743 continue 

1744 if last_newline and not parens: 

1745 logical_start.append((t[2][0] - 1, t[2][1])) 

1746 last_newline = False 

1747 if t[0] == tokenize.OP: 

1748 if t[1] in '([{': 

1749 parens += 1 

1750 elif t[1] in '}])': 

1751 parens -= 1 

1752 return (logical_start, logical_end) 

1753 

1754 

1755def _get_logical(source_lines, result, logical_start, logical_end): 

1756 """Return the logical line corresponding to the result. 

1757 

1758 Assumes input is already E702-clean. 

1759 

1760 """ 

1761 row = result['line'] - 1 

1762 col = result['column'] - 1 

1763 ls = None 

1764 le = None 

1765 for i in range(0, len(logical_start), 1): 

1766 assert logical_end 

1767 x = logical_end[i] 

1768 if x[0] > row or (x[0] == row and x[1] > col): 

1769 le = x 

1770 ls = logical_start[i] 

1771 break 

1772 if ls is None: 

1773 return None 

1774 original = source_lines[ls[0]:le[0] + 1] 

1775 return ls, le, original 

1776 

1777 

1778def get_item(items, index, default=None): 

1779 if 0 <= index < len(items): 

1780 return items[index] 

1781 

1782 return default 

1783 

1784 

1785def reindent(source, indent_size, leave_tabs=False): 

1786 """Reindent all lines.""" 

1787 reindenter = Reindenter(source, leave_tabs) 

1788 return reindenter.run(indent_size) 

1789 

1790 

1791def code_almost_equal(a, b): 

1792 """Return True if code is similar. 

1793 

1794 Ignore whitespace when comparing specific line. 

1795 

1796 """ 

1797 split_a = split_and_strip_non_empty_lines(a) 

1798 split_b = split_and_strip_non_empty_lines(b) 

1799 

1800 if len(split_a) != len(split_b): 

1801 return False 

1802 

1803 for (index, _) in enumerate(split_a): 

1804 if ''.join(split_a[index].split()) != ''.join(split_b[index].split()): 

1805 return False 

1806 

1807 return True 

1808 

1809 

1810def split_and_strip_non_empty_lines(text): 

1811 """Return lines split by newline. 

1812 

1813 Ignore empty lines. 

1814 

1815 """ 

1816 return [line.strip() for line in text.splitlines() if line.strip()] 

1817 

1818 

1819def find_newline(source): 

1820 """Return type of newline used in source. 

1821 

1822 Input is a list of lines. 

1823 

1824 """ 

1825 assert not isinstance(source, str) 

1826 

1827 counter = collections.defaultdict(int) 

1828 for line in source: 

1829 if line.endswith(CRLF): 

1830 counter[CRLF] += 1 

1831 elif line.endswith(CR): 

1832 counter[CR] += 1 

1833 elif line.endswith(LF): 

1834 counter[LF] += 1 

1835 

1836 return (sorted(counter, key=counter.get, reverse=True) or [LF])[0] 

1837 

1838 

1839def _get_indentword(source): 

1840 """Return indentation type.""" 

1841 indent_word = ' ' # Default in case source has no indentation 

1842 try: 

1843 for t in generate_tokens(source): 

1844 if t[0] == token.INDENT: 

1845 indent_word = t[1] 

1846 break 

1847 except (SyntaxError, tokenize.TokenError): 

1848 pass 

1849 return indent_word 

1850 

1851 

1852def _get_indentation(line): 

1853 """Return leading whitespace.""" 

1854 if line.strip(): 

1855 non_whitespace_index = len(line) - len(line.lstrip()) 

1856 return line[:non_whitespace_index] 

1857 

1858 return '' 

1859 

1860 

1861def get_diff_text(old, new, filename): 

1862 """Return text of unified diff between old and new.""" 

1863 newline = '\n' 

1864 diff = difflib.unified_diff( 

1865 old, new, 

1866 'original/' + filename, 

1867 'fixed/' + filename, 

1868 lineterm=newline) 

1869 

1870 text = '' 

1871 for line in diff: 

1872 text += line 

1873 

1874 # Work around missing newline (http://bugs.python.org/issue2142). 

1875 if text and not line.endswith(newline): 

1876 text += newline + r'\ No newline at end of file' + newline 

1877 

1878 return text 

1879 

1880 

1881def _priority_key(pep8_result): 

1882 """Key for sorting PEP8 results. 

1883 

1884 Global fixes should be done first. This is important for things like 

1885 indentation. 

1886 

1887 """ 

1888 priority = [ 

1889 # Fix multiline colon-based before semicolon based. 

1890 'e701', 

1891 # Break multiline statements early. 

1892 'e702', 

1893 # Things that make lines longer. 

1894 'e225', 'e231', 

1895 # Remove extraneous whitespace before breaking lines. 

1896 'e201', 

1897 # Shorten whitespace in comment before resorting to wrapping. 

1898 'e262' 

1899 ] 

1900 middle_index = 10000 

1901 lowest_priority = [ 

1902 # We need to shorten lines last since the logical fixer can get in a 

1903 # loop, which causes us to exit early. 

1904 'e501', 

1905 ] 

1906 key = pep8_result['id'].lower() 

1907 try: 

1908 return priority.index(key) 

1909 except ValueError: 

1910 try: 

1911 return middle_index + lowest_priority.index(key) + 1 

1912 except ValueError: 

1913 return middle_index 

1914 

1915 

1916def shorten_line(tokens, source, indentation, indent_word, max_line_length, 

1917 aggressive=0, experimental=False, previous_line=''): 

1918 """Separate line at OPERATOR. 

1919 

1920 Multiple candidates will be yielded. 

1921 

1922 """ 

1923 for candidate in _shorten_line(tokens=tokens, 

1924 source=source, 

1925 indentation=indentation, 

1926 indent_word=indent_word, 

1927 aggressive=aggressive, 

1928 previous_line=previous_line): 

1929 yield candidate 

1930 

1931 if aggressive: 

1932 for key_token_strings in SHORTEN_OPERATOR_GROUPS: 

1933 shortened = _shorten_line_at_tokens( 

1934 tokens=tokens, 

1935 source=source, 

1936 indentation=indentation, 

1937 indent_word=indent_word, 

1938 key_token_strings=key_token_strings, 

1939 aggressive=aggressive) 

1940 

1941 if shortened is not None and shortened != source: 

1942 yield shortened 

1943 

1944 if experimental: 

1945 for shortened in _shorten_line_at_tokens_new( 

1946 tokens=tokens, 

1947 source=source, 

1948 indentation=indentation, 

1949 max_line_length=max_line_length): 

1950 

1951 yield shortened 

1952 

1953 

1954def _shorten_line(tokens, source, indentation, indent_word, 

1955 aggressive=0, previous_line=''): 

1956 """Separate line at OPERATOR. 

1957 

1958 The input is expected to be free of newlines except for inside multiline 

1959 strings and at the end. 

1960 

1961 Multiple candidates will be yielded. 

1962 

1963 """ 

1964 in_string = False 

1965 for (token_type, 

1966 token_string, 

1967 start_offset, 

1968 end_offset) in token_offsets(tokens): 

1969 

1970 if IS_SUPPORT_TOKEN_FSTRING: 

1971 if token_type == tokenize.FSTRING_START: 

1972 in_string = True 

1973 elif token_type == tokenize.FSTRING_END: 

1974 in_string = False 

1975 if in_string: 

1976 continue 

1977 

1978 if ( 

1979 token_type == tokenize.COMMENT and 

1980 not is_probably_part_of_multiline(previous_line) and 

1981 not is_probably_part_of_multiline(source) and 

1982 not source[start_offset + 1:].strip().lower().startswith( 

1983 ('noqa', 'pragma:', 'pylint:')) 

1984 ): 

1985 # Move inline comments to previous line. 

1986 first = source[:start_offset] 

1987 second = source[start_offset:] 

1988 yield (indentation + second.strip() + '\n' + 

1989 indentation + first.strip() + '\n') 

1990 elif token_type == token.OP and token_string != '=': 

1991 # Don't break on '=' after keyword as this violates PEP 8. 

1992 

1993 assert token_type != token.INDENT 

1994 

1995 first = source[:end_offset] 

1996 

1997 second_indent = indentation 

1998 if (first.rstrip().endswith('(') and 

1999 source[end_offset:].lstrip().startswith(')')): 

2000 pass 

2001 elif first.rstrip().endswith('('): 

2002 second_indent += indent_word 

2003 elif '(' in first: 

2004 second_indent += ' ' * (1 + first.find('(')) 

2005 else: 

2006 second_indent += indent_word 

2007 

2008 second = (second_indent + source[end_offset:].lstrip()) 

2009 if ( 

2010 not second.strip() or 

2011 second.lstrip().startswith('#') 

2012 ): 

2013 continue 

2014 

2015 # Do not begin a line with a comma 

2016 if second.lstrip().startswith(','): 

2017 continue 

2018 # Do end a line with a dot 

2019 if first.rstrip().endswith('.'): 

2020 continue 

2021 if token_string in '+-*/': 

2022 fixed = first + ' \\' + '\n' + second 

2023 else: 

2024 fixed = first + '\n' + second 

2025 

2026 # Only fix if syntax is okay. 

2027 if check_syntax(normalize_multiline(fixed) 

2028 if aggressive else fixed): 

2029 yield indentation + fixed 

2030 

2031 

2032def _is_binary_operator(token_type, text): 

2033 return ((token_type == tokenize.OP or text in ['and', 'or']) and 

2034 text not in '()[]{},:.;@=%~') 

2035 

2036 

2037# A convenient way to handle tokens. 

2038Token = collections.namedtuple('Token', ['token_type', 'token_string', 

2039 'spos', 'epos', 'line']) 

2040 

2041 

2042class ReformattedLines(object): 

2043 

2044 """The reflowed lines of atoms. 

2045 

2046 Each part of the line is represented as an "atom." They can be moved 

2047 around when need be to get the optimal formatting. 

2048 

2049 """ 

2050 

2051 ########################################################################### 

2052 # Private Classes 

2053 

2054 class _Indent(object): 

2055 

2056 """Represent an indentation in the atom stream.""" 

2057 

2058 def __init__(self, indent_amt): 

2059 self._indent_amt = indent_amt 

2060 

2061 def emit(self): 

2062 return ' ' * self._indent_amt 

2063 

2064 @property 

2065 def size(self): 

2066 return self._indent_amt 

2067 

2068 class _Space(object): 

2069 

2070 """Represent a space in the atom stream.""" 

2071 

2072 def emit(self): 

2073 return ' ' 

2074 

2075 @property 

2076 def size(self): 

2077 return 1 

2078 

2079 class _LineBreak(object): 

2080 

2081 """Represent a line break in the atom stream.""" 

2082 

2083 def emit(self): 

2084 return '\n' 

2085 

2086 @property 

2087 def size(self): 

2088 return 0 

2089 

2090 def __init__(self, max_line_length): 

2091 self._max_line_length = max_line_length 

2092 self._lines = [] 

2093 self._bracket_depth = 0 

2094 self._prev_item = None 

2095 self._prev_prev_item = None 

2096 self._in_fstring = False 

2097 

2098 def __repr__(self): 

2099 return self.emit() 

2100 

2101 ########################################################################### 

2102 # Public Methods 

2103 

2104 def add(self, obj, indent_amt, break_after_open_bracket): 

2105 if isinstance(obj, Atom): 

2106 self._add_item(obj, indent_amt) 

2107 return 

2108 

2109 self._add_container(obj, indent_amt, break_after_open_bracket) 

2110 

2111 def add_comment(self, item): 

2112 num_spaces = 2 

2113 if len(self._lines) > 1: 

2114 if isinstance(self._lines[-1], self._Space): 

2115 num_spaces -= 1 

2116 if len(self._lines) > 2: 

2117 if isinstance(self._lines[-2], self._Space): 

2118 num_spaces -= 1 

2119 

2120 while num_spaces > 0: 

2121 self._lines.append(self._Space()) 

2122 num_spaces -= 1 

2123 self._lines.append(item) 

2124 

2125 def add_indent(self, indent_amt): 

2126 self._lines.append(self._Indent(indent_amt)) 

2127 

2128 def add_line_break(self, indent): 

2129 self._lines.append(self._LineBreak()) 

2130 self.add_indent(len(indent)) 

2131 

2132 def add_line_break_at(self, index, indent_amt): 

2133 self._lines.insert(index, self._LineBreak()) 

2134 self._lines.insert(index + 1, self._Indent(indent_amt)) 

2135 

2136 def add_space_if_needed(self, curr_text, equal=False): 

2137 if ( 

2138 not self._lines or isinstance( 

2139 self._lines[-1], (self._LineBreak, self._Indent, self._Space)) 

2140 ): 

2141 return 

2142 

2143 prev_text = str(self._prev_item) 

2144 prev_prev_text = ( 

2145 str(self._prev_prev_item) if self._prev_prev_item else '') 

2146 

2147 if ( 

2148 # The previous item was a keyword or identifier and the current 

2149 # item isn't an operator that doesn't require a space. 

2150 ((self._prev_item.is_keyword or self._prev_item.is_string or 

2151 self._prev_item.is_name or self._prev_item.is_number) and 

2152 (curr_text[0] not in '([{.,:}])' or 

2153 (curr_text[0] == '=' and equal))) or 

2154 

2155 # Don't place spaces around a '.', unless it's in an 'import' 

2156 # statement. 

2157 ((prev_prev_text != 'from' and prev_text[-1] != '.' and 

2158 curr_text != 'import') and 

2159 

2160 # Don't place a space before a colon. 

2161 curr_text[0] != ':' and 

2162 

2163 # Don't split up ending brackets by spaces. 

2164 ((prev_text[-1] in '}])' and curr_text[0] not in '.,}])') or 

2165 

2166 # Put a space after a colon or comma. 

2167 prev_text[-1] in ':,' or 

2168 

2169 # Put space around '=' if asked to. 

2170 (equal and prev_text == '=') or 

2171 

2172 # Put spaces around non-unary arithmetic operators. 

2173 ((self._prev_prev_item and 

2174 (prev_text not in '+-' and 

2175 (self._prev_prev_item.is_name or 

2176 self._prev_prev_item.is_number or 

2177 self._prev_prev_item.is_string)) and 

2178 prev_text in ('+', '-', '%', '*', '/', '//', '**', 'in'))))) 

2179 ): 

2180 self._lines.append(self._Space()) 

2181 

2182 def previous_item(self): 

2183 """Return the previous non-whitespace item.""" 

2184 return self._prev_item 

2185 

2186 def fits_on_current_line(self, item_extent): 

2187 return self.current_size() + item_extent <= self._max_line_length 

2188 

2189 def current_size(self): 

2190 """The size of the current line minus the indentation.""" 

2191 size = 0 

2192 for item in reversed(self._lines): 

2193 size += item.size 

2194 if isinstance(item, self._LineBreak): 

2195 break 

2196 

2197 return size 

2198 

2199 def line_empty(self): 

2200 return (self._lines and 

2201 isinstance(self._lines[-1], 

2202 (self._LineBreak, self._Indent))) 

2203 

2204 def emit(self): 

2205 string = '' 

2206 for item in self._lines: 

2207 if isinstance(item, self._LineBreak): 

2208 string = string.rstrip() 

2209 string += item.emit() 

2210 

2211 return string.rstrip() + '\n' 

2212 

2213 ########################################################################### 

2214 # Private Methods 

2215 

2216 def _add_item(self, item, indent_amt): 

2217 """Add an item to the line. 

2218 

2219 Reflow the line to get the best formatting after the item is 

2220 inserted. The bracket depth indicates if the item is being 

2221 inserted inside of a container or not. 

2222 

2223 """ 

2224 if item.is_fstring_start: 

2225 self._in_fstring = True 

2226 elif self._prev_item and self._prev_item.is_fstring_end: 

2227 self._in_fstring = False 

2228 

2229 if self._prev_item and self._prev_item.is_string and item.is_string: 

2230 # Place consecutive string literals on separate lines. 

2231 self._lines.append(self._LineBreak()) 

2232 self._lines.append(self._Indent(indent_amt)) 

2233 

2234 item_text = str(item) 

2235 if self._lines and self._bracket_depth: 

2236 # Adding the item into a container. 

2237 self._prevent_default_initializer_splitting(item, indent_amt) 

2238 

2239 if item_text in '.,)]}': 

2240 self._split_after_delimiter(item, indent_amt) 

2241 

2242 elif self._lines and not self.line_empty(): 

2243 # Adding the item outside of a container. 

2244 if self.fits_on_current_line(len(item_text)): 

2245 self._enforce_space(item) 

2246 

2247 else: 

2248 # Line break for the new item. 

2249 self._lines.append(self._LineBreak()) 

2250 self._lines.append(self._Indent(indent_amt)) 

2251 

2252 self._lines.append(item) 

2253 self._prev_item, self._prev_prev_item = item, self._prev_item 

2254 

2255 if item_text in '([{' and not self._in_fstring: 

2256 self._bracket_depth += 1 

2257 

2258 elif item_text in '}])' and not self._in_fstring: 

2259 self._bracket_depth -= 1 

2260 assert self._bracket_depth >= 0 

2261 

2262 def _add_container(self, container, indent_amt, break_after_open_bracket): 

2263 actual_indent = indent_amt + 1 

2264 

2265 if ( 

2266 str(self._prev_item) != '=' and 

2267 not self.line_empty() and 

2268 not self.fits_on_current_line( 

2269 container.size + self._bracket_depth + 2) 

2270 ): 

2271 

2272 if str(container)[0] == '(' and self._prev_item.is_name: 

2273 # Don't split before the opening bracket of a call. 

2274 break_after_open_bracket = True 

2275 actual_indent = indent_amt + 4 

2276 elif ( 

2277 break_after_open_bracket or 

2278 str(self._prev_item) not in '([{' 

2279 ): 

2280 # If the container doesn't fit on the current line and the 

2281 # current line isn't empty, place the container on the next 

2282 # line. 

2283 self._lines.append(self._LineBreak()) 

2284 self._lines.append(self._Indent(indent_amt)) 

2285 break_after_open_bracket = False 

2286 else: 

2287 actual_indent = self.current_size() + 1 

2288 break_after_open_bracket = False 

2289 

2290 if isinstance(container, (ListComprehension, IfExpression)): 

2291 actual_indent = indent_amt 

2292 

2293 # Increase the continued indentation only if recursing on a 

2294 # container. 

2295 container.reflow(self, ' ' * actual_indent, 

2296 break_after_open_bracket=break_after_open_bracket) 

2297 

2298 def _prevent_default_initializer_splitting(self, item, indent_amt): 

2299 """Prevent splitting between a default initializer. 

2300 

2301 When there is a default initializer, it's best to keep it all on 

2302 the same line. It's nicer and more readable, even if it goes 

2303 over the maximum allowable line length. This goes back along the 

2304 current line to determine if we have a default initializer, and, 

2305 if so, to remove extraneous whitespaces and add a line 

2306 break/indent before it if needed. 

2307 

2308 """ 

2309 if str(item) == '=': 

2310 # This is the assignment in the initializer. Just remove spaces for 

2311 # now. 

2312 self._delete_whitespace() 

2313 return 

2314 

2315 if (not self._prev_item or not self._prev_prev_item or 

2316 str(self._prev_item) != '='): 

2317 return 

2318 

2319 self._delete_whitespace() 

2320 prev_prev_index = self._lines.index(self._prev_prev_item) 

2321 

2322 if ( 

2323 isinstance(self._lines[prev_prev_index - 1], self._Indent) or 

2324 self.fits_on_current_line(item.size + 1) 

2325 ): 

2326 # The default initializer is already the only item on this line. 

2327 # Don't insert a newline here. 

2328 return 

2329 

2330 # Replace the space with a newline/indent combo. 

2331 if isinstance(self._lines[prev_prev_index - 1], self._Space): 

2332 del self._lines[prev_prev_index - 1] 

2333 

2334 self.add_line_break_at(self._lines.index(self._prev_prev_item), 

2335 indent_amt) 

2336 

2337 def _split_after_delimiter(self, item, indent_amt): 

2338 """Split the line only after a delimiter.""" 

2339 self._delete_whitespace() 

2340 

2341 if self.fits_on_current_line(item.size): 

2342 return 

2343 

2344 last_space = None 

2345 for current_item in reversed(self._lines): 

2346 if ( 

2347 last_space and 

2348 (not isinstance(current_item, Atom) or 

2349 not current_item.is_colon) 

2350 ): 

2351 break 

2352 else: 

2353 last_space = None 

2354 if isinstance(current_item, self._Space): 

2355 last_space = current_item 

2356 if isinstance(current_item, (self._LineBreak, self._Indent)): 

2357 return 

2358 

2359 if not last_space: 

2360 return 

2361 

2362 self.add_line_break_at(self._lines.index(last_space), indent_amt) 

2363 

2364 def _enforce_space(self, item): 

2365 """Enforce a space in certain situations. 

2366 

2367 There are cases where we will want a space where normally we 

2368 wouldn't put one. This just enforces the addition of a space. 

2369 

2370 """ 

2371 if isinstance(self._lines[-1], 

2372 (self._Space, self._LineBreak, self._Indent)): 

2373 return 

2374 

2375 if not self._prev_item: 

2376 return 

2377 

2378 item_text = str(item) 

2379 prev_text = str(self._prev_item) 

2380 

2381 # Prefer a space around a '.' in an import statement, and between the 

2382 # 'import' and '('. 

2383 if ( 

2384 (item_text == '.' and prev_text == 'from') or 

2385 (item_text == 'import' and prev_text == '.') or 

2386 (item_text == '(' and prev_text == 'import') 

2387 ): 

2388 self._lines.append(self._Space()) 

2389 

2390 def _delete_whitespace(self): 

2391 """Delete all whitespace from the end of the line.""" 

2392 while isinstance(self._lines[-1], (self._Space, self._LineBreak, 

2393 self._Indent)): 

2394 del self._lines[-1] 

2395 

2396 

2397class Atom(object): 

2398 

2399 """The smallest unbreakable unit that can be reflowed.""" 

2400 

2401 def __init__(self, atom): 

2402 self._atom = atom 

2403 

2404 def __repr__(self): 

2405 return self._atom.token_string 

2406 

2407 def __len__(self): 

2408 return self.size 

2409 

2410 def reflow( 

2411 self, reflowed_lines, continued_indent, extent, 

2412 break_after_open_bracket=False, 

2413 is_list_comp_or_if_expr=False, 

2414 next_is_dot=False 

2415 ): 

2416 if self._atom.token_type == tokenize.COMMENT: 

2417 reflowed_lines.add_comment(self) 

2418 return 

2419 

2420 total_size = extent if extent else self.size 

2421 

2422 if self._atom.token_string not in ',:([{}])': 

2423 # Some atoms will need an extra 1-sized space token after them. 

2424 total_size += 1 

2425 

2426 prev_item = reflowed_lines.previous_item() 

2427 if ( 

2428 not is_list_comp_or_if_expr and 

2429 not reflowed_lines.fits_on_current_line(total_size) and 

2430 not (next_is_dot and 

2431 reflowed_lines.fits_on_current_line(self.size + 1)) and 

2432 not reflowed_lines.line_empty() and 

2433 not self.is_colon and 

2434 not (prev_item and prev_item.is_name and 

2435 str(self) == '(') 

2436 ): 

2437 # Start a new line if there is already something on the line and 

2438 # adding this atom would make it go over the max line length. 

2439 reflowed_lines.add_line_break(continued_indent) 

2440 else: 

2441 reflowed_lines.add_space_if_needed(str(self)) 

2442 

2443 reflowed_lines.add(self, len(continued_indent), 

2444 break_after_open_bracket) 

2445 

2446 def emit(self): 

2447 return self.__repr__() 

2448 

2449 @property 

2450 def is_keyword(self): 

2451 return keyword.iskeyword(self._atom.token_string) 

2452 

2453 @property 

2454 def is_string(self): 

2455 return self._atom.token_type == tokenize.STRING 

2456 

2457 @property 

2458 def is_fstring_start(self): 

2459 if not IS_SUPPORT_TOKEN_FSTRING: 

2460 return False 

2461 return self._atom.token_type == tokenize.FSTRING_START 

2462 

2463 @property 

2464 def is_fstring_end(self): 

2465 if not IS_SUPPORT_TOKEN_FSTRING: 

2466 return False 

2467 return self._atom.token_type == tokenize.FSTRING_END 

2468 

2469 @property 

2470 def is_name(self): 

2471 return self._atom.token_type == tokenize.NAME 

2472 

2473 @property 

2474 def is_number(self): 

2475 return self._atom.token_type == tokenize.NUMBER 

2476 

2477 @property 

2478 def is_comma(self): 

2479 return self._atom.token_string == ',' 

2480 

2481 @property 

2482 def is_colon(self): 

2483 return self._atom.token_string == ':' 

2484 

2485 @property 

2486 def size(self): 

2487 return len(self._atom.token_string) 

2488 

2489 

2490class Container(object): 

2491 

2492 """Base class for all container types.""" 

2493 

2494 def __init__(self, items): 

2495 self._items = items 

2496 

2497 def __repr__(self): 

2498 string = '' 

2499 last_was_keyword = False 

2500 

2501 for item in self._items: 

2502 if item.is_comma: 

2503 string += ', ' 

2504 elif item.is_colon: 

2505 string += ': ' 

2506 else: 

2507 item_string = str(item) 

2508 if ( 

2509 string and 

2510 (last_was_keyword or 

2511 (not string.endswith(tuple('([{,.:}]) ')) and 

2512 not item_string.startswith(tuple('([{,.:}])')))) 

2513 ): 

2514 string += ' ' 

2515 string += item_string 

2516 

2517 last_was_keyword = item.is_keyword 

2518 return string 

2519 

2520 def __iter__(self): 

2521 for element in self._items: 

2522 yield element 

2523 

2524 def __getitem__(self, idx): 

2525 return self._items[idx] 

2526 

2527 def reflow(self, reflowed_lines, continued_indent, 

2528 break_after_open_bracket=False): 

2529 last_was_container = False 

2530 for (index, item) in enumerate(self._items): 

2531 next_item = get_item(self._items, index + 1) 

2532 

2533 if isinstance(item, Atom): 

2534 is_list_comp_or_if_expr = ( 

2535 isinstance(self, (ListComprehension, IfExpression))) 

2536 item.reflow(reflowed_lines, continued_indent, 

2537 self._get_extent(index), 

2538 is_list_comp_or_if_expr=is_list_comp_or_if_expr, 

2539 next_is_dot=(next_item and 

2540 str(next_item) == '.')) 

2541 if last_was_container and item.is_comma: 

2542 reflowed_lines.add_line_break(continued_indent) 

2543 last_was_container = False 

2544 else: # isinstance(item, Container) 

2545 reflowed_lines.add(item, len(continued_indent), 

2546 break_after_open_bracket) 

2547 last_was_container = not isinstance(item, (ListComprehension, 

2548 IfExpression)) 

2549 

2550 if ( 

2551 break_after_open_bracket and index == 0 and 

2552 # Prefer to keep empty containers together instead of 

2553 # separating them. 

2554 str(item) == self.open_bracket and 

2555 (not next_item or str(next_item) != self.close_bracket) and 

2556 (len(self._items) != 3 or not isinstance(next_item, Atom)) 

2557 ): 

2558 reflowed_lines.add_line_break(continued_indent) 

2559 break_after_open_bracket = False 

2560 else: 

2561 next_next_item = get_item(self._items, index + 2) 

2562 if ( 

2563 str(item) not in ['.', '%', 'in'] and 

2564 next_item and not isinstance(next_item, Container) and 

2565 str(next_item) != ':' and 

2566 next_next_item and (not isinstance(next_next_item, Atom) or 

2567 str(next_item) == 'not') and 

2568 not reflowed_lines.line_empty() and 

2569 not reflowed_lines.fits_on_current_line( 

2570 self._get_extent(index + 1) + 2) 

2571 ): 

2572 reflowed_lines.add_line_break(continued_indent) 

2573 

2574 def _get_extent(self, index): 

2575 """The extent of the full element. 

2576 

2577 E.g., the length of a function call or keyword. 

2578 

2579 """ 

2580 extent = 0 

2581 prev_item = get_item(self._items, index - 1) 

2582 seen_dot = prev_item and str(prev_item) == '.' 

2583 while index < len(self._items): 

2584 item = get_item(self._items, index) 

2585 index += 1 

2586 

2587 if isinstance(item, (ListComprehension, IfExpression)): 

2588 break 

2589 

2590 if isinstance(item, Container): 

2591 if prev_item and prev_item.is_name: 

2592 if seen_dot: 

2593 extent += 1 

2594 else: 

2595 extent += item.size 

2596 

2597 prev_item = item 

2598 continue 

2599 elif (str(item) not in ['.', '=', ':', 'not'] and 

2600 not item.is_name and not item.is_string): 

2601 break 

2602 

2603 if str(item) == '.': 

2604 seen_dot = True 

2605 

2606 extent += item.size 

2607 prev_item = item 

2608 

2609 return extent 

2610 

2611 @property 

2612 def is_string(self): 

2613 return False 

2614 

2615 @property 

2616 def size(self): 

2617 return len(self.__repr__()) 

2618 

2619 @property 

2620 def is_keyword(self): 

2621 return False 

2622 

2623 @property 

2624 def is_name(self): 

2625 return False 

2626 

2627 @property 

2628 def is_comma(self): 

2629 return False 

2630 

2631 @property 

2632 def is_colon(self): 

2633 return False 

2634 

2635 @property 

2636 def open_bracket(self): 

2637 return None 

2638 

2639 @property 

2640 def close_bracket(self): 

2641 return None 

2642 

2643 

2644class Tuple(Container): 

2645 

2646 """A high-level representation of a tuple.""" 

2647 

2648 @property 

2649 def open_bracket(self): 

2650 return '(' 

2651 

2652 @property 

2653 def close_bracket(self): 

2654 return ')' 

2655 

2656 

2657class List(Container): 

2658 

2659 """A high-level representation of a list.""" 

2660 

2661 @property 

2662 def open_bracket(self): 

2663 return '[' 

2664 

2665 @property 

2666 def close_bracket(self): 

2667 return ']' 

2668 

2669 

2670class DictOrSet(Container): 

2671 

2672 """A high-level representation of a dictionary or set.""" 

2673 

2674 @property 

2675 def open_bracket(self): 

2676 return '{' 

2677 

2678 @property 

2679 def close_bracket(self): 

2680 return '}' 

2681 

2682 

2683class ListComprehension(Container): 

2684 

2685 """A high-level representation of a list comprehension.""" 

2686 

2687 @property 

2688 def size(self): 

2689 length = 0 

2690 for item in self._items: 

2691 if isinstance(item, IfExpression): 

2692 break 

2693 length += item.size 

2694 return length 

2695 

2696 

2697class IfExpression(Container): 

2698 

2699 """A high-level representation of an if-expression.""" 

2700 

2701 

2702def _parse_container(tokens, index, for_or_if=None): 

2703 """Parse a high-level container, such as a list, tuple, etc.""" 

2704 

2705 # Store the opening bracket. 

2706 items = [Atom(Token(*tokens[index]))] 

2707 index += 1 

2708 

2709 num_tokens = len(tokens) 

2710 while index < num_tokens: 

2711 tok = Token(*tokens[index]) 

2712 

2713 if tok.token_string in ',)]}': 

2714 # First check if we're at the end of a list comprehension or 

2715 # if-expression. Don't add the ending token as part of the list 

2716 # comprehension or if-expression, because they aren't part of those 

2717 # constructs. 

2718 if for_or_if == 'for': 

2719 return (ListComprehension(items), index - 1) 

2720 

2721 elif for_or_if == 'if': 

2722 return (IfExpression(items), index - 1) 

2723 

2724 # We've reached the end of a container. 

2725 items.append(Atom(tok)) 

2726 

2727 # If not, then we are at the end of a container. 

2728 if tok.token_string == ')': 

2729 # The end of a tuple. 

2730 return (Tuple(items), index) 

2731 

2732 elif tok.token_string == ']': 

2733 # The end of a list. 

2734 return (List(items), index) 

2735 

2736 elif tok.token_string == '}': 

2737 # The end of a dictionary or set. 

2738 return (DictOrSet(items), index) 

2739 

2740 elif tok.token_string in '([{': 

2741 # A sub-container is being defined. 

2742 (container, index) = _parse_container(tokens, index) 

2743 items.append(container) 

2744 

2745 elif tok.token_string == 'for': 

2746 (container, index) = _parse_container(tokens, index, 'for') 

2747 items.append(container) 

2748 

2749 elif tok.token_string == 'if': 

2750 (container, index) = _parse_container(tokens, index, 'if') 

2751 items.append(container) 

2752 

2753 else: 

2754 items.append(Atom(tok)) 

2755 

2756 index += 1 

2757 

2758 return (None, None) 

2759 

2760 

2761def _parse_tokens(tokens): 

2762 """Parse the tokens. 

2763 

2764 This converts the tokens into a form where we can manipulate them 

2765 more easily. 

2766 

2767 """ 

2768 

2769 index = 0 

2770 parsed_tokens = [] 

2771 

2772 num_tokens = len(tokens) 

2773 while index < num_tokens: 

2774 tok = Token(*tokens[index]) 

2775 

2776 assert tok.token_type != token.INDENT 

2777 if tok.token_type == tokenize.NEWLINE: 

2778 # There's only one newline and it's at the end. 

2779 break 

2780 

2781 if tok.token_string in '([{': 

2782 (container, index) = _parse_container(tokens, index) 

2783 if not container: 

2784 return None 

2785 parsed_tokens.append(container) 

2786 else: 

2787 parsed_tokens.append(Atom(tok)) 

2788 

2789 index += 1 

2790 

2791 return parsed_tokens 

2792 

2793 

2794def _reflow_lines(parsed_tokens, indentation, max_line_length, 

2795 start_on_prefix_line): 

2796 """Reflow the lines so that it looks nice.""" 

2797 

2798 if str(parsed_tokens[0]) == 'def': 

2799 # A function definition gets indented a bit more. 

2800 continued_indent = indentation + ' ' * 2 * DEFAULT_INDENT_SIZE 

2801 else: 

2802 continued_indent = indentation + ' ' * DEFAULT_INDENT_SIZE 

2803 

2804 break_after_open_bracket = not start_on_prefix_line 

2805 

2806 lines = ReformattedLines(max_line_length) 

2807 lines.add_indent(len(indentation.lstrip('\r\n'))) 

2808 

2809 if not start_on_prefix_line: 

2810 # If splitting after the opening bracket will cause the first element 

2811 # to be aligned weirdly, don't try it. 

2812 first_token = get_item(parsed_tokens, 0) 

2813 second_token = get_item(parsed_tokens, 1) 

2814 

2815 if ( 

2816 first_token and second_token and 

2817 str(second_token)[0] == '(' and 

2818 len(indentation) + len(first_token) + 1 == len(continued_indent) 

2819 ): 

2820 return None 

2821 

2822 for item in parsed_tokens: 

2823 lines.add_space_if_needed(str(item), equal=True) 

2824 

2825 save_continued_indent = continued_indent 

2826 if start_on_prefix_line and isinstance(item, Container): 

2827 start_on_prefix_line = False 

2828 continued_indent = ' ' * (lines.current_size() + 1) 

2829 

2830 item.reflow(lines, continued_indent, break_after_open_bracket) 

2831 continued_indent = save_continued_indent 

2832 

2833 return lines.emit() 

2834 

2835 

2836def _shorten_line_at_tokens_new(tokens, source, indentation, 

2837 max_line_length): 

2838 """Shorten the line taking its length into account. 

2839 

2840 The input is expected to be free of newlines except for inside 

2841 multiline strings and at the end. 

2842 

2843 """ 

2844 # Yield the original source so to see if it's a better choice than the 

2845 # shortened candidate lines we generate here. 

2846 yield indentation + source 

2847 

2848 parsed_tokens = _parse_tokens(tokens) 

2849 

2850 if parsed_tokens: 

2851 # Perform two reflows. The first one starts on the same line as the 

2852 # prefix. The second starts on the line after the prefix. 

2853 fixed = _reflow_lines(parsed_tokens, indentation, max_line_length, 

2854 start_on_prefix_line=True) 

2855 if fixed and check_syntax(normalize_multiline(fixed.lstrip())): 

2856 yield fixed 

2857 

2858 fixed = _reflow_lines(parsed_tokens, indentation, max_line_length, 

2859 start_on_prefix_line=False) 

2860 if fixed and check_syntax(normalize_multiline(fixed.lstrip())): 

2861 yield fixed 

2862 

2863 

2864def _shorten_line_at_tokens(tokens, source, indentation, indent_word, 

2865 key_token_strings, aggressive): 

2866 """Separate line by breaking at tokens in key_token_strings. 

2867 

2868 The input is expected to be free of newlines except for inside 

2869 multiline strings and at the end. 

2870 

2871 """ 

2872 offsets = [] 

2873 for (index, _t) in enumerate(token_offsets(tokens)): 

2874 (token_type, 

2875 token_string, 

2876 start_offset, 

2877 end_offset) = _t 

2878 

2879 assert token_type != token.INDENT 

2880 

2881 if token_string in key_token_strings: 

2882 # Do not break in containers with zero or one items. 

2883 unwanted_next_token = { 

2884 '(': ')', 

2885 '[': ']', 

2886 '{': '}'}.get(token_string) 

2887 if unwanted_next_token: 

2888 if ( 

2889 get_item(tokens, 

2890 index + 1, 

2891 default=[None, None])[1] == unwanted_next_token or 

2892 get_item(tokens, 

2893 index + 2, 

2894 default=[None, None])[1] == unwanted_next_token 

2895 ): 

2896 continue 

2897 

2898 if ( 

2899 index > 2 and token_string == '(' and 

2900 tokens[index - 1][1] in ',(%[' 

2901 ): 

2902 # Don't split after a tuple start, or before a tuple start if 

2903 # the tuple is in a list. 

2904 continue 

2905 

2906 if end_offset < len(source) - 1: 

2907 # Don't split right before newline. 

2908 offsets.append(end_offset) 

2909 else: 

2910 # Break at adjacent strings. These were probably meant to be on 

2911 # separate lines in the first place. 

2912 previous_token = get_item(tokens, index - 1) 

2913 if ( 

2914 token_type == tokenize.STRING and 

2915 previous_token and previous_token[0] == tokenize.STRING 

2916 ): 

2917 offsets.append(start_offset) 

2918 

2919 current_indent = None 

2920 fixed = None 

2921 for line in split_at_offsets(source, offsets): 

2922 if fixed: 

2923 fixed += '\n' + current_indent + line 

2924 

2925 for symbol in '([{': 

2926 if line.endswith(symbol): 

2927 current_indent += indent_word 

2928 else: 

2929 # First line. 

2930 fixed = line 

2931 assert not current_indent 

2932 current_indent = indent_word 

2933 

2934 assert fixed is not None 

2935 

2936 if check_syntax(normalize_multiline(fixed) 

2937 if aggressive > 1 else fixed): 

2938 return indentation + fixed 

2939 

2940 return None 

2941 

2942 

2943def token_offsets(tokens): 

2944 """Yield tokens and offsets.""" 

2945 end_offset = 0 

2946 previous_end_row = 0 

2947 previous_end_column = 0 

2948 for t in tokens: 

2949 token_type = t[0] 

2950 token_string = t[1] 

2951 (start_row, start_column) = t[2] 

2952 (end_row, end_column) = t[3] 

2953 

2954 # Account for the whitespace between tokens. 

2955 end_offset += start_column 

2956 if previous_end_row == start_row: 

2957 end_offset -= previous_end_column 

2958 

2959 # Record the start offset of the token. 

2960 start_offset = end_offset 

2961 

2962 # Account for the length of the token itself. 

2963 end_offset += len(token_string) 

2964 

2965 yield (token_type, 

2966 token_string, 

2967 start_offset, 

2968 end_offset) 

2969 

2970 previous_end_row = end_row 

2971 previous_end_column = end_column 

2972 

2973 

2974def normalize_multiline(line): 

2975 """Normalize multiline-related code that will cause syntax error. 

2976 

2977 This is for purposes of checking syntax. 

2978 

2979 """ 

2980 if line.startswith(('def ', 'async def ')) and line.rstrip().endswith(':'): 

2981 return line + ' pass' 

2982 elif line.startswith('return '): 

2983 return 'def _(): ' + line 

2984 elif line.startswith('@'): 

2985 return line + 'def _(): pass' 

2986 elif line.startswith('class '): 

2987 return line + ' pass' 

2988 elif line.startswith(('if ', 'elif ', 'for ', 'while ')): 

2989 return line + ' pass' 

2990 

2991 return line 

2992 

2993 

2994def fix_whitespace(line, offset, replacement): 

2995 """Replace whitespace at offset and return fixed line.""" 

2996 # Replace escaped newlines too 

2997 left = line[:offset].rstrip('\n\r \t\\') 

2998 right = line[offset:].lstrip('\n\r \t\\') 

2999 if right.startswith('#'): 

3000 return line 

3001 

3002 return left + replacement + right 

3003 

3004 

3005def _execute_pep8(pep8_options, source): 

3006 """Execute pycodestyle via python method calls.""" 

3007 class QuietReport(pycodestyle.BaseReport): 

3008 

3009 """Version of checker that does not print.""" 

3010 

3011 def __init__(self, options): 

3012 super(QuietReport, self).__init__(options) 

3013 self.__full_error_results = [] 

3014 

3015 def error(self, line_number, offset, text, check): 

3016 """Collect errors.""" 

3017 code = super(QuietReport, self).error(line_number, 

3018 offset, 

3019 text, 

3020 check) 

3021 if code: 

3022 self.__full_error_results.append( 

3023 {'id': code, 

3024 'line': line_number, 

3025 'column': offset + 1, 

3026 'info': text}) 

3027 

3028 def full_error_results(self): 

3029 """Return error results in detail. 

3030 

3031 Results are in the form of a list of dictionaries. Each 

3032 dictionary contains 'id', 'line', 'column', and 'info'. 

3033 

3034 """ 

3035 return self.__full_error_results 

3036 

3037 checker = pycodestyle.Checker('', lines=source, reporter=QuietReport, 

3038 **pep8_options) 

3039 checker.check_all() 

3040 return checker.report.full_error_results() 

3041 

3042 

3043def _remove_leading_and_normalize(line, with_rstrip=True): 

3044 # ignore FF in first lstrip() 

3045 if with_rstrip: 

3046 return line.lstrip(' \t\v').rstrip(CR + LF) + '\n' 

3047 return line.lstrip(' \t\v') 

3048 

3049 

3050class Reindenter(object): 

3051 

3052 """Reindents badly-indented code to uniformly use four-space indentation. 

3053 

3054 Released to the public domain, by Tim Peters, 03 October 2000. 

3055 

3056 """ 

3057 

3058 def __init__(self, input_text, leave_tabs=False): 

3059 sio = io.StringIO(input_text) 

3060 source_lines = sio.readlines() 

3061 

3062 self.string_content_line_numbers = multiline_string_lines(input_text) 

3063 

3064 # File lines, rstripped & tab-expanded. Dummy at start is so 

3065 # that we can use tokenize's 1-based line numbering easily. 

3066 # Note that a line is all-blank iff it is a newline. 

3067 self.lines = [] 

3068 for line_number, line in enumerate(source_lines, start=1): 

3069 # Do not modify if inside a multiline string. 

3070 if line_number in self.string_content_line_numbers: 

3071 self.lines.append(line) 

3072 else: 

3073 # Only expand leading tabs. 

3074 with_rstrip = line_number != len(source_lines) 

3075 if leave_tabs: 

3076 self.lines.append( 

3077 _get_indentation(line) + 

3078 _remove_leading_and_normalize(line, with_rstrip) 

3079 ) 

3080 else: 

3081 self.lines.append( 

3082 _get_indentation(line).expandtabs() + 

3083 _remove_leading_and_normalize(line, with_rstrip) 

3084 ) 

3085 

3086 self.lines.insert(0, None) 

3087 self.index = 1 # index into self.lines of next line 

3088 self.input_text = input_text 

3089 

3090 def run(self, indent_size=DEFAULT_INDENT_SIZE): 

3091 """Fix indentation and return modified line numbers. 

3092 

3093 Line numbers are indexed at 1. 

3094 

3095 """ 

3096 if indent_size < 1: 

3097 return self.input_text 

3098 

3099 try: 

3100 stats = _reindent_stats(tokenize.generate_tokens(self.getline)) 

3101 except (SyntaxError, tokenize.TokenError): 

3102 return self.input_text 

3103 # Remove trailing empty lines. 

3104 lines = self.lines 

3105 # Sentinel. 

3106 stats.append((len(lines), 0)) 

3107 # Map count of leading spaces to # we want. 

3108 have2want = {} 

3109 # Program after transformation. 

3110 after = [] 

3111 # Copy over initial empty lines -- there's nothing to do until 

3112 # we see a line with *something* on it. 

3113 i = stats[0][0] 

3114 after.extend(lines[1:i]) 

3115 for i in range(len(stats) - 1): 

3116 thisstmt, thislevel = stats[i] 

3117 nextstmt = stats[i + 1][0] 

3118 have = _leading_space_count(lines[thisstmt]) 

3119 want = thislevel * indent_size 

3120 if want < 0: 

3121 # A comment line. 

3122 if have: 

3123 # An indented comment line. If we saw the same 

3124 # indentation before, reuse what it most recently 

3125 # mapped to. 

3126 want = have2want.get(have, -1) 

3127 if want < 0: 

3128 # Then it probably belongs to the next real stmt. 

3129 for j in range(i + 1, len(stats) - 1): 

3130 jline, jlevel = stats[j] 

3131 if jlevel >= 0: 

3132 if have == _leading_space_count(lines[jline]): 

3133 want = jlevel * indent_size 

3134 break 

3135 # Maybe it's a hanging comment like this one, 

3136 if want < 0: 

3137 # in which case we should shift it like its base 

3138 # line got shifted. 

3139 for j in range(i - 1, -1, -1): 

3140 jline, jlevel = stats[j] 

3141 if jlevel >= 0: 

3142 want = (have + _leading_space_count( 

3143 after[jline - 1]) - 

3144 _leading_space_count(lines[jline])) 

3145 break 

3146 if want < 0: 

3147 # Still no luck -- leave it alone. 

3148 want = have 

3149 else: 

3150 want = 0 

3151 assert want >= 0 

3152 have2want[have] = want 

3153 diff = want - have 

3154 if diff == 0 or have == 0: 

3155 after.extend(lines[thisstmt:nextstmt]) 

3156 else: 

3157 for line_number, line in enumerate(lines[thisstmt:nextstmt], 

3158 start=thisstmt): 

3159 if line_number in self.string_content_line_numbers: 

3160 after.append(line) 

3161 elif diff > 0: 

3162 if line == '\n': 

3163 after.append(line) 

3164 else: 

3165 after.append(' ' * diff + line) 

3166 else: 

3167 remove = min(_leading_space_count(line), -diff) 

3168 after.append(line[remove:]) 

3169 

3170 return ''.join(after) 

3171 

3172 def getline(self): 

3173 """Line-getter for tokenize.""" 

3174 if self.index >= len(self.lines): 

3175 line = '' 

3176 else: 

3177 line = self.lines[self.index] 

3178 self.index += 1 

3179 return line 

3180 

3181 

3182def _reindent_stats(tokens): 

3183 """Return list of (lineno, indentlevel) pairs. 

3184 

3185 One for each stmt and comment line. indentlevel is -1 for comment 

3186 lines, as a signal that tokenize doesn't know what to do about them; 

3187 indeed, they're our headache! 

3188 

3189 """ 

3190 find_stmt = 1 # Next token begins a fresh stmt? 

3191 level = 0 # Current indent level. 

3192 stats = [] 

3193 

3194 for t in tokens: 

3195 token_type = t[0] 

3196 sline = t[2][0] 

3197 line = t[4] 

3198 

3199 if token_type == tokenize.NEWLINE: 

3200 # A program statement, or ENDMARKER, will eventually follow, 

3201 # after some (possibly empty) run of tokens of the form 

3202 # (NL | COMMENT)* (INDENT | DEDENT+)? 

3203 find_stmt = 1 

3204 

3205 elif token_type == tokenize.INDENT: 

3206 find_stmt = 1 

3207 level += 1 

3208 

3209 elif token_type == tokenize.DEDENT: 

3210 find_stmt = 1 

3211 level -= 1 

3212 

3213 elif token_type == tokenize.COMMENT: 

3214 if find_stmt: 

3215 stats.append((sline, -1)) 

3216 # But we're still looking for a new stmt, so leave 

3217 # find_stmt alone. 

3218 

3219 elif token_type == tokenize.NL: 

3220 pass 

3221 

3222 elif find_stmt: 

3223 # This is the first "real token" following a NEWLINE, so it 

3224 # must be the first token of the next program statement, or an 

3225 # ENDMARKER. 

3226 find_stmt = 0 

3227 if line: # Not endmarker. 

3228 stats.append((sline, level)) 

3229 

3230 return stats 

3231 

3232 

3233def _leading_space_count(line): 

3234 """Return number of leading spaces in line.""" 

3235 i = 0 

3236 while i < len(line) and line[i] == ' ': 

3237 i += 1 

3238 return i 

3239 

3240 

3241def check_syntax(code): 

3242 """Return True if syntax is okay.""" 

3243 try: 

3244 return compile(code, '<string>', 'exec', dont_inherit=True) 

3245 except (SyntaxError, TypeError, ValueError): 

3246 return False 

3247 

3248 

3249def find_with_line_numbers(pattern, contents): 

3250 """A wrapper around 're.finditer' to find line numbers. 

3251 

3252 Returns a list of line numbers where pattern was found in contents. 

3253 """ 

3254 matches = list(re.finditer(pattern, contents)) 

3255 if not matches: 

3256 return [] 

3257 

3258 end = matches[-1].start() 

3259 

3260 # -1 so a failed `rfind` maps to the first line. 

3261 newline_offsets = { 

3262 -1: 0 

3263 } 

3264 for line_num, m in enumerate(re.finditer(r'\n', contents), 1): 

3265 offset = m.start() 

3266 if offset > end: 

3267 break 

3268 newline_offsets[offset] = line_num 

3269 

3270 def get_line_num(match, contents): 

3271 """Get the line number of string in a files contents. 

3272 

3273 Failing to find the newline is OK, -1 maps to 0 

3274 

3275 """ 

3276 newline_offset = contents.rfind('\n', 0, match.start()) 

3277 return newline_offsets[newline_offset] 

3278 

3279 return [get_line_num(match, contents) + 1 for match in matches] 

3280 

3281 

3282def get_disabled_ranges(source): 

3283 """Returns a list of tuples representing the disabled ranges. 

3284 

3285 If disabled and no re-enable will disable for rest of file. 

3286 

3287 """ 

3288 enable_line_nums = find_with_line_numbers(ENABLE_REGEX, source) 

3289 disable_line_nums = find_with_line_numbers(DISABLE_REGEX, source) 

3290 total_lines = len(re.findall("\n", source)) + 1 

3291 

3292 enable_commands = {} 

3293 for num in enable_line_nums: 

3294 enable_commands[num] = True 

3295 for num in disable_line_nums: 

3296 enable_commands[num] = False 

3297 

3298 disabled_ranges = [] 

3299 currently_enabled = True 

3300 disabled_start = None 

3301 

3302 for line, commanded_enabled in sorted(enable_commands.items()): 

3303 if commanded_enabled is False and currently_enabled is True: 

3304 disabled_start = line 

3305 currently_enabled = False 

3306 elif commanded_enabled is True and currently_enabled is False: 

3307 disabled_ranges.append((disabled_start, line)) 

3308 currently_enabled = True 

3309 

3310 if currently_enabled is False: 

3311 disabled_ranges.append((disabled_start, total_lines)) 

3312 

3313 return disabled_ranges 

3314 

3315 

3316def filter_disabled_results(result, disabled_ranges): 

3317 """Filter out reports based on tuple of disabled ranges. 

3318 

3319 """ 

3320 line = result['line'] 

3321 for disabled_range in disabled_ranges: 

3322 if disabled_range[0] <= line <= disabled_range[1]: 

3323 return False 

3324 return True 

3325 

3326 

3327def filter_results(source, results, aggressive): 

3328 """Filter out spurious reports from pycodestyle. 

3329 

3330 If aggressive is True, we allow possibly unsafe fixes (E711, E712). 

3331 

3332 """ 

3333 non_docstring_string_line_numbers = multiline_string_lines( 

3334 source, include_docstrings=False) 

3335 all_string_line_numbers = multiline_string_lines( 

3336 source, include_docstrings=True) 

3337 

3338 commented_out_code_line_numbers = commented_out_code_lines(source) 

3339 

3340 # Filter out the disabled ranges 

3341 disabled_ranges = get_disabled_ranges(source) 

3342 if disabled_ranges: 

3343 results = [ 

3344 result for result in results if filter_disabled_results( 

3345 result, 

3346 disabled_ranges, 

3347 ) 

3348 ] 

3349 

3350 has_e901 = any(result['id'].lower() == 'e901' for result in results) 

3351 

3352 for r in results: 

3353 issue_id = r['id'].lower() 

3354 

3355 if r['line'] in non_docstring_string_line_numbers: 

3356 if issue_id.startswith(('e1', 'e501', 'w191')): 

3357 continue 

3358 

3359 if r['line'] in all_string_line_numbers: 

3360 if issue_id in ['e501']: 

3361 continue 

3362 

3363 # We must offset by 1 for lines that contain the trailing contents of 

3364 # multiline strings. 

3365 if not aggressive and (r['line'] + 1) in all_string_line_numbers: 

3366 # Do not modify multiline strings in non-aggressive mode. Remove 

3367 # trailing whitespace could break doctests. 

3368 if issue_id.startswith(('w29', 'w39')): 

3369 continue 

3370 

3371 if aggressive <= 0: 

3372 if issue_id.startswith(('e711', 'e72', 'w6')): 

3373 continue 

3374 

3375 if aggressive <= 1: 

3376 if issue_id.startswith(('e712', 'e713', 'e714')): 

3377 continue 

3378 

3379 if aggressive <= 2: 

3380 if issue_id.startswith(('e704')): 

3381 continue 

3382 

3383 if r['line'] in commented_out_code_line_numbers: 

3384 if issue_id.startswith(('e261', 'e262', 'e501')): 

3385 continue 

3386 

3387 # Do not touch indentation if there is a token error caused by 

3388 # incomplete multi-line statement. Otherwise, we risk screwing up the 

3389 # indentation. 

3390 if has_e901: 

3391 if issue_id.startswith(('e1', 'e7')): 

3392 continue 

3393 

3394 yield r 

3395 

3396 

3397def multiline_string_lines(source, include_docstrings=False): 

3398 """Return line numbers that are within multiline strings. 

3399 

3400 The line numbers are indexed at 1. 

3401 

3402 Docstrings are ignored. 

3403 

3404 """ 

3405 line_numbers = set() 

3406 previous_token_type = '' 

3407 _check_target_tokens = [tokenize.STRING] 

3408 if IS_SUPPORT_TOKEN_FSTRING: 

3409 _check_target_tokens.extend([ 

3410 tokenize.FSTRING_START, 

3411 tokenize.FSTRING_MIDDLE, 

3412 tokenize.FSTRING_END, 

3413 ]) 

3414 try: 

3415 for t in generate_tokens(source): 

3416 token_type = t[0] 

3417 start_row = t[2][0] 

3418 end_row = t[3][0] 

3419 

3420 if token_type in _check_target_tokens and start_row != end_row: 

3421 if ( 

3422 include_docstrings or 

3423 previous_token_type != tokenize.INDENT 

3424 ): 

3425 # We increment by one since we want the contents of the 

3426 # string. 

3427 line_numbers |= set(range(1 + start_row, 1 + end_row)) 

3428 

3429 previous_token_type = token_type 

3430 except (SyntaxError, tokenize.TokenError): 

3431 pass 

3432 

3433 return line_numbers 

3434 

3435 

3436def commented_out_code_lines(source): 

3437 """Return line numbers of comments that are likely code. 

3438 

3439 Commented-out code is bad practice, but modifying it just adds even 

3440 more clutter. 

3441 

3442 """ 

3443 line_numbers = [] 

3444 try: 

3445 for t in generate_tokens(source): 

3446 token_type = t[0] 

3447 token_string = t[1] 

3448 start_row = t[2][0] 

3449 line = t[4] 

3450 

3451 # Ignore inline comments. 

3452 if not line.lstrip().startswith('#'): 

3453 continue 

3454 

3455 if token_type == tokenize.COMMENT: 

3456 stripped_line = token_string.lstrip('#').strip() 

3457 with warnings.catch_warnings(): 

3458 # ignore SyntaxWarning in Python3.8+ 

3459 # refs: 

3460 # https://bugs.python.org/issue15248 

3461 # https://docs.python.org/3.8/whatsnew/3.8.html#other-language-changes 

3462 warnings.filterwarnings("ignore", category=SyntaxWarning) 

3463 if ( 

3464 ' ' in stripped_line and 

3465 '#' not in stripped_line and 

3466 check_syntax(stripped_line) 

3467 ): 

3468 line_numbers.append(start_row) 

3469 except (SyntaxError, tokenize.TokenError): 

3470 pass 

3471 

3472 return line_numbers 

3473 

3474 

3475def shorten_comment(line, max_line_length, last_comment=False): 

3476 """Return trimmed or split long comment line. 

3477 

3478 If there are no comments immediately following it, do a text wrap. 

3479 Doing this wrapping on all comments in general would lead to jagged 

3480 comment text. 

3481 

3482 """ 

3483 assert len(line) > max_line_length 

3484 line = line.rstrip() 

3485 

3486 # PEP 8 recommends 72 characters for comment text. 

3487 indentation = _get_indentation(line) + '# ' 

3488 max_line_length = min(max_line_length, 

3489 len(indentation) + 72) 

3490 

3491 MIN_CHARACTER_REPEAT = 5 

3492 if ( 

3493 len(line) - len(line.rstrip(line[-1])) >= MIN_CHARACTER_REPEAT and 

3494 not line[-1].isalnum() 

3495 ): 

3496 # Trim comments that end with things like --------- 

3497 return line[:max_line_length] + '\n' 

3498 elif last_comment and re.match(r'\s*#+\s*\w+', line): 

3499 split_lines = textwrap.wrap(line.lstrip(' \t#'), 

3500 initial_indent=indentation, 

3501 subsequent_indent=indentation, 

3502 width=max_line_length, 

3503 break_long_words=False, 

3504 break_on_hyphens=False) 

3505 return '\n'.join(split_lines) + '\n' 

3506 

3507 return line + '\n' 

3508 

3509 

3510def normalize_line_endings(lines, newline): 

3511 """Return fixed line endings. 

3512 

3513 All lines will be modified to use the most common line ending. 

3514 """ 

3515 line = [line.rstrip('\n\r') + newline for line in lines] 

3516 if line and lines[-1] == lines[-1].rstrip('\n\r'): 

3517 line[-1] = line[-1].rstrip('\n\r') 

3518 return line 

3519 

3520 

3521def mutual_startswith(a, b): 

3522 return b.startswith(a) or a.startswith(b) 

3523 

3524 

3525def code_match(code, select, ignore): 

3526 if ignore: 

3527 assert not isinstance(ignore, str) 

3528 for ignored_code in [c.strip() for c in ignore]: 

3529 if mutual_startswith(code.lower(), ignored_code.lower()): 

3530 return False 

3531 

3532 if select: 

3533 assert not isinstance(select, str) 

3534 for selected_code in [c.strip() for c in select]: 

3535 if mutual_startswith(code.lower(), selected_code.lower()): 

3536 return True 

3537 return False 

3538 

3539 return True 

3540 

3541 

3542def fix_code(source, options=None, encoding=None, apply_config=False): 

3543 """Return fixed source code. 

3544 

3545 "encoding" will be used to decode "source" if it is a byte string. 

3546 

3547 """ 

3548 options = _get_options(options, apply_config) 

3549 # normalize 

3550 options.ignore = [opt.upper() for opt in options.ignore] 

3551 options.select = [opt.upper() for opt in options.select] 

3552 

3553 # check ignore args 

3554 # NOTE: If W50x is not included, add W50x because the code 

3555 # correction result is indefinite. 

3556 ignore_opt = options.ignore 

3557 if not {"W50", "W503", "W504"} & set(ignore_opt): 

3558 options.ignore.append("W50") 

3559 

3560 if not isinstance(source, str): 

3561 source = source.decode(encoding or get_encoding()) 

3562 

3563 sio = io.StringIO(source) 

3564 return fix_lines(sio.readlines(), options=options) 

3565 

3566 

3567def _get_options(raw_options, apply_config): 

3568 """Return parsed options.""" 

3569 if not raw_options: 

3570 return parse_args([''], apply_config=apply_config) 

3571 

3572 if isinstance(raw_options, dict): 

3573 options = parse_args([''], apply_config=apply_config) 

3574 for name, value in raw_options.items(): 

3575 if not hasattr(options, name): 

3576 raise ValueError("No such option '{}'".format(name)) 

3577 

3578 # Check for very basic type errors. 

3579 expected_type = type(getattr(options, name)) 

3580 if not isinstance(expected_type, (str, )): 

3581 if isinstance(value, (str, )): 

3582 raise ValueError( 

3583 "Option '{}' should not be a string".format(name)) 

3584 setattr(options, name, value) 

3585 else: 

3586 options = raw_options 

3587 

3588 return options 

3589 

3590 

3591def fix_lines(source_lines, options, filename=''): 

3592 """Return fixed source code.""" 

3593 # Transform everything to line feed. Then change them back to original 

3594 # before returning fixed source code. 

3595 original_newline = find_newline(source_lines) 

3596 tmp_source = ''.join(normalize_line_endings(source_lines, '\n')) 

3597 

3598 # Keep a history to break out of cycles. 

3599 previous_hashes = set() 

3600 

3601 if options.line_range: 

3602 # Disable "apply_local_fixes()" for now due to issue #175. 

3603 fixed_source = tmp_source 

3604 else: 

3605 # Apply global fixes only once (for efficiency). 

3606 fixed_source = apply_global_fixes(tmp_source, 

3607 options, 

3608 filename=filename) 

3609 

3610 passes = 0 

3611 long_line_ignore_cache = set() 

3612 while hash(fixed_source) not in previous_hashes: 

3613 if options.pep8_passes >= 0 and passes > options.pep8_passes: 

3614 break 

3615 passes += 1 

3616 

3617 previous_hashes.add(hash(fixed_source)) 

3618 

3619 tmp_source = copy.copy(fixed_source) 

3620 

3621 fix = FixPEP8( 

3622 filename, 

3623 options, 

3624 contents=tmp_source, 

3625 long_line_ignore_cache=long_line_ignore_cache) 

3626 

3627 fixed_source = fix.fix() 

3628 

3629 sio = io.StringIO(fixed_source) 

3630 return ''.join(normalize_line_endings(sio.readlines(), original_newline)) 

3631 

3632 

3633def fix_file(filename, options=None, output=None, apply_config=False): 

3634 if not options: 

3635 options = parse_args([filename], apply_config=apply_config) 

3636 

3637 original_source = readlines_from_file(filename) 

3638 

3639 fixed_source = original_source 

3640 

3641 if options.in_place or options.diff or output: 

3642 encoding = detect_encoding(filename) 

3643 

3644 if output: 

3645 output = LineEndingWrapper(wrap_output(output, encoding=encoding)) 

3646 

3647 fixed_source = fix_lines(fixed_source, options, filename=filename) 

3648 

3649 if options.diff: 

3650 new = io.StringIO(fixed_source) 

3651 new = new.readlines() 

3652 diff = get_diff_text(original_source, new, filename) 

3653 if output: 

3654 output.write(diff) 

3655 output.flush() 

3656 elif options.jobs > 1: 

3657 diff = diff.encode(encoding) 

3658 return diff 

3659 elif options.in_place: 

3660 original = "".join(original_source).splitlines() 

3661 fixed = fixed_source.splitlines() 

3662 original_source_last_line = ( 

3663 original_source[-1].split("\n")[-1] if original_source else "" 

3664 ) 

3665 fixed_source_last_line = fixed_source.split("\n")[-1] 

3666 if original != fixed or ( 

3667 original_source_last_line != fixed_source_last_line 

3668 ): 

3669 with open_with_encoding(filename, 'w', encoding=encoding) as fp: 

3670 fp.write(fixed_source) 

3671 return fixed_source 

3672 return None 

3673 else: 

3674 if output: 

3675 output.write(fixed_source) 

3676 output.flush() 

3677 return fixed_source 

3678 

3679 

3680def global_fixes(): 

3681 """Yield multiple (code, function) tuples.""" 

3682 for function in list(globals().values()): 

3683 if inspect.isfunction(function): 

3684 arguments = _get_parameters(function) 

3685 if arguments[:1] != ['source']: 

3686 continue 

3687 

3688 code = extract_code_from_function(function) 

3689 if code: 

3690 yield (code, function) 

3691 

3692 

3693def _get_parameters(function): 

3694 # pylint: disable=deprecated-method 

3695 if sys.version_info.major >= 3: 

3696 # We need to match "getargspec()", which includes "self" as the first 

3697 # value for methods. 

3698 # https://bugs.python.org/issue17481#msg209469 

3699 if inspect.ismethod(function): 

3700 function = function.__func__ 

3701 

3702 return list(inspect.signature(function).parameters) 

3703 else: 

3704 return inspect.getargspec(function)[0] 

3705 

3706 

3707def apply_global_fixes(source, options, where='global', filename='', 

3708 codes=None): 

3709 """Run global fixes on source code. 

3710 

3711 These are fixes that only need be done once (unlike those in 

3712 FixPEP8, which are dependent on pycodestyle). 

3713 

3714 """ 

3715 if codes is None: 

3716 codes = [] 

3717 if any(code_match(code, select=options.select, ignore=options.ignore) 

3718 for code in ['E101', 'E111']): 

3719 source = reindent( 

3720 source, 

3721 indent_size=options.indent_size, 

3722 leave_tabs=not ( 

3723 code_match( 

3724 'W191', 

3725 select=options.select, 

3726 ignore=options.ignore 

3727 ) 

3728 ) 

3729 ) 

3730 

3731 for (code, function) in global_fixes(): 

3732 if code_match(code, select=options.select, ignore=options.ignore): 

3733 if options.verbose: 

3734 print('---> Applying {} fix for {}'.format(where, 

3735 code.upper()), 

3736 file=sys.stderr) 

3737 source = function(source, 

3738 aggressive=options.aggressive) 

3739 

3740 return source 

3741 

3742 

3743def extract_code_from_function(function): 

3744 """Return code handled by function.""" 

3745 if not function.__name__.startswith('fix_'): 

3746 return None 

3747 

3748 code = re.sub('^fix_', '', function.__name__) 

3749 if not code: 

3750 return None 

3751 

3752 try: 

3753 int(code[1:]) 

3754 except ValueError: 

3755 return None 

3756 

3757 return code 

3758 

3759 

3760def _get_package_version(): 

3761 packages = ["pycodestyle: {}".format(pycodestyle.__version__)] 

3762 return ", ".join(packages) 

3763 

3764 

3765def create_parser(): 

3766 """Return command-line parser.""" 

3767 parser = argparse.ArgumentParser(description=docstring_summary(__doc__), 

3768 prog='autopep8') 

3769 parser.add_argument('--version', action='version', 

3770 version='%(prog)s {} ({})'.format( 

3771 __version__, _get_package_version())) 

3772 parser.add_argument('-v', '--verbose', action='count', 

3773 default=0, 

3774 help='print verbose messages; ' 

3775 'multiple -v result in more verbose messages') 

3776 parser.add_argument('-d', '--diff', action='store_true', 

3777 help='print the diff for the fixed source') 

3778 parser.add_argument('-i', '--in-place', action='store_true', 

3779 help='make changes to files in place') 

3780 parser.add_argument('--global-config', metavar='filename', 

3781 default=DEFAULT_CONFIG, 

3782 help='path to a global pep8 config file; if this file ' 

3783 'does not exist then this is ignored ' 

3784 '(default: {})'.format(DEFAULT_CONFIG)) 

3785 parser.add_argument('--ignore-local-config', action='store_true', 

3786 help="don't look for and apply local config files; " 

3787 'if not passed, defaults are updated with any ' 

3788 "config files in the project's root directory") 

3789 parser.add_argument('-r', '--recursive', action='store_true', 

3790 help='run recursively over directories; ' 

3791 'must be used with --in-place or --diff') 

3792 parser.add_argument('-j', '--jobs', type=int, metavar='n', default=1, 

3793 help='number of parallel jobs; ' 

3794 'match CPU count if value is less than 1') 

3795 parser.add_argument('-p', '--pep8-passes', metavar='n', 

3796 default=-1, type=int, 

3797 help='maximum number of additional pep8 passes ' 

3798 '(default: infinite)') 

3799 parser.add_argument('-a', '--aggressive', action='count', default=0, 

3800 help='enable non-whitespace changes; ' 

3801 'multiple -a result in more aggressive changes') 

3802 parser.add_argument('--experimental', action='store_true', 

3803 help='enable experimental fixes') 

3804 parser.add_argument('--exclude', metavar='globs', 

3805 help='exclude file/directory names that match these ' 

3806 'comma-separated globs') 

3807 parser.add_argument('--list-fixes', action='store_true', 

3808 help='list codes for fixes; ' 

3809 'used by --ignore and --select') 

3810 parser.add_argument('--ignore', metavar='errors', default='', 

3811 help='do not fix these errors/warnings ' 

3812 '(default: {})'.format(DEFAULT_IGNORE)) 

3813 parser.add_argument('--select', metavar='errors', default='', 

3814 help='fix only these errors/warnings (e.g. E4,W)') 

3815 parser.add_argument('--max-line-length', metavar='n', default=79, type=int, 

3816 help='set maximum allowed line length ' 

3817 '(default: %(default)s)') 

3818 parser.add_argument('--line-range', '--range', metavar='line', 

3819 default=None, type=int, nargs=2, 

3820 help='only fix errors found within this inclusive ' 

3821 'range of line numbers (e.g. 1 99); ' 

3822 'line numbers are indexed at 1') 

3823 parser.add_argument('--indent-size', default=DEFAULT_INDENT_SIZE, 

3824 type=int, help=argparse.SUPPRESS) 

3825 parser.add_argument('--hang-closing', action='store_true', 

3826 help='hang-closing option passed to pycodestyle') 

3827 parser.add_argument('--exit-code', action='store_true', 

3828 help='change to behavior of exit code.' 

3829 ' default behavior of return value, 0 is no ' 

3830 'differences, 1 is error exit. return 2 when' 

3831 ' add this option. 2 is exists differences.') 

3832 parser.add_argument('files', nargs='*', 

3833 help="files to format or '-' for standard in") 

3834 

3835 return parser 

3836 

3837 

3838def _expand_codes(codes, ignore_codes): 

3839 """expand to individual E/W codes""" 

3840 ret = set() 

3841 

3842 is_conflict = False 

3843 if all( 

3844 any( 

3845 conflicting_code.startswith(code) 

3846 for code in codes 

3847 ) 

3848 for conflicting_code in CONFLICTING_CODES 

3849 ): 

3850 is_conflict = True 

3851 

3852 is_ignore_w503 = "W503" in ignore_codes 

3853 is_ignore_w504 = "W504" in ignore_codes 

3854 

3855 for code in codes: 

3856 if code == "W": 

3857 if is_ignore_w503 and is_ignore_w504: 

3858 ret.update({"W1", "W2", "W3", "W505", "W6"}) 

3859 elif is_ignore_w503: 

3860 ret.update({"W1", "W2", "W3", "W504", "W505", "W6"}) 

3861 else: 

3862 ret.update({"W1", "W2", "W3", "W503", "W505", "W6"}) 

3863 elif code in ("W5", "W50"): 

3864 if is_ignore_w503 and is_ignore_w504: 

3865 ret.update({"W505"}) 

3866 elif is_ignore_w503: 

3867 ret.update({"W504", "W505"}) 

3868 else: 

3869 ret.update({"W503", "W505"}) 

3870 elif not (code in ("W503", "W504") and is_conflict): 

3871 ret.add(code) 

3872 

3873 return ret 

3874 

3875 

3876def _parser_error_with_code( 

3877 parser: argparse.ArgumentParser, code: int, msg: str, 

3878) -> None: 

3879 """wrap parser.error with exit code""" 

3880 parser.print_usage(sys.stderr) 

3881 parser.exit(code, f"{msg}\n") 

3882 

3883 

3884def parse_args(arguments, apply_config=False): 

3885 """Parse command-line options.""" 

3886 parser = create_parser() 

3887 args = parser.parse_args(arguments) 

3888 

3889 if not args.files and not args.list_fixes: 

3890 _parser_error_with_code( 

3891 parser, EXIT_CODE_ARGPARSE_ERROR, 'incorrect number of arguments', 

3892 ) 

3893 

3894 args.files = [decode_filename(name) for name in args.files] 

3895 

3896 if apply_config: 

3897 parser = read_config(args, parser) 

3898 # prioritize settings when exist pyproject.toml's tool.autopep8 section 

3899 try: 

3900 parser_with_pyproject_toml = read_pyproject_toml(args, parser) 

3901 except Exception: 

3902 parser_with_pyproject_toml = None 

3903 if parser_with_pyproject_toml: 

3904 parser = parser_with_pyproject_toml 

3905 args = parser.parse_args(arguments) 

3906 args.files = [decode_filename(name) for name in args.files] 

3907 

3908 if '-' in args.files: 

3909 if len(args.files) > 1: 

3910 _parser_error_with_code( 

3911 parser, 

3912 EXIT_CODE_ARGPARSE_ERROR, 

3913 'cannot mix stdin and regular files', 

3914 ) 

3915 

3916 if args.diff: 

3917 _parser_error_with_code( 

3918 parser, 

3919 EXIT_CODE_ARGPARSE_ERROR, 

3920 '--diff cannot be used with standard input', 

3921 ) 

3922 

3923 if args.in_place: 

3924 _parser_error_with_code( 

3925 parser, 

3926 EXIT_CODE_ARGPARSE_ERROR, 

3927 '--in-place cannot be used with standard input', 

3928 ) 

3929 

3930 if args.recursive: 

3931 _parser_error_with_code( 

3932 parser, 

3933 EXIT_CODE_ARGPARSE_ERROR, 

3934 '--recursive cannot be used with standard input', 

3935 ) 

3936 

3937 if len(args.files) > 1 and not (args.in_place or args.diff): 

3938 _parser_error_with_code( 

3939 parser, 

3940 EXIT_CODE_ARGPARSE_ERROR, 

3941 'autopep8 only takes one filename as argument ' 

3942 'unless the "--in-place" or "--diff" args are used', 

3943 ) 

3944 

3945 if args.recursive and not (args.in_place or args.diff): 

3946 _parser_error_with_code( 

3947 parser, 

3948 EXIT_CODE_ARGPARSE_ERROR, 

3949 '--recursive must be used with --in-place or --diff', 

3950 ) 

3951 

3952 if args.in_place and args.diff: 

3953 _parser_error_with_code( 

3954 parser, 

3955 EXIT_CODE_ARGPARSE_ERROR, 

3956 '--in-place and --diff are mutually exclusive', 

3957 ) 

3958 

3959 if args.max_line_length <= 0: 

3960 _parser_error_with_code( 

3961 parser, 

3962 EXIT_CODE_ARGPARSE_ERROR, 

3963 '--max-line-length must be greater than 0', 

3964 ) 

3965 

3966 if args.indent_size <= 0: 

3967 _parser_error_with_code( 

3968 parser, 

3969 EXIT_CODE_ARGPARSE_ERROR, 

3970 '--indent-size must be greater than 0', 

3971 ) 

3972 

3973 if args.select: 

3974 args.select = _expand_codes( 

3975 _split_comma_separated(args.select), 

3976 (_split_comma_separated(args.ignore) if args.ignore else []) 

3977 ) 

3978 

3979 if args.ignore: 

3980 args.ignore = _split_comma_separated(args.ignore) 

3981 if all( 

3982 not any( 

3983 conflicting_code.startswith(ignore_code) 

3984 for ignore_code in args.ignore 

3985 ) 

3986 for conflicting_code in CONFLICTING_CODES 

3987 ): 

3988 args.ignore.update(CONFLICTING_CODES) 

3989 elif not args.select: 

3990 if args.aggressive: 

3991 # Enable everything by default if aggressive. 

3992 args.select = {'E', 'W1', 'W2', 'W3', 'W6'} 

3993 else: 

3994 args.ignore = _split_comma_separated(DEFAULT_IGNORE) 

3995 

3996 if args.exclude: 

3997 args.exclude = _split_comma_separated(args.exclude) 

3998 else: 

3999 args.exclude = {} 

4000 

4001 if args.jobs < 1: 

4002 # Do not import multiprocessing globally in case it is not supported 

4003 # on the platform. 

4004 import multiprocessing 

4005 args.jobs = multiprocessing.cpu_count() 

4006 

4007 if args.jobs > 1 and not (args.in_place or args.diff): 

4008 _parser_error_with_code( 

4009 parser, 

4010 EXIT_CODE_ARGPARSE_ERROR, 

4011 'parallel jobs requires --in-place', 

4012 ) 

4013 

4014 if args.line_range: 

4015 if args.line_range[0] <= 0: 

4016 _parser_error_with_code( 

4017 parser, 

4018 EXIT_CODE_ARGPARSE_ERROR, 

4019 '--range must be positive numbers', 

4020 ) 

4021 if args.line_range[0] > args.line_range[1]: 

4022 _parser_error_with_code( 

4023 parser, 

4024 EXIT_CODE_ARGPARSE_ERROR, 

4025 'First value of --range should be less than or equal ' 

4026 'to the second', 

4027 ) 

4028 

4029 original_formatwarning = warnings.formatwarning 

4030 warnings.formatwarning = _custom_formatwarning 

4031 if args.experimental: 

4032 warnings.warn( 

4033 "`experimental` option is deprecated and will be " 

4034 "removed in a future version.", 

4035 DeprecationWarning, 

4036 ) 

4037 warnings.formatwarning = original_formatwarning 

4038 

4039 return args 

4040 

4041 

4042def _get_normalize_options(args, config, section, option_list): 

4043 for (k, v) in config.items(section): 

4044 norm_opt = k.lstrip('-').replace('-', '_') 

4045 if not option_list.get(norm_opt): 

4046 continue 

4047 opt_type = option_list[norm_opt] 

4048 if opt_type is int: 

4049 if v.strip() == "auto": 

4050 # skip to special case 

4051 if args.verbose: 

4052 print(f"ignore config: {k}={v}") 

4053 continue 

4054 value = config.getint(section, k) 

4055 elif opt_type is bool: 

4056 value = config.getboolean(section, k) 

4057 else: 

4058 value = config.get(section, k) 

4059 yield norm_opt, k, value 

4060 

4061 

4062def read_config(args, parser): 

4063 """Read both user configuration and local configuration.""" 

4064 config = SafeConfigParser() 

4065 

4066 try: 

4067 if args.verbose and os.path.exists(args.global_config): 

4068 print("read config path: {}".format(args.global_config)) 

4069 config.read(args.global_config) 

4070 

4071 if not args.ignore_local_config: 

4072 parent = tail = args.files and os.path.abspath( 

4073 os.path.commonprefix(args.files)) 

4074 while tail: 

4075 if config.read([os.path.join(parent, fn) 

4076 for fn in PROJECT_CONFIG]): 

4077 if args.verbose: 

4078 for fn in PROJECT_CONFIG: 

4079 config_file = os.path.join(parent, fn) 

4080 if not os.path.exists(config_file): 

4081 continue 

4082 print( 

4083 "read config path: {}".format( 

4084 os.path.join(parent, fn) 

4085 ) 

4086 ) 

4087 break 

4088 (parent, tail) = os.path.split(parent) 

4089 

4090 defaults = {} 

4091 option_list = {o.dest: o.type or type(o.default) 

4092 for o in parser._actions} 

4093 

4094 for section in ['pep8', 'pycodestyle', 'flake8']: 

4095 if not config.has_section(section): 

4096 continue 

4097 for norm_opt, k, value in _get_normalize_options( 

4098 args, config, section, option_list 

4099 ): 

4100 if args.verbose: 

4101 print("enable config: section={}, key={}, value={}".format( 

4102 section, k, value)) 

4103 defaults[norm_opt] = value 

4104 

4105 parser.set_defaults(**defaults) 

4106 except Error: 

4107 # Ignore for now. 

4108 pass 

4109 

4110 return parser 

4111 

4112 

4113def read_pyproject_toml(args, parser): 

4114 """Read pyproject.toml and load configuration.""" 

4115 if sys.version_info >= (3, 11): 

4116 import tomllib 

4117 else: 

4118 import tomli as tomllib 

4119 

4120 config = None 

4121 

4122 if os.path.exists(args.global_config): 

4123 with open(args.global_config, "rb") as fp: 

4124 config = tomllib.load(fp) 

4125 

4126 if not args.ignore_local_config: 

4127 parent = tail = args.files and os.path.abspath( 

4128 os.path.commonprefix(args.files)) 

4129 while tail: 

4130 pyproject_toml = os.path.join(parent, "pyproject.toml") 

4131 if os.path.exists(pyproject_toml): 

4132 with open(pyproject_toml, "rb") as fp: 

4133 config = tomllib.load(fp) 

4134 break 

4135 (parent, tail) = os.path.split(parent) 

4136 

4137 if not config: 

4138 return None 

4139 

4140 if config.get("tool", {}).get("autopep8") is None: 

4141 return None 

4142 

4143 config = config.get("tool", {}).get("autopep8") 

4144 

4145 defaults = {} 

4146 option_list = {o.dest: o.type or type(o.default) 

4147 for o in parser._actions} 

4148 

4149 TUPLED_OPTIONS = ("ignore", "select") 

4150 for (k, v) in config.items(): 

4151 norm_opt = k.lstrip('-').replace('-', '_') 

4152 if not option_list.get(norm_opt): 

4153 continue 

4154 if type(v) in (list, tuple) and norm_opt in TUPLED_OPTIONS: 

4155 value = ",".join(v) 

4156 else: 

4157 value = v 

4158 if args.verbose: 

4159 print("enable pyproject.toml config: " 

4160 "key={}, value={}".format(k, value)) 

4161 defaults[norm_opt] = value 

4162 

4163 if defaults: 

4164 # set value when exists key-value in defaults dict 

4165 parser.set_defaults(**defaults) 

4166 

4167 return parser 

4168 

4169 

4170def _split_comma_separated(string): 

4171 """Return a set of strings.""" 

4172 return {text.strip() for text in string.split(',') if text.strip()} 

4173 

4174 

4175def decode_filename(filename): 

4176 """Return Unicode filename.""" 

4177 if isinstance(filename, str): 

4178 return filename 

4179 

4180 return filename.decode(sys.getfilesystemencoding()) 

4181 

4182 

4183def supported_fixes(): 

4184 """Yield pep8 error codes that autopep8 fixes. 

4185 

4186 Each item we yield is a tuple of the code followed by its 

4187 description. 

4188 

4189 """ 

4190 yield ('E101', docstring_summary(reindent.__doc__)) 

4191 

4192 instance = FixPEP8(filename=None, options=None, contents='') 

4193 for attribute in dir(instance): 

4194 code = re.match('fix_([ew][0-9][0-9][0-9])', attribute) 

4195 if code: 

4196 yield ( 

4197 code.group(1).upper(), 

4198 re.sub(r'\s+', ' ', 

4199 docstring_summary(getattr(instance, attribute).__doc__)) 

4200 ) 

4201 

4202 for (code, function) in sorted(global_fixes()): 

4203 yield (code.upper() + (4 - len(code)) * ' ', 

4204 re.sub(r'\s+', ' ', docstring_summary(function.__doc__))) 

4205 

4206 

4207def docstring_summary(docstring): 

4208 """Return summary of docstring.""" 

4209 return docstring.split('\n')[0] if docstring else '' 

4210 

4211 

4212def line_shortening_rank(candidate, indent_word, max_line_length, 

4213 experimental=False): 

4214 """Return rank of candidate. 

4215 

4216 This is for sorting candidates. 

4217 

4218 """ 

4219 if not candidate.strip(): 

4220 return 0 

4221 

4222 rank = 0 

4223 lines = candidate.rstrip().split('\n') 

4224 

4225 offset = 0 

4226 if ( 

4227 not lines[0].lstrip().startswith('#') and 

4228 lines[0].rstrip()[-1] not in '([{' 

4229 ): 

4230 for (opening, closing) in ('()', '[]', '{}'): 

4231 # Don't penalize empty containers that aren't split up. Things like 

4232 # this "foo(\n )" aren't particularly good. 

4233 opening_loc = lines[0].find(opening) 

4234 closing_loc = lines[0].find(closing) 

4235 if opening_loc >= 0: 

4236 if closing_loc < 0 or closing_loc != opening_loc + 1: 

4237 offset = max(offset, 1 + opening_loc) 

4238 

4239 current_longest = max(offset + len(x.strip()) for x in lines) 

4240 

4241 rank += 4 * max(0, current_longest - max_line_length) 

4242 

4243 rank += len(lines) 

4244 

4245 # Too much variation in line length is ugly. 

4246 rank += 2 * standard_deviation(len(line) for line in lines) 

4247 

4248 bad_staring_symbol = { 

4249 '(': ')', 

4250 '[': ']', 

4251 '{': '}'}.get(lines[0][-1]) 

4252 

4253 if len(lines) > 1: 

4254 if ( 

4255 bad_staring_symbol and 

4256 lines[1].lstrip().startswith(bad_staring_symbol) 

4257 ): 

4258 rank += 20 

4259 

4260 for lineno, current_line in enumerate(lines): 

4261 current_line = current_line.strip() 

4262 

4263 if current_line.startswith('#'): 

4264 continue 

4265 

4266 for bad_start in ['.', '%', '+', '-', '/']: 

4267 if current_line.startswith(bad_start): 

4268 rank += 100 

4269 

4270 # Do not tolerate operators on their own line. 

4271 if current_line == bad_start: 

4272 rank += 1000 

4273 

4274 if ( 

4275 current_line.endswith(('.', '%', '+', '-', '/')) and 

4276 "': " in current_line 

4277 ): 

4278 rank += 1000 

4279 

4280 if current_line.endswith(('(', '[', '{', '.')): 

4281 # Avoid lonely opening. They result in longer lines. 

4282 if len(current_line) <= len(indent_word): 

4283 rank += 100 

4284 

4285 # Avoid the ugliness of ", (\n". 

4286 if ( 

4287 current_line.endswith('(') and 

4288 current_line[:-1].rstrip().endswith(',') 

4289 ): 

4290 rank += 100 

4291 

4292 # Avoid the ugliness of "something[\n" and something[index][\n. 

4293 if ( 

4294 current_line.endswith('[') and 

4295 len(current_line) > 1 and 

4296 (current_line[-2].isalnum() or current_line[-2] in ']') 

4297 ): 

4298 rank += 300 

4299 

4300 # Also avoid the ugliness of "foo.\nbar" 

4301 if current_line.endswith('.'): 

4302 rank += 100 

4303 

4304 if has_arithmetic_operator(current_line): 

4305 rank += 100 

4306 

4307 # Avoid breaking at unary operators. 

4308 if re.match(r'.*[(\[{]\s*[\-\+~]$', current_line.rstrip('\\ ')): 

4309 rank += 1000 

4310 

4311 if re.match(r'.*lambda\s*\*$', current_line.rstrip('\\ ')): 

4312 rank += 1000 

4313 

4314 if current_line.endswith(('%', '(', '[', '{')): 

4315 rank -= 20 

4316 

4317 # Try to break list comprehensions at the "for". 

4318 if current_line.startswith('for '): 

4319 rank -= 50 

4320 

4321 if current_line.endswith('\\'): 

4322 # If a line ends in \-newline, it may be part of a 

4323 # multiline string. In that case, we would like to know 

4324 # how long that line is without the \-newline. If it's 

4325 # longer than the maximum, or has comments, then we assume 

4326 # that the \-newline is an okay candidate and only 

4327 # penalize it a bit. 

4328 total_len = len(current_line) 

4329 lineno += 1 

4330 while lineno < len(lines): 

4331 total_len += len(lines[lineno]) 

4332 

4333 if lines[lineno].lstrip().startswith('#'): 

4334 total_len = max_line_length 

4335 break 

4336 

4337 if not lines[lineno].endswith('\\'): 

4338 break 

4339 

4340 lineno += 1 

4341 

4342 if total_len < max_line_length: 

4343 rank += 10 

4344 else: 

4345 rank += 100 if experimental else 1 

4346 

4347 # Prefer breaking at commas rather than colon. 

4348 if ',' in current_line and current_line.endswith(':'): 

4349 rank += 10 

4350 

4351 # Avoid splitting dictionaries between key and value. 

4352 if current_line.endswith(':'): 

4353 rank += 100 

4354 

4355 rank += 10 * count_unbalanced_brackets(current_line) 

4356 

4357 return max(0, rank) 

4358 

4359 

4360def standard_deviation(numbers): 

4361 """Return standard deviation.""" 

4362 numbers = list(numbers) 

4363 if not numbers: 

4364 return 0 

4365 mean = sum(numbers) / len(numbers) 

4366 return (sum((n - mean) ** 2 for n in numbers) / 

4367 len(numbers)) ** .5 

4368 

4369 

4370def has_arithmetic_operator(line): 

4371 """Return True if line contains any arithmetic operators.""" 

4372 for operator in pycodestyle.ARITHMETIC_OP: 

4373 if operator in line: 

4374 return True 

4375 

4376 return False 

4377 

4378 

4379def count_unbalanced_brackets(line): 

4380 """Return number of unmatched open/close brackets.""" 

4381 count = 0 

4382 for opening, closing in ['()', '[]', '{}']: 

4383 count += abs(line.count(opening) - line.count(closing)) 

4384 

4385 return count 

4386 

4387 

4388def split_at_offsets(line, offsets): 

4389 """Split line at offsets. 

4390 

4391 Return list of strings. 

4392 

4393 """ 

4394 result = [] 

4395 

4396 previous_offset = 0 

4397 current_offset = 0 

4398 for current_offset in sorted(offsets): 

4399 if current_offset < len(line) and previous_offset != current_offset: 

4400 result.append(line[previous_offset:current_offset].strip()) 

4401 previous_offset = current_offset 

4402 

4403 result.append(line[current_offset:]) 

4404 

4405 return result 

4406 

4407 

4408class LineEndingWrapper(object): 

4409 

4410 r"""Replace line endings to work with sys.stdout. 

4411 

4412 It seems that sys.stdout expects only '\n' as the line ending, no matter 

4413 the platform. Otherwise, we get repeated line endings. 

4414 

4415 """ 

4416 

4417 def __init__(self, output): 

4418 self.__output = output 

4419 

4420 def write(self, s): 

4421 self.__output.write(s.replace('\r\n', '\n').replace('\r', '\n')) 

4422 

4423 def flush(self): 

4424 self.__output.flush() 

4425 

4426 

4427def match_file(filename, exclude): 

4428 """Return True if file is okay for modifying/recursing.""" 

4429 base_name = os.path.basename(filename) 

4430 

4431 if base_name.startswith('.'): 

4432 return False 

4433 

4434 for pattern in exclude: 

4435 if fnmatch.fnmatch(base_name, pattern): 

4436 return False 

4437 if fnmatch.fnmatch(filename, pattern): 

4438 return False 

4439 

4440 if not os.path.isdir(filename) and not is_python_file(filename): 

4441 return False 

4442 

4443 return True 

4444 

4445 

4446def find_files(filenames, recursive, exclude): 

4447 """Yield filenames.""" 

4448 while filenames: 

4449 name = filenames.pop(0) 

4450 if recursive and os.path.isdir(name): 

4451 for root, directories, children in os.walk(name): 

4452 filenames += [os.path.join(root, f) for f in children 

4453 if match_file(os.path.join(root, f), 

4454 exclude)] 

4455 directories[:] = [d for d in directories 

4456 if match_file(os.path.join(root, d), 

4457 exclude)] 

4458 else: 

4459 is_exclude_match = False 

4460 for pattern in exclude: 

4461 if fnmatch.fnmatch(name, pattern): 

4462 is_exclude_match = True 

4463 break 

4464 if not is_exclude_match: 

4465 yield name 

4466 

4467 

4468def _fix_file(parameters): 

4469 """Helper function for optionally running fix_file() in parallel.""" 

4470 if parameters[1].verbose: 

4471 print('[file:{}]'.format(parameters[0]), file=sys.stderr) 

4472 try: 

4473 return fix_file(*parameters) 

4474 except IOError as error: 

4475 print(str(error), file=sys.stderr) 

4476 raise error 

4477 

4478 

4479def fix_multiple_files(filenames, options, output=None): 

4480 """Fix list of files. 

4481 

4482 Optionally fix files recursively. 

4483 

4484 """ 

4485 results = [] 

4486 filenames = find_files(filenames, options.recursive, options.exclude) 

4487 if options.jobs > 1: 

4488 import multiprocessing 

4489 pool = multiprocessing.Pool(options.jobs) 

4490 rets = [] 

4491 for name in filenames: 

4492 ret = pool.apply_async(_fix_file, ((name, options),)) 

4493 rets.append(ret) 

4494 pool.close() 

4495 pool.join() 

4496 if options.diff: 

4497 for r in rets: 

4498 sys.stdout.write(r.get().decode()) 

4499 sys.stdout.flush() 

4500 results.extend([x.get() for x in rets if x is not None]) 

4501 else: 

4502 for name in filenames: 

4503 ret = _fix_file((name, options, output)) 

4504 if ret is None: 

4505 continue 

4506 if options.diff: 

4507 if ret != '': 

4508 results.append(ret) 

4509 elif options.in_place: 

4510 results.append(ret) 

4511 else: 

4512 original_source = readlines_from_file(name) 

4513 if "".join(original_source).splitlines() != ret.splitlines(): 

4514 results.append(ret) 

4515 return results 

4516 

4517 

4518def is_python_file(filename): 

4519 """Return True if filename is Python file.""" 

4520 if filename.endswith('.py'): 

4521 return True 

4522 

4523 try: 

4524 with open_with_encoding( 

4525 filename, 

4526 limit_byte_check=MAX_PYTHON_FILE_DETECTION_BYTES) as f: 

4527 text = f.read(MAX_PYTHON_FILE_DETECTION_BYTES) 

4528 if not text: 

4529 return False 

4530 first_line = text.splitlines()[0] 

4531 except (IOError, IndexError): 

4532 return False 

4533 

4534 if not PYTHON_SHEBANG_REGEX.match(first_line): 

4535 return False 

4536 

4537 return True 

4538 

4539 

4540def is_probably_part_of_multiline(line): 

4541 """Return True if line is likely part of a multiline string. 

4542 

4543 When multiline strings are involved, pep8 reports the error as being 

4544 at the start of the multiline string, which doesn't work for us. 

4545 

4546 """ 

4547 return ( 

4548 '"""' in line or 

4549 "'''" in line or 

4550 line.rstrip().endswith('\\') 

4551 ) 

4552 

4553 

4554def wrap_output(output, encoding): 

4555 """Return output with specified encoding.""" 

4556 return codecs.getwriter(encoding)(output.buffer 

4557 if hasattr(output, 'buffer') 

4558 else output) 

4559 

4560 

4561def get_encoding(): 

4562 """Return preferred encoding.""" 

4563 return locale.getpreferredencoding() or sys.getdefaultencoding() 

4564 

4565 

4566def main(argv=None, apply_config=True): 

4567 """Command-line entry.""" 

4568 if argv is None: 

4569 argv = sys.argv 

4570 

4571 try: 

4572 # Exit on broken pipe. 

4573 signal.signal(signal.SIGPIPE, signal.SIG_DFL) 

4574 except AttributeError: # pragma: no cover 

4575 # SIGPIPE is not available on Windows. 

4576 pass 

4577 

4578 try: 

4579 args = parse_args(argv[1:], apply_config=apply_config) 

4580 

4581 if args.list_fixes: 

4582 for code, description in sorted(supported_fixes()): 

4583 print('{code} - {description}'.format( 

4584 code=code, description=description)) 

4585 return EXIT_CODE_OK 

4586 

4587 if args.files == ['-']: 

4588 assert not args.in_place 

4589 

4590 encoding = sys.stdin.encoding or get_encoding() 

4591 read_stdin = sys.stdin.read() 

4592 fixed_stdin = fix_code(read_stdin, args, encoding=encoding) 

4593 

4594 # LineEndingWrapper is unnecessary here due to the symmetry between 

4595 # standard in and standard out. 

4596 wrap_output(sys.stdout, encoding=encoding).write(fixed_stdin) 

4597 

4598 if hash(read_stdin) != hash(fixed_stdin): 

4599 if args.exit_code: 

4600 return EXIT_CODE_EXISTS_DIFF 

4601 else: 

4602 if args.in_place or args.diff: 

4603 args.files = list(set(args.files)) 

4604 else: 

4605 assert len(args.files) == 1 

4606 assert not args.recursive 

4607 

4608 results = fix_multiple_files(args.files, args, sys.stdout) 

4609 if args.diff: 

4610 ret = any([len(ret) != 0 for ret in results]) 

4611 else: 

4612 # with in-place option 

4613 ret = any([ret is not None for ret in results]) 

4614 if args.exit_code and ret: 

4615 return EXIT_CODE_EXISTS_DIFF 

4616 except IOError: 

4617 return EXIT_CODE_ERROR 

4618 except KeyboardInterrupt: 

4619 return EXIT_CODE_ERROR # pragma: no cover 

4620 

4621 

4622class CachedTokenizer(object): 

4623 

4624 """A one-element cache around tokenize.generate_tokens(). 

4625 

4626 Original code written by Ned Batchelder, in coverage.py. 

4627 

4628 """ 

4629 

4630 def __init__(self): 

4631 self.last_text = None 

4632 self.last_tokens = None 

4633 

4634 def generate_tokens(self, text): 

4635 """A stand-in for tokenize.generate_tokens().""" 

4636 if text != self.last_text: 

4637 string_io = io.StringIO(text) 

4638 self.last_tokens = list( 

4639 tokenize.generate_tokens(string_io.readline) 

4640 ) 

4641 self.last_text = text 

4642 return self.last_tokens 

4643 

4644 

4645_cached_tokenizer = CachedTokenizer() 

4646generate_tokens = _cached_tokenizer.generate_tokens 

4647 

4648 

4649if __name__ == '__main__': 

4650 sys.exit(main())