Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/black/linegen.py: 10%

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

941 statements  

1""" 

2Generating lines of code. 

3""" 

4 

5import re 

6import sys 

7from collections.abc import Collection, Iterable, Iterator 

8from dataclasses import replace 

9from enum import Enum, auto 

10from functools import partial, wraps 

11from typing import Union, cast 

12 

13from black.brackets import ( 

14 COMMA_PRIORITY, 

15 COMPARATOR_PRIORITY, 

16 DOT_PRIORITY, 

17 STRING_PRIORITY, 

18 get_leaves_inside_matching_brackets, 

19 max_delimiter_priority_in_atom, 

20) 

21from black.comments import ( 

22 FMT_OFF, 

23 FMT_ON, 

24 contains_fmt_directive, 

25 generate_comments, 

26 list_comments, 

27) 

28from black.lines import ( 

29 Line, 

30 RHSResult, 

31 append_leaves, 

32 can_be_split, 

33 can_omit_invisible_parens, 

34 is_line_short_enough, 

35 line_to_string, 

36) 

37from black.mode import Feature, Mode, Preview 

38from black.nodes import ( 

39 ASSIGNMENTS, 

40 BRACKETS, 

41 CLOSING_BRACKETS, 

42 OPENING_BRACKETS, 

43 STANDALONE_COMMENT, 

44 STATEMENT, 

45 WHITESPACE, 

46 Visitor, 

47 ensure_visible, 

48 fstring_tstring_to_string, 

49 get_annotation_type, 

50 has_sibling_with_type, 

51 is_arith_like, 

52 is_async_stmt_or_funcdef, 

53 is_atom_with_invisible_parens, 

54 is_docstring, 

55 is_empty_tuple, 

56 is_generator, 

57 is_lpar_token, 

58 is_multiline_string, 

59 is_name_token, 

60 is_one_sequence_between, 

61 is_one_tuple, 

62 is_parent_function_or_class, 

63 is_part_of_annotation, 

64 is_rpar_token, 

65 is_stub_body, 

66 is_stub_suite, 

67 is_tuple, 

68 is_tuple_containing_star, 

69 is_tuple_containing_walrus, 

70 is_type_ignore_comment, 

71 is_type_ignore_comment_string, 

72 is_vararg, 

73 is_walrus_assignment, 

74 is_yield, 

75 syms, 

76 wrap_in_parentheses, 

77) 

78from black.numerics import normalize_numeric_literal 

79from black.strings import ( 

80 fix_multiline_docstring, 

81 get_string_prefix, 

82 normalize_string_prefix, 

83 normalize_string_quotes, 

84 normalize_unicode_escape_sequences, 

85 str_width, 

86) 

87from black.trans import ( 

88 CannotTransform, 

89 StringMerger, 

90 StringParenStripper, 

91 StringParenWrapper, 

92 StringSplitter, 

93 Transformer, 

94 hug_power_op, 

95) 

96from blib2to3.pgen2 import token 

97from blib2to3.pytree import Leaf, Node 

98 

99# types 

100LeafID = int 

101LN = Union[Leaf, Node] 

102 

103 

104class CannotSplit(CannotTransform): 

105 """A readable split that fits the allotted line length is impossible.""" 

106 

107 

108# This isn't a dataclass because @dataclass + Generic breaks mypyc. 

109# See also https://github.com/mypyc/mypyc/issues/827. 

110class LineGenerator(Visitor[Line]): 

111 """Generates reformatted Line objects. Empty lines are not emitted. 

112 

113 Note: destroys the tree it's visiting by mutating prefixes of its leaves 

114 in ways that will no longer stringify to valid Python code on the tree. 

115 """ 

116 

117 def __init__(self, mode: Mode, features: Collection[Feature]) -> None: 

118 self.mode = mode 

119 self.features = features 

120 self.current_line: Line 

121 self.__post_init__() 

122 

123 def line(self, indent: int = 0) -> Iterator[Line]: 

124 """Generate a line. 

125 

126 If the line is empty, only emit if it makes sense. 

127 If the line is too long, split it first and then generate. 

128 

129 If any lines were generated, set up a new current_line. 

130 """ 

131 if not self.current_line: 

132 self.current_line.depth += indent 

133 return # Line is empty, don't emit. Creating a new one unnecessary. 

134 

135 if len(self.current_line.leaves) == 1 and is_async_stmt_or_funcdef( 

136 self.current_line.leaves[0] 

137 ): 

138 # Special case for async def/for/with statements. `visit_async_stmt` 

139 # adds an `ASYNC` leaf then visits the child def/for/with statement 

140 # nodes. Line yields from those nodes shouldn't treat the former 

141 # `ASYNC` leaf as a complete line. 

142 return 

143 

144 complete_line = self.current_line 

145 self.current_line = Line(mode=self.mode, depth=complete_line.depth + indent) 

146 yield complete_line 

147 

148 def visit_default(self, node: LN) -> Iterator[Line]: 

149 """Default `visit_*()` implementation. Recurses to children of `node`.""" 

150 if isinstance(node, Leaf): 

151 any_open_brackets = self.current_line.bracket_tracker.any_open_brackets() 

152 for comment in generate_comments(node, mode=self.mode): 

153 if any_open_brackets: 

154 # any comment within brackets is subject to splitting 

155 self.current_line.append(comment) 

156 elif comment.type == token.COMMENT: 

157 # regular trailing comment 

158 self.current_line.append(comment) 

159 yield from self.line() 

160 

161 else: 

162 # regular standalone comment 

163 yield from self.line() 

164 

165 self.current_line.append(comment) 

166 yield from self.line() 

167 

168 if any_open_brackets: 

169 node.prefix = "" 

170 if node.type not in WHITESPACE: 

171 self.current_line.append(node) 

172 yield from super().visit_default(node) 

173 

174 def visit_test(self, node: Node) -> Iterator[Line]: 

175 """Visit an `x if y else z` test""" 

176 

177 already_parenthesized = ( 

178 node.prev_sibling and node.prev_sibling.type == token.LPAR 

179 ) 

180 

181 if not already_parenthesized: 

182 # Similar to logic in wrap_in_parentheses 

183 lpar = Leaf(token.LPAR, "") 

184 rpar = Leaf(token.RPAR, "") 

185 prefix = node.prefix 

186 node.prefix = "" 

187 lpar.prefix = prefix 

188 node.insert_child(0, lpar) 

189 node.append_child(rpar) 

190 

191 yield from self.visit_default(node) 

192 

193 def visit_INDENT(self, node: Leaf) -> Iterator[Line]: 

194 """Increase indentation level, maybe yield a line.""" 

195 # In blib2to3 INDENT never holds comments. 

196 yield from self.line(+1) 

197 yield from self.visit_default(node) 

198 

199 def visit_DEDENT(self, node: Leaf) -> Iterator[Line]: 

200 """Decrease indentation level, maybe yield a line.""" 

201 # The current line might still wait for trailing comments. At DEDENT time 

202 # there won't be any (they would be prefixes on the preceding NEWLINE). 

203 # Emit the line then. 

204 yield from self.line() 

205 

206 # While DEDENT has no value, its prefix may contain standalone comments 

207 # that belong to the current indentation level. Get 'em. 

208 yield from self.visit_default(node) 

209 

210 # Finally, emit the dedent. 

211 yield from self.line(-1) 

212 

213 def visit_stmt( 

214 self, node: Node, keywords: set[str], parens: set[str] 

215 ) -> Iterator[Line]: 

216 """Visit a statement. 

217 

218 This implementation is shared for `if`, `while`, `for`, `try`, `except`, 

219 `def`, `with`, `class`, `assert`, and assignments. 

220 

221 The relevant Python language `keywords` for a given statement 

222 appear as NAME leaves within it. This method puts those on a 

223 separate line. 

224 

225 `parens` holds a set of string leaf values immediately after which 

226 invisible parens should be put. 

227 """ 

228 normalize_invisible_parens( 

229 node, parens_after=parens, mode=self.mode, features=self.features 

230 ) 

231 for child in node.children: 

232 if is_name_token(child) and child.value in keywords: 

233 yield from self.line() 

234 

235 yield from self.visit(child) 

236 

237 def visit_typeparams(self, node: Node) -> Iterator[Line]: 

238 yield from self.visit_default(node) 

239 node.children[0].prefix = "" 

240 

241 def visit_typevartuple(self, node: Node) -> Iterator[Line]: 

242 yield from self.visit_default(node) 

243 node.children[1].prefix = "" 

244 

245 def visit_paramspec(self, node: Node) -> Iterator[Line]: 

246 yield from self.visit_default(node) 

247 node.children[1].prefix = "" 

248 

249 def visit_dictsetmaker(self, node: Node) -> Iterator[Line]: 

250 if Preview.wrap_long_dict_values_in_parens in self.mode: 

251 for i, child in enumerate(node.children): 

252 if i == 0: 

253 continue 

254 if node.children[i - 1].type == token.COLON: 

255 if ( 

256 child.type == syms.atom 

257 and child.children[0].type in OPENING_BRACKETS 

258 and not is_walrus_assignment(child) 

259 ): 

260 maybe_make_parens_invisible_in_atom( 

261 child, 

262 parent=node, 

263 mode=self.mode, 

264 features=self.features, 

265 remove_brackets_around_comma=False, 

266 ) 

267 else: 

268 wrap_in_parentheses(node, child, visible=False, index=i) 

269 yield from self.visit_default(node) 

270 

271 def visit_funcdef(self, node: Node) -> Iterator[Line]: 

272 """Visit function definition.""" 

273 yield from self.line() 

274 

275 # Remove redundant brackets around return type annotation. 

276 is_return_annotation = False 

277 for child in node.children: 

278 if child.type == token.RARROW: 

279 is_return_annotation = True 

280 elif is_return_annotation: 

281 if child.type == syms.atom and child.children[0].type == token.LPAR: 

282 if maybe_make_parens_invisible_in_atom( 

283 child, 

284 parent=node, 

285 mode=self.mode, 

286 features=self.features, 

287 remove_brackets_around_comma=False, 

288 ): 

289 wrap_in_parentheses(node, child, visible=False) 

290 else: 

291 wrap_in_parentheses(node, child, visible=False) 

292 is_return_annotation = False 

293 

294 for child in node.children: 

295 yield from self.visit(child) 

296 

297 def visit_match_case(self, node: Node) -> Iterator[Line]: 

298 """Visit either a match or case statement.""" 

299 normalize_invisible_parens( 

300 node, parens_after=set(), mode=self.mode, features=self.features 

301 ) 

302 

303 yield from self.line() 

304 for child in node.children: 

305 yield from self.visit(child) 

306 

307 def visit_suite(self, node: Node) -> Iterator[Line]: 

308 """Visit a suite.""" 

309 if is_stub_suite(node): 

310 yield from self.visit(node.children[2]) 

311 else: 

312 yield from self.visit_default(node) 

313 

314 def visit_simple_stmt(self, node: Node) -> Iterator[Line]: 

315 """Visit a statement without nested statements.""" 

316 prev_type: int | None = None 

317 for i, child in enumerate(node.children): 

318 if (prev_type is None or prev_type == token.SEMI) and is_arith_like(child): 

319 wrap_in_parentheses(node, child, visible=False, index=i) 

320 prev_type = child.type 

321 

322 if node.parent and node.parent.type in STATEMENT: 

323 if is_parent_function_or_class(node) and is_stub_body(node): 

324 yield from self.visit_default(node) 

325 else: 

326 yield from self.line(+1) 

327 yield from self.visit_default(node) 

328 yield from self.line(-1) 

329 

330 else: 

331 if node.parent and is_stub_suite(node.parent): 

332 node.prefix = "" 

