isidentical / refactor

AST-based fragmental source code refactoring toolkit for Python
https://refactor.readthedocs.io
MIT License
439 stars 17 forks source link

Multiline strings get indented #74

Open alexmojaki opened 1 year ago

alexmojaki commented 1 year ago

This refactoring, similar to what I actually used:

import ast
import refactor

class WrapF(refactor.Rule):
    def match(self, node: ast.AST) -> refactor.BaseAction:
        assert isinstance(node, ast.Constant)

        # Prevent wrapping F-strings that are already wrapped in F()
        # Otherwise you get infinite F(F(F(F(...))))
        parent = self.context.ancestry.get_parent(node)
        assert not (isinstance(parent, ast.Call) and isinstance(parent.func, ast.Name) and parent.func.id == 'F')

        return refactor.Replace(node, ast.Call(func=ast.Name(id="F"), args=[node], keywords=[]))

refactor.run(rules=[WrapF])

produces this:

 def f():
-    return """
-a
-"""
+    return F("""
+    a
+    """)

This changes the value of the string.

Possibly related is https://github.com/isidentical/refactor/issues/12, but I couldn't reproduce an equivalent problem with just ast.unparse:

import ast

source = '''
def f():
    return """
a
"""
'''

tree = ast.parse(source)
node = tree.body[0].body[0].value
call = ast.Call(func=ast.Name(id="F"), args=[node], keywords=[])
ast.copy_location(call, node)
ast.fix_missing_locations(call)
print(ast.unparse(node))  # '\na\n'
print(ast.unparse(call))  # F('\na\n')
MementoRC commented 1 year ago

Interesting and not thought about in: https://github.com/isidentical/refactor/pull/66, which partially solves the issue with a line, but because it is f-string'd with parenthesis, still applies the indent for """ line, due to """ not matching """) - Looking into a workaround to improve the PR, maybe: