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

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