• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

psf / black / 7345276423

28 Dec 2023 06:59AM UTC coverage: 96.354% (-0.008%) from 96.362%
7345276423

push

github

web-flow
Unify docstring detection (#4095)

Co-authored-by: hauntsaninja <hauntsaninja@gmail.com>

3014 of 3226 branches covered (0.0%)

14 of 14 new or added lines in 5 files covered. (100.0%)

1 existing line in 1 file now uncovered.

7108 of 7377 relevant lines covered (96.35%)

4.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.29
/src/black/lines.py
1
import itertools
5✔
2
import math
5✔
3
from dataclasses import dataclass, field
5✔
4
from typing import (
5✔
5
    Callable,
6
    Dict,
7
    Iterator,
8
    List,
9
    Optional,
10
    Sequence,
11
    Tuple,
12
    TypeVar,
13
    Union,
14
    cast,
15
)
16

17
from black.brackets import COMMA_PRIORITY, DOT_PRIORITY, BracketTracker
5✔
18
from black.mode import Mode, Preview
5✔
19
from black.nodes import (
5✔
20
    BRACKETS,
21
    CLOSING_BRACKETS,
22
    OPENING_BRACKETS,
23
    STANDALONE_COMMENT,
24
    TEST_DESCENDANTS,
25
    child_towards,
26
    is_docstring,
27
    is_funcdef,
28
    is_import,
29
    is_multiline_string,
30
    is_one_sequence_between,
31
    is_type_comment,
32
    is_type_ignore_comment,
33
    is_with_or_async_with_stmt,
34
    make_simple_prefix,
35
    replace_child,
36
    syms,
37
    whitespace,
38
)
39
from black.strings import str_width
5✔
40
from blib2to3.pgen2 import token
5✔
41
from blib2to3.pytree import Leaf, Node
5✔
42

43
# types
44
T = TypeVar("T")
5✔
45
Index = int
5✔
46
LeafID = int
5✔
47
LN = Union[Leaf, Node]
5✔
48

49

50
@dataclass
5✔
51
class Line:
5✔
52
    """Holds leaves and comments. Can be printed with `str(line)`."""
53

54
    mode: Mode = field(repr=False)
5✔
55
    depth: int = 0
5✔
56
    leaves: List[Leaf] = field(default_factory=list)
5✔
57
    # keys ordered like `leaves`
58
    comments: Dict[LeafID, List[Leaf]] = field(default_factory=dict)
5✔
59
    bracket_tracker: BracketTracker = field(default_factory=BracketTracker)
5✔
60
    inside_brackets: bool = False
5✔
61
    should_split_rhs: bool = False
5✔
62
    magic_trailing_comma: Optional[Leaf] = None
5✔
63

64
    def append(
5✔
65
        self, leaf: Leaf, preformatted: bool = False, track_bracket: bool = False
66
    ) -> None:
67
        """Add a new `leaf` to the end of the line.
68

69
        Unless `preformatted` is True, the `leaf` will receive a new consistent
70
        whitespace prefix and metadata applied by :class:`BracketTracker`.
71
        Trailing commas are maybe removed, unpacked for loop variables are
72
        demoted from being delimiters.
73

74
        Inline comments are put aside.
75
        """
76
        has_value = leaf.type in BRACKETS or bool(leaf.value.strip())
5✔
77
        if not has_value:
5✔
78
            return
5✔
79

80
        if token.COLON == leaf.type and self.is_class_paren_empty:
5✔
81
            del self.leaves[-2:]
5✔
82
        if self.leaves and not preformatted:
5✔
83
            # Note: at this point leaf.prefix should be empty except for
84
            # imports, for which we only preserve newlines.
85
            leaf.prefix += whitespace(
5✔
86
                leaf,
87
                complex_subscript=self.is_complex_subscript(leaf),
88
                mode=self.mode,
89
            )
90
        if self.inside_brackets or not preformatted or track_bracket:
5✔
91
            self.bracket_tracker.mark(leaf)
5✔
92
            if self.mode.magic_trailing_comma:
5✔
93
                if self.has_magic_trailing_comma(leaf):
5✔
94
                    self.magic_trailing_comma = leaf
5✔
95
            elif self.has_magic_trailing_comma(leaf, ensure_removable=True):
5✔
96
                self.remove_trailing_comma()
5✔
97
        if not self.append_comment(leaf):
5✔
98
            self.leaves.append(leaf)
5✔
99

100
    def append_safe(self, leaf: Leaf, preformatted: bool = False) -> None:
5✔
101
        """Like :func:`append()` but disallow invalid standalone comment structure.
102

103
        Raises ValueError when any `leaf` is appended after a standalone comment
104
        or when a standalone comment is not the first leaf on the line.
105
        """
106
        if (
5✔
107
            self.bracket_tracker.depth == 0
108
            or self.bracket_tracker.any_open_for_or_lambda()
109
        ):
110
            if self.is_comment:
5✔
111
                raise ValueError("cannot append to standalone comments")
5✔
112

113
            if self.leaves and leaf.type == STANDALONE_COMMENT:
5✔
114
                raise ValueError(
5✔
115
                    "cannot append standalone comments to a populated line"
116
                )
117

118
        self.append(leaf, preformatted=preformatted)
5✔
119

120
    @property
5✔
121
    def is_comment(self) -> bool:
5✔
122
        """Is this line a standalone comment?"""
123
        return len(self.leaves) == 1 and self.leaves[0].type == STANDALONE_COMMENT
5✔
124

125
    @property
5✔
126
    def is_decorator(self) -> bool:
5✔
127
        """Is this line a decorator?"""
128
        return bool(self) and self.leaves[0].type == token.AT
5✔
129

130
    @property
5✔
131
    def is_import(self) -> bool:
5✔
132
        """Is this an import line?"""
133
        return bool(self) and is_import(self.leaves[0])
5✔
134

135
    @property
5✔
136
    def is_with_or_async_with_stmt(self) -> bool:
5✔
137
        """Is this a with_stmt line?"""
138
        return bool(self) and is_with_or_async_with_stmt(self.leaves[0])
5✔
139

140
    @property
5✔
141
    def is_class(self) -> bool:
5✔
142
        """Is this line a class definition?"""
143
        return (
5✔
144
            bool(self)
145
            and self.leaves[0].type == token.NAME
146
            and self.leaves[0].value == "class"
147
        )
148

149
    @property
5✔
150
    def is_stub_class(self) -> bool:
5✔
151
        """Is this line a class definition with a body consisting only of "..."?"""
152
        return self.is_class and self.leaves[-3:] == [
5✔
153
            Leaf(token.DOT, ".") for _ in range(3)
154
        ]
155

156
    @property
5✔
157
    def is_def(self) -> bool:
5✔
158
        """Is this a function definition? (Also returns True for async defs.)"""
159
        try:
5✔
160
            first_leaf = self.leaves[0]
5✔
161
        except IndexError:
×
162
            return False
×
163

164
        try:
5✔
165
            second_leaf: Optional[Leaf] = self.leaves[1]
5✔
166
        except IndexError:
5✔
167
            second_leaf = None
5✔
168
        return (first_leaf.type == token.NAME and first_leaf.value == "def") or (
5✔
169
            first_leaf.type == token.ASYNC
170
            and second_leaf is not None
171
            and second_leaf.type == token.NAME
172
            and second_leaf.value == "def"
173
        )
174

175
    @property
5✔
176
    def is_stub_def(self) -> bool:
5✔
177
        """Is this line a function definition with a body consisting only of "..."?"""
178
        return self.is_def and self.leaves[-4:] == [Leaf(token.COLON, ":")] + [
5✔
179
            Leaf(token.DOT, ".") for _ in range(3)
180
        ]
181

182
    @property
5✔
183
    def is_class_paren_empty(self) -> bool:
5✔
184
        """Is this a class with no base classes but using parentheses?
185

186
        Those are unnecessary and should be removed.
187
        """
188
        return (
5✔
189
            bool(self)
190
            and len(self.leaves) == 4
191
            and self.is_class
192
            and self.leaves[2].type == token.LPAR
193
            and self.leaves[2].value == "("
194
            and self.leaves[3].type == token.RPAR
195
            and self.leaves[3].value == ")"
196
        )
197

198
    @property
5✔
199
    def _is_triple_quoted_string(self) -> bool:
5✔
200
        """Is the line a triple quoted string?"""
201
        if not self or self.leaves[0].type != token.STRING:
5✔
202
            return False
5✔
203
        value = self.leaves[0].value
5✔
204
        if value.startswith(('"""', "'''")):
5✔
205
            return True
5✔
206
        if Preview.accept_raw_docstrings in self.mode and value.startswith(
5!
207
            ("r'''", 'r"""', "R'''", 'R"""')
208
        ):
UNCOV
209
            return True
×
210
        return False
5✔
211

212
    @property
5✔
213
    def is_docstring(self) -> bool:
5✔
214
        """Is the line a docstring?"""
215
        if Preview.unify_docstring_detection not in self.mode:
5✔
216
            return self._is_triple_quoted_string
5✔
217
        return bool(self) and is_docstring(self.leaves[0], self.mode)
5✔
218

219
    @property
5✔
220
    def is_chained_assignment(self) -> bool:
5✔
221
        """Is the line a chained assignment"""
222
        return [leaf.type for leaf in self.leaves].count(token.EQUAL) > 1
5✔
223

224
    @property
5✔
225
    def opens_block(self) -> bool:
5✔
226
        """Does this line open a new level of indentation."""
227
        if len(self.leaves) == 0:
5!
228
            return False
×
229
        return self.leaves[-1].type == token.COLON
5✔
230

231
    def is_fmt_pass_converted(
5✔
232
        self, *, first_leaf_matches: Optional[Callable[[Leaf], bool]] = None
233
    ) -> bool:
234
        """Is this line converted from fmt off/skip code?
235

236
        If first_leaf_matches is not None, it only returns True if the first
237
        leaf of converted code matches.
238
        """
239
        if len(self.leaves) != 1:
5✔
240
            return False
5✔
241
        leaf = self.leaves[0]
5✔
242
        if (
5✔
243
            leaf.type != STANDALONE_COMMENT
244
            or leaf.fmt_pass_converted_first_leaf is None
245
        ):
246
            return False
5✔
247
        return first_leaf_matches is None or first_leaf_matches(
5✔
248
            leaf.fmt_pass_converted_first_leaf
249
        )
250

251
    def contains_standalone_comments(self) -> bool:
5✔
252
        """If so, needs to be split before emitting."""
253
        for leaf in self.leaves:
5✔
254
            if leaf.type == STANDALONE_COMMENT:
5✔
255
                return True
5✔
256

257
        return False
5✔
258

259
    def contains_implicit_multiline_string_with_comments(self) -> bool:
5✔
260
        """Chck if we have an implicit multiline string with comments on the line"""
261
        for leaf_type, leaf_group_iterator in itertools.groupby(
5✔
262
            self.leaves, lambda leaf: leaf.type
263
        ):
264
            if leaf_type != token.STRING:
5✔
265
                continue
5✔
266
            leaf_list = list(leaf_group_iterator)
5✔
267
            if len(leaf_list) == 1:
5✔
268
                continue
5✔
269
            for leaf in leaf_list:
5✔
270
                if self.comments_after(leaf):
5✔
271
                    return True
5✔
272
        return False
5✔
273

274
    def contains_uncollapsable_type_comments(self) -> bool:
5✔
275
        ignored_ids = set()
5✔
276
        try:
5✔
277
            last_leaf = self.leaves[-1]
5✔
278
            ignored_ids.add(id(last_leaf))
5✔
279
            if last_leaf.type == token.COMMA or (
5✔
280
                last_leaf.type == token.RPAR and not last_leaf.value
281
            ):
282
                # When trailing commas or optional parens are inserted by Black for
283
                # consistency, comments after the previous last element are not moved
284
                # (they don't have to, rendering will still be correct).  So we ignore
285
                # trailing commas and invisible.
286
                last_leaf = self.leaves[-2]
5✔
287
                ignored_ids.add(id(last_leaf))
5✔
288
        except IndexError:
×
289
            return False
×
290

291
        # A type comment is uncollapsable if it is attached to a leaf
292
        # that isn't at the end of the line (since that could cause it
293
        # to get associated to a different argument) or if there are
294
        # comments before it (since that could cause it to get hidden
295
        # behind a comment.
296
        comment_seen = False
5✔
297
        for leaf_id, comments in self.comments.items():
5✔
298
            for comment in comments:
5✔
299
                if is_type_comment(comment):
5✔
300
                    if comment_seen or (
5✔
301
                        not is_type_ignore_comment(comment)
302
                        and leaf_id not in ignored_ids
303
                    ):
304
                        return True
5✔
305

306
                comment_seen = True
5✔
307

308
        return False
5✔
309

310
    def contains_unsplittable_type_ignore(self) -> bool:
5✔
311
        if not self.leaves:
5!
312
            return False
×
313

314
        # If a 'type: ignore' is attached to the end of a line, we
315
        # can't split the line, because we can't know which of the
316
        # subexpressions the ignore was meant to apply to.
317
        #
318
        # We only want this to apply to actual physical lines from the
319
        # original source, though: we don't want the presence of a
320
        # 'type: ignore' at the end of a multiline expression to
321
        # justify pushing it all onto one line. Thus we
322
        # (unfortunately) need to check the actual source lines and
323
        # only report an unsplittable 'type: ignore' if this line was
324
        # one line in the original code.
325

326
        # Grab the first and last line numbers, skipping generated leaves
327
        first_line = next((leaf.lineno for leaf in self.leaves if leaf.lineno != 0), 0)
5✔
328
        last_line = next(
5✔
329
            (leaf.lineno for leaf in reversed(self.leaves) if leaf.lineno != 0), 0
330
        )
331

332
        if first_line == last_line:
5✔
333
            # We look at the last two leaves since a comma or an
334
            # invisible paren could have been added at the end of the
335
            # line.
336
            for node in self.leaves[-2:]:
5✔
337
                for comment in self.comments.get(id(node), []):
5✔
338
                    if is_type_ignore_comment(comment):
5✔
339
                        return True
5✔
340

341
        return False
5✔
342

343
    def contains_multiline_strings(self) -> bool:
5✔
344
        return any(is_multiline_string(leaf) for leaf in self.leaves)
5✔
345

346
    def has_magic_trailing_comma(
5✔
347
        self, closing: Leaf, ensure_removable: bool = False
348
    ) -> bool:
349
        """Return True if we have a magic trailing comma, that is when:
350
        - there's a trailing comma here
351
        - it's not a one-tuple
352
        - it's not a single-element subscript
353
        Additionally, if ensure_removable:
354
        - it's not from square bracket indexing
355
        (specifically, single-element square bracket indexing)
356
        """
357
        if not (
5✔
358
            closing.type in CLOSING_BRACKETS
359
            and self.leaves
360
            and self.leaves[-1].type == token.COMMA
361
        ):
362
            return False
5✔
363

364
        if closing.type == token.RBRACE:
5✔
365
            return True
5✔
366

367
        if closing.type == token.RSQB:
5✔
368
            if (
5✔
369
                closing.parent is not None
370
                and closing.parent.type == syms.trailer
371
                and closing.opening_bracket is not None
372
                and is_one_sequence_between(
373
                    closing.opening_bracket,
374
                    closing,
375
                    self.leaves,
376
                    brackets=(token.LSQB, token.RSQB),
377
                )
378
            ):
379
                return False
5✔
380

381
            return True
5✔
382

383
        if self.is_import:
5✔
384
            return True
5✔
385

386
        if closing.opening_bracket is not None and not is_one_sequence_between(
5✔
387
            closing.opening_bracket, closing, self.leaves
388
        ):
389
            return True
5✔
390

391
        return False
5✔
392

393
    def append_comment(self, comment: Leaf) -> bool:
5✔
394
        """Add an inline or standalone comment to the line."""
395
        if (
5✔
396
            comment.type == STANDALONE_COMMENT
397
            and self.bracket_tracker.any_open_brackets()
398
        ):
399
            comment.prefix = ""
5✔
400
            return False
5✔
401

402
        if comment.type != token.COMMENT:
5✔
403
            return False
5✔
404

405
        if not self.leaves:
5✔
406
            comment.type = STANDALONE_COMMENT
5✔
407
            comment.prefix = ""
5✔
408
            return False
5✔
409

410
        last_leaf = self.leaves[-1]
5✔
411
        if (
5✔
412
            last_leaf.type == token.RPAR
413
            and not last_leaf.value
414
            and last_leaf.parent
415
            and len(list(last_leaf.parent.leaves())) <= 3
416
            and not is_type_comment(comment)
417
        ):
418
            # Comments on an optional parens wrapping a single leaf should belong to
419
            # the wrapped node except if it's a type comment. Pinning the comment like
420
            # this avoids unstable formatting caused by comment migration.
421
            if len(self.leaves) < 2:
5!
422
                comment.type = STANDALONE_COMMENT
×
423
                comment.prefix = ""
×
424
                return False
×
425

426
            last_leaf = self.leaves[-2]
5✔
427
        self.comments.setdefault(id(last_leaf), []).append(comment)
5✔
428
        return True
5✔
429

430
    def comments_after(self, leaf: Leaf) -> List[Leaf]:
5✔
431
        """Generate comments that should appear directly after `leaf`."""
432
        return self.comments.get(id(leaf), [])
5✔
433

434
    def remove_trailing_comma(self) -> None:
5✔
435
        """Remove the trailing comma and moves the comments attached to it."""
436
        trailing_comma = self.leaves.pop()
5✔
437
        trailing_comma_comments = self.comments.pop(id(trailing_comma), [])
5✔
438
        self.comments.setdefault(id(self.leaves[-1]), []).extend(
5✔
439
            trailing_comma_comments
440
        )
441

442
    def is_complex_subscript(self, leaf: Leaf) -> bool:
5✔
443
        """Return True iff `leaf` is part of a slice with non-trivial exprs."""
444
        open_lsqb = self.bracket_tracker.get_open_lsqb()
5✔
445
        if open_lsqb is None:
5✔
446
            return False
5✔
447

448
        subscript_start = open_lsqb.next_sibling
5✔
449

450
        if isinstance(subscript_start, Node):
5✔
451
            if subscript_start.type == syms.listmaker:
5✔
452
                return False
5✔
453

454
            if subscript_start.type == syms.subscriptlist:
5✔
455
                subscript_start = child_towards(subscript_start, leaf)
5✔
456

457
        # When this is moved out of preview, add syms.namedexpr_test directly to
458
        # TEST_DESCENDANTS in nodes.py
459
        if Preview.walrus_subscript in self.mode:
5✔
460
            test_decendants = TEST_DESCENDANTS | {syms.namedexpr_test}
5✔
461
        else:
462
            test_decendants = TEST_DESCENDANTS
5✔
463
        return subscript_start is not None and any(
5✔
464
            n.type in test_decendants for n in subscript_start.pre_order()
465
        )
466

467
    def enumerate_with_length(
5✔
468
        self, is_reversed: bool = False
469
    ) -> Iterator[Tuple[Index, Leaf, int]]:
470
        """Return an enumeration of leaves with their length.
471

472
        Stops prematurely on multiline strings and standalone comments.
473
        """
474
        op = cast(
5✔
475
            Callable[[Sequence[Leaf]], Iterator[Tuple[Index, Leaf]]],
476
            enumerate_reversed if is_reversed else enumerate,
477
        )
478
        for index, leaf in op(self.leaves):
5✔
479
            length = len(leaf.prefix) + len(leaf.value)
5✔
480
            if "\n" in leaf.value:
5✔
481
                return  # Multiline strings, we can't continue.
5✔
482

483
            for comment in self.comments_after(leaf):
5✔
484
                length += len(comment.value)
5✔
485

486
            yield index, leaf, length
5✔
487

488
    def clone(self) -> "Line":
5✔
489
        return Line(
5✔
490
            mode=self.mode,
491
            depth=self.depth,
492
            inside_brackets=self.inside_brackets,
493
            should_split_rhs=self.should_split_rhs,
494
            magic_trailing_comma=self.magic_trailing_comma,
495
        )
496

497
    def __str__(self) -> str:
5✔
498
        """Render the line."""
499
        if not self:
5✔
500
            return "\n"
5✔
501

502
        indent = "    " * self.depth
5✔
503
        leaves = iter(self.leaves)
5✔
504
        first = next(leaves)
5✔
505
        res = f"{first.prefix}{indent}{first.value}"
5✔
506
        for leaf in leaves:
5✔
507
            res += str(leaf)
5✔
508
        for comment in itertools.chain.from_iterable(self.comments.values()):
5✔
509
            res += str(comment)
5✔
510

511
        return res + "\n"
5✔
512

513
    def __bool__(self) -> bool:
5✔
514
        """Return True if the line has leaves or comments."""
515
        return bool(self.leaves or self.comments)
5✔
516

517

518
@dataclass
5✔
519
class RHSResult:
5✔
520
    """Intermediate split result from a right hand split."""
521

522
    head: Line
5✔
523
    body: Line
5✔
524
    tail: Line
5✔
525
    opening_bracket: Leaf
5✔
526
    closing_bracket: Leaf
5✔
527

528

529
@dataclass
5✔
530
class LinesBlock:
5✔
531
    """Class that holds information about a block of formatted lines.
532

533
    This is introduced so that the EmptyLineTracker can look behind the standalone
534
    comments and adjust their empty lines for class or def lines.
535
    """
536

537
    mode: Mode
5✔
538
    previous_block: Optional["LinesBlock"]
5✔
539
    original_line: Line
5✔
540
    before: int = 0
5✔
541
    content_lines: List[str] = field(default_factory=list)
5✔
542
    after: int = 0
5✔
543
    form_feed: bool = False
5✔
544

545
    def all_lines(self) -> List[str]:
5✔
546
        empty_line = str(Line(mode=self.mode))
5✔
547
        prefix = make_simple_prefix(self.before, self.form_feed, empty_line)
5✔
548
        return [prefix] + self.content_lines + [empty_line * self.after]
5✔
549

550

551
@dataclass
5✔
552
class EmptyLineTracker:
5✔
553
    """Provides a stateful method that returns the number of potential extra
554
    empty lines needed before and after the currently processed line.
555

556
    Note: this tracker works on lines that haven't been split yet.  It assumes
557
    the prefix of the first leaf consists of optional newlines.  Those newlines
558
    are consumed by `maybe_empty_lines()` and included in the computation.
559
    """
560

561
    mode: Mode
5✔
562
    previous_line: Optional[Line] = None
5✔
563
    previous_block: Optional[LinesBlock] = None
5✔
564
    previous_defs: List[Line] = field(default_factory=list)
5✔
565
    semantic_leading_comment: Optional[LinesBlock] = None
5✔
566

567
    def maybe_empty_lines(self, current_line: Line) -> LinesBlock:
5✔
568
        """Return the number of extra empty lines before and after the `current_line`.
569

570
        This is for separating `def`, `async def` and `class` with extra empty
571
        lines (two on module-level).
572
        """
573
        form_feed = (
5✔
574
            Preview.allow_form_feeds in self.mode
575
            and current_line.depth == 0
576
            and bool(current_line.leaves)
577
            and "\f\n" in current_line.leaves[0].prefix
578
        )
579
        before, after = self._maybe_empty_lines(current_line)
5✔
580
        previous_after = self.previous_block.after if self.previous_block else 0
5✔
581
        before = (
5✔
582
            # Black should not insert empty lines at the beginning
583
            # of the file
584
            0
585
            if self.previous_line is None
586
            else before - previous_after
587
        )
588
        if (
5✔
589
            Preview.module_docstring_newlines in current_line.mode
590
            and self.previous_block
591
            and self.previous_block.previous_block is None
592
            and len(self.previous_block.original_line.leaves) == 1
593
            and self.previous_block.original_line.is_docstring
594
            and not (current_line.is_class or current_line.is_def)
595
        ):
596
            before = 1
5✔
597

598
        block = LinesBlock(
5✔
599
            mode=self.mode,
600
            previous_block=self.previous_block,
601
            original_line=current_line,
602
            before=before,
603
            after=after,
604
            form_feed=form_feed,
605
        )
606

607
        # Maintain the semantic_leading_comment state.
608
        if current_line.is_comment:
5✔
609
            if self.previous_line is None or (
5✔
610
                not self.previous_line.is_decorator
611
                # `or before` means this comment already has an empty line before
612
                and (not self.previous_line.is_comment or before)
613
                and (self.semantic_leading_comment is None or before)
614
            ):
615
                self.semantic_leading_comment = block
5✔
616
        # `or before` means this decorator already has an empty line before
617
        elif not current_line.is_decorator or before:
5✔
618
            self.semantic_leading_comment = None
5✔
619

620
        self.previous_line = current_line
5✔
621
        self.previous_block = block
5✔
622
        return block
5✔
623

624
    def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
5✔
625
        max_allowed = 1
5✔
626
        if current_line.depth == 0:
5✔
627
            max_allowed = 1 if self.mode.is_pyi else 2
5✔
628
        if current_line.leaves:
5!
629
            # Consume the first leaf's extra newlines.
630
            first_leaf = current_line.leaves[0]
5✔
631
            before = first_leaf.prefix.count("\n")
5✔
632
            before = min(before, max_allowed)
5✔
633
            first_leaf.prefix = ""
5✔
634
        else:
635
            before = 0
×
636

637
        user_had_newline = bool(before)
5✔
638
        depth = current_line.depth
5✔
639

640
        previous_def = None
5✔
641
        while self.previous_defs and self.previous_defs[-1].depth >= depth:
5✔
642
            previous_def = self.previous_defs.pop()
5✔
643

644
        if previous_def is not None:
5✔
645
            assert self.previous_line is not None
5✔
646
            if self.mode.is_pyi:
5✔
647
                if depth and not current_line.is_def and self.previous_line.is_def:
5✔
648
                    # Empty lines between attributes and methods should be preserved.
649
                    before = 1 if user_had_newline else 0
5✔
650
                elif (
5✔
651
                    Preview.blank_line_after_nested_stub_class in self.mode
652
                    and previous_def.is_class
653
                    and not previous_def.is_stub_class
654
                ):
655
                    before = 1
5✔
656
                elif depth:
5✔
657
                    before = 0
5✔
658
                else:
659
                    before = 1
5✔
660
            else:
661
                if depth:
5✔
662
                    before = 1
5✔
663
                elif (
5✔
664
                    not depth
665
                    and previous_def.depth
666
                    and current_line.leaves[-1].type == token.COLON
667
                    and (
668
                        current_line.leaves[0].value
669
                        not in ("with", "try", "for", "while", "if", "match")
670
                    )
671
                ):
672
                    # We shouldn't add two newlines between an indented function and
673
                    # a dependent non-indented clause. This is to avoid issues with
674
                    # conditional function definitions that are technically top-level
675
                    # and therefore get two trailing newlines, but look weird and
676
                    # inconsistent when they're followed by elif, else, etc. This is
677
                    # worse because these functions only get *one* preceding newline
678
                    # already.
679
                    before = 1
5✔
680
                else:
681
                    before = 2
5✔
682

683
        if current_line.is_decorator or current_line.is_def or current_line.is_class:
5✔
684
            return self._maybe_empty_lines_for_class_or_def(
5✔
685
                current_line, before, user_had_newline
686
            )
687

688
        if (
5✔
689
            self.previous_line
690
            and self.previous_line.is_import
691
            and not current_line.is_import
692
            and not current_line.is_fmt_pass_converted(first_leaf_matches=is_import)
693
            and depth == self.previous_line.depth
694
        ):
695
            return (before or 1), 0
5✔
696

697
        if (
5✔
698
            self.previous_line
699
            and self.previous_line.is_class
700
            and current_line.is_docstring
701
        ):
702
            if Preview.no_blank_line_before_class_docstring in current_line.mode:
5✔
703
                return 0, 1
5✔
704
            return before, 1
5✔
705

706
        # In preview mode, always allow blank lines, except right before a function
707
        # docstring
708
        is_empty_first_line_ok = (
5✔
709
            Preview.allow_empty_first_line_in_block in current_line.mode
710
            and (
711
                not is_docstring(current_line.leaves[0], current_line.mode)
712
                or (
713
                    self.previous_line
714
                    and self.previous_line.leaves[0]
715
                    and self.previous_line.leaves[0].parent
716
                    and not is_funcdef(self.previous_line.leaves[0].parent)
717
                )
718
            )
719
        )
720

721
        if (
5✔
722
            self.previous_line
723
            and self.previous_line.opens_block
724
            and not is_empty_first_line_ok
725
        ):
726
            return 0, 0
5✔
727
        return before, 0
5✔
728

729
    def _maybe_empty_lines_for_class_or_def(  # noqa: C901
5✔
730
        self, current_line: Line, before: int, user_had_newline: bool
731
    ) -> Tuple[int, int]:
732
        if not current_line.is_decorator:
5✔
733
            self.previous_defs.append(current_line)
5✔
734
        if self.previous_line is None:
5✔
735
            # Don't insert empty lines before the first line in the file.
736
            return 0, 0
5✔
737

738
        if self.previous_line.is_decorator:
5✔
739
            if self.mode.is_pyi and current_line.is_stub_class:
5✔
740
                # Insert an empty line after a decorated stub class
741
                return 0, 1
5✔
742

743
            return 0, 0
5✔
744

745
        if self.previous_line.depth < current_line.depth and (
5✔
746
            self.previous_line.is_class or self.previous_line.is_def
747
        ):
748
            return 0, 0
5✔
749

750
        comment_to_add_newlines: Optional[LinesBlock] = None
5✔
751
        if (
5✔
752
            self.previous_line.is_comment
753
            and self.previous_line.depth == current_line.depth
754
            and before == 0
755
        ):
756
            slc = self.semantic_leading_comment
5✔
757
            if (
5✔
758
                slc is not None
759
                and slc.previous_block is not None
760
                and not slc.previous_block.original_line.is_class
761
                and not slc.previous_block.original_line.opens_block
762
                and slc.before <= 1
763
            ):
764
                comment_to_add_newlines = slc
5✔
765
            else:
766
                return 0, 0
5✔
767

768
        if self.mode.is_pyi:
5✔
769
            if current_line.is_class or self.previous_line.is_class:
5✔
770
                if self.previous_line.depth < current_line.depth:
5✔
771
                    newlines = 0
5✔
772
                elif self.previous_line.depth > current_line.depth:
5✔
773
                    newlines = 1
5✔
774
                elif current_line.is_stub_class and self.previous_line.is_stub_class:
5✔
775
                    # No blank line between classes with an empty body
776
                    newlines = 0
5✔
777
                else:
778
                    newlines = 1
5✔
779
            # Remove case `self.previous_line.depth > current_line.depth` below when
780
            # this becomes stable.
781
            #
782
            # Don't inspect the previous line if it's part of the body of the previous
783
            # statement in the same level, we always want a blank line if there's
784
            # something with a body preceding.
785
            elif (
5✔
786
                Preview.blank_line_between_nested_and_def_stub_file in current_line.mode
787
                and self.previous_line.depth > current_line.depth
788
            ):
789
                newlines = 1
5✔
790
            elif (
5✔
791
                current_line.is_def or current_line.is_decorator
792
            ) and not self.previous_line.is_def:
793
                if current_line.depth:
5✔
794
                    # In classes empty lines between attributes and methods should
795
                    # be preserved.
796
                    newlines = min(1, before)
5✔
797
                else:
798
                    # Blank line between a block of functions (maybe with preceding
799
                    # decorators) and a block of non-functions
800
                    newlines = 1
5✔
801
            elif self.previous_line.depth > current_line.depth:
5✔
802
                newlines = 1
5✔
803
            else:
804
                newlines = 0
5✔
805
        else:
806
            newlines = 1 if current_line.depth else 2
5✔
807
            # If a user has left no space after a dummy implementation, don't insert
808
            # new lines. This is useful for instance for @overload or Protocols.
809
            if (
5✔
810
                Preview.dummy_implementations in self.mode
811
                and self.previous_line.is_stub_def
812
                and not user_had_newline
813
            ):
814
                newlines = 0
5✔
815
        if comment_to_add_newlines is not None:
5✔
816
            previous_block = comment_to_add_newlines.previous_block
5✔
817
            if previous_block is not None:
5!
818
                comment_to_add_newlines.before = (
5✔
819
                    max(comment_to_add_newlines.before, newlines) - previous_block.after
820
                )
821
                newlines = 0
5✔
822
        return newlines, 0
5✔
823

824

825
def enumerate_reversed(sequence: Sequence[T]) -> Iterator[Tuple[Index, T]]:
5✔
826
    """Like `reversed(enumerate(sequence))` if that were possible."""
827
    index = len(sequence) - 1
5✔
828
    for element in reversed(sequence):
5✔
829
        yield (index, element)
5✔
830
        index -= 1
5✔
831

832

833
def append_leaves(
5✔
834
    new_line: Line, old_line: Line, leaves: List[Leaf], preformatted: bool = False
835
) -> None:
836
    """
837
    Append leaves (taken from @old_line) to @new_line, making sure to fix the
838
    underlying Node structure where appropriate.
839

840
    All of the leaves in @leaves are duplicated. The duplicates are then
841
    appended to @new_line and used to replace their originals in the underlying
842
    Node structure. Any comments attached to the old leaves are reattached to
843
    the new leaves.
844

845
    Pre-conditions:
846
        set(@leaves) is a subset of set(@old_line.leaves).
847
    """
848
    for old_leaf in leaves:
5✔
849
        new_leaf = Leaf(old_leaf.type, old_leaf.value)
5✔
850
        replace_child(old_leaf, new_leaf)
5✔
851
        new_line.append(new_leaf, preformatted=preformatted)
5✔
852

853
        for comment_leaf in old_line.comments_after(old_leaf):
5✔
854
            new_line.append(comment_leaf, preformatted=True)
5✔
855

856

857
def is_line_short_enough(  # noqa: C901
5✔
858
    line: Line, *, mode: Mode, line_str: str = ""
859
) -> bool:
860
    """For non-multiline strings, return True if `line` is no longer than `line_length`.
861
    For multiline strings, looks at the context around `line` to determine
862
    if it should be inlined or split up.
863
    Uses the provided `line_str` rendering, if any, otherwise computes a new one.
864
    """
865
    if not line_str:
5✔
866
        line_str = line_to_string(line)
5✔
867

868
    width = str_width if Preview.respect_east_asian_width in mode else len
5✔
869

870
    if Preview.multiline_string_handling not in mode:
5✔
871
        return (
5✔
872
            width(line_str) <= mode.line_length
873
            and "\n" not in line_str  # multiline strings
874
            and not line.contains_standalone_comments()
875
        )
876

877
    if line.contains_standalone_comments():
5✔
878
        return False
5✔
879
    if "\n" not in line_str:
5✔
880
        # No multiline strings (MLS) present
881
        return width(line_str) <= mode.line_length
5✔
882

883
    first, *_, last = line_str.split("\n")
5✔
884
    if width(first) > mode.line_length or width(last) > mode.line_length:
5✔
885
        return False
5✔
886

887
    # Traverse the AST to examine the context of the multiline string (MLS),
888
    # tracking aspects such as depth and comma existence,
889
    # to determine whether to split the MLS or keep it together.
890
    # Depth (which is based on the existing bracket_depth concept)
891
    # is needed to determine nesting level of the MLS.
892
    # Includes special case for trailing commas.
893
    commas: List[int] = []  # tracks number of commas per depth level
5✔
894
    multiline_string: Optional[Leaf] = None
5✔
895
    # store the leaves that contain parts of the MLS
896
    multiline_string_contexts: List[LN] = []
5✔
897

898
    max_level_to_update: Union[int, float] = math.inf  # track the depth of the MLS
5✔
899
    for i, leaf in enumerate(line.leaves):
5✔
900
        if max_level_to_update == math.inf:
5✔
901
            had_comma: Optional[int] = None
5✔
902
            if leaf.bracket_depth + 1 > len(commas):
5✔
903
                commas.append(0)
5✔
904
            elif leaf.bracket_depth + 1 < len(commas):
5✔
905
                had_comma = commas.pop()
5✔
906
            if (
5✔
907
                had_comma is not None
908
                and multiline_string is not None
909
                and multiline_string.bracket_depth == leaf.bracket_depth + 1
910
            ):
911
                # Have left the level with the MLS, stop tracking commas
912
                max_level_to_update = leaf.bracket_depth
5✔
913
                if had_comma > 0:
5✔
914
                    # MLS was in parens with at least one comma - force split
915
                    return False
5✔
916

917
        if leaf.bracket_depth <= max_level_to_update and leaf.type == token.COMMA:
5✔
918
            # Ignore non-nested trailing comma
919
            # directly after MLS/MLS-containing expression
920
            ignore_ctxs: List[Optional[LN]] = [None]
5✔
921
            ignore_ctxs += multiline_string_contexts
5✔
922
            if not (leaf.prev_sibling in ignore_ctxs and i == len(line.leaves) - 1):
5✔
923
                commas[leaf.bracket_depth] += 1
5✔
924
        if max_level_to_update != math.inf:
5✔
925
            max_level_to_update = min(max_level_to_update, leaf.bracket_depth)
5✔
926

927
        if is_multiline_string(leaf):
5✔
928
            if len(multiline_string_contexts) > 0:
5✔
929
                # >1 multiline string cannot fit on a single line - force split
930
                return False
5✔
931
            multiline_string = leaf
5✔
932
            ctx: LN = leaf
5✔
933
            # fetch the leaf components of the MLS in the AST
934
            while str(ctx) in line_str:
5✔
935
                multiline_string_contexts.append(ctx)
5✔
936
                if ctx.parent is None:
5!
937
                    break
×
938
                ctx = ctx.parent
5✔
939

940
    # May not have a triple-quoted multiline string at all,
941
    # in case of a regular string with embedded newlines and line continuations
942
    if len(multiline_string_contexts) == 0:
5✔
943
        return True
5✔
944

945
    return all(val == 0 for val in commas)
5✔
946

947

948
def can_be_split(line: Line) -> bool:
5✔
949
    """Return False if the line cannot be split *for sure*.
950

951
    This is not an exhaustive search but a cheap heuristic that we can use to
952
    avoid some unfortunate formattings (mostly around wrapping unsplittable code
953
    in unnecessary parentheses).
954
    """
955
    leaves = line.leaves
5✔
956
    if len(leaves) < 2:
5✔
957
        return False
5✔
958

959
    if leaves[0].type == token.STRING and leaves[1].type == token.DOT:
5✔
960
        call_count = 0
5✔
961
        dot_count = 0
5✔
962
        next = leaves[-1]
5✔
963
        for leaf in leaves[-2::-1]:
5!
964
            if leaf.type in OPENING_BRACKETS:
5✔
965
                if next.type not in CLOSING_BRACKETS:
5!
966
                    return False
×
967

968
                call_count += 1
5✔
969
            elif leaf.type == token.DOT:
5✔
970
                dot_count += 1
5✔
971
            elif leaf.type == token.NAME:
5✔
972
                if not (next.type == token.DOT or next.type in OPENING_BRACKETS):
5!
973
                    return False
5✔
974

975
            elif leaf.type not in CLOSING_BRACKETS:
5!
976
                return False
5✔
977

978
            if dot_count > 1 and call_count > 1:
5!
979
                return False
×
980

981
    return True
5✔
982

983

984
def can_omit_invisible_parens(
5✔
985
    rhs: RHSResult,
986
    line_length: int,
987
) -> bool:
988
    """Does `rhs.body` have a shape safe to reformat without optional parens around it?
989

990
    Returns True for only a subset of potentially nice looking formattings but
991
    the point is to not return false positives that end up producing lines that
992
    are too long.
993
    """
994
    line = rhs.body
5✔
995

996
    # We need optional parens in order to split standalone comments to their own lines
997
    # if there are no nested parens around the standalone comments
998
    closing_bracket: Optional[Leaf] = None
5✔
999
    for leaf in reversed(line.leaves):
5✔
1000
        if closing_bracket and leaf is closing_bracket.opening_bracket:
5✔
1001
            closing_bracket = None
5✔
1002
        if leaf.type == STANDALONE_COMMENT and not closing_bracket:
5✔
1003
            return False
5✔
1004
        if (
5✔
1005
            not closing_bracket
1006
            and leaf.type in CLOSING_BRACKETS
1007
            and leaf.opening_bracket in line.leaves
1008
            and leaf.value
1009
        ):
1010
            closing_bracket = leaf
5✔
1011

1012
    bt = line.bracket_tracker
5✔
1013
    if not bt.delimiters:
5✔
1014
        # Without delimiters the optional parentheses are useless.
1015
        return True
5✔
1016

1017
    max_priority = bt.max_delimiter_priority()
5✔
1018
    delimiter_count = bt.delimiter_count_with_priority(max_priority)
5✔
1019
    if delimiter_count > 1:
5✔
1020
        # With more than one delimiter of a kind the optional parentheses read better.
1021
        return False
5✔
1022

1023
    if delimiter_count == 1:
5!
1024
        if (
5✔
1025
            Preview.wrap_multiple_context_managers_in_parens in line.mode
1026
            and max_priority == COMMA_PRIORITY
1027
            and rhs.head.is_with_or_async_with_stmt
1028
        ):
1029
            # For two context manager with statements, the optional parentheses read
1030
            # better. In this case, `rhs.body` is the context managers part of
1031
            # the with statement. `rhs.head` is the `with (` part on the previous
1032
            # line.
1033
            return False
5✔
1034
        # Otherwise it may also read better, but we don't do it today and requires
1035
        # careful considerations for all possible cases. See
1036
        # https://github.com/psf/black/issues/2156.
1037

1038
    if max_priority == DOT_PRIORITY:
5✔
1039
        # A single stranded method call doesn't require optional parentheses.
1040
        return True
5✔
1041

1042
    assert len(line.leaves) >= 2, "Stranded delimiter"
5✔
1043

1044
    # With a single delimiter, omit if the expression starts or ends with
1045
    # a bracket.
1046
    first = line.leaves[0]
5✔
1047
    second = line.leaves[1]
5✔
1048
    if first.type in OPENING_BRACKETS and second.type not in CLOSING_BRACKETS:
5✔
1049
        if _can_omit_opening_paren(line, first=first, line_length=line_length):
5✔
1050
            return True
5✔
1051

1052
        # Note: we are not returning False here because a line might have *both*
1053
        # a leading opening bracket and a trailing closing bracket.  If the
1054
        # opening bracket doesn't match our rule, maybe the closing will.
1055

1056
    penultimate = line.leaves[-2]
5✔
1057
    last = line.leaves[-1]
5✔
1058

1059
    if (
5✔
1060
        last.type == token.RPAR
1061
        or last.type == token.RBRACE
1062
        or (
1063
            # don't use indexing for omitting optional parentheses;
1064
            # it looks weird
1065
            last.type == token.RSQB
1066
            and last.parent
1067
            and last.parent.type != syms.trailer
1068
        )
1069
    ):
1070
        if penultimate.type in OPENING_BRACKETS:
5✔
1071
            # Empty brackets don't help.
1072
            return False
5✔
1073

1074
        if is_multiline_string(first):
5✔
1075
            # Additional wrapping of a multiline string in this situation is
1076
            # unnecessary.
1077
            return True
5✔
1078

1079
        if _can_omit_closing_paren(line, last=last, line_length=line_length):
5✔
1080
            return True
5✔
1081

1082
    return False
5✔
1083

1084

1085
def _can_omit_opening_paren(line: Line, *, first: Leaf, line_length: int) -> bool:
5✔
1086
    """See `can_omit_invisible_parens`."""
1087
    remainder = False
5✔
1088
    length = 4 * line.depth
5✔
1089
    _index = -1
5✔
1090
    for _index, leaf, leaf_length in line.enumerate_with_length():
5✔
1091
        if leaf.type in CLOSING_BRACKETS and leaf.opening_bracket is first:
5✔
1092
            remainder = True
5✔
1093
        if remainder:
5✔
1094
            length += leaf_length
5✔
1095
            if length > line_length:
5✔
1096
                break
5✔
1097

1098
            if leaf.type in OPENING_BRACKETS:
5✔
1099
                # There are brackets we can further split on.
1100
                remainder = False
5✔
1101

1102
    else:
1103
        # checked the entire string and line length wasn't exceeded
1104
        if len(line.leaves) == _index + 1:
5!
1105
            return True
5✔
1106

1107
    return False
5✔
1108

1109

1110
def _can_omit_closing_paren(line: Line, *, last: Leaf, line_length: int) -> bool:
5✔
1111
    """See `can_omit_invisible_parens`."""
1112
    length = 4 * line.depth
5✔
1113
    seen_other_brackets = False
5✔
1114
    for _index, leaf, leaf_length in line.enumerate_with_length():
5✔
1115
        length += leaf_length
5✔
1116
        if leaf is last.opening_bracket:
5✔
1117
            if seen_other_brackets or length <= line_length:
5✔
1118
                return True
5✔
1119

1120
        elif leaf.type in OPENING_BRACKETS:
5✔
1121
            # There are brackets we can further split on.
1122
            seen_other_brackets = True
5✔
1123

1124
    return False
5✔
1125

1126

1127
def line_to_string(line: Line) -> str:
5✔
1128
    """Returns the string representation of @line.
1129

1130
    WARNING: This is known to be computationally expensive.
1131
    """
1132
    return str(line).strip("\n")
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc