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

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