Skip to content

Commit

Permalink
move EOL management to its own utility class, add test for just that
Browse files Browse the repository at this point in the history
  • Loading branch information
Penguinwizzard authored and randhircs committed Feb 20, 2025
1 parent 78b259c commit 80b92cd
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@

namespace NuGetUpdater.Core.Test.Run;

using static NuGetUpdater.Core.Utilities.EOLHandling;

using TestFile = (string Path, string Content);

public class RunWorkerTests
{
[Theory]
[InlineData(["\r"])]
[InlineData(["\n"])]
[InlineData(["\r\n"])]
public async Task UpdateSinglePackageProducedExpectedAPIMessages(string EOL)
[InlineData(EOLType.CR)]
[InlineData(EOLType.LF)]
[InlineData(EOLType.CRLF)]
public async Task UpdateSinglePackageProducedExpectedAPIMessages(EOLType EOL)
{
await RunAsync(
packages: [],
Expand Down Expand Up @@ -216,10 +218,10 @@ await File.WriteAllTextAsync(projectPath, """
}

[Theory]
[InlineData(["\r"])]
[InlineData(["\n"])]
[InlineData(["\r\n"])]
public async Task UpdateHandlesSemicolonsInPackageReference(string EOL)
[InlineData(EOLType.CR)]
[InlineData(EOLType.LF)]
[InlineData(EOLType.CRLF)]
public async Task UpdateHandlesSemicolonsInPackageReference(EOLType EOL)
{
var repoMetadata = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some-package" />""".SetEOL(EOL));
var repoMetadata2 = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some-package2" />""".SetEOL(EOL));
Expand Down Expand Up @@ -468,10 +470,10 @@ await File.WriteAllTextAsync(projectPath, """
}

[Theory]
[InlineData(["\r"])]
[InlineData(["\n"])]
[InlineData(["\r\n"])]
public async Task PrivateSourceAuthenticationFailureIsForwaredToApiHandler(string EOL)
[InlineData(EOLType.CR)]
[InlineData(EOLType.LF)]
[InlineData(EOLType.CRLF)]
public async Task PrivateSourceAuthenticationFailureIsForwaredToApiHandler(EOLType EOL)
{
await RunAsync(
packages:
Expand Down Expand Up @@ -527,10 +529,10 @@ await RunAsync(
}

[Theory]
[InlineData(["\r"])]
[InlineData(["\n"])]
[InlineData(["\r\n"])]
public async Task UpdateHandlesPackagesConfigFiles(string EOL)
[InlineData(EOLType.CR)]
[InlineData(EOLType.LF)]
[InlineData(EOLType.CRLF)]
public async Task UpdateHandlesPackagesConfigFiles(EOLType EOL)
{
var repoMetadata = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some-package" />""".SetEOL(EOL));
var repoMetadata2 = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some-package2" />""".SetEOL(EOL));
Expand Down Expand Up @@ -862,10 +864,10 @@ await File.WriteAllTextAsync(packagesConfigPath, """
}

[Theory]
[InlineData(["\r"])]
[InlineData(["\n"])]
[InlineData(["\r\n"])]
public async Task UpdateHandlesPackagesConfigFromReferencedCsprojFiles(string EOL)
[InlineData(EOLType.CR)]
[InlineData(EOLType.LF)]
[InlineData(EOLType.CRLF)]
public async Task UpdateHandlesPackagesConfigFromReferencedCsprojFiles(EOLType EOL)
{
var repoMetadata = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some-package" />""".SetEOL(EOL));
var repoMetadata2 = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some-package2" />""".SetEOL(EOL));
Expand Down Expand Up @@ -1422,10 +1424,10 @@ await File.WriteAllTextAsync(packagesConfigPath, """
}

[Theory]
[InlineData(["\r"])]
[InlineData(["\n"])]
[InlineData(["\r\n"])]
public async Task UpdatedFilesAreOnlyReportedOnce(string EOL)
[InlineData(EOLType.CR)]
[InlineData(EOLType.LF)]
[InlineData(EOLType.CRLF)]
public async Task UpdatedFilesAreOnlyReportedOnce(EOLType EOL)
{
await RunAsync(
job: new()
Expand Down Expand Up @@ -1730,10 +1732,10 @@ await File.WriteAllTextAsync(directoryBuildPropsPath, """
}