333 yield from self.visit_default(node) 

334 return 

335 yield from self.line() 

336 yield from self.visit_default(node) 

337 

338 def visit_async_stmt(self, node: Node) -> Iterator[Line]: 

339 """Visit `async def`, `async for`, `async with`.""" 

340 yield from self.line() 

341 

342 children = iter(node.children) 

343 for child in children: 

344 yield from self.visit(child) 

345 

346 if child.type == token.ASYNC or child.type == STANDALONE_COMMENT: 

347 # STANDALONE_COMMENT happens when `# fmt: skip` is applied on the async 

348 # line. 

349 break 

350 

351 internal_stmt = next(children) 

352 yield from self.visit(internal_stmt) 

353 

354 def visit_decorators(self, node: Node) -> Iterator[Line]: 

355 """Visit decorators.""" 

356 for child in node.children: 

357 yield from self.line() 

358 yield from self.visit(child) 

359 

360 def visit_power(self, node: Node) -> Iterator[Line]: 

361 for idx, leaf in enumerate(node.children[:-1]): 

362 next_leaf = node.children[idx + 1] 

363 

364 if not isinstance(leaf, Leaf): 

365 continue 

366 

367 value = leaf.value.lower() 

368 if ( 

369 leaf.type == token.NUMBER 

370 and next_leaf.type == syms.trailer 

371 # Ensure that we are in an attribute trailer 

372 and next_leaf.children[0].type == token.DOT 

373 # It shouldn't wrap hexadecimal, binary and octal literals 

374 and not value.startswith(("0x", "0b", "0o")) 

375 # It shouldn't wrap complex literals 

376 and "j" not in value 

377 ): 

378 wrap_in_parentheses(node, leaf) 

379 

380 if Preview.remove_redundant_generator_parentheses in self.mode: 

381 for child in node.children: 

382 if ( 

383 child.type == syms.trailer 

384 and len(child.children) == 3 

385 and is_lpar_token(child.children[0]) 

386 and is_generator(child.children[1]) 

387 and is_rpar_token(child.children[2]) 

388 ): 

389 maybe_make_parens_invisible_in_atom( 

390 child.children[1], 

391 parent=child, 

392 mode=self.mode, 

393 features=self.features, 

394 remove_generator_parens=True, 

395 ) 

396 

397 remove_await_parens(node, mode=self.mode, features=self.features) 

398 

399 yield from self.visit_default(node) 

400 

401 def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]: 

402 """Remove a semicolon and put the other statement on a separate line.""" 

403 yield from self.line() 

404 

405 def visit_ENDMARKER(self, leaf: Leaf) -> Iterator[Line]: 

406 """End of file. Process outstanding comments and end with a newline.""" 

407 yield from self.visit_default(leaf) 

408 yield from self.line() 

409 

410 def visit_STANDALONE_COMMENT(self, leaf: Leaf) -> Iterator[Line]: 

411 any_open_brackets = self.current_line.bracket_tracker.any_open_brackets() 

412 if not any_open_brackets: 

413 yield from self.line() 

414 # STANDALONE_COMMENT nodes created by our special handling in 

415 # normalize_fmt_off for comment-only blocks have fmt:off as the first 

416 # line and fmt:on as the last line (each directive on its own line, 

417 # not embedded in other text). These should be appended directly 

418 # without calling visit_default, which would process their prefix and 

419 # lose indentation. Normal STANDALONE_COMMENT nodes go through 

420 # visit_default. 

421 value = leaf.value 

422 lines = value.splitlines() 

423 is_fmt_off_block = ( 

424 len(lines) >= 2 

425 and contains_fmt_directive(lines[0], FMT_OFF) 

426 and contains_fmt_directive(lines[-1], FMT_ON) 

427 ) 

428 if is_fmt_off_block: 

429 is_after_invisible_lpar = ( 

430 leaf.fmt_pass_converted_first_leaf is None 

431 and len(self.current_line.leaves) == 1 

432 and self.current_line.leaves[0].type == token.LPAR 

433 and not self.current_line.leaves[0].value 

434 ) 

435 # This is a fmt:off/on block from normalize_fmt_off - we still need 

436 # to process any prefix comments (like markdown comments) but append 

437 # the fmt block itself directly to preserve its formatting 

438 

439 # Only process prefix comments if there actually is a prefix with comments 

440 if leaf.prefix and any( 

441 line.strip().startswith("#") 

442 and not contains_fmt_directive(line.strip()) 

443 for line in leaf.prefix.split("\n") 

444 ): 

445 for comment in generate_comments(leaf, mode=self.mode): 

446 yield from self.line() 

447 self.current_line.append(comment) 

448 yield from self.line() 

449 # Clear the prefix since we've processed it as comments above 

450 leaf.prefix = "" 

451 

452 self.current_line.append(leaf) 

453 if not any_open_brackets or is_after_invisible_lpar: 

454 yield from self.line() 

455 else: 

456 # Normal standalone comment - process through visit_default 

457 yield from self.visit_default(leaf) 

458 

459 def visit_factor(self, node: Node) -> Iterator[Line]: 

460 """Force parentheses between a unary op and a binary power: 

461 

462 -2 ** 8 -> -(2 ** 8) 

463 """ 

464 _operator, operand = node.children 

465 if ( 

466 operand.type == syms.power 

467 and len(operand.children) == 3 

468 and operand.children[1].type == token.DOUBLESTAR 

469 ): 

470 lpar = Leaf(token.LPAR, "(") 

471 rpar = Leaf(token.RPAR, ")") 

472 index = operand.remove() or 0 

473 node.insert_child(index, Node(syms.atom, [lpar, operand, rpar])) 

474 yield from self.visit_default(node) 

475 

476 def visit_tname(self, node: Node) -> Iterator[Line]: 

477 """ 

478 Add potential parentheses around types in function parameter lists to be made 

479 into real parentheses in case the type hint is too long to fit on a line 

480 Examples: 

481 def foo(a: int, b: float = 7): ... 

482 

483 -> 

484 

485 def foo(a: (int), b: (float) = 7): ... 

486 """ 

487 if len(node.children) == 3 and maybe_make_parens_invisible_in_atom( 

488 node.children[2], parent=node, mode=self.mode, features=self.features 

489 ): 

490 wrap_in_parentheses(node, node.children[2], visible=False) 

491 

492 yield from self.visit_default(node) 

493 

494 def visit_STRING(self, leaf: Leaf) -> Iterator[Line]: 

495 normalize_unicode_escape_sequences(leaf) 

496 

497 if is_docstring(leaf) and not re.search(r"\\\s*\n", leaf.value): 

498 # We're ignoring docstrings with backslash newline escapes because changing 

499 # indentation of those changes the AST representation of the code. 

500 if self.mode.string_normalization: 

501 docstring = normalize_string_prefix(leaf.value) 

502 # We handle string normalization at the end of this method, but since 

503 # what we do right now acts differently depending on quote style (ex. 

504 # see padding logic below), there's a possibility for unstable 

505 # formatting. To avoid a situation where this function formats a 

506 # docstring differently on the second pass, normalize it early. 

507 docstring = normalize_string_quotes(docstring) 

508 else: 

509 docstring = leaf.value 

510 prefix = get_string_prefix(docstring) 

511 docstring = docstring[len(prefix) :] # Remove the prefix 

512 quote_char = docstring[0] 

513 # A natural way to remove the outer quotes is to do: 

514 # docstring = docstring.strip(quote_char) 

515 # but that breaks on """""x""" (which is '""x'). 

516 # So we actually need to remove the first character and the next two 

517 # characters but only if they are the same as the first. 

518 quote_len = 1 if docstring[1] != quote_char else 3 

519 docstring = docstring[quote_len:-quote_len] 

520 docstring_started_empty = not docstring 

521 indent = " " * 4 * self.current_line.depth 

522 

523 if is_multiline_string(leaf): 

524 docstring = fix_multiline_docstring(docstring, indent) 

525 else: 

526 docstring = docstring.strip() 

527 

528 has_trailing_backslash = False 

529 if docstring: 

530 # Add some padding if the docstring starts / ends with a quote mark. 

531 if docstring[0] == quote_char: 

532 docstring = " " + docstring 

533 if docstring[-1] == quote_char: 

534 docstring += " " 

535 if docstring[-1] == "\\": 

536 backslash_count = len(docstring) - len(docstring.rstrip("\\")) 

537 if backslash_count % 2: 

538 # Odd number of tailing backslashes, add some padding to 

539 # avoid escaping the closing string quote. 

540 docstring += " " 

541 has_trailing_backslash = True 

542 elif not docstring_started_empty: 

543 docstring = " " 

544 

545 # We could enforce triple quotes at this point. 

546 quote = quote_char * quote_len 

547 

548 # It's invalid to put closing single-character quotes on a new line. 

549 if quote_len == 3: 

550 # We need to find the length of the last line of the docstring 

551 # to find if we can add the closing quotes to the line without 

552 # exceeding the maximum line length. 

553 # If docstring is one line, we don't put the closing quotes on a 

554 # separate line because it looks ugly (#3320). 

555 lines = docstring.splitlines() 

556 last_line_length = len(lines[-1]) if docstring else 0 

557 

558 # If adding closing quotes would cause the last line to exceed 

559 # the maximum line length, and the closing quote is not 

560 # prefixed by a newline then put a line break before 

561 # the closing quotes 

562 if ( 

563 len(lines) > 1 

564 and last_line_length + quote_len > self.mode.line_length 

565 and len(indent) + quote_len <= self.mode.line_length 

566 and not has_trailing_backslash 

567 ): 

568 if leaf.value[-1 - quote_len] == "\n": 

569 leaf.value = prefix + quote + docstring + quote 

570 else: 

571 leaf.value = prefix + quote + docstring + "\n" + indent + quote 

572 else: 

573 leaf.value = prefix + quote + docstring + quote 

574 else: 

575 leaf.value = prefix + quote + docstring + quote 

576 

577 if self.mode.string_normalization and leaf.type == token.STRING: 

578 leaf.value = normalize_string_prefix(leaf.value) 

579 leaf.value = normalize_string_quotes(leaf.value) 

580 yield from self.visit_default(leaf) 

581 

582 def visit_NUMBER(self, leaf: Leaf) -> Iterator[Line]: 

583 normalize_numeric_literal(leaf) 

584 yield from self.visit_default(leaf) 

585 

586 def visit_atom(self, node: Node) -> Iterator[Line]: 

587 """Visit any atom""" 

588 if ( 

589 Preview.remove_redundant_generator_parentheses in self.mode 

590 and _has_redundant_generator_parentheses(node) 

591 ): 

592 maybe_make_parens_invisible_in_atom( 

593 node, 

594 parent=node.parent or node, 

595 mode=self.mode, 

596 features=self.features, 

597 ) 

598 

599 if len(node.children) == 3: 

600 first = node.children[0] 

601 last = node.children[-1] 

602 if (first.type == token.LSQB and last.type == token.RSQB) or ( 

603 first.type == token.LBRACE and last.type == token.RBRACE 

604 ): 

605 # Lists or sets of one item 

606 maybe_make_parens_invisible_in_atom( 

607 node.children[1], 

608 parent=node, 

609 mode=self.mode, 

610 features=self.features, 

611 ) 

612 

613 yield from self.visit_default(node) 

614 

615 def visit_fstring(self, node: Node) -> Iterator[Line]: 

616 # If the fstring was converted to a STANDALONE_COMMENT by 

617 # normalize_fmt_off (e.g. it was inside a # fmt: off block), 

618 # skip the fstring-to-string conversion and just visit normally. 

619 if any(child.type == STANDALONE_COMMENT for child in node.children): 

