From 92f12aa0a2bf8a05066fb8109a99224ea2c605b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 20:00:04 +0000 Subject: [PATCH 1/8] Initial plan From 9b7a4f728296565d5aae74556098c6f2f72226d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 20:15:16 +0000 Subject: [PATCH 2/8] Fix UTF-8 BOM preservation in file-based programs - Modified SourceFile.Load() to detect UTF-8 BOM in files - Modified SourceFile.Save() to preserve original BOM state - Added HasUtf8Bom property to SourceFile struct - Updated InternalAPI.Unshipped.txt with new API members - Added tests to verify BOM preservation (no-BOM and with-BOM cases) Fixes issue where dotnet add package command was adding BOM to files that originally didn't have one, breaking shebang scripts. Co-authored-by: DamianEdwards <249088+DamianEdwards@users.noreply.github.com> --- .../FileLevelDirectiveHelpers.cs | 25 +++++-- .../InternalAPI.Unshipped.txt | 4 ++ .../Run/FileBasedAppSourceEditorTests.cs | 71 +++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs index 1157bc586313..9a71231ca949 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs @@ -253,23 +253,40 @@ public static ImmutableArray EvaluateDirectives( } } -internal readonly record struct SourceFile(string Path, SourceText Text) +internal readonly record struct SourceFile(string Path, SourceText Text, bool HasUtf8Bom = false) { public static SourceFile Load(string filePath) { using var stream = File.OpenRead(filePath); - return new SourceFile(filePath, SourceText.From(stream, Encoding.UTF8)); + bool hasUtf8Bom = DetectUtf8Bom(stream); + stream.Position = 0; // Reset stream position after BOM detection + return new SourceFile(filePath, SourceText.From(stream, Encoding.UTF8), hasUtf8Bom); + } + + private static bool DetectUtf8Bom(Stream stream) + { + // UTF-8 BOM is 0xEF 0xBB 0xBF + if (stream.Length < 3) + { + return false; + } + + byte[] buffer = new byte[3]; + int bytesRead = stream.Read(buffer, 0, 3); + return bytesRead == 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF; } public SourceFile WithText(SourceText newText) { - return new SourceFile(Path, newText); + return new SourceFile(Path, newText, HasUtf8Bom); } public void Save() { using var stream = File.Open(Path, FileMode.Create, FileAccess.Write); - using var writer = new StreamWriter(stream, Encoding.UTF8); + // Use UTF8Encoding with encoderShouldEmitUTF8Identifier set to match the original file + var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: HasUtf8Bom); + using var writer = new StreamWriter(stream, encoding); Text.Write(writer); } diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt index fad31237acf9..c87532ab1686 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt @@ -89,14 +89,18 @@ Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.TextSpan.init -> vo Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.SimpleDiagnostic() -> void Microsoft.DotNet.FileBasedPrograms.SourceFile Microsoft.DotNet.FileBasedPrograms.SourceFile.Deconstruct(out string! Path, out Microsoft.CodeAnalysis.Text.SourceText! Text) -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile.Deconstruct(out string! Path, out Microsoft.CodeAnalysis.Text.SourceText! Text, out bool HasUtf8Bom) -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.Equals(Microsoft.DotNet.FileBasedPrograms.SourceFile other) -> bool Microsoft.DotNet.FileBasedPrograms.SourceFile.GetFileLinePositionSpan(Microsoft.CodeAnalysis.Text.TextSpan span) -> Microsoft.CodeAnalysis.FileLinePositionSpan Microsoft.DotNet.FileBasedPrograms.SourceFile.GetLocationString(Microsoft.CodeAnalysis.Text.TextSpan span) -> string! +Microsoft.DotNet.FileBasedPrograms.SourceFile.HasUtf8Bom.get -> bool +Microsoft.DotNet.FileBasedPrograms.SourceFile.HasUtf8Bom.init -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.Path.get -> string! Microsoft.DotNet.FileBasedPrograms.SourceFile.Path.init -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.Save() -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.SourceFile() -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.SourceFile(string! Path, Microsoft.CodeAnalysis.Text.SourceText! Text) -> void +Microsoft.DotNet.FileBasedPrograms.SourceFile.SourceFile(string! Path, Microsoft.CodeAnalysis.Text.SourceText! Text, bool HasUtf8Bom = false) -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.Text.get -> Microsoft.CodeAnalysis.Text.SourceText! Microsoft.DotNet.FileBasedPrograms.SourceFile.Text.init -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.WithText(Microsoft.CodeAnalysis.Text.SourceText! newText) -> Microsoft.DotNet.FileBasedPrograms.SourceFile diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index f6b181615b72..47c198eb1c63 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -517,6 +517,77 @@ public void RemoveMultiple() """)); } + /// + /// Verifies that files without UTF-8 BOM don't get one added when saved. + /// This is critical for shebang (#!) scripts on Unix-like systems. + /// + [Fact] + public void PreservesNoBomEncoding() + { + var tempFile = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.cs"); + try + { + // 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, + "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); + } + finally + { + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } + } + } + + /// + /// Verifies that files with UTF-8 BOM preserve it when saved. + /// + [Fact] + public void PreservesBomEncoding() + { + var tempFile = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.cs"); + try + { + // 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, + "File should have UTF-8 BOM"); + } + finally + { + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } + } + } + private void Verify( string input, params ReadOnlySpan<(Action action, string expectedOutput)> verify) From 13dd3b6a35cf75cd3428045687bc58bd883fc2e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 20:56:25 +0000 Subject: [PATCH 3/8] Address PR feedback: use SourceText.Encoding and stackalloc - Use SourceText's Encoding property instead of custom HasUtf8Bom tracking - Use stackalloc for 3-byte buffer on .NET Core (with fallback for netstandard2.0) - Update tests to use _testAssetsManager.CreateTestDirectory() infrastructure - Add issue reference (https://github.com/dotnet/sdk/issues/52054) to test docs - Remove unnecessary API surface from InternalAPI.Unshipped.txt Co-authored-by: jjonescz <3669664+jjonescz@users.noreply.github.com> --- .../FileLevelDirectiveHelpers.cs | 26 +++-- .../InternalAPI.Unshipped.txt | 4 - .../Run/FileBasedAppSourceEditorTests.cs | 94 ++++++++----------- 3 files changed, 57 insertions(+), 67 deletions(-) diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs index 9a71231ca949..e194e9746476 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs @@ -253,39 +253,47 @@ public static ImmutableArray EvaluateDirectives( } } -internal readonly record struct SourceFile(string Path, SourceText Text, bool HasUtf8Bom = false) +internal readonly record struct SourceFile(string Path, SourceText Text) { public static SourceFile Load(string filePath) { using var stream = File.OpenRead(filePath); - bool hasUtf8Bom = DetectUtf8Bom(stream); + // 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.UTF8), hasUtf8Bom); + return new SourceFile(filePath, SourceText.From(stream, encoding)); } - private static bool DetectUtf8Bom(Stream stream) + private static Encoding DetectEncoding(Stream stream) { // UTF-8 BOM is 0xEF 0xBB 0xBF if (stream.Length < 3) { - return false; + return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); } +#if NETCOREAPP + Span buffer = stackalloc byte[3]; + int bytesRead = stream.Read(buffer); +#else byte[] buffer = new byte[3]; int bytesRead = stream.Read(buffer, 0, 3); - return bytesRead == 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF; +#endif + bool hasUtf8Bom = bytesRead == 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF; + + return new UTF8Encoding(encoderShouldEmitUTF8Identifier: hasUtf8Bom); } public SourceFile WithText(SourceText newText) { - return new SourceFile(Path, newText, HasUtf8Bom); + return new SourceFile(Path, newText); } public void Save() { using var stream = File.Open(Path, FileMode.Create, FileAccess.Write); - // Use UTF8Encoding with encoderShouldEmitUTF8Identifier set to match the original file - var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: HasUtf8Bom); + // 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); } diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt index c87532ab1686..fad31237acf9 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt @@ -89,18 +89,14 @@ Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.Position.TextSpan.init -> vo Microsoft.DotNet.FileBasedPrograms.SimpleDiagnostic.SimpleDiagnostic() -> void Microsoft.DotNet.FileBasedPrograms.SourceFile Microsoft.DotNet.FileBasedPrograms.SourceFile.Deconstruct(out string! Path, out Microsoft.CodeAnalysis.Text.SourceText! Text) -> void -Microsoft.DotNet.FileBasedPrograms.SourceFile.Deconstruct(out string! Path, out Microsoft.CodeAnalysis.Text.SourceText! Text, out bool HasUtf8Bom) -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.Equals(Microsoft.DotNet.FileBasedPrograms.SourceFile other) -> bool Microsoft.DotNet.FileBasedPrograms.SourceFile.GetFileLinePositionSpan(Microsoft.CodeAnalysis.Text.TextSpan span) -> Microsoft.CodeAnalysis.FileLinePositionSpan Microsoft.DotNet.FileBasedPrograms.SourceFile.GetLocationString(Microsoft.CodeAnalysis.Text.TextSpan span) -> string! -Microsoft.DotNet.FileBasedPrograms.SourceFile.HasUtf8Bom.get -> bool -Microsoft.DotNet.FileBasedPrograms.SourceFile.HasUtf8Bom.init -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.Path.get -> string! Microsoft.DotNet.FileBasedPrograms.SourceFile.Path.init -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.Save() -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.SourceFile() -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.SourceFile(string! Path, Microsoft.CodeAnalysis.Text.SourceText! Text) -> void -Microsoft.DotNet.FileBasedPrograms.SourceFile.SourceFile(string! Path, Microsoft.CodeAnalysis.Text.SourceText! Text, bool HasUtf8Bom = false) -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.Text.get -> Microsoft.CodeAnalysis.Text.SourceText! Microsoft.DotNet.FileBasedPrograms.SourceFile.Text.init -> void Microsoft.DotNet.FileBasedPrograms.SourceFile.WithText(Microsoft.CodeAnalysis.Text.SourceText! newText) -> Microsoft.DotNet.FileBasedPrograms.SourceFile diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index 47c198eb1c63..92d39bce1794 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -521,71 +521,57 @@ public void RemoveMultiple() /// Verifies that files without UTF-8 BOM don't get one added when saved. /// This is critical for shebang (#!) scripts on Unix-like systems. /// + /// [Fact] public void PreservesNoBomEncoding() { - var tempFile = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.cs"); - try - { - // 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, - "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); - } - finally - { - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - } + 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, + "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); } /// /// Verifies that files with UTF-8 BOM preserve it when saved. /// + /// [Fact] public void PreservesBomEncoding() { - var tempFile = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.cs"); - try - { - // 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, - "File should have UTF-8 BOM"); - } - finally - { - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - } + 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, + "File should have UTF-8 BOM"); } private void Verify( From c061b9e5907f6987d4b5875c8b7d22f6325faa0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 21:25:12 +0000 Subject: [PATCH 4/8] Simplify encoding detection using SourceText auto-detection - Remove custom DetectEncoding() method and use SourceText.From(stream, encoding: null) - SourceText automatically detects encoding including BOM when encoding is null - Move tags inside elements for better documentation - Simplifies implementation while maintaining full functionality Co-authored-by: jjonescz <3669664+jjonescz@users.noreply.github.com> --- .../FileLevelDirectiveHelpers.cs | 26 ++----------------- .../Run/FileBasedAppSourceEditorTests.cs | 4 +-- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs index e194e9746476..0dcd623c93d4 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs @@ -258,30 +258,8 @@ internal readonly record struct SourceFile(string Path, SourceText Text) public static SourceFile Load(string filePath) { using var stream = File.OpenRead(filePath); - // 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)); - } - - 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 buffer = stackalloc byte[3]; - int bytesRead = stream.Read(buffer); -#else - byte[] buffer = new byte[3]; - 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); + // Let SourceText.From auto-detect the encoding (including BOM detection) + return new SourceFile(filePath, SourceText.From(stream, encoding: null)); } public SourceFile WithText(SourceText newText) diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index 92d39bce1794..19692e1c29ce 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -520,8 +520,8 @@ public void RemoveMultiple() /// /// Verifies that files without UTF-8 BOM don't get one added when saved. /// This is critical for shebang (#!) scripts on Unix-like systems. - /// /// + /// [Fact] public void PreservesNoBomEncoding() { @@ -550,8 +550,8 @@ public void PreservesNoBomEncoding() /// /// Verifies that files with UTF-8 BOM preserve it when saved. - /// /// + /// [Fact] public void PreservesBomEncoding() { From 643569f6200e815551cd8da7048b255031fe1a02 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:53:30 +0000 Subject: [PATCH 5/8] Add test for non-UTF-8 encoding preservation - Add PreservesNonUtf8Encoding test to verify UTF-16 encoding is preserved - Tests that files with UTF-16 LE encoding (with BOM 0xFF 0xFE) maintain their encoding - Ensures SourceText's auto-detection works for encodings beyond UTF-8 - All encoding preservation tests now pass (UTF-8 no BOM, UTF-8 with BOM, UTF-16) Co-authored-by: jjonescz <3669664+jjonescz@users.noreply.github.com> --- .../Run/FileBasedAppSourceEditorTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index 19692e1c29ce..67209a6b9ac3 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -574,6 +574,37 @@ public void PreservesBomEncoding() "File should have UTF-8 BOM"); } + /// + /// Verifies that files with non-UTF-8 encodings (like UTF-16) preserve their encoding when saved. + /// + /// + [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.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 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 action, string expectedOutput)> verify) From 07074b66d05cbcf5c532da562cbb641e3067c943 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 11 Dec 2025 11:32:59 +0100 Subject: [PATCH 6/8] Use collection expressions in asserts Co-authored-by: Fred Silberberg --- .../CommandTests/Run/FileBasedAppSourceEditorTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index 67209a6b9ac3..b34ce92dfd96 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -540,7 +540,7 @@ public void PreservesNoBomEncoding() // Verify no BOM was added var bytes = File.ReadAllBytes(tempFile); - Assert.False(bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF, + Assert.True(bytes is not [0xEF, 0xBB, 0xBF, ..], "File should not have UTF-8 BOM"); // Verify shebang is still first @@ -570,7 +570,7 @@ public void PreservesBomEncoding() // Verify BOM is still present var bytes = File.ReadAllBytes(tempFile); - Assert.True(bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF, + Assert.True(bytes is [0xEF, 0xBB, 0xBF, ..], "File should have UTF-8 BOM"); } @@ -596,7 +596,7 @@ public void PreservesNonUtf8Encoding() // Verify UTF-16 BOM is still present (0xFF 0xFE for UTF-16 LE) var bytes = File.ReadAllBytes(tempFile); - Assert.True(bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE, + Assert.True(bytes is [0xFF, 0xFE, ..], "File should have UTF-16 LE BOM"); // Verify content is still readable as UTF-16 From 1f177e939858dfd38256374bdd2a45cc5fabf1c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:33:13 +0000 Subject: [PATCH 7/8] Verify complete file content in PreservesNoBomEncoding test - Replace Assert.StartsWith with Assert.Equal to verify the entire file content - Ensures the test validates that Save() correctly writes the complete content - Makes the test more robust by checking the full output including the added package directive Co-authored-by: jjonescz <3669664+jjonescz@users.noreply.github.com> --- .../CommandTests/Run/FileBasedAppSourceEditorTests.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index b34ce92dfd96..bae5915e4378 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -543,9 +543,16 @@ public void PreservesNoBomEncoding() Assert.True(bytes is not [0xEF, 0xBB, 0xBF, ..], "File should not have UTF-8 BOM"); - // Verify shebang is still first + // Verify the complete file content is correct var savedContent = File.ReadAllText(tempFile); - Assert.StartsWith("#!/usr/bin/env dotnet run", savedContent); + var expectedContent = """ + #!/usr/bin/env dotnet run + + #:package MyPackage@1.0.0 + + Console.WriteLine(); + """; + Assert.Equal(expectedContent, savedContent); } /// From a4b9dffa7719c6f214b4eb0a67e3cb8b77c833e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:04:52 +0000 Subject: [PATCH 8/8] Fix test to use explicit newlines instead of raw string literal - Replace raw string literal with explicit \n characters in expected content - Raw string literals with indentation were causing blank lines to be stripped - Explicit newlines ensure consistent comparison across platforms - Fixes test failure on Windows where line endings differ Co-authored-by: jjonescz <3669664+jjonescz@users.noreply.github.com> --- .../CommandTests/Run/FileBasedAppSourceEditorTests.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs index bae5915e4378..191276f6a03a 100644 --- a/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs @@ -545,13 +545,7 @@ public void PreservesNoBomEncoding() // Verify the complete file content is correct var savedContent = File.ReadAllText(tempFile); - var expectedContent = """ - #!/usr/bin/env dotnet run - - #:package MyPackage@1.0.0 - - Console.WriteLine(); - """; + var expectedContent = "#!/usr/bin/env dotnet run\n\n#:package MyPackage@1.0.0\n\nConsole.WriteLine();"; Assert.Equal(expectedContent, savedContent); }