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

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

823 statements  

1""" 

2Generating lines of code. 

3""" 

4 

5import re 

6import sys 

7from collections.abc import Collection, 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 DOT_PRIORITY, 

16 STRING_PRIORITY, 

17 get_leaves_inside_matching_brackets, 

18 max_delimiter_priority_in_atom, 

19) 

20from black.comments import ( 

21 FMT_OFF, 

22 FMT_ON, 

23 contains_fmt_directive, 

24 generate_comments, 

25 list_comments, 

26) 

27from black.lines import ( 

28 Line, 

29 RHSResult, 

30 append_leaves, 

31 can_be_split, 

32 can_omit_invisible_parens, 

33 is_line_short_enough, 

34 line_to_string, 

35) 

36from black.mode import Feature, Mode, Preview 

37from black.nodes import ( 

38 ASSIGNMENTS, 

39 BRACKETS, 

40 CLOSING_BRACKETS, 

41 OPENING_BRACKETS, 

42 STANDALONE_COMMENT, 

43 STATEMENT, 

44 WHITESPACE, 

45 Visitor, 

46 ensure_visible, 

47 fstring_tstring_to_string, 

48 get_annotation_type, 

49 has_sibling_with_type, 

50 is_arith_like, 

51 is_async_stmt_or_funcdef, 

52 is_atom_with_invisible_parens, 

53 is_docstring, 

54 is_empty_tuple, 

55 is_generator, 

56 is_lpar_token, 

57 is_multiline_string, 

58 is_name_token, 

59 is_one_sequence_between, 

60 is_one_tuple, 

61 is_parent_function_or_class, 

62 is_part_of_annotation, 

63 is_rpar_token, 

64 is_stub_body, 

65 is_stub_suite, 

66 is_tuple, 

67 is_tuple_containing_star, 

68 is_tuple_containing_walrus, 

69 is_type_ignore_comment_string, 

70 is_vararg, 

71 is_walrus_assignment, 

72 is_yield, 

73 syms, 

74 wrap_in_parentheses, 

75) 

76from black.numerics import normalize_numeric_literal 

77from black.strings import ( 

78 fix_multiline_docstring, 

79 get_string_prefix, 

80 normalize_string_prefix, 

81 normalize_string_quotes, 

82 normalize_unicode_escape_sequences, 

83) 

84from black.trans import ( 

85 CannotTransform, 

86 StringMerger, 

87 StringParenStripper, 

88 StringParenWrapper, 

89 StringSplitter, 

90 Transformer, 

91 hug_power_op, 

92) 

93from blib2to3.pgen2 import token 

94from blib2to3.pytree import Leaf, Node 

95 

96# types 

97LeafID = int 

98LN = Union[Leaf, Node] 

99 

100 

101class CannotSplit(CannotTransform): 

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

103 

104 

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

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

107class LineGenerator(Visitor[Line]): 

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

109 

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

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

112 """ 

113 

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

115 self.mode = mode 

116 self.features = features 

117 self.current_line: Line 

118 self.__post_init__() 

119 

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

121 """Generate a line. 

122 

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

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

125 

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

127 """ 

128 if not self.current_line: 

129 self.current_line.depth += indent 

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

131 

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

133 self.current_line.leaves[0] 

134 ): 

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

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

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

138 # `ASYNC` leaf as a complete line. 

139 return 

140 

141 complete_line = self.current_line 

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

143 yield complete_line 

144 

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

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

147 if isinstance(node, Leaf): 

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

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

150 if any_open_brackets: 

151 # any comment within brackets is subject to splitting 

152 self.current_line.append(comment) 

153 elif comment.type == token.COMMENT: 

154 # regular trailing comment 

155 self.current_line.append(comment) 

156 yield from self.line() 

157 

158 else: 

159 # regular standalone comment 

160 yield from self.line() 

161 

162 self.current_line.append(comment) 

163 yield from self.line() 

164 

165 if any_open_brackets: 

166 node.prefix = "" 

167 if node.type not in WHITESPACE: 

168 self.current_line.append(node) 

169 yield from super().visit_default(node) 

170 

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

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

173 

174 already_parenthesized = ( 

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

176 ) 

177 

178 if not already_parenthesized: 

179 # Similar to logic in wrap_in_parentheses 

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

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

182 prefix = node.prefix 

183 node.prefix = "" 

184 lpar.prefix = prefix 

185 node.insert_child(0, lpar) 

186 node.append_child(rpar) 

187 

188 yield from self.visit_default(node) 

189 

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

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

192 # In blib2to3 INDENT never holds comments. 

193 yield from self.line(+1) 

194 yield from self.visit_default(node) 

195 

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

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

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

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

200 # Emit the line then. 

201 yield from self.line() 

202 

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

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

205 yield from self.visit_default(node) 

206 

207 # Finally, emit the dedent. 

208 yield from self.line(-1) 

209 

210 def visit_stmt( 

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

212 ) -> Iterator[Line]: 

213 """Visit a statement. 

214 

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

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

217 

218 The relevant Python language `keywords` for a given statement will be 

219 NAME leaves within it. This methods puts those on a separate line. 

220 

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

222 invisible parens should be put. 

