as_string.py 28 KB

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