0

I want to create a transformer that converts all quotes of f-strings from a single quote to triple quotes, but leaves nested f-strings intact.

For example, the next expression left intact.

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

But, the transformer result is:

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

I tried the following matchers but without success:

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='"""')

None of them worked.

What is the correct matcher to exclude transformation if inner f-strings expressions?

1 Answer 1

1

I don't think call_if_[not_]inside is the correct tool to do this - that will always invoke a method when a node or its parents has the matching condition, not just its parents:

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

A method that is decorated with this decorator will only be called if it or one of its parents does not match the supplied matcher.

Convert all quotes of f-strings from a single quote to ..., but leave nested f-strings intact translates to always transform an outer f-string, then when leaving child f-strings, don't transform them. A matcher decoratable transformer implementation is only necessary if you have other f-string transforms in the same transformer, but something like this should work:

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}")}"""
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.