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

800 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 Optional, 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 FMT_OFF, FMT_ON, generate_comments, list_comments 

21from black.lines import ( 

22 Line, 

23 RHSResult, 

24 append_leaves, 

25 can_be_split, 

26 can_omit_invisible_parens, 

27 is_line_short_enough, 

28 line_to_string, 

29) 

30from black.mode import Feature, Mode, Preview 

31from black.nodes import ( 

32 ASSIGNMENTS, 

33 BRACKETS, 

34 CLOSING_BRACKETS, 

35 OPENING_BRACKETS, 

36 STANDALONE_COMMENT, 

37 STATEMENT, 

38 WHITESPACE, 

39 Visitor, 

40 ensure_visible, 

41 fstring_to_string, 

42 get_annotation_type, 

43 has_sibling_with_type, 

44 is_arith_like, 

45 is_async_stmt_or_funcdef, 

46 is_atom_with_invisible_parens, 

47 is_docstring, 

48 is_empty_tuple, 

49 is_generator, 

50 is_lpar_token, 

51 is_multiline_string, 

52 is_name_token, 

53 is_one_sequence_between, 

54 is_one_tuple, 

55 is_parent_function_or_class, 

56 is_part_of_annotation, 

57 is_rpar_token, 

58 is_stub_body, 

59 is_stub_suite, 

60 is_tuple, 

61 is_tuple_containing_star, 

62 is_tuple_containing_walrus, 

63 is_type_ignore_comment_string, 

64 is_vararg, 

65 is_walrus_assignment, 

66 is_yield, 

67 syms, 

68 wrap_in_parentheses, 

69) 

70from black.numerics import normalize_numeric_literal 

71from black.strings import ( 

72 fix_multiline_docstring, 

73 get_string_prefix, 

74 normalize_string_prefix, 

75 normalize_string_quotes, 

76 normalize_unicode_escape_sequences, 

77) 

78from black.trans import ( 

79 CannotTransform, 

80 StringMerger, 

81 StringParenStripper, 

82 StringParenWrapper, 

83 StringSplitter, 

84 Transformer, 

85 hug_power_op, 

86) 

87from blib2to3.pgen2 import token 

88from blib2to3.pytree import Leaf, Node 

89 

90# types 

91LeafID = int 

92LN = Union[Leaf, Node] 

93 

94 

95class CannotSplit(CannotTransform): 

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

97 

98 

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

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

101class LineGenerator(Visitor[Line]): 

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

103 

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

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

106 """ 

107 

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

109 self.mode = mode 

110 self.features = features 

111 self.current_line: Line 

112 self.__post_init__() 

113 

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

115 """Generate a line. 

116 

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

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

119 

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

121 """ 

122 if not self.current_line: 

123 self.current_line.depth += indent 

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

125 

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

127 self.current_line.leaves[0] 

128 ): 

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

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

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

132 # `ASYNC` leaf as a complete line. 

133 return 

134 

135 complete_line = self.current_line 

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

137 yield complete_line 

138 

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

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

141 if isinstance(node, Leaf): 

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

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

144 if any_open_brackets: 

145 # any comment within brackets is subject to splitting 

146 self.current_line.append(comment) 

147 elif comment.type == token.COMMENT: 

148 # regular trailing comment 

149 self.current_line.append(comment) 

150 yield from self.line() 

151 

152 else: 

153 # regular standalone comment 

154 yield from self.line() 

155 

156 self.current_line.append(comment) 

157 yield from self.line() 

158 

159 if any_open_brackets: 

160 node.prefix = "" 

161 if node.type not in WHITESPACE: 

162 self.current_line.append(node) 

163 yield from super().visit_default(node) 

164 

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

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

167 

168 already_parenthesized = ( 

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

170 ) 

171 

172 if not already_parenthesized: 

173 # Similar to logic in wrap_in_parentheses 

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

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

176 prefix = node.prefix 

177 node.prefix = "" 

178 lpar.prefix = prefix 

179 node.insert_child(0, lpar) 

180 node.append_child(rpar) 

181 

182 yield from self.visit_default(node) 

183 

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

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

186 # In blib2to3 INDENT never holds comments. 

187 yield from self.line(+1) 

188 yield from self.visit_default(node) 

189 

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

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

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

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

194 # Emit the line then. 

195 yield from self.line() 

196 

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

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

199 yield from self.visit_default(node) 

200 

201 # Finally, emit the dedent. 

202 yield from self.line(-1) 

203 

204 def visit_stmt( 

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

206 ) -> Iterator[Line]: 

207 """Visit a statement. 

208 

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

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

211 

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

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

214 

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

216 invisible parens should be put. 

