]> git.madduck.net Git - etc/vim.git/blob - src/black/nodes.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:

Allow top-level starred expression on match (#2659)
[etc/vim.git] / src / black / nodes.py
1 """
2 blib2to3 Node/Leaf transformation-related utility functions.
3 """
4
5 import sys
6 from typing import (
7     Collection,
8     Generic,
9     Iterator,
10     List,
11     Optional,
12     Set,
13     Tuple,
14     TypeVar,
15     Union,
16 )
17
18 if sys.version_info >= (3, 8):
19     from typing import Final
20 else:
21     from typing_extensions import Final
22
23 from mypy_extensions import mypyc_attr
24
25 # lib2to3 fork
26 from blib2to3.pytree import Node, Leaf, type_repr
27 from blib2to3 import pygram
28 from blib2to3.pgen2 import token
29
30 from black.cache import CACHE_DIR
31 from black.strings import has_triple_quotes
32
33
34 pygram.initialize(CACHE_DIR)
35 syms: Final = pygram.python_symbols
36
37
38 # types
39 T = TypeVar("T")
40 LN = Union[Leaf, Node]
41 LeafID = int
42 NodeType = int
43
44
45 WHITESPACE: Final = {token.DEDENT, token.INDENT, token.NEWLINE}
46 STATEMENT: Final = {
47     syms.if_stmt,
48     syms.while_stmt,
49     syms.for_stmt,
50     syms.try_stmt,
51     syms.except_clause,
52     syms.with_stmt,
53     syms.funcdef,
54     syms.classdef,
55 }
56 STANDALONE_COMMENT: Final = 153
57 token.tok_name[STANDALONE_COMMENT] = "STANDALONE_COMMENT"
58 LOGIC_OPERATORS: Final = {"and", "or"}
59 COMPARATORS: Final = {
60     token.LESS,
61     token.GREATER,
62     token.EQEQUAL,
63     token.NOTEQUAL,
64     token.LESSEQUAL,
65     token.GREATEREQUAL,
66 }
67 MATH_OPERATORS: Final = {
68     token.VBAR,
69     token.CIRCUMFLEX,
70     token.AMPER,
71     token.LEFTSHIFT,
72     token.RIGHTSHIFT,
73     token.PLUS,
74     token.MINUS,
75     token.STAR,
76     token.SLASH,
77     token.DOUBLESLASH,
78     token.PERCENT,
79     token.AT,
80     token.TILDE,
81     token.DOUBLESTAR,
82 }
83 STARS: Final = {token.STAR, token.DOUBLESTAR}
84 VARARGS_SPECIALS: Final = STARS | {token.SLASH}
85 VARARGS_PARENTS: Final = {
86     syms.arglist,
87     syms.argument,  # double star in arglist
88     syms.trailer,  # single argument to call
89     syms.typedargslist,
90     syms.varargslist,  # lambdas
91 }
92 UNPACKING_PARENTS: Final = {
93     syms.atom,  # single element of a list or set literal
94     syms.dictsetmaker,
95     syms.listmaker,
96     syms.testlist_gexp,
97     syms.testlist_star_expr,
98 }
99 TEST_DESCENDANTS: Final = {
100     syms.test,
101     syms.lambdef,
102     syms.or_test,
103     syms.and_test,
104     syms.not_test,
105     syms.comparison,
106     syms.star_expr,
107     syms.expr,
108     syms.xor_expr,
109     syms.and_expr,
110     syms.shift_expr,
111     syms.arith_expr,
112     syms.trailer,
113     syms.term,
114     syms.power,
115 }
116 ASSIGNMENTS: Final = {
117     "=",
118     "+=",
119     "-=",
120     "*=",
121     "@=",
122     "/=",
123     "%=",
124     "&=",
125     "|=",
126     "^=",
127     "<<=",
128     ">>=",
129     "**=",
130     "//=",
131 }
132
133 IMPLICIT_TUPLE: Final = {syms.testlist, syms.testlist_star_expr, syms.exprlist}
134 BRACKET: Final = {
135     token.LPAR: token.RPAR,
136     token.LSQB: token.RSQB,
137     token.LBRACE: token.RBRACE,
138 }
139 OPENING_BRACKETS: Final = set(BRACKET.keys())
140 CLOSING_BRACKETS: Final = set(BRACKET.values())
141 BRACKETS: Final = OPENING_BRACKETS | CLOSING_BRACKETS
142 ALWAYS_NO_SPACE: Final = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT}
143
144 RARROW = 55
145
146
147 @mypyc_attr(allow_interpreted_subclasses=True)
148 class Visitor(Generic[T]):
149     """Basic lib2to3 visitor that yields things of type `T` on `visit()`."""
150
151     def visit(self, node: LN) -> Iterator[T]:
152         """Main method to visit `node` and its children.
153
154         It tries to find a `visit_*()` method for the given `node.type`, like
155         `visit_simple_stmt` for Node objects or `visit_INDENT` for Leaf objects.
156         If no dedicated `visit_*()` method is found, chooses `visit_default()`
157         instead.
158
159         Then yields objects of type `T` from the selected visitor.
160         """
161         if node.type < 256:
162             name = token.tok_name[node.type]
163         else:
164             name = str(type_repr(node.type))
165         # We explicitly branch on whether a visitor exists (instead of
166         # using self.visit_default as the default arg to getattr) in order
167         # to save needing to create a bound method object and so mypyc can
168         # generate a native call to visit_default.
169         visitf = getattr(self, f"visit_{name}", None)
170         if visitf:
171             yield from visitf(node)
172         else:
173             yield from self.visit_default(node)
174
175     def visit_default(self, node: LN) -> Iterator[T]:
176         """Default `visit_*()` implementation. Recurses to children of `node`."""
177         if isinstance(node, Node):
178             for child in node.children:
179                 yield from self.visit(child)
180
181
182 def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str:  # noqa: C901
183     """Return whitespace prefix if needed for the given `leaf`.
184
185     `complex_subscript` signals whether the given leaf is part of a subscription
186     which has non-trivial arguments, like arithmetic expressions or function calls.
187     """
188     NO: Final = ""
189     SPACE: Final = " "
190     DOUBLESPACE: Final = "  "
191     t = leaf.type
192     p = leaf.parent
193     v = leaf.value
194     if t in ALWAYS_NO_SPACE:
195         return NO
196
197     if t == token.COMMENT:
198         return DOUBLESPACE
199
200     assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
201     if t == token.COLON and p.type not in {
202         syms.subscript,
203         syms.subscriptlist,
204         syms.sliceop,
205     }:
206         return NO
207
208     prev = leaf.prev_sibling
209     if not prev:
210         prevp = preceding_leaf(p)
211         if not prevp or prevp.type in OPENING_BRACKETS:
212             return NO
213
214         if t == token.COLON:
215             if prevp.type == token.COLON:
216                 return NO
217
218             elif prevp.type != token.COMMA and not complex_subscript:
219                 return NO
220
221             return SPACE
222
223         if prevp.type == token.EQUAL:
224             if prevp.parent:
225                 if prevp.parent.type in {
226                     syms.arglist,
227                     syms.argument,
228                     syms.parameters,
229                     syms.varargslist,
230                 }:
231                     return NO
232
233                 elif prevp.parent.type == syms.typedargslist:
234                     # A bit hacky: if the equal sign has whitespace, it means we
235                     # previously found it's a typed argument.  So, we're using
236                     # that, too.
237                     return prevp.prefix
238
239         elif prevp.type in VARARGS_SPECIALS:
240             if is_vararg(prevp, within=VARARGS_PARENTS | UNPACKING_PARENTS):
241                 return NO
242
243         elif prevp.type == token.COLON:
244             if prevp.parent and prevp.parent.type in {syms.subscript, syms.sliceop}:
245                 return SPACE if complex_subscript else NO
246
247         elif (
248             prevp.parent
249             and prevp.parent.type == syms.factor
250             and prevp.type in MATH_OPERATORS
251         ):
252             return NO
253
254         elif (
255             prevp.type == token.RIGHTSHIFT
256             and prevp.parent
257             and prevp.parent.type == syms.shift_expr
258             and prevp.prev_sibling
259             and prevp.prev_sibling.type == token.NAME
260             and prevp.prev_sibling.value == "print"  # type: ignore
261         ):
262             # Python 2 print chevron
263             return NO
264         elif prevp.type == token.AT and p.parent and p.parent.type == syms.decorator:
265             # no space in decorators
266             return NO
267
268     elif prev.type in OPENING_BRACKETS:
269         return NO
270
271     if p.type in {syms.parameters, syms.arglist}:
272         # untyped function signatures or calls
273         if not prev or prev.type != token.COMMA:
274             return NO
275
276     elif p.type == syms.varargslist:
277         # lambdas
278         if prev and prev.type != token.COMMA:
279             return NO
280
281     elif p.type == syms.typedargslist:
282         # typed function signatures
283         if not prev:
284             return NO
285
286         if t == token.EQUAL:
287             if prev.type != syms.tname:
288                 return NO
289
290         elif prev.type == token.EQUAL:
291             # A bit hacky: if the equal sign has whitespace, it means we
292             # previously found it's a typed argument.  So, we're using that, too.
293             return prev.prefix
294
295         elif prev.type != token.COMMA:
296             return NO
297
298     elif p.type == syms.tname:
299         # type names
300         if not prev:
301             prevp = preceding_leaf(p)
302             if not prevp or prevp.type != token.COMMA:
303                 return NO
304
305     elif p.type == syms.trailer:
306         # attributes and calls
307         if t == token.LPAR or t == token.RPAR:
308             return NO
309
310         if not prev:
311             if t == token.DOT:
312                 prevp = preceding_leaf(p)
313                 if not prevp or prevp.type != token.NUMBER:
314                     return NO
315
316             elif t == token.LSQB:
317                 return NO
318
319         elif prev.type != token.COMMA:
320             return NO
321
322     elif p.type == syms.argument:
323         # single argument
324         if t == token.EQUAL:
325             return NO
326
327         if not prev:
328             prevp = preceding_leaf(p)
329             if not prevp or prevp.type == token.LPAR:
330                 return NO
331
332         elif prev.type in {token.EQUAL} | VARARGS_SPECIALS:
333             return NO
334
335     elif p.type == syms.decorator:
336         # decorators
337         return NO
338
339     elif p.type == syms.dotted_name:
340         if prev:
341             return NO
342
343         prevp = preceding_leaf(p)
344         if not prevp or prevp.type == token.AT or prevp.type == token.DOT:
345             return NO
346
347     elif p.type == syms.classdef:
348         if t == token.LPAR:
349             return NO
350
351         if prev and prev.type == token.LPAR:
352             return NO
353
354     elif p.type in {syms.subscript, syms.sliceop}:
355         # indexing
356         if not prev:
357             assert p.parent is not None, "subscripts are always parented"
358             if p.parent.type == syms.subscriptlist:
359                 return SPACE
360
361             return NO
362
363         elif not complex_subscript:
364             return NO
365
366     elif p.type == syms.atom:
367         if prev and t == token.DOT:
368             # dots, but not the first one.
369             return NO
370
371     elif p.type == syms.dictsetmaker:
372         # dict unpacking
373         if prev and prev.type == token.DOUBLESTAR:
374             return NO
375
376     elif p.type in {syms.factor, syms.star_expr}:
377         # unary ops
378         if not prev:
379             prevp = preceding_leaf(p)
380             if not prevp or prevp.type in OPENING_BRACKETS:
381                 return NO
382
383             prevp_parent = prevp.parent
384             assert prevp_parent is not None
385             if prevp.type == token.COLON and prevp_parent.type in {
386                 syms.subscript,
387                 syms.sliceop,
388             }:
389                 return NO
390
391             elif prevp.type == token.EQUAL and prevp_parent.type == syms.argument:
392                 return NO
393
394         elif t in {token.NAME, token.NUMBER, token.STRING}:
395             return NO
396
397     elif p.type == syms.import_from:
398         if t == token.DOT:
399             if prev and prev.type == token.DOT:
400                 return NO
401
402         elif t == token.NAME:
403             if v == "import":
404                 return SPACE
405
406             if prev and prev.type == token.DOT:
407                 return NO
408
409     elif p.type == syms.sliceop:
410         return NO
411
412     return SPACE
413
414
415 def preceding_leaf(node: Optional[LN]) -> Optional[Leaf]:
416     """Return the first leaf that precedes `node`, if any."""
417     while node:
418         res = node.prev_sibling
419         if res:
420             if isinstance(res, Leaf):
421                 return res
422
423             try:
424                 return list(res.leaves())[-1]
425
426             except IndexError:
427                 return None
428
429         node = node.parent
430     return None
431
432
433 def prev_siblings_are(node: Optional[LN], tokens: List[Optional[NodeType]]) -> bool:
434     """Return if the `node` and its previous siblings match types against the provided
435     list of tokens; the provided `node`has its type matched against the last element in
436     the list.  `None` can be used as the first element to declare that the start of the
437     list is anchored at the start of its parent's children."""
438     if not tokens:
439         return True
440     if tokens[-1] is None:
441         return node is None
442     if not node:
443         return False
444     if node.type != tokens[-1]:
445         return False
446     return prev_siblings_are(node.prev_sibling, tokens[:-1])
447
448
449 def last_two_except(leaves: List[Leaf], omit: Collection[LeafID]) -> Tuple[Leaf, Leaf]:
450     """Return (penultimate, last) leaves skipping brackets in `omit` and contents."""
451     stop_after: Optional[Leaf] = None
452     last: Optional[Leaf] = None
453     for leaf in reversed(leaves):
454         if stop_after:
455             if leaf is stop_after:
456                 stop_after = None
457             continue
458
459         if last:
460             return leaf, last
461
462         if id(leaf) in omit:
463             stop_after = leaf.opening_bracket
464         else:
465             last = leaf
466     else:
467         raise LookupError("Last two leaves were also skipped")
468
469
470 def parent_type(node: Optional[LN]) -> Optional[NodeType]:
471     """
472     Returns:
473         @node.parent.type, if @node is not None and has a parent.
474             OR
475         None, otherwise.
476     """
477     if node is None or node.parent is None:
478         return None
479
480     return node.parent.type
481
482
483 def child_towards(ancestor: Node, descendant: LN) -> Optional[LN]:
484     """Return the child of `ancestor` that contains `descendant`."""
485     node: Optional[LN] = descendant
486     while node and node.parent != ancestor:
487         node = node.parent
488     return node
489
490
491 def replace_child(old_child: LN, new_child: LN) -> None:
492     """
493     Side Effects:
494         * If @old_child.parent is set, replace @old_child with @new_child in
495         @old_child's underlying Node structure.
496             OR
497         * Otherwise, this function does nothing.
498     """
499     parent = old_child.parent
500     if not parent:
501         return
502
503     child_idx = old_child.remove()
504     if child_idx is not None:
505         parent.insert_child(child_idx, new_child)
506
507
508 def container_of(leaf: Leaf) -> LN:
509     """Return `leaf` or one of its ancestors that is the topmost container of it.
510
511     By "container" we mean a node where `leaf` is the very first child.
512     """
513     same_prefix = leaf.prefix
514     container: LN = leaf
515     while container:
516         parent = container.parent
517         if parent is None:
518             break
519
520         if parent.children[0].prefix != same_prefix:
521             break
522
523         if parent.type == syms.file_input:
524             break
525
526         if parent.prev_sibling is not None and parent.prev_sibling.type in BRACKETS:
527             break
528
529         container = parent
530     return container
531
532
533 def first_leaf_column(node: Node) -> Optional[int]:
534     """Returns the column of the first leaf child of a node."""
535     for child in node.children:
536         if isinstance(child, Leaf):
537             return child.column
538     return None
539
540
541 def first_child_is_arith(node: Node) -> bool:
542     """Whether first child is an arithmetic or a binary arithmetic expression"""
543     expr_types = {
544         syms.arith_expr,
545         syms.shift_expr,
546         syms.xor_expr,
547         syms.and_expr,
548     }
549     return bool(node.children and node.children[0].type in expr_types)
550
551
552 def is_docstring(leaf: Leaf) -> bool:
553     if prev_siblings_are(
554         leaf.parent, [None, token.NEWLINE, token.INDENT, syms.simple_stmt]
555     ):
556         return True
557
558     # Multiline docstring on the same line as the `def`.
559     if prev_siblings_are(leaf.parent, [syms.parameters, token.COLON, syms.simple_stmt]):
560         # `syms.parameters` is only used in funcdefs and async_funcdefs in the Python
561         # grammar. We're safe to return True without further checks.
562         return True
563
564     return False
565
566
567 def is_empty_tuple(node: LN) -> bool:
568     """Return True if `node` holds an empty tuple."""
569     return (
570         node.type == syms.atom
571         and len(node.children) == 2
572         and node.children[0].type == token.LPAR
573         and node.children[1].type == token.RPAR
574     )
575
576
577 def is_one_tuple(node: LN) -> bool:
578     """Return True if `node` holds a tuple with one element, with or without parens."""
579     if node.type == syms.atom:
580         gexp = unwrap_singleton_parenthesis(node)
581         if gexp is None or gexp.type != syms.testlist_gexp:
582             return False
583
584         return len(gexp.children) == 2 and gexp.children[1].type == token.COMMA
585
586     return (
587         node.type in IMPLICIT_TUPLE
588         and len(node.children) == 2
589         and node.children[1].type == token.COMMA
590     )
591
592
593 def is_one_tuple_between(opening: Leaf, closing: Leaf, leaves: List[Leaf]) -> bool:
594     """Return True if content between `opening` and `closing` looks like a one-tuple."""
595     if opening.type != token.LPAR and closing.type != token.RPAR:
596         return False
597
598     depth = closing.bracket_depth + 1
599     for _opening_index, leaf in enumerate(leaves):
600         if leaf is opening:
601             break
602
603     else:
604         raise LookupError("Opening paren not found in `leaves`")
605
606     commas = 0
607     _opening_index += 1
608     for leaf in leaves[_opening_index:]:
609         if leaf is closing:
610             break
611
612         bracket_depth = leaf.bracket_depth
613         if bracket_depth == depth and leaf.type == token.COMMA:
614             commas += 1
615             if leaf.parent and leaf.parent.type in {
616                 syms.arglist,
617                 syms.typedargslist,
618             }:
619                 commas += 1
620                 break
621
622     return commas < 2
623
624
625 def is_walrus_assignment(node: LN) -> bool:
626     """Return True iff `node` is of the shape ( test := test )"""
627     inner = unwrap_singleton_parenthesis(node)
628     return inner is not None and inner.type == syms.namedexpr_test
629
630
631 def is_simple_decorator_trailer(node: LN, last: bool = False) -> bool:
632     """Return True iff `node` is a trailer valid in a simple decorator"""
633     return node.type == syms.trailer and (
634         (
635             len(node.children) == 2
636             and node.children[0].type == token.DOT
637             and node.children[1].type == token.NAME
638         )
639         # last trailer can be an argument-less parentheses pair
640         or (
641             last
642             and len(node.children) == 2
643             and node.children[0].type == token.LPAR
644             and node.children[1].type == token.RPAR
645         )
646         # last trailer can be arguments
647         or (
648             last
649             and len(node.children) == 3
650             and node.children[0].type == token.LPAR
651             # and node.children[1].type == syms.argument
652             and node.children[2].type == token.RPAR
653         )
654     )
655
656
657 def is_simple_decorator_expression(node: LN) -> bool:
658     """Return True iff `node` could be a 'dotted name' decorator
659
660     This function takes the node of the 'namedexpr_test' of the new decorator
661     grammar and test if it would be valid under the old decorator grammar.
662
663     The old grammar was: decorator: @ dotted_name [arguments] NEWLINE
664     The new grammar is : decorator: @ namedexpr_test NEWLINE
665     """
666     if node.type == token.NAME:
667         return True
668     if node.type == syms.power:
669         if node.children:
670             return (
671                 node.children[0].type == token.NAME
672                 and all(map(is_simple_decorator_trailer, node.children[1:-1]))
673                 and (
674                     len(node.children) < 2
675                     or is_simple_decorator_trailer(node.children[-1], last=True)
676                 )
677             )
678     return False
679
680
681 def is_yield(node: LN) -> bool:
682     """Return True if `node` holds a `yield` or `yield from` expression."""
683     if node.type == syms.yield_expr:
684         return True
685
686     if node.type == token.NAME and node.value == "yield":  # type: ignore
687         return True
688
689     if node.type != syms.atom:
690         return False
691
692     if len(node.children) != 3:
693         return False
694
695     lpar, expr, rpar = node.children
696     if lpar.type == token.LPAR and rpar.type == token.RPAR:
697         return is_yield(expr)
698
699     return False
700
701
702 def is_vararg(leaf: Leaf, within: Set[NodeType]) -> bool:
703     """Return True if `leaf` is a star or double star in a vararg or kwarg.
704
705     If `within` includes VARARGS_PARENTS, this applies to function signatures.
706     If `within` includes UNPACKING_PARENTS, it applies to right hand-side
707     extended iterable unpacking (PEP 3132) and additional unpacking
708     generalizations (PEP 448).
709     """
710     if leaf.type not in VARARGS_SPECIALS or not leaf.parent:
711         return False
712
713     p = leaf.parent
714     if p.type == syms.star_expr:
715         # Star expressions are also used as assignment targets in extended
716         # iterable unpacking (PEP 3132).  See what its parent is instead.
717         if not p.parent:
718             return False
719
720         p = p.parent
721
722     return p.type in within
723
724
725 def is_multiline_string(leaf: Leaf) -> bool:
726     """Return True if `leaf` is a multiline string that actually spans many lines."""
727     return has_triple_quotes(leaf.value) and "\n" in leaf.value
728
729
730 def is_stub_suite(node: Node) -> bool:
731     """Return True if `node` is a suite with a stub body."""
732     if (
733         len(node.children) != 4
734         or node.children[0].type != token.NEWLINE
735         or node.children[1].type != token.INDENT
736         or node.children[3].type != token.DEDENT
737     ):
738         return False
739
740     return is_stub_body(node.children[2])
741
742
743 def is_stub_body(node: LN) -> bool:
744     """Return True if `node` is a simple statement containing an ellipsis."""
745     if not isinstance(node, Node) or node.type != syms.simple_stmt:
746         return False
747
748     if len(node.children) != 2:
749         return False
750
751     child = node.children[0]
752     return (
753         child.type == syms.atom
754         and len(child.children) == 3
755         and all(leaf == Leaf(token.DOT, ".") for leaf in child.children)
756     )
757
758
759 def is_atom_with_invisible_parens(node: LN) -> bool:
760     """Given a `LN`, determines whether it's an atom `node` with invisible
761     parens. Useful in dedupe-ing and normalizing parens.
762     """
763     if isinstance(node, Leaf) or node.type != syms.atom:
764         return False
765
766     first, last = node.children[0], node.children[-1]
767     return (
768         isinstance(first, Leaf)
769         and first.type == token.LPAR
770         and first.value == ""
771         and isinstance(last, Leaf)
772         and last.type == token.RPAR
773         and last.value == ""
774     )
775
776
777 def is_empty_par(leaf: Leaf) -> bool:
778     return is_empty_lpar(leaf) or is_empty_rpar(leaf)
779
780
781 def is_empty_lpar(leaf: Leaf) -> bool:
782     return leaf.type == token.LPAR and leaf.value == ""
783
784
785 def is_empty_rpar(leaf: Leaf) -> bool:
786     return leaf.type == token.RPAR and leaf.value == ""
787
788
789 def is_import(leaf: Leaf) -> bool:
790     """Return True if the given leaf starts an import statement."""
791     p = leaf.parent
792     t = leaf.type
793     v = leaf.value
794     return bool(
795         t == token.NAME
796         and (
797             (v == "import" and p and p.type == syms.import_name)
798             or (v == "from" and p and p.type == syms.import_from)
799         )
800     )
801
802
803 def is_type_comment(leaf: Leaf, suffix: str = "") -> bool:
804     """Return True if the given leaf is a special comment.
805     Only returns true for type comments for now."""
806     t = leaf.type
807     v = leaf.value
808     return t in {token.COMMENT, STANDALONE_COMMENT} and v.startswith("# type:" + suffix)
809
810
811 def wrap_in_parentheses(parent: Node, child: LN, *, visible: bool = True) -> None:
812     """Wrap `child` in parentheses.
813
814     This replaces `child` with an atom holding the parentheses and the old
815     child.  That requires moving the prefix.
816
817     If `visible` is False, the leaves will be valueless (and thus invisible).
818     """
819     lpar = Leaf(token.LPAR, "(" if visible else "")
820     rpar = Leaf(token.RPAR, ")" if visible else "")
821     prefix = child.prefix
822     child.prefix = ""
823     index = child.remove() or 0
824     new_child = Node(syms.atom, [lpar, child, rpar])
825     new_child.prefix = prefix
826     parent.insert_child(index, new_child)
827
828
829 def unwrap_singleton_parenthesis(node: LN) -> Optional[LN]:
830     """Returns `wrapped` if `node` is of the shape ( wrapped ).
831
832     Parenthesis can be optional. Returns None otherwise"""
833     if len(node.children) != 3:
834         return None
835
836     lpar, wrapped, rpar = node.children
837     if not (lpar.type == token.LPAR and rpar.type == token.RPAR):
838         return None
839
840     return wrapped
841
842
843 def ensure_visible(leaf: Leaf) -> None:
844     """Make sure parentheses are visible.
845
846     They could be invisible as part of some statements (see
847     :func:`normalize_invisible_parens` and :func:`visit_import_from`).
848     """
849     if leaf.type == token.LPAR:
850         leaf.value = "("
851     elif leaf.type == token.RPAR:
852         leaf.value = ")"