620 yield from self.visit_default(node) 

621 return 

622 # currently we don't want to format and split f-strings at all. 

623 string_leaf = fstring_tstring_to_string(node) 

624 node.replace(string_leaf) 

625 if "\\" in string_leaf.value and any( 

626 "\\" in str(child) 

627 for child in node.children 

628 if child.type == syms.fstring_replacement_field 

629 ): 

630 # string normalization doesn't account for nested quotes, 

631 # causing breakages. skip normalization when nested quotes exist 

632 yield from self.visit_default(string_leaf) 

633 return 

634 yield from self.visit_STRING(string_leaf) 

635 

636 def visit_tstring(self, node: Node) -> Iterator[Line]: 

637 # If the tstring was converted to a STANDALONE_COMMENT by 

638 # normalize_fmt_off, skip the conversion and just visit normally. 

639 if any(child.type == STANDALONE_COMMENT for child in node.children): 

640 yield from self.visit_default(node) 

641 return 

642 # currently we don't want to format and split t-strings at all. 

643 string_leaf = fstring_tstring_to_string(node) 

644 node.replace(string_leaf) 

645 if "\\" in string_leaf.value and any( 

646 "\\" in str(child) 

647 for child in node.children 

648 if child.type == syms.tstring_replacement_field 

649 ): 

650 # string normalization doesn't account for nested quotes, 

651 # causing breakages. skip normalization when nested quotes exist 

652 yield from self.visit_default(string_leaf) 

653 return 

654 yield from self.visit_STRING(string_leaf) 

655 

656 # TODO: Uncomment Implementation to format f-string children 

657 # fstring_start = node.children[0] 

658 # fstring_end = node.children[-1] 

659 # assert isinstance(fstring_start, Leaf) 

660 # assert isinstance(fstring_end, Leaf) 

661 

662 # quote_char = fstring_end.value[0] 

663 # quote_idx = fstring_start.value.index(quote_char) 

664 # prefix, quote = ( 

665 # fstring_start.value[:quote_idx], 

666 # fstring_start.value[quote_idx:] 

667 # ) 

668 

669 # if not is_docstring(node, self.mode): 

670 # prefix = normalize_string_prefix(prefix) 

671 

672 # assert quote == fstring_end.value 

673 

674 # is_raw_fstring = "r" in prefix or "R" in prefix 

675 # middles = [ 

676 # leaf 

677 # for leaf in node.leaves() 

678 # if leaf.type == token.FSTRING_MIDDLE 

679 # ] 

680 

681 # if self.mode.string_normalization: 

682 # middles, quote = normalize_fstring_quotes(quote, middles, is_raw_fstring) 

683 

684 # fstring_start.value = prefix + quote 

685 # fstring_end.value = quote 

686 

687 # yield from self.visit_default(node) 

688 

689 def visit_comp_for(self, node: Node) -> Iterator[Line]: 

690 if Preview.wrap_comprehension_in in self.mode: 

691 normalize_invisible_parens( 

692 node, parens_after={"in"}, mode=self.mode, features=self.features 

693 ) 

694 yield from self.visit_default(node) 

695 

696 def visit_old_comp_for(self, node: Node) -> Iterator[Line]: 

697 yield from self.visit_comp_for(node) 

698 

699 def __post_init__(self) -> None: 

700 """You are in a twisty little maze of passages.""" 

701 self.current_line = Line(mode=self.mode) 

702 

703 v = self.visit_stmt 

704 Ø: set[str] = set() 

705 self.visit_assert_stmt = partial(v, keywords={"assert"}, parens={"assert", ","}) 

706 self.visit_if_stmt = partial( 

707 v, keywords={"if", "else", "elif"}, parens={"if", "elif"} 

708 ) 

709 self.visit_while_stmt = partial(v, keywords={"while", "else"}, parens={"while"}) 

710 self.visit_for_stmt = partial(v, keywords={"for", "else"}, parens={"for", "in"}) 

711 self.visit_try_stmt = partial( 

712 v, keywords={"try", "except", "else", "finally"}, parens=Ø 

713 ) 

714 self.visit_except_clause = partial(v, keywords={"except"}, parens={"except"}) 

715 self.visit_with_stmt = partial(v, keywords={"with"}, parens={"with"}) 

716 self.visit_classdef = partial(v, keywords={"class"}, parens=Ø) 

717 

718 self.visit_expr_stmt = partial(v, keywords=Ø, parens=ASSIGNMENTS) 

719 self.visit_return_stmt = partial(v, keywords={"return"}, parens={"return"}) 

720 self.visit_yield_expr = partial( 

721 v, 

722 keywords=Ø, 

723 parens=( 

724 {"yield"} if Preview.parenthesize_tuple_in_yield in self.mode else Ø 

725 ), 

726 ) 

727 self.visit_import_from = partial(v, keywords=Ø, parens={"import"}) 

728 self.visit_del_stmt = partial(v, keywords=Ø, parens={"del"}) 

729 self.visit_async_funcdef = self.visit_async_stmt 

730 self.visit_decorated = self.visit_decorators 

731 

732 # PEP 634 

733 self.visit_match_stmt = self.visit_match_case 

734 self.visit_case_block = self.visit_match_case 

735 self.visit_guard = partial(v, keywords=Ø, parens={"if"}) 

736 

737 

738# Remove when `simplify_power_operator_hugging` becomes stable. 

739def _hugging_power_ops_line_to_string( 

740 line: Line, 

741 features: Collection[Feature], 

742 mode: Mode, 

743) -> str | None: 

744 try: 

745 return line_to_string(next(hug_power_op(line, features, mode))) 

746 except CannotTransform: 

747 return None 

748 

749 

750def transform_line( 

751 line: Line, mode: Mode, features: Collection[Feature] = () 

752) -> Iterator[Line]: 

753 """Transform a `line`, potentially splitting it into many lines. 

754 

755 They should fit in the allotted `line_length` but might not be able to. 

756 

757 `features` are syntactical features that may be used in the output. 

758 """ 

759 if line.is_comment: 

760 yield line 

761 return 

762 

763 line_str = line_to_string(line) 

764 

765 if Preview.simplify_power_operator_hugging in mode: 

766 line_str_hugging_power_ops = line_str 

767 else: 

768 # We need the line string when power operators are hugging to determine if we 

769 # should split the line. Default to line_str, if no power operator are present 

770 # on the line. 

771 line_str_hugging_power_ops = ( 

772 _hugging_power_ops_line_to_string(line, features, mode) or line_str 

773 ) 

774 

775 ll = mode.line_length 

776 sn = mode.string_normalization 

777 string_merge = StringMerger(ll, sn) 

778 string_paren_strip = StringParenStripper(ll, sn) 

779 string_split = StringSplitter(ll, sn) 

780 string_paren_wrap = StringParenWrapper(ll, sn) 

781 

782 transformers: list[Transformer] 

783 if ( 

784 not line.contains_uncollapsable_type_comments() 

785 and not line.should_split_rhs 

786 and not line.magic_trailing_comma 

787 and ( 

788 is_line_short_enough(line, mode=mode, line_str=line_str_hugging_power_ops) 

789 or line.contains_unsplittable_type_ignore() 

790 ) 

791 and not (line.inside_brackets and line.contains_standalone_comments()) 

792 and not line.contains_implicit_multiline_string_with_comments() 

793 ): 

794 # Only apply basic string preprocessing, since lines shouldn't be split here. 

795 if Preview.string_processing in mode: 

796 transformers = [string_merge, string_paren_strip] 

797 else: 

798 transformers = [] 

799 elif line.is_def and not should_split_funcdef_with_rhs(line, mode): 

800 transformers = [left_hand_split] 

801 else: 

802 

803 def _rhs( 

804 self: object, line: Line, features: Collection[Feature], mode: Mode 

805 ) -> Iterator[Line]: 

806 """Wraps calls to `right_hand_split`. 

807 

808 The calls increasingly `omit` right-hand trailers (bracket pairs with 

809 content), meaning the trailers get glued together to split on another 

810 bracket pair instead. 

811 """ 

812 for omit in generate_trailers_to_omit(line, mode.line_length): 

813 lines = list(right_hand_split(line, mode, features, omit=omit)) 

814 # Note: this check is only able to figure out if the first line of the 

815 # *current* transformation fits in the line length. This is true only 

816 # for simple cases. All others require running more transforms via 

817 # `transform_line()`. This check doesn't know if those would succeed. 

818 if is_line_short_enough(lines[0], mode=mode) or ( 

819 omit and _over_length_only_due_to_subscript_comment(lines[0], mode) 

820 ): 

821 yield from lines 

822 return 

823 

824 # All splits failed, best effort split with no omits. 

825 # This mostly happens to multiline strings that are by definition 

826 # reported as not fitting a single line, as well as lines that contain 

827 # trailing commas (those have to be exploded). 

828 yield from right_hand_split(line, mode, features=features) 

829 

830 # HACK: nested functions (like _rhs) compiled by mypyc don't retain their 

831 # __name__ attribute which is needed in `run_transformer` further down. 

832 # Unfortunately a nested class breaks mypyc too. So a class must be created 

833 # via type ... https://github.com/mypyc/mypyc/issues/884 

834 rhs = type("rhs", (), {"__call__": _rhs})() 

835 

836 if Preview.string_processing in mode: 

837 if line.inside_brackets: 

838 transformers = [ 

839 string_merge, 

840 string_paren_strip, 

841 string_split, 

842 delimiter_split, 

843 standalone_comment_split, 

844 string_paren_wrap, 

845 rhs, 

846 ] 

847 else: 

848 transformers = [ 

849 string_merge, 

850 string_paren_strip, 

851 string_split, 

852 string_paren_wrap, 

853 rhs, 

854 ] 

855 else: 

856 if line.inside_brackets: 

857 transformers = [delimiter_split, standalone_comment_split, rhs] 

858 else: 

859 transformers = [rhs] 

860 

861 if Preview.simplify_power_operator_hugging not in mode: 

862 # It's always safe to attempt hugging of power operations and pretty much every 

863 # line could match. 

864 transformers.append(hug_power_op) 

865 

866 for transform in transformers: 

867 # We are accumulating lines in `result` because we might want to abort 

868 # mission and return the original line in the end, or attempt a different 

869 # split altogether. 

870 try: 

871 result = run_transformer(line, transform, mode, features, line_str=line_str) 

872 except CannotTransform: 

873 continue 

874 else: 

875 yield from result 

876 break 

877 

878 else: 

879 # A leftover standalone comment would render inline and break the 

880 # second pass with a parse error (#4296). Force a split so the 

881 # output is at least valid Python. 

882 if line.contains_standalone_comments(): 

883 yield from _force_standalone_comment_split(line) 

884 else: 

885 yield line 

886 

887 

888def should_split_funcdef_with_rhs(line: Line, mode: Mode) -> bool: 

889 """If a funcdef has a magic trailing comma in the return type, then we should first 

890 split the line with rhs to respect the comma. 

891 """ 

892 return_type_leaves: list[Leaf] = [] 

893 in_return_type = False 

894 

895 for leaf in line.leaves: 

896 if leaf.type == token.COLON: 

897 in_return_type = False 

898 if in_return_type: 

899 return_type_leaves.append(leaf) 

900 if leaf.type == token.RARROW: 

901 in_return_type = True 

902 

903 # using `bracket_split_build_line` will mess with whitespace, so we duplicate a 

904 # couple lines from it. 

905 result = Line(mode=line.mode, depth=line.depth) 

906 leaves_to_track = get_leaves_inside_matching_brackets(return_type_leaves) 

907 for leaf in return_type_leaves: 

908 result.append( 

909 leaf, 

910 preformatted=True, 

911 track_bracket=id(leaf) in leaves_to_track, 

912 ) 