217 """ 

218 normalize_invisible_parens( 

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

220 ) 

221 for child in node.children: 

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

223 yield from self.line() 

224 

225 yield from self.visit(child) 

226 

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

228 yield from self.visit_default(node) 

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

230 

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

232 yield from self.visit_default(node) 

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

234 

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

236 yield from self.visit_default(node) 

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

238 

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

240 if Preview.wrap_long_dict_values_in_parens in self.mode: 

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

242 if i == 0: 

243 continue 

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

245 if ( 

246 child.type == syms.atom 

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

248 and not is_walrus_assignment(child) 

249 ): 

250 maybe_make_parens_invisible_in_atom( 

251 child, 

252 parent=node, 

253 mode=self.mode, 

254 features=self.features, 

255 remove_brackets_around_comma=False, 

256 ) 

257 else: 

258 wrap_in_parentheses(node, child, visible=False) 

259 yield from self.visit_default(node) 

260 

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

262 """Visit function definition.""" 

263 yield from self.line() 

264 

265 # Remove redundant brackets around return type annotation. 

266 is_return_annotation = False 

267 for child in node.children: 

268 if child.type == token.RARROW: 

269 is_return_annotation = True 

270 elif is_return_annotation: 

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

272 if maybe_make_parens_invisible_in_atom( 

273 child, 

274 parent=node, 

275 mode=self.mode, 

276 features=self.features, 

277 remove_brackets_around_comma=False, 

278 ): 

279 wrap_in_parentheses(node, child, visible=False) 

280 else: 

281 wrap_in_parentheses(node, child, visible=False) 

282 is_return_annotation = False 

283 

284 for child in node.children: 

285 yield from self.visit(child) 

286 

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

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

289 normalize_invisible_parens( 

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

291 ) 

292 

293 yield from self.line() 

294 for child in node.children: 

295 yield from self.visit(child) 

296 

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

298 """Visit a suite.""" 

299 if is_stub_suite(node): 

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

301 else: 

302 yield from self.visit_default(node) 

303 

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

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

306 prev_type: Optional[int] = None 

307 for child in node.children: 

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

309 wrap_in_parentheses(node, child, visible=False) 

310 prev_type = child.type 

311 

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

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

314 yield from self.visit_default(node) 

315 else: 

316 yield from self.line(+1) 

317 yield from self.visit_default(node) 

318 yield from self.line(-1) 

319 

320 else: 

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

322 node.prefix = "" 

323 yield from self.visit_default(node) 

324 return 

325 yield from self.line() 

326 yield from self.visit_default(node) 

327 

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

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

330 yield from self.line() 

331 

332 children = iter(node.children) 

333 for child in children: 

334 yield from self.visit(child) 

335 

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

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

338 # line. 

339 break 

340 

341 internal_stmt = next(children) 

342 yield from self.visit(internal_stmt) 

343 

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

345 """Visit decorators.""" 

346 for child in node.children: 

347 yield from self.line() 

348 yield from self.visit(child) 

349 

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

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

352 next_leaf = node.children[idx + 1] 

353 

354 if not isinstance(leaf, Leaf): 

355 continue 

356 

357 value = leaf.value.lower() 

358 if ( 

359 leaf.type == token.NUMBER 

360 and next_leaf.type == syms.trailer 

361 # Ensure that we are in an attribute trailer 

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

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

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

365 # It shouldn't wrap complex literals 

366 and "j" not in value 

367 ): 

368 wrap_in_parentheses(node, leaf) 

369 

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

371 

372 yield from self.visit_default(node) 

373 

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

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

376 yield from self.line() 

377 

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

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

380 yield from self.visit_default(leaf) 

381 yield from self.line() 

382 

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

384 if not self.current_line.bracket_tracker.any_open_brackets(): 

385 yield from self.line() 

386 # STANDALONE_COMMENT nodes created by our special handling in 

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

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

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

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

391 # lose indentation. Normal STANDALONE_COMMENT nodes go through 

392 # visit_default. 

393 value = leaf.value 

394 lines = value.splitlines() 

395 if len(lines) >= 2: 

396 # Check if first line (after stripping whitespace) is exactly a 

397 # fmt:off directive 

398 first_line = lines[0].lstrip() 

399 first_is_fmt_off = first_line in FMT_OFF 

400 # Check if last line (after stripping whitespace) is exactly a 

401 # fmt:on directive 

402 last_line = lines[-1].lstrip() 

403 last_is_fmt_on = last_line in FMT_ON 

404 is_fmt_off_block = first_is_fmt_off and last_is_fmt_on 

405 else: 

406 is_fmt_off_block = False 

407 if is_fmt_off_block: 

408 # This is a fmt:off/on block from normalize_fmt_off - append directly 

409 self.current_line.append(leaf) 

410 yield from self.line() 

411 else: 

412 # Normal standalone comment - process through visit_default 

413 yield from self.visit_default(leaf) 

414 

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

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

417 

418 -2 ** 8 -> -(2 ** 8) 

419 """ 

420 _operator, operand = node.children 

421 if ( 

422 operand.type == syms.power 

423 and len(operand.children) == 3 

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

425 ): 

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

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

428 index = operand.remove() or 0 

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

430 yield from self.visit_default(node) 

431 

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

433 """ 

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

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

436 Examples: 

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

438 

439 -> 

440 

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

442 """ 

443 assert len(node.children) == 3 

444 if maybe_make_parens_invisible_in_atom( 

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

446 ): 

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

448 

449 yield from self.visit_default(node) 

450 

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

452 normalize_unicode_escape_sequences(leaf) 

453 

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

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

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

457 if self.mode.string_normalization: 

458 docstring = normalize_string_prefix(leaf.value) 

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

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

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

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

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

464 docstring = normalize_string_quotes(docstring) 

465 else: 

466 docstring = leaf.value 

467 prefix = get_string_prefix(docstring) 

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

469 quote_char = docstring[0] 

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

471 # docstring = docstring.strip(quote_char) 

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

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

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

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

476 docstring = docstring[quote_len:-quote_len] 

477 docstring_started_empty = not docstring 

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

479 

480 if is_multiline_string(leaf): 

481 docstring = fix_multiline_docstring(docstring, indent) 

482 else: 

483 docstring = docstring.strip() 

484 

485 has_trailing_backslash = False 

486 if docstring: 

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

488 if docstring[0] == quote_char: 

489 docstring = " " + docstring 

490 if docstring[-1] == quote_char: 

491 docstring += " " 

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

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

494 if backslash_count % 2: 

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

496 # avoid escaping the closing string quote. 

497 docstring += " " 

498 has_trailing_backslash = True 

499 elif not docstring_started_empty: 

500 docstring = " " 

501 

502 # We could enforce triple quotes at this point. 

503 quote = quote_char * quote_len 

504 

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

506 if quote_len == 3: 

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

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

509 # exceeding the maximum line length. 

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

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

512 lines = docstring.splitlines() 

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

514 

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

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

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

518 # the closing quotes 

519 if ( 

520 len(lines) > 1 

521 and last_line_length + quote_len > self.mode.line_length 

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

523 and not has_trailing_backslash 

524 ): 

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

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

527 else: 

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

529 else: 

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

531 else: 

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

533 

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

535 leaf.value = normalize_string_prefix(leaf.value) 

536 leaf.value = normalize_string_quotes(leaf.value) 