[Theory]
[InlineData(["\r"])]
[InlineData(["\n"])]
[InlineData(["\r\n"])]
public async Task UpdatePackageWithDifferentVersionsInDifferentDirectories(string EOL)
[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

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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));
}
}
}
78 changes: 4 additions & 74 deletions nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,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
Expand All @@ -25,28 +27,6 @@ public class RunWorker
private readonly IAnalyzeWorker _analyzeWorker;
private readonly IUpdaterWorker _updaterWorker;

/// <summary>
/// Used to save (and then restore) which line endings are predominant in a file.
/// </summary>
private enum EOLType
{
/// <summary>
/// Line feed - \n
/// Typical on most systems.
/// </summary>
LF,
/// <summary>
/// Carriage return - \r
/// Typical on older MacOS, unlikely (but possible) to come up here
/// </summary>
CR,
/// <summary>
/// Carriage return and line feed - \r\n.
/// Typical on Windows
/// </summary>
CRLF
};

internal static readonly JsonSerializerOptions SerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower,
Expand Down Expand Up @@ -155,7 +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] = GetPredominantEOL(content);
originalDependencyFileEOFs[repoFullPath] = content.GetPredominantEOL();
}

foreach (var project in discoveryResult.Projects)
Expand Down Expand Up @@ -229,7 +209,7 @@ async Task AddUpdatedFileIfDifferentAsync(string directory, string fileName)
var originalContent = originalDependencyFileContents[repoFullPath];
var updatedContent = await File.ReadAllTextAsync(localFullPath);

updatedContent = FixEOL(updatedContent, originalDependencyFileEOFs[repoFullPath]);
updatedContent = updatedContent.SetEOL(originalDependencyFileEOFs[repoFullPath]);
await File.WriteAllTextAsync(localFullPath, updatedContent);

if (updatedContent != originalContent)
Expand Down Expand Up @@ -296,56 +276,6 @@ async Task AddUpdatedFileIfDifferentAsync(string directory, string fileName)
return result;
}

/// <summary>
/// Analyze the input string and find the most common line ending type.
/// </summary>
/// <param name="content">The string to analyze</param>
/// <returns>The most common type of line ending in the input string.</returns>
private static EOLType GetPredominantEOL(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;
}
}

/// <summary>
/// Given a line ending, modify the input string to uniformly use that line ending.
/// </summary>
/// <param name="content">The input string, which may have any combination of line endings.</param>
/// <param name="desiredEOL">The line ending type to use across the result.</param>
/// <returns>The string with any line endings swapped to the desired type.</returns>
/// <exception cref="ArgumentOutOfRangeException">If EOLType is an unexpected value.</exception>
private static string FixEOL(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));
}

internal static IEnumerable<(string ProjectPath, Dependency Dependency)> GetUpdateOperations(WorkspaceDiscoveryResult discovery)
{
// discovery is grouped by project then dependency, but we want to pivot and return a list of update operations sorted by dependency name then project path
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.Text.RegularExpressions;

namespace NuGetUpdater.Core.Utilities;

public static class EOLHandling
{
/// <summary>
/// Used to save (and then restore) which line endings are predominant in a file.
/// </summary>
public enum EOLType
{
/// <summary>
/// Line feed - \n
/// Typical on most systems.
/// </summary>
LF,
/// <summary>
/// Carriage return - \r
/// Typical on older MacOS, unlikely (but possible) to come up here
/// </summary>
CR,
/// <summary>
/// Carriage return and line feed - \r\n.
/// Typical on Windows
/// </summary>
CRLF
};

/// <summary>
/// Analyze the input string and find the most common line ending type.
/// </summary>
/// <param name="content">The string to analyze</param>
/// <returns>The most common type of line ending in the input string.</returns>
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;
}
}

/// <summary>
/// Given a line ending, modify the input string to uniformly use that line ending.
/// </summary>
/// <param name="content">The input string, which may have any combination of line endings.</param>
/// <param name="desiredEOL">The line ending type to use across the result.</param>
/// <returns>The string with any line endings swapped to the desired type.</returns>
/// <exception cref="ArgumentOutOfRangeException">If EOLType is an unexpected value.</exception>
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));
}
}

0 comments on commit 80b92cd

Please sign in to comment.