913 

914 # we could also return true if the line is too long, and the return type is longer 

915 # than the param list. Or if `should_split_rhs` returns True. 

916 return result.magic_trailing_comma is not None 

917 

918 

919class _BracketSplitComponent(Enum): 

920 head = auto() 

921 body = auto() 

922 tail = auto() 

923 

924 

925def left_hand_split( 

926 line: Line, _features: Collection[Feature], mode: Mode 

927) -> Iterator[Line]: 

928 """Split line into many lines, starting with the first matching bracket pair. 

929 

930 Note: this usually looks weird, only use this for function definitions. 

931 Prefer RHS otherwise. This is why this function is not symmetrical with 

932 :func:`right_hand_split` which also handles optional parentheses. 

933 """ 

934 for leaf_type in [token.LPAR, token.LSQB]: 

935 tail_leaves: list[Leaf] = [] 

936 body_leaves: list[Leaf] = [] 

937 head_leaves: list[Leaf] = [] 

938 current_leaves = head_leaves 

939 matching_bracket: Leaf | None = None 

940 depth = 0 

941 for index, leaf in enumerate(line.leaves): 

942 if index == 2 and leaf.type == token.LSQB: 

943 # A [ at index 2 means this is a type param, so start 

944 # tracking the depth 

945 depth += 1 

946 elif depth > 0: 

947 if leaf.type == token.LSQB: 

948 depth += 1 

949 elif leaf.type == token.RSQB: 

950 depth -= 1 

951 if ( 

952 current_leaves is body_leaves 

953 and leaf.type in CLOSING_BRACKETS 

954 and leaf.opening_bracket is matching_bracket 

955 and isinstance(matching_bracket, Leaf) 

956 # If the code is still on LPAR and we are inside a type 

957 # param, ignore the match since this is searching 

958 # for the function arguments 

959 and not (leaf_type == token.LPAR and depth > 0) 

960 ): 

961 ensure_visible(leaf) 

962 ensure_visible(matching_bracket) 

963 current_leaves = tail_leaves if body_leaves else head_leaves 

964 current_leaves.append(leaf) 

965 if current_leaves is head_leaves: 

966 if leaf.type == leaf_type and ( 

967 not (leaf_type == token.LPAR and depth > 0) 

968 ): 

969 matching_bracket = leaf 

970 current_leaves = body_leaves 

971 if matching_bracket and tail_leaves: 

972 break 

973 if not matching_bracket or not tail_leaves: 

974 raise CannotSplit("No brackets found") 

975 

976 head = bracket_split_build_line( 

977 head_leaves, line, matching_bracket, component=_BracketSplitComponent.head 

978 ) 

979 body = bracket_split_build_line( 

980 body_leaves, line, matching_bracket, component=_BracketSplitComponent.body 

981 ) 

982 tail = bracket_split_build_line( 

983 tail_leaves, line, matching_bracket, component=_BracketSplitComponent.tail 

984 ) 

985 bracket_split_succeeded_or_raise(head, body, tail) 

986 for result in (head, body, tail): 

987 if result: 

988 yield result 

989 

990 

991def right_hand_split( 

992 line: Line, 

993 mode: Mode, 

994 features: Collection[Feature] = (), 

995 omit: Collection[LeafID] = (), 

996) -> Iterator[Line]: 

997 """Split line into many lines, starting with the last matching bracket pair. 

998 

999 If the split was by optional parentheses, attempt splitting without them, too. 

1000 `omit` is a collection of closing bracket IDs that shouldn't be considered for 

1001 this split. 

1002 

1003 Note: running this function modifies `bracket_depth` on the leaves of `line`. 

1004 """ 

1005 rhs_result = _first_right_hand_split(line, omit=omit) 

1006 yield from _maybe_split_omitting_optional_parens( 

1007 rhs_result, line, mode, features=features, omit=omit 

1008 ) 

1009 

1010 

1011def _first_right_hand_split( 

1012 line: Line, 

1013 omit: Collection[LeafID] = (), 

1014) -> RHSResult: 

1015 """Split the line into head, body, tail starting with the last bracket pair. 

1016 

1017 Note: this function should not have side effects. It's relied upon by 

1018 _maybe_split_omitting_optional_parens to get an opinion whether to prefer 

1019 splitting on the right side of an assignment statement. 

1020 """ 

1021 tail_leaves: list[Leaf] = [] 

1022 body_leaves: list[Leaf] = [] 

1023 head_leaves: list[Leaf] = [] 

1024 current_leaves = tail_leaves 

1025 opening_bracket: Leaf | None = None 

1026 closing_bracket: Leaf | None = None 

1027 for leaf in reversed(line.leaves): 

1028 if current_leaves is body_leaves: 

1029 if leaf is opening_bracket: 

1030 current_leaves = head_leaves if body_leaves else tail_leaves 

1031 current_leaves.append(leaf) 

1032 if current_leaves is tail_leaves: 

1033 if leaf.type in CLOSING_BRACKETS and id(leaf) not in omit: 

1034 opening_bracket = leaf.opening_bracket 

1035 closing_bracket = leaf 

1036 current_leaves = body_leaves 

1037 if not (opening_bracket and closing_bracket and head_leaves): 

1038 # If there is no opening or closing_bracket that means the split failed and 

1039 # all content is in the tail. Otherwise, if `head_leaves` are empty, it means 

1040 # the matching `opening_bracket` wasn't available on `line` anymore. 

1041 raise CannotSplit("No brackets found") 

1042 

1043 tail_leaves.reverse() 

1044 body_leaves.reverse() 

1045 head_leaves.reverse() 

1046 

1047 body: Line | None = None 

1048 if ( 

1049 Preview.hug_parens_with_braces_and_square_brackets in line.mode 

1050 and tail_leaves[0].value 

1051 and tail_leaves[0].opening_bracket is head_leaves[-1] 

1052 ): 

1053 inner_body_leaves = list(body_leaves) 

1054 hugged_opening_leaves: list[Leaf] = [] 

1055 hugged_closing_leaves: list[Leaf] = [] 

1056 is_unpacking = body_leaves[0].type in [token.STAR, token.DOUBLESTAR] 

1057 unpacking_offset: int = 1 if is_unpacking else 0 

1058 while ( 

1059 len(inner_body_leaves) >= 2 + unpacking_offset 

1060 and inner_body_leaves[-1].type in CLOSING_BRACKETS 

1061 and inner_body_leaves[-1].opening_bracket 

1062 is inner_body_leaves[unpacking_offset] 

1063 ): 

1064 if unpacking_offset: 

1065 hugged_opening_leaves.append(inner_body_leaves.pop(0)) 

1066 unpacking_offset = 0 

1067 hugged_opening_leaves.append(inner_body_leaves.pop(0)) 

1068 hugged_closing_leaves.insert(0, inner_body_leaves.pop()) 

1069 

1070 if hugged_opening_leaves and inner_body_leaves: 

1071 inner_body = bracket_split_build_line( 

1072 inner_body_leaves, 

1073 line, 

1074 hugged_opening_leaves[-1], 

1075 component=_BracketSplitComponent.body, 

1076 ) 

1077 if ( 

1078 line.mode.magic_trailing_comma 

1079 and inner_body_leaves[-1].type == token.COMMA 

1080 ): 

1081 should_hug = True 

1082 else: 

1083 line_length = line.mode.line_length - sum( 

1084 len(str(leaf)) 

1085 for leaf in hugged_opening_leaves + hugged_closing_leaves 

1086 ) 

1087 if is_line_short_enough( 

1088 inner_body, mode=replace(line.mode, line_length=line_length) 

1089 ): 

1090 # Do not hug if it fits on a single line. 

1091 should_hug = False 

1092 else: 

1093 should_hug = True 

1094 if should_hug and ( 

1095 _hugging_merges_type_ignores(line, head_leaves, hugged_opening_leaves) 

1096 or _hugging_merges_type_ignores( 

1097 line, hugged_closing_leaves, tail_leaves 

1098 ) 

1099 ): 

1100 # Hugging joins these leaves onto one physical line, and their 

1101 # trailing comments come along. `type: ignore` is recorded per 

1102 # line by the AST, so two of them landing on the same line would 

1103 # drop one and make the output non-equivalent. 

1104 should_hug = False 

1105 if should_hug: 

1106 body_leaves = inner_body_leaves 

1107 head_leaves.extend(hugged_opening_leaves) 

1108 tail_leaves = hugged_closing_leaves + tail_leaves 

1109 body = inner_body # No need to re-calculate the body again later. 

1110 

1111 head = bracket_split_build_line( 

1112 head_leaves, line, opening_bracket, component=_BracketSplitComponent.head 

1113 ) 

1114 if body is None: 

1115 body = bracket_split_build_line( 

1116 body_leaves, line, opening_bracket, component=_BracketSplitComponent.body 

1117 ) 

1118 tail = bracket_split_build_line( 

1119 tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail 

1120 ) 

1121 bracket_split_succeeded_or_raise(head, body, tail) 

1122 return RHSResult(head, body, tail, opening_bracket, closing_bracket) 

1123 

1124 

1125def _maybe_split_omitting_optional_parens( 

1126 rhs: RHSResult, 

1127 line: Line, 

1128 mode: Mode, 

1129 features: Collection[Feature] = (), 

1130 omit: Collection[LeafID] = (), 

1131) -> Iterator[Line]: 

1132 if ( 

1133 Feature.FORCE_OPTIONAL_PARENTHESES not in features 

1134 # the opening bracket is an optional paren 

1135 and rhs.opening_bracket.type == token.LPAR 

1136 and not rhs.opening_bracket.value 

1137 # the closing bracket is an optional paren 

1138 and rhs.closing_bracket.type == token.RPAR 

1139 and not rhs.closing_bracket.value 

1140 # it's not an import (optional parens are the only thing we can split on 

1141 # in this case; attempting a split without them is a waste of time) 

1142 and not line.is_import 

1143 # and we can actually remove the parens 

1144 and can_omit_invisible_parens(rhs, mode.line_length, mode) 

1145 ): 

1146 omit = {id(rhs.closing_bracket), *omit} 

1147 try: 

1148 # The RHSResult Omitting Optional Parens. 

1149 rhs_oop = _first_right_hand_split(line, omit=omit) 

1150 if _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode): 

1151 yield from _maybe_split_omitting_optional_parens( 

1152 rhs_oop, line, mode, features=features, omit=omit 

1153 ) 

1154 return 

1155 

1156 except CannotSplit as e: 

1157 # For chained assignments we want to use the previous successful split 

1158 if line.is_chained_assignment: 

1159 pass 

1160 

1161 elif ( 

1162 not can_be_split(rhs.body) 

1163 and not is_line_short_enough(rhs.body, mode=mode) 

1164 and not ( 

1165 Preview.wrap_long_dict_values_in_parens in mode 

1166 and rhs.opening_bracket.parent 

1167 and rhs.opening_bracket.parent.parent 

1168 and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker 

1169 ) 

1170 and not ( 

1171 rhs.opening_bracket.parent 

1172 and rhs.opening_bracket.parent.parent 

1173 and rhs.opening_bracket.parent.parent.type == syms.case_block 

1174 ) 

1175 ): 

1176 raise CannotSplit( 

1177 "Splitting failed, body is still too long and can't be split." 

1178 ) from e 

1179 

1180 elif ( 

1181 rhs.head.contains_multiline_strings() 

1182 or rhs.tail.contains_multiline_strings() 

1183 ): 

1184 raise CannotSplit( 

1185 "The current optional pair of parentheses is bound to fail to" 

1186 " satisfy the splitting algorithm because the head or the tail" 

1187 " contains multiline strings which by definition never fit one" 

1188 " line." 

1189 ) from e 

