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

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

746 statements  

1import itertools 

2import math 

3from collections.abc import Callable, Iterator, Sequence 

4from dataclasses import dataclass, field 

5from typing import NamedTuple, Optional, TypeVar, Union, cast 

6 

7from black.brackets import COMMA_PRIORITY, DOT_PRIORITY, BracketTracker 

8from black.mode import Mode, Preview 

9from black.nodes import ( 

10 BRACKETS, 

11 CLOSING_BRACKETS, 

12 OPENING_BRACKETS, 

13 STANDALONE_COMMENT, 

14 TEST_DESCENDANTS, 

15 child_towards, 

16 is_docstring, 

17 is_import, 

18 is_multiline_string, 

19 is_one_sequence_between, 

20 is_type_comment, 

21 is_type_ignore_comment, 

22 is_with_or_async_with_stmt, 

23 make_simple_prefix, 

24 replace_child, 

25 syms, 

26 whitespace, 

27) 

28from black.strings import str_width 

29from blib2to3.pgen2 import token 

30from blib2to3.pytree import Leaf, Node 

31 

32# types 

33T = TypeVar("T") 

34Index = int 

35LeafID = int 

36LN = Union[Leaf, Node] 

37 

38 

39@dataclass 

40class Line: 

41 """Holds leaves and comments. Can be printed with `str(line)`.""" 

42 

43 mode: Mode = field(repr=False) 

44 depth: int = 0 

45 leaves: list[Leaf] = field(default_factory=list) 

46 # keys ordered like `leaves` 

47 comments: dict[LeafID, list[Leaf]] = field(default_factory=dict) 

48 bracket_tracker: BracketTracker = field(default_factory=BracketTracker) 

49 inside_brackets: bool = False 

50 should_split_rhs: bool = False 

51 magic_trailing_comma: Leaf | None = None 

52 

53 def append( 

54 self, leaf: Leaf, preformatted: bool = False, track_bracket: bool = False 

55 ) -> None: 

56 """Add a new `leaf` to the end of the line. 

57 

58 Unless `preformatted` is True, the `leaf` will receive a new consistent 

59 whitespace prefix and metadata applied by :class:`BracketTracker`. 

60 Trailing commas are maybe removed, unpacked for loop variables are 

61 demoted from being delimiters. 

62 

63 Inline comments are put aside. 

64 """ 

65 has_value = ( 

66 leaf.type in BRACKETS 

67 # empty fstring and tstring middles must not be truncated 

68 or leaf.type in (token.FSTRING_MIDDLE, token.TSTRING_MIDDLE) 

69 or bool(leaf.value.strip()) 

70 ) 

71 if not has_value: 

72 return 

73 

74 if leaf.type == token.COLON and self.is_class_paren_empty: 

75 del self.leaves[-2:] 

76 if self.leaves and not preformatted: 

77 # Note: at this point leaf.prefix should be empty except for 

78 # imports, for which we only preserve newlines. 

79 leaf.prefix += whitespace( 

80 leaf, 

81 complex_subscript=self.is_complex_subscript(leaf), 

82 mode=self.mode, 

83 ) 

84 if self.inside_brackets or not preformatted or track_bracket: 

85 self.bracket_tracker.mark(leaf) 

86 if self.mode.magic_trailing_comma: 

87 if self.has_magic_trailing_comma(leaf): 

88 self.magic_trailing_comma = leaf 

89 elif self.has_magic_trailing_comma(leaf): 

90 self.remove_trailing_comma() 

91 if not self.append_comment(leaf): 

92 self.leaves.append(leaf) 

93 

94 def append_safe(self, leaf: Leaf, preformatted: bool = False) -> None: 

95 """Like :func:`append()` but disallow invalid standalone comment structure. 

96 

97 Raises ValueError when any `leaf` is appended after a standalone comment 

98 or when a standalone comment is not the first leaf on the line. 

99 """ 

100 if ( 

101 self.bracket_tracker.depth == 0 

102 or self.bracket_tracker.any_open_for_or_lambda() 

103 ): 

104 if self.is_comment: 

105 raise ValueError("cannot append to standalone comments") 

106 

107 if self.leaves and leaf.type == STANDALONE_COMMENT: 

108 raise ValueError( 

109 "cannot append standalone comments to a populated line" 

110 ) 

111 

112 self.append(leaf, preformatted=preformatted) 

113 

114 @property 

115 def is_comment(self) -> bool: 

116 """Is this line a standalone comment?""" 

117 return len(self.leaves) == 1 and self.leaves[0].type == STANDALONE_COMMENT 

118 

119 @property 

120 def is_decorator(self) -> bool: 

121 """Is this line a decorator?""" 

122 return bool(self) and self.leaves[0].type == token.AT 

123 

124 @property 

125 def is_import(self) -> bool: 

126 """Is this an import line?""" 

127 return bool(self) and is_import(self.leaves[0]) 

128 

129 @property 

130 def is_with_or_async_with_stmt(self) -> bool: 

131 """Is this a with_stmt line?""" 

132 return bool(self) and is_with_or_async_with_stmt(self.leaves[0]) 

133 

134 @property 

135 def is_class(self) -> bool: 

136 """Is this line a class definition?""" 

137 return ( 

138 bool(self) 

139 and self.leaves[0].type == token.NAME 

140 and self.leaves[0].value == "class" 

141 ) 

142 

143 @property 

144 def is_stub_class(self) -> bool: 

145 """Is this line a class definition with a body consisting only of "..."?""" 

146 return self.is_class and self.leaves[-3:] == [ 

147 Leaf(token.DOT, ".") for _ in range(3) 

148 ] 

149 

150 @property 

151 def is_def(self) -> bool: 

152 """Is this a function definition? (Also returns True for async defs.)""" 

153 try: 

154 first_leaf = self.leaves[0] 

155 except IndexError: 

156 return False 

157 

158 try: 

159 second_leaf: Leaf | None = self.leaves[1] 

160 except IndexError: 

161 second_leaf = None 

162 return (first_leaf.type == token.NAME and first_leaf.value == "def") or ( 

163 first_leaf.type == token.ASYNC 

164 and second_leaf is not None 

165 and second_leaf.type == token.NAME 

166 and second_leaf.value == "def" 

167 ) 

168 

169 @property 

170 def is_stub_def(self) -> bool: 

171 """Is this line a function definition with a body consisting only of "..."?""" 

172 return self.is_def and self.leaves[-4:] == [Leaf(token.COLON, ":")] + [ 

173 Leaf(token.DOT, ".") for _ in range(3) 

174 ] 

175 

176 @property 

177 def is_class_paren_empty(self) -> bool: 

178 """Is this a class with no base classes but using parentheses? 

179 

180 Those are unnecessary and should be removed. 

181 """ 

182 return ( 

183 bool(self) 

184 and len(self.leaves) == 4 

185 and self.is_class 

186 and self.leaves[2].type == token.LPAR 

187 and self.leaves[2].value == "(" 

188 and self.leaves[3].type == token.RPAR 

189 and self.leaves[3].value == ")" 

190 ) 

191 

192 @property 

193 def _is_triple_quoted_string(self) -> bool: 

194 """Is the line a triple quoted string?""" 

195 if not self or self.leaves[0].type != token.STRING: 

196 return False 

197 value = self.leaves[0].value 

198 if value.startswith(('"""', "'''")): 

199 return True 

200 if value.startswith(("r'''", 'r"""', "R'''", 'R"""')): 

