Skip to content

Formatter adds extra carriage return to multiline f-string debug replacements #18667

@MeGaGiGaGon

Description

@MeGaGiGaGon

Summary

I noticed this while messing around on the playground, but it looks like formatting adds an extra \r to multiline f-string debug replacements. In combination with another software/editor that normalizes \r to \r\n, this causes the file to grow on every formatting.

The code I've been using to test this is

f"{
1=
}"

Where the line endings are \r\n

One example of this is on the playground. If you keep copying the output to the input, it keeps growing.
first step
second step
third step

Another example is inside VSCode, if you have line endings set to CRLF. After file modification and save inside VSC it normalizes the newlines, so by running ruff format -> do a edit like adding a 1 to the end of the file and save in VSC -> run ruff format -> repeat, the code grows every time.

I also made a powershell script to demonstrate this, where it replaces \r with \r\n to show the growth. (I tried to make a bash script, but I'm both not familiar enough with bash and it feels like every built in tool/command nukes/normalizes newlines).

PS script
Set-Content "issue.py" -Value "f`"{`r`n1=`r`n}`""
echo "issue.py content"
Get-Content issue.py -Raw | ForEach-Object { $_ -replace "\n", "\n"} | ForEach-Object { $_ -replace "\r", "\r"}
echo "Running ruff format"
uvx ruff format issue.py
echo "issue.py content"
Get-Content issue.py -Raw | ForEach-Object { $_ -replace "\n", "\n"} | ForEach-Object { $_ -replace "\r", "\r"}
echo "Newline normalization"
$edited = Get-Content issue.py -Raw | ForEach-Object { $_ -replace "\r(?!\n)", "`r`n" }
Set-Content "issue.py" $edited
echo "issue.py content"
Get-Content issue.py -Raw | ForEach-Object { $_ -replace "\n", "\n"} | ForEach-Object { $_ -replace "\r", "\r"}
echo "Running ruff format"
uvx ruff format issue.py
echo "issue.py content"
Get-Content issue.py -Raw | ForEach-Object { $_ -replace "\n", "\n"} | ForEach-Object { $_ -replace "\r", "\r"}
echo "Newline normalization"
$edited = Get-Content issue.py -Raw | ForEach-Object { $_ -replace "\r(?!\n)", "`r`n" }
Set-Content "issue.py" $edited
echo "issue.py content"
Get-Content issue.py -Raw | ForEach-Object { $_ -replace "\n", "\n"} | ForEach-Object { $_ -replace "\r", "\r"}

Output of script:

PS > ./issue.ps1
issue.py content
f"{\r\n1=\r\n}"\r\n
Running ruff format
1 file reformatted
issue.py content
f"{\r\r\n1=\r\r\n}"\r\n
Newline normalization
issue.py content
f"{\r\n\r\n1=\r\n\r\n}"\r\n\r\n
Running ruff format
1 file reformatted
issue.py content
f"{\r\r\n\r\r\n1=\r\r\n\r\r\n}"\r\n
Newline normalization
issue.py content
f"{\r\n\r\n\r\n\r\n1=\r\n\r\n\r\n\r\n}"\r\n\r\n

This only seems to happen when the f-string has a debug expression.

This also happens with t-strings

Version

ruff 0.11.13 (5faf72a 2025-06-05) + playground

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingformatterRelated to the formatter

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions