LibCST 匹配器,用于检测 Python AST 中的嵌套 f 字符串表达式

问题描述 投票:0回答:1

我想创建一个转换器,将 f 字符串的所有引号从单引号转换为三引号,但保持嵌套的 f 字符串完好无损。

例如,下一个表达式保持不变。

f"""\
Hello {developer_name}
My name is {_get_machine(f"{self.prop_a}, {self.prop_b}")}
"""

但是,变压器结果是:

f"""\
Hello {developer_name}
My name is {_get_machine(f"""{self.prop_a}, {self.prop_b}""")}
"""

我尝试了以下匹配器但没有成功:

class _FormattedStringEscapingTransformer(m.MatcherDecoratableTransformer):

    @m.call_if_not_inside(
        m.FormattedString(
            parts=m.OneOf(
                m.FormattedStringExpression(expression=m.TypeOf(m.FormattedString))
            )
        )
    )
    @m.leave(m.FormattedString())
    def escape_f_string(
        self, original_node: cst.FormattedString, updated_node: cst.FormattedString
    ) -> cst.FormattedString:
        return updated_node.with_changes(start='f"""', end='"""')
class _FormattedStringEscapingTransformer(m.MatcherDecoratableTransformer):

    @m.call_if_not_inside(
        m.FormattedString(
            parts=m.OneOf(
                m.FormattedStringExpression(
                    expression=m.Not(m.FormattedString(parts=m.DoNotCare()))
                )
            )
        )
    )
    @m.leave(m.FormattedString())
    def escape_f_string(
        self, original_node: cst.FormattedString, updated_node: cst.FormattedString
    ) -> cst.FormattedString:
        return updated_node.with_changes(start='f"""', end='"""')

他们都不起作用。

如果内部 f 字符串表达式排除转换,正确的匹配器是什么?

python abstract-syntax-tree libcst
1个回答
0
投票

我不认为

call_if_[not_]inside
是执行此操作的正确工具 - 它将始终调用具有匹配条件的节点 或其父级 ,而不是 只是其父级 :

https://libcst.readthedocs.io/en/latest/matchers.html#libcst.matchers.call_if_not_inside

仅当 it 或其父级之一与提供的匹配器不匹配时,才会调用用此装饰器装饰的方法。

将 f 字符串的所有引号从单引号转换为 ...,但保留嵌套的 f 字符串完整 翻译为 始终转换外部 f 字符串,然后在访问子 f 字符串时,不要转换它们。仅当同一变压器中有其他转换时才需要匹配器可装饰变压器实现,但类似这样的东西应该可以工作:

import libcst as cst
import libcst.matchers as m

class _FormattedStringEscapingTransformer(m.MatcherDecoratableTransformer):
    _escapee_fstring_node: cst.FormattedString | None = None

    @m.visit(m.FormattedString())
    def visit_f_string(self, node: cst.FormattedString) -> None:
        if self._escapee_fstring_node is None:
            self._escapee_fstring_node = node

    @m.leave(m.FormattedString())
    def escape_f_string(
        self, original_node: cst.FormattedString, updated_node: cst.FormattedString
    ) -> cst.FormattedString:
        if original_node is self._escapee_fstring_node:
            self._escapee_fstring_node = None
            return updated_node.with_changes(start='f"""', end='"""')
        return updated_node

>>> mod = cst.parse_module(
... '''
... f"Hello {developer_name}, My name is {_get_machine(f"{self.prop_a}, {self.prop_b}")}"
... '''
... )
...
>>> print(mod.visit(_FormattedStringEscapingTransformer()).code)
f"""Hello {developer_name}, My name is {_get_machine(f"{self.prop_a}, {self.prop_b}")}"""
© www.soinside.com 2019 - 2024. All rights reserved.