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