dotnet format on Windows partially rewrites LF-only C# files to CRLF
- Dominant language
- C#
- Stars
- 3.2k
- Forks
- 1.3k
- PR merge metrics
- PR metrics pending
Description
### Describe the bug
`dotnet format` on Windows can partially rewrite an LF-only, UTF-8 BOM C# file to CRLF, producing mixed line endings instead of preserving LF.
This is a reduced fresh-repo repro that appears suitable for upstream investigation. `Class1.cs` starts as UTF-8 with BOM and LF on every line, `.gitattributes` forces LF for `*.cs`, and `.editorconfig` intentionally does **not** set `end_of_line`. Running the command below rewrites only lines 2-3 of `Class1.cs` to CRLF while leaving the rest of the file LF-only, so the output becomes mixed-EOL. Pre-existing mixed line endings are not required. Removing the `// c` comment line makes the repro disappear.
### To Reproduce
1. Create a fresh directory or repo.
2. Create the four files below exactly as shown.
3. Run:
```bash
dotnet format MiniRepro.csproj --include Class1.cs --diagnostics IDE0055 --severity info
```
4. Inspect `Class1.cs` after formatting.
#### Minimal repro files
`MiniRepro.csproj`
```xml
net8.0
```
`.gitattributes`
```gitattributes
* text=auto eol=lf
*.cs text eol=lf
*.csproj text eol=lf
*.editorconfig text eol=lf
```
`.editorconfig`
```editorconfig
root = true
[*.cs]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
csharp_new_line_before_open_brace = none
```
`Class1.cs` before formatting (text, BOM present, line endings shown explicitly)
```text
1: class C {␊
2: static object f = (System.Func)(x => x␊
3: // c␊
4: .A());␊
5: C A() => this;␊
6: }␊
```
`Class1.cs` before formatting (HEX)
```text
EF BB BF 63 6C 61 73 73 20 43 20 7B 0A
20 20 20 20 73 74 61 74 69 63 20 6F 62 6A 65 63 74 20 66 20 3D 20 28 53 79 73 74 65 6D 2E 46 75 6E 63 3C 43 2C 20 43 3E 29 28 78 20 3D 3E 20 78 0A
20 20 20 20 20 20 20 20 2F 2F 20 63 0A
20 20 20 20 20 20 20 20 2E 41 28 29 29 3B 0A
20 20 20 20 43 20 41 28 29 20 3D 3E 20 74 68 69 73 3B 0A
7D 0A
```
#### Exact Bash commands to recreate the files byte-for-byte
```bash
mkdir MiniRepro
cd MiniRepro
git init
python3 - <<'PY'
from pathlib import Path
files = {
"MiniRepro.csproj": "3c50726f6a6563742053646b3d224d6963726f736f66742e4e45542e53646b223e0a20203c50726f706572747947726f75703e0a202020203c5461726765744672616d65776f726b3e6e6574382e303c2f5461726765744672616d65776f726b3e0a20203c2f50726f706572747947726f75703e0a3c2f50726f6a6563743e0a",
".gitattributes": "2a20746578743d6175746f20656f6c3d6c660a2a2e6373207465787420656f6c3d6c660a2a2e637370726f6a207465787420656f6c3d6c660a2a2e656469746f72636f6e666967207465787420656f6c3d6c660a",
".editorconfig": "726f6f74203d20747275650a0a5b2a2e63735d0a696e64656e745f73697a65203d20340a696e736572745f66696e616c5f6e65776c696e65203d20747275650a63686172736574203d207574662d382d626f6d0a6373686172705f6e65775f6c696e655f6265666f72655f6f70656e5f6272616365203d206e6f6e650a",
"Class1.cs": "efbbbf636c6173732043207b0a20202020737461746963206f626a6563742066203d202853797374656d2e46756e633c432c20433e292878203d3e20780a20202020202020202f2f20630a20202020202020202e412829293b0a202020204320412829203d3e20746869733b0a7d0a",
}
for name, hex_data in files.items():
Path(name).write_bytes(bytes.fromhex(hex_data))
PY
dotnet format MiniRepro.csproj --include Class1.cs --diagnostics IDE0055 --severity info
```
#### Exact PowerShell commands to recreate the files byte-for-byte
```powershell
New-Item -ItemType Directory -Path MiniRepro | Out-Null
Set-Location MiniRepro
git init | Out-Null
function Write-HexFile([string] $Path, [string] $Hex) {
$bytes = for ($i = 0; $i -lt $Hex.Length; $i += 2) {
[Convert]::ToByte($Hex.Substring($i, 2), 16)
}
[System.IO.File]::WriteAllBytes((Join-Path (Get-Location) $Path), [byte[]] $bytes)
}
Write-HexFile 'MiniRepro.csproj' '3c50726f6a6563742053646b3d224d6963726f736f66742e4e45542e53646b223e0a20203c50726f706572747947726f75703e0a202020203c5461726765744672616d65776f726b3e6e6574382e303c2f5461726765744672616d65776f726b3e0a20203c2f50726f706572747947726f75703e0a3c2f50726f6a6563743e0a'
Write-HexFile '.gitattributes' '2a20746578743d6175746f20656f6c3d6c660a2a2e6373207465787420656f6c3d6c660a2a2e637370726f6a207465787420656f6c3d6c660a2a2e656469746f72636f6e666967207465787420656f6c3d6c660a'
Write-HexFile '.editorconfig' '726f6f74203d20747275650a0a5b2a2e63735d0a696e64656e745f73697a65203d20340a696e736572745f66696e616c5f6e65776c696e65203d20747275650a63686172736574203d207574662d382d626f6d0a6373686172705f6e65775f6c696e655f6265666f72655f6f70656e5f6272616365203d206e6f6e650a'
Write-HexFile 'Class1.cs' 'efbbbf636c6173732043207b0a20202020737461746963206f626a6563742066203d202853797374656d2e46756e633c432c20433e292878203d3e20780a20202020202020202f2f20630a20202020202020202e412829293b0a202020204320412829203d3e20746869733b0a7d0a'
dotnet format MiniRepro.csproj --include Class1.cs --diagnostics IDE0055 --severity info
```
#### Expected result
The file should remain UTF-8 with BOM and LF on every line. In this reduced repro, the expected output is byte-for-byte identical to the input.
Expected text (line endings shown explicitly):
```text
1: class C {␊
2: static object f = (System.Func)(x => x␊
3: // c␊
4: .A());␊
5: C A() => this;␊
6: }␊
```
Expected HEX:
```text
EF BB BF 63 6C 61 73 73 20 43 20 7B 0A
20 20 20 20 73 74 61 74 69 63 20 6F 62 6A 65 63 74 20 66 20 3D 20 28 53 79 73 74 65 6D 2E 46 75 6E 63 3C 43 2C 20 43 3E 29 28 78 20 3D 3E 20 78 0A
20 20 20 20 20 20 20 20 2F 2F 20 63 0A
20 20 20 20 20 20 20 20 2E 41 28 29 29 3B 0A
20 20 20 20 43 20 41 28 29 20 3D 3E 20 74 68 69 73 3B 0A
7D 0A
```
#### Actual result
After formatting on Windows, only lines 2-3 are rewritten from LF to CRLF. The file started LF-only, but the formatted output becomes mixed-EOL.
Actual text (line endings shown explicitly):
```text
1: class C {␊
2: static object f = (System.Func)(x => x␍␊ <-- unexpected
3: // c␍␊ <-- unexpected
4: .A());␊
5: C A() => this;␊
6: }␊
```
Actual HEX:
```text
EF BB BF 63 6C 61 73 73 20 43 20 7B 0A
20 20 20 20 73 74 61 74 69 63 20 6F 62 6A 65 63 74 20 66 20 3D 20 28 53 79 73 74 65 6D 2E 46 75 6E 63 3C 43 2C 20 43 3E 29 28 78 20 3D 3E 20 78 0D 0A
20 20 20 20 20 20 20 20 2F 2F 20 63 0D 0A
20 20 20 20 20 20 20 20 2E 41 28 29 29 3B 0A
20 20 20 20 43 20 41 28 29 20 3D 3E 20 74 68 69 73 3B 0A
7D 0A
```
Diff highlight:
- Line 2 terminator changes from `0A` to `0D 0A`.
- Line 3 terminator changes from `0A` to `0D 0A`.
- Lines 1 and 4-6 remain `0A`.
- The command exits with code `0` and, in this repro, produces no exception or diagnostic explaining the partial LF -> CRLF rewrite.
### Exceptions (if any)
None. The command completes successfully, but the resulting file has mixed line endings.
### Further technical details
details of dotnet --info
```text
.NET SDK:
Version: 10.0.202
Commit: 1e7d5a8ae3
Workload version: 10.0.200-manifests.d9cd7ded
MSBuild version: 18.3.3+1e7d5a8ae
Runtime Environment:
OS Name: Windows
OS Version: 10.0.26200
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\10.0.202\
Host:
Version: 10.0.6
Architecture: x64
Commit: 47fb725acf
.NET SDKs installed:
8.0.420 [C:\Program Files\dotnet\sdk]
10.0.202 [C:\Program Files\dotnet\sdk]
```
- The IDE (VS / VS Code/ VS4Mac) you're running on, and its version: Not IDE-specific. Reproduced from the CLI with `dotnet format` on Windows; no IDE is required for the repro.
- `dotnet format --version`: `10.0.202-servicing.26180.118+1e7d5a8ae309af8fad1826a5e968aae30719a281`
- Environments / version combinations tried:
- Windows x64 (OS version `10.0.26200`) + .NET SDK `10.0.202` + `dotnet format` `10.0.202-servicing.26180.118+1e7d5a8ae309af8fad1826a5e968aae30719a281`: fresh minimal repro confirmed.
- On the broader real-world repo scenario from which this was reduced, SDK drift testing between `8.0.420` and `10.0.202` did not show a meaningful output difference.
- Additional information:
- This minimal repro was reduced from a larger real-world case originally observed in `Yafc.UI/Core/Logging.cs` during a PR #579 investigation in another repository.
- Removing the comment line (`// c`) makes the repro disappear.
- Pre-existing mixed line endings are not required.
- Fresh synthetic mixed-EOL seeds by themselves did not trigger the bug; the reduced sample above plus the listed config context did.
- Related issue: #52249 looks like a strong near-match, but this report is not intended as an exact duplicate claim. This issue specifically documents a partial LF -> CRLF rewrite that leaves a previously LF-only file in a mixed-EOL state.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.