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

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