Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/flynt/code_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from flynt.utils.utils import (
apply_unicode_escape_map,
contains_comment,
preserve_escaped_newlines,
unicode_escape_map,
)

Expand Down Expand Up @@ -176,6 +177,7 @@ def try_chunk(self, chunk: AstChunk) -> None:
if changed and escape_map and not is_raw:
converted = apply_unicode_escape_map(converted, escape_map)
if changed:
converted = preserve_escaped_newlines(snippet, converted)
contract_lines = chunk.n_lines - 1
if contract_lines == 0:
line = self.src_lines[chunk.start_line]
Expand Down Expand Up @@ -218,7 +220,9 @@ def maybe_replace(
- self._byte_to_char_idx(chunk.start_line, chunk.start_idx)
for line in lines
)
converted = converted.replace("\\n", "\n")
snippet_text = self.code_in_chunk(chunk)
if "\\n" not in snippet_text:
converted = converted.replace("\\n", "\n")
else:
lines_fit = len(
f"{converted}{rest}"
Expand Down
27 changes: 27 additions & 0 deletions src/flynt/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,30 @@ def repl(match: re.Match[str]) -> str:
def contains_unicode_escape(code: str) -> bool:
"""Return ``True`` if ``code`` contains unicode or octal escape sequences."""
return bool(unicode_escape_re.search(code))


def preserve_escaped_newlines(original: str, converted: str) -> str:
"""Restore backslash newline escapes lost during AST transformation."""
orig_lines = original.splitlines()
conv_lines = converted.splitlines()
if not orig_lines or not conv_lines:
return converted
if orig_lines[0].rstrip().endswith("\\") and not conv_lines[0].rstrip().endswith(
"\\"
):
m = re.match(r"(\s*[furbFURB]*[\'\"]{3})", conv_lines[0])
if m:
prefix = m.group(1)
rest = conv_lines[0][len(prefix) :].lstrip()
if len(orig_lines) > 1:
indent = len(orig_lines[1]) - len(orig_lines[1].lstrip())
else:
indent = len(prefix) - len(prefix.lstrip())
conv_lines[0] = prefix + "\\"
if rest:
conv_lines.insert(1, " " * indent + rest)
for idx, line in enumerate(orig_lines[1:], start=1):
if line.strip() == "\\":
indent = len(line) - len(line.lstrip())
conv_lines.insert(idx, " " * indent + "\\")
return "\n".join(conv_lines)
31 changes: 31 additions & 0 deletions test/integration/expected_out_single_line/escaped_newline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from textwrap import dedent

def f():
arg = "text"
return dedent(
"""\
\
some {}
lorem ipsum
""".format(arg)
)


def f_extra():
arg = "text"
return """\
some {}\n""".format(arg)


def f_multiple():
arg = "text"
return """\
\
\
some {}\n""".format(arg)


def f_single_quotes():
arg = "text"
return '''\
some {}\n'''.format(arg)
2 changes: 0 additions & 2 deletions test/integration/test_issue83.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import pytest
from functools import partial

from test.integration.utils import try_on_file
from flynt.code_editor import fstringify_code_by_line


@pytest.mark.xfail(reason="newline escapes lost, issue #83")
def test_escaped_newline(state):
out, expected = try_on_file(
"escaped_newline.py",
Expand Down
1 change: 0 additions & 1 deletion test/integration/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
EXCLUDED = {
"bom.py",
"class.py",
"escaped_newline.py", # not supported yet, #83 on github
"multiline_limit.py",
}
samples = {p.name for p in (int_test_path / "samples_in").glob("*.py")} - EXCLUDED
Expand Down
Loading