dependabot-nuget 0.301.0 → 0.302.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.props +4 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +19 -4
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +85 -81
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +177 -28
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +15 -6
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +6 -4
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +1 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/{WebApplicationTargetsConditionPatcher.cs → SpecialImportsConditionPatcher.cs} +18 -11
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs +26 -11
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +37 -11
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +54 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +54 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +94 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +1 -1
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/SpecialFilePatcherTests.cs +99 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +38 -0
  18. data/lib/dependabot/nuget/file_parser.rb +22 -19
  19. metadata +7 -6
@@ -2,43 +2,50 @@ using Microsoft.Language.Xml;
2
2
 
3
3
  namespace NuGetUpdater.Core.Updater
4
4
  {
5
- internal class WebApplicationTargetsConditionPatcher : IDisposable
5
+ internal class SpecialImportsConditionPatcher : IDisposable
6
6
  {
7
- private string? _capturedCondition;
7
+ private readonly List<string?> _capturedConditions = new List<string?>();
8
8
  private readonly XmlFilePreAndPostProcessor _processor;
9
9
 
10
- public WebApplicationTargetsConditionPatcher(string projectFilePath)
10
+ private readonly HashSet<string> ImportedFilesToIgnore = new(StringComparer.OrdinalIgnoreCase)
11
+ {
12
+ "Microsoft.TextTemplating.targets",
13
+ "Microsoft.WebApplication.targets"
14
+ };
15
+
16
+ public SpecialImportsConditionPatcher(string projectFilePath)
11
17
  {
12
18
  _processor = new XmlFilePreAndPostProcessor(
13
19
  getContent: () => File.ReadAllText(projectFilePath),
14
20
  setContent: s => File.WriteAllText(projectFilePath, s),
15
21
  nodeFinder: doc => doc.Descendants()
16
22
  .Where(e => e.Name == "Import")
17
- .FirstOrDefault(e =>
23
+ .Where(e =>
18
24
  {
19
25
  var projectPath = e.GetAttributeValue("Project");
20
26
  if (projectPath is not null)
21
27
  {
22
28
  var projectFileName = Path.GetFileName(projectPath.NormalizePathToUnix());
23
- return projectFileName.Equals("Microsoft.WebApplication.targets", StringComparison.OrdinalIgnoreCase);
29
+ return ImportedFilesToIgnore.Contains(projectFileName);
24
30
  }
25
31
 
26
32
  return false;
27
33
  })
28
- as XmlNodeSyntax,
29
- preProcessor: n =>
34
+ .Cast<XmlNodeSyntax>(),
35
+ preProcessor: (i, n) =>
30
36
  {
31
37
  var element = (IXmlElementSyntax)n;
32
- _capturedCondition = element.GetAttributeValue("Condition");
38
+ _capturedConditions.Add(element.GetAttributeValue("Condition"));
33
39
  return (XmlNodeSyntax)element.RemoveAttributeByName("Condition").WithAttribute("Condition", "false");
34
40
  },
35
- postProcessor: n =>
41
+ postProcessor: (i, n) =>
36
42
  {
37
43
  var element = (IXmlElementSyntax)n;
38
44
  var newElement = element.RemoveAttributeByName("Condition");
39
- if (_capturedCondition is not null)
45
+ var capturedCondition = _capturedConditions[i];
46
+ if (capturedCondition is not null)
40
47
  {
41
- newElement = newElement.WithAttribute("Condition", _capturedCondition);
48
+ newElement = newElement.WithAttribute("Condition", capturedCondition);
42
49
  }
43
50
 
44
51
  return (XmlNodeSyntax)newElement;
@@ -1,3 +1,5 @@
1
+ using System.Collections.Immutable;
2
+
1
3
  using Microsoft.Language.Xml;
2
4
 
3
5
  namespace NuGetUpdater.Core.Updater
@@ -6,11 +8,11 @@ namespace NuGetUpdater.Core.Updater
6
8
  {
7
9
  public Func<string> GetContent { get; }
8
10
  public Action<string> SetContent { get; }
9
- public Func<XmlDocumentSyntax, XmlNodeSyntax?> NodeFinder { get; }
10
- public Func<XmlNodeSyntax, XmlNodeSyntax> PreProcessor { get; }
11
- public Func<XmlNodeSyntax, XmlNodeSyntax> PostProcessor { get; }
11
+ public Func<XmlDocumentSyntax, IEnumerable<XmlNodeSyntax>> NodeFinder { get; }
12
+ public Func<int, XmlNodeSyntax, XmlNodeSyntax> PreProcessor { get; }
13
+ public Func<int, XmlNodeSyntax, XmlNodeSyntax> PostProcessor { get; }
12
14
 
13
- public XmlFilePreAndPostProcessor(Func<string> getContent, Action<string> setContent, Func<XmlDocumentSyntax, XmlNodeSyntax?> nodeFinder, Func<XmlNodeSyntax, XmlNodeSyntax> preProcessor, Func<XmlNodeSyntax, XmlNodeSyntax> postProcessor)
15
+ public XmlFilePreAndPostProcessor(Func<string> getContent, Action<string> setContent, Func<XmlDocumentSyntax, IEnumerable<XmlNodeSyntax>> nodeFinder, Func<int, XmlNodeSyntax, XmlNodeSyntax> preProcessor, Func<int, XmlNodeSyntax, XmlNodeSyntax> postProcessor)
14
16
  {
15
17
  GetContent = getContent;
16
18
  SetContent = setContent;
@@ -29,7 +31,7 @@ namespace NuGetUpdater.Core.Updater
29
31
 
30
32
  private void PostProcess() => RunProcessor(PostProcessor);
31
33
 
32
- private void RunProcessor(Func<XmlNodeSyntax, XmlNodeSyntax> processor)
34
+ private void RunProcessor(Func<int, XmlNodeSyntax, XmlNodeSyntax> processor)
33
35
  {
34
36
  var content = GetContent();
35
37
  var xml = Parser.ParseText(content);
@@ -38,15 +40,28 @@ namespace NuGetUpdater.Core.Updater
38
40
  return;
39
41
  }
40
42
 
41
- var node = NodeFinder(xml);
42
- if (node is null)
43
+ var offset = 0;
44
+ var nodes = NodeFinder(xml).ToImmutableArray();
45
+ for (int i = 0; i < nodes.Length; i++)
43
46
  {
44
- return;
47
+ // modify the node...
48
+ var node = nodes[i];
49
+ var replacementElement = processor(i, node);
50
+
51
+ // ...however, the XML structure we're using is immutable and calling `.ReplaceNode()` below will fail because the nodes are no longer equal
52
+ // find the equivalent node by offset, accounting for any changes in length
53
+ var candidateEquivalentNodes = xml.DescendantNodes().OfType<XmlNodeSyntax>().ToArray();
54
+ var equivalentNode = candidateEquivalentNodes.First(n => n.Start == node.Start + offset);
55
+
56
+ // do the actual replacement
57
+ xml = xml.ReplaceNode(equivalentNode, replacementElement);
58
+
59
+ // update our offset
60
+ var thisNodeOffset = replacementElement.ToFullString().Length - node.ToFullString().Length;
61
+ offset += thisNodeOffset;
45
62
  }
46
63
 
47
- var replacementElement = processor(node);
48
- var replacementXml = xml.ReplaceNode(node, replacementElement);
49
- var replacementString = replacementXml.ToFullString();
64
+ var replacementString = xml.ToFullString();
50
65
  SetContent(replacementString);
51
66
  }
52
67
  }
@@ -6,6 +6,7 @@ using System.Text.Json;
6
6
  using System.Text.Json.Nodes;
7
7
  using System.Text.RegularExpressions;
8
8
  using System.Xml;
9
+ using System.Xml.Linq;
9
10
 
10
11
  using Microsoft.Build.Construction;
11
12
  using Microsoft.Build.Definition;
@@ -30,6 +31,8 @@ internal static partial class MSBuildHelper
30
31
 
31
32
  public static bool IsMSBuildRegistered => MSBuildPath.Length > 0;
32
33
 
34
+ public static string GetFileFromRuntimeDirectory(string fileName) => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, fileName);
35
+
33
36
  public static void RegisterMSBuild(string currentDirectory, string rootDirectory)
34
37
  {
35
38
  // Ensure MSBuild types are registered before calling a method that loads the types
@@ -663,7 +666,7 @@ internal static partial class MSBuildHelper
663
666
  }
664
667
  }
665
668
 
666
- internal static async Task<string> CreateTempProjectAsync(
669
+ internal static Task<string> CreateTempProjectAsync(
667
670
  DirectoryInfo tempDir,
668
671
  string repoRoot,
669
672
  string projectPath,
@@ -671,7 +674,32 @@ internal static partial class MSBuildHelper
671
674
  IReadOnlyCollection<Dependency> packages,
672
675
  ExperimentsManager experimentsManager,
673
676
  ILogger logger,
674
- bool usePackageDownload = false)
677
+ bool usePackageDownload = false,
678
+ bool importDependencyTargets = true
679
+ ) => CreateTempProjectAsync(tempDir, repoRoot, projectPath, new XElement("TargetFramework", targetFramework), packages, experimentsManager, logger, usePackageDownload, importDependencyTargets);
680
+
681
+ internal static Task<string> CreateTempProjectAsync(
682
+ DirectoryInfo tempDir,
683
+ string repoRoot,
684
+ string projectPath,
685
+ ImmutableArray<string> targetFrameworks,
686
+ IReadOnlyCollection<Dependency> packages,
687
+ ExperimentsManager experimentsManager,
688
+ ILogger logger,
689
+ bool usePackageDownload = false,
690
+ bool importDependencyTargets = true
691
+ ) => CreateTempProjectAsync(tempDir, repoRoot, projectPath, new XElement("TargetFrameworks", string.Join(";", targetFrameworks)), packages, experimentsManager, logger, usePackageDownload, importDependencyTargets);
692
+
693
+ private static async Task<string> CreateTempProjectAsync(
694
+ DirectoryInfo tempDir,
695
+ string repoRoot,
696
+ string projectPath,
697
+ XElement targetFrameworkElement,
698
+ IReadOnlyCollection<Dependency> packages,
699
+ ExperimentsManager experimentsManager,
700
+ ILogger logger,
701
+ bool usePackageDownload,
702
+ bool importDependencyTargets)
675
703
  {
676
704
  var projectDirectory = Path.GetDirectoryName(projectPath);
677
705
  projectDirectory ??= repoRoot;
@@ -722,16 +750,16 @@ internal static partial class MSBuildHelper
722
750
  // If all PackageReferences for a package are update-only mark it as such, otherwise it can cause package incoherence errors which do not exist in the repo.
723
751
  .Select(p => $"<{(usePackageDownload ? "PackageDownload" : "PackageReference")} {(p.IsUpdate ? "Update" : "Include")}=\"{p.Name}\" Version=\"[{p.Version}]\" />"));
724
752
 
753
+ var dependencyTargetsImport = importDependencyTargets
754
+ ? $"""<Import Project="{GetFileFromRuntimeDirectory("DependencyDiscovery.targets")}" />"""
755
+ : string.Empty;
756
+
725
757
  var projectContents = $"""
726
758
  <Project Sdk="Microsoft.NET.Sdk">
727
759
  <PropertyGroup>
728
- <TargetFramework>{targetFramework}</TargetFramework>
729
- <GenerateDependencyFile>true</GenerateDependencyFile>
730
- <RunAnalyzers>false</RunAnalyzers>
731
- <NuGetInteractive>false</NuGetInteractive>
732
- <DesignTimeBuild>true</DesignTimeBuild>
733
- <TargetPlatformVersion Condition=" $(TargetFramework.Contains('-')) ">1.0</TargetPlatformVersion>
760
+ {targetFrameworkElement}
734
761
  </PropertyGroup>
762
+ {dependencyTargetsImport}
735
763
  <ItemGroup>
736
764
  {packageReferences}
737
765
  </ItemGroup>
@@ -763,8 +791,6 @@ internal static partial class MSBuildHelper
763
791
  """
764
792
  <Project>
765
793
  <PropertyGroup>
766
- <!-- For Windows-specific apps -->
767
- <EnableWindowsTargeting>true</EnableWindowsTargeting>
768
794
  <!-- Really ensure CPM is disabled -->
769
795
  <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
770
796
  </PropertyGroup>
@@ -867,7 +893,7 @@ internal static partial class MSBuildHelper
867
893
  try
868
894
  {
869
895
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
870
- var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger);
896
+ var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages, experimentsManager, logger, importDependencyTargets: !experimentsManager.UseDirectDiscovery);
871
897
 
872
898
  Dependency[] allDependencies;
873
899
  if (experimentsManager.UseDirectDiscovery)
@@ -533,6 +533,60 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
533
533
  );
534
534
  }
535
535
 
536
+ [Fact]
537
+ public async Task WindowsSpecificProjectAndWindowsSpecificDependency()
538
+ {
539
+ await TestAnalyzeAsync(
540
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = true },
541
+ packages: [
542
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net6.0-windows7.0"),
543
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.1", "net6.0-windows7.0"),
544
+ ],
545
+ discovery: new()
546
+ {
547
+ Path = "/",
548
+ Projects = [
549
+ new()
550
+ {
551
+ FilePath = "./project.csproj",
552
+ TargetFrameworks = ["net9.0-windows"],
553
+ Dependencies = [
554
+ new("Some.Package", "1.0.0", DependencyType.PackageReference),
555
+ ],
556
+ ReferencedProjectPaths = [],
557
+ ImportedFiles = [],
558
+ AdditionalFiles = [],
559
+ },
560
+ ],
561
+ },
562
+ dependencyInfo: new()
563
+ {
564
+ Name = "Some.Package",
565
+ Version = "1.0.0",
566
+ IgnoredVersions = [],
567
+ IsVulnerable = false,
568
+ Vulnerabilities = [
569
+ new()
570
+ {
571
+ DependencyName = "Some.Package",
572
+ PackageManager = "nuget",
573
+ VulnerableVersions = [],
574
+ SafeVersions = []
575
+ }
576
+ ],
577
+ },
578
+ expectedResult: new()
579
+ {
580
+ UpdatedVersion = "1.0.1",
581
+ CanUpdate = true,
582
+ VersionComesFromMultiDependencyProperty = false,
583
+ UpdatedDependencies = [
584
+ new("Some.Package", "1.0.1", DependencyType.PackageReference, TargetFrameworks: ["net9.0-windows"], IsDirect: true),
585
+ ],
586
+ }
587
+ );
588
+ }
589
+
536
590
  [Fact]
