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
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ internal readonly record struct SourceFile(string Path, SourceText Text)
public static SourceFile Load(string filePath)
{
using var stream = File.OpenRead(filePath);
return new SourceFile(filePath, SourceText.From(stream, Encoding.UTF8));
// Let SourceText.From auto-detect the encoding (including BOM detection)
return new SourceFile(filePath, SourceText.From(stream, encoding: null));
}

public SourceFile WithText(SourceText newText)
Expand All @@ -269,7 +270,9 @@ public SourceFile WithText(SourceText newText)
public void Save()
{
using var stream = File.Open(Path, FileMode.Create, FileAccess.Write);
using var writer = new StreamWriter(stream, Encoding.UTF8);
// Use the encoding from SourceText, which preserves the original BOM state
var encoding = Text.Encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
using var writer = new StreamWriter(stream, encoding);
Text.Write(writer);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,94 @@ public void RemoveMultiple()
"""));
}

/// <summary>
/// Verifies that files without UTF-8 BOM don't get one added when saved.
/// This is critical for shebang (#!) scripts on Unix-like systems.
/// <see href="https://github.com/dotnet/sdk/issues/52054"/>
/// </summary>
[Fact]
public void PreservesNoBomEncoding()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
var tempFile = Path.Join(testInstance.Path, "test.cs");

// Create a file without BOM
var content = "#!/usr/bin/env dotnet run\nConsole.WriteLine();";
File.WriteAllText(tempFile, content, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));

// Load, modify, and save
var sourceFile = SourceFile.Load(tempFile);
var editor = FileBasedAppSourceEditor.Load(sourceFile);
editor.Add(new CSharpDirective.Package(default) { Name = "MyPackage", Version = "1.0.0" });
editor.SourceFile.Save();

// Verify no BOM was added
var bytes = File.ReadAllBytes(tempFile);
Assert.True(bytes is not [0xEF, 0xBB, 0xBF, ..],
"File should not have UTF-8 BOM");

// Verify shebang is still first
var savedContent = File.ReadAllText(tempFile);
Assert.StartsWith("#!/usr/bin/env dotnet run", savedContent);
}

/// <summary>
/// Verifies that files with UTF-8 BOM preserve it when saved.
/// <see href="https://github.com/dotnet/sdk/issues/52054"/>
/// </summary>
[Fact]
public void PreservesBomEncoding()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
var tempFile = Path.Join(testInstance.Path, "test.cs");

// Create a file with BOM
var content = "Console.WriteLine();";
File.WriteAllText(tempFile, content, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));

// Load, modify, and save
var sourceFile = SourceFile.Load(tempFile);
var editor = FileBasedAppSourceEditor.Load(sourceFile);
editor.Add(new CSharpDirective.Package(default) { Name = "MyPackage", Version = "1.0.0" });
editor.SourceFile.Save();

// Verify BOM is still present
var bytes = File.ReadAllBytes(tempFile);
Assert.True(bytes is [0xEF, 0xBB, 0xBF, ..],
"File should have UTF-8 BOM");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to verify the behavior when original file uses some other encoding besides UTF-8.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 643569f. The new PreservesNonUtf8Encoding test verifies that UTF-16 LE encoding (with BOM 0xFF 0xFE) is preserved correctly. SourceText's auto-detection handles various encodings, not just UTF-8.


/// <summary>
/// Verifies that files with non-UTF-8 encodings (like UTF-16) preserve their encoding when saved.
/// <see href="https://github.com/dotnet/sdk/issues/52054"/>
/// </summary>
[Fact]
public void PreservesNonUtf8Encoding()
{
var testInstance = _testAssetsManager.CreateTestDirectory();
var tempFile = Path.Join(testInstance.Path, "test.cs");

// Create a file with UTF-16 encoding (includes BOM by default)
var content = "Console.WriteLine(\"UTF-16 test\");";
File.WriteAllText(tempFile, content, Encoding.Unicode);

// Load, modify, and save
var sourceFile = SourceFile.Load(tempFile);
var editor = FileBasedAppSourceEditor.Load(sourceFile);
editor.Add(new CSharpDirective.Package(default) { Name = "MyPackage", Version = "1.0.0" });
editor.SourceFile.Save();

// Verify UTF-16 BOM is still present (0xFF 0xFE for UTF-16 LE)
var bytes = File.ReadAllBytes(tempFile);
Assert.True(bytes is [0xFF, 0xFE, ..],
"File should have UTF-16 LE BOM");

// Verify content is still readable as UTF-16
var savedContent = File.ReadAllText(tempFile, Encoding.Unicode);
Assert.Contains("#:package MyPackage@1.0.0", savedContent);
Assert.Contains("Console.WriteLine", savedContent);
}

private void Verify(
string input,
params ReadOnlySpan<(Action<FileBasedAppSourceEditor> action, string expectedOutput)> verify)
Expand Down
Loading