223 """ 

224 normalize_invisible_parens( 

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

226 ) 

227 for child in node.children: 

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

229 yield from self.line() 

230 

231 yield from self.visit(child) 

232 

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

234 yield from self.visit_default(node) 

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

236 

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

238 yield from self.visit_default(node) 

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

240 

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

242 yield from self.visit_default(node) 

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

244 

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

246 if Preview.wrap_long_dict_values_in_parens in self.mode: 

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

248 if i == 0: 

249 continue 

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

251 if ( 

252 child.type == syms.atom 

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

254 and not is_walrus_assignment(child) 

255 ): 

256 maybe_make_parens_invisible_in_atom( 

257 child, 

258 parent=node, 

259 mode=self.mode, 

260 features=self.features, 

261 remove_brackets_around_comma=False, 

262 ) 

263 else: 

264 wrap_in_parentheses(node, child, visible=False) 

265 yield from self.visit_default(node) 

266 

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

268 """Visit function definition.""" 

269 yield from self.line() 

270 

271 # Remove redundant brackets around return type annotation. 

272 is_return_annotation = False 

273 for child in node.children: 

274 if child.type == token.RARROW: 

275 is_return_annotation = True 

276 elif is_return_annotation: 

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

278 if maybe_make_parens_invisible_in_atom( 

279 child, 

280 parent=node, 

281 mode=self.mode, 

282 features=self.features, 

283 remove_brackets_around_comma=False, 

284 ): 

285 wrap_in_parentheses(node, child, visible=False) 

286 else: 

287 wrap_in_parentheses(node, child, visible=False) 

288 is_return_annotation = False 

289 

290 for child in node.children: 

291 yield from self.visit(child) 

292 

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

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

295 normalize_invisible_parens( 

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

297 ) 

298 

299 yield from self.line() 

300 for child in node.children: 

301 yield from self.visit(child) 

302 

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

304 """Visit a suite.""" 

305 if is_stub_suite(node): 

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

307 else: 

308 yield from self.visit_default(node) 

309 

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

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

312 prev_type: int | None = None 

313 for child in node.children: 

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

315 wrap_in_parentheses(node, child, visible=False) 

316 prev_type = child.type 

317 

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

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

320 yield from self.visit_default(node) 

321 else: 

322 yield from self.line(+1) 

323 yield from self.visit_default(node) 

324 yield from self.line(-1) 

325 

326 else: 

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

328 node.prefix = "" 

329 yield from self.visit_default(node) 

330 return 

331 yield from self.line() 

332 yield from self.visit_default(node) 

333 

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

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

336 yield from self.line() 

337 

338 children = iter(node.children) 

339 for child in children: 

340 yield from self.visit(child) 

341 

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

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

344 # line. 

345 break 

346 

347 internal_stmt = next(children) 

348 yield from self.visit(internal_stmt) 

349 

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

351 """Visit decorators.""" 

352 for child in node.children: 

353 yield from self.line() 

354 yield from self.visit(child) 

355 

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

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

358 next_leaf = node.children[idx + 1] 

359 

360 if not isinstance(leaf, Leaf): 

361 continue 

362 

363 value = leaf.value.lower() 

364 if ( 

365 leaf.type == token.NUMBER 

366 and next_leaf.type == syms.trailer 

367 # Ensure that we are in an attribute trailer 

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

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

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

371 # It shouldn't wrap complex literals 

372 and "j" not in value 

373 ): 

374 wrap_in_parentheses(node, leaf) 

375 

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

377 

378 yield from self.visit_default(node) 

379 

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

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

382 yield from self.line() 

383 

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

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

386 yield from self.visit_default(leaf) 

387 yield from self.line() 

388 

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

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

391 if not any_open_brackets: 

392 yield from self.line() 

393 # STANDALONE_COMMENT nodes created by our special handling in 

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

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

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

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

398 # lose indentation. Normal STANDALONE_COMMENT nodes go through 

399 # visit_default. 

400 value = leaf.value 

401 lines = value.splitlines() 

402 is_fmt_off_block = ( 

403 len(lines) >= 2 

404 and contains_fmt_directive(lines[0], FMT_OFF) 

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

406 ) 

407 if is_fmt_off_block: 

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

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

410 # the fmt block itself directly to preserve its formatting 

411 

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

413 if leaf.prefix and any( 

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

415 and not contains_fmt_directive(line.strip()) 

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

417 ): 

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

419 yield from self.line() 

420 self.current_line.append(comment) 

421 yield from self.line() 

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

423 leaf.prefix = "" 

424 

425 self.current_line.append(leaf) 

426 if not any_open_brackets: 

427 yield from self.line() 

428 else: 

429 # Normal standalone comment - process through visit_default 

430 yield from self.visit_default(leaf) 

431 

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

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

434 

435 -2 ** 8 -> -(2 ** 8) 

436 """ 

437 _operator, operand = node.children 

438 if ( 

439 operand.type == syms.power 

440 and len(operand.children) == 3 

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

442 ): 

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

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

445 index = operand.remove() or 0 

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

447 yield from self.visit_default(node) 

448 

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

450 """ 

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

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

453 Examples: 

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

455 

456 -> 

457 

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

459 """ 

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

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

462 ): 

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

464 

465 yield from self.visit_default(node) 

466 

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

468 normalize_unicode_escape_sequences(leaf) 

469 

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

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

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

473 if self.mode.string_normalization: 

474 docstring = normalize_string_prefix(leaf.value) 

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

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

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

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

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

480 docstring = normalize_string_quotes(docstring) 

481 else: 

482 docstring = leaf.value 

483 prefix = get_string_prefix(docstring) 

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

485 quote_char = docstring[0] 

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

487 # docstring = docstring.strip(quote_char) 

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

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

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

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

492 docstring = docstring[quote_len:-quote_len] 

493 docstring_started_empty = not docstring 

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

495 

496 if is_multiline_string(leaf): 

497 docstring = fix_multiline_docstring(docstring, indent) 

498 else: 

499 docstring = docstring.strip() 

500 

501 has_trailing_backslash = False 

502 if docstring: 

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

504 if docstring[0] == quote_char: 

505 docstring = " " + docstring 

506 if docstring[-1] == quote_char: 

507 docstring += " " 

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

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

510 if backslash_count % 2: 

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

512 # avoid escaping the closing string quote. 

513 docstring += " " 

514 has_trailing_backslash = True 

515 elif not docstring_started_empty: 

516 docstring = " " 

517 

518 # We could enforce triple quotes at this point. 

519 quote = quote_char * quote_len 

520 

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

522 if quote_len == 3: 

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

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

525 # exceeding the maximum line length. 

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

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

528 lines = docstring.splitlines() 

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

530 

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

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

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

534 # the closing quotes 

535 if ( 

536 len(lines) > 1 

537 and last_line_length + quote_len > self.mode.line_length 

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

539 and not has_trailing_backslash 

540 ): 

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

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

543 else: 

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

545 else: 

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

547 else: 

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

549 

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

551 leaf.value = normalize_string_prefix(leaf.value) 

552 leaf.value = normalize_string_quotes(leaf.value) 

553 yield from self.visit_default(leaf) 

554 

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

556 normalize_numeric_literal(leaf) 

557 yield from self.visit_default(leaf) 

558 

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

560 """Visit any atom""" 

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

562 first = node.children[0] 

563 last = node.children[-1] 

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

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

566 ): 