1190 

1191 ensure_visible(rhs.opening_bracket) 

1192 ensure_visible(rhs.closing_bracket) 

1193 for result in (rhs.head, rhs.body, rhs.tail): 

1194 if result: 

1195 yield result 

1196 

1197 

1198def _prefer_split_rhs_oop_over_rhs( 

1199 rhs_oop: RHSResult, rhs: RHSResult, mode: Mode 

1200) -> bool: 

1201 """ 

1202 Returns whether we should prefer the result from a split omitting optional parens 

1203 (rhs_oop) over the original (rhs). 

1204 """ 

1205 # contains unsplittable type ignore 

1206 if ( 

1207 rhs_oop.head.contains_unsplittable_type_ignore() 

1208 or rhs_oop.body.contains_unsplittable_type_ignore() 

1209 or rhs_oop.tail.contains_unsplittable_type_ignore() 

1210 ): 

1211 return True 

1212 

1213 # Retain optional parens around dictionary values 

1214 if ( 

1215 Preview.wrap_long_dict_values_in_parens in mode 

1216 and rhs.opening_bracket.parent 

1217 and rhs.opening_bracket.parent.parent 

1218 and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker 

1219 and rhs.body.bracket_tracker.delimiters 

1220 ): 

1221 # Unless the split is inside the key 

1222 return any(leaf.type == token.COLON for leaf in rhs_oop.tail.leaves) 

1223 

1224 # the split is right after `=` 

1225 if not (len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL): 

1226 return True 

1227 

1228 # the left side of assignment contains brackets 

1229 if not any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): 

1230 return True 

1231 

1232 # the left side of assignment is short enough (the -1 is for the ending optional 

1233 # paren) 

1234 if not is_line_short_enough( 

1235 rhs.head, mode=replace(mode, line_length=mode.line_length - 1) 

1236 ): 

1237 return True 

1238 

1239 # the left side of assignment won't explode further because of magic trailing comma 

1240 if rhs.head.magic_trailing_comma is not None: 

1241 return True 

1242 

1243 # If we have multiple targets, we prefer more `=`s on the head vs pushing them to 

1244 # the body 

1245 rhs_head_equal_count = [leaf.type for leaf in rhs.head.leaves].count(token.EQUAL) 

1246 rhs_oop_head_equal_count = [leaf.type for leaf in rhs_oop.head.leaves].count( 

1247 token.EQUAL 

1248 ) 

1249 if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count: 

1250 return False 

1251 

1252 has_closing_bracket_after_assign = False 

1253 for leaf in reversed(rhs_oop.head.leaves): 

1254 if leaf.type == token.EQUAL: 

1255 break 

1256 if leaf.type in CLOSING_BRACKETS: 

1257 has_closing_bracket_after_assign = True 

1258 break 

1259 return ( 

1260 # contains matching brackets after the `=` (done by checking there is a 

1261 # closing bracket) 

1262 has_closing_bracket_after_assign 

1263 or ( 

1264 # the split is actually from inside the optional parens (done by checking 

1265 # the first line still contains the `=`) 

1266 any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves) 

1267 # the first line is short enough 

1268 and is_line_short_enough(rhs_oop.head, mode=mode) 

1269 ) 

1270 ) 

1271 

1272 

1273def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None: 

1274 """Raise :exc:`CannotSplit` if the last left- or right-hand split failed. 

1275 

1276 Do nothing otherwise. 

1277 

1278 A left- or right-hand split is based on a pair of brackets. Content before 

1279 (and including) the opening bracket is left on one line, content inside the 

1280 brackets is put on a separate line, and finally content starting with and 

1281 following the closing bracket is put on a separate line. 

1282 

1283 Those are called `head`, `body`, and `tail`, respectively. If the split 

1284 produced the same line (all content in `head`) or ended up with an empty `body` 

1285 and the `tail` is just the closing bracket, then it's considered failed. 

1286 """ 

1287 tail_len = len(str(tail).strip()) 

1288 if not body: 

1289 if tail_len == 0: 

1290 raise CannotSplit("Splitting brackets produced the same line") 

1291 

1292 elif tail_len < 3: 

1293 raise CannotSplit( 

1294 f"Splitting brackets on an empty body to save {tail_len} characters is" 

1295 " not worth it" 

1296 ) 

1297 

1298 

1299def _ensure_trailing_comma( 

1300 leaves: list[Leaf], original: Line, opening_bracket: Leaf 

1301) -> bool: 

1302 if not leaves: 

1303 return False 

1304 # Ensure a trailing comma for imports 

1305 if original.is_import: 

1306 return True 

1307 # ...and standalone function arguments 

1308 if not original.is_def: 

1309 return False 

1310 if opening_bracket.value != "(": 

1311 return False 

1312 # Don't add commas if we already have any commas 

1313 if any( 

1314 leaf.type == token.COMMA and not is_part_of_annotation(leaf) for leaf in leaves 

1315 ): 

1316 return False 

1317 

1318 # Find a leaf with a parent (comments don't have parents) 

1319 leaf_with_parent = next((leaf for leaf in leaves if leaf.parent), None) 

1320 if leaf_with_parent is None: 

1321 return True 

1322 # Don't add commas inside parenthesized return annotations 

1323 if get_annotation_type(leaf_with_parent) == "return": 

1324 return False 

1325 # Don't add commas inside PEP 604 unions 

1326 if ( 

1327 leaf_with_parent.parent 

1328 and leaf_with_parent.parent.next_sibling 

1329 and leaf_with_parent.parent.next_sibling.type == token.VBAR 

1330 ): 

1331 return False 

1332 return True 

1333 

1334 

1335def _hugging_merges_type_ignores(line: Line, *leaf_groups: Iterable[Leaf]) -> bool: 

1336 """Return True if hugging these groups would put two `type: ignore`s on a line.""" 

1337 seen = 0 

1338 for leaves in leaf_groups: 

1339 for leaf in leaves: 

1340 for comment in line.comments_after(leaf): 

1341 if is_type_ignore_comment(comment, mode=line.mode): 

1342 seen += 1 

1343 if seen > 1: 

1344 return True 

1345 return False 

1346 

1347 

1348def bracket_split_build_line( 

1349 leaves: list[Leaf], 

1350 original: Line, 

1351 opening_bracket: Leaf, 

1352 *, 

1353 component: _BracketSplitComponent, 

1354) -> Line: 

1355 """Return a new line with given `leaves` and respective comments from `original`. 

1356 

1357 If it's the head component, brackets will be tracked so trailing commas are 

1358 respected. 

1359 

1360 If it's the body component, the result line is one-indented inside brackets and as 

1361 such has its first leaf's prefix normalized and a trailing comma added when 

1362 expected. 

1363 """ 

1364 result = Line(mode=original.mode, depth=original.depth) 

1365 if component is _BracketSplitComponent.body: 

1366 result.inside_brackets = True 

1367 result.depth += 1 

1368 if _ensure_trailing_comma(leaves, original, opening_bracket): 

1369 for i in range(len(leaves) - 1, -1, -1): 

1370 if leaves[i].type == STANDALONE_COMMENT: 

1371 continue 

1372 

1373 if leaves[i].type != token.COMMA: 

1374 new_comma = Leaf(token.COMMA, ",") 

1375 leaves.insert(i + 1, new_comma) 

1376 break 

1377 

1378 leaves_to_track: set[LeafID] = set() 

1379 if component is _BracketSplitComponent.head: 

1380 leaves_to_track = get_leaves_inside_matching_brackets(leaves) 

1381 # Populate the line 

1382 for leaf in leaves: 

1383 result.append( 

1384 leaf, 

1385 preformatted=True, 

1386 track_bracket=id(leaf) in leaves_to_track, 

1387 ) 

1388 for comment_after in original.comments_after(leaf): 

1389 result.append(comment_after, preformatted=True) 

1390 if component is _BracketSplitComponent.body and should_split_line( 

1391 result, opening_bracket 

1392 ): 

1393 result.should_split_rhs = True 

1394 return result 

1395 

1396 

1397def dont_increase_indentation(split_func: Transformer) -> Transformer: 

1398 """Normalize prefix of the first leaf in every line returned by `split_func`. 

1399 

1400 This is a decorator over relevant split functions. 

1401 """ 

1402 

1403 @wraps(split_func) 

1404 def split_wrapper( 

1405 line: Line, features: Collection[Feature], mode: Mode 

1406 ) -> Iterator[Line]: 

1407 for split_line in split_func(line, features, mode): 

1408 split_line.leaves[0].prefix = "" 

1409 yield split_line 

1410 

1411 return split_wrapper 

1412 

1413 

1414def _get_last_non_comment_leaf(line: Line) -> int | None: 

1415 for leaf_idx in range(len(line.leaves) - 1, 0, -1): 

1416 if line.leaves[leaf_idx].type != STANDALONE_COMMENT: 

1417 return leaf_idx 

1418 return None 

1419 

1420 

1421def _can_add_trailing_comma(leaf: Leaf, features: Collection[Feature]) -> bool: 

1422 if is_vararg(leaf, within={syms.typedargslist}): 

1423 return Feature.TRAILING_COMMA_IN_DEF in features 

1424 if is_vararg(leaf, within={syms.arglist, syms.argument}): 

1425 return Feature.TRAILING_COMMA_IN_CALL in features 

1426 return True 

1427 

1428 

1429def _safe_add_trailing_comma(safe: bool, delimiter_priority: int, line: Line) -> Line: 

1430 if ( 

1431 safe 

1432 and delimiter_priority == COMMA_PRIORITY 

1433 and line.leaves[-1].type != token.COMMA 

1434 and line.leaves[-1].type != STANDALONE_COMMENT 

1435 ): 

1436 new_comma = Leaf(token.COMMA, ",") 

1437 line.append(new_comma) 

1438 return line 

1439 

1440 

1441MIGRATE_COMMENT_DELIMITERS = {STRING_PRIORITY, COMMA_PRIORITY} 

1442 

1443 

1444def _can_defer_lone_comparator_to_rhs(line: Line, mode: Mode) -> bool: 

1445 """Return True if the lone comparator on `line` can defer to right_hand_split. 

1446 

1447 Caller has already established exactly one delimiter at 

1448 `COMPARATOR_PRIORITY`. We defer only when: 

1449 

1450 - the LHS up to the comparator has no opening brackets, so the existing 

1451 "break before the comparator" wouldn't produce a balanced two-sided 

1452 split anyway, and 

1453 - `right_hand_split` would produce a head that fits in the line length, 

1454 so we don't strand `if t` on its own line just to push it back onto an 

1455 overflowing single line when the RHS bracket can't be exploded 

1456 usefully (e.g. an empty `decode()` paren). 

1457 """ 

1458 past_comparator = False 

1459 for leaf in line.leaves: 

1460 if leaf.type in OPENING_BRACKETS and not past_comparator: 

1461 return False 

1462 if not past_comparator and ( 

1463 line.bracket_tracker.delimiters.get(id(leaf)) == COMPARATOR_PRIORITY 

1464 ): 

1465 past_comparator = True 

1466 try: 

1467 rhs = _first_right_hand_split(line) 

1468 except CannotSplit: 

1469 return False 

1470 return is_line_short_enough(rhs.head, mode=mode) 

1471 

1472 

1473@dont_increase_indentation 

1474def delimiter_split( 

1475 line: Line, features: Collection[Feature], mode: Mode 

1476) -> Iterator[Line]: 

1477 """Split according to delimiters of the highest priority. 

1478 

1479 If the appropriate Features are given, the split will add trailing commas 

1480 also in function signatures and calls that contain `*` and `**`. 

1481 """ 

1482 if len(line.leaves) == 0: 

1483 raise CannotSplit("Line empty") from None 

1484 last_leaf = line.leaves[-1] 

1485 

1486 bt = line.bracket_tracker 

1487 try: 

1488 delimiter_priority = bt.max_delimiter_priority(exclude={id(last_leaf)}) 

1489 except ValueError: 

1490 raise CannotSplit("No delimiters found") from None 

1491 

1492 if ( 

1493 delimiter_priority == DOT_PRIORITY 

1494 and bt.delimiter_count_with_priority(delimiter_priority) == 1 

1495 ): 

1496 raise CannotSplit("Splitting a single attribute from its owner looks wrong") 

1497 

1498 if ( 

1499 Preview.hug_comparator in mode 

1500 and delimiter_priority == COMPARATOR_PRIORITY 

1501 and bt.delimiter_count_with_priority(delimiter_priority) == 1 

1502 and _can_defer_lone_comparator_to_rhs(line, mode) 

1503 ): 

1504 raise CannotSplit("Bracketed RHS will explode via right_hand_split") 

1505 

1506 current_line = Line( 

1507 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1508 ) 

1509 lowest_depth = sys.maxsize 

1510 trailing_comma_safe = True 

1511 

1512 def append_to_line(leaf: Leaf) -> Iterator[Line]: 

1513 """Append `leaf` to current line or to new line if appending impossible.""" 

1514 nonlocal current_line 

1515 try: 

1516 current_line.append_safe(leaf, preformatted=True) 

1517 except ValueError: 

1518 yield current_line 

1519 

1520 current_line = Line( 

1521 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1522 ) 

1523 current_line.append(leaf) 

1524 

1525 def append_comments(leaf: Leaf) -> Iterator[Line]: 

1526 for comment_after in line.comments_after(leaf): 

1527 yield from append_to_line(comment_after) 

1528 

1529 last_non_comment_leaf = _get_last_non_comment_leaf(line) 

1530 for leaf_idx, leaf in enumerate(line.leaves): 

1531 yield from append_to_line(leaf) 

1532 

1533 previous_priority = leaf_idx > 0 and bt.delimiters.get( 

1534 id(line.leaves[leaf_idx - 1]) 

1535 ) 

1536 if ( 

1537 previous_priority != delimiter_priority 

1538 or delimiter_priority in MIGRATE_COMMENT_DELIMITERS 

1539 ): 

1540 yield from append_comments(leaf) 

1541 

1542 lowest_depth = min(lowest_depth, leaf.bracket_depth) 

1543 if trailing_comma_safe and leaf.bracket_depth == lowest_depth: 

1544 trailing_comma_safe = _can_add_trailing_comma(leaf, features) 

1545 

1546 if last_leaf.type == STANDALONE_COMMENT and leaf_idx == last_non_comment_leaf: 

1547 current_line = _safe_add_trailing_comma( 

1548 trailing_comma_safe, delimiter_priority, current_line 

1549 ) 

1550 

1551 leaf_priority = bt.delimiters.get(id(leaf)) 

1552 if leaf_priority == delimiter_priority: 

1553 if ( 

1554 leaf_idx + 1 < len(line.leaves) 

1555 and delimiter_priority not in MIGRATE_COMMENT_DELIMITERS 

1556 ): 

1557 yield from append_comments(line.leaves[leaf_idx + 1]) 

1558 

1559 yield current_line 

1560 current_line = Line( 

1561 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1562 ) 

1563 

1564 if current_line: 

1565 current_line = _safe_add_trailing_comma( 

1566 trailing_comma_safe, delimiter_priority, current_line 

1567 ) 

1568 yield current_line 

1569 

1570 

1571@dont_increase_indentation 

1572def standalone_comment_split( 

1573 line: Line, features: Collection[Feature], mode: Mode 

1574) -> Iterator[Line]: 

1575 """Split standalone comments from the rest of the line.""" 

1576 if not line.contains_standalone_comments(): 

1577 raise CannotSplit("Line does not have any standalone comments") 

1578 

1579 current_line = Line( 

1580 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1581 ) 

1582 

1583 def append_to_line(leaf: Leaf) -> Iterator[Line]: 

1584 """Append `leaf` to current line or to new line if appending impossible.""" 

1585 nonlocal current_line 

1586 try: 

1587 current_line.append_safe(leaf, preformatted=True) 

1588 except ValueError: 

1589 yield current_line 

1590 

1591 current_line = Line( 

1592 line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1593 ) 

1594 current_line.append(leaf) 

1595 

1596 for leaf in line.leaves: 

1597 yield from append_to_line(leaf) 

1598 

1599 for comment_after in line.comments_after(leaf): 

1600 yield from append_to_line(comment_after) 

1601 

1602 if current_line: 

1603 yield current_line 

1604 

1605 

1606def _force_standalone_comment_split(line: Line) -> Iterator[Line]: 

1607 """Last-resort split at every standalone-comment boundary.""" 

1608 current_line = Line( 

1609 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1610 ) 

1611 for leaf in line.leaves: 

1612 if current_line.leaves and ( 

1613 leaf.type == STANDALONE_COMMENT or current_line.is_comment 

1614 ): 

1615 yield current_line 

1616 current_line = Line( 

1617 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1618 ) 

1619 current_line.append(leaf, preformatted=True) 

1620 for comment_after in line.comments_after(leaf): 

1621 current_line.append(comment_after, preformatted=True) 

1622 if current_line: 

1623 yield current_line 

1624 

1625 

1626def _is_parenthesized_lambda_or_ternary(node: LN) -> bool: 

1627 """Whether `node` is an atom wrapping a lambda or conditional expression, 

1628 looking through any redundant nested parentheses. 

1629 

1630 As a comprehension's iterable, such an expression must keep at least one pair 

1631 of parentheses: without them the trailing `for`/`if` clauses would be parsed 

1632 as part of the lambda body (or break the ternary), producing invalid code. 

1633 """ 

1634 while ( 

1635 node.type == syms.atom 

1636 and len(node.children) == 3 

1637 and is_lpar_token(node.children[0]) 

1638 and is_rpar_token(node.children[-1]) 

1639 ): 

1640 middle = node.children[1] 

1641 if middle.type in {syms.test, syms.lambdef}: 

1642 return True 

1643 node = middle 

1644 return False 

1645 

1646 

1647def _has_redundant_generator_parentheses(node: LN) -> bool: 

1648 """Whether `node` adds parentheses around a parenthesized generator.""" 

1649 if ( 

1650 node.type != syms.atom 

1651 or len(node.children) != 3 

1652 or not is_lpar_token(node.children[0]) 

1653 or not is_rpar_token(node.children[-1]) 

1654 ): 

1655 return False 

1656 

1657 middle = node.children[1] 

1658 return is_generator(middle) or _has_redundant_generator_parentheses(middle) 

1659 

1660 

1661def normalize_invisible_parens( 

1662 node: Node, parens_after: set[str], *, mode: Mode, features: Collection[Feature] 

1663) -> None: 

1664 """Make existing optional parentheses invisible or create new ones. 

1665 

1666 `parens_after` is a set of string leaf values immediately after which parens 

1667 should be put. 

1668 

1669 Standardizes on visible parentheses for single-element tuples, and keeps 

1670 existing visible parentheses for other tuples and generator expressions. 

1671 """ 

1672 for pc in list_comments(node.prefix, is_endmarker=False, mode=mode): 

1673 if contains_fmt_directive(pc.value, FMT_OFF): 

1674 # This `node` has a prefix with `# fmt: off`, don't mess with parens. 

1675 return 

1676 

1677 # The multiple context managers grammar has a different pattern, thus this is 

1678 # separate from the for-loop below. This possibly wraps them in invisible parens, 

1679 # and later will be removed in remove_with_parens when needed. 

1680 if node.type == syms.with_stmt: 

1681 _maybe_wrap_cms_in_parens(node, mode, features) 

1682 

1683 check_lpar = False 

1684 for index, child in enumerate(list(node.children)): 

1685 # Fixes a bug where invisible parens are not properly stripped from 

1686 # assignment statements that contain type annotations. 

1687 if isinstance(child, Node) and child.type == syms.annassign: 

1688 normalize_invisible_parens( 

1689 child, parens_after=parens_after, mode=mode, features=features 

1690 ) 

1691 

1692 # Fixes a bug where invisible parens are not properly wrapped around 

1693 # case blocks. 

1694 if isinstance(child, Node) and child.type == syms.case_block: 

1695 normalize_invisible_parens( 

1696 child, parens_after={"case"}, mode=mode, features=features 

1697 ) 

1698 

1699 # Add parentheses around if guards in case blocks 

1700 if isinstance(child, Node) and child.type == syms.guard: 

1701 normalize_invisible_parens( 

1702 child, parens_after={"if"}, mode=mode, features=features 

1703 ) 

1704 

1705 # Add parentheses around long tuple unpacking in assignments. 

1706 if ( 

1707 index == 0 

1708 and isinstance(child, Node) 

1709 and child.type == syms.testlist_star_expr 

1710 ): 

1711 check_lpar = True 

1712 

1713 if ( 

1714 index == 0 

1715 and isinstance(child, Node) 

1716 and child.type == syms.atom 

1717 and node.type == syms.expr_stmt 

1718 and not _atom_has_magic_trailing_comma(child, mode) 

1719 and not _is_atom_multiline(child) 

1720 ): 

1721 if _is_parenthesized_annotation_target(node, child): 

1722 # One pair is what makes the target non-simple, so any nesting 

1723 # inside it is redundant and goes. 

1724 inner = child.children[1] 

1725 if isinstance(inner, Node) and inner.type == syms.atom: 

1726 maybe_make_parens_invisible_in_atom( 

1727 inner, 

1728 parent=child, 

1729 mode=mode, 

1730 features=features, 

1731 ) 

1732 elif maybe_make_parens_invisible_in_atom( 

1733 child, 

1734 parent=node, 

1735 mode=mode, 

1736 features=features, 

1737 remove_brackets_around_comma=True, 

1738 allow_star_expr=True, 

1739 ): 

1740 wrap_in_parentheses(node, child, visible=False, index=index) 

1741 

1742 if check_lpar: 

1743 if ( 

1744 child.type == syms.atom 

1745 and node.type == syms.for_stmt 

1746 and isinstance(child.prev_sibling, Leaf) 

1747 and child.prev_sibling.type == token.NAME 

1748 and child.prev_sibling.value == "for" 

1749 ): 

1750 if maybe_make_parens_invisible_in_atom( 

1751 child, 

1752 parent=node, 

1753 mode=mode, 

1754 features=features, 

1755 remove_brackets_around_comma=True, 

1756 ): 

1757 wrap_in_parentheses(node, child, visible=False, index=index) 

1758 elif isinstance(child, Node) and node.type == syms.with_stmt: 

1759 remove_with_parens(child, node, mode=mode, features=features) 

1760 elif ( 

1761 isinstance(child, Node) 

1762 and node.type == syms.yield_expr 

1763 and child.type == syms.yield_arg 

1764 and Preview.parenthesize_tuple_in_yield in mode 

1765 ): 

1766 if ( 

1767 len(child.children) == 1 

1768 and child.children[0].type != syms.atom 

1769 and is_one_tuple(child.children[0]) 

1770 ): 

1771 wrap_in_parentheses(node, child, visible=True, index=index) 

1772 elif child.type == syms.atom: 

1773 if "in" in parens_after and _is_parenthesized_lambda_or_ternary(child): 

1774 # A lambda or conditional expression used as a comprehension's 

1775 # iterable must keep at least one pair of parentheses, otherwise 

1776 # the trailing `for`/`if` clauses get absorbed into it and the 