537
591
  public async Task VersionFinderCanHandle404FromPackageSource_V2()
538
592
  {
@@ -171,5 +171,59 @@ public partial class DiscoveryWorkerTests
171
171
  }
172
172
  );
173
173
  }
174
+
175
+ [Fact]
176
+ public async Task DirectDiscoveryWorksEvenWithTargetsImportsOnlyProvidedByVisualStudio()
177
+ {
178
+ await TestDiscoveryAsync(
179
+ workspacePath: "",
180
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = true },
181
+ packages: [
182
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net48"),
183
+ ],
184
+ files: [
185
+ ("project.csproj", """
186
+ <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
187
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
188
+ <PropertyGroup>
189
+ <OutputType>Library</OutputType>
190
+ <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
191
+ </PropertyGroup>
192
+ <ItemGroup>
193
+ <None Include="packages.config" />
194
+ </ItemGroup>
195
+ <Import Project="$(VSToolsPath)\SomeSubPath\WebApplications\Microsoft.WebApplication.targets" />
196
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
197
+ </Project>
198
+ """),
199
+ ("packages.config", """
200
+ <?xml version="1.0" encoding="utf-8"?>
201
+ <packages>
202
+ <package id="Some.Package" version="1.0.0" targetFramework="net48" />
203
+ </packages>
204
+ """)
205
+ ],
206
+ expectedResult: new()
207
+ {
208
+ Path = "",
209
+ Projects = [
210
+ new()
211
+ {
212
+ FilePath = "project.csproj",
213
+ Properties = [],
214
+ TargetFrameworks = ["net48"],
215
+ Dependencies = [
216
+ new("Some.Package", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net48"]),
217
+ ],
218
+ ReferencedProjectPaths = [],
219
+ ImportedFiles = [],
220
+ AdditionalFiles = [
221
+ "packages.config"
222
+ ],
223
+ }
224
+ ]
225
+ }
226
+ );
227
+ }
174
228
  }