567 # Lists or sets of one item 

568 maybe_make_parens_invisible_in_atom( 

569 node.children[1], 

570 parent=node, 

571 mode=self.mode, 

572 features=self.features, 

573 ) 

574 

575 yield from self.visit_default(node) 

576 

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

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

579 string_leaf = fstring_tstring_to_string(node) 

580 node.replace(string_leaf) 

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

582 "\\" in str(child) 

583 for child in node.children 

584 if child.type == syms.fstring_replacement_field 

585 ): 

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

587 # causing breakages. skip normalization when nested quotes exist 

588 yield from self.visit_default(string_leaf) 

589 return 

590 yield from self.visit_STRING(string_leaf) 

591 

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

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

594 string_leaf = fstring_tstring_to_string(node) 

595 node.replace(string_leaf) 

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

597 "\\" in str(child) 

598 for child in node.children 

599 if child.type == syms.fstring_replacement_field 

600 ): 

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

602 # causing breakages. skip normalization when nested quotes exist 

603 yield from self.visit_default(string_leaf) 

604 return 

605 yield from self.visit_STRING(string_leaf) 

606 

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

608 # fstring_start = node.children[0] 

609 # fstring_end = node.children[-1] 

610 # assert isinstance(fstring_start, Leaf) 

611 # assert isinstance(fstring_end, Leaf) 

612 

613 # quote_char = fstring_end.value[0] 

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

615 # prefix, quote = ( 

616 # fstring_start.value[:quote_idx], 

617 # fstring_start.value[quote_idx:] 

618 # ) 

619 

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

621 # prefix = normalize_string_prefix(prefix) 

622 

623 # assert quote == fstring_end.value 

624 

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

626 # middles = [ 

627 # leaf 

628 # for leaf in node.leaves() 

629 # if leaf.type == token.FSTRING_MIDDLE 

630 # ] 

631 

632 # if self.mode.string_normalization: 

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

634 

635 # fstring_start.value = prefix + quote 

636 # fstring_end.value = quote 

637 

638 # yield from self.visit_default(node) 

639 

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

641 if Preview.wrap_comprehension_in in self.mode: 

642 normalize_invisible_parens( 

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

644 ) 

645 yield from self.visit_default(node) 

646 

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

648 yield from self.visit_comp_for(node) 

649 

650 def __post_init__(self) -> None: 

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

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

653 

654 v = self.visit_stmt 

655 Ø: set[str] = set() 

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

657 self.visit_if_stmt = partial( 

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

659 ) 

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

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

662 self.visit_try_stmt = partial( 

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

664 ) 

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

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

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

668 

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

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

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

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

673 self.visit_async_funcdef = self.visit_async_stmt 

674 self.visit_decorated = self.visit_decorators 

675 

676 # PEP 634 

677 self.visit_match_stmt = self.visit_match_case 

678 self.visit_case_block = self.visit_match_case 

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

680 

681 

682def _hugging_power_ops_line_to_string( 

683 line: Line, 

684 features: Collection[Feature], 

685 mode: Mode, 

686) -> str | None: 

687 try: 

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

689 except CannotTransform: 

690 return None 

691 

692 

693def transform_line( 

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

695) -> Iterator[Line]: 

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

697 

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

699 

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

701 """ 

702 if line.is_comment: 

703 yield line 

704 return 

705 

706 line_str = line_to_string(line) 

707 

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

709 # split the line. Default to line_str, if no power operator are present on the line. 

710 line_str_hugging_power_ops = ( 

711 _hugging_power_ops_line_to_string(line, features, mode) or line_str 

712 ) 

713 

714 ll = mode.line_length 

715 sn = mode.string_normalization 

716 string_merge = StringMerger(ll, sn) 

717 string_paren_strip = StringParenStripper(ll, sn) 

718 string_split = StringSplitter(ll, sn) 

719 string_paren_wrap = StringParenWrapper(ll, sn) 

720 

721 transformers: list[Transformer] 

722 if ( 

723 not line.contains_uncollapsable_type_comments() 

724 and not line.should_split_rhs 

725 and not line.magic_trailing_comma 

726 and ( 

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

728 or line.contains_unsplittable_type_ignore() 

729 ) 

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

731 and not line.contains_implicit_multiline_string_with_comments() 

732 ): 

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

734 if Preview.string_processing in mode: 

735 transformers = [string_merge, string_paren_strip] 

736 else: 

737 transformers = [] 

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

739 transformers = [left_hand_split] 

740 else: 

741 

742 def _rhs( 

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

744 ) -> Iterator[Line]: 

745 """Wraps calls to `right_hand_split`. 

746 

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

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

749 bracket pair instead. 

750 """ 

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

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

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

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

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

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

757 if is_line_short_enough(lines[0], mode=mode): 

758 yield from lines 

759 return 

760 

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

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

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

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

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

766 

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

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

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

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

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

772 

773 if Preview.string_processing in mode: 

774 if line.inside_brackets: 

775 transformers = [ 

776 string_merge, 

777 string_paren_strip, 

778 string_split, 

779 delimiter_split, 

780 standalone_comment_split, 

781 string_paren_wrap, 

782 rhs, 

783 ] 

784 else: 

785 transformers = [ 

786 string_merge, 

787 string_paren_strip, 

788 string_split, 

789 string_paren_wrap, 

790 rhs, 

791 ] 

792 else: 

793 if line.inside_brackets: 

794 transformers = [delimiter_split, standalone_comment_split, rhs] 

795 else: 

796 transformers = [rhs] 

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

798 # could match. 

799 transformers.append(hug_power_op) 

800 

801 for transform in transformers: 

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

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

804 # split altogether. 

805 try: 

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

807 except CannotTransform: 

808 continue 

809 else: 

810 yield from result 

811 break 

812 

813 else: 

814 yield line 

815 

816 

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

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

819 split the line with rhs to respect the comma. 

820 """ 

821 return_type_leaves: list[Leaf] = [] 

822 in_return_type = False 

823 

824 for leaf in line.leaves: 

825 if leaf.type == token.COLON: 

826 in_return_type = False 

827 if in_return_type: 

828 return_type_leaves.append(leaf) 

829 if leaf.type == token.RARROW: 

830 in_return_type = True 

831 

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

833 # couple lines from it. 

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