201 return True 

202 return False 

203 

204 @property 

205 def is_docstring(self) -> bool: 

206 """Is the line a docstring?""" 

207 return bool(self) and is_docstring(self.leaves[0]) 

208 

209 @property 

210 def is_chained_assignment(self) -> bool: 

211 """Is the line a chained assignment""" 

212 return [leaf.type for leaf in self.leaves].count(token.EQUAL) > 1 

213 

214 @property 

215 def opens_block(self) -> bool: 

216 """Does this line open a new level of indentation.""" 

217 if len(self.leaves) == 0: 

218 return False 

219 return self.leaves[-1].type == token.COLON 

220 

221 def is_fmt_pass_converted( 

222 self, *, first_leaf_matches: Callable[[Leaf], bool] | None = None 

223 ) -> bool: 

224 """Is this line converted from fmt off/skip code? 

225 

226 If first_leaf_matches is not None, it only returns True if the first 

227 leaf of converted code matches. 

228 """ 

229 if len(self.leaves) != 1: 

230 return False 

231 leaf = self.leaves[0] 

232 if ( 

233 leaf.type != STANDALONE_COMMENT 

234 or leaf.fmt_pass_converted_first_leaf is None 

235 ): 

236 return False 

237 return first_leaf_matches is None or first_leaf_matches( 

238 leaf.fmt_pass_converted_first_leaf 

239 ) 

240 

241 def contains_standalone_comments(self) -> bool: 

242 """If so, needs to be split before emitting.""" 

243 for leaf in self.leaves: 

244 if leaf.type == STANDALONE_COMMENT: 

245 return True 

246 

247 return False 

248 

249 def contains_implicit_multiline_string_with_comments(self) -> bool: 

250 """Check if we have an implicit multiline string with comments on the line""" 

251 for leaf_type, leaf_group_iterator in itertools.groupby( 

252 self.leaves, lambda leaf: leaf.type 

253 ): 

254 if leaf_type != token.STRING: 

255 continue 

256 leaf_list = list(leaf_group_iterator) 

257 if len(leaf_list) == 1: 

258 continue 

259 for leaf in leaf_list: 

260 if self.comments_after(leaf): 

261 return True 

262 return False 

263 

264 def contains_uncollapsable_type_comments(self) -> bool: 

265 ignored_ids = set() 

266 try: 

267 last_leaf = self.leaves[-1] 

268 ignored_ids.add(id(last_leaf)) 

269 if last_leaf.type == token.COMMA or ( 

270 last_leaf.type == token.RPAR and not last_leaf.value 

271 ): 

272 # When trailing commas or optional parens are inserted by Black for 

273 # consistency, comments after the previous last element are not moved 

274 # (they don't have to, rendering will still be correct). So we ignore 

275 # trailing commas and invisible. 

276 last_leaf = self.leaves[-2] 

277 ignored_ids.add(id(last_leaf)) 

278 except IndexError: 

279 return False 

280 

281 # A type comment is uncollapsable if it is attached to a leaf 

282 # that isn't at the end of the line (since that could cause it 

283 # to get associated to a different argument) or if there are 

284 # comments before it (since that could cause it to get hidden 

285 # behind a comment. 

286 comment_seen = False 

287 for leaf_id, comments in self.comments.items(): 

288 for comment in comments: 

289 if is_type_comment(comment, mode=self.mode): 

290 if comment_seen or ( 

291 not is_type_ignore_comment(comment, mode=self.mode) 

292 and leaf_id not in ignored_ids 

293 ): 

294 return True 

295 

296 comment_seen = True 

297 

298 return False 

299 

300 def contains_unsplittable_type_ignore(self) -> bool: 

301 if not self.leaves: 

302 return False 

303 

304 # If a 'type: ignore' is attached to the end of a line, we 

305 # can't split the line, because we can't know which of the 

306 # subexpressions the ignore was meant to apply to. 

307 # 

308 # We only want this to apply to actual physical lines from the 

309 # original source, though: we don't want the presence of a 

310 # 'type: ignore' at the end of a multiline expression to 

311 # justify pushing it all onto one line. Thus we 

312 # (unfortunately) need to check the actual source lines and 

313 # only report an unsplittable 'type: ignore' if this line was 

314 # one line in the original code. 

315 

316 # Grab the first and last line numbers, skipping generated leaves 

317 first_line = next((leaf.lineno for leaf in self.leaves if leaf.lineno != 0), 0) 

318 last_line = next( 

319 (leaf.lineno for leaf in reversed(self.leaves) if leaf.lineno != 0), 0 

320 ) 

321 

322 if first_line == last_line: 

323 # We look at the last two leaves since a comma or an 

324 # invisible paren could have been added at the end of the 

325 # line. 

326 for node in self.leaves[-2:]: 

327 for comment in self.comments.get(id(node), []): 

328 if is_type_ignore_comment(comment, mode=self.mode): 

329 return True 

330 

331 return False 

332 

333 def contains_multiline_strings(self) -> bool: 

334 return any(is_multiline_string(leaf) for leaf in self.leaves) 

335 

336 def has_magic_trailing_comma(self, closing: Leaf) -> bool: 

337 """Return True if we have a magic trailing comma, that is when: 

338 - there's a trailing comma here 

339 - it's not from single-element square bracket indexing 

340 - it's not a one-tuple 

341 """ 

342 if not ( 

343 closing.type in CLOSING_BRACKETS 

344 and self.leaves 

345 and self.leaves[-1].type == token.COMMA 

346 ): 

347 return False 

348 

349 if closing.type == token.RBRACE: 

350 return True 

351 

352 if closing.type == token.RSQB: 

353 if ( 

354 closing.parent is not None 

355 and closing.parent.type == syms.trailer 

356 and closing.opening_bracket is not None 

357 and is_one_sequence_between( 

358 closing.opening_bracket, 

359 closing, 

360 self.leaves, 

361 brackets=(token.LSQB, token.RSQB), 

362 ) 

363 ): 

364 assert closing.prev_sibling is not None 

365 assert closing.prev_sibling.type == syms.subscriptlist 

366 return False 

367 

368 return True 

369 

370 if self.is_import: 

371 return True 

372 

373 if closing.opening_bracket is not None and not is_one_sequence_between( 

374 closing.opening_bracket, closing, self.leaves 

375 ): 

376 return True 

377 

378 return False 

379 

380 def append_comment(self, comment: Leaf) -> bool: 

381 """Add an inline or standalone comment to the line.""" 

382 if ( 

383 comment.type == STANDALONE_COMMENT 

384 and self.bracket_tracker.any_open_brackets() 

385 ): 

386 comment.prefix = "" 

387 return False 

388 

389 if comment.type != token.COMMENT: 

390 return False 

391 

392 if not self.leaves: 

393 comment.type = STANDALONE_COMMENT 

394 comment.prefix = "" 

395 return False 

396 

397 last_leaf = self.leaves[-1] 

398 if ( 

399 last_leaf.type == token.RPAR 

400 and not last_leaf.value 

401 and last_leaf.parent 

402 and len(list(last_leaf.parent.leaves())) <= 3 

403 and not is_type_comment(comment, mode=self.mode) 

404 ): 

405 # Comments on an optional parens wrapping a single leaf should belong to 

406 # the wrapped node except if it's a type comment. Pinning the comment like 

407 # this avoids unstable formatting caused by comment migration. 