175
229
  }
@@ -1116,6 +1116,50 @@ public partial class DiscoveryWorkerTests
1116
1116
  );
1117
1117
  }
1118
1118
 
1119
+ [Fact]
1120
+ public async Task WindowsSpecificProjectAndWindowsSpecificDependency()
1121
+ {
1122
+ await TestDiscoveryAsync(
1123
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = true },
1124
+ packages: [
1125
+ MockNuGetPackage.CreateSimplePackage("Some.Os.Package", "1.2.3", "net6.0-windows7.0")
1126
+ ],
1127
+ workspacePath: "",
1128
+ files: [
1129
+ ("project.csproj", """
1130
+ <Project Sdk="Microsoft.NET.Sdk">
1131
+ <PropertyGroup>
1132
+ <TargetFramework>net9.0-windows</TargetFramework>
1133
+ </PropertyGroup>
1134
+ <ItemGroup>
1135
+ <PackageReference Include="Some.Os.Package" Version="1.2.3" />
1136
+ </ItemGroup>
1137
+ </Project>
1138
+ """)
1139
+ ],
1140
+ expectedResult: new()
1141
+ {
1142
+ Path = "",
1143
+ Projects = [
1144
+ new()
1145
+ {
1146
+ FilePath = "project.csproj",
1147
+ Dependencies = [
1148
+ new("Some.Os.Package", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net9.0-windows"], IsDirect: true)
1149
+ ],
1150
+ Properties = [
1151
+ new("TargetFramework", "net9.0-windows", "project.csproj")
1152
+ ],
1153
+ TargetFrameworks = ["net9.0-windows"],
1154
+ ReferencedProjectPaths = [],
1155
+ ImportedFiles = [],
1156
+ AdditionalFiles = []
1157
+ }
1158
+ ]
1159
+ }
1160
+ );
1161
+ }
1162
+
1119
1163
  [Fact]
