Repository navigation
Auto-remove manual NuGet.* version pins on future major versions
#666
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| using System.Text.RegularExpressions; | ||
| using System.Threading.Tasks; | ||
| using Fallout.Migrate.Common; | ||
|
|
||
| namespace Fallout.Migrate.Steps; | ||
|
|
||
| /// <summary> | ||
| /// Removes explicit <c>NuGet.Framework</c>, <c>NuGet.Protocol</c>, <c>NuGet.Packaging</c>, and | ||
| /// <c>NuGet.Resolver</c> package pins from project files. The pins are no longer needed. | ||
| /// </summary> | ||
| internal sealed class RemoveNugetFrameworkPinStep : IMigrationStep | ||
| { | ||
| private static readonly Regex explicitPinPattern = new( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔧 Add a human-friendly explanation to the Regex, e.g. something like /// <summary>
/// Finds a whole line with a self-closing <c><PackageReference></c>
/// for <c>NuGet.Frameworks</c> that has a <c>Version</c> attribute.
/// </summary>
/// <remarks>
/// The match includes leading spaces, trailing spaces, and the line break.
/// Replace it with an empty string to remove the line.
/// The tag must be on one line and must end with <c>/></c>.
/// <c>Include</c> and <c>Version</c> can appear in any order.
/// Use this with <see cref="RegexOptions.Multiline"/>.
/// The match is case-sensitive.
/// </remarks>
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| @"^[ \t]*<PackageReference\s+(?=[^>\r\n]*\bInclude=""NuGet\.Frameworks"")(?=[^>\r\n]*\bVersion=""[^""]+"")[^>\r\n]*/>[ \t]*\r?\n?", | ||
| RegexOptions.Compiled | RegexOptions.Multiline); | ||
|
|
||
| /// <inheritdoc /> | ||
| public Task ExecuteAsync(MigrationContext context, Summary summary) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| { | ||
| foreach (var path in MigrationFileOperations.EnumerateFiles(context.RootDirectory, "*.csproj")) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓🤖 This scans every |
||
| { | ||
| MigrationFileOperations.ApplyRewrite(context, path, Rewrite, summary); | ||
| } | ||
|
|
||
| return Task.CompletedTask; | ||
| } | ||
|
|
||
| private static RewriteResult Rewrite(string original) | ||
| { | ||
| var edits = 0; | ||
| var content = explicitPinPattern.Replace(original, _ => | ||
| { | ||
| edits++; | ||
| return string.Empty; | ||
| }); | ||
|
|
||
| return new RewriteResult(content, edits); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| using System.Globalization; | ||
| using System.Text.RegularExpressions; | ||
| using System.Threading.Tasks; | ||
| using Fallout.Migrate.Common; | ||
|
|
||
| namespace Fallout.Migrate.Steps; | ||
|
|
||
| /// <summary> | ||
| /// Removes explicit <c>NuGet.Protocol</c>, <c>NuGet.Packaging</c>, and <c>NuGet.Resolver</c> | ||
| /// package pins from project files when migrating to Fallout 11 or later. | ||
| /// </summary> | ||
| internal sealed class RemoveRelatedNugetPinsStep : IMigrationStep | ||
| { | ||
| private const int MinimumFalloutMajor = 11; | ||
|
|
||
| private static readonly Regex explicitPinPattern = new( | ||
| @"^[ \t]*<PackageReference\s+(?=[^>\r\n]*\bInclude=""NuGet\.(?:Protocol|Packaging|Resolver)"")(?=[^>\r\n]*\bVersion=""[^""]+"")[^>\r\n]*/>[ \t]*\r?\n?", | ||
| RegexOptions.Compiled | RegexOptions.Multiline); | ||
|
|
||
| /// <inheritdoc /> | ||
| public Task ExecuteAsync(MigrationContext context, Summary summary) | ||
| { | ||
| if (!RunsFor(context.FalloutVersion)) | ||
| { | ||
| return Task.CompletedTask; | ||
| } | ||
|
|
||
| foreach (var path in MigrationFileOperations.EnumerateFiles(context.RootDirectory, "*.csproj")) | ||
| { | ||
| MigrationFileOperations.ApplyRewrite(context, path, Rewrite, summary); | ||
| } | ||
|
|
||
| return Task.CompletedTask; | ||
| } | ||
|
|
||
| private static bool RunsFor(string version) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔧🤖 |
||
| { | ||
| int separatorIndex = version?.IndexOf('.') ?? -1; | ||
| string majorSegment = separatorIndex == -1 ? version : version[..separatorIndex]; | ||
|
|
||
| return int.TryParse(majorSegment, NumberStyles.None, CultureInfo.InvariantCulture, out int major) && | ||
| major >= MinimumFalloutMajor; | ||
| } | ||
|
|
||
| private static RewriteResult Rewrite(string original) | ||
| { | ||
| var edits = 0; | ||
| var content = explicitPinPattern.Replace(original, _ => | ||
| { | ||
| edits++; | ||
| return string.Empty; | ||
| }); | ||
|
|
||
| return new RewriteResult(content, edits); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| using System; | ||
| using System.IO; | ||
| using System.Threading.Tasks; | ||
| using Fallout.Common.IO; | ||
| using Fallout.Migrate.Common; | ||
| using Fallout.Migrate.Steps; | ||
| using FluentAssertions; | ||
| using Xunit; | ||
|
|
||
| namespace Fallout.Migrate.Specs; | ||
|
|
||
| public class RemoveNugetFrameworkPinStepSpecs : IDisposable | ||
| { | ||
| private readonly AbsolutePath tempDirectory; | ||
| private readonly MigrationContext context; | ||
| private readonly Summary summary = new(); | ||
|
|
||
| public RemoveNugetFrameworkPinStepSpecs() | ||
| { | ||
| tempDirectory = AbsolutePath.Temp("fallout-remove-nuget-framework-pin"); | ||
| context = new MigrationContext(tempDirectory, dryRun: false, TextWriter.Null); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Explicit_nuget_framework_pin_is_removed() | ||
| { | ||
| // Arrange | ||
| var project = tempDirectory / "Library.csproj"; | ||
| project.WriteAllText( | ||
| """ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <ItemGroup> | ||
| <PackageReference Version="7.9.0" PrivateAssets="all" Include="NuGet.Frameworks" /> | ||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| """, eofLineBreak: false); | ||
|
|
||
| // Act | ||
| await new RemoveNugetFrameworkPinStep().ExecuteAsync(context, summary); | ||
|
|
||
| // Assert | ||
| project.ReadAllText().Should().Be( | ||
| """ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <ItemGroup> | ||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| """); | ||
|
|
||
| summary.FilesChanged.Should().Be(1); | ||
| summary.EditCount.Should().Be(1); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Nuget_framework_reference_without_version_is_left_unchanged() | ||
| { | ||
| // Arrange | ||
| var project = tempDirectory / "Library.csproj"; | ||
| const string input = | ||
| """ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <ItemGroup> | ||
| <PackageReference Include="NuGet.Frameworks" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| """; | ||
|
|
||
| project.WriteAllText(input, eofLineBreak: false); | ||
|
|
||
| // Act | ||
| await new RemoveNugetFrameworkPinStep().ExecuteAsync(context, summary); | ||
|
|
||
| // Assert | ||
| project.ReadAllText().Should().Be(input); | ||
| summary.EditCount.Should().Be(0); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Missing_nuget_framework_pin_is_left_unchanged() | ||
| { | ||
| // Arrange | ||
| var project = tempDirectory / "build" / "_build.csproj"; | ||
| const string input = | ||
| """ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| </PropertyGroup> | ||
| </Project> | ||
| """; | ||
|
|
||
| project.WriteAllText(input, eofLineBreak: false); | ||
|
|
||
| // Act | ||
| await new RemoveNugetFrameworkPinStep().ExecuteAsync(context, summary); | ||
|
|
||
| // Assert | ||
| project.ReadAllText().Should().Be(input); | ||
| summary.EditCount.Should().Be(0); | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| tempDirectory.DeleteDirectory(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| using System; | ||
| using System.IO; | ||
| using System.Threading.Tasks; | ||
| using Fallout.Common.IO; | ||
| using Fallout.Migrate.Common; | ||
| using Fallout.Migrate.Steps; | ||
| using FluentAssertions; | ||
| using Xunit; | ||
|
|
||
| namespace Fallout.Migrate.Specs; | ||
|
|
||
| public class RemoveRelatedNugetPinsStepSpecs : IDisposable | ||
| { | ||
| private readonly AbsolutePath tempDirectory; | ||
| private readonly Summary summary = new(); | ||
|
|
||
| public RemoveRelatedNugetPinsStepSpecs() | ||
| { | ||
| tempDirectory = AbsolutePath.Temp("fallout-remove-related-nuget-pins"); | ||
| } | ||
|
|
||
| [Theory] | ||
| [InlineData("NuGet.Protocol", "11.0.0")] | ||
| [InlineData("NuGet.Packaging", "11.0.0-preview.1")] | ||
| [InlineData("NuGet.Resolver", "12.0.0")] | ||
| public async Task Explicit_related_nuget_pin_is_removed_for_v11_or_later( | ||
| string packageName, | ||
| string falloutVersion) | ||
| { | ||
| // Arrange | ||
| var project = tempDirectory / "Library.csproj"; | ||
| project.WriteAllText( | ||
| $""" | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <ItemGroup> | ||
| <PackageReference Include="{packageName}" Version="7.9.0" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| """, eofLineBreak: false); | ||
| var context = CreateContext(falloutVersion); | ||
|
|
||
| // Act | ||
| await new RemoveRelatedNugetPinsStep().ExecuteAsync(context, summary); | ||
|
|
||
| // Assert | ||
| project.ReadAllText().Should().Be( | ||
| """ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <ItemGroup> | ||
| </ItemGroup> | ||
| </Project> | ||
| """); | ||
| summary.FilesChanged.Should().Be(1); | ||
| summary.EditCount.Should().Be(1); | ||
| } | ||
|
|
||
| [Theory] | ||
| [InlineData("10.9.0")] | ||
| [InlineData(null)] | ||
| public async Task Explicit_related_nuget_pin_is_left_unchanged_before_v11(string falloutVersion) | ||
| { | ||
| // Arrange | ||
| var project = tempDirectory / "Library.csproj"; | ||
| const string input = | ||
| """ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <ItemGroup> | ||
| <PackageReference Include="NuGet.Protocol" Version="7.9.0" /> | ||
| </ItemGroup> | ||
| </Project> | ||
| """; | ||
|
|
||
| project.WriteAllText(input, eofLineBreak: false); | ||
| var context = CreateContext(falloutVersion); | ||
|
|
||
| // Act | ||
| await new RemoveRelatedNugetPinsStep().ExecuteAsync(context, summary); | ||
|
|
||
| // Assert | ||
| project.ReadAllText().Should().Be(input); | ||
| summary.EditCount.Should().Be(0); | ||
| } | ||
|
|
||
| private MigrationContext CreateContext(string falloutVersion) => | ||
| new(tempDirectory, dryRun: false, TextWriter.Null) | ||
| { | ||
| FalloutVersion = falloutVersion | ||
| }; | ||
|
|
||
| public void Dispose() | ||
| { | ||
| tempDirectory.DeleteDirectory(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧🤖 The summary lists Framework, Protocol, Packaging and Resolver, but the regex only removes
NuGet.Frameworks. Fix the text.