835 leaves_to_track = get_leaves_inside_matching_brackets(return_type_leaves) 

836 for leaf in return_type_leaves: 

837 result.append( 

838 leaf, 

839 preformatted=True, 

840 track_bracket=id(leaf) in leaves_to_track, 

841 ) 

842 

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

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

845 return result.magic_trailing_comma is not None 

846 

847 

848class _BracketSplitComponent(Enum): 

849 head = auto() 

850 body = auto() 

851 tail = auto() 

852 

853 

854def left_hand_split( 

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

856) -> Iterator[Line]: 

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

858 

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

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

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

862 """ 

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

864 tail_leaves: list[Leaf] = [] 

865 body_leaves: list[Leaf] = [] 

866 head_leaves: list[Leaf] = [] 

867 current_leaves = head_leaves 

868 matching_bracket: Leaf | None = None 

869 depth = 0 

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

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

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

873 # tracking the depth 

874 depth += 1 

875 elif depth > 0: 

876 if leaf.type == token.LSQB: 

877 depth += 1 

878 elif leaf.type == token.RSQB: 

879 depth -= 1 

880 if ( 

881 current_leaves is body_leaves 

882 and leaf.type in CLOSING_BRACKETS 

883 and leaf.opening_bracket is matching_bracket 

884 and isinstance(matching_bracket, Leaf) 

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

886 # param, ignore the match since this is searching 

887 # for the function arguments 

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

889 ): 

890 ensure_visible(leaf) 

891 ensure_visible(matching_bracket) 

892 current_leaves = tail_leaves if body_leaves else head_leaves 

893 current_leaves.append(leaf) 

894 if current_leaves is head_leaves: 

895 if leaf.type == leaf_type and ( 

896 Preview.fix_type_expansion_split not in mode 

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

898 ): 

899 matching_bracket = leaf 

900 current_leaves = body_leaves 

901 if matching_bracket and tail_leaves: 

902 break 

903 if not matching_bracket or not tail_leaves: 

904 raise CannotSplit("No brackets found") 

905 

906 head = bracket_split_build_line( 

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

908 ) 

909 body = bracket_split_build_line( 

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

911 ) 

912 tail = bracket_split_build_line( 

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

914 ) 

915 bracket_split_succeeded_or_raise(head, body, tail) 

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

917 if result: 

918 yield result 

919 

920 

921def right_hand_split( 

922 line: Line, 

923 mode: Mode, 

924 features: Collection[Feature] = (), 

925 omit: Collection[LeafID] = (), 

926) -> Iterator[Line]: 

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

928 

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

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

931 this split. 

932 

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

934 """ 

935 rhs_result = _first_right_hand_split(line, omit=omit) 

936 yield from _maybe_split_omitting_optional_parens( 

937 rhs_result, line, mode, features=features, omit=omit 

938 ) 

939 

940 

941def _first_right_hand_split( 

942 line: Line, 

943 omit: Collection[LeafID] = (), 

944) -> RHSResult: 

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

946 

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

948 _maybe_split_omitting_optional_parens to get an opinion whether to prefer 

949 splitting on the right side of an assignment statement. 

950 """ 

951 tail_leaves: list[Leaf] = [] 

952 body_leaves: list[Leaf] = [] 

953 head_leaves: list[Leaf] = [] 

954 current_leaves = tail_leaves 

955 opening_bracket: Leaf | None = None 

956 closing_bracket: Leaf | None = None 

957 for leaf in reversed(line.leaves): 

958 if current_leaves is body_leaves: 

959 if leaf is opening_bracket: 

960 current_leaves = head_leaves if body_leaves else tail_leaves 

961 current_leaves.append(leaf) 

962 if current_leaves is tail_leaves: 

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

964 opening_bracket = leaf.opening_bracket 

965 closing_bracket = leaf 

966 current_leaves = body_leaves 

967 if not (opening_bracket and closing_bracket and head_leaves): 

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

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

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

971 raise CannotSplit("No brackets found") 

972 

973 tail_leaves.reverse() 

974 body_leaves.reverse() 

975 head_leaves.reverse() 

976 

977 body: Line | None = None 

978 if ( 

979 Preview.hug_parens_with_braces_and_square_brackets in line.mode 

980 and tail_leaves[0].value 

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

982 ): 

983 inner_body_leaves = list(body_leaves) 

984 hugged_opening_leaves: list[Leaf] = [] 

985 hugged_closing_leaves: list[Leaf] = [] 

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

987 unpacking_offset: int = 1 if is_unpacking else 0 

988 while ( 

989 len(inner_body_leaves) >= 2 + unpacking_offset 

990 and inner_body_leaves[-1].type in CLOSING_BRACKETS 

991 and inner_body_leaves[-1].opening_bracket 

992 is inner_body_leaves[unpacking_offset] 

993 ): 

994 if unpacking_offset: 

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

996 unpacking_offset = 0 

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

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

999 

1000 if hugged_opening_leaves and inner_body_leaves: 

1001 inner_body = bracket_split_build_line( 

1002 inner_body_leaves, 

1003 line, 

1004 hugged_opening_leaves[-1], 

1005 component=_BracketSplitComponent.body, 

1006 ) 

1007 if ( 

1008 line.mode.magic_trailing_comma 

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

1010 ): 

1011 should_hug = True 

1012 else: 

1013 line_length = line.mode.line_length - sum( 

1014 len(str(leaf)) 

1015 for leaf in hugged_opening_leaves + hugged_closing_leaves 

1016 ) 

1017 if is_line_short_enough( 

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

1019 ): 

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

1021 should_hug = False 

1022 else: 

1023 should_hug = True 

1024 if should_hug: 

1025 body_leaves = inner_body_leaves 

1026 head_leaves.extend(hugged_opening_leaves) 

1027 tail_leaves = hugged_closing_leaves + tail_leaves 

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

1029 

1030 head = bracket_split_build_line( 

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

1032 ) 

1033 if body is None: 

1034 body = bracket_split_build_line( 

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

1036 ) 

1037 tail = bracket_split_build_line( 

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

1039 ) 

1040 bracket_split_succeeded_or_raise(head, body, tail) 

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

1042 

1043 

1044def _maybe_split_omitting_optional_parens( 

1045 rhs: RHSResult, 

1046 line: Line, 

1047 mode: Mode, 

1048 features: Collection[Feature] = (), 

1049 omit: Collection[LeafID] = (), 

1050) -> Iterator[Line]: 

1051 if ( 

1052 Feature.FORCE_OPTIONAL_PARENTHESES not in features 

1053 # the opening bracket is an optional paren 

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

1055 and not rhs.opening_bracket.value 

1056 # the closing bracket is an optional paren 

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

1058 and not rhs.closing_bracket.value 

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

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

1061 and not line.is_import 

1062 # and we can actually remove the parens 

1063 and can_omit_invisible_parens(rhs, mode.line_length) 

1064 ): 

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

1066 try: 

1067 # The RHSResult Omitting Optional Parens. 

1068 rhs_oop = _first_right_hand_split(line, omit=omit) 

1069 if _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode): 

1070 yield from _maybe_split_omitting_optional_parens( 

1071 rhs_oop, line, mode, features=features, omit=omit 

1072 ) 

1073 return 

1074 

1075 except CannotSplit as e: 

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

1077 if line.is_chained_assignment: 

1078 pass 

1079 

1080 elif ( 

1081 not can_be_split(rhs.body) 

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

1083 and not ( 

1084 Preview.wrap_long_dict_values_in_parens 

1085 and rhs.opening_bracket.parent 

1086 and rhs.opening_bracket.parent.parent 

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

1088 ) 

1089 ): 

1090 raise CannotSplit( 

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

1092 ) from e 

1093 

1094 elif ( 

1095 rhs.head.contains_multiline_strings() 

1096 or rhs.tail.contains_multiline_strings() 

1097 ): 

1098 raise CannotSplit( 

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

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

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

1102 " line." 

1103 ) from e 

1104 

1105 ensure_visible(rhs.opening_bracket) 

1106 ensure_visible(rhs.closing_bracket) 

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

1108 if result: 

1109 yield result 

1110 

1111 

1112def _prefer_split_rhs_oop_over_rhs( 

1113 rhs_oop: RHSResult, rhs: RHSResult, mode: Mode 

1114) -> bool: 

1115 """ 

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