408 if len(self.leaves) < 2: 

409 comment.type = STANDALONE_COMMENT 

410 comment.prefix = "" 

411 return False 

412 

413 last_leaf = self.leaves[-2] 

414 self.comments.setdefault(id(last_leaf), []).append(comment) 

415 return True 

416 

417 def comments_after(self, leaf: Leaf) -> list[Leaf]: 

418 """Generate comments that should appear directly after `leaf`.""" 

419 return self.comments.get(id(leaf), []) 

420 

421 def remove_trailing_comma(self) -> None: 

422 """Remove the trailing comma and moves the comments attached to it.""" 

423 trailing_comma = self.leaves.pop() 

424 trailing_comma_comments = self.comments.pop(id(trailing_comma), []) 

425 self.comments.setdefault(id(self.leaves[-1]), []).extend( 

426 trailing_comma_comments 

427 ) 

428 

429 def is_complex_subscript(self, leaf: Leaf) -> bool: 

430 """Return True iff `leaf` is part of a slice with non-trivial exprs.""" 

431 open_lsqb = self.bracket_tracker.get_open_lsqb() 

432 if open_lsqb is None: 

433 return False 

434 

435 subscript_start = open_lsqb.next_sibling 

436 

437 if isinstance(subscript_start, Node): 

438 if subscript_start.type == syms.listmaker: 

439 return False 

440 

441 if subscript_start.type == syms.subscriptlist: 

442 subscript_start = child_towards(subscript_start, leaf) 

443 

444 return subscript_start is not None and any( 

445 n.type in TEST_DESCENDANTS for n in subscript_start.pre_order() 

446 ) 

447 

448 def enumerate_with_length( 

449 self, is_reversed: bool = False 

450 ) -> Iterator[tuple[Index, Leaf, int]]: 

451 """Return an enumeration of leaves with their length. 

452 

453 Stops prematurely on multiline strings and standalone comments. 

454 """ 

455 op = cast( 

456 Callable[[Sequence[Leaf]], Iterator[tuple[Index, Leaf]]], 

457 enumerate_reversed if is_reversed else enumerate, 

458 ) 

459 for index, leaf in op(self.leaves): 

460 length = len(leaf.prefix) + len(leaf.value) 

461 if "\n" in leaf.value: 

462 return # Multiline strings, we can't continue. 

463 

464 for comment in self.comments_after(leaf): 

465 length += len(comment.value) 

466 

467 yield index, leaf, length 

468 

469 def clone(self) -> "Line": 

470 return Line( 

471 mode=self.mode, 

472 depth=self.depth, 

473 inside_brackets=self.inside_brackets, 

474 should_split_rhs=self.should_split_rhs, 

475 magic_trailing_comma=self.magic_trailing_comma, 

476 ) 

477 

478 def __str__(self) -> str: 

479 """Render the line.""" 

480 if not self: 

481 return "\n" 

482 

483 indent = " " * self.depth 

484 leaves = iter(self.leaves) 

485 first = next(leaves) 

486 res = f"{first.prefix}{indent}{first.value}" 

487 res += "".join(str(leaf) for leaf in leaves) 

488 comments_iter = itertools.chain.from_iterable(self.comments.values()) 

489 comments = [str(comment) for comment in comments_iter] 

490 res += "".join(comments) 

491 

492 return res + "\n" 

493 

494 def __bool__(self) -> bool: 

495 """Return True if the line has leaves or comments.""" 

496 return bool(self.leaves or self.comments) 

497 

498 

499@dataclass 

500class RHSResult: 

501 """Intermediate split result from a right hand split.""" 

502 

503 head: Line 

504 body: Line 

505 tail: Line 

506 opening_bracket: Leaf 

507 closing_bracket: Leaf 

508 

509 

510@dataclass 

511class LinesBlock: 

512 """Class that holds information about a block of formatted lines. 

513 

514 This is introduced so that the EmptyLineTracker can look behind the standalone 

515 comments and adjust their empty lines for class or def lines. 

516 """ 

517 

518 mode: Mode 

519 previous_block: Optional["LinesBlock"] 

520 original_line: Line 

521 before: int = 0 

522 content_lines: list[str] = field(default_factory=list) 

523 after: int = 0 

524 form_feed: bool = False 

525 

526 def all_lines(self) -> list[str]: 

527 empty_line = str(Line(mode=self.mode)) 

528 prefix = make_simple_prefix(self.before, self.form_feed, empty_line) 

529 return [prefix] + self.content_lines + [empty_line * self.after] 

530 

531 

532_WHITESPACE_TOKENS = ( 

533 token.NEWLINE, 

534 token.NL, 

535 token.INDENT, 

536 token.DEDENT, 

537 token.COMMENT, 

538 token.ENDMARKER, 

539) 

540 

541 

542class _DecoratedFuncInfo(NamedTuple): 

543 """Tracks the most recently seen decorated function for overload grouping.""" 

544 

545 name: str 

546 depth: int 

547 is_multi: bool 

548 

549 

550@dataclass 

551class EmptyLineTracker: 

552 """Provides a stateful method that returns the number of potential extra 

553 empty lines needed before and after the currently processed line. 

554 

555 Note: this tracker works on lines that haven't been split yet. It assumes 

556 the prefix of the first leaf consists of optional newlines. Those newlines 

557 are consumed by `maybe_empty_lines()` and included in the computation. 

558 """ 

559 

560 mode: Mode 

561 previous_line: Line | None = None 

562 previous_block: LinesBlock | None = None 

563 previous_defs: list[Line] = field(default_factory=list) 

564 semantic_leading_comment: LinesBlock | None = None 

565 _pyi_previous_decorated_func: _DecoratedFuncInfo | None = None 

566 

567 @staticmethod 

568 def _get_funcdef_name(node: Node | Leaf) -> str | None: 

569 """Extract the function name from a funcdef or async_funcdef node.""" 

570 funcdef: Node | Leaf = node 

571 if isinstance(node, Node) and node.type == syms.async_funcdef: 

572 for sub in node.children: 

573 if sub.type == syms.funcdef: 

574 funcdef = sub 

575 break 

576 if not isinstance(funcdef, Node) or funcdef.type != syms.funcdef: 

577 return None 

578 # Grammar: funcdef = 'def' NAME parameters ':' ... 

579 name_node = funcdef.children[1] 

580 assert isinstance(name_node, Leaf) 

581 return name_node.value 

582 

583 @staticmethod 

584 def _decorated_node_has_func_named(node: Node, name: str) -> bool: 

585 """Check if a ``decorated`` node contains a function with the given name.""" 

586 return any( 

587 sub.type in (syms.funcdef, syms.async_funcdef) 

588 and EmptyLineTracker._get_funcdef_name(sub) == name 

589 for sub in node.children 

590 ) 

591 

592 @staticmethod 

593 def _find_adjacent_decorated( 

594 node: Node | Leaf, *, reverse: bool = False 

595 ) -> Node | None: 

596 """Walk siblings skipping whitespace tokens, returning the first 

597 ``decorated`` node found or ``None``.""" 

598 sibling = node.prev_sibling if reverse else node.next_sibling 

599 while sibling is not None: 

600 if sibling.type == syms.decorated: 

601 assert isinstance(sibling, Node) 

602 return sibling 

603 elif sibling.type in _WHITESPACE_TOKENS: 

604 sibling = sibling.prev_sibling if reverse else sibling.next_sibling 

