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

Bump docker/build-push-action from 2 to 3 (#3057)
[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     elif p.type == syms.except_clause:
405         if t == token.STAR:
406             return NO
407
408     return SPACE
409
410
411 def preceding_leaf(node: Optional[LN]) -> Optional[Leaf]:
412     """Return the first leaf that precedes `node`, if any."""
413     while node:
414         res = node.prev_sibling
415         if res:
416             if isinstance(res, Leaf):
417                 return res
418
419             try:
420                 return list(res.leaves())[-1]
421
422             except IndexError:
423                 return None
424
425         node = node.parent
426     return None
427
428
429 def prev_siblings_are(node: Optional[LN], tokens: List[Optional[NodeType]]) -> bool:
430     """Return if the `node` and its previous siblings match types against the provided
431     list of tokens; the provided `node`has its type matched against the last element in
432     the list.  `None` can be used as the first element to declare that the start of the
433     list is anchored at the start of its parent's children."""
434     if not tokens:
435         return True
436     if tokens[-1] is None:
437         return node is None
438     if not node:
439         return False
440     if node.type != tokens[-1]:
441         return False
442     return prev_siblings_are(node.prev_sibling, tokens[:-1])
443
444
445 def parent_type(node: Optional[LN]) -> Optional[NodeType]:
446     """
447     Returns:
448         @node.parent.type, if @node is not None and has a parent.
449             OR
450         None, otherwise.
451     """
452     if node is None or node.parent is None:
453         return None
454
455     return node.parent.type
456
457
458 def child_towards(ancestor: Node, descendant: LN) -> Optional[LN]:
459     """Return the child of `ancestor` that contains `descendant`."""
460     node: Optional[LN] = descendant
461     while node and node.parent != ancestor:
462         node = node.parent
463     return node
464
465
466 def replace_child(old_child: LN, new_child: LN) -> None:
467     """
468     Side Effects:
469         * If @old_child.parent is set, replace @old_child with @new_child in
470         @old_child's underlying Node structure.
471             OR
472         * Otherwise, this function does nothing.
473     """
474     parent = old_child.parent
475     if not parent:
476         return
477
478     child_idx = old_child.remove()
479     if child_idx is not None:
480         parent.insert_child(child_idx, new_child)
481
482
483 def container_of(leaf: Leaf) -> LN:
484     """Return `leaf` or one of its ancestors that is the topmost container of it.
485
486     By "container" we mean a node where `leaf` is the very first child.
487     """
488     same_prefix = leaf.prefix
489     container: LN = leaf
490     while container:
491         parent = container.parent
492         if parent is None:
493             break
494
495         if parent.children[0].prefix != same_prefix:
496             break
497
498         if parent.type == syms.file_input:
499             break
500
501         if parent.prev_sibling is not None and parent.prev_sibling.type in BRACKETS:
502             break
503
504         container = parent
505     return container
506
507
508 def first_leaf_column(node: Node) -> Optional[int]:
509     """Returns the column of the first leaf child of a node."""
510     for child in node.children:
511         if isinstance(child, Leaf):
512             return child.column
513     return None
514
515
516 def is_arith_like(node: LN) -> bool:
517     """Whether node is an arithmetic or a binary arithmetic expression"""
518     return node.type in {
519         syms.arith_expr,
520         syms.shift_expr,
521         syms.xor_expr,
522         syms.and_expr,
523     }
524
525
526 def is_docstring(leaf: Leaf) -> bool:
527     if prev_siblings_are(
528         leaf.parent, [None, token.NEWLINE, token.INDENT, syms.simple_stmt]
529     ):
530         return True
531
532     # Multiline docstring on the same line as the `def`.
533     if prev_siblings_are(leaf.parent, [syms.parameters, token.COLON, syms.simple_stmt]):
534         # `syms.parameters` is only used in funcdefs and async_funcdefs in the Python
535         # grammar. We're safe to return True without further checks.
536         return True
537
538     return False
539
540
541 def is_empty_tuple(node: LN) -> bool:
542     """Return True if `node` holds an empty tuple."""
543     return (
544         node.type == syms.atom
545         and len(node.children) == 2
546         and node.children[0].type == token.LPAR
547         and node.children[1].type == token.RPAR
548     )
549
550
551 def is_one_tuple(node: LN) -> bool:
552     """Return True if `node` holds a tuple with one element, with or without parens."""
553     if node.type == syms.atom:
554         gexp = unwrap_singleton_parenthesis(node)
555         if gexp is None or gexp.type != syms.testlist_gexp:
556             return False
557
558         return len(gexp.children) == 2 and gexp.children[1].type == token.COMMA
559
560     return (
561         node.type in IMPLICIT_TUPLE
562         and len(node.children) == 2
563         and node.children[1].type == token.COMMA
564     )
565
566
567 def is_one_sequence_between(
568     opening: Leaf,
569     closing: Leaf,
570     leaves: List[Leaf],
571     brackets: Tuple[int, int] = (token.LPAR, token.RPAR),
572 ) -> bool:
573     """Return True if content between `opening` and `closing` is a one-sequence."""
574     if (opening.type, closing.type) != brackets:
575         return False
576
577     depth = closing.bracket_depth + 1
578     for _opening_index, leaf in enumerate(leaves):
579         if leaf is opening:
580             break
581
582     else:
583         raise LookupError("Opening paren not found in `leaves`")
584
585     commas = 0
586     _opening_index += 1
587     for leaf in leaves[_opening_index:]:
588         if leaf is closing:
589             break
590
591         bracket_depth = leaf.bracket_depth
592         if bracket_depth == depth and leaf.type == token.COMMA:
593             commas += 1
594             if leaf.parent and leaf.parent.type in {
595                 syms.arglist,
596                 syms.typedargslist,
597             }:
598                 commas += 1
599                 break
600
601     return commas < 2
602
603
604 def is_walrus_assignment(node: LN) -> bool:
605     """Return True iff `node` is of the shape ( test := test )"""
606     inner = unwrap_singleton_parenthesis(node)
607     return inner is not None and inner.type == syms.namedexpr_test
608
609
610 def is_simple_decorator_trailer(node: LN, last: bool = False) -> bool:
611     """Return True iff `node` is a trailer valid in a simple decorator"""
612     return node.type == syms.trailer and (
613         (
614             len(node.children) == 2
615             and node.children[0].type == token.DOT
616             and node.children[1].type == token.NAME
617         )
618         # last trailer can be an argument-less parentheses pair
619         or (
620             last
621             and len(node.children) == 2
622             and node.children[0].type == token.LPAR
623             and node.children[1].type == token.RPAR
624         )
625         # last trailer can be arguments
626         or (
627             last
628             and len(node.children) == 3
629             and node.children[0].type == token.LPAR
630             # and node.children[1].type == syms.argument
631             and node.children[2].type == token.RPAR
632         )
633     )
634
635
636 def is_simple_decorator_expression(node: LN) -> bool:
637     """Return True iff `node` could be a 'dotted name' decorator
638
639     This function takes the node of the 'namedexpr_test' of the new decorator
640     grammar and test if it would be valid under the old decorator grammar.
641
642     The old grammar was: decorator: @ dotted_name [arguments] NEWLINE
643     The new grammar is : decorator: @ namedexpr_test NEWLINE
644     """
645     if node.type == token.NAME:
646         return True
647     if node.type == syms.power:
648         if node.children:
649             return (
650                 node.children[0].type == token.NAME
651                 and all(map(is_simple_decorator_trailer, node.children[1:-1]))
652                 and (
653                     len(node.children) < 2
654                     or is_simple_decorator_trailer(node.children[-1], last=True)
655                 )
656             )
657     return False
658
659
660 def is_yield(node: LN) -> bool:
661     """Return True if `node` holds a `yield` or `yield from` expression."""
662     if node.type == syms.yield_expr:
663         return True
664
665     if is_name_token(node) and node.value == "yield":
666         return True
667
668     if node.type != syms.atom:
669         return False
670
671     if len(node.children) != 3:
672         return False
673
674     lpar, expr, rpar = node.children
675     if lpar.type == token.LPAR and rpar.type == token.RPAR:
676         return is_yield(expr)
677
678     return False
679
680
681 def is_vararg(leaf: Leaf, within: Set[NodeType]) -> bool:
682     """Return True if `leaf` is a star or double star in a vararg or kwarg.
683
684     If `within` includes VARARGS_PARENTS, this applies to function signatures.
685     If `within` includes UNPACKING_PARENTS, it applies to right hand-side
686     extended iterable unpacking (PEP 3132) and additional unpacking
687     generalizations (PEP 448).
688     """
689     if leaf.type not in VARARGS_SPECIALS or not leaf.parent:
690         return False
691
692     p = leaf.parent
693     if p.type == syms.star_expr:
694         # Star expressions are also used as assignment targets in extended
695         # iterable unpacking (PEP 3132).  See what its parent is instead.
696         if not p.parent:
697             return False
698
699         p = p.parent
700
701     return p.type in within
702
703
704 def is_multiline_string(leaf: Leaf) -> bool:
705     """Return True if `leaf` is a multiline string that actually spans many lines."""
706     return has_triple_quotes(leaf.value) and "\n" in leaf.value
707
708
709 def is_stub_suite(node: Node) -> bool:
710     """Return True if `node` is a suite with a stub body."""
711     if (
712         len(node.children) != 4
713         or node.children[0].type != token.NEWLINE
714         or node.children[1].type != token.INDENT
715         or node.children[3].type != token.DEDENT
716     ):
717         return False
718
719     return is_stub_body(node.children[2])
720
721
722 def is_stub_body(node: LN) -> bool:
723     """Return True if `node` is a simple statement containing an ellipsis."""
724     if not isinstance(node, Node) or node.type != syms.simple_stmt:
725         return False
726
727     if len(node.children) != 2:
728         return False
729
730     child = node.children[0]
731     return (
732         child.type == syms.atom
733         and len(child.children) == 3
734         and all(leaf == Leaf(token.DOT, ".") for leaf in child.children)
735     )
736
737
738 def is_atom_with_invisible_parens(node: LN) -> bool:
739     """Given a `LN`, determines whether it's an atom `node` with invisible
740     parens. Useful in dedupe-ing and normalizing parens.
741     """
742     if isinstance(node, Leaf) or node.type != syms.atom:
743         return False
744
745     first, last = node.children[0], node.children[-1]
746     return (
747         isinstance(first, Leaf)
748         and first.type == token.LPAR
749         and first.value == ""
750         and isinstance(last, Leaf)
751         and last.type == token.RPAR
752         and last.value == ""
753     )
754
755
756 def is_empty_par(leaf: Leaf) -> bool:
757     return is_empty_lpar(leaf) or is_empty_rpar(leaf)
758
759
760 def is_empty_lpar(leaf: Leaf) -> bool:
761     return leaf.type == token.LPAR and leaf.value == ""
762
763
764 def is_empty_rpar(leaf: Leaf) -> bool:
765     return leaf.type == token.RPAR and leaf.value == ""
766
767
768 def is_import(leaf: Leaf) -> bool:
769     """Return True if the given leaf starts an import statement."""
770     p = leaf.parent
771     t = leaf.type
772     v = leaf.value
773     return bool(
774         t == token.NAME
775         and (
776             (v == "import" and p and p.type == syms.import_name)
777             or (v == "from" and p and p.type == syms.import_from)
778         )
779     )
780
781
782 def is_type_comment(leaf: Leaf, suffix: str = "") -> bool:
783     """Return True if the given leaf is a special comment.
784     Only returns true for type comments for now."""
785     t = leaf.type
786     v = leaf.value
787     return t in {token.COMMENT, STANDALONE_COMMENT} and v.startswith("# type:" + suffix)
788
789
790 def wrap_in_parentheses(parent: Node, child: LN, *, visible: bool = True) -> None:
791     """Wrap `child` in parentheses.
792
793     This replaces `child` with an atom holding the parentheses and the old
794     child.  That requires moving the prefix.
795
796     If `visible` is False, the leaves will be valueless (and thus invisible).
797     """
798     lpar = Leaf(token.LPAR, "(" if visible else "")
799     rpar = Leaf(token.RPAR, ")" if visible else "")
800     prefix = child.prefix
801     child.prefix = ""
802     index = child.remove() or 0
803     new_child = Node(syms.atom, [lpar, child, rpar])
804     new_child.prefix = prefix
805     parent.insert_child(index, new_child)
806
807
808 def unwrap_singleton_parenthesis(node: LN) -> Optional[LN]:
809     """Returns `wrapped` if `node` is of the shape ( wrapped ).
810
811     Parenthesis can be optional. Returns None otherwise"""
812     if len(node.children) != 3:
813         return None
814
815     lpar, wrapped, rpar = node.children
816     if not (lpar.type == token.LPAR and rpar.type == token.RPAR):
817         return None
818
819     return wrapped
820
821
822 def ensure_visible(leaf: Leaf) -> None:
823     """Make sure parentheses are visible.
824
825     They could be invisible as part of some statements (see
826     :func:`normalize_invisible_parens` and :func:`visit_import_from`).
827     """
828     if leaf.type == token.LPAR:
829         leaf.value = "("
830     elif leaf.type == token.RPAR:
831         leaf.value = ")"
832
833
834 def is_name_token(nl: NL) -> TypeGuard[Leaf]:
835     return nl.type == token.NAME
836
837
838 def is_lpar_token(nl: NL) -> TypeGuard[Leaf]:
839     return nl.type == token.LPAR
840
841
842 def is_rpar_token(nl: NL) -> TypeGuard[Leaf]:
843     return nl.type == token.RPAR
844
845
846 def is_string_token(nl: NL) -> TypeGuard[Leaf]:
847     return nl.type == token.STRING