]> git.madduck.net Git - etc/vim.git/blob - src/black/lines.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Enforce empty lines before classes/functions with sticky leading comments. (#3302)
[etc/vim.git] / src / black / lines.py
1 import itertools
2 import sys
3 from dataclasses import dataclass, field
4 from typing import (
5     Callable,
6     Dict,
7     Iterator,
8     List,
9     Optional,
10     Sequence,
11     Tuple,
12     TypeVar,
13     cast,
14 )
15
16 from black.brackets import DOT_PRIORITY, BracketTracker
17 from black.mode import Mode, Preview
18 from black.nodes import (
19     BRACKETS,
20     CLOSING_BRACKETS,
21     OPENING_BRACKETS,
22     STANDALONE_COMMENT,
23     TEST_DESCENDANTS,
24     child_towards,
25     is_import,
26     is_multiline_string,
27     is_one_sequence_between,
28     is_type_comment,
29     replace_child,
30     syms,
31     whitespace,
32 )
33 from blib2to3.pgen2 import token
34 from blib2to3.pytree import Leaf, Node
35
36 # types
37 T = TypeVar("T")
38 Index = int
39 LeafID = int
40
41
42 @dataclass
43 class Line:
44     """Holds leaves and comments. Can be printed with `str(line)`."""
45
46     mode: Mode
47     depth: int = 0
48     leaves: List[Leaf] = field(default_factory=list)
49     # keys ordered like `leaves`
50     comments: Dict[LeafID, List[Leaf]] = field(default_factory=dict)
51     bracket_tracker: BracketTracker = field(default_factory=BracketTracker)
52     inside_brackets: bool = False
53     should_split_rhs: bool = False
54     magic_trailing_comma: Optional[Leaf] = None
55
56     def append(self, leaf: Leaf, preformatted: bool = False) -> None:
57         """Add a new `leaf` to the end of the line.
58
59         Unless `preformatted` is True, the `leaf` will receive a new consistent
60         whitespace prefix and metadata applied by :class:`BracketTracker`.
61         Trailing commas are maybe removed, unpacked for loop variables are
62         demoted from being delimiters.
63
64         Inline comments are put aside.
65         """
66         has_value = leaf.type in BRACKETS or bool(leaf.value.strip())
67         if not has_value:
68             return
69
70         if token.COLON == leaf.type and self.is_class_paren_empty:
71             del self.leaves[-2:]
72         if self.leaves and not preformatted:
73             # Note: at this point leaf.prefix should be empty except for
74             # imports, for which we only preserve newlines.
75             leaf.prefix += whitespace(
76                 leaf, complex_subscript=self.is_complex_subscript(leaf)
77             )
78         if self.inside_brackets or not preformatted:
79             self.bracket_tracker.mark(leaf)
80             if self.mode.magic_trailing_comma:
81                 if self.has_magic_trailing_comma(leaf):
82                     self.magic_trailing_comma = leaf
83             elif self.has_magic_trailing_comma(leaf, ensure_removable=True):
84                 self.remove_trailing_comma()
85         if not self.append_comment(leaf):
86             self.leaves.append(leaf)
87
88     def append_safe(self, leaf: Leaf, preformatted: bool = False) -> None:
89         """Like :func:`append()` but disallow invalid standalone comment structure.
90
91         Raises ValueError when any `leaf` is appended after a standalone comment
92         or when a standalone comment is not the first leaf on the line.
93         """
94         if self.bracket_tracker.depth == 0:
95             if self.is_comment:
96                 raise ValueError("cannot append to standalone comments")
97
98             if self.leaves and leaf.type == STANDALONE_COMMENT:
99                 raise ValueError(
100                     "cannot append standalone comments to a populated line"
101                 )
102
103         self.append(leaf, preformatted=preformatted)
104
105     @property
106     def is_comment(self) -> bool:
107         """Is this line a standalone comment?"""
108         return len(self.leaves) == 1 and self.leaves[0].type == STANDALONE_COMMENT
109
110     @property
111     def is_decorator(self) -> bool:
112         """Is this line a decorator?"""
113         return bool(self) and self.leaves[0].type == token.AT
114
115     @property
116     def is_import(self) -> bool:
117         """Is this an import line?"""
118         return bool(self) and is_import(self.leaves[0])
119
120     @property
121     def is_class(self) -> bool:
122         """Is this line a class definition?"""
123         return (
124             bool(self)
125             and self.leaves[0].type == token.NAME
126             and self.leaves[0].value == "class"
127         )
128
129     @property
130     def is_stub_class(self) -> bool:
131         """Is this line a class definition with a body consisting only of "..."?"""
132         return self.is_class and self.leaves[-3:] == [
133             Leaf(token.DOT, ".") for _ in range(3)
134         ]
135
136     @property
137     def is_def(self) -> bool:
138         """Is this a function definition? (Also returns True for async defs.)"""
139         try:
140             first_leaf = self.leaves[0]
141         except IndexError:
142             return False
143
144         try:
145             second_leaf: Optional[Leaf] = self.leaves[1]
146         except IndexError:
147             second_leaf = None
148         return (first_leaf.type == token.NAME and first_leaf.value == "def") or (
149             first_leaf.type == token.ASYNC
150             and second_leaf is not None
151             and second_leaf.type == token.NAME
152             and second_leaf.value == "def"
153         )
154
155     @property
156     def is_class_paren_empty(self) -> bool:
157         """Is this a class with no base classes but using parentheses?
158
159         Those are unnecessary and should be removed.
160         """
161         return (
162             bool(self)
163             and len(self.leaves) == 4
164             and self.is_class
165             and self.leaves[2].type == token.LPAR
166             and self.leaves[2].value == "("
167             and self.leaves[3].type == token.RPAR
168             and self.leaves[3].value == ")"
169         )
170
171     @property
172     def is_triple_quoted_string(self) -> bool:
173         """Is the line a triple quoted string?"""
174         return (
175             bool(self)
176             and self.leaves[0].type == token.STRING
177             and self.leaves[0].value.startswith(('"""', "'''"))
178         )
179
180     @property
181     def opens_block(self) -> bool:
182         """Does this line open a new level of indentation."""
183         if len(self.leaves) == 0:
184             return False
185         return self.leaves[-1].type == token.COLON
186
187     def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
188         """If so, needs to be split before emitting."""
189         for leaf in self.leaves:
190             if leaf.type == STANDALONE_COMMENT and leaf.bracket_depth <= depth_limit:
191                 return True
192
193         return False
194
195     def contains_uncollapsable_type_comments(self) -> bool:
196         ignored_ids = set()
197         try:
198             last_leaf = self.leaves[-1]
199             ignored_ids.add(id(last_leaf))
200             if last_leaf.type == token.COMMA or (
201                 last_leaf.type == token.RPAR and not last_leaf.value
202             ):
203                 # When trailing commas or optional parens are inserted by Black for
204                 # consistency, comments after the previous last element are not moved
205                 # (they don't have to, rendering will still be correct).  So we ignore
206                 # trailing commas and invisible.
207                 last_leaf = self.leaves[-2]
208                 ignored_ids.add(id(last_leaf))
209         except IndexError:
210             return False
211
212         # A type comment is uncollapsable if it is attached to a leaf
213         # that isn't at the end of the line (since that could cause it
214         # to get associated to a different argument) or if there are
215         # comments before it (since that could cause it to get hidden
216         # behind a comment.
217         comment_seen = False
218         for leaf_id, comments in self.comments.items():
219             for comment in comments:
220                 if is_type_comment(comment):
221                     if comment_seen or (
222                         not is_type_comment(comment, " ignore")
223                         and leaf_id not in ignored_ids
224                     ):
225                         return True
226
227                 comment_seen = True
228
229         return False
230
231     def contains_unsplittable_type_ignore(self) -> bool:
232         if not self.leaves:
233             return False
234
235         # If a 'type: ignore' is attached to the end of a line, we
236         # can't split the line, because we can't know which of the
237         # subexpressions the ignore was meant to apply to.
238         #
239         # We only want this to apply to actual physical lines from the
240         # original source, though: we don't want the presence of a
241         # 'type: ignore' at the end of a multiline expression to
242         # justify pushing it all onto one line. Thus we
243         # (unfortunately) need to check the actual source lines and
244         # only report an unsplittable 'type: ignore' if this line was
245         # one line in the original code.
246
247         # Grab the first and last line numbers, skipping generated leaves
248         first_line = next((leaf.lineno for leaf in self.leaves if leaf.lineno != 0), 0)
249         last_line = next(
250             (leaf.lineno for leaf in reversed(self.leaves) if leaf.lineno != 0), 0
251         )
252
253         if first_line == last_line:
254             # We look at the last two leaves since a comma or an
255             # invisible paren could have been added at the end of the
256             # line.
257             for node in self.leaves[-2:]:
258                 for comment in self.comments.get(id(node), []):
259                     if is_type_comment(comment, " ignore"):
260                         return True
261
262         return False
263
264     def contains_multiline_strings(self) -> bool:
265         return any(is_multiline_string(leaf) for leaf in self.leaves)
266
267     def has_magic_trailing_comma(
268         self, closing: Leaf, ensure_removable: bool = False
269     ) -> bool:
270         """Return True if we have a magic trailing comma, that is when:
271         - there's a trailing comma here
272         - it's not a one-tuple
273         - it's not a single-element subscript
274         Additionally, if ensure_removable:
275         - it's not from square bracket indexing
276         (specifically, single-element square bracket indexing with
277         Preview.skip_magic_trailing_comma_in_subscript)
278         """
279         if not (
280             closing.type in CLOSING_BRACKETS
281             and self.leaves
282             and self.leaves[-1].type == token.COMMA
283         ):
284             return False
285
286         if closing.type == token.RBRACE:
287             return True
288
289         if closing.type == token.RSQB:
290             if (
291                 Preview.one_element_subscript in self.mode
292                 and closing.parent
293                 and closing.parent.type == syms.trailer
294                 and closing.opening_bracket
295                 and is_one_sequence_between(
296                     closing.opening_bracket,
297                     closing,
298                     self.leaves,
299                     brackets=(token.LSQB, token.RSQB),
300                 )
301             ):
302                 return False
303
304             if not ensure_removable:
305                 return True
306
307             comma = self.leaves[-1]
308             if comma.parent is None:
309                 return False
310             if Preview.skip_magic_trailing_comma_in_subscript in self.mode:
311                 return (
312                     comma.parent.type != syms.subscriptlist
313                     or closing.opening_bracket is None
314                     or not is_one_sequence_between(
315                         closing.opening_bracket,
316                         closing,
317                         self.leaves,
318                         brackets=(token.LSQB, token.RSQB),
319                     )
320                 )
321             return comma.parent.type == syms.listmaker
322
323         if self.is_import:
324             return True
325
326         if closing.opening_bracket is not None and not is_one_sequence_between(
327             closing.opening_bracket, closing, self.leaves
328         ):
329             return True
330
331         return False
332
333     def append_comment(self, comment: Leaf) -> bool:
334         """Add an inline or standalone comment to the line."""
335         if (
336             comment.type == STANDALONE_COMMENT
337             and self.bracket_tracker.any_open_brackets()
338         ):
339             comment.prefix = ""
340             return False
341
342         if comment.type != token.COMMENT:
343             return False
344
345         if not self.leaves:
346             comment.type = STANDALONE_COMMENT
347             comment.prefix = ""
348             return False
349
350         last_leaf = self.leaves[-1]
351         if (
352             last_leaf.type == token.RPAR
353             and not last_leaf.value
354             and last_leaf.parent
355             and len(list(last_leaf.parent.leaves())) <= 3
356             and not is_type_comment(comment)
357         ):
358             # Comments on an optional parens wrapping a single leaf should belong to
359             # the wrapped node except if it's a type comment. Pinning the comment like
360             # this avoids unstable formatting caused by comment migration.
361             if len(self.leaves) < 2:
362                 comment.type = STANDALONE_COMMENT
363                 comment.prefix = ""
364                 return False
365
366             last_leaf = self.leaves[-2]
367         self.comments.setdefault(id(last_leaf), []).append(comment)
368         return True
369
370     def comments_after(self, leaf: Leaf) -> List[Leaf]:
371         """Generate comments that should appear directly after `leaf`."""
372         return self.comments.get(id(leaf), [])
373
374     def remove_trailing_comma(self) -> None:
375         """Remove the trailing comma and moves the comments attached to it."""
376         trailing_comma = self.leaves.pop()
377         trailing_comma_comments = self.comments.pop(id(trailing_comma), [])
378         self.comments.setdefault(id(self.leaves[-1]), []).extend(
379             trailing_comma_comments
380         )
381
382     def is_complex_subscript(self, leaf: Leaf) -> bool:
383         """Return True iff `leaf` is part of a slice with non-trivial exprs."""
384         open_lsqb = self.bracket_tracker.get_open_lsqb()
385         if open_lsqb is None:
386             return False
387
388         subscript_start = open_lsqb.next_sibling
389
390         if isinstance(subscript_start, Node):
391             if subscript_start.type == syms.listmaker:
392                 return False
393
394             if subscript_start.type == syms.subscriptlist:
395                 subscript_start = child_towards(subscript_start, leaf)
396         return subscript_start is not None and any(
397             n.type in TEST_DESCENDANTS for n in subscript_start.pre_order()
398         )
399
400     def enumerate_with_length(
401         self, reversed: bool = False
402     ) -> Iterator[Tuple[Index, Leaf, int]]:
403         """Return an enumeration of leaves with their length.
404
405         Stops prematurely on multiline strings and standalone comments.
406         """
407         op = cast(
408             Callable[[Sequence[Leaf]], Iterator[Tuple[Index, Leaf]]],
409             enumerate_reversed if reversed else enumerate,
410         )
411         for index, leaf in op(self.leaves):
412             length = len(leaf.prefix) + len(leaf.value)
413             if "\n" in leaf.value:
414                 return  # Multiline strings, we can't continue.
415
416             for comment in self.comments_after(leaf):
417                 length += len(comment.value)
418
419             yield index, leaf, length
420
421     def clone(self) -> "Line":
422         return Line(
423             mode=self.mode,
424             depth=self.depth,
425             inside_brackets=self.inside_brackets,
426             should_split_rhs=self.should_split_rhs,
427             magic_trailing_comma=self.magic_trailing_comma,
428         )
429
430     def __str__(self) -> str:
431         """Render the line."""
432         if not self:
433             return "\n"
434
435         indent = "    " * self.depth
436         leaves = iter(self.leaves)
437         first = next(leaves)
438         res = f"{first.prefix}{indent}{first.value}"
439         for leaf in leaves:
440             res += str(leaf)
441         for comment in itertools.chain.from_iterable(self.comments.values()):
442             res += str(comment)
443
444         return res + "\n"
445
446     def __bool__(self) -> bool:
447         """Return True if the line has leaves or comments."""
448         return bool(self.leaves or self.comments)
449
450
451 @dataclass
452 class LinesBlock:
453     """Class that holds information about a block of formatted lines.
454
455     This is introduced so that the EmptyLineTracker can look behind the standalone
456     comments and adjust their empty lines for class or def lines.
457     """
458
459     mode: Mode
460     previous_block: Optional["LinesBlock"]
461     original_line: Line
462     before: int = 0
463     content_lines: List[str] = field(default_factory=list)
464     after: int = 0
465
466     def all_lines(self) -> List[str]:
467         empty_line = str(Line(mode=self.mode))
468         return (
469             [empty_line * self.before] + self.content_lines + [empty_line * self.after]
470         )
471
472
473 @dataclass
474 class EmptyLineTracker:
475     """Provides a stateful method that returns the number of potential extra
476     empty lines needed before and after the currently processed line.
477
478     Note: this tracker works on lines that haven't been split yet.  It assumes
479     the prefix of the first leaf consists of optional newlines.  Those newlines
480     are consumed by `maybe_empty_lines()` and included in the computation.
481     """
482
483     mode: Mode
484     previous_line: Optional[Line] = None
485     previous_block: Optional[LinesBlock] = None
486     previous_defs: List[int] = field(default_factory=list)
487     semantic_leading_comment: Optional[LinesBlock] = None
488
489     def maybe_empty_lines(self, current_line: Line) -> LinesBlock:
490         """Return the number of extra empty lines before and after the `current_line`.
491
492         This is for separating `def`, `async def` and `class` with extra empty
493         lines (two on module-level).
494         """
495         before, after = self._maybe_empty_lines(current_line)
496         previous_after = self.previous_block.after if self.previous_block else 0
497         before = (
498             # Black should not insert empty lines at the beginning
499             # of the file
500             0
501             if self.previous_line is None
502             else before - previous_after
503         )
504         block = LinesBlock(
505             mode=self.mode,
506             previous_block=self.previous_block,
507             original_line=current_line,
508             before=before,
509             after=after,
510         )
511
512         # Maintain the semantic_leading_comment state.
513         if current_line.is_comment:
514             if self.previous_line is None or (
515                 not self.previous_line.is_decorator
516                 # `or before` means this comment already has an empty line before
517                 and (not self.previous_line.is_comment or before)
518                 and (self.semantic_leading_comment is None or before)
519             ):
520                 self.semantic_leading_comment = block
521         elif not current_line.is_decorator:
522             self.semantic_leading_comment = None
523
524         self.previous_line = current_line
525         self.previous_block = block
526         return block
527
528     def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
529         max_allowed = 1
530         if current_line.depth == 0:
531             max_allowed = 1 if self.mode.is_pyi else 2
532         if current_line.leaves:
533             # Consume the first leaf's extra newlines.
534             first_leaf = current_line.leaves[0]
535             before = first_leaf.prefix.count("\n")
536             before = min(before, max_allowed)
537             first_leaf.prefix = ""
538         else:
539             before = 0
540         depth = current_line.depth
541         while self.previous_defs and self.previous_defs[-1] >= depth:
542             if self.mode.is_pyi:
543                 assert self.previous_line is not None
544                 if depth and not current_line.is_def and self.previous_line.is_def:
545                     # Empty lines between attributes and methods should be preserved.
546                     before = min(1, before)
547                 elif depth:
548                     before = 0
549                 else:
550                     before = 1
551             else:
552                 if depth:
553                     before = 1
554                 elif (
555                     not depth
556                     and self.previous_defs[-1]
557                     and current_line.leaves[-1].type == token.COLON
558                     and (
559                         current_line.leaves[0].value
560                         not in ("with", "try", "for", "while", "if", "match")
561                     )
562                 ):
563                     # We shouldn't add two newlines between an indented function and
564                     # a dependent non-indented clause. This is to avoid issues with
565                     # conditional function definitions that are technically top-level
566                     # and therefore get two trailing newlines, but look weird and
567                     # inconsistent when they're followed by elif, else, etc. This is
568                     # worse because these functions only get *one* preceding newline
569                     # already.
570                     before = 1
571                 else:
572                     before = 2
573             self.previous_defs.pop()
574         if current_line.is_decorator or current_line.is_def or current_line.is_class:
575             return self._maybe_empty_lines_for_class_or_def(current_line, before)
576
577         if (
578             self.previous_line
579             and self.previous_line.is_import
580             and not current_line.is_import
581             and depth == self.previous_line.depth
582         ):
583             return (before or 1), 0
584
585         if (
586             self.previous_line
587             and self.previous_line.is_class
588             and current_line.is_triple_quoted_string
589         ):
590             return before, 1
591
592         if (
593             Preview.remove_block_trailing_newline in current_line.mode
594             and self.previous_line
595             and self.previous_line.opens_block
596         ):
597             return 0, 0
598         return before, 0
599
600     def _maybe_empty_lines_for_class_or_def(
601         self, current_line: Line, before: int
602     ) -> Tuple[int, int]:
603         if not current_line.is_decorator:
604             self.previous_defs.append(current_line.depth)
605         if self.previous_line is None:
606             # Don't insert empty lines before the first line in the file.
607             return 0, 0
608
609         if self.previous_line.is_decorator:
610             if self.mode.is_pyi and current_line.is_stub_class:
611                 # Insert an empty line after a decorated stub class
612                 return 0, 1
613
614             return 0, 0
615
616         if self.previous_line.depth < current_line.depth and (
617             self.previous_line.is_class or self.previous_line.is_def
618         ):
619             return 0, 0
620
621         comment_to_add_newlines: Optional[LinesBlock] = None
622         if (
623             self.previous_line.is_comment
624             and self.previous_line.depth == current_line.depth
625             and before == 0
626         ):
627             slc = self.semantic_leading_comment
628             if (
629                 Preview.empty_lines_before_class_or_def_with_leading_comments
630                 in current_line.mode
631                 and slc is not None
632                 and slc.previous_block is not None
633                 and not slc.previous_block.original_line.is_class
634                 and not slc.previous_block.original_line.opens_block
635                 and slc.before <= 1
636             ):
637                 comment_to_add_newlines = slc
638             else:
639                 return 0, 0
640
641         if self.mode.is_pyi:
642             if current_line.is_class or self.previous_line.is_class:
643                 if self.previous_line.depth < current_line.depth:
644                     newlines = 0
645                 elif self.previous_line.depth > current_line.depth:
646                     newlines = 1
647                 elif current_line.is_stub_class and self.previous_line.is_stub_class:
648                     # No blank line between classes with an empty body
649                     newlines = 0
650                 else:
651                     newlines = 1
652             elif (
653                 current_line.is_def or current_line.is_decorator
654             ) and not self.previous_line.is_def:
655                 if current_line.depth:
656                     # In classes empty lines between attributes and methods should
657                     # be preserved.
658                     newlines = min(1, before)
659                 else:
660                     # Blank line between a block of functions (maybe with preceding
661                     # decorators) and a block of non-functions
662                     newlines = 1
663             elif self.previous_line.depth > current_line.depth:
664                 newlines = 1
665             else:
666                 newlines = 0
667         else:
668             newlines = 1 if current_line.depth else 2
669         if comment_to_add_newlines is not None:
670             previous_block = comment_to_add_newlines.previous_block
671             if previous_block is not None:
672                 comment_to_add_newlines.before = (
673                     max(comment_to_add_newlines.before, newlines) - previous_block.after
674                 )
675                 newlines = 0
676         return newlines, 0
677
678
679 def enumerate_reversed(sequence: Sequence[T]) -> Iterator[Tuple[Index, T]]:
680     """Like `reversed(enumerate(sequence))` if that were possible."""
681     index = len(sequence) - 1
682     for element in reversed(sequence):
683         yield (index, element)
684         index -= 1
685
686
687 def append_leaves(
688     new_line: Line, old_line: Line, leaves: List[Leaf], preformatted: bool = False
689 ) -> None:
690     """
691     Append leaves (taken from @old_line) to @new_line, making sure to fix the
692     underlying Node structure where appropriate.
693
694     All of the leaves in @leaves are duplicated. The duplicates are then
695     appended to @new_line and used to replace their originals in the underlying
696     Node structure. Any comments attached to the old leaves are reattached to
697     the new leaves.
698
699     Pre-conditions:
700         set(@leaves) is a subset of set(@old_line.leaves).
701     """
702     for old_leaf in leaves:
703         new_leaf = Leaf(old_leaf.type, old_leaf.value)
704         replace_child(old_leaf, new_leaf)
705         new_line.append(new_leaf, preformatted=preformatted)
706
707         for comment_leaf in old_line.comments_after(old_leaf):
708             new_line.append(comment_leaf, preformatted=True)
709
710
711 def is_line_short_enough(line: Line, *, line_length: int, line_str: str = "") -> bool:
712     """Return True if `line` is no longer than `line_length`.
713
714     Uses the provided `line_str` rendering, if any, otherwise computes a new one.
715     """
716     if not line_str:
717         line_str = line_to_string(line)
718     return (
719         len(line_str) <= line_length
720         and "\n" not in line_str  # multiline strings
721         and not line.contains_standalone_comments()
722     )
723
724
725 def can_be_split(line: Line) -> bool:
726     """Return False if the line cannot be split *for sure*.
727
728     This is not an exhaustive search but a cheap heuristic that we can use to
729     avoid some unfortunate formattings (mostly around wrapping unsplittable code
730     in unnecessary parentheses).
731     """
732     leaves = line.leaves
733     if len(leaves) < 2:
734         return False
735
736     if leaves[0].type == token.STRING and leaves[1].type == token.DOT:
737         call_count = 0
738         dot_count = 0
739         next = leaves[-1]
740         for leaf in leaves[-2::-1]:
741             if leaf.type in OPENING_BRACKETS:
742                 if next.type not in CLOSING_BRACKETS:
743                     return False
744
745                 call_count += 1
746             elif leaf.type == token.DOT:
747                 dot_count += 1
748             elif leaf.type == token.NAME:
749                 if not (next.type == token.DOT or next.type in OPENING_BRACKETS):
750                     return False
751
752             elif leaf.type not in CLOSING_BRACKETS:
753                 return False
754
755             if dot_count > 1 and call_count > 1:
756                 return False
757
758     return True
759
760
761 def can_omit_invisible_parens(
762     line: Line,
763     line_length: int,
764 ) -> bool:
765     """Does `line` have a shape safe to reformat without optional parens around it?
766
767     Returns True for only a subset of potentially nice looking formattings but
768     the point is to not return false positives that end up producing lines that
769     are too long.
770     """
771     bt = line.bracket_tracker
772     if not bt.delimiters:
773         # Without delimiters the optional parentheses are useless.
774         return True
775
776     max_priority = bt.max_delimiter_priority()
777     if bt.delimiter_count_with_priority(max_priority) > 1:
778         # With more than one delimiter of a kind the optional parentheses read better.
779         return False
780
781     if max_priority == DOT_PRIORITY:
782         # A single stranded method call doesn't require optional parentheses.
783         return True
784
785     assert len(line.leaves) >= 2, "Stranded delimiter"
786
787     # With a single delimiter, omit if the expression starts or ends with
788     # a bracket.
789     first = line.leaves[0]
790     second = line.leaves[1]
791     if first.type in OPENING_BRACKETS and second.type not in CLOSING_BRACKETS:
792         if _can_omit_opening_paren(line, first=first, line_length=line_length):
793             return True
794
795         # Note: we are not returning False here because a line might have *both*
796         # a leading opening bracket and a trailing closing bracket.  If the
797         # opening bracket doesn't match our rule, maybe the closing will.
798
799     penultimate = line.leaves[-2]
800     last = line.leaves[-1]
801
802     if (
803         last.type == token.RPAR
804         or last.type == token.RBRACE
805         or (
806             # don't use indexing for omitting optional parentheses;
807             # it looks weird
808             last.type == token.RSQB
809             and last.parent
810             and last.parent.type != syms.trailer
811         )
812     ):
813         if penultimate.type in OPENING_BRACKETS:
814             # Empty brackets don't help.
815             return False
816
817         if is_multiline_string(first):
818             # Additional wrapping of a multiline string in this situation is
819             # unnecessary.
820             return True
821
822         if _can_omit_closing_paren(line, last=last, line_length=line_length):
823             return True
824
825     return False
826
827
828 def _can_omit_opening_paren(line: Line, *, first: Leaf, line_length: int) -> bool:
829     """See `can_omit_invisible_parens`."""
830     remainder = False
831     length = 4 * line.depth
832     _index = -1
833     for _index, leaf, leaf_length in line.enumerate_with_length():
834         if leaf.type in CLOSING_BRACKETS and leaf.opening_bracket is first:
835             remainder = True
836         if remainder:
837             length += leaf_length
838             if length > line_length:
839                 break
840
841             if leaf.type in OPENING_BRACKETS:
842                 # There are brackets we can further split on.
843                 remainder = False
844
845     else:
846         # checked the entire string and line length wasn't exceeded
847         if len(line.leaves) == _index + 1:
848             return True
849
850     return False
851
852
853 def _can_omit_closing_paren(line: Line, *, last: Leaf, line_length: int) -> bool:
854     """See `can_omit_invisible_parens`."""
855     length = 4 * line.depth
856     seen_other_brackets = False
857     for _index, leaf, leaf_length in line.enumerate_with_length():
858         length += leaf_length
859         if leaf is last.opening_bracket:
860             if seen_other_brackets or length <= line_length:
861                 return True
862
863         elif leaf.type in OPENING_BRACKETS:
864             # There are brackets we can further split on.
865             seen_other_brackets = True
866
867     return False
868
869
870 def line_to_string(line: Line) -> str:
871     """Returns the string representation of @line.
872
873     WARNING: This is known to be computationally expensive.
874     """
875     return str(line).strip("\n")