537 yield from self.visit_default(leaf) 

538 

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

540 normalize_numeric_literal(leaf) 

541 yield from self.visit_default(leaf) 

542 

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

544 """Visit any atom""" 

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

546 first = node.children[0] 

547 last = node.children[-1] 

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

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

550 ): 

551 # Lists or sets of one item 

552 maybe_make_parens_invisible_in_atom( 

553 node.children[1], 

554 parent=node, 

555 mode=self.mode, 

556 features=self.features, 

557 ) 

558 

559 yield from self.visit_default(node) 

560 

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

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

563 string_leaf = fstring_to_string(node) 

564 node.replace(string_leaf) 

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

566 "\\" in str(child) 

567 for child in node.children 

568 if child.type == syms.fstring_replacement_field 

569 ): 

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

571 # causing breakages. skip normalization when nested quotes exist 

572 yield from self.visit_default(string_leaf) 

573 return 

574 yield from self.visit_STRING(string_leaf) 

575 

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

577 # fstring_start = node.children[0] 

578 # fstring_end = node.children[-1] 

579 # assert isinstance(fstring_start, Leaf) 

580 # assert isinstance(fstring_end, Leaf) 

581 

582 # quote_char = fstring_end.value[0] 

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

584 # prefix, quote = ( 

585 # fstring_start.value[:quote_idx], 

586 # fstring_start.value[quote_idx:] 

587 # ) 

588 

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

590 # prefix = normalize_string_prefix(prefix) 

591 

592 # assert quote == fstring_end.value 

593 

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

595 # middles = [ 

596 # leaf 

597 # for leaf in node.leaves() 

598 # if leaf.type == token.FSTRING_MIDDLE 

599 # ] 

600 

601 # if self.mode.string_normalization: 

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

603 

604 # fstring_start.value = prefix + quote 

605 # fstring_end.value = quote 

606 

607 # yield from self.visit_default(node) 

608 

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

610 if Preview.wrap_comprehension_in in self.mode: 

611 normalize_invisible_parens( 

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

613 ) 

614 yield from self.visit_default(node) 

615 

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

617 yield from self.visit_comp_for(node) 

618 

619 def __post_init__(self) -> None: 

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

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

622 

623 v = self.visit_stmt 

624 Ø: set[str] = set() 

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

626 self.visit_if_stmt = partial( 

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

628 ) 

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

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

631 self.visit_try_stmt = partial( 

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

633 ) 

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

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

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

637 

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

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

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

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

642 self.visit_async_funcdef = self.visit_async_stmt 

643 self.visit_decorated = self.visit_decorators 

644 

645 # PEP 634 

646 self.visit_match_stmt = self.visit_match_case 

647 self.visit_case_block = self.visit_match_case 

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

649 

650 

651def _hugging_power_ops_line_to_string( 

652 line: Line, 

653 features: Collection[Feature], 

654 mode: Mode, 

655) -> Optional[str]: 

656 try: 

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

658 except CannotTransform: 

659 return None 

660 

661 

662def transform_line( 

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

664) -> Iterator[Line]: 

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

666 

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

668 

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

670 """ 

671 if line.is_comment: 

672 yield line 

673 return 

674 

675 line_str = line_to_string(line) 

676 

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

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

679 line_str_hugging_power_ops = ( 

680 _hugging_power_ops_line_to_string(line, features, mode) or line_str 

681 ) 

682 

683 ll = mode.line_length 

684 sn = mode.string_normalization 

685 string_merge = StringMerger(ll, sn) 

686 string_paren_strip = StringParenStripper(ll, sn) 

687 string_split = StringSplitter(ll, sn) 

688 string_paren_wrap = StringParenWrapper(ll, sn) 

689 

690 transformers: list[Transformer] 

691 if ( 

692 not line.contains_uncollapsable_type_comments() 

693 and not line.should_split_rhs 

694 and not line.magic_trailing_comma 

695 and ( 

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

697 or line.contains_unsplittable_type_ignore() 

698 ) 

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

700 and not line.contains_implicit_multiline_string_with_comments() 

701 ): 

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

703 if Preview.string_processing in mode: 

704 transformers = [string_merge, string_paren_strip] 

705 else: 

706 transformers = [] 

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

708 transformers = [left_hand_split] 

709 else: 

710 

711 def _rhs( 

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

713 ) -> Iterator[Line]: 

714 """Wraps calls to `right_hand_split`. 

715 

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

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

718 bracket pair instead. 

719 """ 

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

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

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

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

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

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

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

727 yield from lines 

728 return 

729 

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

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

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

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

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

735 

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

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

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

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

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

741 

742 if Preview.string_processing in mode: 

743 if line.inside_brackets: 

744 transformers = [ 

745 string_merge, 

746 string_paren_strip, 

747 string_split, 

748 delimiter_split, 

749 standalone_comment_split, 

750 string_paren_wrap, 

751 rhs, 

752 ] 

753 else: 

754 transformers = [ 

755 string_merge, 

756 string_paren_strip, 

757 string_split, 

758 string_paren_wrap, 

759 rhs, 

760 ] 

761 else: 

762 if line.inside_brackets: 

763 transformers = [delimiter_split, standalone_comment_split, rhs] 

764 else: 

765 transformers = [rhs] 

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

767 # could match. 

768 transformers.append(hug_power_op) 

769 

770 for transform in transformers: 

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

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

773 # split altogether. 

774 try: 

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

776 except CannotTransform: 

777 continue 

778 else: 

779 yield from result 

780 break 

781 

782 else: 

783 yield line 

784 

785 

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

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

788 split the line with rhs to respect the comma. 

789 """ 

790 return_type_leaves: list[Leaf] = [] 

791 in_return_type = False 

792 

793 for leaf in line.leaves: 

794 if leaf.type == token.COLON: 

795 in_return_type = False 

796 if in_return_type: 

797 return_type_leaves.append(leaf) 

798 if leaf.type == token.RARROW: 

799 in_return_type = True 

800 

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

802 # couple lines from it. 

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

