1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
4
5"""This module renders Astroid nodes as string"""
6
7from __future__ import annotations
8
9import warnings
10from collections.abc import Iterator
11from typing import TYPE_CHECKING
12
13from astroid import nodes
14
15if TYPE_CHECKING:
16 from astroid import objects
17 from astroid.nodes import Const
18 from astroid.nodes.node_classes import (
19 Match,
20 MatchAs,
21 MatchCase,
22 MatchClass,
23 MatchMapping,
24 MatchOr,
25 MatchSequence,
26 MatchSingleton,
27 MatchStar,
28 MatchValue,
29 Unknown,
30 )
31 from astroid.nodes.node_ng import NodeNG
32
33# pylint: disable=unused-argument
34
35DOC_NEWLINE = "\0"
36
37
38# Visitor pattern require argument all the time and is not better with staticmethod
39# noinspection PyUnusedLocal,PyMethodMayBeStatic
40class AsStringVisitor:
41 """Visitor to render an Astroid node as a valid python code string"""
42
43 def __init__(self, indent: str = " "):
44 self.indent: str = indent
45
46 def __call__(self, node: NodeNG) -> str:
47 """Makes this visitor behave as a simple function"""
48 return node.accept(self).replace(DOC_NEWLINE, "\n")
49
50 def _docs_dedent(self, doc_node: Const | None) -> str:
51 """Stop newlines in docs being indented by self._stmt_list"""
52 if not doc_node:
53 return ""
54
55 return '\n{}"""{}"""'.format(
56 self.indent, doc_node.value.replace("\n", DOC_NEWLINE)
57 )
58
59 def _stmt_list(self, stmts: list, indent: bool = True) -> str:
60 """return a list of nodes to string"""
61 stmts_str: str = "\n".join(
62 nstr for nstr in [n.accept(self) for n in stmts] if nstr
63 )
64 if not indent:
65 return stmts_str
66
67 return self.indent + stmts_str.replace("\n", "\n" + self.indent)
68
69 def _precedence_parens(
70 self, node: NodeNG, child: NodeNG, is_left: bool = True
71 ) -> str:
72 """Wrap child in parens only if required to keep same semantics"""
73 if self._should_wrap(node, child, is_left):
74 return f"({child.accept(self)})"
75
76 return child.accept(self)
77
78 def _should_wrap(self, node: NodeNG, child: NodeNG, is_left: bool) -> bool:
79 """Wrap child if:
80 - it has lower precedence
81 - same precedence with position opposite to associativity direction
82 """
83 node_precedence = node.op_precedence()
84 child_precedence = child.op_precedence()
85
86 if node_precedence > child_precedence:
87 # 3 * (4 + 5)
88 return True
89
90 if (
91 node_precedence == child_precedence
92 and is_left != node.op_left_associative()
93 ):
94 # 3 - (4 - 5)
95 # (2**3)**4
96 return True
97
98 return False
99
100 # visit_<node> methods ###########################################
101
102 def visit_await(self, node: nodes.Await) -> str:
103 return f"await {node.value.accept(self)}"
104
105 def visit_asyncwith(self, node: nodes.AsyncWith) -> str:
106 return f"async {self.visit_with(node)}"
107
108 def visit_asyncfor(self, node: nodes.AsyncFor) -> str:
109 return f"async {self.visit_for(node)}"
110
111 def visit_arguments(self, node: nodes.Arguments) -> str:
112 """return an astroid.Arguments node as string"""
113 return node.format_args()
114
115 def visit_assignattr(self, node: nodes.AssignAttr) -> str:
116 """return an astroid.AssignAttr node as string"""
117 return self.visit_attribute(node)
118
119 def visit_assert(self, node: nodes.Assert) -> str:
120 """return an astroid.Assert node as string"""
121 if node.fail:
122 return f"assert {node.test.accept(self)}, {node.fail.accept(self)}"
123 return f"assert {node.test.accept(self)}"
124
125 def visit_assignname(self, node: nodes.AssignName) -> str:
126 """return an astroid.AssignName node as string"""
127 return node.name
128
129 def visit_assign(self, node: nodes.Assign) -> str:
130 """return an astroid.Assign node as string"""
131 lhs = " = ".join(n.accept(self) for n in node.targets)
132 return f"{lhs} = {node.value.accept(self)}"
133
134 def visit_augassign(self, node: nodes.AugAssign) -> str:
135 """return an astroid.AugAssign node as string"""
136 return f"{node.target.accept(self)} {node.op} {node.value.accept(self)}"
137
138 def visit_annassign(self, node: nodes.AnnAssign) -> str:
139 """Return an astroid.AnnAssign node as string"""
140
141 target = node.target.accept(self)
142 annotation = node.annotation.accept(self)
143 if node.value is None:
144 return f"{target}: {annotation}"
145 return f"{target}: {annotation} = {node.value.accept(self)}"
146
147 def visit_binop(self, node: nodes.BinOp) -> str:
148 """return an astroid.BinOp node as string"""
149 left = self._precedence_parens(node, node.left)
150 right = self._precedence_parens(node, node.right, is_left=False)
151 if node.op == "**":
152 return f"{left}{node.op}{right}"
153
154 return f"{left} {node.op} {right}"
155
156 def visit_boolop(self, node: nodes.BoolOp) -> str:
157 """return an astroid.BoolOp node as string"""
158 values = [f"{self._precedence_parens(node, n)}" for n in node.values]
159 return (f" {node.op} ").join(values)
160
161 def visit_break(self, node: nodes.Break) -> str:
162 """return an astroid.Break node as string"""
163 return "break"
164
165 def visit_call(self, node: nodes.Call) -> str:
166 """return an astroid.Call node as string"""
167 expr_str = self._precedence_parens(node, node.func)
168 args = [arg.accept(self) for arg in node.args]
169 if node.keywords:
170 keywords = [kwarg.accept(self) for kwarg in node.keywords]
171 else:
172 keywords = []
173
174 args.extend(keywords)
175 return f"{expr_str}({', '.join(args)})"
176
177 def visit_classdef(self, node: nodes.ClassDef) -> str:
178 """return an astroid.ClassDef node as string"""
179 decorate = node.decorators.accept(self) if node.decorators else ""
180 args = [n.accept(self) for n in node.bases]
181 if node._metaclass and not node.has_metaclass_hack():
182 args.append("metaclass=" + node._metaclass.accept(self))
183 args += [n.accept(self) for n in node.keywords]
184 args_str = f"({', '.join(args)})" if args else ""
185 docs = self._docs_dedent(node.doc_node)
186 # TODO: handle type_params
187 return "\n\n{}class {}{}:{}\n{}\n".format(
188 decorate, node.name, args_str, docs, self._stmt_list(node.body)
189 )
190
191 def visit_compare(self, node: nodes.Compare) -> str:
192 """return an astroid.Compare node as string"""
193 rhs_str = " ".join(
194 f"{op} {self._precedence_parens(node, expr, is_left=False)}"
195 for op, expr in node.ops
196 )
197 return f"{self._precedence_parens(node, node.left)} {rhs_str}"
198
199 def visit_comprehension(self, node: nodes.Comprehension) -> str:
200 """return an astroid.Comprehension node as string"""
201 ifs = "".join(f" if {n.accept(self)}" for n in node.ifs)
202 generated = f"for {node.target.accept(self)} in {node.iter.accept(self)}{ifs}"
203 return f"{'async ' if node.is_async else ''}{generated}"
204
205 def visit_const(self, node: nodes.Const) -> str:
206 """return an astroid.Const node as string"""
207 if node.value is Ellipsis:
208 return "..."
209 return repr(node.value)
210
211 def visit_continue(self, node: nodes.Continue) -> str:
212 """return an astroid.Continue node as string"""
213 return "continue"
214
215 def visit_delete(self, node: nodes.Delete) -> str:
216 """return an astroid.Delete node as string"""
217 return f"del {', '.join(child.accept(self) for child in node.targets)}"
218
219 def visit_delattr(self, node: nodes.DelAttr) -> str:
220 """return an astroid.DelAttr node as string"""
221 return self.visit_attribute(node)
222
223 def visit_delname(self, node: nodes.DelName) -> str:
224 """return an astroid.DelName node as string"""
225 return node.name
226
227 def visit_decorators(self, node: nodes.Decorators) -> str:
228 """return an astroid.Decorators node as string"""
229 return "@%s\n" % "\n@".join(item.accept(self) for item in node.nodes)
230
231 def visit_dict(self, node: nodes.Dict) -> str:
232 """return an astroid.Dict node as string"""
233 return "{%s}" % ", ".join(self._visit_dict(node))
234
235 def _visit_dict(self, node: nodes.Dict) -> Iterator[str]:
236 for key, value in node.items:
237 key = key.accept(self)
238 value = value.accept(self)
239 if key == "**":
240 # It can only be a DictUnpack node.
241 yield key + value
242 else:
243 yield f"{key}: {value}"
244
245 def visit_dictunpack(self, node: nodes.DictUnpack) -> str:
246 return "**"
247
248 def visit_dictcomp(self, node: nodes.DictComp) -> str:
249 """return an astroid.DictComp node as string"""
250 return "{{{}: {} {}}}".format(
251 node.key.accept(self),
252 node.value.accept(self),
253 " ".join(n.accept(self) for n in node.generators),
254 )
255
256 def visit_expr(self, node: nodes.Expr) -> str:
257 """return an astroid.Expr node as string"""
258 return node.value.accept(self)
259
260 def visit_emptynode(self, node: nodes.EmptyNode) -> str:
261 """dummy method for visiting an EmptyNode"""
262 return ""
263
264 def visit_excepthandler(self, node: nodes.ExceptHandler) -> str:
265 n = "except"
266 if isinstance(getattr(node, "parent", None), nodes.TryStar):
267 n = "except*"
268 if node.type:
269 if node.name:
270 excs = f"{n} {node.type.accept(self)} as {node.name.accept(self)}"
271 else:
272 excs = f"{n} {node.type.accept(self)}"
273 else:
274 excs = f"{n}"
275 return f"{excs}:\n{self._stmt_list(node.body)}"
276
277 def visit_empty(self, node: nodes.EmptyNode) -> str:
278 """return an EmptyNode as string"""
279 return ""
280
281 def visit_for(self, node: nodes.For) -> str:
282 """return an astroid.For node as string"""
283 fors = "for {} in {}:\n{}".format(
284 node.target.accept(self), node.iter.accept(self), self._stmt_list(node.body)
285 )
286 if node.orelse:
287 fors = f"{fors}\nelse:\n{self._stmt_list(node.orelse)}"
288 return fors
289
290 def visit_importfrom(self, node: nodes.ImportFrom) -> str:
291 """return an astroid.ImportFrom node as string"""
292 return "from {} import {}".format(
293 "." * (node.level or 0) + node.modname, _import_string(node.names)
294 )
295
296 def visit_joinedstr(self, node: nodes.JoinedStr) -> str:
297 string = "".join(
298 # Use repr on the string literal parts
299 # to get proper escapes, e.g. \n, \\, \"
300 # But strip the quotes off the ends
301 # (they will always be one character: ' or ")
302 (
303 repr(value.value)[1:-1]
304 # Literal braces must be doubled to escape them
305 .replace("{", "{{").replace("}", "}}")
306 # Each value in values is either a string literal (Const)
307 # or a FormattedValue
308 if type(value).__name__ == "Const"
309 else value.accept(self)
310 )
311 for value in node.values
312 )
313
314 # Try to find surrounding quotes that don't appear at all in the string.
315 # Because the formatted values inside {} can't contain backslash (\)
316 # using a triple quote is sometimes necessary
317 for quote in ("'", '"', '"""', "'''"):
318 if quote not in string:
319 break
320
321 return "f" + quote + string + quote
322
323 def visit_formattedvalue(self, node: nodes.FormattedValue) -> str:
324 result = node.value.accept(self)
325 if node.conversion and node.conversion >= 0:
326 # e.g. if node.conversion == 114: result += "!r"
327 result += "!" + chr(node.conversion)
328 if node.format_spec:
329 # The format spec is itself a JoinedString, i.e. an f-string
330 # We strip the f and quotes of the ends
331 result += ":" + node.format_spec.accept(self)[2:-1]
332 return "{%s}" % result
333
334 def handle_functiondef(self, node: nodes.FunctionDef, keyword: str) -> str:
335 """return a (possibly async) function definition node as string"""
336 decorate = node.decorators.accept(self) if node.decorators else ""
337 docs = self._docs_dedent(node.doc_node)
338 trailer = ":"
339 if node.returns:
340 return_annotation = " -> " + node.returns.as_string()
341 trailer = return_annotation + ":"
342 # TODO: handle type_params
343 def_format = "\n%s%s %s(%s)%s%s\n%s"
344 return def_format % (
345 decorate,
346 keyword,
347 node.name,
348 node.args.accept(self),
349 trailer,
350 docs,
351 self._stmt_list(node.body),
352 )
353
354 def visit_functiondef(self, node: nodes.FunctionDef) -> str:
355 """return an astroid.FunctionDef node as string"""
356 return self.handle_functiondef(node, "def")
357
358 def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> str:
359 """return an astroid.AsyncFunction node as string"""
360 return self.handle_functiondef(node, "async def")
361
362 def visit_generatorexp(self, node: nodes.GeneratorExp) -> str:
363 """return an astroid.GeneratorExp node as string"""
364 return "({} {})".format(
365 node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
366 )
367
368 def visit_attribute(
369 self, node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr
370 ) -> str:
371 """return an astroid.Attribute node as string"""
372 try:
373 left = self._precedence_parens(node, node.expr)
374 except RecursionError:
375 warnings.warn(
376 "Recursion limit exhausted; defaulting to adding parentheses.",
377 UserWarning,
378 stacklevel=2,
379 )
380 left = f"({node.expr.accept(self)})"
381 if left.isdigit():
382 left = f"({left})"
383 return f"{left}.{node.attrname}"
384
385 def visit_global(self, node: nodes.Global) -> str:
386 """return an astroid.Global node as string"""
387 return f"global {', '.join(node.names)}"
388
389 def visit_if(self, node: nodes.If) -> str:
390 """return an astroid.If node as string"""
391 ifs = [f"if {node.test.accept(self)}:\n{self._stmt_list(node.body)}"]
392 if node.has_elif_block():
393 ifs.append(f"el{self._stmt_list(node.orelse, indent=False)}")
394 elif node.orelse:
395 ifs.append(f"else:\n{self._stmt_list(node.orelse)}")
396 return "\n".join(ifs)
397
398 def visit_ifexp(self, node: nodes.IfExp) -> str:
399 """return an astroid.IfExp node as string"""
400 return "{} if {} else {}".format(
401 self._precedence_parens(node, node.body, is_left=True),
402 self._precedence_parens(node, node.test, is_left=True),
403 self._precedence_parens(node, node.orelse, is_left=False),
404 )
405
406 def visit_import(self, node: nodes.Import) -> str:
407 """return an astroid.Import node as string"""
408 return f"import {_import_string(node.names)}"
409
410 def visit_keyword(self, node: nodes.Keyword) -> str:
411 """return an astroid.Keyword node as string"""
412 if node.arg is None:
413 return f"**{node.value.accept(self)}"
414 return f"{node.arg}={node.value.accept(self)}"
415
416 def visit_lambda(self, node: nodes.Lambda) -> str:
417 """return an astroid.Lambda node as string"""
418 args = node.args.accept(self)
419 body = node.body.accept(self)
420 if args:
421 return f"lambda {args}: {body}"
422
423 return f"lambda: {body}"
424
425 def visit_list(self, node: nodes.List) -> str:
426 """return an astroid.List node as string"""
427 return f"[{', '.join(child.accept(self) for child in node.elts)}]"
428
429 def visit_listcomp(self, node: nodes.ListComp) -> str:
430 """return an astroid.ListComp node as string"""
431 return "[{} {}]".format(
432 node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
433 )
434
435 def visit_module(self, node: nodes.Module) -> str:
436 """return an astroid.Module node as string"""
437 docs = f'"""{node.doc_node.value}"""\n\n' if node.doc_node else ""
438 return docs + "\n".join(n.accept(self) for n in node.body) + "\n\n"
439
440 def visit_name(self, node: nodes.Name) -> str:
441 """return an astroid.Name node as string"""
442 return node.name
443
444 def visit_namedexpr(self, node: nodes.NamedExpr) -> str:
445 """Return an assignment expression node as string"""
446 target = node.target.accept(self)
447 value = node.value.accept(self)
448 return f"{target} := {value}"
449
450 def visit_nonlocal(self, node: nodes.Nonlocal) -> str:
451 """return an astroid.Nonlocal node as string"""
452 return f"nonlocal {', '.join(node.names)}"
453
454 def visit_paramspec(self, node: nodes.ParamSpec) -> str:
455 """return an astroid.ParamSpec node as string"""
456 return node.name.accept(self)
457
458 def visit_pass(self, node: nodes.Pass) -> str:
459 """return an astroid.Pass node as string"""
460 return "pass"
461
462 def visit_partialfunction(self, node: objects.PartialFunction) -> str:
463 """Return an objects.PartialFunction as string."""
464 return self.visit_functiondef(node)
465
466 def visit_raise(self, node: nodes.Raise) -> str:
467 """return an astroid.Raise node as string"""
468 if node.exc:
469 if node.cause:
470 return f"raise {node.exc.accept(self)} from {node.cause.accept(self)}"
471 return f"raise {node.exc.accept(self)}"
472 return "raise"
473
474 def visit_return(self, node: nodes.Return) -> str:
475 """return an astroid.Return node as string"""
476 if node.is_tuple_return() and len(node.value.elts) > 1:
477 elts = [child.accept(self) for child in node.value.elts]
478 return f"return {', '.join(elts)}"
479
480 if node.value:
481 return f"return {node.value.accept(self)}"
482
483 return "return"
484
485 def visit_set(self, node: nodes.Set) -> str:
486 """return an astroid.Set node as string"""
487 return "{%s}" % ", ".join(child.accept(self) for child in node.elts)
488
489 def visit_setcomp(self, node: nodes.SetComp) -> str:
490 """return an astroid.SetComp node as string"""
491 return "{{{} {}}}".format(
492 node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
493 )
494
495 def visit_slice(self, node: nodes.Slice) -> str:
496 """return an astroid.Slice node as string"""
497 lower = node.lower.accept(self) if node.lower else ""
498 upper = node.upper.accept(self) if node.upper else ""
499 step = node.step.accept(self) if node.step else ""
500 if step:
501 return f"{lower}:{upper}:{step}"
502 return f"{lower}:{upper}"
503
504 def visit_subscript(self, node: nodes.Subscript) -> str:
505 """return an astroid.Subscript node as string"""
506 idx = node.slice
507 if idx.__class__.__name__.lower() == "index":
508 idx = idx.value
509 idxstr = idx.accept(self)
510 if idx.__class__.__name__.lower() == "tuple" and idx.elts:
511 # Remove parenthesis in tuple and extended slice.
512 # a[(::1, 1:)] is not valid syntax.
513 idxstr = idxstr[1:-1]
514 return f"{self._precedence_parens(node, node.value)}[{idxstr}]"
515
516 def visit_try(self, node: nodes.Try) -> str:
517 """return an astroid.Try node as string"""
518 trys = [f"try:\n{self._stmt_list(node.body)}"]
519 for handler in node.handlers:
520 trys.append(handler.accept(self))
521 if node.orelse:
522 trys.append(f"else:\n{self._stmt_list(node.orelse)}")
523 if node.finalbody:
524 trys.append(f"finally:\n{self._stmt_list(node.finalbody)}")
525 return "\n".join(trys)
526
527 def visit_trystar(self, node: nodes.TryStar) -> str:
528 """return an astroid.TryStar node as string"""
529 trys = [f"try:\n{self._stmt_list(node.body)}"]
530 for handler in node.handlers:
531 trys.append(handler.accept(self))
532 if node.orelse:
533 trys.append(f"else:\n{self._stmt_list(node.orelse)}")
534 if node.finalbody:
535 trys.append(f"finally:\n{self._stmt_list(node.finalbody)}")
536 return "\n".join(trys)
537
538 def visit_tuple(self, node: nodes.Tuple) -> str:
539 """return an astroid.Tuple node as string"""
540 if len(node.elts) == 1:
541 return f"({node.elts[0].accept(self)}, )"
542 return f"({', '.join(child.accept(self) for child in node.elts)})"
543
544 def visit_typealias(self, node: nodes.TypeAlias) -> str:
545 """return an astroid.TypeAlias node as string"""
546 return node.name.accept(self) if node.name else "_"
547
548 def visit_typevar(self, node: nodes.TypeVar) -> str:
549 """return an astroid.TypeVar node as string"""
550 return node.name.accept(self) if node.name else "_"
551
552 def visit_typevartuple(self, node: nodes.TypeVarTuple) -> str:
553 """return an astroid.TypeVarTuple node as string"""
554 return "*" + node.name.accept(self) if node.name else ""
555
556 def visit_unaryop(self, node: nodes.UnaryOp) -> str:
557 """return an astroid.UnaryOp node as string"""
558 if node.op == "not":
559 operator = "not "
560 else:
561 operator = node.op
562 return f"{operator}{self._precedence_parens(node, node.operand)}"
563
564 def visit_while(self, node: nodes.While) -> str:
565 """return an astroid.While node as string"""
566 whiles = f"while {node.test.accept(self)}:\n{self._stmt_list(node.body)}"
567 if node.orelse:
568 whiles = f"{whiles}\nelse:\n{self._stmt_list(node.orelse)}"
569 return whiles
570
571 def visit_with(self, node: nodes.With) -> str: # 'with' without 'as' is possible
572 """return an astroid.With node as string"""
573 items = ", ".join(
574 f"{expr.accept(self)}" + ((v and f" as {v.accept(self)}") or "")
575 for expr, v in node.items
576 )
577 return f"with {items}:\n{self._stmt_list(node.body)}"
578
579 def visit_yield(self, node: nodes.Yield) -> str:
580 """yield an ast.Yield node as string"""
581 yi_val = (" " + node.value.accept(self)) if node.value else ""
582 expr = "yield" + yi_val
583 if node.parent.is_statement:
584 return expr
585
586 return f"({expr})"
587
588 def visit_yieldfrom(self, node: nodes.YieldFrom) -> str:
589 """Return an astroid.YieldFrom node as string."""
590 yi_val = (" " + node.value.accept(self)) if node.value else ""
591 expr = "yield from" + yi_val
592 if node.parent.is_statement:
593 return expr
594
595 return f"({expr})"
596
597 def visit_starred(self, node: nodes.Starred) -> str:
598 """return Starred node as string"""
599 return "*" + node.value.accept(self)
600
601 def visit_match(self, node: Match) -> str:
602 """Return an astroid.Match node as string."""
603 return f"match {node.subject.accept(self)}:\n{self._stmt_list(node.cases)}"
604
605 def visit_matchcase(self, node: MatchCase) -> str:
606 """Return an astroid.MatchCase node as string."""
607 guard_str = f" if {node.guard.accept(self)}" if node.guard else ""
608 return (
609 f"case {node.pattern.accept(self)}{guard_str}:\n"
610 f"{self._stmt_list(node.body)}"
611 )
612
613 def visit_matchvalue(self, node: MatchValue) -> str:
614 """Return an astroid.MatchValue node as string."""
615 return node.value.accept(self)
616
617 @staticmethod
618 def visit_matchsingleton(node: MatchSingleton) -> str:
619 """Return an astroid.MatchSingleton node as string."""
620 return str(node.value)
621
622 def visit_matchsequence(self, node: MatchSequence) -> str:
623 """Return an astroid.MatchSequence node as string."""
624 if node.patterns is None:
625 return "[]"
626 return f"[{', '.join(p.accept(self) for p in node.patterns)}]"
627
628 def visit_matchmapping(self, node: MatchMapping) -> str:
629 """Return an astroid.MatchMapping node as string."""
630 mapping_strings: list[str] = []
631 if node.keys and node.patterns:
632 mapping_strings.extend(
633 f"{key.accept(self)}: {p.accept(self)}"
634 for key, p in zip(node.keys, node.patterns)
635 )
636 if node.rest:
637 mapping_strings.append(f"**{node.rest.accept(self)}")
638 return f"{'{'}{', '.join(mapping_strings)}{'}'}"
639
640 def visit_matchclass(self, node: MatchClass) -> str:
641 """Return an astroid.MatchClass node as string."""
642 if node.cls is None:
643 raise AssertionError(f"{node} does not have a 'cls' node")
644 class_strings: list[str] = []
645 if node.patterns:
646 class_strings.extend(p.accept(self) for p in node.patterns)
647 if node.kwd_attrs and node.kwd_patterns:
648 for attr, pattern in zip(node.kwd_attrs, node.kwd_patterns):
649 class_strings.append(f"{attr}={pattern.accept(self)}")
650 return f"{node.cls.accept(self)}({', '.join(class_strings)})"
651
652 def visit_matchstar(self, node: MatchStar) -> str:
653 """Return an astroid.MatchStar node as string."""
654 return f"*{node.name.accept(self) if node.name else '_'}"
655
656 def visit_matchas(self, node: MatchAs) -> str:
657 """Return an astroid.MatchAs node as string."""
658 # pylint: disable=import-outside-toplevel
659 # Prevent circular dependency
660 from astroid.nodes.node_classes import MatchClass, MatchMapping, MatchSequence
661
662 if isinstance(node.parent, (MatchSequence, MatchMapping, MatchClass)):
663 return node.name.accept(self) if node.name else "_"
664 return (
665 f"{node.pattern.accept(self) if node.pattern else '_'}"
666 f"{f' as {node.name.accept(self)}' if node.name else ''}"
667 )
668
669 def visit_matchor(self, node: MatchOr) -> str:
670 """Return an astroid.MatchOr node as string."""
671 if node.patterns is None:
672 raise AssertionError(f"{node} does not have pattern nodes")
673 return " | ".join(p.accept(self) for p in node.patterns)
674
675 # These aren't for real AST nodes, but for inference objects.
676
677 def visit_frozenset(self, node: objects.FrozenSet) -> str:
678 return node.parent.accept(self)
679
680 def visit_super(self, node: objects.Super) -> str:
681 return node.parent.accept(self)
682
683 def visit_uninferable(self, node) -> str:
684 return str(node)
685
686 def visit_property(self, node: objects.Property) -> str:
687 return node.function.accept(self)
688
689 def visit_evaluatedobject(self, node: nodes.EvaluatedObject) -> str:
690 return node.original.accept(self)
691
692 def visit_unknown(self, node: Unknown) -> str:
693 return str(node)
694
695
696def _import_string(names: list[tuple[str, str | None]]) -> str:
697 """return a list of (name, asname) formatted as a string"""
698 _names = []
699 for name, asname in names:
700 if asname is not None:
701 _names.append(f"{name} as {asname}")
702 else:
703 _names.append(name)
704 return ", ".join(_names)
705
706
707# This sets the default indent to 4 spaces.
708to_code = AsStringVisitor(" ")