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

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