1120
1164
  public async Task DiscoveryWithTargetPlaformVersion_DirectDiscovery()
1121
1165
  {
@@ -1375,5 +1419,55 @@ public partial class DiscoveryWorkerTests
1375
1419
  }
1376
1420
  );
1377
1421
  }
1422
+
1423
+ [Fact]
1424
+ public async Task LegacyProjectWithPackageReferencesReportsDependencies()
1425
+ {
1426
+ // This is a feature of the VS project system - a legacy project with <PackageReference> elements. The `dotnet` CLI
1427
+ // can't resolve the transitive dependencies; only the VS project system can, so there are some manual steps to allow
1428
+ // dependency discovery.
1429
+ await TestDiscoveryAsync(
1430
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = true },
1431
+ packages: [
1432
+ MockNuGetPackage.CreateSimplePackage("Some.Dependency", "1.0.0", "net48", [(null, [("Some.Transitive.Dependency", "2.0.0")])]),
1433
+ MockNuGetPackage.CreateSimplePackage("Some.Transitive.Dependency", "2.0.0", "net48"),
1434
+ ],
1435
+ workspacePath: "",
1436
+ files: [
1437
+ ("project.csproj", """
1438
+ <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
1439
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
1440
+ <PropertyGroup>
1441
+ <OutputType>Library</OutputType>
1442
+ <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
1443
+ </PropertyGroup>
1444
+ <ItemGroup>
1445
+ <PackageReference Include="Some.Dependency" Version="1.0.0" />
1446
+ </ItemGroup>
1447
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
1448
+ </Project>
1449
+ """)
1450
+ ],
1451
+ expectedResult: new()
1452
+ {
1453
+ Path = "",
1454
+ Projects = [
1455
+ new()
1456
+ {
1457
+ FilePath = "project.csproj",
1458
+ Dependencies = [
1459
+ new("Some.Dependency", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net48"], IsDirect: true),
1460
+ new("Some.Transitive.Dependency", "2.0.0", DependencyType.Unknown, TargetFrameworks: ["net48"], IsTransitive: true),
1461
+ ],
1462
+ Properties = [],
1463
+ TargetFrameworks = ["net48"],
1464
+ ReferencedProjectPaths = [],
1465
+ ImportedFiles = [],
1466
+ AdditionalFiles = [],
1467
+ }
1468
+ ]
1469
+ }
1470
+ );
1471
+ }
1378
1472
  }
