]> 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:

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