804 leaves_to_track = get_leaves_inside_matching_brackets(return_type_leaves) 

805 for leaf in return_type_leaves: 

806 result.append( 

807 leaf, 

808 preformatted=True, 

809 track_bracket=id(leaf) in leaves_to_track, 

810 ) 

811 

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

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

814 return result.magic_trailing_comma is not None 

815 

816 

817class _BracketSplitComponent(Enum): 

818 head = auto() 

819 body = auto() 

820 tail = auto() 

821 

822 

823def left_hand_split( 

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

825) -> Iterator[Line]: 

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

827 

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

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

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

831 """ 

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

833 tail_leaves: list[Leaf] = [] 

834 body_leaves: list[Leaf] = [] 

835 head_leaves: list[Leaf] = [] 

836 current_leaves = head_leaves 

837 matching_bracket: Optional[Leaf] = None 

838 depth = 0 

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

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

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

842 # tracking the depth 

843 depth += 1 

844 elif depth > 0: 

845 if leaf.type == token.LSQB: 

846 depth += 1 

847 elif leaf.type == token.RSQB: 

848 depth -= 1 

849 if ( 

850 current_leaves is body_leaves 

851 and leaf.type in CLOSING_BRACKETS 

852 and leaf.opening_bracket is matching_bracket 

853 and isinstance(matching_bracket, Leaf) 

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

855 # param, ignore the match since this is searching 

856 # for the function arguments 

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

858 ): 

859 ensure_visible(leaf) 

860 ensure_visible(matching_bracket) 

861 current_leaves = tail_leaves if body_leaves else head_leaves 

862 current_leaves.append(leaf) 

863 if current_leaves is head_leaves: 

864 if leaf.type == leaf_type and ( 

865 Preview.fix_type_expansion_split not in mode 

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

867 ): 

868 matching_bracket = leaf 

869 current_leaves = body_leaves 

870 if matching_bracket and tail_leaves: 

871 break 

872 if not matching_bracket or not tail_leaves: 

873 raise CannotSplit("No brackets found") 

874 

875 head = bracket_split_build_line( 

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

877 ) 

878 body = bracket_split_build_line( 

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

880 ) 

881 tail = bracket_split_build_line( 

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

883 ) 

884 bracket_split_succeeded_or_raise(head, body, tail) 

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

886 if result: 

887 yield result 

888 

889 

890def right_hand_split( 

891 line: Line, 

892 mode: Mode, 

893 features: Collection[Feature] = (), 

894 omit: Collection[LeafID] = (), 

895) -> Iterator[Line]: 

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

897 

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

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

900 this split. 

901 

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

903 """ 

904 rhs_result = _first_right_hand_split(line, omit=omit) 

905 yield from _maybe_split_omitting_optional_parens( 

906 rhs_result, line, mode, features=features, omit=omit 

907 ) 

908 

909 

910def _first_right_hand_split( 

911 line: Line, 

912 omit: Collection[LeafID] = (), 

913) -> RHSResult: 

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

915 

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

917 _maybe_split_omitting_optional_parens to get an opinion whether to prefer 

918 splitting on the right side of an assignment statement. 

919 """ 

920 tail_leaves: list[Leaf] = [] 

921 body_leaves: list[Leaf] = [] 

922 head_leaves: list[Leaf] = [] 

923 current_leaves = tail_leaves 

924 opening_bracket: Optional[Leaf] = None 

925 closing_bracket: Optional[Leaf] = None 

926 for leaf in reversed(line.leaves): 

927 if current_leaves is body_leaves: 

928 if leaf is opening_bracket: 

929 current_leaves = head_leaves if body_leaves else tail_leaves 

930 current_leaves.append(leaf) 

931 if current_leaves is tail_leaves: 

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

933 opening_bracket = leaf.opening_bracket 

934 closing_bracket = leaf 

935 current_leaves = body_leaves 

936 if not (opening_bracket and closing_bracket and head_leaves): 

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

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

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

940 raise CannotSplit("No brackets found") 

941 

942 tail_leaves.reverse() 

943 body_leaves.reverse() 

944 head_leaves.reverse() 

945 

946 body: Optional[Line] = None 

947 if ( 

948 Preview.hug_parens_with_braces_and_square_brackets in line.mode 

949 and tail_leaves[0].value 

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

951 ): 

952 inner_body_leaves = list(body_leaves) 

953 hugged_opening_leaves: list[Leaf] = [] 

954 hugged_closing_leaves: list[Leaf] = [] 

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

956 unpacking_offset: int = 1 if is_unpacking else 0 

957 while ( 

958 len(inner_body_leaves) >= 2 + unpacking_offset 

959 and inner_body_leaves[-1].type in CLOSING_BRACKETS 

960 and inner_body_leaves[-1].opening_bracket 

961 is inner_body_leaves[unpacking_offset] 

962 ): 

963 if unpacking_offset: 

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

965 unpacking_offset = 0 

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

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

968 

969 if hugged_opening_leaves and inner_body_leaves: 

970 inner_body = bracket_split_build_line( 

971 inner_body_leaves, 

972 line, 

973 hugged_opening_leaves[-1], 

974 component=_BracketSplitComponent.body, 

975 ) 

976 if ( 

977 line.mode.magic_trailing_comma 

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

979 ): 

980 should_hug = True 

981 else: 

982 line_length = line.mode.line_length - sum( 

983 len(str(leaf)) 

984 for leaf in hugged_opening_leaves + hugged_closing_leaves 

985 ) 

986 if is_line_short_enough( 

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

988 ): 

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

990 should_hug = False 

991 else: 

992 should_hug = True 

993 if should_hug: 

994 body_leaves = inner_body_leaves 

995 head_leaves.extend(hugged_opening_leaves) 

996 tail_leaves = hugged_closing_leaves + tail_leaves 

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

998 

999 head = bracket_split_build_line( 

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

1001 ) 

1002 if body is None: 

1003 body = bracket_split_build_line( 

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

1005 ) 

1006 tail = bracket_split_build_line( 

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

1008 ) 

1009 bracket_split_succeeded_or_raise(head, body, tail) 

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

1011 

1012 

1013def _maybe_split_omitting_optional_parens( 

1014 rhs: RHSResult, 

1015 line: Line, 

1016 mode: Mode, 

1017 features: Collection[Feature] = (), 

1018 omit: Collection[LeafID] = (), 

1019) -> Iterator[Line]: 

1020 if ( 

1021 Feature.FORCE_OPTIONAL_PARENTHESES not in features 

1022 # the opening bracket is an optional paren 

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

1024 and not rhs.opening_bracket.value 

1025 # the closing bracket is an optional paren 

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

1027 and not rhs.closing_bracket.value 

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

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

1030 and not line.is_import 

1031 # and we can actually remove the parens 

1032 and can_omit_invisible_parens(rhs, mode.line_length) 

1033 ): 

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

1035 try: 

1036 # The RHSResult Omitting Optional Parens. 

1037 rhs_oop = _first_right_hand_split(line, omit=omit) 

1038 if _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode): 

1039 yield from _maybe_split_omitting_optional_parens( 

1040 rhs_oop, line, mode, features=features, omit=omit 

1041 ) 

1042 return 

1043 

1044 except CannotSplit as e: 

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

1046 if line.is_chained_assignment: 

1047 pass 

1048 

1049 elif ( 

1050 not can_be_split(rhs.body) 

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

1052 and not ( 

1053 Preview.wrap_long_dict_values_in_parens 

1054 and rhs.opening_bracket.parent 

1055 and rhs.opening_bracket.parent.parent 

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

1057 ) 

1058 ): 

1059 raise CannotSplit( 

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

1061 ) from e 

1062 

1063 elif ( 

1064 rhs.head.contains_multiline_strings() 

1065 or rhs.tail.contains_multiline_strings() 

1066 ): 

1067 raise CannotSplit( 

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

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

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

1071 " line." 

1072 ) from e 

1073 

1074 ensure_visible(rhs.opening_bracket) 

1075 ensure_visible(rhs.closing_bracket) 

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

1077 if result: 

1078 yield result 

1079 

1080 

1081def _prefer_split_rhs_oop_over_rhs( 

1082 rhs_oop: RHSResult, rhs: RHSResult, mode: Mode 

1083) -> bool: 

1084 """ 

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

1086 (rhs_oop) over the original (rhs). 

1087 """ 

1088 # contains unsplittable type ignore 

1089 if ( 

1090 rhs_oop.head.contains_unsplittable_type_ignore() 

1091 or rhs_oop.body.contains_unsplittable_type_ignore() 

1092 or rhs_oop.tail.contains_unsplittable_type_ignore() 

1093 ): 

1094 return True 

1095 

1096 # Retain optional parens around dictionary values 

1097 if ( 

1098 Preview.wrap_long_dict_values_in_parens 

1099 and rhs.opening_bracket.parent 

1100 and rhs.opening_bracket.parent.parent 

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

1102 and rhs.body.bracket_tracker.delimiters 

1103 ): 

1104 # Unless the split is inside the key 

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

1106 

1107 # the split is right after `=` 

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

1109 return True 

1110 

1111 # the left side of assignment contains brackets 

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

1113 return True 

1114 

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

1116 # paren) 

1117 if not is_line_short_enough( 

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

1119 ): 

1120 return True 

1121 

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

1123 if rhs.head.magic_trailing_comma is not None: 

1124 return True 

1125 

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

1127 # the body 

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

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

1130 token.EQUAL 

1131 ) 

1132 if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count: 

1133 return False 

1134 

1135 has_closing_bracket_after_assign = False 

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

1137 if leaf.type == token.EQUAL: 

1138 break 

1139 if leaf.type in CLOSING_BRACKETS: 

1140 has_closing_bracket_after_assign = True 

1141 break 

1142 return ( 

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

1144 # closing bracket) 

1145 has_closing_bracket_after_assign 

1146 or ( 

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

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

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

1150 # the first line is short enough 

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

1152 ) 

1153 ) 

1154 

1155 

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

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

1158 

1159 Do nothing otherwise. 

1160 

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

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

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

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

1165 

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

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

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

1169 """ 

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

1171 if not body: 

1172 if tail_len == 0: 

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

1174 

1175 elif tail_len < 3: 

1176 raise CannotSplit( 

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

1178 " not worth it" 

1179 ) 

1180 

1181 

1182def _ensure_trailing_comma( 

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

1184) -> bool: 

1185 if not leaves: 

1186 return False 

1187 # Ensure a trailing comma for imports 

1188 if original.is_import: 

1189 return True 

1190 # ...and standalone function arguments 

1191 if not original.is_def: 

1192 return False 

1193 if opening_bracket.value != "(": 

1194 return False 

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

1196 if any( 

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

1198 ): 

1199 return False 

1200 

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

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

1203 if leaf_with_parent is None: 

1204 return True 

1205 # Don't add commas inside parenthesized return annotations 

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

1207 return False 

1208 # Don't add commas inside PEP 604 unions 

1209 if ( 

1210 leaf_with_parent.parent 

1211 and leaf_with_parent.parent.next_sibling 

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

1213 ): 

1214 return False 

1215 return True 

1216 

1217 

1218def bracket_split_build_line( 

1219 leaves: list[Leaf], 

1220 original: Line, 

1221 opening_bracket: Leaf, 

1222 *, 

1223 component: _BracketSplitComponent, 

1224) -> Line: 

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

1226 

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

1228 respected. 

1229 

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

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

1232 expected. 

