692 lines
25 KiB
Python
692 lines
25 KiB
Python
|
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
|
||
|
# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
|
||
|
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
|
||
|
|
||
|
"""This module renders Astroid nodes as string"""
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
from collections.abc import Iterator
|
||
|
from typing import TYPE_CHECKING
|
||
|
|
||
|
from astroid import nodes
|
||
|
|
||
|
if TYPE_CHECKING:
|
||
|
from astroid import objects
|
||
|
from astroid.nodes import Const
|
||
|
from astroid.nodes.node_classes import (
|
||
|
Match,
|
||
|
MatchAs,
|
||
|
MatchCase,
|
||
|
MatchClass,
|
||
|
MatchMapping,
|
||
|
MatchOr,
|
||
|
MatchSequence,
|
||
|
MatchSingleton,
|
||
|
MatchStar,
|
||
|
MatchValue,
|
||
|
Unknown,
|
||
|
)
|
||
|
|
||
|
# pylint: disable=unused-argument
|
||
|
|
||
|
DOC_NEWLINE = "\0"
|
||
|
|
||
|
|
||
|
# Visitor pattern require argument all the time and is not better with staticmethod
|
||
|
# noinspection PyUnusedLocal,PyMethodMayBeStatic
|
||
|
class AsStringVisitor:
|
||
|
"""Visitor to render an Astroid node as a valid python code string"""
|
||
|
|
||
|
def __init__(self, indent: str = " "):
|
||
|
self.indent: str = indent
|
||
|
|
||
|
def __call__(self, node) -> str:
|
||
|
"""Makes this visitor behave as a simple function"""
|
||
|
return node.accept(self).replace(DOC_NEWLINE, "\n")
|
||
|
|
||
|
def _docs_dedent(self, doc_node: Const | None) -> str:
|
||
|
"""Stop newlines in docs being indented by self._stmt_list"""
|
||
|
if not doc_node:
|
||
|
return ""
|
||
|
|
||
|
return '\n{}"""{}"""'.format(
|
||
|
self.indent, doc_node.value.replace("\n", DOC_NEWLINE)
|
||
|
)
|
||
|
|
||
|
def _stmt_list(self, stmts: list, indent: bool = True) -> str:
|
||
|
"""return a list of nodes to string"""
|
||
|
stmts_str: str = "\n".join(
|
||
|
nstr for nstr in [n.accept(self) for n in stmts] if nstr
|
||
|
)
|
||
|
if not indent:
|
||
|
return stmts_str
|
||
|
|
||
|
return self.indent + stmts_str.replace("\n", "\n" + self.indent)
|
||
|
|
||
|
def _precedence_parens(self, node, child, is_left: bool = True) -> str:
|
||
|
"""Wrap child in parens only if required to keep same semantics"""
|
||
|
if self._should_wrap(node, child, is_left):
|
||
|
return f"({child.accept(self)})"
|
||
|
|
||
|
return child.accept(self)
|
||
|
|
||
|
def _should_wrap(self, node, child, is_left: bool) -> bool:
|
||
|
"""Wrap child if:
|
||
|
- it has lower precedence
|
||
|
- same precedence with position opposite to associativity direction
|
||
|
"""
|
||
|
node_precedence = node.op_precedence()
|
||
|
child_precedence = child.op_precedence()
|
||
|
|
||
|
if node_precedence > child_precedence:
|
||
|
# 3 * (4 + 5)
|
||
|
return True
|
||
|
|
||
|
if (
|
||
|
node_precedence == child_precedence
|
||
|
and is_left != node.op_left_associative()
|
||
|
):
|
||
|
# 3 - (4 - 5)
|
||
|
# (2**3)**4
|
||
|
return True
|
||
|
|
||
|
return False
|
||
|
|
||
|
# visit_<node> methods ###########################################
|
||
|
|
||
|
def visit_await(self, node) -> str:
|
||
|
return f"await {node.value.accept(self)}"
|
||
|
|
||
|
def visit_asyncwith(self, node) -> str:
|
||
|
return f"async {self.visit_with(node)}"
|
||
|
|
||
|
def visit_asyncfor(self, node) -> str:
|
||
|
return f"async {self.visit_for(node)}"
|
||
|
|
||
|
def visit_arguments(self, node) -> str:
|
||
|
"""return an astroid.Function node as string"""
|
||
|
return node.format_args()
|
||
|
|
||
|
def visit_assignattr(self, node) -> str:
|
||
|
"""return an astroid.AssAttr node as string"""
|
||
|
return self.visit_attribute(node)
|
||
|
|
||
|
def visit_assert(self, node) -> str:
|
||
|
"""return an astroid.Assert node as string"""
|
||
|
if node.fail:
|
||
|
return f"assert {node.test.accept(self)}, {node.fail.accept(self)}"
|
||
|
return f"assert {node.test.accept(self)}"
|
||
|
|
||
|
def visit_assignname(self, node) -> str:
|
||
|
"""return an astroid.AssName node as string"""
|
||
|
return node.name
|
||
|
|
||
|
def visit_assign(self, node) -> str:
|
||
|
"""return an astroid.Assign node as string"""
|
||
|
lhs = " = ".join(n.accept(self) for n in node.targets)
|
||
|
return f"{lhs} = {node.value.accept(self)}"
|
||
|
|
||
|
def visit_augassign(self, node) -> str:
|
||
|
"""return an astroid.AugAssign node as string"""
|
||
|
return f"{node.target.accept(self)} {node.op} {node.value.accept(self)}"
|
||
|
|
||
|
def visit_annassign(self, node) -> str:
|
||
|
"""Return an astroid.AugAssign node as string"""
|
||
|
|
||
|
target = node.target.accept(self)
|
||
|
annotation = node.annotation.accept(self)
|
||
|
if node.value is None:
|
||
|
return f"{target}: {annotation}"
|
||
|
return f"{target}: {annotation} = {node.value.accept(self)}"
|
||
|
|
||
|
def visit_binop(self, node) -> str:
|
||
|
"""return an astroid.BinOp node as string"""
|
||
|
left = self._precedence_parens(node, node.left)
|
||
|
right = self._precedence_parens(node, node.right, is_left=False)
|
||
|
if node.op == "**":
|
||
|
return f"{left}{node.op}{right}"
|
||
|
|
||
|
return f"{left} {node.op} {right}"
|
||
|
|
||
|
def visit_boolop(self, node) -> str:
|
||
|
"""return an astroid.BoolOp node as string"""
|
||
|
values = [f"{self._precedence_parens(node, n)}" for n in node.values]
|
||
|
return (f" {node.op} ").join(values)
|
||
|
|
||
|
def visit_break(self, node) -> str:
|
||
|
"""return an astroid.Break node as string"""
|
||
|
return "break"
|
||
|
|
||
|
def visit_call(self, node) -> str:
|
||
|
"""return an astroid.Call node as string"""
|
||
|
expr_str = self._precedence_parens(node, node.func)
|
||
|
args = [arg.accept(self) for arg in node.args]
|
||
|
if node.keywords:
|
||
|
keywords = [kwarg.accept(self) for kwarg in node.keywords]
|
||
|
else:
|
||
|
keywords = []
|
||
|
|
||
|
args.extend(keywords)
|
||
|
return f"{expr_str}({', '.join(args)})"
|
||
|
|
||
|
def visit_classdef(self, node) -> str:
|
||
|
"""return an astroid.ClassDef node as string"""
|
||
|
decorate = node.decorators.accept(self) if node.decorators else ""
|
||
|
args = [n.accept(self) for n in node.bases]
|
||
|
if node._metaclass and not node.has_metaclass_hack():
|
||
|
args.append("metaclass=" + node._metaclass.accept(self))
|
||
|
args += [n.accept(self) for n in node.keywords]
|
||
|
args_str = f"({', '.join(args)})" if args else ""
|
||
|
docs = self._docs_dedent(node.doc_node)
|
||
|
# TODO: handle type_params
|
||
|
return "\n\n{}class {}{}:{}\n{}\n".format(
|
||
|
decorate, node.name, args_str, docs, self._stmt_list(node.body)
|
||
|
)
|
||
|
|
||
|
def visit_compare(self, node) -> str:
|
||
|
"""return an astroid.Compare node as string"""
|
||
|
rhs_str = " ".join(
|
||
|
f"{op} {self._precedence_parens(node, expr, is_left=False)}"
|
||
|
for op, expr in node.ops
|
||
|
)
|
||
|
return f"{self._precedence_parens(node, node.left)} {rhs_str}"
|
||
|
|
||
|
def visit_comprehension(self, node) -> str:
|
||
|
"""return an astroid.Comprehension node as string"""
|
||
|
ifs = "".join(f" if {n.accept(self)}" for n in node.ifs)
|
||
|
generated = f"for {node.target.accept(self)} in {node.iter.accept(self)}{ifs}"
|
||
|
return f"{'async ' if node.is_async else ''}{generated}"
|
||
|
|
||
|
def visit_const(self, node) -> str:
|
||
|
"""return an astroid.Const node as string"""
|
||
|
if node.value is Ellipsis:
|
||
|
return "..."
|
||
|
return repr(node.value)
|
||
|
|
||
|
def visit_continue(self, node) -> str:
|
||
|
"""return an astroid.Continue node as string"""
|
||
|
return "continue"
|
||
|
|
||
|
def visit_delete(self, node) -> str: # XXX check if correct
|
||
|
"""return an astroid.Delete node as string"""
|
||
|
return f"del {', '.join(child.accept(self) for child in node.targets)}"
|
||
|
|
||
|
def visit_delattr(self, node) -> str:
|
||
|
"""return an astroid.DelAttr node as string"""
|
||
|
return self.visit_attribute(node)
|
||
|
|
||
|
def visit_delname(self, node) -> str:
|
||
|
"""return an astroid.DelName node as string"""
|
||
|
return node.name
|
||
|
|
||
|
def visit_decorators(self, node) -> str:
|
||
|
"""return an astroid.Decorators node as string"""
|
||
|
return "@%s\n" % "\n@".join(item.accept(self) for item in node.nodes)
|
||
|
|
||
|
def visit_dict(self, node) -> str:
|
||
|
"""return an astroid.Dict node as string"""
|
||
|
return "{%s}" % ", ".join(self._visit_dict(node))
|
||
|
|
||
|
def _visit_dict(self, node) -> Iterator[str]:
|
||
|
for key, value in node.items:
|
||
|
key = key.accept(self)
|
||
|
value = value.accept(self)
|
||
|
if key == "**":
|
||
|
# It can only be a DictUnpack node.
|
||
|
yield key + value
|
||
|
else:
|
||
|
yield f"{key}: {value}"
|
||
|
|
||
|
def visit_dictunpack(self, node) -> str:
|
||
|
return "**"
|
||
|
|
||
|
def visit_dictcomp(self, node) -> str:
|
||
|
"""return an astroid.DictComp node as string"""
|
||
|
return "{{{}: {} {}}}".format(
|
||
|
node.key.accept(self),
|
||
|
node.value.accept(self),
|
||
|
" ".join(n.accept(self) for n in node.generators),
|
||
|
)
|
||
|
|
||
|
def visit_expr(self, node) -> str:
|
||
|
"""return an astroid.Discard node as string"""
|
||
|
return node.value.accept(self)
|
||
|
|
||
|
def visit_emptynode(self, node) -> str:
|
||
|
"""dummy method for visiting an Empty node"""
|
||
|
return ""
|
||
|
|
||
|
def visit_excepthandler(self, node) -> str:
|
||
|
n = "except"
|
||
|
if isinstance(getattr(node, "parent", None), nodes.TryStar):
|
||
|
n = "except*"
|
||
|
if node.type:
|
||
|
if node.name:
|
||
|
excs = f"{n} {node.type.accept(self)} as {node.name.accept(self)}"
|
||
|
else:
|
||
|
excs = f"{n} {node.type.accept(self)}"
|
||
|
else:
|
||
|
excs = f"{n}"
|
||
|
return f"{excs}:\n{self._stmt_list(node.body)}"
|
||
|
|
||
|
def visit_empty(self, node) -> str:
|
||
|
"""return an Empty node as string"""
|
||
|
return ""
|
||
|
|
||
|
def visit_for(self, node) -> str:
|
||
|
"""return an astroid.For node as string"""
|
||
|
fors = "for {} in {}:\n{}".format(
|
||
|
node.target.accept(self), node.iter.accept(self), self._stmt_list(node.body)
|
||
|
)
|
||
|
if node.orelse:
|
||
|
fors = f"{fors}\nelse:\n{self._stmt_list(node.orelse)}"
|
||
|
return fors
|
||
|
|
||
|
def visit_importfrom(self, node) -> str:
|
||
|
"""return an astroid.ImportFrom node as string"""
|
||
|
return "from {} import {}".format(
|
||
|
"." * (node.level or 0) + node.modname, _import_string(node.names)
|
||
|
)
|
||
|
|
||
|
def visit_joinedstr(self, node) -> str:
|
||
|
string = "".join(
|
||
|
# Use repr on the string literal parts
|
||
|
# to get proper escapes, e.g. \n, \\, \"
|
||
|
# But strip the quotes off the ends
|
||
|
# (they will always be one character: ' or ")
|
||
|
repr(value.value)[1:-1]
|
||
|
# Literal braces must be doubled to escape them
|
||
|
.replace("{", "{{").replace("}", "}}")
|
||
|
# Each value in values is either a string literal (Const)
|
||
|
# or a FormattedValue
|
||
|
if type(value).__name__ == "Const" else value.accept(self)
|
||
|
for value in node.values
|
||
|
)
|
||
|
|
||
|
# Try to find surrounding quotes that don't appear at all in the string.
|
||
|
# Because the formatted values inside {} can't contain backslash (\)
|
||
|
# using a triple quote is sometimes necessary
|
||
|
for quote in ("'", '"', '"""', "'''"):
|
||
|
if quote not in string:
|
||
|
break
|
||
|
|
||
|
return "f" + quote + string + quote
|
||
|
|
||
|
def visit_formattedvalue(self, node) -> str:
|
||
|
result = node.value.accept(self)
|
||
|
if node.conversion and node.conversion >= 0:
|
||
|
# e.g. if node.conversion == 114: result += "!r"
|
||
|
result += "!" + chr(node.conversion)
|
||
|
if node.format_spec:
|
||
|
# The format spec is itself a JoinedString, i.e. an f-string
|
||
|
# We strip the f and quotes of the ends
|
||
|
result += ":" + node.format_spec.accept(self)[2:-1]
|
||
|
return "{%s}" % result
|
||
|
|
||
|
def handle_functiondef(self, node: nodes.FunctionDef, keyword: str) -> str:
|
||
|
"""return a (possibly async) function definition node as string"""
|
||
|
decorate = node.decorators.accept(self) if node.decorators else ""
|
||
|
docs = self._docs_dedent(node.doc_node)
|
||
|
trailer = ":"
|
||
|
if node.returns:
|
||
|
return_annotation = " -> " + node.returns.as_string()
|
||
|
trailer = return_annotation + ":"
|
||
|
# TODO: handle type_params
|
||
|
def_format = "\n%s%s %s(%s)%s%s\n%s"
|
||
|
return def_format % (
|
||
|
decorate,
|
||
|
keyword,
|
||
|
node.name,
|
||
|
node.args.accept(self),
|
||
|
trailer,
|
||
|
docs,
|
||
|
self._stmt_list(node.body),
|
||
|
)
|
||
|
|
||
|
def visit_functiondef(self, node: nodes.FunctionDef) -> str:
|
||
|
"""return an astroid.FunctionDef node as string"""
|
||
|
return self.handle_functiondef(node, "def")
|
||
|
|
||
|
def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> str:
|
||
|
"""return an astroid.AsyncFunction node as string"""
|
||
|
return self.handle_functiondef(node, "async def")
|
||
|
|
||
|
def visit_generatorexp(self, node) -> str:
|
||
|
"""return an astroid.GeneratorExp node as string"""
|
||
|
return "({} {})".format(
|
||
|
node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
|
||
|
)
|
||
|
|
||
|
def visit_attribute(self, node) -> str:
|
||
|
"""return an astroid.Getattr node as string"""
|
||
|
left = self._precedence_parens(node, node.expr)
|
||
|
if left.isdigit():
|
||
|
left = f"({left})"
|
||
|
return f"{left}.{node.attrname}"
|
||
|
|
||
|
def visit_global(self, node) -> str:
|
||
|
"""return an astroid.Global node as string"""
|
||
|
return f"global {', '.join(node.names)}"
|
||
|
|
||
|
def visit_if(self, node) -> str:
|
||
|
"""return an astroid.If node as string"""
|
||
|
ifs = [f"if {node.test.accept(self)}:\n{self._stmt_list(node.body)}"]
|
||
|
if node.has_elif_block():
|
||
|
ifs.append(f"el{self._stmt_list(node.orelse, indent=False)}")
|
||
|
elif node.orelse:
|
||
|
ifs.append(f"else:\n{self._stmt_list(node.orelse)}")
|
||
|
return "\n".join(ifs)
|
||
|
|
||
|
def visit_ifexp(self, node) -> str:
|
||
|
"""return an astroid.IfExp node as string"""
|
||
|
return "{} if {} else {}".format(
|
||
|
self._precedence_parens(node, node.body, is_left=True),
|
||
|
self._precedence_parens(node, node.test, is_left=True),
|
||
|
self._precedence_parens(node, node.orelse, is_left=False),
|
||
|
)
|
||
|
|
||
|
def visit_import(self, node) -> str:
|
||
|
"""return an astroid.Import node as string"""
|
||
|
return f"import {_import_string(node.names)}"
|
||
|
|
||
|
def visit_keyword(self, node) -> str:
|
||
|
"""return an astroid.Keyword node as string"""
|
||
|
if node.arg is None:
|
||
|
return f"**{node.value.accept(self)}"
|
||
|
return f"{node.arg}={node.value.accept(self)}"
|
||
|
|
||
|
def visit_lambda(self, node) -> str:
|
||
|
"""return an astroid.Lambda node as string"""
|
||
|
args = node.args.accept(self)
|
||
|
body = node.body.accept(self)
|
||
|
if args:
|
||
|
return f"lambda {args}: {body}"
|
||
|
|
||
|
return f"lambda: {body}"
|
||
|
|
||
|
def visit_list(self, node) -> str:
|
||
|
"""return an astroid.List node as string"""
|
||
|
return f"[{', '.join(child.accept(self) for child in node.elts)}]"
|
||
|
|
||
|
def visit_listcomp(self, node) -> str:
|
||
|
"""return an astroid.ListComp node as string"""
|
||
|
return "[{} {}]".format(
|
||
|
node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
|
||
|
)
|
||
|
|
||
|
def visit_module(self, node) -> str:
|
||
|
"""return an astroid.Module node as string"""
|
||
|
docs = f'"""{node.doc_node.value}"""\n\n' if node.doc_node else ""
|
||
|
return docs + "\n".join(n.accept(self) for n in node.body) + "\n\n"
|
||
|
|
||
|
def visit_name(self, node) -> str:
|
||
|
"""return an astroid.Name node as string"""
|
||
|
return node.name
|
||
|
|
||
|
def visit_namedexpr(self, node) -> str:
|
||
|
"""Return an assignment expression node as string"""
|
||
|
target = node.target.accept(self)
|
||
|
value = node.value.accept(self)
|
||
|
return f"{target} := {value}"
|
||
|
|
||
|
def visit_nonlocal(self, node) -> str:
|
||
|
"""return an astroid.Nonlocal node as string"""
|
||
|
return f"nonlocal {', '.join(node.names)}"
|
||
|
|
||
|
def visit_paramspec(self, node: nodes.ParamSpec) -> str:
|
||
|
"""return an astroid.ParamSpec node as string"""
|
||
|
return node.name.accept(self)
|
||
|
|
||
|
def visit_pass(self, node) -> str:
|
||
|
"""return an astroid.Pass node as string"""
|
||
|
return "pass"
|
||
|
|
||
|
def visit_partialfunction(self, node: objects.PartialFunction) -> str:
|
||
|
"""Return an objects.PartialFunction as string."""
|
||
|
return self.visit_functiondef(node)
|
||
|
|
||
|
def visit_raise(self, node) -> str:
|
||
|
"""return an astroid.Raise node as string"""
|
||
|
if node.exc:
|
||
|
if node.cause:
|
||
|
return f"raise {node.exc.accept(self)} from {node.cause.accept(self)}"
|
||
|
return f"raise {node.exc.accept(self)}"
|
||
|
return "raise"
|
||
|
|
||
|
def visit_return(self, node) -> str:
|
||
|
"""return an astroid.Return node as string"""
|
||
|
if node.is_tuple_return() and len(node.value.elts) > 1:
|
||
|
elts = [child.accept(self) for child in node.value.elts]
|
||
|
return f"return {', '.join(elts)}"
|
||
|
|
||
|
if node.value:
|
||
|
return f"return {node.value.accept(self)}"
|
||
|
|
||
|
return "return"
|
||
|
|
||
|
def visit_set(self, node) -> str:
|
||
|
"""return an astroid.Set node as string"""
|
||
|
return "{%s}" % ", ".join(child.accept(self) for child in node.elts)
|
||
|
|
||
|
def visit_setcomp(self, node) -> str:
|
||
|
"""return an astroid.SetComp node as string"""
|
||
|
return "{{{} {}}}".format(
|
||
|
node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
|
||
|
)
|
||
|
|
||
|
def visit_slice(self, node) -> str:
|
||
|
"""return an astroid.Slice node as string"""
|
||
|
lower = node.lower.accept(self) if node.lower else ""
|
||
|
upper = node.upper.accept(self) if node.upper else ""
|
||
|
step = node.step.accept(self) if node.step else ""
|
||
|
if step:
|
||
|
return f"{lower}:{upper}:{step}"
|
||
|
return f"{lower}:{upper}"
|
||
|
|
||
|
def visit_subscript(self, node) -> str:
|
||
|
"""return an astroid.Subscript node as string"""
|
||
|
idx = node.slice
|
||
|
if idx.__class__.__name__.lower() == "index":
|
||
|
idx = idx.value
|
||
|
idxstr = idx.accept(self)
|
||
|
if idx.__class__.__name__.lower() == "tuple" and idx.elts:
|
||
|
# Remove parenthesis in tuple and extended slice.
|
||
|
# a[(::1, 1:)] is not valid syntax.
|
||
|
idxstr = idxstr[1:-1]
|
||
|
return f"{self._precedence_parens(node, node.value)}[{idxstr}]"
|
||
|
|
||
|
def visit_try(self, node) -> str:
|
||
|
"""return an astroid.Try node as string"""
|
||
|
trys = [f"try:\n{self._stmt_list(node.body)}"]
|
||
|
for handler in node.handlers:
|
||
|
trys.append(handler.accept(self))
|
||
|
if node.orelse:
|
||
|
trys.append(f"else:\n{self._stmt_list(node.orelse)}")
|
||
|
if node.finalbody:
|
||
|
trys.append(f"finally:\n{self._stmt_list(node.finalbody)}")
|
||
|
return "\n".join(trys)
|
||
|
|
||
|
def visit_trystar(self, node) -> str:
|
||
|
"""return an astroid.TryStar node as string"""
|
||
|
trys = [f"try:\n{self._stmt_list(node.body)}"]
|
||
|
for handler in node.handlers:
|
||
|
trys.append(handler.accept(self))
|
||
|
if node.orelse:
|
||
|
trys.append(f"else:\n{self._stmt_list(node.orelse)}")
|
||
|
if node.finalbody:
|
||
|
trys.append(f"finally:\n{self._stmt_list(node.finalbody)}")
|
||
|
return "\n".join(trys)
|
||
|
|
||
|
def visit_tuple(self, node) -> str:
|
||
|
"""return an astroid.Tuple node as string"""
|
||
|
if len(node.elts) == 1:
|
||
|
return f"({node.elts[0].accept(self)}, )"
|
||
|
return f"({', '.join(child.accept(self) for child in node.elts)})"
|
||
|
|
||
|
def visit_typealias(self, node: nodes.TypeAlias) -> str:
|
||
|
"""return an astroid.TypeAlias node as string"""
|
||
|
return node.name.accept(self) if node.name else "_"
|
||
|
|
||
|
def visit_typevar(self, node: nodes.TypeVar) -> str:
|
||
|
"""return an astroid.TypeVar node as string"""
|
||
|
return node.name.accept(self) if node.name else "_"
|
||
|
|
||
|
def visit_typevartuple(self, node: nodes.TypeVarTuple) -> str:
|
||
|
"""return an astroid.TypeVarTuple node as string"""
|
||
|
return "*" + node.name.accept(self) if node.name else ""
|
||
|
|
||
|
def visit_unaryop(self, node) -> str:
|
||
|
"""return an astroid.UnaryOp node as string"""
|
||
|
if node.op == "not":
|
||
|
operator = "not "
|
||
|
else:
|
||
|
operator = node.op
|
||
|
return f"{operator}{self._precedence_parens(node, node.operand)}"
|
||
|
|
||
|
def visit_while(self, node) -> str:
|
||
|
"""return an astroid.While node as string"""
|
||
|
whiles = f"while {node.test.accept(self)}:\n{self._stmt_list(node.body)}"
|
||
|
if node.orelse:
|
||
|
whiles = f"{whiles}\nelse:\n{self._stmt_list(node.orelse)}"
|
||
|
return whiles
|
||
|
|
||
|
def visit_with(self, node) -> str: # 'with' without 'as' is possible
|
||
|
"""return an astroid.With node as string"""
|
||
|
items = ", ".join(
|
||
|
f"{expr.accept(self)}" + (v and f" as {v.accept(self)}" or "")
|
||
|
for expr, v in node.items
|
||
|
)
|
||
|
return f"with {items}:\n{self._stmt_list(node.body)}"
|
||
|
|
||
|
def visit_yield(self, node) -> str:
|
||
|
"""yield an ast.Yield node as string"""
|
||
|
yi_val = (" " + node.value.accept(self)) if node.value else ""
|
||
|
expr = "yield" + yi_val
|
||
|
if node.parent.is_statement:
|
||
|
return expr
|
||
|
|
||
|
return f"({expr})"
|
||
|
|
||
|
def visit_yieldfrom(self, node) -> str:
|
||
|
"""Return an astroid.YieldFrom node as string."""
|
||
|
yi_val = (" " + node.value.accept(self)) if node.value else ""
|
||
|
expr = "yield from" + yi_val
|
||
|
if node.parent.is_statement:
|
||
|
return expr
|
||
|
|
||
|
return f"({expr})"
|
||
|
|
||
|
def visit_starred(self, node) -> str:
|
||
|
"""return Starred node as string"""
|
||
|
return "*" + node.value.accept(self)
|
||
|
|
||
|
def visit_match(self, node: Match) -> str:
|
||
|
"""Return an astroid.Match node as string."""
|
||
|
return f"match {node.subject.accept(self)}:\n{self._stmt_list(node.cases)}"
|
||
|
|
||
|
def visit_matchcase(self, node: MatchCase) -> str:
|
||
|
"""Return an astroid.MatchCase node as string."""
|
||
|
guard_str = f" if {node.guard.accept(self)}" if node.guard else ""
|
||
|
return (
|
||
|
f"case {node.pattern.accept(self)}{guard_str}:\n"
|
||
|
f"{self._stmt_list(node.body)}"
|
||
|
)
|
||
|
|
||
|
def visit_matchvalue(self, node: MatchValue) -> str:
|
||
|
"""Return an astroid.MatchValue node as string."""
|
||
|
return node.value.accept(self)
|
||
|
|
||
|
@staticmethod
|
||
|
def visit_matchsingleton(node: MatchSingleton) -> str:
|
||
|
"""Return an astroid.MatchSingleton node as string."""
|
||
|
return str(node.value)
|
||
|
|
||
|
def visit_matchsequence(self, node: MatchSequence) -> str:
|
||
|
"""Return an astroid.MatchSequence node as string."""
|
||
|
if node.patterns is None:
|
||
|
return "[]"
|
||
|
return f"[{', '.join(p.accept(self) for p in node.patterns)}]"
|
||
|
|
||
|
def visit_matchmapping(self, node: MatchMapping) -> str:
|
||
|
"""Return an astroid.MatchMapping node as string."""
|
||
|
mapping_strings: list[str] = []
|
||
|
if node.keys and node.patterns:
|
||
|
mapping_strings.extend(
|
||
|
f"{key.accept(self)}: {p.accept(self)}"
|
||
|
for key, p in zip(node.keys, node.patterns)
|
||
|
)
|
||
|
if node.rest:
|
||
|
mapping_strings.append(f"**{node.rest.accept(self)}")
|
||
|
return f"{'{'}{', '.join(mapping_strings)}{'}'}"
|
||
|
|
||
|
def visit_matchclass(self, node: MatchClass) -> str:
|
||
|
"""Return an astroid.MatchClass node as string."""
|
||
|
if node.cls is None:
|
||
|
raise AssertionError(f"{node} does not have a 'cls' node")
|
||
|
class_strings: list[str] = []
|
||
|
if node.patterns:
|
||
|
class_strings.extend(p.accept(self) for p in node.patterns)
|
||
|
if node.kwd_attrs and node.kwd_patterns:
|
||
|
for attr, pattern in zip(node.kwd_attrs, node.kwd_patterns):
|
||
|
class_strings.append(f"{attr}={pattern.accept(self)}")
|
||
|
return f"{node.cls.accept(self)}({', '.join(class_strings)})"
|
||
|
|
||
|
def visit_matchstar(self, node: MatchStar) -> str:
|
||
|
"""Return an astroid.MatchStar node as string."""
|
||
|
return f"*{node.name.accept(self) if node.name else '_'}"
|
||
|
|
||
|
def visit_matchas(self, node: MatchAs) -> str:
|
||
|
"""Return an astroid.MatchAs node as string."""
|
||
|
# pylint: disable=import-outside-toplevel
|
||
|
# Prevent circular dependency
|
||
|
from astroid.nodes.node_classes import MatchClass, MatchMapping, MatchSequence
|
||
|
|
||
|
if isinstance(node.parent, (MatchSequence, MatchMapping, MatchClass)):
|
||
|
return node.name.accept(self) if node.name else "_"
|
||
|
return (
|
||
|
f"{node.pattern.accept(self) if node.pattern else '_'}"
|
||
|
f"{f' as {node.name.accept(self)}' if node.name else ''}"
|
||
|
)
|
||
|
|
||
|
def visit_matchor(self, node: MatchOr) -> str:
|
||
|
"""Return an astroid.MatchOr node as string."""
|
||
|
if node.patterns is None:
|
||
|
raise AssertionError(f"{node} does not have pattern nodes")
|
||
|
return " | ".join(p.accept(self) for p in node.patterns)
|
||
|
|
||
|
# These aren't for real AST nodes, but for inference objects.
|
||
|
|
||
|
def visit_frozenset(self, node):
|
||
|
return node.parent.accept(self)
|
||
|
|
||
|
def visit_super(self, node):
|
||
|
return node.parent.accept(self)
|
||
|
|
||
|
def visit_uninferable(self, node):
|
||
|
return str(node)
|
||
|
|
||
|
def visit_property(self, node):
|
||
|
return node.function.accept(self)
|
||
|
|
||
|
def visit_evaluatedobject(self, node):
|
||
|
return node.original.accept(self)
|
||
|
|
||
|
def visit_unknown(self, node: Unknown) -> str:
|
||
|
return str(node)
|
||
|
|
||
|
|
||
|
def _import_string(names) -> str:
|
||
|
"""return a list of (name, asname) formatted as a string"""
|
||
|
_names = []
|
||
|
for name, asname in names:
|
||
|
if asname is not None:
|
||
|
_names.append(f"{name} as {asname}")
|
||
|
else:
|
||
|
_names.append(name)
|
||
|
return ", ".join(_names)
|
||
|
|
||
|
|
||
|
# This sets the default indent to 4 spaces.
|
||
|
to_code = AsStringVisitor(" ")
|