diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs
index e13bc0a6fd9..a38b2746682 100644
--- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs
+++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs
@@ -14,12 +14,17 @@
namespace NuGetUpdater.Core.Test.Run;
+using static NuGetUpdater.Core.Utilities.EOLHandling;
+
using TestFile = (string Path, string Content);
public class RunWorkerTests
{
- [Fact]
- public async Task UpdateSinglePackageProducedExpectedAPIMessages()
+ [Theory]
+ [InlineData(EOLType.CR)]
+ [InlineData(EOLType.LF)]
+ [InlineData(EOLType.CRLF)]
+ public async Task UpdateSinglePackageProducedExpectedAPIMessages(EOLType EOL)
{
await RunAsync(
packages: [],
@@ -43,7 +48,7 @@ await RunAsync(
- """)
+ """.SetEOL(EOL))
],
discoveryWorker: new TestDiscoveryWorker(_input =>
{
@@ -94,7 +99,7 @@ await File.WriteAllTextAsync(projectPath, """
- """);
+ """.SetEOL(EOL));
return new UpdateOperationResult();
}),
expectedResult: new RunResult()
@@ -114,7 +119,7 @@ await File.WriteAllTextAsync(projectPath, """
- """))
+ """.SetEOL(EOL)))
}
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -199,7 +204,7 @@ await File.WriteAllTextAsync(projectPath, """
- """,
+ """.SetEOL(EOL),
},
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -212,11 +217,14 @@ await File.WriteAllTextAsync(projectPath, """
);
}
- [Fact]
- public async Task UpdateHandlesSemicolonsInPackageReference()
+ [Theory]
+ [InlineData(EOLType.CR)]
+ [InlineData(EOLType.LF)]
+ [InlineData(EOLType.CRLF)]
+ public async Task UpdateHandlesSemicolonsInPackageReference(EOLType EOL)
{
- var repoMetadata = XElement.Parse("""""");
- var repoMetadata2 = XElement.Parse("""""");
+ var repoMetadata = XElement.Parse("""""".SetEOL(EOL));
+ var repoMetadata2 = XElement.Parse("""""".SetEOL(EOL));
await RunAsync(
packages:
[
@@ -246,7 +254,7 @@ await RunAsync(
- """)
+ """.SetEOL(EOL))
],
discoveryWorker: new TestDiscoveryWorker(_input =>
{
@@ -299,7 +307,7 @@ await File.WriteAllTextAsync(projectPath, """
- """);
+ """.SetEOL(EOL));
return new UpdateOperationResult();
}),
expectedResult: new RunResult()
@@ -319,7 +327,7 @@ await File.WriteAllTextAsync(projectPath, """
- """))
+ """.SetEOL(EOL)))
}
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -447,7 +455,7 @@ await File.WriteAllTextAsync(projectPath, """
- """,
+ """.SetEOL(EOL),
}
],
@@ -461,8 +469,11 @@ await File.WriteAllTextAsync(projectPath, """
);
}
- [Fact]
- public async Task PrivateSourceAuthenticationFailureIsForwaredToApiHandler()
+ [Theory]
+ [InlineData(EOLType.CR)]
+ [InlineData(EOLType.LF)]
+ [InlineData(EOLType.CRLF)]
+ public async Task PrivateSourceAuthenticationFailureIsForwaredToApiHandler(EOLType EOL)
{
await RunAsync(
packages:
@@ -486,7 +497,7 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("project.csproj", """
@@ -496,7 +507,7 @@ await RunAsync(
- """)
+ """.SetEOL(EOL))
],
discoveryWorker: new TestDiscoveryWorker((_input) =>
{
@@ -517,11 +528,14 @@ await RunAsync(
);
}
- [Fact]
- public async Task UpdateHandlesPackagesConfigFiles()
+ [Theory]
+ [InlineData(EOLType.CR)]
+ [InlineData(EOLType.LF)]
+ [InlineData(EOLType.CRLF)]
+ public async Task UpdateHandlesPackagesConfigFiles(EOLType EOL)
{
- var repoMetadata = XElement.Parse("""""");
- var repoMetadata2 = XElement.Parse("""""");
+ var repoMetadata = XElement.Parse("""""".SetEOL(EOL));
+ var repoMetadata2 = XElement.Parse("""""".SetEOL(EOL));
await RunAsync(
packages:
[
@@ -551,13 +565,13 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("some-dir/packages.config", """
- """),
+ """.SetEOL(EOL)),
],
discoveryWorker: new TestDiscoveryWorker(_input =>
{
@@ -630,7 +644,7 @@ await File.WriteAllTextAsync(projectPath, """
- """);
+ """.SetEOL(EOL));
break;
case "Some.Package2":
await File.WriteAllTextAsync(projectPath, """
@@ -648,14 +662,14 @@ await File.WriteAllTextAsync(projectPath, """
- """);
+ """.SetEOL(EOL));
var packagesConfigPath = Path.Join(Path.GetDirectoryName(projectPath)!, "packages.config");
await File.WriteAllTextAsync(packagesConfigPath, """
- """);
+ """.SetEOL(EOL));
break;
default:
throw new NotSupportedException();
@@ -676,7 +690,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -691,7 +705,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """))
+ """.SetEOL(EOL)))
},
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -815,7 +829,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """,
+ """.SetEOL(EOL),
},
new DependencyFile()
{
@@ -836,7 +850,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """,
+ """.SetEOL(EOL),
},
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -849,11 +863,14 @@ await File.WriteAllTextAsync(packagesConfigPath, """
);
}
- [Fact]
- public async Task UpdateHandlesPackagesConfigFromReferencedCsprojFiles()
+ [Theory]
+ [InlineData(EOLType.CR)]
+ [InlineData(EOLType.LF)]
+ [InlineData(EOLType.CRLF)]
+ public async Task UpdateHandlesPackagesConfigFromReferencedCsprojFiles(EOLType EOL)
{
- var repoMetadata = XElement.Parse("""""");
- var repoMetadata2 = XElement.Parse("""""");
+ var repoMetadata = XElement.Parse("""""".SetEOL(EOL));
+ var repoMetadata2 = XElement.Parse("""""".SetEOL(EOL));
await RunAsync(
packages:
[
@@ -886,13 +903,13 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("some-dir/ProjectA/packages.config", """
- """),
+ """.SetEOL(EOL)),
("some-dir/ProjectB/ProjectB.csproj", """
@@ -902,13 +919,13 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("some-dir/ProjectB/packages.config", """
- """),
+ """.SetEOL(EOL)),
],
discoveryWorker: new TestDiscoveryWorker(_input =>
{
@@ -999,7 +1016,7 @@ await File.WriteAllTextAsync(projectPath, """
- """);
+ """.SetEOL(EOL));
break;
case ("ProjectA.csproj", "Some.Package2"):
await File.WriteAllTextAsync(projectPath, """
@@ -1020,13 +1037,13 @@ await File.WriteAllTextAsync(projectPath, """
- """);
+ """.SetEOL(EOL));
await File.WriteAllTextAsync(packagesConfigPath, """
- """);
+ """.SetEOL(EOL));
break;
case ("ProjectB.csproj", "Some.Package"):
await File.WriteAllTextAsync(projectPath, """
@@ -1038,7 +1055,7 @@ await File.WriteAllTextAsync(projectPath, """
- """);
+ """.SetEOL(EOL));
break;
case ("ProjectB.csproj", "Some.Package2"):
await File.WriteAllTextAsync(projectPath, """
@@ -1056,13 +1073,13 @@ await File.WriteAllTextAsync(projectPath, """
- """);
+ """.SetEOL(EOL));
await File.WriteAllTextAsync(packagesConfigPath, """
- """);
+ """.SetEOL(EOL));
break;
default:
throw new NotSupportedException();
@@ -1083,7 +1100,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -1101,7 +1118,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -1112,7 +1129,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -1127,7 +1144,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """))
+ """.SetEOL(EOL)))
},
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -1337,7 +1354,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """,
+ """.SetEOL(EOL),
},
new DependencyFile()
{
@@ -1361,7 +1378,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """,
+ """.SetEOL(EOL),
},
new DependencyFile()
{
@@ -1372,7 +1389,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """,
+ """.SetEOL(EOL),
},
new DependencyFile()
{
@@ -1393,7 +1410,7 @@ await File.WriteAllTextAsync(packagesConfigPath, """
- """,
+ """.SetEOL(EOL),
},
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -1406,8 +1423,11 @@ await File.WriteAllTextAsync(packagesConfigPath, """
);
}
- [Fact]
- public async Task UpdatedFilesAreOnlyReportedOnce()
+ [Theory]
+ [InlineData(EOLType.CR)]
+ [InlineData(EOLType.LF)]
+ [InlineData(EOLType.CRLF)]
+ public async Task UpdatedFilesAreOnlyReportedOnce(EOLType EOL)
{
await RunAsync(
job: new()
@@ -1434,14 +1454,14 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("Directory.Build.props", """
1.0.0
- """),
+ """.SetEOL(EOL)),
("project1/project1.csproj", """
@@ -1451,7 +1471,7 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("project2/project2.csproj", """
@@ -1461,7 +1481,7 @@ await RunAsync(
- """)
+ """.SetEOL(EOL))
],
discoveryWorker: new TestDiscoveryWorker(_input =>
{
@@ -1526,7 +1546,7 @@ await File.WriteAllTextAsync(directoryBuildPropsPath, """
1.1.0
- """);
+ """.SetEOL(EOL));
return new UpdateOperationResult();
}),
expectedResult: new RunResult()
@@ -1543,7 +1563,7 @@ await File.WriteAllTextAsync(directoryBuildPropsPath, """
1.0.0
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -1558,7 +1578,7 @@ await File.WriteAllTextAsync(directoryBuildPropsPath, """
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -1573,7 +1593,7 @@ await File.WriteAllTextAsync(directoryBuildPropsPath, """
- """))
+ """.SetEOL(EOL)))
},
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -1698,7 +1718,7 @@ await File.WriteAllTextAsync(directoryBuildPropsPath, """
1.1.0
- """,
+ """.SetEOL(EOL),
}
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -1711,8 +1731,11 @@ await File.WriteAllTextAsync(directoryBuildPropsPath, """
);
}
- [Fact]
- public async Task UpdatePackageWithDifferentVersionsInDifferentDirectories()
+ [Theory]
+ [InlineData(EOLType.CR)]
+ [InlineData(EOLType.LF)]
+ [InlineData(EOLType.CRLF)]
+ public async Task UpdatePackageWithDifferentVersionsInDifferentDirectories(EOLType EOL)
{
// this test passes `null` for discovery, analyze, and update workers to fully test the desired behavior
@@ -1754,7 +1777,7 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("Directory.Build.props", ""),
("Directory.Build.targets", ""),
("Directory.Packages.props", """
@@ -1763,7 +1786,7 @@ await RunAsync(
false
- """),
+ """.SetEOL(EOL)),
("library1/library1.csproj", """
@@ -1773,7 +1796,7 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("library2/library2.csproj", """
@@ -1783,7 +1806,7 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
("library3/library3.csproj", """
@@ -1793,7 +1816,7 @@ await RunAsync(
- """),
+ """.SetEOL(EOL)),
],
discoveryWorker: null,
analyzeWorker: null,
@@ -1824,7 +1847,7 @@ await RunAsync(
false
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -1839,7 +1862,7 @@ await RunAsync(
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -1854,7 +1877,7 @@ await RunAsync(
- """))
+ """.SetEOL(EOL)))
},
new DependencyFile()
{
@@ -1869,7 +1892,7 @@ await RunAsync(
- """))
+ """.SetEOL(EOL)))
}
],
BaseCommitSha = "TEST-COMMIT-SHA",
@@ -2020,7 +2043,7 @@ await RunAsync(
- """
+ """.SetEOL(EOL)
},
new()
{
@@ -2036,7 +2059,7 @@ await RunAsync(
- """
+ """.SetEOL(EOL)
}
],
BaseCommitSha = "TEST-COMMIT-SHA",
diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/EOLHandlingTests.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/EOLHandlingTests.cs
new file mode 100644
index 00000000000..ad3c9c194c4
--- /dev/null
+++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/EOLHandlingTests.cs
@@ -0,0 +1,23 @@
+using System.Text.RegularExpressions;
+
+using Xunit;
+
+using static NuGetUpdater.Core.Utilities.EOLHandling;
+
+namespace NuGetUpdater.Core.Test.Utilities
+{
+ public class EOLHandlingTests
+ {
+ [Theory]
+ [InlineData(EOLType.LF, "\n")]
+ [InlineData(EOLType.CR, "\r")]
+ [InlineData(EOLType.CRLF, "\r\n")]
+ public void ValidateEOLNormalizesFromLF(EOLType eolType, string literal)
+ {
+ var teststring = "this\ris\na\r\nstring\rwith\nmixed\r\nline\rendings\n.";
+ var changed = teststring.SetEOL(eolType);
+ var lineEndings = Regex.Split(changed, "\\S+");
+ Assert.All(lineEndings, lineEnding => lineEnding.Equals(literal));
+ }
+ }
+}
diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs
index 3b020d68edc..2a5f3773b7a 100644
--- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs
+++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs
@@ -3,6 +3,7 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
using Microsoft.Extensions.FileSystemGlobbing;
@@ -13,6 +14,8 @@
using NuGetUpdater.Core.Run.ApiModel;
using NuGetUpdater.Core.Utilities;
+using static NuGetUpdater.Core.Utilities.EOLHandling;
+
namespace NuGetUpdater.Core.Run;
public class RunWorker
@@ -122,6 +125,7 @@ private async Task RunForDirectory(Job job, DirectoryInfo repoContent
// TODO: pull out relevant dependencies, then check each for updates and track the changes
var originalDependencyFileContents = new Dictionary();
+ var originalDependencyFileEOFs = new Dictionary();
var actualUpdatedDependencies = new List();
// track original contents for later handling
@@ -131,6 +135,7 @@ async Task TrackOriginalContentsAsync(string directory, string fileName)
var localFullPath = Path.Join(repoContentsPath.FullName, repoFullPath);
var content = await File.ReadAllTextAsync(localFullPath);
originalDependencyFileContents[repoFullPath] = content;
+ originalDependencyFileEOFs[repoFullPath] = content.GetPredominantEOL();
}
foreach (var project in discoveryResult.Projects)
@@ -203,8 +208,13 @@ async Task AddUpdatedFileIfDifferentAsync(string directory, string fileName)
var localFullPath = Path.GetFullPath(Path.Join(repoContentsPath.FullName, repoFullPath));
var originalContent = originalDependencyFileContents[repoFullPath];
var updatedContent = await File.ReadAllTextAsync(localFullPath);
+
+ updatedContent = updatedContent.SetEOL(originalDependencyFileEOFs[repoFullPath]);
+ await File.WriteAllTextAsync(localFullPath, updatedContent);
+
if (updatedContent != originalContent)
{
+
updatedDependencyFiles[localFullPath] = new DependencyFile()
{
Name = Path.GetFileName(repoFullPath),
diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/EOLHandling.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/EOLHandling.cs
new file mode 100644
index 00000000000..33339c9c8da
--- /dev/null
+++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/EOLHandling.cs
@@ -0,0 +1,78 @@
+using System.Text.RegularExpressions;
+
+namespace NuGetUpdater.Core.Utilities;
+
+public static class EOLHandling
+{
+ ///
+ /// Used to save (and then restore) which line endings are predominant in a file.
+ ///
+ public enum EOLType
+ {
+ ///
+ /// Line feed - \n
+ /// Typical on most systems.
+ ///
+ LF,
+ ///
+ /// Carriage return - \r
+ /// Typical on older MacOS, unlikely (but possible) to come up here
+ ///
+ CR,
+ ///
+ /// Carriage return and line feed - \r\n.
+ /// Typical on Windows
+ ///
+ CRLF
+ };
+
+ ///
+ /// Analyze the input string and find the most common line ending type.
+ ///
+ /// The string to analyze
+ /// The most common type of line ending in the input string.
+ public static EOLType GetPredominantEOL(this string content)
+ {
+ // Get stats on EOL characters/character sequences, if one predominates choose that for writing later.
+ var lfcount = content.Count(c => c == '\n');
+ var crcount = content.Count(c => c == '\r');
+ var crlfcount = Regex.Matches(content, "\r\n").Count();
+
+ // Since CRLF contains both a CR and a LF, subtract it from those counts
+ lfcount -= crlfcount;
+ crcount -= crlfcount;
+ if (crcount > lfcount && crcount > crlfcount)
+ {
+ return EOLType.CR;
+ }
+ else if (crlfcount > lfcount)
+ {
+ return EOLType.CRLF;
+ }
+ else
+ {
+ return EOLType.LF;
+ }
+ }
+
+ ///
+ /// Given a line ending, modify the input string to uniformly use that line ending.
+ ///
+ /// The input string, which may have any combination of line endings.
+ /// The line ending type to use across the result.
+ /// The string with any line endings swapped to the desired type.
+ /// If EOLType is an unexpected value.
+ public static string SetEOL(this string content, EOLType desiredEOL)
+ {
+ switch (desiredEOL)
+ {
+ case EOLType.LF:
+ return Regex.Replace(content, "(\r\n|\r)", "\n");
+ case EOLType.CR:
+ return Regex.Replace(content, "(\r\n|\n)", "\r");
+ case EOLType.CRLF:
+ return Regex.Replace(content, "(\r\n|\r|\n)", "\r\n");
+ }
+ throw new ArgumentOutOfRangeException(nameof(desiredEOL));
+ }
+}