1233 """ 

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

1235 if component is _BracketSplitComponent.body: 

1236 result.inside_brackets = True 

1237 result.depth += 1 

1238 if _ensure_trailing_comma(leaves, original, opening_bracket): 

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

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

1241 continue 

1242 

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

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

1245 leaves.insert(i + 1, new_comma) 

1246 break 

1247 

1248 leaves_to_track: set[LeafID] = set() 

1249 if component is _BracketSplitComponent.head: 

1250 leaves_to_track = get_leaves_inside_matching_brackets(leaves) 

1251 # Populate the line 

1252 for leaf in leaves: 

1253 result.append( 

1254 leaf, 

1255 preformatted=True, 

1256 track_bracket=id(leaf) in leaves_to_track, 

1257 ) 

1258 for comment_after in original.comments_after(leaf): 

1259 result.append(comment_after, preformatted=True) 

1260 if component is _BracketSplitComponent.body and should_split_line( 

1261 result, opening_bracket 

1262 ): 

1263 result.should_split_rhs = True 

1264 return result 

1265 

1266 

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

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

1269 

1270 This is a decorator over relevant split functions. 

1271 """ 

1272 

1273 @wraps(split_func) 

1274 def split_wrapper( 

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

1276 ) -> Iterator[Line]: 

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

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

1279 yield split_line 

1280 

1281 return split_wrapper 

1282 

1283 

1284def _get_last_non_comment_leaf(line: Line) -> Optional[int]: 

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

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

1287 return leaf_idx 

1288 return None 

1289 

1290 

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

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

1293 return Feature.TRAILING_COMMA_IN_DEF in features 

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

1295 return Feature.TRAILING_COMMA_IN_CALL in features 

1296 return True 

1297 

1298 

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

1300 if ( 

1301 safe 

1302 and delimiter_priority == COMMA_PRIORITY 

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

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

1305 ): 

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

1307 line.append(new_comma) 

1308 return line 

1309 

1310 

1311MIGRATE_COMMENT_DELIMITERS = {STRING_PRIORITY, COMMA_PRIORITY} 

1312 

1313 

1314@dont_increase_indentation 

1315def delimiter_split( 

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

1317) -> Iterator[Line]: 

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

1319 

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

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

1322 """ 

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

1324 raise CannotSplit("Line empty") from None 

1325 last_leaf = line.leaves[-1] 

1326 

1327 bt = line.bracket_tracker 

1328 try: 

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

1330 except ValueError: 

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

1332 

1333 if ( 

1334 delimiter_priority == DOT_PRIORITY 

1335 and bt.delimiter_count_with_priority(delimiter_priority) == 1 

1336 ): 

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

1338 

1339 current_line = Line( 

1340 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1341 ) 

1342 lowest_depth = sys.maxsize 

1343 trailing_comma_safe = True 

1344 

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

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

1347 nonlocal current_line 

1348 try: 

1349 current_line.append_safe(leaf, preformatted=True) 

1350 except ValueError: 

1351 yield current_line 

1352 

1353 current_line = Line( 

1354 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1355 ) 

1356 current_line.append(leaf) 

1357 

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

1359 for comment_after in line.comments_after(leaf): 

1360 yield from append_to_line(comment_after) 

1361 

1362 last_non_comment_leaf = _get_last_non_comment_leaf(line) 

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

1364 yield from append_to_line(leaf) 

1365 

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

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

1368 ) 

1369 if ( 

1370 previous_priority != delimiter_priority 

1371 or delimiter_priority in MIGRATE_COMMENT_DELIMITERS 

1372 ): 

1373 yield from append_comments(leaf) 

1374 

1375 lowest_depth = min(lowest_depth, leaf.bracket_depth) 

1376 if trailing_comma_safe and leaf.bracket_depth == lowest_depth: 

1377 trailing_comma_safe = _can_add_trailing_comma(leaf, features) 

1378 

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

1380 current_line = _safe_add_trailing_comma( 

1381 trailing_comma_safe, delimiter_priority, current_line 

1382 ) 

1383 

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

1385 if leaf_priority == delimiter_priority: 

1386 if ( 

1387 leaf_idx + 1 < len(line.leaves) 

1388 and delimiter_priority not in MIGRATE_COMMENT_DELIMITERS 

1389 ): 

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

1391 

1392 yield current_line 

1393 current_line = Line( 

1394 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1395 ) 

1396 

1397 if current_line: 

1398 current_line = _safe_add_trailing_comma( 

1399 trailing_comma_safe, delimiter_priority, current_line 

1400 ) 

1401 yield current_line 

1402 

1403 

1404@dont_increase_indentation 

1405def standalone_comment_split( 

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

1407) -> Iterator[Line]: 

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

1409 if not line.contains_standalone_comments(): 

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

1411 

1412 current_line = Line( 

1413 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1414 ) 

1415 

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

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

1418 nonlocal current_line 

1419 try: 

1420 current_line.append_safe(leaf, preformatted=True) 

1421 except ValueError: 

1422 yield current_line 

1423 

1424 current_line = Line( 

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

1426 ) 

1427 current_line.append(leaf) 

1428 

1429 for leaf in line.leaves: 

1430 yield from append_to_line(leaf) 

1431 

1432 for comment_after in line.comments_after(leaf): 

1433 yield from append_to_line(comment_after) 

1434 

1435 if current_line: 

1436 yield current_line 

1437 

1438 

1439def normalize_invisible_parens( # noqa: C901 

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

1441) -> None: 

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

1443 

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

1445 should be put. 

1446 

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

1448 existing visible parentheses for other tuples and generator expressions. 

1449 """ 

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

1451 if pc.value in FMT_OFF: 

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

1453 return 

1454 

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

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

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

1458 if node.type == syms.with_stmt: 

1459 _maybe_wrap_cms_in_parens(node, mode, features) 

1460 

1461 check_lpar = False 

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

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

1464 # assignment statements that contain type annotations. 

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

1466 normalize_invisible_parens( 

1467 child, parens_after=parens_after, mode=mode, features=features 

1468 ) 

1469 

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

1471 # case blocks. 

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

1473 normalize_invisible_parens( 

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

1475 ) 

1476 

1477 # Add parentheses around if guards in case blocks 

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

1479 normalize_invisible_parens( 

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

1481 ) 

1482 

1483 # Add parentheses around long tuple unpacking in assignments. 

1484 if ( 

1485 index == 0 

1486 and isinstance(child, Node) 

1487 and child.type == syms.testlist_star_expr 

1488 ): 

