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