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

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