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

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