Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/nodes/as_string.py: 39%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

419 statements  

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 

18# pylint: disable=unused-argument 

19 

20DOC_NEWLINE = "\0" 

21 

22 

23# Visitor pattern require argument all the time and is not better with staticmethod 

24# noinspection PyUnusedLocal,PyMethodMayBeStatic 

25class AsStringVisitor: 

26 """Visitor to render an Astroid node as a valid python code string""" 

27 

28 def __init__(self, indent: str = " "): 

29 self.indent: str = indent 

30 

31 def __call__(self, node: nodes.NodeNG) -> str: 

32 """Makes this visitor behave as a simple function""" 

33 return node.accept(self).replace(DOC_NEWLINE, "\n") 

34 

35 def _docs_dedent(self, doc_node: nodes.Const | None) -> str: 

36 """Stop newlines in docs being indented by self._stmt_list""" 

37 if not doc_node: 

38 return "" 

39 

40 return '\n{}"""{}"""'.format( 

41 self.indent, doc_node.value.replace("\n", DOC_NEWLINE) 

42 ) 

43 

44 def _stmt_list(self, stmts: list, indent: bool = True) -> str: 

45 """return a list of nodes to string""" 

46 stmts_str: str = "\n".join( 

47 nstr for nstr in [n.accept(self) for n in stmts] if nstr 

48 ) 

49 if not indent: 

50 return stmts_str 

51 

52 return self.indent + stmts_str.replace("\n", "\n" + self.indent) 

53 

54 def _precedence_parens( 

55 self, node: nodes.NodeNG, child: nodes.NodeNG, is_left: bool = True 

56 ) -> str: 

57 """Wrap child in parens only if required to keep same semantics""" 

58 if self._should_wrap(node, child, is_left): 

59 return f"({child.accept(self)})" 

60 

61 return child.accept(self) 

62 

63 def _should_wrap( 

64 self, node: nodes.NodeNG, child: nodes.NodeNG, is_left: bool 

65 ) -> bool: 

66 """Wrap child if: 

67 - it has lower precedence 

68 - same precedence with position opposite to associativity direction 

69 """ 

70 node_precedence = node.op_precedence() 

71 child_precedence = child.op_precedence() 

72 

73 if node_precedence > child_precedence: 

74 # 3 * (4 + 5) 

75 return True 

76 

77 if ( 

78 node_precedence == child_precedence 

79 and is_left != node.op_left_associative() 

80 ): 

81 # 3 - (4 - 5) 

82 # (2**3)**4 

83 return True 

84 

85 return False 

86 

87 # visit_<node> methods ########################################### 

88 

89 def visit_await(self, node: nodes.Await) -> str: 

90 return f"await {node.value.accept(self)}" 

91 

92 def visit_asyncwith(self, node: nodes.AsyncWith) -> str: 

93 return f"async {self.visit_with(node)}" 

94 

95 def visit_asyncfor(self, node: nodes.AsyncFor) -> str: 

96 return f"async {self.visit_for(node)}" 

97 

98 def visit_arguments(self, node: nodes.Arguments) -> str: 

99 """return an nodes.Arguments node as string""" 

100 return node.format_args() 

101 

102 def visit_assignattr(self, node: nodes.AssignAttr) -> str: 

103 """return an nodes.AssignAttr node as string""" 

104 return self.visit_attribute(node) 

105 

106 def visit_assert(self, node: nodes.Assert) -> str: 

107 """return an nodes.Assert node as string""" 

108 if node.fail: 

109 return f"assert {node.test.accept(self)}, {node.fail.accept(self)}" 

110 return f"assert {node.test.accept(self)}" 

111 

112 def visit_assignname(self, node: nodes.AssignName) -> str: 

113 """return an nodes.AssignName node as string""" 

114 return node.name 

115 

116 def visit_assign(self, node: nodes.Assign) -> str: 

117 """return an nodes.Assign node as string""" 

118 lhs = " = ".join(n.accept(self) for n in node.targets) 