1489 check_lpar = True 

1490 

1491 if check_lpar: 

1492 if ( 

1493 child.type == syms.atom 

1494 and node.type == syms.for_stmt 

1495 and isinstance(child.prev_sibling, Leaf) 

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

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

1498 ): 

1499 if maybe_make_parens_invisible_in_atom( 

1500 child, 

1501 parent=node, 

1502 mode=mode, 

1503 features=features, 

1504 remove_brackets_around_comma=True, 

1505 ): 

1506 wrap_in_parentheses(node, child, visible=False) 

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

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

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

1510 "in" in parens_after 

1511 and len(child.children) == 3 

1512 and is_lpar_token(child.children[0]) 

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

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

1515 ): 

1516 if maybe_make_parens_invisible_in_atom( 

1517 child, parent=node, mode=mode, features=features 

1518 ): 

1519 wrap_in_parentheses(node, child, visible=False) 

1520 elif is_one_tuple(child): 

1521 wrap_in_parentheses(node, child, visible=True) 

1522 elif node.type == syms.import_from: 

1523 _normalize_import_from(node, child, index) 

1524 break 

1525 elif ( 

1526 index == 1 

1527 and child.type == token.STAR 

1528 and node.type == syms.except_clause 

1529 ): 

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

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

1532 # invisible parentheses to work more precisely. 

1533 continue 

1534 

1535 elif ( 

1536 isinstance(child, Leaf) 

1537 and child.next_sibling is not None 

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

1539 and child.value == "case" 

1540 ): 

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

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

1543 break 

1544 

1545 elif not is_multiline_string(child): 

1546 wrap_in_parentheses(node, child, visible=False) 

1547 

1548 comma_check = child.type == token.COMMA 

1549 

1550 check_lpar = isinstance(child, Leaf) and ( 

1551 child.value in parens_after or comma_check 

1552 ) 

1553 

1554 

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

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

1557 # the statement 

1558 if is_lpar_token(child): 

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

1560 # make parentheses invisible 

1561 child.value = "" 

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

1563 elif child.type != token.STAR: 

1564 # insert invisible parentheses 

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

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

1567 

1568 

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

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

1571 if ( 

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

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

1574 ): 

1575 if maybe_make_parens_invisible_in_atom( 

1576 node.children[1], 

1577 parent=node, 

1578 mode=mode, 

1579 features=features, 

1580 remove_brackets_around_comma=True, 

1581 ): 

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

1583 

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

1585 # brackets in cases where this would change 

1586 # the AST due to operator precedence. 

1587 # Therefore we only aim to remove brackets around 

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

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

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

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

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

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

1594 if isinstance(bracket_contents, Node) and ( 

1595 bracket_contents.type != syms.power 

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

1597 or any( 

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

1599 for child in bracket_contents.children 

1600 ) 

1601 ): 

1602 ensure_visible(opening_bracket) 

1603 ensure_visible(closing_bracket) 

1604 

1605 

1606def _maybe_wrap_cms_in_parens( 

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

1608) -> None: 

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

1610 

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

1612 """ 

1613 if ( 

1614 Feature.PARENTHESIZED_CONTEXT_MANAGERS not in features 

1615 or len(node.children) <= 2 

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

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

1618 ): 

1619 return 

1620 colon_index: Optional[int] = None 

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

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

1623 colon_index = i 

1624 break 

1625 if colon_index is not None: 

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

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

1628 context_managers = node.children[1:colon_index] 

1629 for child in context_managers: 

1630 child.remove() 

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

1632 # with_stmt 

1633 # NAME 'with' 

1634 # atom 

1635 # LPAR '' 

1636 # testlist_gexp 

1637 # ... <-- context_managers 

1638 # /testlist_gexp 

1639 # RPAR '' 

1640 # /atom 

1641 # COLON ':' 

1642 new_child = Node( 

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

1644 ) 

1645 node.insert_child(1, new_child) 

1646 

1647 

1648def remove_with_parens( 

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

1650) -> None: 

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

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

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

1654 # different parse trees: 

1655 # 

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

1657 # ... 

1658 # 

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

1660 # ... # asexpr_test 

1661 # 

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

1663 # ... # asexpr_test 

1664 # 

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

1666 # ... # testlist_gexp which then 

1667 # # contains multiple asexpr_test(s) 

1668 if node.type == syms.atom: 

1669 if maybe_make_parens_invisible_in_atom( 

1670 node, 

1671 parent=parent, 

1672 mode=mode, 

1673 features=features, 

1674 remove_brackets_around_comma=True, 

1675 ): 

1676 wrap_in_parentheses(parent, node, visible=False) 

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

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

1679 elif node.type == syms.testlist_gexp: 

1680 for child in node.children: 

1681 if isinstance(child, Node): 

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

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

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

1685 ): 

1686 if maybe_make_parens_invisible_in_atom( 

1687 node.children[0], 

1688 parent=node, 

1689 mode=mode, 

1690 features=features, 

1691 remove_brackets_around_comma=True, 

1692 ): 

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

1694 

1695 

1696def maybe_make_parens_invisible_in_atom( 

1697 node: LN, 

1698 parent: LN, 

1699 mode: Mode, 

1700 features: Collection[Feature], 

1701 remove_brackets_around_comma: bool = False, 

1702) -> bool: 

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

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

1705 as they are redundant. 

1706 

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

1708 """ 