1777 # code becomes invalid. Any extra nested pairs are redundant, so 

1778 # collapse them while keeping exactly one visible pair. 

1779 maybe_make_parens_invisible_in_atom( 

1780 child, parent=node, mode=mode, features=features 

1781 ) 

1782 opening = child.children[0] 

1783 closing = child.children[-1] 

1784 if is_lpar_token(opening) and is_rpar_token(closing): 

1785 opening.value = "(" 

1786 closing.value = ")" 

1787 elif maybe_make_parens_invisible_in_atom( 

1788 child, parent=node, mode=mode, features=features 

1789 ): 

1790 wrap_in_parentheses(node, child, visible=False, index=index) 

1791 elif is_one_tuple(child): 

1792 wrap_in_parentheses(node, child, visible=True, index=index) 

1793 elif node.type == syms.import_from: 

1794 _normalize_import_from(node, child, index) 

1795 break 

1796 elif ( 

1797 index == 1 

1798 and child.type == token.STAR 

1799 and node.type == syms.except_clause 

1800 ): 

1801 # In except* (PEP 654), the star is actually part of 

1802 # of the keyword. So we need to skip the insertion of 

1803 # invisible parentheses to work more precisely. 

1804 continue 

1805 

1806 elif ( 

1807 isinstance(child, Leaf) 

1808 and child.next_sibling is not None 

1809 and child.next_sibling.type == token.COLON 

1810 and child.value == "case" 

1811 ): 

1812 # A special patch for "case case:" scenario, the second occurrence 

1813 # of case will be not parsed as a Python keyword. 

1814 break 

1815 

1816 elif isinstance(child, Node) and child.type == syms.guard: 

1817 # Guard nodes handle their own inner wrapping. Wrapping the guard 

1818 # itself can produce invalid output when the case pattern splits. 

1819 pass 

1820 

1821 elif not is_multiline_string(child): 

1822 if ( 

1823 Preview.fix_if_guard_explosion_in_case_statement in mode 

1824 and node.type == syms.guard 

1825 ): 

1826 mock_line = Line(mode=mode) 

1827 for leaf in child.leaves(): 

1828 mock_line.append(leaf) 

1829 # If it's a guard AND it's short, we DON'T wrap 

1830 if not is_line_short_enough(mock_line, mode=mode): 

1831 wrap_in_parentheses(node, child, visible=False, index=index) 

1832 else: 

1833 wrap_in_parentheses(node, child, visible=False, index=index) 

1834 

1835 comma_check = child.type == token.COMMA 

1836 

1837 check_lpar = isinstance(child, Leaf) and ( 

1838 child.value in parens_after or comma_check 

1839 ) 

1840 

1841 

1842def _normalize_import_from(parent: Node, child: LN, index: int) -> None: 

1843 # "import from" nodes store parentheses directly as part of 

1844 # the statement 

1845 if is_lpar_token(child): 

1846 assert is_rpar_token(parent.children[-1]) 

1847 # make parentheses invisible 

1848 child.value = "" 

1849 parent.children[-1].value = "" 

1850 elif child.type != token.STAR: 

1851 # insert invisible parentheses 

1852 parent.insert_child(index, Leaf(token.LPAR, "")) 

1853 parent.append_child(Leaf(token.RPAR, "")) 

1854 

1855 

1856def remove_await_parens(node: Node, mode: Mode, features: Collection[Feature]) -> None: 

1857 if node.children[0].type == token.AWAIT and len(node.children) > 1: 

1858 if ( 

1859 node.children[1].type == syms.atom 

1860 and node.children[1].children[0].type == token.LPAR 

1861 ): 

1862 if maybe_make_parens_invisible_in_atom( 

1863 node.children[1], 

1864 parent=node, 

1865 mode=mode, 

1866 features=features, 

1867 remove_brackets_around_comma=True, 

1868 ): 

1869 wrap_in_parentheses(node, node.children[1], visible=False) 

1870 

1871 # Since await is an expression we shouldn't remove 

1872 # brackets in cases where this would change 

1873 # the AST due to operator precedence. 

1874 # Therefore we only aim to remove brackets around 

1875 # power nodes that aren't also await expressions themselves. 

1876 # https://peps.python.org/pep-0492/#updated-operator-precedence-table 

1877 # N.B. We've still removed any redundant nested brackets though :) 

1878 opening_bracket = cast(Leaf, node.children[1].children[0]) 

1879 closing_bracket = cast(Leaf, node.children[1].children[-1]) 

1880 bracket_contents = node.children[1].children[1] 

1881 if isinstance(bracket_contents, Node) and ( 

1882 bracket_contents.type != syms.power 

1883 or bracket_contents.children[0].type == token.AWAIT 

1884 or any( 

1885 isinstance(child, Leaf) and child.type == token.DOUBLESTAR 

1886 for child in bracket_contents.children 

1887 ) 

1888 ): 

1889 ensure_visible(opening_bracket) 

1890 ensure_visible(closing_bracket) 

1891 

1892 

1893def _maybe_wrap_cms_in_parens( 

1894 node: Node, mode: Mode, features: Collection[Feature] 

1895) -> None: 

1896 """When enabled and safe, wrap the multiple context managers in invisible parens. 

1897 

1898 It is only safe when `features` contain Feature.PARENTHESIZED_CONTEXT_MANAGERS. 

1899 """ 

1900 if ( 

1901 Feature.PARENTHESIZED_CONTEXT_MANAGERS not in features 

1902 or len(node.children) <= 2 

1903 # If it's an atom, it's already wrapped in parens. 

1904 or node.children[1].type == syms.atom 

1905 ): 

1906 return 

1907 colon_index: int | None = None 

1908 for i in range(2, len(node.children)): 

1909 if node.children[i].type == token.COLON: 

1910 colon_index = i 

1911 break 

1912 if colon_index is not None: 

1913 lpar = Leaf(token.LPAR, "") 

1914 rpar = Leaf(token.RPAR, "") 

1915 context_managers = node.children[1:colon_index] 

1916 for child in context_managers: 

1917 child.remove() 

1918 # After wrapping, the with_stmt will look like this: 

1919 # with_stmt 

1920 # NAME 'with' 

1921 # atom 

1922 # LPAR '' 

1923 # testlist_gexp 

1924 # ... <-- context_managers 

1925 # /testlist_gexp 

1926 # RPAR '' 

1927 # /atom 

1928 # COLON ':' 

1929 new_child = Node( 

1930 syms.atom, [lpar, Node(syms.testlist_gexp, context_managers), rpar] 

1931 ) 

1932 node.insert_child(1, new_child) 

1933 

1934 

1935def remove_with_parens( 

1936 node: Node, parent: Node, mode: Mode, features: Collection[Feature] 

1937) -> None: 

1938 """Recursively hide optional parens in `with` statements.""" 

1939 # Removing all unnecessary parentheses in with statements in one pass is a tad 

1940 # complex as different variations of bracketed statements result in pretty 

1941 # different parse trees: 

1942 # 

1943 # with (open("file")) as f: # this is an asexpr_test 

1944 # ... 

1945 # 

1946 # with (open("file") as f): # this is an atom containing an 

1947 # ... # asexpr_test 

1948 # 

1949 # with (open("file")) as f, (open("file")) as f: # this is asexpr_test, COMMA, 

1950 # ... # asexpr_test 

1951 # 

1952 # with (open("file") as f, open("file") as f): # an atom containing a 

1953 # ... # testlist_gexp which then 

1954 # # contains multiple asexpr_test(s) 

1955 if node.type == syms.atom: 

1956 if maybe_make_parens_invisible_in_atom( 

1957 node, 

1958 parent=parent, 

1959 mode=mode, 

1960 features=features, 

1961 remove_brackets_around_comma=True, 

1962 ): 

1963 wrap_in_parentheses(parent, node, visible=False) 

1964 if isinstance(node.children[1], Node): 

1965 remove_with_parens(node.children[1], node, mode=mode, features=features) 

1966 elif node.type == syms.testlist_gexp: 

1967 for child in node.children: 

1968 if isinstance(child, Node): 

1969 remove_with_parens(child, node, mode=mode, features=features) 

1970 elif node.type == syms.asexpr_test and not any( 

1971 leaf.type == token.COLONEQUAL for leaf in node.leaves() 

1972 ): 

1973 if maybe_make_parens_invisible_in_atom( 

1974 node.children[0], 

1975 parent=node, 

1976 mode=mode, 

1977 features=features, 

1978 remove_brackets_around_comma=True, 

1979 ): 

1980 wrap_in_parentheses(node, node.children[0], visible=False) 

1981 

1982 

1983def _atom_has_magic_trailing_comma(node: LN, mode: Mode) -> bool: 

1984 """Check if an atom node has a magic trailing comma. 

1985 

1986 Returns True for single-element tuples with trailing commas like (a,), 

1987 which should be preserved to maintain their tuple type. 

1988 """ 

1989 if not mode.magic_trailing_comma: 

1990 return False 

1991 

1992 return is_one_tuple(node) 

1993 

1994 

1995def _is_parenthesized_annotation_target(node: Node, child: Node) -> bool: 

1996 """Is `child` a parenthesized plain name that `node` annotates? 

1997 

1998 `(x): int = 5` and `x: int = 5` do not do the same thing: only the second one 

1999 records `x` in `__annotations__`, because the parentheses make the target 

2000 non-simple (`AnnAssign.simple` is 0). Dropping them changes what the module 

2001 does at runtime, so they have to stay. Attribute and subscript targets are 

2002 non-simple either way, so those are left alone here. 

2003 """ 

2004 if len(node.children) < 2: 

2005 return False 

2006 

2007 annassign = node.children[1] 

2008 if not isinstance(annassign, Node) or annassign.type != syms.annassign: 

2009 return False 

2010 

2011 # `((x)): int = 5` is non-simple too, so look through any nesting. 

2012 target: LN = child 

2013 while ( 

2014 isinstance(target, Node) 

2015 and target.type == syms.atom 

2016 and len(target.children) == 3 

2017 ): 

2018 target = target.children[1] 

2019 

2020 return isinstance(target, Leaf) and target.type == token.NAME 

2021 

2022 

2023def _is_atom_multiline(node: LN) -> bool: 

2024 """Check if an atom node is multiline (indicating intentional formatting).""" 

2025 if not isinstance(node, Node) or len(node.children) < 3: 

2026 return False 

2027 

2028 # Check the middle child (between LPAR and RPAR) for newlines in its subtree 

2029 # The first child's prefix contains blank lines/comments before the opening paren 

2030 middle = node.children[1] 

2031 for child in middle.pre_order(): 

2032 if isinstance(child, Leaf) and "\n" in child.prefix: 

2033 return True 

2034 

2035 return False 

2036 

2037 

2038def maybe_make_parens_invisible_in_atom( 

2039 node: LN, 

2040 parent: LN, 

2041 mode: Mode, 

2042 features: Collection[Feature], 

2043 remove_brackets_around_comma: bool = False, 

2044 allow_star_expr: bool = False, 

2045 remove_generator_parens: bool = False, 

2046) -> bool: 

2047 """If it's safe, make the parens in the atom `node` invisible, recursively. 

2048 Additionally, remove repeated, adjacent invisible parens from the atom `node` 

2049 as they are redundant. 

2050 

2051 Returns whether the node should itself be wrapped in invisible parentheses. 

2052 """ 

2053 can_remove_generator_parens = remove_generator_parens and is_generator(node) 