1117 (rhs_oop) over the original (rhs). 

1118 """ 

1119 # contains unsplittable type ignore 

1120 if ( 

1121 rhs_oop.head.contains_unsplittable_type_ignore() 

1122 or rhs_oop.body.contains_unsplittable_type_ignore() 

1123 or rhs_oop.tail.contains_unsplittable_type_ignore() 

1124 ): 

1125 return True 

1126 

1127 # Retain optional parens around dictionary values 

1128 if ( 

1129 Preview.wrap_long_dict_values_in_parens 

1130 and rhs.opening_bracket.parent 

1131 and rhs.opening_bracket.parent.parent 

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

1133 and rhs.body.bracket_tracker.delimiters 

1134 ): 

1135 # Unless the split is inside the key 

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

1137 

1138 # the split is right after `=` 

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

1140 return True 

1141 

1142 # the left side of assignment contains brackets 

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

1144 return True 

1145 

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

1147 # paren) 

1148 if not is_line_short_enough( 

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

1150 ): 

1151 return True 

1152 

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

1154 if rhs.head.magic_trailing_comma is not None: 

1155 return True 

1156 

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

1158 # the body 

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

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

1161 token.EQUAL 

1162 ) 

1163 if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count: 

1164 return False 

1165 

1166 has_closing_bracket_after_assign = False 

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

1168 if leaf.type == token.EQUAL: 

1169 break 

1170 if leaf.type in CLOSING_BRACKETS: 

1171 has_closing_bracket_after_assign = True 

1172 break 

1173 return ( 

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

1175 # closing bracket) 

1176 has_closing_bracket_after_assign 

1177 or ( 

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

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

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

1181 # the first line is short enough 

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

1183 ) 

1184 ) 

1185 

1186 

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

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

1189 

1190 Do nothing otherwise. 

1191 

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

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

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

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

1196 

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

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

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

1200 """ 

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

1202 if not body: 

1203 if tail_len == 0: 

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

1205 

1206 elif tail_len < 3: 

1207 raise CannotSplit( 

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

1209 " not worth it" 

1210 ) 

1211 

1212 

1213def _ensure_trailing_comma( 

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

1215) -> bool: 

1216 if not leaves: 

1217 return False 

1218 # Ensure a trailing comma for imports 

1219 if original.is_import: 

1220 return True 

1221 # ...and standalone function arguments 

1222 if not original.is_def: 

1223 return False 

1224 if opening_bracket.value != "(": 

1225 return False 

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

1227 if any( 

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

1229 ): 

1230 return False 

1231 

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

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

1234 if leaf_with_parent is None: 

1235 return True 

1236 # Don't add commas inside parenthesized return annotations 

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

1238 return False 

1239 # Don't add commas inside PEP 604 unions 

1240 if ( 

1241 leaf_with_parent.parent 

1242 and leaf_with_parent.parent.next_sibling 

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

1244 ): 

1245 return False 

1246 return True 

1247 

1248 

1249def bracket_split_build_line( 

1250 leaves: list[Leaf], 

1251 original: Line, 

1252 opening_bracket: Leaf, 

1253 *, 

1254 component: _BracketSplitComponent, 

1255) -> Line: 

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

1257 

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

1259 respected. 

1260 

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

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

1263 expected. 

1264 """ 

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

1266 if component is _BracketSplitComponent.body: 

1267 result.inside_brackets = True 

1268 result.depth += 1 

1269 if _ensure_trailing_comma(leaves, original, opening_bracket): 

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

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

1272 continue 

1273 

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

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

1276 leaves.insert(i + 1, new_comma) 

1277 break 

1278 

1279 leaves_to_track: set[LeafID] = set() 

1280 if component is _BracketSplitComponent.head: 

1281 leaves_to_track = get_leaves_inside_matching_brackets(leaves) 

1282 # Populate the line 

1283 for leaf in leaves: 

1284 result.append( 

1285 leaf, 

1286 preformatted=True, 

1287 track_bracket=id(leaf) in leaves_to_track, 

1288 ) 

1289 for comment_after in original.comments_after(leaf): 

1290 result.append(comment_after, preformatted=True) 

1291 if component is _BracketSplitComponent.body and should_split_line( 

1292 result, opening_bracket 

1293 ): 

1294 result.should_split_rhs = True 

1295 return result 

1296 

1297 

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

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

1300 

1301 This is a decorator over relevant split functions. 

1302 """ 