605 else: 

606 return None 

607 return None 

608 

609 @staticmethod 

610 def _if_stmt_branch_has_func_named( 

611 if_stmt: Node, exclude_suite: Node, name: str 

612 ) -> bool: 

613 """Check if any branch of an ``if_stmt`` (other than *exclude_suite*) 

614 contains a decorated function with the given *name*.""" 

615 for child in if_stmt.children: 

616 if not isinstance(child, Node): 

617 continue 

618 if child.type != syms.suite: 

619 continue 

620 if child is exclude_suite: 

621 continue 

622 for stmt in child.children: 

623 if not isinstance(stmt, Node): 

624 continue 

625 if stmt.type != syms.decorated: 

626 continue 

627 if EmptyLineTracker._decorated_node_has_func_named(stmt, name): 

628 return True 

629 break 

630 return False 

631 

632 @staticmethod 

633 def _get_def_name(line: Line) -> str | None: 

634 """Extract the function name from a line that is a function definition.""" 

635 if not line.is_def or not line.leaves: 

636 return None 

637 if line.leaves[0].value == "def": 

638 return line.leaves[1].value 

639 # async def: leaves = ['async', 'def', NAME, ...] 

640 return line.leaves[2].value 

641 

642 @staticmethod 

643 def _is_line_decorated(line: Line) -> bool: 

644 """Check if a def line is part of a decorated statement.""" 

645 if not line.is_def or not line.leaves: 

646 return False 

647 return EmptyLineTracker._find_decorated_node(line) is not None 

648 

649 @staticmethod 

650 def _find_decorated_node(line: Line) -> Node | None: 

651 """Walk up from the first leaf of a line to its ``decorated`` node.""" 

652 if not line.leaves: 

653 return None 

654 node = line.leaves[0].parent 

655 while node is not None: 

656 if node.type == syms.decorated: 

657 assert isinstance(node, Node) 

658 return node 

659 if node.type in (syms.suite, syms.file_input): 

660 return None 

661 node = node.parent 

662 return None 

663 

664 @staticmethod 

665 def _get_decorator_target_name(line: Line) -> str | None: 

666 """For a decorator line, extract the name of the function it decorates. 

667 

668 Only handles decorated functions, not decorated classes. 

669 """ 

670 if not line.is_decorator: 

671 return None 

672 decorated = EmptyLineTracker._find_decorated_node(line) 

673 if decorated is None: 

674 return None 

675 for child in decorated.children: 

676 if child.type in (syms.funcdef, syms.async_funcdef): 

677 return EmptyLineTracker._get_funcdef_name(child) 

678 return None 

679 

680 @staticmethod 

681 def _decorator_decorates_class(line: Line) -> bool: 

682 """Check if a decorator line decorates a class definition.""" 

683 if not line.is_decorator: 

684 return False 

685 decorated = EmptyLineTracker._find_decorated_node(line) 

686 if decorated is None: 

687 return False 

688 return any(child.type == syms.classdef for child in decorated.children) 

689 

690 def _is_in_current_group(self, current_line: Line) -> bool: 

691 """Check if current_line belongs to the same overload group being tracked.""" 

692 prev = self._pyi_previous_decorated_func 

693 if prev is None: 

694 return False 

695 cur_name = ( 

696 self._get_decorator_target_name(current_line) 

697 if current_line.is_decorator 

698 else self._get_def_name(current_line) 

699 ) 

700 return ( 

701 cur_name is not None 

702 and cur_name == prev.name 

703 and prev.depth == current_line.depth 

704 and (current_line.is_decorator or self._is_line_decorated(current_line)) 

705 ) 

706 

707 @staticmethod 

708 def _decorated_node_starts_group(decorated_node: Node) -> bool: 

709 """Check if a `decorated` AST node is the first in a multi-function group. 

710 

711 Returns True when the next statement-level sibling is also a decorated 

712 function with the same name. 

713 """ 

714 name = None 

715 for child in decorated_node.children: 

716 if child.type in (syms.funcdef, syms.async_funcdef): 

717 name = EmptyLineTracker._get_funcdef_name(child) 

718 break 

719 if name is None: 

720 return False 

721 adjacent = EmptyLineTracker._find_adjacent_decorated(decorated_node) 

722 return adjacent is not None and EmptyLineTracker._decorated_node_has_func_named( 

723 adjacent, name 

724 ) 

725 

726 @staticmethod 

727 def _is_start_of_decorated_group(line: Line) -> bool: 

728 """Check if a decorator line starts a multi-function group. 

729 

730 A multi-function group is 2+ consecutive decorated functions sharing the 

731 same name (e.g. @overload groups, @property + setter pairs). 

732 """ 

733 if not line.is_decorator: 

734 return False 

735 decorated_node = EmptyLineTracker._find_decorated_node(line) 

736 if decorated_node is None or decorated_node.parent is None: 

737 return False 

738 return EmptyLineTracker._decorated_node_starts_group(decorated_node) 

739 

740 @staticmethod 

741 def _get_block_first_decorated_funcname(line: Line) -> str | None: 

742 """Return the function name of the first decorated function in a block. 

743 

744 *line* must be a block-opening line (ending with ``:``) such as 

745 ``if ...:``. Returns ``None`` when the block doesn't start with a 

746 decorated function. 

747 """ 

748 if not line.leaves or line.leaves[-1].type != token.COLON: 

749 return None 

750 suite = line.leaves[-1].next_sibling 

751 if suite is None or not isinstance(suite, Node) or suite.type != syms.suite: 

752 return None 

753 for child in suite.children: 

754 if isinstance(child, Node) and child.type == syms.decorated: 

755 for sub in child.children: 

756 if sub.type in (syms.funcdef, syms.async_funcdef): 

757 return EmptyLineTracker._get_funcdef_name(sub) 

758 break 

759 if child.type not in (token.NEWLINE, token.NL, token.INDENT, token.DEDENT): 

760 break 

761 return None 

762 

763 @staticmethod 

764 def _block_is_part_of_overload_group(line: Line) -> bool: 

765 """Check if a block-opening line contains a decorated function that is 

766 part of a larger overload group — either because the ``if_stmt``'s next 

767 sibling is a same-name decorated function, or because another branch of 

768 the same ``if_stmt`` has one. 

769 """ 

770 func_name = EmptyLineTracker._get_block_first_decorated_funcname(line) 

771 if func_name is None: 

772 return False 

773 

774 suite = line.leaves[-1].next_sibling 

775 if suite is None or not isinstance(suite, Node): 

776 return False 

777 if_stmt = suite.parent 

778 if if_stmt is None or not isinstance(if_stmt, Node): 

779 return False 

780 

781 # Check if the if_stmt's next sibling is a same-name decorated function. 

782 adjacent = EmptyLineTracker._find_adjacent_decorated(if_stmt) 

783 if adjacent is not None and EmptyLineTracker._decorated_node_has_func_named( 

784 adjacent, func_name 

785 ): 

786 return True 

787 

788 # Check other branches (elif/else) of the same if_stmt. 

789 return EmptyLineTracker._if_stmt_branch_has_func_named( 

790 if_stmt, suite, func_name 

791 ) 

792 

793 @staticmethod 

794 def _is_decorator_in_conditional_overload(line: Line) -> bool: 

795 """Check if a decorator is inside an if/else block that is part of a 

796 broader overload group (a sibling or another branch of the same 

797 ``if_stmt`` contains a same-name decorated function).""" 

