Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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,30 @@ 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));
// Detect BOM to determine the appropriate encoding
Encoding encoding = DetectEncoding(stream);
stream.Position = 0; // Reset stream position after BOM detection
return new SourceFile(filePath, SourceText.From(stream, encoding));
Comment thread
jjonescz marked this conversation as resolved.
Outdated
}

private static Encoding DetectEncoding(Stream stream)
{
// UTF-8 BOM is 0xEF 0xBB 0xBF
if (stream.Length < 3)
{
return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
}

#if NETCOREAPP
Span<byte> buffer = stackalloc byte[3];
int bytesRead = stream.Read(buffer);
#else
byte[] buffer = new byte[3];
Comment thread
jjonescz marked this conversation as resolved.
Outdated
int bytesRead = stream.Read(buffer, 0, 3);
#endif
bool hasUtf8Bom = bytesRead == 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF;

return new UTF8Encoding(encoderShouldEmitUTF8Identifier: hasUtf8Bom);
}

public SourceFile WithText(SourceText newText)
Expand All @@ -269,7 +292,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,63 @@ 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.
/// </summary>
Comment thread
jjonescz marked this conversation as resolved.
/// <see href="https://github.com/dotnet/sdk/issues/52054"/>
Comment thread
jjonescz marked this conversation as resolved.
[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.False(bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF,
Comment thread
jjonescz marked this conversation as resolved.
Outdated
"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);
Comment thread
jjonescz marked this conversation as resolved.
Outdated
}

/// <summary>
/// Verifies that files with UTF-8 BOM preserve it when saved.
/// </summary>
/// <see href="https://github.com/dotnet/sdk/issues/52054"/>
[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.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF,
Comment thread
jjonescz marked this conversation as resolved.
Outdated
"File should have UTF-8 BOM");
}
Comment thread
jjonescz marked this conversation as resolved.

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