2054 if ( 

2055 node.type not in (syms.atom, syms.expr) 

2056 or is_empty_tuple(node) 

2057 or is_one_tuple(node) 

2058 or (is_tuple(node) and parent.type == syms.asexpr_test) 

2059 or ( 

2060 is_tuple(node) 

2061 and parent.type == syms.with_stmt 

2062 and has_sibling_with_type(node, token.COMMA) 

2063 ) 

2064 or (is_yield(node) and parent.type != syms.expr_stmt) 

2065 or ( 

2066 # This condition tries to prevent removing non-optional brackets 

2067 # around a tuple, however, can be a bit overzealous so we provide 

2068 # and option to skip this check for `for` and `with` statements. 

2069 not remove_brackets_around_comma 

2070 and max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY 

2071 and not can_remove_generator_parens 

2072 # Remove parentheses around multiple exception types in except and 

2073 # except* without as. See PEP 758 for details. 

2074 and not ( 

2075 Feature.UNPARENTHESIZED_EXCEPT_TYPES in features 

2076 # is a tuple 

2077 and is_tuple(node) 

2078 # has a parent node 

2079 and node.parent is not None 

2080 # parent is an except clause 

2081 and node.parent.type == syms.except_clause 

2082 # is not immediately followed by as clause 

2083 and not ( 

2084 node.next_sibling is not None 

2085 and is_name_token(node.next_sibling) 

2086 and node.next_sibling.value == "as" 

2087 ) 

2088 ) 

2089 ) 

2090 or is_tuple_containing_walrus(node) 

2091 or (not allow_star_expr and is_tuple_containing_star(node)) 

2092 or (not can_remove_generator_parens and is_generator(node)) 

2093 ): 

2094 return False 

2095 

2096 if is_walrus_assignment(node): 

2097 if parent.type in [ 

2098 syms.annassign, 

2099 syms.expr_stmt, 

2100 syms.assert_stmt, 

2101 syms.return_stmt, 

2102 syms.yield_arg, 

2103 syms.yield_expr, 

2104 syms.except_clause, 

2105 syms.funcdef, 

2106 syms.with_stmt, 

2107 syms.testlist_gexp, 

2108 syms.tname, 

2109 # these ones aren't useful to end users, but they do please fuzzers 

2110 syms.for_stmt, 

2111 syms.del_stmt, 

2112 ]: 

2113 return False 

2114 

2115 first = node.children[0] 

2116 last = node.children[-1] 

2117 if is_lpar_token(first) and is_rpar_token(last): 

2118 middle = node.children[1] 

2119 # make parentheses invisible 

2120 if ( 

2121 # If the prefix of `middle` includes a type comment with 

2122 # ignore annotation, then we do not remove the parentheses 

2123 not is_type_ignore_comment_string(middle.prefix.strip(), mode=mode) 

2124 ): 

2125 first.value = "" 

2126 last.value = "" 

2127 maybe_make_parens_invisible_in_atom( 

2128 middle, 

2129 parent=parent, 

2130 mode=mode, 

2131 features=features, 

2132 remove_brackets_around_comma=remove_brackets_around_comma, 

2133 ) 

2134 

2135 if is_atom_with_invisible_parens(middle): 

2136 # Strip the invisible parens from `middle` by replacing 

2137 # it with the child in-between the invisible parens 

2138 middle.replace(middle.children[1]) 

2139 

2140 if middle.children[0].prefix.strip(): 

2141 # Preserve comments before first paren 

2142 middle.children[1].prefix = ( 

2143 middle.children[0].prefix + middle.children[1].prefix 

2144 ) 

2145 

2146 if middle.children[-1].prefix.strip(): 

2147 # Preserve comments before last paren 

2148 last.prefix = middle.children[-1].prefix + last.prefix 

2149 

2150 return False 

2151 

2152 return True 

2153 

2154 

2155def should_split_line(line: Line, opening_bracket: Leaf) -> bool: 

2156 """Should `line` be immediately split with `delimiter_split()` after RHS?""" 

2157 

2158 if not (opening_bracket.parent and opening_bracket.value in "[{("): 

2159 return False 

2160 

2161 # We're essentially checking if the body is delimited by commas and there's more 

2162 # than one of them (we're excluding the trailing comma and if the delimiter priority 

2163 # is still commas, that means there's more). 

2164 exclude = set() 

2165 trailing_comma = False 

2166 try: 

2167 last_leaf = line.leaves[-1] 

2168 if last_leaf.type == token.COMMA: 

2169 trailing_comma = True 

2170 exclude.add(id(last_leaf)) 

2171 max_priority = line.bracket_tracker.max_delimiter_priority(exclude=exclude) 

2172 except (IndexError, ValueError): 

2173 return False 

2174 

2175 return max_priority == COMMA_PRIORITY and ( 

2176 (line.mode.magic_trailing_comma and trailing_comma) 

2177 # always explode imports 

2178 or opening_bracket.parent.type in {syms.atom, syms.import_from} 

2179 ) 

2180 

2181 

2182def generate_trailers_to_omit(line: Line, line_length: int) -> Iterator[set[LeafID]]: 

2183 """Generate sets of closing bracket IDs that should be omitted in a RHS. 

2184 

2185 Brackets can be omitted if the entire trailer up to and including 

2186 a preceding closing bracket fits in one line. 

2187 

2188 Yielded sets are cumulative (contain results of previous yields, too). First 

2189 set is empty, unless the line should explode, in which case bracket pairs until 

2190 the one that needs to explode are omitted. 

2191 """ 

2192 

2193 omit: set[LeafID] = set() 

2194 if not line.magic_trailing_comma: 

2195 yield omit 

2196 

2197 length = 4 * line.depth 

2198 opening_bracket: Leaf | None = None 

2199 closing_bracket: Leaf | None = None 

2200 inner_brackets: set[LeafID] = set() 

2201 for index, leaf, leaf_length in line.enumerate_with_length(is_reversed=True): 

2202 length += leaf_length 

2203 if length > line_length: 

2204 break 

2205 

2206 has_inline_comment = leaf_length > len(leaf.value) + len(leaf.prefix) 

2207 if leaf.type == STANDALONE_COMMENT or has_inline_comment: 

2208 break 

2209 

2210 if opening_bracket: 

2211 if leaf is opening_bracket: 

2212 opening_bracket = None 

2213 elif leaf.type in CLOSING_BRACKETS: 

2214 prev = line.leaves[index - 1] if index > 0 else None 

2215 if ( 

2216 prev 

2217 and prev.type == token.COMMA 

2218 and leaf.opening_bracket is not None 

2219 and not is_one_sequence_between( 

2220 leaf.opening_bracket, leaf, line.leaves 

2221 ) 

2222 ): 

2223 # Never omit bracket pairs with trailing commas. 

2224 # We need to explode on those. 

2225 break 

2226 

2227 inner_brackets.add(id(leaf)) 

2228 elif leaf.type in CLOSING_BRACKETS: 

2229 prev = line.leaves[index - 1] if index > 0 else None 

2230 if prev and prev.type in OPENING_BRACKETS: 

2231 # Empty brackets would fail a split so treat them as "inner" 

2232 # brackets (e.g. only add them to the `omit` set if another 

2233 # pair of brackets was good enough. 

2234 inner_brackets.add(id(leaf)) 

2235 continue 

2236 

2237 if closing_bracket: 

2238 omit.add(id(closing_bracket)) 

2239 omit.update(inner_brackets) 

2240 inner_brackets.clear() 

2241 yield omit 

2242 

2243 if ( 

2244 prev 

2245 and prev.type == token.COMMA 

2246 and leaf.opening_bracket is not None 

2247 and not is_one_sequence_between(leaf.opening_bracket, leaf, line.leaves) 

2248 ): 

2249 # Never omit bracket pairs with trailing commas. 

2250 # We need to explode on those. 

2251 break 

2252 

2253 if leaf.value: 

2254 opening_bracket = leaf.opening_bracket 

2255 closing_bracket = leaf 

2256 

2257 

2258def _over_length_only_due_to_subscript_comment(line: Line, mode: Mode) -> bool: 

2259 """Return True if `line` only exceeds `mode.line_length` because of an inline 

2260 comment attached to a subscript opening bracket (`[`). 

2261 

2262 This is the shape produced by the original of the issue #4733 reproducer: 

2263 a comment inside the annotation's subscript brackets renders at the end of 

2264 the head line after Black splits the statement, pushing it past the limit. 

2265 Taking the FORCE_OPTIONAL_PARENTHESES "second opinion" in that case wraps 

2266 the annotation in extra parens and migrates the comment outside the 

2267 subscript, which then oscillates on the next formatter pass. 

2268 """ 

2269 if not line.leaves: 

2270 return False 

2271 # The over-length must be caused entirely by a trailing comment. 

2272 indent = " " * line.depth 

2273 leaves_iter = iter(line.leaves) 

2274 first = next(leaves_iter) 

2275 text_without_comments = f"{first.prefix}{indent}{first.value}" 

2276 text_without_comments += "".join(str(leaf) for leaf in leaves_iter) 

2277 if str_width(text_without_comments) > mode.line_length: 

2278 return False 

2279 # And the comment must be attached to a subscript opening bracket. 

2280 for leaf_id, comments in line.comments.items(): 

2281 if not comments: 

2282 continue 

2283 leaf = next((lf for lf in line.leaves if id(lf) == leaf_id), None) 

2284 if leaf is None or leaf.type != token.LSQB: 

2285 return False 

2286 return True 

2287 

2288 

2289def run_transformer( 

2290 line: Line, 

2291 transform: Transformer, 

2292 mode: Mode, 

2293 features: Collection[Feature], 

2294 *, 

2295 line_str: str = "", 

2296) -> list[Line]: 

2297 if not line_str: 

2298 line_str = line_to_string(line) 

2299 result: list[Line] = [] 

2300 for transformed_line in transform(line, features, mode): 

2301 if str(transformed_line).strip("\n") == line_str: 

2302 raise CannotTransform("Line transformer returned an unchanged result") 

2303 

2304 result.extend(transform_line(transformed_line, mode=mode, features=features)) 

2305 

2306 features_set = set(features) 

2307 if ( 

2308 Feature.FORCE_OPTIONAL_PARENTHESES in features_set 

2309 or transform.__class__.__name__ != "rhs" 

2310 or not line.bracket_tracker.invisible 

2311 or any(bracket.value for bracket in line.bracket_tracker.invisible) 

2312 or line.contains_multiline_strings() 

2313 or result[0].contains_uncollapsable_type_comments() 

2314 or result[0].contains_unsplittable_type_ignore() 

2315 or is_line_short_enough(result[0], mode=mode) 

2316 # result[0] only exceeds the length because of a comment attached to a 

2317 # subscript opening bracket. Taking the FORCE_OPTIONAL_PARENTHESES 

2318 # "second opinion" wraps the annotation in extra invisible parens and 

2319 # migrates the comment outside the subscript, which then oscillates with 

2320 # a deeper-bracket split on the next formatter pass (issue #4733). 

2321 or _over_length_only_due_to_subscript_comment(result[0], mode) 

2322 # If any leaves have no parents (which _can_ occur since 

2323 # `transform(line)` potentially destroys the line's underlying node 

2324 # structure), then we can't proceed. Doing so would cause the below 

2325 # call to `append_leaves()` to fail. 

2326 or any(leaf.parent is None for leaf in line.leaves) 

2327 ): 

2328 return result 

2329 

2330 line_copy = line.clone() 

2331 append_leaves(line_copy, line, line.leaves) 

2332 features_fop = features_set | {Feature.FORCE_OPTIONAL_PARENTHESES} 

2333 second_opinion = run_transformer( 

2334 line_copy, transform, mode, features_fop, line_str=line_str 

2335 ) 

2336 if all(is_line_short_enough(ln, mode=mode) for ln in second_opinion): 

2337 result = second_opinion 

2338 return result