798 name = EmptyLineTracker._get_decorator_target_name(line) 

799 if name is None: 

800 return False 

801 

802 decorated = EmptyLineTracker._find_decorated_node(line) 

803 if decorated is None: 

804 return False 

805 

806 suite = decorated.parent 

807 if suite is None or suite.type != syms.suite: 

808 return False 

809 

810 if_stmt = suite.parent 

811 if if_stmt is None or if_stmt.type != syms.if_stmt: 

812 return False 

813 

814 # Check if_stmt's adjacent siblings for same-name decorated function. 

815 for reverse in (True, False): 

816 adjacent = EmptyLineTracker._find_adjacent_decorated( 

817 if_stmt, reverse=reverse 

818 ) 

819 if ( 

820 adjacent is not None 

821 and EmptyLineTracker._decorated_node_has_func_named(adjacent, name) 

822 ): 

823 return True 

824 

825 # Check other branches of the same if_stmt. 

826 return EmptyLineTracker._if_stmt_branch_has_func_named(if_stmt, suite, name) 

827 

828 def maybe_empty_lines(self, current_line: Line) -> LinesBlock: 

829 """Return the number of extra empty lines before and after the `current_line`. 

830 

831 This is for separating `def`, `async def` and `class` with extra empty 

832 lines (two on module-level). 

833 """ 

834 form_feed = ( 

835 current_line.depth == 0 

836 and bool(current_line.leaves) 

837 and "\f\n" in current_line.leaves[0].prefix 

838 ) 

839 before, after = self._maybe_empty_lines(current_line) 

840 previous_after = self.previous_block.after if self.previous_block else 0 

841 before = max(0, before - previous_after) 

842 

843 # Always have one empty line after a module docstring 

844 if self._line_is_module_docstring(current_line): 

845 before = 1 

846 

847 block = LinesBlock( 

848 mode=self.mode, 

849 previous_block=self.previous_block, 

850 original_line=current_line, 

851 before=before, 

852 after=after, 

853 form_feed=form_feed, 

854 ) 

855 

856 # Maintain the semantic_leading_comment state. 

857 if current_line.is_comment: 

858 if self.previous_line is None or ( 

859 not self.previous_line.is_decorator 

860 # `or before` means this comment already has an empty line before 

861 and (not self.previous_line.is_comment or before) 

862 and (self.semantic_leading_comment is None or before) 

863 ): 

864 self.semantic_leading_comment = block 

865 # `or before` means this decorator already has an empty line before 

866 elif not current_line.is_decorator or before: 

867 self.semantic_leading_comment = None 

868 

869 # Maintain _pyi_previous_decorated_func state for overload groups. 

870 # The tuple is (name, depth, is_multi) where is_multi indicates 

871 # the group has 2+ same-name decorated functions. 

872 overload_groups = ( 

873 self.mode.is_pyi and Preview.pyi_overload_group_blank_lines in self.mode 

874 ) 

875 if overload_groups: 

876 if current_line.is_def and self._is_line_decorated(current_line): 

877 name = self._get_def_name(current_line) 

878 if name is not None: 

879 prev = self._pyi_previous_decorated_func 

880 is_multi = ( 

881 prev is not None 

882 and prev.name == name 

883 and prev.depth == current_line.depth 

884 ) 

885 self._pyi_previous_decorated_func = _DecoratedFuncInfo( 

886 name=name, 

887 depth=current_line.depth, 

888 is_multi=is_multi 

889 or (prev is not None and prev.name == name and prev.is_multi), 

890 ) 

891 elif ( 

892 not current_line.is_decorator 

893 and not current_line.is_comment 

894 and ( 

895 self._pyi_previous_decorated_func is None 

896 or ( 

897 current_line.depth <= self._pyi_previous_decorated_func.depth 

898 # Don't reset on else/elif — they continue an if/else 

899 # chain that may contain overloads at a deeper depth. 

900 and not ( 

901 current_line.leaves 

902 and current_line.leaves[0].value in ("else", "elif") 

903 ) 

904 ) 

905 ) 

906 ): 

907 # Only reset when we see a non-decorator line at the same or 

908 # lower depth. Body lines (docstrings, ...) at deeper depth 

909 # should not clear the state. 

910 self._pyi_previous_decorated_func = None 

911 

912 self.previous_line = current_line 

913 self.previous_block = block 

914 return block 

915 

916 def _line_is_module_docstring(self, current_line: Line) -> bool: 

917 previous_block = self.previous_block 

918 if not previous_block: 

919 return False 

920 if ( 

921 len(previous_block.original_line.leaves) != 1 

922 or not previous_block.original_line.is_docstring 

923 or current_line.is_class 

924 or current_line.is_def 

925 ): 

926 return False 

927 while previous_block := previous_block.previous_block: 

928 if not previous_block.original_line.is_comment: 

929 return False 

930 return True 

931 

932 def _maybe_empty_lines(self, current_line: Line) -> tuple[int, int]: 

933 max_allowed = 1 

934 if current_line.depth == 0: 

935 max_allowed = 1 if self.mode.is_pyi else 2 

936 overload_groups = ( 

937 self.mode.is_pyi and Preview.pyi_overload_group_blank_lines in self.mode 

938 ) 

939 

940 if current_line.leaves: 

941 # Consume the first leaf's extra newlines. 

942 first_leaf = current_line.leaves[0] 

943 before = first_leaf.prefix.count("\n") 

944 before = min(before, max_allowed) 

945 first_leaf.prefix = "" 

946 else: 

947 before = 0 

948 

949 user_had_newline = bool(before) 

950 depth = current_line.depth 

951 

952 # Mutate self.previous_defs, remainder of this function should be pure 

953 previous_def = None 

954 while self.previous_defs and self.previous_defs[-1].depth >= depth: 

955 previous_def = self.previous_defs.pop() 

956 if current_line.is_def or current_line.is_class: 

957 self.previous_defs.append(current_line) 

958 

959 if self.previous_line is None: 

960 # Don't insert empty lines before the first line in the file. 

961 return 0, 0 

962 

963 if current_line.is_docstring: 

964 if self.previous_line.is_class: 

965 return 0, 1 

966 if self.previous_line.opens_block and self.previous_line.is_def: 

967 return 0, 0 

968 

969 if previous_def is not None: 

970 assert self.previous_line is not None 

971 # Note: for decorator/def/class lines, `before` computed here is 

972 # passed to _maybe_empty_lines_for_class_or_def which may override 

973 # it. This block still matters for non-decorator/def/class lines 

974 # (e.g. a `var: int` statement following an overload group). 

975 if self.mode.is_pyi: 

976 if previous_def.is_class and not previous_def.is_stub_class: 

977 before = 1 

978 elif ( 

979 overload_groups 

980 and self._pyi_previous_decorated_func is not None 

981 and self._pyi_previous_decorated_func.is_multi 

982 and not current_line.is_comment 

983 and self.previous_line.depth >= current_line.depth 

984 and not ( 

985 current_line.leaves 

986 and current_line.leaves[0].value in ("else", "elif") 

987 ) 

988 ): 

989 if self._is_in_current_group(current_line): 

990 before = 0 

991 elif current_line.opens_block and ( 

992 self._get_block_first_decorated_funcname(current_line) 

993 == self._pyi_previous_decorated_func.name 

994 ): 

995 before = 0 

996 else: 

997 before = 1 