1303 

1304 @wraps(split_func) 

1305 def split_wrapper( 

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

1307 ) -> Iterator[Line]: 

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

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

1310 yield split_line 

1311 

1312 return split_wrapper 

1313 

1314 

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

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

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

1318 return leaf_idx 

1319 return None 

1320 

1321 

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

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

1324 return Feature.TRAILING_COMMA_IN_DEF in features 

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

1326 return Feature.TRAILING_COMMA_IN_CALL in features 

1327 return True 

1328 

1329 

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

1331 if ( 

1332 safe 

1333 and delimiter_priority == COMMA_PRIORITY 

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

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

1336 ): 

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

1338 line.append(new_comma) 

1339 return line 

1340 

1341 

1342MIGRATE_COMMENT_DELIMITERS = {STRING_PRIORITY, COMMA_PRIORITY} 

1343 

1344 

1345@dont_increase_indentation 

1346def delimiter_split( 

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

1348) -> Iterator[Line]: 

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

1350 

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

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

1353 """ 

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

1355 raise CannotSplit("Line empty") from None 

1356 last_leaf = line.leaves[-1] 

1357 

1358 bt = line.bracket_tracker 

1359 try: 

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

1361 except ValueError: 

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

1363 

1364 if ( 

1365 delimiter_priority == DOT_PRIORITY 

1366 and bt.delimiter_count_with_priority(delimiter_priority) == 1 

1367 ): 

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

1369 

1370 current_line = Line( 

1371 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1372 ) 

1373 lowest_depth = sys.maxsize 

1374 trailing_comma_safe = True 

1375 

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

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

1378 nonlocal current_line 

1379 try: 

1380 current_line.append_safe(leaf, preformatted=True) 

1381 except ValueError: 

1382 yield current_line 

1383 

1384 current_line = Line( 

1385 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1386 ) 

1387 current_line.append(leaf) 

1388 

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

1390 for comment_after in line.comments_after(leaf): 

1391 yield from append_to_line(comment_after) 

1392 

1393 last_non_comment_leaf = _get_last_non_comment_leaf(line) 

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

1395 yield from append_to_line(leaf) 

1396 

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

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

1399 ) 

1400 if ( 

1401 previous_priority != delimiter_priority 

1402 or delimiter_priority in MIGRATE_COMMENT_DELIMITERS 

1403 ): 

1404 yield from append_comments(leaf) 

1405 

1406 lowest_depth = min(lowest_depth, leaf.bracket_depth) 

1407 if trailing_comma_safe and leaf.bracket_depth == lowest_depth: 

1408 trailing_comma_safe = _can_add_trailing_comma(leaf, features) 

1409 

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

1411 current_line = _safe_add_trailing_comma( 

1412 trailing_comma_safe, delimiter_priority, current_line 

1413 ) 

1414 

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

1416 if leaf_priority == delimiter_priority: 

1417 if ( 

1418 leaf_idx + 1 < len(line.leaves) 

1419 and delimiter_priority not in MIGRATE_COMMENT_DELIMITERS 

1420 ): 

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

1422 

1423 yield current_line 

1424 current_line = Line( 

1425 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1426 ) 

1427 

1428 if current_line: 

1429 current_line = _safe_add_trailing_comma( 

1430 trailing_comma_safe, delimiter_priority, current_line 

1431 ) 

1432 yield current_line 

1433 

1434 

1435@dont_increase_indentation 

1436def standalone_comment_split( 

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

1438) -> Iterator[Line]: 

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

1440 if not line.contains_standalone_comments(): 

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

1442 

1443 current_line = Line( 

1444 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1445 ) 

1446 

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

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

1449 nonlocal current_line 

1450 try: 

1451 current_line.append_safe(leaf, preformatted=True) 

1452 except ValueError: 

1453 yield current_line 

1454 

1455 current_line = Line( 

1456 line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1457 ) 

1458 current_line.append(leaf) 

1459 

1460 for leaf in line.leaves: 

1461 yield from append_to_line(leaf) 

1462 

1463 for comment_after in line.comments_after(leaf): 

1464 yield from append_to_line(comment_after) 

1465 

1466 if current_line: 

1467 yield current_line 

1468 

1469 

1470def normalize_invisible_parens( 

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

1472) -> None: 

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

1474 

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

1476 should be put. 

1477 

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

1479 existing visible parentheses for other tuples and generator expressions. 

1480 """ 

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

1482 if contains_fmt_directive(pc.value, FMT_OFF): 

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

1484 return 

1485 

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

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

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

1489 if node.type == syms.with_stmt: 

1490 _maybe_wrap_cms_in_parens(node, mode, features) 

1491 

1492 check_lpar = False 

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

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

1495 # assignment statements that contain type annotations. 

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

1497 normalize_invisible_parens( 

1498 child, parens_after=parens_after, mode=mode, features=features 

1499 ) 

1500 

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

1502 # case blocks. 

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

1504 normalize_invisible_parens( 

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

1506 ) 

1507 

1508 # Add parentheses around if guards in case blocks 

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

1510 normalize_invisible_parens( 

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

1512 ) 

1513 

1514 # Add parentheses around long tuple unpacking in assignments. 

1515 if ( 

1516 index == 0 

1517 and isinstance(child, Node) 

1518 and child.type == syms.testlist_star_expr 

1519 ): 

1520 check_lpar = True 

1521 

1522 # Check for assignment LHS with preview feature enabled 

1523 if ( 

1524 Preview.remove_parens_from_assignment_lhs in mode 

1525 and index == 0 

1526 and isinstance(child, Node) 

1527 and child.type == syms.atom 

1528 and node.type == syms.expr_stmt 

1529 and not _atom_has_magic_trailing_comma(child, mode) 

1530 and not _is_atom_multiline(child) 

1531 ): 

1532 if maybe_make_parens_invisible_in_atom( 

1533 child, 

1534 parent=node, 

1535 mode=mode, 

1536 features=features, 

1537 remove_brackets_around_comma=True, 

1538 allow_star_expr=True, 

1539 ): 

1540 wrap_in_parentheses(node, child, visible=False) 