119 return f"{lhs} = {node.value.accept(self)}" 

120 

121 def visit_augassign(self, node: nodes.AugAssign) -> str: 

122 """return an nodes.AugAssign node as string""" 

123 return f"{node.target.accept(self)} {node.op} {node.value.accept(self)}" 

124 

125 def visit_annassign(self, node: nodes.AnnAssign) -> str: 

126 """Return an nodes.AnnAssign node as string""" 

127 

128 target = node.target.accept(self) 

129 annotation = node.annotation.accept(self) 

130 if node.value is None: 

131 return f"{target}: {annotation}" 

132 return f"{target}: {annotation} = {node.value.accept(self)}" 

133 

134 def visit_binop(self, node: nodes.BinOp) -> str: 

135 """return an nodes.BinOp node as string""" 

136 left = self._precedence_parens(node, node.left) 

137 right = self._precedence_parens(node, node.right, is_left=False) 

138 if node.op == "**": 

139 return f"{left}{node.op}{right}" 

140 

141 return f"{left} {node.op} {right}" 

142 

143 def visit_boolop(self, node: nodes.BoolOp) -> str: 

144 """return an nodes.BoolOp node as string""" 

145 values = [f"{self._precedence_parens(node, n)}" for n in node.values] 

146 return (f" {node.op} ").join(values) 

147 

148 def visit_break(self, node: nodes.Break) -> str: 

149 """return an nodes.Break node as string""" 

150 return "break" 

151 

152 def visit_call(self, node: nodes.Call) -> str: 

153 """return an nodes.Call node as string""" 

154 expr_str = self._precedence_parens(node, node.func) 

155 args = [arg.accept(self) for arg in node.args] 

156 if node.keywords: 

157 keywords = [kwarg.accept(self) for kwarg in node.keywords] 

158 else: 

159 keywords = [] 

160 

161 args.extend(keywords) 

162 return f"{expr_str}({', '.join(args)})" 

163 

164 def _handle_type_params( 

165 self, type_params: list[nodes.TypeVar | nodes.ParamSpec | nodes.TypeVarTuple] 

166 ) -> str: 

167 return ( 

168 f"[{', '.join(tp.accept(self) for tp in type_params)}]" 

169 if type_params 

170 else "" 

171 ) 

172 

173 def visit_classdef(self, node: nodes.ClassDef) -> str: 

174 """return an nodes.ClassDef node as string""" 

175 decorate = node.decorators.accept(self) if node.decorators else "" 

176 type_params = self._handle_type_params(node.type_params) 

177 args = [n.accept(self) for n in node.bases] 

178 if node._metaclass and not node.has_metaclass_hack(): 

179 args.append("metaclass=" + node._metaclass.accept(self)) 

180 args += [n.accept(self) for n in node.keywords] 

181 args_str = f"({', '.join(args)})" if args else "" 

182 docs = self._docs_dedent(node.doc_node) 

183 body = self._stmt_list(node.body) 

184 return ( 

185 f"\n\n{decorate}class {node.name}{type_params}{args_str}:{docs}\n{body}\n" 

186 ) 

187 

188 def visit_compare(self, node: nodes.Compare) -> str: 

189 """return an nodes.Compare node as string""" 

190 rhs_str = " ".join( 

191 f"{op} {self._precedence_parens(node, expr, is_left=False)}" 

192 for op, expr in node.ops 

193 ) 

194 return f"{self._precedence_parens(node, node.left)} {rhs_str}" 

195 

196 def visit_comprehension(self, node: nodes.Comprehension) -> str: 

197 """return an nodes.Comprehension node as string""" 

198 ifs = "".join(f" if {n.accept(self)}" for n in node.ifs) 

199 generated = f"for {node.target.accept(self)} in {node.iter.accept(self)}{ifs}" 

200 return f"{'async ' if node.is_async else ''}{generated}" 

201 

202 def visit_const(self, node: nodes.Const) -> str: 

203 """return an nodes.Const node as string""" 

204 if node.value is Ellipsis: 

205 return "..." 

206 return repr(node.value) 

207 

208 def visit_continue(self, node: nodes.Continue) -> str: 

209 """return an nodes.Continue node as string""" 

210 return "continue" 

211 

212 def visit_delete(self, node: nodes.Delete) -> str: 

213 """return an nodes.Delete node as string""" 

214 return f"del {', '.join(child.accept(self) for child in node.targets)}" 

215 

216 def visit_delattr(self, node: nodes.DelAttr) -> str: 

217 """return an nodes.DelAttr node as string""" 

218 return self.visit_attribute(node) 

219 

220 def visit_delname(self, node: nodes.DelName) -> str: 

221 """return an nodes.DelName node as string""" 

222 return node.name 

223 

224 def visit_decorators(self, node: nodes.Decorators) -> str: 

225 """return an nodes.Decorators node as string""" 

226 joined = "\n@".join(item.accept(self) for item in node.nodes) 

227 return f"@{joined}\n" 

228 

229 def visit_dict(self, node: nodes.Dict) -> str: 

230 """return an nodes.Dict node as string""" 

231 return f"{{{', '.join(self._visit_dict(node))}}}" 

232 

233 def _visit_dict(self, node: nodes.Dict) -> Iterator[str]: 

234 for key, value in node.items: 

235 key = key.accept(self) 

236 value = value.accept(self) 

237 if key == "**": 

238 # It can only be a DictUnpack node. 

239 yield key + value 

240 else: 

241 yield f"{key}: {value}" 

242 

243 def visit_dictunpack(self, node: nodes.DictUnpack) -> str: 

244 return "**" 

245 

246 def visit_dictcomp(self, node: nodes.DictComp) -> str: 

247 """return an nodes.DictComp node as string""" 

248 key = node.key.accept(self) 

249 value = node.value.accept(self) 

250 generators = " ".join(n.accept(self) for n in node.generators) 

251 if key == "**": 

252 # PEP 798 dict-comprehension unpacking, e.g. ``{**d for d in dicts}``. 

253 return f"{{{key}{value} {generators}}}" 

254 return f"{{{key}: {value} {generators}}}" 

255 

256 def visit_expr(self, node: nodes.Expr) -> str: 

257 """return an nodes.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 nodes.For node as string""" 

283 fors = f"for {node.target.accept(self)} in {node.iter.accept(self)}:\n{self._stmt_list(node.body)}" 

284 if node.orelse: 

285 fors = f"{fors}\nelse:\n{self._stmt_list(node.orelse)}" 

286 return fors 

287 

288 def visit_importfrom(self, node: nodes.ImportFrom) -> str: 

289 """return an nodes.ImportFrom node as string""" 

290 lazy = "lazy " if node.is_lazy else "" 

291 return "{}from {} import {}".format( 

292 lazy, "." * (node.level or 0) + node.modname, _import_string(node.names) 

293 ) 

294 

295 def visit_joinedstr(self, node: nodes.JoinedStr) -> str: 

296 string = "".join( 

297 # Use repr on the string literal parts 

298 # to get proper escapes, e.g. \n, \\, \" 

299 # But strip the quotes off the ends 

300 # (they will always be one character: ' or ") 

301 ( 

302 repr(value.value)[1:-1] 

303 # Literal braces must be doubled to escape them 

304 .replace("{", "{{").replace("}", "}}") 

305 # Each value in values is either a string literal (Const) 

306 # or a FormattedValue 

307 if type(value).__name__ == "Const" 

308 else value.accept(self) 

309 ) 

310 for value in node.values 

311 ) 

312 

313 # Try to find surrounding quotes that don't appear at all in the string. 

314 # Because the formatted values inside {} can't contain backslash (\) 

315 # using a triple quote is sometimes necessary 

316 for quote in ("'", '"', '"""', "'''"): 

317 if quote not in string: 

318 break 