1709 if ( 

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

1711 or is_empty_tuple(node) 

1712 or is_one_tuple(node) 

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

1714 or ( 

1715 is_tuple(node) 

1716 and parent.type == syms.with_stmt 

1717 and has_sibling_with_type(node, token.COMMA) 

1718 ) 

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

1720 or ( 

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

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

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

1724 not remove_brackets_around_comma 

1725 and max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY 

1726 # Skip this check in Preview mode in order to 

1727 # Remove parentheses around multiple exception types in except and 

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

1729 and not ( 

1730 Preview.remove_parens_around_except_types in mode 

1731 and Feature.UNPARENTHESIZED_EXCEPT_TYPES in features 

1732 # is a tuple 

1733 and is_tuple(node) 

1734 # has a parent node 

1735 and node.parent is not None 

1736 # parent is an except clause 

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

1738 # is not immediately followed by as clause 

1739 and not ( 

1740 node.next_sibling is not None 

1741 and is_name_token(node.next_sibling) 

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

1743 ) 

1744 ) 

1745 ) 

1746 or is_tuple_containing_walrus(node) 

1747 or is_tuple_containing_star(node) 

1748 or is_generator(node) 

1749 ): 

1750 return False 

1751 

1752 if is_walrus_assignment(node): 

1753 if parent.type in [ 

1754 syms.annassign, 

1755 syms.expr_stmt, 

1756 syms.assert_stmt, 

1757 syms.return_stmt, 

1758 syms.except_clause, 

1759 syms.funcdef, 

1760 syms.with_stmt, 

1761 syms.testlist_gexp, 

1762 syms.tname, 

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

1764 syms.for_stmt, 

1765 syms.del_stmt, 

1766 syms.for_stmt, 

1767 ]: 

1768 return False 

1769 

1770 first = node.children[0] 

1771 last = node.children[-1] 

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

1773 middle = node.children[1] 

1774 # make parentheses invisible 

1775 if ( 

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

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

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

1779 ): 

1780 first.value = "" 

1781 last.value = "" 

1782 maybe_make_parens_invisible_in_atom( 

1783 middle, 

1784 parent=parent, 

1785 mode=mode, 

1786 features=features, 

1787 remove_brackets_around_comma=remove_brackets_around_comma, 

1788 ) 

1789 

1790 if is_atom_with_invisible_parens(middle): 

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

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

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

1794 

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

1796 # Preserve comments before first paren 

1797 middle.children[1].prefix = ( 

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

1799 ) 

1800 

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

1802 # Preserve comments before last paren 

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

1804 

1805 return False 

1806 

1807 return True 

1808 

1809 

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

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

1812 

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

1814 return False 

1815 

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

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

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

1819 exclude = set() 

1820 trailing_comma = False 

1821 try: 

1822 last_leaf = line.leaves[-1] 

1823 if last_leaf.type == token.COMMA: 

1824 trailing_comma = True 

1825 exclude.add(id(last_leaf)) 

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

1827 except (IndexError, ValueError): 

1828 return False 

1829 

1830 return max_priority == COMMA_PRIORITY and ( 

1831 (line.mode.magic_trailing_comma and trailing_comma) 

1832 # always explode imports 

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

1834 ) 

1835 

1836 

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

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

1839 

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

1841 a preceding closing bracket fits in one line. 

1842 

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

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

1845 the one that needs to explode are omitted. 

1846 """ 

1847 

1848 omit: set[LeafID] = set() 

1849 if not line.magic_trailing_comma: 

1850 yield omit 

1851 

1852 length = 4 * line.depth 

1853 opening_bracket: Optional[Leaf] = None 

1854 closing_bracket: Optional[Leaf] = None 

1855 inner_brackets: set[LeafID] = set() 

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

1857 length += leaf_length 

1858 if length > line_length: 

1859 break 

1860 

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

1862 if leaf.type == STANDALONE_COMMENT or has_inline_comment: 

1863 break 

1864 

1865 if opening_bracket: 

1866 if leaf is opening_bracket: 

1867 opening_bracket = None 

1868 elif leaf.type in CLOSING_BRACKETS: 

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

1870 if ( 

1871 prev 

1872 and prev.type == token.COMMA 

1873 and leaf.opening_bracket is not None 

1874 and not is_one_sequence_between( 

1875 leaf.opening_bracket, leaf, line.leaves 

1876 ) 

1877 ): 

1878 # Never omit bracket pairs with trailing commas. 

1879 # We need to explode on those. 

1880 break 

1881 

1882 inner_brackets.add(id(leaf)) 

1883 elif leaf.type in CLOSING_BRACKETS: 

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

1885 if prev and prev.type in OPENING_BRACKETS: 

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

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

1888 # pair of brackets was good enough. 

1889 inner_brackets.add(id(leaf)) 

1890 continue 

1891 

1892 if closing_bracket: 

1893 omit.add(id(closing_bracket)) 

1894 omit.update(inner_brackets) 

1895 inner_brackets.clear() 

1896 yield omit 

1897 

1898 if ( 

1899 prev 

1900 and prev.type == token.COMMA 

1901 and leaf.opening_bracket is not None 

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

1903 ): 

1904 # Never omit bracket pairs with trailing commas. 

1905 # We need to explode on those. 

1906 break 

1907 

1908 if leaf.value: 

1909 opening_bracket = leaf.opening_bracket 

1910 closing_bracket = leaf 

1911 

1912 

1913def run_transformer( 

1914 line: Line, 

1915 transform: Transformer, 

1916 mode: Mode, 

1917 features: Collection[Feature], 

1918 *, 

1919 line_str: str = "", 

1920) -> list[Line]: 

1921 if not line_str: 

1922 line_str = line_to_string(line) 

1923 result: list[Line] = [] 

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

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

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

1927 

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

1929 

1930 features_set = set(features) 

1931 if ( 

1932 Feature.FORCE_OPTIONAL_PARENTHESES in features_set 

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

1934 or not line.bracket_tracker.invisible 

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

1936 or line.contains_multiline_strings() 

1937 or result[0].contains_uncollapsable_type_comments() 

1938 or result[0].contains_unsplittable_type_ignore() 

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

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

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

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

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

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

1945 ): 

1946 return result 

1947 

1948 line_copy = line.clone() 

1949 append_leaves(line_copy, line, line.leaves) 

1950 features_fop = features_set | {Feature.FORCE_OPTIONAL_PARENTHESES} 

1951 second_opinion = run_transformer( 

1952 line_copy, transform, mode, features_fop, line_str=line_str 

1953 ) 

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

1955 result = second_opinion 

1956 return result