1379
1473
  }
@@ -102,7 +102,7 @@ public class FrameworkCompatibilityServiceFacts
102
102
  }
103
103
 
104
104
  [Theory]
105
- [InlineData("net6.0-windows7.0", "net6.0-windows", "net6.0-windows7.0", "net7.0-windows", "net7.0-windows7.0")]
105
+ [InlineData("net6.0-windows7.0", "net6.0-windows", "net6.0-windows7.0", "net7.0-windows", "net7.0-windows7.0", "net8.0-windows", "net8.0-windows7.0", "net9.0-windows", "net9.0-windows7.0")]
106
106
  public void WindowsPlatformVersionsShouldContainAllSpecifiedFrameworks(string windowsDefaultVersionFramework, params string[] windowsProjectFrameworks)
107
107
  {
108
108
  var packageFramework = NuGetFramework.Parse(windowsDefaultVersionFramework);
@@ -0,0 +1,99 @@
1
+ using NuGetUpdater.Core.Updater;
2
+
3
+ using Xunit;
4
+
5
+ namespace NuGetUpdater.Core.Test.Update;
6
+
7
+ public class SpecialFilePatcherTests
8
+ {
9
+ [Theory]
10
+ [MemberData(nameof(SpecialImportsConditionPatcherTestData))]
11
+ public async Task SpecialImportsConditionPatcher(string fileContent, string expectedPatchedContent)
12
+ {
13
+ // arrange
14
+ var projectFileName = "project.csproj";
15
+ using var tempDir = await TemporaryDirectory.CreateWithContentsAsync((projectFileName, fileContent));
16
+ var projectPath = Path.Join(tempDir.DirectoryPath, projectFileName);
17
+
18
+ // act
19
+ using (var patcher = new SpecialImportsConditionPatcher(projectPath))
20
+ {
21
+ var actualPatchedContent = await File.ReadAllTextAsync(projectPath);
22
+
23
+ // assert
24
+ Assert.Equal(expectedPatchedContent.Replace("\r", ""), actualPatchedContent.Replace("\r", ""));
25
+ }
26
+
27
+ // assert again
28
+ var restoredContent = await File.ReadAllTextAsync(projectPath);
29
+ Assert.Equal(restoredContent.Replace("\r", ""), fileContent.Replace("\r", ""));
30
+ }
31
+
32
+ public static IEnumerable<object[]> SpecialImportsConditionPatcherTestData()
33
+ {
34
+ // one-off test to verify namespaces don't interfere
35
+ yield return
36
+ [
37
+ // fileContent
38
+ """
39
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
40
+ <Import Project="Unrelated.One.targets" />
41
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" />
42
+ <Import Project="Unrelated.Two.targets" />
43
+ </Project>
44
+ """,
45
+ // expectedPatchedContent
46
+ """
47
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
48
+ <Import Project="Unrelated.One.targets" />
49
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" Condition="false" />
50
+ <Import Project="Unrelated.Two.targets" />
51
+ </Project>
52
+ """
53
+ ];
54
+
55
+ // one-off test to verify existing conditions are restored
56
+ yield return
57
+ [
58
+ // fileContent
59
+ """
60
+ <Project>
61
+ <Import Project="Unrelated.One.targets" />
62
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" Condition="existing condition" />
63
+ <Import Project="Unrelated.Two.targets" />
64
+ </Project>
65
+ """,
66
+ // expectedPatchedContent
67
+ """
68
+ <Project>
69
+ <Import Project="Unrelated.One.targets" />
70
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" Condition="false" />
71
+ <Import Project="Unrelated.Two.targets" />
72
+ </Project>
73
+ """
74
+ ];
75
+
76
+ // all file variations - by its nature, also verifies that multiple replacements can occur
77
+ yield return
78
+ [
79
+ // fileContent
80
+ """
81
+ <Project>
82
+ <Import Project="Unrelated.One.targets" />
83
+ <Import Project="Some\Path\Microsoft.TextTemplating.targets" />
84
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" />
85
+ <Import Project="Unrelated.Two.targets" />
86
+ </Project>
87
+ """,
88
+ // expectedPatchedContent
89
+ """
90
+ <Project>
91
+ <Import Project="Unrelated.One.targets" />
92
+ <Import Project="Some\Path\Microsoft.TextTemplating.targets" Condition="false" />
93
+ <Import Project="Some\Path\Microsoft.WebApplication.targets" Condition="false" />
94
+ <Import Project="Unrelated.Two.targets" />
95
+ </Project>
96
+ """
97
+ ];
98
+ }
99
+ }
@@ -3707,5 +3707,43 @@ public partial class UpdateWorkerTests
3707
3707
  ]