319 

320 return "f" + quote + string + quote 

321 

322 def visit_formattedvalue(self, node: nodes.FormattedValue) -> str: 

323 result = node.value.accept(self) 

324 if node.conversion and node.conversion >= 0: 

325 # e.g. if node.conversion == 114: result += "!r" 

326 result += "!" + chr(node.conversion) 

327 if node.format_spec: 

328 # The format spec is itself a JoinedString, i.e. an f-string 

329 # We strip the f and quotes of the ends 

330 result += ":" + node.format_spec.accept(self)[2:-1] 

331 return f"{{{result}}}" 

332 

333 def handle_functiondef(self, node: nodes.FunctionDef, keyword: str) -> str: 

334 """return a (possibly async) function definition node as string""" 

335 decorate = node.decorators.accept(self) if node.decorators else "" 

336 type_params = self._handle_type_params(node.type_params) 

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 def_format = "\n%s%s %s%s(%s)%s%s\n%s" 

343 return def_format % ( 

344 decorate, 

345 keyword, 

346 node.name, 

347 type_params, 

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 nodes.FunctionDef node as string""" 

356 return self.handle_functiondef(node, "def") 

357 

358 def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> str: 

359 """return an nodes.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 nodes.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 nodes.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 nodes.Global node as string""" 

387 return f"global {', '.join(node.names)}" 

388 

389 def visit_if(self, node: nodes.If) -> str: 

390 """return an nodes.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 nodes.IfExp node as string""" 

400 body = self._precedence_parens(node, node.body, is_left=True) 

401 test = self._precedence_parens(node, node.test, is_left=True) 

402 orelse = self._precedence_parens(node, node.orelse, is_left=False) 

403 return f"{body} if {test} else {orelse}" 

404 

405 def visit_import(self, node: nodes.Import) -> str: 

406 """return an nodes.Import node as string""" 

407 lazy = "lazy " if node.is_lazy else "" 

408 return f"{lazy}import {_import_string(node.names)}" 

409 

410 def visit_keyword(self, node: nodes.Keyword) -> str: 

411 """return an nodes.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 nodes.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 nodes.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 nodes.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 nodes.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 nodes.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, always parenthesized.""" 

446 target = node.target.accept(self) 

447 value = node.value.accept(self) 

448 if isinstance(node.parent, nodes.Compare): 

449 return f"{target} := {value}" 

450 return f"({target} := {value})" 

451 

452 def visit_nonlocal(self, node: nodes.Nonlocal) -> str: 

453 """return an nodes.Nonlocal node as string""" 

454 return f"nonlocal {', '.join(node.names)}" 

455 

456 def visit_paramspec(self, node: nodes.ParamSpec) -> str: 

457 """return an nodes.ParamSpec node as string""" 

458 default_value_str = ( 

459 f" = {node.default_value.accept(self)}" if node.default_value else "" 

460 ) 

461 return f"**{node.name.accept(self)}{default_value_str}" 

462 

463 def visit_pass(self, node: nodes.Pass) -> str: 

464 """return an nodes.Pass node as string""" 

465 return "pass" 

466 

467 def visit_partialfunction(self, node: objects.PartialFunction) -> str: 

468 """Return an objects.PartialFunction as string.""" 

469 return self.visit_functiondef(node) 

470 

471 def visit_raise(self, node: nodes.Raise) -> str: 

472 """return an nodes.Raise node as string""" 

473 if node.exc: 

474 if node.cause: 

475 return f"raise {node.exc.accept(self)} from {node.cause.accept(self)}" 

476 return f"raise {node.exc.accept(self)}" 

477 return "raise" 

478 

479 def visit_return(self, node: nodes.Return) -> str: 

480 """return an nodes.Return node as string""" 

481 if node.is_tuple_return() and len(node.value.elts) > 1: 

482 elts = [child.accept(self) for child in node.value.elts] 

483 return f"return {', '.join(elts)}" 

484 

485 if node.value: 

486 return f"return {node.value.accept(self)}" 

487 

488 return "return" 

489 

490 def visit_set(self, node: nodes.Set) -> str: 

491 """return an nodes.Set node as string""" 

492 return f"{{{', '.join(child.accept(self) for child in node.elts)}}}" 

493 

494 def visit_setcomp(self, node: nodes.SetComp) -> str: 

495 """return an nodes.SetComp node as string""" 

496 return "{{{} {}}}".format( 

497 node.elt.accept(self), " ".join(n.accept(self) for n in node.generators) 

498 ) 

499 

500 def visit_slice(self, node: nodes.Slice) -> str: 

501 """return an nodes.Slice node as string""" 

502 lower = node.lower.accept(self) if node.lower else "" 

503 upper = node.upper.accept(self) if node.upper else "" 

504 step = node.step.accept(self) if node.step else "" 

505 if step: 

506 return f"{lower}:{upper}:{step}" 

507 return f"{lower}:{upper}" 

508 

509 def visit_subscript(self, node: nodes.Subscript) -> str: 

510 """return an nodes.Subscript node as string""" 

511 idx = node.slice 

512 if idx.__class__.__name__.lower() == "index": 

513 idx = idx.value 

514 idxstr = idx.accept(self) 

515 if idx.__class__.__name__.lower() == "tuple" and idx.elts: 

516 # Remove parenthesis in tuple and extended slice. 

517 # a[(::1, 1:)] is not valid syntax. 

518 idxstr = idxstr[1:-1] 

519 return f"{self._precedence_parens(node, node.value)}[{idxstr}]" 

520 

521 def visit_try(self, node: nodes.Try) -> str: 

522 """return an nodes.Try node as string""" 

523 trys = [f"try:\n{self._stmt_list(node.body)}"] 

524 for handler in node.handlers: 

525 trys.append(handler.accept(self)) 

526 if node.orelse: 

527 trys.append(f"else:\n{self._stmt_list(node.orelse)}") 

528 if node.finalbody: 

529 trys.append(f"finally:\n{self._stmt_list(node.finalbody)}") 

530 return "\n".join(trys) 

531 

532 def visit_trystar(self, node: nodes.TryStar) -> str: 

533 """return an nodes.TryStar node as string""" 

534 trys = [f"try:\n{self._stmt_list(node.body)}"] 

535 for handler in node.handlers: 

536 trys.append(handler.accept(self)) 

537 if node.orelse: 

538 trys.append(f"else:\n{self._stmt_list(node.orelse)}") 

539 if node.finalbody: 

540 trys.append(f"finally:\n{self._stmt_list(node.finalbody)}") 

541 return "\n".join(trys) 

542 

543 def visit_tuple(self, node: nodes.Tuple) -> str: 

544 """return an nodes.Tuple node as string""" 

545 if len(node.elts) == 1: 

546 return f"({node.elts[0].accept(self)}, )" 

547 return f"({', '.join(child.accept(self) for child in node.elts)})" 

548 

549 def visit_typealias(self, node: nodes.TypeAlias) -> str: 

550 """return an nodes.TypeAlias node as string""" 

551 type_params = self._handle_type_params(node.type_params) 

552 return f"type {node.name.accept(self)}{type_params} = {node.value.accept(self)}" 

553 

554 def visit_typevar(self, node: nodes.TypeVar) -> str: 

555 """return an nodes.TypeVar node as string""" 

556 bound_str = f": {node.bound.accept(self)}" if node.bound else "" 

557 default_value_str = ( 

558 f" = {node.default_value.accept(self)}" if node.default_value else "" 

559 ) 

560 return f"{node.name.accept(self)}{bound_str}{default_value_str}" 

561 

562 def visit_typevartuple(self, node: nodes.TypeVarTuple) -> str: 

563 """return an nodes.TypeVarTuple node as string""" 

564 default_value_str = ( 

565 f" = {node.default_value.accept(self)}" if node.default_value else "" 

566 ) 

567 return f"*{node.name.accept(self)}{default_value_str}" 

568 

569 def visit_unaryop(self, node: nodes.UnaryOp) -> str: 

570 """return an nodes.UnaryOp node as string""" 

571 if node.op == "not": 

572 operator = "not " 

573 else: 

574 operator = node.op 

575 return f"{operator}{self._precedence_parens(node, node.operand)}" 

576 

577 def visit_while(self, node: nodes.While) -> str: 

578 """return an nodes.While node as string""" 

579 whiles = f"while {node.test.accept(self)}:\n{self._stmt_list(node.body)}" 

580 if node.orelse: 

581 whiles = f"{whiles}\nelse:\n{self._stmt_list(node.orelse)}" 

582 return whiles 

583 

584 def visit_with(self, node: nodes.With) -> str: # 'with' without 'as' is possible 

585 """return an nodes.With node as string""" 

586 items = ", ".join( 

587 f"{expr.accept(self)}" + ((v and f" as {v.accept(self)}") or "") 

588 for expr, v in node.items 

589 ) 

590 return f"with {items}:\n{self._stmt_list(node.body)}" 

591 

592 def visit_yield(self, node: nodes.Yield) -> str: 

593 """yield an ast.Yield node as string""" 

594 yi_val = (" " + node.value.accept(self)) if node.value else "" 

595 expr = "yield" + yi_val 

596 if node.parent.is_statement: 

597 return expr 

598 

599 return f"({expr})" 

600 

601 def visit_yieldfrom(self, node: nodes.YieldFrom) -> str: 

602 """Return an nodes.YieldFrom node as string.""" 

603 yi_val = (" " + node.value.accept(self)) if node.value else "" 

604 expr = "yield from" + yi_val 

605 if node.parent.is_statement: 

606 return expr 

607 

608 return f"({expr})" 

609 

610 def visit_starred(self, node: nodes.Starred) -> str: 

611 """return Starred node as string""" 

612 return "*" + node.value.accept(self) 

613 

614 def visit_match(self, node: nodes.Match) -> str: 

615 """Return an nodes.Match node as string.""" 

616 return f"match {node.subject.accept(self)}:\n{self._stmt_list(node.cases)}" 

617 

618 def visit_matchcase(self, node: nodes.MatchCase) -> str: 

619 """Return an nodes.MatchCase node as string.""" 

620 guard_str = f" if {node.guard.accept(self)}" if node.guard else "" 

621 return ( 

622 f"case {node.pattern.accept(self)}{guard_str}:\n" 

623 f"{self._stmt_list(node.body)}" 

624 ) 

625 

626 def visit_matchvalue(self, node: nodes.MatchValue) -> str: 

627 """Return an nodes.MatchValue node as string.""" 

628 return node.value.accept(self) 

629 

630 @staticmethod 

631 def visit_matchsingleton(node: nodes.MatchSingleton) -> str: 

632 """Return an nodes.MatchSingleton node as string.""" 

633 return str(node.value) 

634 

635 def visit_matchsequence(self, node: nodes.MatchSequence) -> str: 

636 """Return an nodes.MatchSequence node as string.""" 

637 if node.patterns is None: 

638 return "[]" 

639 return f"[{', '.join(p.accept(self) for p in node.patterns)}]" 

640 

641 def visit_matchmapping(self, node: nodes.MatchMapping) -> str: 

642 """Return an nodes..MatchMapping node as string.""" 

643 mapping_strings: list[str] = [] 

644 if node.keys and node.patterns: 

645 mapping_strings.extend( 

646 f"{key.accept(self)}: {p.accept(self)}" 

647 for key, p in zip(node.keys, node.patterns) 

648 ) 

649 if node.rest: 

650 mapping_strings.append(f"**{node.rest.accept(self)}") 

651 return f"{'{'}{', '.join(mapping_strings)}{'}'}" 

652 

653 def visit_matchclass(self, node: nodes.MatchClass) -> str: 

654 """Return an nodes..MatchClass node as string.""" 

655 if node.cls is None: 

656 raise AssertionError(f"{node} does not have a 'cls' node") 

657 class_strings: list[str] = [] 

658 if node.patterns: 

659 class_strings.extend(p.accept(self) for p in node.patterns) 

660 if node.kwd_attrs and node.kwd_patterns: 

661 for attr, pattern in zip(node.kwd_attrs, node.kwd_patterns): 

662 class_strings.append(f"{attr}={pattern.accept(self)}") 

663 return f"{node.cls.accept(self)}({', '.join(class_strings)})" 

664 

665 def visit_matchstar(self, node: nodes.MatchStar) -> str: 

666 """Return an nodes..MatchStar node as string.""" 

667 return f"*{node.name.accept(self) if node.name else '_'}" 

668 

669 def visit_matchas(self, node: nodes.MatchAs) -> str: 

670 """Return an nodes..MatchAs node as string.""" 

671 if isinstance( 

672 node.parent, (nodes.MatchSequence, nodes.MatchMapping, nodes.MatchClass) 

673 ): 

674 return node.name.accept(self) if node.name else "_" 

675 return ( 

676 f"{node.pattern.accept(self) if node.pattern else '_'}" 

677 f"{f' as {node.name.accept(self)}' if node.name else ''}" 

678 ) 

679 

680 def visit_matchor(self, node: nodes.MatchOr) -> str: 

681 """Return an nodes.MatchOr node as string.""" 

682 if node.patterns is None: 

683 raise AssertionError(f"{node} does not have pattern nodes") 

684 return " | ".join(p.accept(self) for p in node.patterns) 

685 

686 def visit_templatestr(self, node: nodes.TemplateStr) -> str: 

687 """Return an nodes.TemplateStr node as string.""" 

688 string = "" 

689 for value in node.values: 

690 match value: 

691 case nodes.Interpolation(): 

692 string += "{" + value.accept(self) + "}" 

693 case _: 

694 string += value.accept(self)[1:-1] 

695 for quote in ("'", '"', '"""', "'''"): 

696 if quote not in string: 

697 break 

698 return "t" + quote + string + quote 

699 

700 def visit_interpolation(self, node: nodes.Interpolation) -> str: 

701 """Return an nodes.Interpolation node as string.""" 

702 result = f"{node.str}" 

703 if node.conversion and node.conversion >= 0: 

704 # e.g. if node.conversion == 114: result += "!r" 

705 result += "!" + chr(node.conversion) 

706 if node.format_spec: 

707 # The format spec is itself a JoinedString, i.e. an f-string 

708 # We strip the f and quotes of the ends 

709 result += ":" + node.format_spec.accept(self)[2:-1] 

710 return result 

711 

712 # These aren't for real AST nodes, but for inference objects. 

713 

714 def visit_frozenset(self, node: objects.FrozenSet) -> str: 

715 return node.parent.accept(self) 

716 

717 def visit_super(self, node: objects.Super) -> str: 

718 return node.parent.accept(self) 

719 

720 def visit_uninferable(self, node) -> str: 

721 return str(node) 

722 

723 def visit_property(self, node: objects.Property) -> str: 

724 return node.function.accept(self) 

725 

726 def visit_evaluatedobject(self, node: nodes.EvaluatedObject) -> str: 

727 return node.original.accept(self) 

728 

729 def visit_unknown(self, node: nodes.Unknown) -> str: 

730 return str(node) 

731 

732 

733def _import_string(names: list[tuple[str, str | None]]) -> str: 

734 """return a list of (name, asname) formatted as a string""" 

735 _names = [] 

736 for name, asname in names: 

737 if asname is not None: 

738 _names.append(f"{name} as {asname}") 

739 else: 

740 _names.append(name) 

741 return ", ".join(_names) 

742 

743 

744# This sets the default indent to 4 spaces. 

745to_code = AsStringVisitor(" ")