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

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