1541 

1542 if check_lpar: 

1543 if ( 

1544 child.type == syms.atom 

1545 and node.type == syms.for_stmt 

1546 and isinstance(child.prev_sibling, Leaf) 

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

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

1549 ): 

1550 if maybe_make_parens_invisible_in_atom( 

1551 child, 

1552 parent=node, 

1553 mode=mode, 

1554 features=features, 

1555 remove_brackets_around_comma=True, 

1556 ): 

1557 wrap_in_parentheses(node, child, visible=False) 

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

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

1560 elif child.type == syms.atom and not ( 

1561 "in" in parens_after 

1562 and len(child.children) == 3 

1563 and is_lpar_token(child.children[0]) 

1564 and is_rpar_token(child.children[-1]) 

1565 and child.children[1].type == syms.test 

1566 ): 

1567 if maybe_make_parens_invisible_in_atom( 

1568 child, parent=node, mode=mode, features=features 

1569 ): 

1570 wrap_in_parentheses(node, child, visible=False) 

1571 elif is_one_tuple(child): 

1572 wrap_in_parentheses(node, child, visible=True) 

1573 elif node.type == syms.import_from: 

1574 _normalize_import_from(node, child, index) 

1575 break 

1576 elif ( 

1577 index == 1 

1578 and child.type == token.STAR 

1579 and node.type == syms.except_clause 

1580 ): 

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

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

1583 # invisible parentheses to work more precisely. 

1584 continue 

1585 

1586 elif ( 

1587 isinstance(child, Leaf) 

1588 and child.next_sibling is not None 

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

1590 and child.value == "case" 

1591 ): 

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

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

1594 break 

1595 

1596 elif not is_multiline_string(child): 

1597 wrap_in_parentheses(node, child, visible=False) 

1598 

1599 comma_check = child.type == token.COMMA 

1600 

1601 check_lpar = isinstance(child, Leaf) and ( 

1602 child.value in parens_after or comma_check 

1603 ) 

1604 

1605 

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

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

1608 # the statement 

1609 if is_lpar_token(child): 

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

1611 # make parentheses invisible 

1612 child.value = "" 

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

1614 elif child.type != token.STAR: 

1615 # insert invisible parentheses 

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

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

1618 

1619 

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

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

1622 if ( 

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

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

1625 ): 

1626 if maybe_make_parens_invisible_in_atom( 

1627 node.children[1], 

1628 parent=node, 

1629 mode=mode, 

1630 features=features, 

1631 remove_brackets_around_comma=True, 

1632 ): 

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

1634 

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

1636 # brackets in cases where this would change 

1637 # the AST due to operator precedence. 

1638 # Therefore we only aim to remove brackets around 

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

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

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

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

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

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

1645 if isinstance(bracket_contents, Node) and ( 

1646 bracket_contents.type != syms.power 

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

1648 or any( 

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

1650 for child in bracket_contents.children 

1651 ) 

1652 ): 

1653 ensure_visible(opening_bracket) 

1654 ensure_visible(closing_bracket) 

1655 

1656 

1657def _maybe_wrap_cms_in_parens( 

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

1659) -> None: 

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

1661 

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

1663 """ 

1664 if ( 

1665 Feature.PARENTHESIZED_CONTEXT_MANAGERS not in features 

1666 or len(node.children) <= 2 

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

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

1669 ): 

1670 return 

1671 colon_index: int | None = None 

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

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

1674 colon_index = i 

1675 break 

1676 if colon_index is not None: 

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

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

1679 context_managers = node.children[1:colon_index] 

1680 for child in context_managers: 

1681 child.remove() 

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

1683 # with_stmt 

1684 # NAME 'with' 

1685 # atom 

1686 # LPAR '' 

1687 # testlist_gexp 

1688 # ... <-- context_managers 

1689 # /testlist_gexp 

1690 # RPAR '' 

1691 # /atom 

1692 # COLON ':' 

1693 new_child = Node( 

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

1695 ) 

1696 node.insert_child(1, new_child) 

1697 

1698 

1699def remove_with_parens( 

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

1701) -> None: 

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

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

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

1705 # different parse trees: 

1706 # 

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

1708 # ... 

1709 # 

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

1711 # ... # asexpr_test 

1712 # 

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

1714 # ... # asexpr_test 

1715 # 

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

1717 # ... # testlist_gexp which then 

1718 # # contains multiple asexpr_test(s) 

1719 if node.type == syms.atom: 

1720 if maybe_make_parens_invisible_in_atom( 

1721 node, 

1722 parent=parent, 

1723 mode=mode, 

1724 features=features, 

1725 remove_brackets_around_comma=True, 

1726 ): 

1727 wrap_in_parentheses(parent, node, visible=False) 

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

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

1730 elif node.type == syms.testlist_gexp: 

1731 for child in node.children: 

1732 if isinstance(child, Node): 

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

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

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

1736 ): 

1737 if maybe_make_parens_invisible_in_atom( 

1738 node.children[0], 

1739 parent=node, 

1740 mode=mode, 

1741 features=features, 

1742 remove_brackets_around_comma=True, 

1743 ): 

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

1745 

1746 

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

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

1749 

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

1751 which should be preserved to maintain their tuple type. 

1752 """ 

1753 if not mode.magic_trailing_comma: 

1754 return False 

1755 

1756 return is_one_tuple(node) 

1757 

1758 

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

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

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

1762 return False 

1763 

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

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

1766 middle = node.children[1] 

1767 for child in middle.pre_order(): 

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

1769 return True 

1770 

1771 return False 

1772 

1773 

1774def maybe_make_parens_invisible_in_atom( 

1775 node: LN, 

1776 parent: LN, 

1777 mode: Mode, 

1778 features: Collection[Feature], 

1779 remove_brackets_around_comma: bool = False, 

1780 allow_star_expr: bool = False, 

1781) -> bool: 

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

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

1784 as they are redundant. 

1785 

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