3708
3708
  );
3709
3709
  }
3710
+
3711
+ [Fact]
3712
+ public async Task LegacyProjectWithPackageReferencesCanUpdate()
3713
+ {
3714
+ await TestUpdateForProject("Some.Dependency", "1.0.0", "1.0.1",
3715
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = true },
3716
+ packages: [
3717
+ MockNuGetPackage.CreateSimplePackage("Some.Dependency", "1.0.0", "net48"),
3718
+ MockNuGetPackage.CreateSimplePackage("Some.Dependency", "1.0.1", "net48"),
3719
+ ],
3720
+ projectContents: """
3721
+ <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3722
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
3723
+ <PropertyGroup>
3724
+ <OutputType>Library</OutputType>
3725
+ <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
3726
+ </PropertyGroup>
3727
+ <ItemGroup>
3728
+ <PackageReference Include="Some.Dependency" Version="1.0.0" />
3729
+ </ItemGroup>
3730
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
3731
+ </Project>
3732
+ """,
3733
+ expectedProjectContents: """
3734
+ <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3735
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
3736
+ <PropertyGroup>
3737
+ <OutputType>Library</OutputType>
3738
+ <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
3739
+ </PropertyGroup>
3740
+ <ItemGroup>
3741
+ <PackageReference Include="Some.Dependency" Version="1.0.1" />
3742
+ </ItemGroup>
3743
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
3744
+ </Project>
3745
+ """
3746
+ );
3747
+ }
3710
3748
  }
3711
3749
  }