998 elif ( 

999 overload_groups 

1000 and current_line.opens_block 

1001 and current_line.leaves 

1002 and current_line.leaves[0].value in ("else", "elif") 

1003 and self._block_is_part_of_overload_group(current_line) 

1004 ): 

1005 # else/elif continuing a conditional overload group: 

1006 # don't insert a blank line above. 

1007 before = 0 

1008 elif depth and not current_line.is_def and self.previous_line.is_def: 

1009 if ( 

1010 overload_groups 

1011 and current_line.opens_block 

1012 and self.previous_line.depth <= current_line.depth 

1013 and self._block_is_part_of_overload_group(current_line) 

1014 ): 

1015 before = 1 

1016 else: 

1017 # Empty lines between attributes and methods should 

1018 # be preserved. 

1019 before = 1 if user_had_newline else 0 

1020 elif ( 

1021 overload_groups 

1022 and current_line.is_comment 

1023 and self._pyi_previous_decorated_func is not None 

1024 and self._pyi_previous_decorated_func.depth == current_line.depth 

1025 ): 

1026 # Own-line comments after a decorated function in .pyi: 

1027 # preserve a single user blank line but never insert one. 

1028 # For non-overload cases the comment_to_add_newlines 

1029 # mechanism will retroactively add needed blank lines. 

1030 before = 1 if user_had_newline else 0 

1031 elif depth: 

1032 before = 0 

1033 else: 

1034 before = 1 

1035 else: 

1036 if depth: 

1037 before = 1 

1038 elif ( 

1039 not depth 

1040 and previous_def.depth 

1041 and current_line.leaves[-1].type == token.COLON 

1042 and ( 

1043 current_line.leaves[0].value 

1044 not in ("with", "try", "for", "while", "if", "match") 

1045 ) 

1046 ): 

1047 # We shouldn't add two newlines between an indented function and 

1048 # a dependent non-indented clause. This is to avoid issues with 

1049 # conditional function definitions that are technically top-level 

1050 # and therefore get two trailing newlines, but look weird and 

1051 # inconsistent when they're followed by elif, else, etc. This is 

1052 # worse because these functions only get *one* preceding newline 

1053 # already. 

1054 before = 1 

1055 else: 

1056 before = 2 

1057 

1058 if current_line.is_decorator or current_line.is_def or current_line.is_class: 

1059 return self._maybe_empty_lines_for_class_or_def( 

1060 current_line, before, user_had_newline 

1061 ) 

1062 

1063 if ( 

1064 self.previous_line.is_import 

1065 and self.previous_line.depth == 0 

1066 and current_line.depth == 0 

1067 and not current_line.is_import 

1068 and not current_line.is_fmt_pass_converted(first_leaf_matches=is_import) 

1069 ): 

1070 return 1, 0 

1071 

1072 if ( 

1073 self.previous_line.is_import 

1074 and not current_line.is_import 

1075 and not current_line.is_fmt_pass_converted(first_leaf_matches=is_import) 

1076 and depth == self.previous_line.depth 

1077 ): 

1078 return (before or 1), 0 

1079 

1080 return before, 0 

1081 

1082 def _maybe_empty_lines_for_class_or_def( 

1083 self, current_line: Line, before: int, user_had_newline: bool 

1084 ) -> tuple[int, int]: 

1085 assert self.previous_line is not None 

1086 overload_groups = ( 

1087 self.mode.is_pyi and Preview.pyi_overload_group_blank_lines in self.mode 

1088 ) 

1089 

1090 if self.previous_line.is_decorator: 

1091 if self.mode.is_pyi and current_line.is_stub_class: 

1092 # Insert an empty line after a decorated stub class 

1093 return 0, 1 

1094 return 0, 0 

1095 

1096 if self.previous_line.depth < current_line.depth and ( 

1097 self.previous_line.is_class or self.previous_line.is_def 

1098 ): 

1099 if self.mode.is_pyi: 

1100 return 0, 0 

1101 return 1 if user_had_newline else 0, 0 

1102 

1103 comment_to_add_newlines: LinesBlock | None = None 

1104 if ( 

1105 self.previous_line.is_comment 

1106 and self.previous_line.depth == current_line.depth 

1107 and before == 0 

1108 ): 

1109 slc = self.semantic_leading_comment 

1110 if ( 

1111 slc is not None 

1112 and slc.previous_block is not None 

1113 and not slc.previous_block.original_line.is_class 

1114 and not slc.previous_block.original_line.opens_block 

1115 and slc.before <= 1 

1116 ): 

1117 comment_to_add_newlines = slc 

1118 else: 

1119 return 0, 0 

1120 

1121 if self.mode.is_pyi: 

1122 if current_line.is_class or self.previous_line.is_class: 

1123 if self.previous_line.depth < current_line.depth: 

1124 newlines = 0 

1125 elif self.previous_line.depth > current_line.depth: 

1126 newlines = 1 

1127 elif current_line.is_stub_class and self.previous_line.is_stub_class: 

1128 # No blank line between classes with an empty body 

1129 newlines = 0 

1130 else: 

1131 newlines = 1 

1132 # Don't inspect the previous line if it's part of the body of the previous 

1133 # statement in the same level, we always want a blank line if there's 

1134 # something with a body preceding. 

1135 elif self.previous_line.depth > current_line.depth: 

1136 if overload_groups and self._is_in_current_group(current_line): 

1137 newlines = 0 

1138 else: 

1139 newlines = 1 

1140 elif ( 

1141 overload_groups 

1142 and self._pyi_previous_decorated_func is not None 

1143 and self._pyi_previous_decorated_func.is_multi 

1144 and self.previous_line.depth >= current_line.depth 

1145 ): 

1146 newlines = 0 if self._is_in_current_group(current_line) else 1 

1147 elif overload_groups and self._is_in_current_group(current_line): 

1148 # A comment between overloads may prevent is_multi from being 

1149 # set, but _is_in_current_group still detects name continuity. 

1150 newlines = 0 

1151 elif ( 

1152 overload_groups 

1153 and current_line.is_decorator 

1154 and self.previous_line.depth >= current_line.depth 

1155 and self._is_start_of_decorated_group(current_line) 

1156 and ( 

1157 self._pyi_previous_decorated_func is None 

1158 or ( 

1159 self._pyi_previous_decorated_func.name 

1160 != self._get_decorator_target_name(current_line) 

1161 ) 

1162 or self._pyi_previous_decorated_func.depth != current_line.depth 

1163 ) 

1164 ): 

1165 newlines = 1 

1166 elif ( 

1167 current_line.is_def or current_line.is_decorator 

1168 ) and not self.previous_line.is_def: 

1169 if ( 

1170 overload_groups 

1171 and current_line.is_decorator 

1172 and self.previous_line.is_comment 

1173 and self._is_decorator_in_conditional_overload(current_line) 

1174 ): 

1175 # Comment before an overload inside a conditional block: 

1176 # remove blank lines between the comment and decorator. 

1177 newlines = 0 

1178 elif current_line.depth: 

1179 # In classes empty lines between attributes and methods should 

1180 # be preserved. 

1181 newlines = min(1, before) 

1182 else: 

1183 # Blank line between a block of functions (maybe with preceding 

1184 # decorators) and a block of non-functions 

1185 newlines = 1 

1186 elif ( 

1187 Preview.pyi_blank_line_before_decorated_class in self.mode 

1188 and current_line.is_decorator 

1189 and self._decorator_decorates_class(current_line) 

1190 ): 