1787 """ 

1788 if ( 

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

1790 or is_empty_tuple(node) 

1791 or is_one_tuple(node) 

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

1793 or ( 

1794 is_tuple(node) 

1795 and parent.type == syms.with_stmt 

1796 and has_sibling_with_type(node, token.COMMA) 

1797 ) 

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

1799 or ( 

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

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

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

1803 not remove_brackets_around_comma 

1804 and max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY 

1805 # Skip this check in Preview mode in order to 

1806 # Remove parentheses around multiple exception types in except and 

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

1808 and not ( 

1809 Preview.remove_parens_around_except_types in mode 

1810 and Feature.UNPARENTHESIZED_EXCEPT_TYPES in features 

1811 # is a tuple 

1812 and is_tuple(node) 

1813 # has a parent node 

1814 and node.parent is not None 

1815 # parent is an except clause 

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

1817 # is not immediately followed by as clause 

1818 and not ( 

1819 node.next_sibling is not None 

1820 and is_name_token(node.next_sibling) 

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

1822 ) 

1823 ) 

1824 ) 

1825 or is_tuple_containing_walrus(node) 

1826 or (not allow_star_expr and is_tuple_containing_star(node)) 

1827 or is_generator(node) 

1828 ): 

1829 return False 

1830 

1831 if is_walrus_assignment(node): 

1832 if parent.type in [ 

1833 syms.annassign, 

1834 syms.expr_stmt, 

1835 syms.assert_stmt, 

1836 syms.return_stmt, 

1837 syms.except_clause, 

1838 syms.funcdef, 

1839 syms.with_stmt, 

1840 syms.testlist_gexp, 

1841 syms.tname, 

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

1843 syms.for_stmt, 

1844 syms.del_stmt, 

1845 syms.for_stmt, 

1846 ]: 

1847 return False 

1848 

1849 first = node.children[0] 

1850 last = node.children[-1] 

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

1852 middle = node.children[1] 

1853 # make parentheses invisible 

1854 if ( 

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

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

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

1858 ): 

1859 first.value = "" 

1860 last.value = "" 

1861 maybe_make_parens_invisible_in_atom( 

1862 middle, 

1863 parent=parent, 

1864 mode=mode, 

1865 features=features, 

1866 remove_brackets_around_comma=remove_brackets_around_comma, 

1867 ) 

1868 

1869 if is_atom_with_invisible_parens(middle): 

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

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

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

1873 

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

1875 # Preserve comments before first paren 

1876 middle.children[1].prefix = ( 

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

1878 ) 

1879 

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

1881 # Preserve comments before last paren 

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

1883 

1884 return False 

1885 

1886 return True 

1887 

1888 

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

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

1891 

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

1893 return False 

1894 

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

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

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

1898 exclude = set() 

1899 trailing_comma = False 

1900 try: 

1901 last_leaf = line.leaves[-1] 

1902 if last_leaf.type == token.COMMA: 

1903 trailing_comma = True 

1904 exclude.add(id(last_leaf)) 

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

1906 except (IndexError, ValueError): 

1907 return False 

1908 

1909 return max_priority == COMMA_PRIORITY and ( 

1910 (line.mode.magic_trailing_comma and trailing_comma) 

1911 # always explode imports 

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

1913 ) 

1914 

1915 

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

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

1918 

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

1920 a preceding closing bracket fits in one line. 

1921 

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

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

1924 the one that needs to explode are omitted. 

1925 """ 

1926 

1927 omit: set[LeafID] = set() 

1928 if not line.magic_trailing_comma: 

1929 yield omit 

1930 

1931 length = 4 * line.depth 

1932 opening_bracket: Leaf | None = None 

1933 closing_bracket: Leaf | None = None 

1934 inner_brackets: set[LeafID] = set() 

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

1936 length += leaf_length 

1937 if length > line_length: 

1938 break 

1939 

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

1941 if leaf.type == STANDALONE_COMMENT or has_inline_comment: 

1942 break 

1943 

1944 if opening_bracket: 

1945 if leaf is opening_bracket: 

1946 opening_bracket = None 

1947 elif leaf.type in CLOSING_BRACKETS: 

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

1949 if ( 

1950 prev 

1951 and prev.type == token.COMMA 

1952 and leaf.opening_bracket is not None 

1953 and not is_one_sequence_between( 

1954 leaf.opening_bracket, leaf, line.leaves 

1955 ) 

1956 ): 

1957 # Never omit bracket pairs with trailing commas. 

1958 # We need to explode on those. 

1959 break 

1960 

1961 inner_brackets.add(id(leaf)) 

1962 elif leaf.type in CLOSING_BRACKETS: 

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

1964 if prev and prev.type in OPENING_BRACKETS: 

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

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

1967 # pair of brackets was good enough. 

1968 inner_brackets.add(id(leaf)) 

1969 continue 

1970 

1971 if closing_bracket: 

1972 omit.add(id(closing_bracket)) 

1973 omit.update(inner_brackets) 

1974 inner_brackets.clear() 

1975 yield omit 

1976 

1977 if ( 

1978 prev 

1979 and prev.type == token.COMMA 

1980 and leaf.opening_bracket is not None 

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

1982 ): 

1983 # Never omit bracket pairs with trailing commas. 

1984 # We need to explode on those. 

1985 break 

1986 

1987 if leaf.value: 

1988 opening_bracket = leaf.opening_bracket 

1989 closing_bracket = leaf 

1990 

1991 

1992def run_transformer( 

1993 line: Line, 

1994 transform: Transformer, 

1995 mode: Mode, 

1996 features: Collection[Feature], 

1997 *, 

1998 line_str: str = "", 

1999) -> list[Line]: 

2000 if not line_str: 

2001 line_str = line_to_string(line) 

2002 result: list[Line] = [] 

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

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

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

2006 

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

2008 

2009 features_set = set(features) 

2010 if ( 

2011 Feature.FORCE_OPTIONAL_PARENTHESES in features_set 

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

2013 or not line.bracket_tracker.invisible 

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

2015 or line.contains_multiline_strings() 

2016 or result[0].contains_uncollapsable_type_comments() 

2017 or result[0].contains_unsplittable_type_ignore() 

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

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

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

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

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

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

2024 ): 

2025 return result 

2026 

2027 line_copy = line.clone() 

2028 append_leaves(line_copy, line, line.leaves) 

2029 features_fop = features_set | {Feature.FORCE_OPTIONAL_PARENTHESES} 

2030 second_opinion = run_transformer( 

2031 line_copy, transform, mode, features_fop, line_str=line_str 

2032 ) 

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

2034 result = second_opinion 

2035 return result