1191 newlines = 1 

1192 else: 

1193 newlines = 0 

1194 else: 

1195 newlines = 1 if current_line.depth else 2 

1196 # If a user has left no space after a dummy implementation, don't insert 

1197 # new lines. This is useful for instance for @overload or Protocols. 

1198 if self.previous_line.is_stub_def and not user_had_newline: 

1199 newlines = 0 

1200 if comment_to_add_newlines is not None: 

1201 previous_block = comment_to_add_newlines.previous_block 

1202 if previous_block is not None: 

1203 comment_to_add_newlines.before = ( 

1204 max(comment_to_add_newlines.before, newlines) - previous_block.after 

1205 ) 

1206 newlines = 0 

1207 return newlines, 0 

1208 

1209 

1210def enumerate_reversed(sequence: Sequence[T]) -> Iterator[tuple[Index, T]]: 

1211 """Like `reversed(enumerate(sequence))` if that were possible.""" 

1212 index = len(sequence) - 1 

1213 for element in reversed(sequence): 

1214 yield (index, element) 

1215 index -= 1 

1216 

1217 

1218def append_leaves( 

1219 new_line: Line, old_line: Line, leaves: list[Leaf], preformatted: bool = False 

1220) -> None: 

1221 """ 

1222 Append leaves (taken from @old_line) to @new_line, making sure to fix the 

1223 underlying Node structure where appropriate. 

1224 

1225 All of the leaves in @leaves are duplicated. The duplicates are then 

1226 appended to @new_line and used to replace their originals in the underlying 

1227 Node structure. Any comments attached to the old leaves are reattached to 

1228 the new leaves. 

1229 

1230 Pre-conditions: 

1231 set(@leaves) is a subset of set(@old_line.leaves). 

1232 """ 

1233 for old_leaf in leaves: 

1234 new_leaf = Leaf(old_leaf.type, old_leaf.value) 

1235 replace_child(old_leaf, new_leaf) 

1236 new_line.append(new_leaf, preformatted=preformatted) 

1237 

1238 for comment_leaf in old_line.comments_after(old_leaf): 

1239 new_line.append(comment_leaf, preformatted=True) 

1240 

1241 

1242def is_line_short_enough(line: Line, *, mode: Mode, line_str: str = "") -> bool: 

1243 """For non-multiline strings, return True if `line` is no longer than `line_length`. 

1244 For multiline strings, looks at the context around `line` to determine 

1245 if it should be inlined or split up. 

1246 Uses the provided `line_str` rendering, if any, otherwise computes a new one. 

1247 """ 

1248 if not line_str: 

1249 line_str = line_to_string(line) 

1250 

1251 if line.contains_standalone_comments(): 

1252 return False 

1253 if "\n" not in line_str: 

1254 # No multiline strings (MLS) present 

1255 return str_width(line_str) <= mode.line_length 

1256 

1257 first, *_, last = line_str.split("\n") 

1258 if str_width(first) > mode.line_length or str_width(last) > mode.line_length: 

1259 return False 

1260 

1261 # Traverse the AST to examine the context of the multiline string (MLS), 

1262 # tracking aspects such as depth and comma existence, 

1263 # to determine whether to split the MLS or keep it together. 

1264 # Depth (which is based on the existing bracket_depth concept) 

1265 # is needed to determine nesting level of the MLS. 

1266 # Includes special case for trailing commas. 

1267 commas: list[int] = [] # tracks number of commas per depth level 

1268 multiline_string: Leaf | None = None 

1269 # store the leaves that contain parts of the MLS 

1270 multiline_string_contexts: list[LN] = [] 

1271 

1272 max_level_to_update: int | float = math.inf # track the depth of the MLS 

1273 for i, leaf in enumerate(line.leaves): 

1274 if max_level_to_update == math.inf: 

1275 had_comma: int | None = None 

1276 if leaf.bracket_depth + 1 > len(commas): 

1277 commas.append(0) 

1278 elif leaf.bracket_depth + 1 < len(commas): 

1279 had_comma = commas.pop() 

1280 if ( 

1281 had_comma is not None 

1282 and multiline_string is not None 

1283 and multiline_string.bracket_depth == leaf.bracket_depth + 1 

1284 ): 

1285 # Have left the level with the MLS, stop tracking commas 

1286 max_level_to_update = leaf.bracket_depth 

1287 if had_comma > 0: 

1288 # MLS was in parens with at least one comma - force split 

1289 return False 

1290 

1291 if leaf.bracket_depth <= max_level_to_update and leaf.type == token.COMMA: 

1292 # Inside brackets, ignore trailing comma 

1293 # directly after MLS/MLS-containing expression 

1294 ignore_ctxs: list[LN | None] = [None] 

1295 ignore_ctxs += multiline_string_contexts 

1296 if (line.inside_brackets or leaf.bracket_depth > 0) and ( 

1297 i != len(line.leaves) - 1 or leaf.prev_sibling not in ignore_ctxs 

1298 ): 

1299 commas[leaf.bracket_depth] += 1 

1300 if max_level_to_update != math.inf: 

1301 max_level_to_update = min(max_level_to_update, leaf.bracket_depth) 

1302 

1303 if is_multiline_string(leaf): 

1304 if leaf.parent and ( 

1305 leaf.parent.type == syms.test 

1306 or (leaf.parent.parent and leaf.parent.parent.type == syms.dictsetmaker) 

1307 ): 

1308 # Keep ternary and dictionary values parenthesized 

1309 return False 

1310 if len(multiline_string_contexts) > 0: 

1311 # >1 multiline string cannot fit on a single line - force split 

1312 return False 

1313 multiline_string = leaf 

1314 ctx: LN = leaf 

1315 # fetch the leaf components of the MLS in the AST 

1316 while str(ctx) in line_str: 

1317 multiline_string_contexts.append(ctx) 

1318 if ctx.parent is None: 

1319 break 

1320 ctx = ctx.parent 

1321 

1322 # May not have a triple-quoted multiline string at all, 

1323 # in case of a regular string with embedded newlines and line continuations 

1324 if len(multiline_string_contexts) == 0: 

1325 return True 

1326 

1327 return all(val == 0 for val in commas) 

1328 

1329 

1330def can_be_split(line: Line) -> bool: 

1331 """Return False if the line cannot be split *for sure*. 

1332 

1333 This is not an exhaustive search but a cheap heuristic that we can use to 

1334 avoid some unfortunate formattings (mostly around wrapping unsplittable code 

1335 in unnecessary parentheses). 

1336 """ 

1337 leaves = line.leaves 

1338 if len(leaves) < 2: 

1339 return False 

1340 

1341 if leaves[0].type == token.STRING and leaves[1].type == token.DOT: 

1342 call_count = 0 

1343 dot_count = 0 

1344 next = leaves[-1] 

1345 for leaf in leaves[-2::-1]: 

1346 if leaf.type in OPENING_BRACKETS: 

1347 if next.type not in CLOSING_BRACKETS: 

1348 return False 

1349 

1350 call_count += 1 

1351 elif leaf.type == token.DOT: 

1352 dot_count += 1 

1353 elif leaf.type == token.NAME: 

1354 if not (next.type == token.DOT or next.type in OPENING_BRACKETS): 

1355 return False 

1356 

1357 elif leaf.type not in CLOSING_BRACKETS: 

1358 return False 

1359 

1360 if dot_count > 1 and call_count > 1: 

1361 return False 

1362 

1363 return True 

1364 

1365 

1366def can_omit_invisible_parens( 

1367 rhs: RHSResult, 

1368 line_length: int, 

1369) -> bool: 

1370 """Does `rhs.body` have a shape safe to reformat without optional parens around it? 

1371 

1372 Returns True for only a subset of potentially nice looking formattings but 

1373 the point is to not return false positives that end up producing lines that 

1374 are too long. 

1375 """ 

1376 line = rhs.body 

1377 

1378 # We can't omit parens if doing so would result in a type: ignore comment 

1379 # sharing a line with other comments, as that breaks type: ignore parsing. 

1380 # Check if the opening bracket (last leaf of head) has comments that would merge 

1381 # with comments from the first line of the body. 

1382 if rhs.head.leaves: 

1383 opening_bracket = rhs.head.leaves[-1] 

1384 head_comments = rhs.head.comments.get(id(opening_bracket), []) 

1385 

1386 # If there are comments on the opening bracket line, check if any would 

1387 # conflict with type: ignore comments in the body 

1388 if head_comments: 

1389 has_type_ignore_in_head = any( 

1390 is_type_ignore_comment(comment, mode=rhs.head.mode) 

1391 for comment in head_comments 

1392 ) 

1393 has_other_comment_in_head = any( 

1394 not is_type_ignore_comment(comment, mode=rhs.head.mode) 

1395 for comment in head_comments 

1396 ) 

1397 

1398 # Check for comments in the body that would potentially end up on the 

1399 # same line as the head comments when parens are removed 

1400 has_type_ignore_in_body = False 

1401 has_other_comment_in_body = False 

1402 for leaf in rhs.body.leaves: 

1403 for comment in rhs.body.comments.get(id(leaf), []): 

1404 if is_type_ignore_comment(comment, mode=rhs.body.mode): 

1405 has_type_ignore_in_body = True 

1406 else: 

1407 has_other_comment_in_body = True 

1408 

1409 # Preserve parens if we have both type: ignore and other comments that 

1410 # could end up on the same line 

1411 if (has_type_ignore_in_head and has_other_comment_in_body) or ( 

1412 has_other_comment_in_head and has_type_ignore_in_body 

1413 ): 

1414 return False 

1415 

1416 # We need optional parens in order to split standalone comments to their own lines 

1417 # if there are no nested parens around the standalone comments 

1418 closing_bracket: Leaf | None = None 

1419 for leaf in reversed(line.leaves): 

1420 if closing_bracket and leaf is closing_bracket.opening_bracket: 

1421 closing_bracket = None 

1422 if leaf.type == STANDALONE_COMMENT and not closing_bracket: 

1423 return False 

1424 if ( 

1425 not closing_bracket 

1426 and leaf.type in CLOSING_BRACKETS 

1427 and leaf.opening_bracket in line.leaves 

1428 and leaf.value 

1429 ): 

1430 closing_bracket = leaf 

1431 

1432 bt = line.bracket_tracker 

1433 if not bt.delimiters: 

1434 # Without delimiters the optional parentheses are useless. 

1435 return True 

1436 

1437 max_priority = bt.max_delimiter_priority() 

1438 delimiter_count = bt.delimiter_count_with_priority(max_priority) 

1439 if delimiter_count > 1: 

1440 # With more than one delimiter of a kind the optional parentheses read better. 

1441 return False 

1442 

1443 if delimiter_count == 1: 

1444 if max_priority == COMMA_PRIORITY and rhs.head.is_with_or_async_with_stmt: 

1445 # For two context manager with statements, the optional parentheses read 

1446 # better. In this case, `rhs.body` is the context managers part of 

1447 # the with statement. `rhs.head` is the `with (` part on the previous 

1448 # line. 

1449 return False 

1450 # Otherwise it may also read better, but we don't do it today and requires 

1451 # careful considerations for all possible cases. See 

1452 # https://github.com/psf/black/issues/2156. 

1453 

1454 if max_priority == DOT_PRIORITY: 

1455 # A single stranded method call doesn't require optional parentheses. 

1456 return True 

1457 

1458 assert len(line.leaves) >= 2, "Stranded delimiter" 

1459 

1460 # With a single delimiter, omit if the expression starts or ends with 

1461 # a bracket. 

1462 first = line.leaves[0] 

1463 second = line.leaves[1] 

1464 if first.type in OPENING_BRACKETS and second.type not in CLOSING_BRACKETS: 

1465 if _can_omit_opening_paren(line, first=first, line_length=line_length): 

1466 return True 

1467 

1468 # Note: we are not returning False here because a line might have *both* 

1469 # a leading opening bracket and a trailing closing bracket. If the 

1470 # opening bracket doesn't match our rule, maybe the closing will. 

1471 

1472 penultimate = line.leaves[-2] 

1473 last = line.leaves[-1] 

1474 

1475 if ( 

1476 last.type == token.RPAR 

1477 or last.type == token.RBRACE 

1478 or ( 

1479 # don't use indexing for omitting optional parentheses; 

1480 # it looks weird 

1481 last.type == token.RSQB 

1482 and last.parent 

1483 and last.parent.type != syms.trailer 

1484 ) 

1485 ): 

1486 if penultimate.type in OPENING_BRACKETS: 

1487 # Empty brackets don't help. 

1488 return False 

1489 

1490 if is_multiline_string(first): 

1491 # Additional wrapping of a multiline string in this situation is 

1492 # unnecessary. 

1493 return True 

1494 

1495 if _can_omit_closing_paren(line, last=last, line_length=line_length): 

1496 return True 

1497 

1498 return False 

1499 

1500 

1501def _can_omit_opening_paren(line: Line, *, first: Leaf, line_length: int) -> bool: 

1502 """See `can_omit_invisible_parens`.""" 

1503 remainder = False 

1504 length = 4 * line.depth 

1505 _index = -1 

1506 for _index, leaf, leaf_length in line.enumerate_with_length(): 

1507 if leaf.type in CLOSING_BRACKETS and leaf.opening_bracket is first: 

1508 remainder = True 

1509 if remainder: 

1510 length += leaf_length 

1511 if length > line_length: 

1512 break 

1513 

1514 if leaf.type in OPENING_BRACKETS: 

1515 # There are brackets we can further split on. 

1516 remainder = False 

1517 

1518 else: 

1519 # checked the entire string and line length wasn't exceeded 

1520 if len(line.leaves) == _index + 1: 

1521 return True 

1522 

1523 return False 

1524 

1525 

1526def _can_omit_closing_paren(line: Line, *, last: Leaf, line_length: int) -> bool: 

1527 """See `can_omit_invisible_parens`.""" 

1528 length = 4 * line.depth 

1529 seen_other_brackets = False 

1530 for _index, leaf, leaf_length in line.enumerate_with_length(): 

1531 length += leaf_length 

1532 if leaf is last.opening_bracket: 

1533 if seen_other_brackets or length <= line_length: 

1534 return True 

1535 

1536 elif leaf.type in OPENING_BRACKETS: 

1537 # There are brackets we can further split on. 

1538 seen_other_brackets = True 

1539 

1540 return False 

1541 

1542 

1543def line_to_string(line: Line) -> str: 

1544 """Returns the string representation of @line. 

1545 

1546 WARNING: This is known to be computationally expensive. 

1547 """ 

1548 return str(line).strip("\n")