dependabot-nuget 0.242.1 → 0.243.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.editorconfig +37 -28
  3. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  4. data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs +2 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +2 -2
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +178 -176
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +2 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +1 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +5 -4
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +1 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +10 -5
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +16 -12
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +18 -17
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs +7 -7
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +13 -20
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +9 -3
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +32 -16
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +42 -22
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +32 -13
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +47 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs +55 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +12 -9
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +49 -42
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +16 -3
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +6 -6
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +11 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +18 -9
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs +2 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +7 -7
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +9 -9
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs +81 -80
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +22 -9
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +140 -104
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +25 -25
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +8 -9
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +198 -22
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +401 -399
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs +17 -15
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +111 -42
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +103 -87
  42. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +45 -19
  43. data/lib/dependabot/nuget/file_parser.rb +21 -3
  44. data/lib/dependabot/nuget/file_updater.rb +42 -6
  45. data/lib/dependabot/nuget/native_helpers.rb +27 -8
  46. data/lib/dependabot/nuget/nuget_client.rb +66 -23
  47. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +7 -3
  48. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +63 -59
  49. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +2 -2
  50. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +1 -1
  51. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +22 -17
  52. data/lib/dependabot/nuget/update_checker/repository_finder.rb +292 -270
  53. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +11 -13
  54. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +80 -82
  55. data/lib/dependabot/nuget/update_checker/version_finder.rb +3 -2
  56. data/lib/dependabot/nuget/version.rb +18 -7
  57. data/lib/dependabot/nuget.rb +0 -2
  58. metadata +7 -5
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
5
5
 
6
6
  namespace NuGetUpdater.Core;
7
7
 
8
- public partial class UpdaterWorker
8
+ public class UpdaterWorker
9
9
  {
10
10
  private readonly Logger _logger;
11
11
  private readonly HashSet<string> _processedGlobalJsonPaths = new(StringComparer.OrdinalIgnoreCase);
@@ -15,33 +15,33 @@ public partial class UpdaterWorker
15
15
  _logger = logger;
16
16
  }
17
17
 
18
- public async Task RunAsync(string repoRootPath, string filePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
18
+ public async Task RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
19
19
  {
20
20
  MSBuildHelper.RegisterMSBuild();
21
21
 
22
- if (!Path.IsPathRooted(filePath) || !File.Exists(filePath))
22
+ if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
23
23
  {
24
- filePath = Path.GetFullPath(Path.Join(repoRootPath, filePath));
24
+ workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
25
25
  }
26
26
 
27
27
  if (!isTransitive)
28
28
  {
29
- await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
29
+ await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
30
30
  }
31
31
 
32
- var extension = Path.GetExtension(filePath).ToLowerInvariant();
32
+ var extension = Path.GetExtension(workspacePath).ToLowerInvariant();
33
33
  switch (extension)
34
34
  {
35
35
  case ".sln":
36
- await RunForSolutionAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
36
+ await RunForSolutionAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
37
37
  break;
38
38
  case ".proj":
39
- await RunForProjFileAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
39
+ await RunForProjFileAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
40
40
  break;
41
41
  case ".csproj":
42
42
  case ".fsproj":
43
43
  case ".vbproj":
44
- await RunForProjectAsync(repoRootPath, filePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
44
+ await RunForProjectAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
45
45
  break;
46
46
  default:
47
47
  _logger.Log($"File extension [{extension}] is not supported.");
@@ -51,7 +51,13 @@ public partial class UpdaterWorker
51
51
  _processedGlobalJsonPaths.Clear();
52
52
  }
53
53
 
54
- private async Task RunForSolutionAsync(string repoRootPath, string solutionPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
54
+ private async Task RunForSolutionAsync(
55
+ string repoRootPath,
56
+ string solutionPath,
57
+ string dependencyName,
58
+ string previousDependencyVersion,
59
+ string newDependencyVersion,
60
+ bool isTransitive)
55
61
  {
56
62
  _logger.Log($"Running for solution [{Path.GetRelativePath(repoRootPath, solutionPath)}]");
57
63
  var projectPaths = MSBuildHelper.GetProjectPathsFromSolution(solutionPath);
@@ -61,7 +67,13 @@ public partial class UpdaterWorker
61
67
  }
62
68
  }
63
69
 
64
- private async Task RunForProjFileAsync(string repoRootPath, string projFilePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
70
+ private async Task RunForProjFileAsync(
71
+ string repoRootPath,
72
+ string projFilePath,
73
+ string dependencyName,
74
+ string previousDependencyVersion,
75
+ string newDependencyVersion,
76
+ bool isTransitive)
65
77
  {
66
78
  _logger.Log($"Running for proj file [{Path.GetRelativePath(repoRootPath, projFilePath)}]");
67
79
  if (!File.Exists(projFilePath))
@@ -69,6 +81,7 @@ public partial class UpdaterWorker
69
81
  _logger.Log($"File [{projFilePath}] does not exist.");
70
82
  return;
71
83
  }
84
+
72
85
  var projectFilePaths = MSBuildHelper.GetProjectPathsFromProject(projFilePath);
73
86
  foreach (var projectFullPath in projectFilePaths)
74
87
  {
@@ -80,12 +93,18 @@ public partial class UpdaterWorker
80
93
  }
81
94
  }
82
95
 
83
- private async Task RunForProjectAsync(string repoRootPath, string projectPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
96
+ private async Task RunForProjectAsync(
97
+ string repoRootPath,
98
+ string projectPath,
99
+ string dependencyName,
100
+ string previousDependencyVersion,
101
+ string newDependencyVersion,
102
+ bool isTransitive)
84
103
  {
85
104
  _logger.Log($"Running for project [{projectPath}]");
86
105
 
87
106
  if (!isTransitive
88
- && MSBuildHelper.GetGlobalJsonPath(repoRootPath, projectPath) is string globalJsonPath
107
+ && MSBuildHelper.GetGlobalJsonPath(repoRootPath, projectPath) is { } globalJsonPath
89
108
  && !_processedGlobalJsonPaths.Contains(globalJsonPath))
90
109
  {
91
110
  _processedGlobalJsonPaths.Add(globalJsonPath);
@@ -0,0 +1,47 @@
1
+ using System;
2
+ using System.IO;
3
+ using System.Linq;
4
+
5
+ using Microsoft.Language.Xml;
6
+
7
+ namespace NuGetUpdater.Core.Updater
8
+ {
9
+ internal class WebApplicationTargetsConditionPatcher : IDisposable
10
+ {
11
+ private string? _capturedCondition;
12
+ private readonly XmlFilePreAndPostProcessor _processor;
13
+
14
+ public WebApplicationTargetsConditionPatcher(string projectFilePath)
15
+ {
16
+ _processor = new XmlFilePreAndPostProcessor(
17
+ getContent: () => File.ReadAllText(projectFilePath),
18
+ setContent: s => File.WriteAllText(projectFilePath, s),
19
+ nodeFinder: doc => doc.Descendants()
20
+ .FirstOrDefault(e => e.Name == "Import" && e.GetAttributeValue("Project") == @"$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets")
21
+ as XmlNodeSyntax,
22
+ preProcessor: n =>
23
+ {
24
+ var element = (IXmlElementSyntax)n;
25
+ _capturedCondition = element.GetAttributeValue("Condition");
26
+ return (XmlNodeSyntax)element.RemoveAttributeByName("Condition").WithAttribute("Condition", "false");
27
+ },
28
+ postProcessor: n =>
29
+ {
30
+ var element = (IXmlElementSyntax)n;
31
+ var newElement = element.RemoveAttributeByName("Condition");
32
+ if (_capturedCondition is not null)
33
+ {
34
+ newElement = newElement.WithAttribute("Condition", _capturedCondition);
35
+ }
36
+
37
+ return (XmlNodeSyntax)newElement;
38
+ }
39
+ );
40
+ }
41
+
42
+ public void Dispose()
43
+ {
44
+ _processor.Dispose();
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,55 @@
1
+ using System;
2
+
3
+ using Microsoft.Language.Xml;
4
+
5
+ namespace NuGetUpdater.Core.Updater
6
+ {
7
+ internal class XmlFilePreAndPostProcessor : IDisposable
8
+ {
9
+ public Func<string> GetContent { get; }
10
+ public Action<string> SetContent { get; }
11
+ public Func<XmlDocumentSyntax, XmlNodeSyntax?> NodeFinder { get; }
12
+ public Func<XmlNodeSyntax, XmlNodeSyntax> PreProcessor { get; }
13
+ public Func<XmlNodeSyntax, XmlNodeSyntax> PostProcessor { get; }
14
+
15
+ public XmlFilePreAndPostProcessor(Func<string> getContent, Action<string> setContent, Func<XmlDocumentSyntax, XmlNodeSyntax?> nodeFinder, Func<XmlNodeSyntax, XmlNodeSyntax> preProcessor, Func<XmlNodeSyntax, XmlNodeSyntax> postProcessor)
16
+ {
17
+ GetContent = getContent;
18
+ SetContent = setContent;
19
+ NodeFinder = nodeFinder;
20
+ PreProcessor = preProcessor;
21
+ PostProcessor = postProcessor;
22
+ PreProcess();
23
+ }
24
+
25
+ public void Dispose()
26
+ {
27
+ PostProcess();
28
+ }
29
+
30
+ private void PreProcess() => RunProcessor(PreProcessor);
31
+
32
+ private void PostProcess() => RunProcessor(PostProcessor);
33
+
34
+ private void RunProcessor(Func<XmlNodeSyntax, XmlNodeSyntax> processor)
35
+ {
36
+ var content = GetContent();
37
+ var xml = Parser.ParseText(content);
38
+ if (xml is null)
39
+ {
40
+ return;
41
+ }
42
+
43
+ var node = NodeFinder(xml);
44
+ if (node is null)
45
+ {
46
+ return;
47
+ }
48
+
49
+ var replacementElement = processor(node);
50
+ var replacementXml = xml.ReplaceNode(node, replacementElement);
51
+ var replacementString = replacementXml.ToFullString();
52
+ SetContent(replacementString);
53
+ }
54
+ }
55
+ }
@@ -4,6 +4,7 @@ using System.IO;
4
4
  using System.Linq;
5
5
  using System.Runtime.InteropServices;
6
6
  using System.Text;
7
+ using System.Text.Encodings.Web;
7
8
  using System.Text.Json;
8
9
  using System.Text.Json.Nodes;
9
10
 
@@ -11,7 +12,7 @@ namespace NuGetUpdater.Core.Utilities
11
12
  {
12
13
  internal static class JsonHelper
13
14
  {
14
- public static JsonDocumentOptions DocumentOptions { get; } = new JsonDocumentOptions()
15
+ public static JsonDocumentOptions DocumentOptions { get; } = new JsonDocumentOptions
15
16
  {
16
17
  CommentHandling = JsonCommentHandling.Skip,
17
18
  };
@@ -24,16 +25,16 @@ namespace NuGetUpdater.Core.Utilities
24
25
 
25
26
  public static string UpdateJsonProperty(string json, string[] propertyPath, string newValue, StringComparison comparisonType = StringComparison.Ordinal)
26
27
  {
27
- var readerOptions = new JsonReaderOptions()
28
+ var readerOptions = new JsonReaderOptions
28
29
  {
29
30
  CommentHandling = JsonCommentHandling.Allow,
30
31
  };
31
32
  var bytes = Encoding.UTF8.GetBytes(json);
32
33
  var reader = new Utf8JsonReader(bytes, readerOptions);
33
34
  using var ms = new MemoryStream();
34
- var writerOptions = new JsonWriterOptions()
35
+ var writerOptions = new JsonWriterOptions
35
36
  {
36
- Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
37
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
37
38
  Indented = true,
38
39
  };
39
40
  var writer = new Utf8JsonWriter(ms, writerOptions);
@@ -70,6 +71,7 @@ namespace NuGetUpdater.Core.Utilities
70
71
  // let the default block comment writer handle it
71
72
  writer.WriteCommentValue(reader.GetComment());
72
73
  }
74
+
73
75
  break;
74
76
  case JsonTokenType.EndArray:
75
77
  writer.WriteEndArray();
@@ -126,7 +128,7 @@ namespace NuGetUpdater.Core.Utilities
126
128
  {
127
129
  currentPath.RemoveAt(currentPath.Count - 1);
128
130
  }
129
-
131
+
130
132
  currentPath[pathDepth] = pathValue;
131
133
  if (IsPathMatch(currentPath, propertyPath, comparisonType))
132
134
  {
@@ -187,18 +189,19 @@ namespace NuGetUpdater.Core.Utilities
187
189
  // quit at newline, modulo some special cases
188
190
  if (c == '\n')
189
191
  {
190
- // check for preceeding CR
192
+ // check for preceding CR
191
193
  if (IsPreceedingCharacterEqual(originalJson, prefixStart, '\r'))
192
194
  {
193
195
  prefixStart--;
194
196
  }
195
197
  }
196
198
 
197
- // check for preceeding comma
199
+ // check for preceding comma
198
200
  if (IsPreceedingCharacterEqual(originalJson, prefixStart, ','))
199
201
  {
200
202
  prefixStart--;
201
203
  }
204
+
202
205
  goto done;
203
206
  default:
204
207
  // found regular character; move forward one and quit
@@ -215,8 +218,8 @@ namespace NuGetUpdater.Core.Utilities
215
218
  private static bool IsPreceedingCharacterEqual(string originalText, int currentIndex, char expectedCharacter)
216
219
  {
217
220
  return currentIndex > 0
218
- && currentIndex < originalText.Length
219
- && originalText[currentIndex - 1] == expectedCharacter;
221
+ && currentIndex < originalText.Length
222
+ && originalText[currentIndex - 1] == expectedCharacter;
220
223
  }
221
224
  }
222
225
  }
@@ -145,11 +145,11 @@ internal static partial class MSBuildHelper
145
145
  var projectRoot = CreateProjectRootElement(buildFile);
146
146
 
147
147
  foreach (var packageItem in projectRoot.Items
148
- .Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference")))
148
+ .Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference")))
149
149
  {
150
150
  var versionSpecification = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
151
- ?? packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase))?.Value
152
- ?? string.Empty;
151
+ ?? packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase))?.Value
152
+ ?? string.Empty;
153
153
  foreach (var attributeValue in new[] { packageItem.Include, packageItem.Update })
154
154
  {
155
155
  if (!string.IsNullOrWhiteSpace(attributeValue))
@@ -175,10 +175,10 @@ internal static partial class MSBuildHelper
175
175
  }
176
176
 
177
177
  foreach (var packageItem in projectRoot.Items
178
- .Where(i => i.ItemType == "PackageVersion" && !string.IsNullOrEmpty(i.Include)))
178
+ .Where(i => i.ItemType == "PackageVersion" && !string.IsNullOrEmpty(i.Include)))
179
179
  {
180
180
  packageVersionInfo[packageItem.Include] = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value
181
- ?? string.Empty;
181
+ ?? string.Empty;
182
182
  }
183
183
 
184
184
  foreach (var property in projectRoot.Properties)
@@ -187,7 +187,8 @@ internal static partial class MSBuildHelper
187
187
  // going to be used, and even then we might not be able to update it. As a best guess, we'll simply
188
188
  // skip any property that has a condition _or_ where the condition is checking for an empty string.
189
189
  var hasEmptyCondition = string.IsNullOrEmpty(property.Condition);
190
- var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase);
190
+ var conditionIsCheckingForEmptyString = string.Equals(property.Condition, $"$({property.Name}) == ''", StringComparison.OrdinalIgnoreCase) ||
191
+ string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
191
192
  if (hasEmptyCondition || conditionIsCheckingForEmptyString)
192
193
  {
193
194
  propertyInfo[property.Name] = property.Value;
@@ -285,7 +286,12 @@ internal static partial class MSBuildHelper
285
286
  return projectRoot;
286
287
  }
287
288
 
288
- private static async Task<string> CreateTempProjectAsync(DirectoryInfo tempDir, string repoRoot, string projectPath, string targetFramework, Dependency[] packages)
289
+ private static async Task<string> CreateTempProjectAsync(
290
+ DirectoryInfo tempDir,
291
+ string repoRoot,
292
+ string projectPath,
293
+ string targetFramework,
294
+ IReadOnlyCollection<Dependency> packages)
289
295
  {
290
296
  var projectDirectory = Path.GetDirectoryName(projectPath);
291
297
  projectDirectory ??= repoRoot;
@@ -299,39 +305,40 @@ internal static partial class MSBuildHelper
299
305
  var packageReferences = string.Join(
300
306
  Environment.NewLine,
301
307
  packages
302
- .Where(p => !string.IsNullOrWhiteSpace(p.Version)) // empty `Version` attributes will cause the temporary project to not build
308
+ // empty `Version` attributes will cause the temporary project to not build
309
+ .Where(p => !string.IsNullOrWhiteSpace(p.Version))
303
310
  // 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.
304
311
  .Select(static p => $"<PackageReference {(p.IsUpdate ? "Update" : "Include")}=\"{p.Name}\" Version=\"[{p.Version}]\" />"));
305
312
 
306
313
  var projectContents = $"""
307
- <Project Sdk="Microsoft.NET.Sdk">
308
- <PropertyGroup>
309
- <TargetFramework>{targetFramework}</TargetFramework>
310
- <GenerateDependencyFile>true</GenerateDependencyFile>
311
- <RunAnalyzers>false</RunAnalyzers>
312
- </PropertyGroup>
313
- <ItemGroup>
314
- {packageReferences}
315
- </ItemGroup>
316
- <Target Name="_CollectDependencies" DependsOnTargets="GenerateBuildDependencyFile">
317
- <ItemGroup>
318
- <_NuGetPackageData Include="@(NativeCopyLocalItems)" />
319
- <_NuGetPackageData Include="@(ResourceCopyLocalItems)" />
320
- <_NuGetPackageData Include="@(RuntimeCopyLocalItems)" />
321
- <_NuGetPackageData Include="@(ResolvedAnalyzers)" />
322
- <_NuGetPackageData Include="@(_PackageDependenciesDesignTime)">
323
- <NuGetPackageId>%(_PackageDependenciesDesignTime.Name)</NuGetPackageId>
324
- <NuGetPackageVersion>%(_PackageDependenciesDesignTime.Version)</NuGetPackageVersion>
325
- </_NuGetPackageData>
326
- </ItemGroup>
327
- </Target>
328
- <Target Name="_ReportDependencies" DependsOnTargets="_CollectDependencies">
329
- <Message Text="NuGetData::Package=%(_NuGetPackageData.NuGetPackageId), Version=%(_NuGetPackageData.NuGetPackageVersion)"
330
- Condition="'%(_NuGetPackageData.NuGetPackageId)' != '' AND '%(_NuGetPackageData.NuGetPackageVersion)' != ''"
331
- Importance="High" />
332
- </Target>
333
- </Project>
334
- """;
314
+ <Project Sdk="Microsoft.NET.Sdk">
315
+ <PropertyGroup>
316
+ <TargetFramework>{targetFramework}</TargetFramework>
317
+ <GenerateDependencyFile>true</GenerateDependencyFile>
318
+ <RunAnalyzers>false</RunAnalyzers>
319
+ </PropertyGroup>
320
+ <ItemGroup>
321
+ {packageReferences}
322
+ </ItemGroup>
323
+ <Target Name="_CollectDependencies" DependsOnTargets="GenerateBuildDependencyFile">
324
+ <ItemGroup>
325
+ <_NuGetPackageData Include="@(NativeCopyLocalItems)" />
326
+ <_NuGetPackageData Include="@(ResourceCopyLocalItems)" />
327
+ <_NuGetPackageData Include="@(RuntimeCopyLocalItems)" />
328
+ <_NuGetPackageData Include="@(ResolvedAnalyzers)" />
329
+ <_NuGetPackageData Include="@(_PackageDependenciesDesignTime)">
330
+ <NuGetPackageId>%(_PackageDependenciesDesignTime.Name)</NuGetPackageId>
331
+ <NuGetPackageVersion>%(_PackageDependenciesDesignTime.Version)</NuGetPackageVersion>
332
+ </_NuGetPackageData>
333
+ </ItemGroup>
334
+ </Target>
335
+ <Target Name="_ReportDependencies" DependsOnTargets="_CollectDependencies">
336
+ <Message Text="NuGetData::Package=%(_NuGetPackageData.NuGetPackageId), Version=%(_NuGetPackageData.NuGetPackageVersion)"
337
+ Condition="'%(_NuGetPackageData.NuGetPackageId)' != '' AND '%(_NuGetPackageData.NuGetPackageVersion)' != ''"
338
+ Importance="High" />
339
+ </Target>
340
+ </Project>
341
+ """;
335
342
  var tempProjectPath = Path.Combine(tempDir.FullName, "Project.csproj");
336
343
  await File.WriteAllTextAsync(tempProjectPath, projectContents);
337
344
 
@@ -344,7 +351,7 @@ internal static partial class MSBuildHelper
344
351
  }
345
352
 
346
353
  internal static async Task<Dependency[]> GetAllPackageDependenciesAsync(
347
- string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger? logger = null)
354
+ string repoRoot, string projectPath, string targetFramework, IReadOnlyCollection<Dependency> packages, Logger? logger = null)
348
355
  {
349
356
  var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_");
350
357
  try
@@ -368,7 +375,7 @@ internal static partial class MSBuildHelper
368
375
  else
369
376
  {
370
377
  logger?.Log($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}");
371
- return Array.Empty<Dependency>();
378
+ return [];
372
379
  }
373
380
  }
374
381
  finally
@@ -390,7 +397,7 @@ internal static partial class MSBuildHelper
390
397
 
391
398
  internal static async Task<ImmutableArray<ProjectBuildFile>> LoadBuildFiles(string repoRootPath, string projectPath)
392
399
  {
393
- var buildFileList = new List<string>()
400
+ var buildFileList = new List<string>
394
401
  {
395
402
  projectPath.NormalizePathToUnix() // always include the starting project
396
403
  };
@@ -409,12 +416,12 @@ internal static partial class MSBuildHelper
409
416
  // create a safe version with only certain top-level keys
410
417
  var globalJsonContent = await File.ReadAllTextAsync(safeGlobalJsonName);
411
418
  var json = JsonHelper.ParseNode(globalJsonContent);
412
- var sdks = json["msbuild-sdks"];
419
+ var sdks = json?["msbuild-sdks"];
413
420
  if (sdks is not null)
414
421
  {
415
422
  var newObject = new Dictionary<string, object>()
416
423
  {
417
- { "msbuild-sdks", sdks }
424
+ ["msbuild-sdks"] = sdks,
418
425
  };
419
426
  var newContent = JsonSerializer.Serialize(newObject);
420
427
  await File.WriteAllTextAsync(globalJsonPath, newContent);
@@ -426,7 +433,7 @@ internal static partial class MSBuildHelper
426
433
  // load the project even if it imports a file that doesn't exist (e.g. a file that's generated at restore
427
434
  // or build time).
428
435
  using var projectCollection = new ProjectCollection(); // do this in a one-off instance and don't pollute the global collection
429
- var project = Project.FromFile(projectPath, new ProjectOptions()
436
+ var project = Project.FromFile(projectPath, new ProjectOptions
430
437
  {
431
438
  LoadSettings = ProjectLoadSettings.IgnoreMissingImports,
432
439
  ProjectCollection = projectCollection,
@@ -10,6 +10,7 @@ internal static class PathHelper
10
10
  {
11
11
  MatchCasing = MatchCasing.CaseInsensitive,
12
12
  };
13
+
13
14
  private static readonly EnumerationOptions _caseSensitiveEnumerationOptions = new()
14
15
  {
15
16
  MatchCasing = MatchCasing.CaseSensitive,
@@ -39,6 +40,11 @@ internal static class PathHelper
39
40
  /// <returns>The path of the found file or null.</returns>
40
41
  public static string? GetFileInDirectoryOrParent(string initialPath, string rootPath, string fileName, bool caseSensitive = true)
41
42
  {
43
+ if (File.Exists(initialPath))
44
+ {
45
+ initialPath = Path.GetDirectoryName(initialPath)!;
46
+ }
47
+
42
48
  var candidatePaths = new List<string>();
43
49
  var rootDirectory = new DirectoryInfo(rootPath);
44
50
  var candidateDirectory = new DirectoryInfo(initialPath);
@@ -56,11 +62,18 @@ internal static class PathHelper
56
62
 
57
63
  foreach (var candidatePath in candidatePaths)
58
64
  {
59
- var files = Directory.EnumerateFiles(candidatePath, fileName, caseSensitive ? _caseSensitiveEnumerationOptions : _caseInsensitiveEnumerationOptions);
65
+ try
66
+ {
67
+ var files = Directory.EnumerateFiles(candidatePath, fileName, caseSensitive ? _caseSensitiveEnumerationOptions : _caseInsensitiveEnumerationOptions);
60
68
 
61
- if (files.Any())
69
+ if (files.Any())
70
+ {
71
+ return files.First();
72
+ }
73
+ }
74
+ catch (DirectoryNotFoundException)
62
75
  {
63
- return files.First();
76
+ // When searching for a file in a directory that doesn't exist, Directory.EnumerateFiles throws a DirectoryNotFoundException.
64
77
  }
65
78
  }
66
79
 
@@ -45,14 +45,14 @@ public static class ProcessEx
45
45
  // than enter right back into the Process type and start a wait which isn't guaranteed to be safe.
46
46
  Task.Run(() =>
47
47
  {
48
- redirectInitiated.Wait();
49
- redirectInitiated.Dispose();
50
- redirectInitiated = null;
48
+ redirectInitiated.Wait();
49
+ redirectInitiated.Dispose();
50
+ redirectInitiated = null;
51
51
 
52
- process.WaitForExit();
52
+ process.WaitForExit();
53
53
 
54
- tcs.TrySetResult((process.ExitCode, stdout.ToString(), stderr.ToString()));
55
- process.Dispose();
54
+ tcs.TrySetResult((process.ExitCode, stdout.ToString(), stderr.ToString()));
55
+ process.Dispose();
56
56
  });
57
57
  };
58
58
 
@@ -30,6 +30,17 @@ public static class XmlExtensions
30
30
  return element.Attributes.FirstOrDefault(a => a.Name.Equals(name, comparisonType));
31
31
  }
32
32
 
33
+ public static IXmlElementSyntax RemoveAttributeByName(this IXmlElementSyntax element, string attributeName, StringComparison comparisonType = StringComparison.Ordinal)
34
+ {
35
+ var attribute = element.GetAttribute(attributeName, comparisonType);
36
+ if (attribute is null)
37
+ {
38
+ return element;
39
+ }
40
+
41
+ return element.RemoveAttribute(attribute);
42
+ }
43
+
33
44
  public static string GetAttributeValue(this IXmlElementSyntax element, string name, StringComparison comparisonType)
34
45
  {
35
46
  return element.Attributes.First(a => a.Name.Equals(name, comparisonType)).Value;
@@ -133,17 +133,26 @@ public class ProjectBuildFileTests
133
133
  }
134
134
 
135
135
  [Theory]
136
- [InlineData( // no change made
137
- @"<Project><ItemGroup><Reference><HintPath>path\to\file.dll</HintPath></Reference></ItemGroup></Project>",
138
- @"<Project><ItemGroup><Reference><HintPath>path\to\file.dll</HintPath></Reference></ItemGroup></Project>"
136
+ // no change made
137
+ [InlineData(
138
+ // language=csproj
139
+ @"<Project><ItemGroup><Reference><HintPath>path\to\file.dll</HintPath></Reference></ItemGroup></Project>",
140
+ // language=csproj
141
+ @"<Project><ItemGroup><Reference><HintPath>path\to\file.dll</HintPath></Reference></ItemGroup></Project>"
139
142
  )]
140
- [InlineData( // change from `/` to `\`
141
- "<Project><ItemGroup><Reference><HintPath>path/to/file.dll</HintPath></Reference></ItemGroup></Project>",
142
- @"<Project><ItemGroup><Reference><HintPath>path\to\file.dll</HintPath></Reference></ItemGroup></Project>"
143
+ // change from `/` to `\`
144
+ [InlineData(
145
+ // language=csproj
146
+ "<Project><ItemGroup><Reference><HintPath>path/to/file.dll</HintPath></Reference></ItemGroup></Project>",
147
+ // language=csproj
148
+ @"<Project><ItemGroup><Reference><HintPath>path\to\file.dll</HintPath></Reference></ItemGroup></Project>"
143
149
  )]
144
- [InlineData( // multiple changes made
145
- "<Project><ItemGroup><Reference><HintPath>path1/to1/file1.dll</HintPath></Reference><Reference><HintPath>path2/to2/file2.dll</HintPath></Reference></ItemGroup></Project>",
146
- @"<Project><ItemGroup><Reference><HintPath>path1\to1\file1.dll</HintPath></Reference><Reference><HintPath>path2\to2\file2.dll</HintPath></Reference></ItemGroup></Project>"
150
+ // multiple changes made
151
+ [InlineData(
152
+ // language=csproj
153
+ "<Project><ItemGroup><Reference><HintPath>path1/to1/file1.dll</HintPath></Reference><Reference><HintPath>path2/to2/file2.dll</HintPath></Reference></ItemGroup></Project>",
154
+ // language=csproj
155
+ @"<Project><ItemGroup><Reference><HintPath>path1\to1\file1.dll</HintPath></Reference><Reference><HintPath>path2\to2\file2.dll</HintPath></Reference></ItemGroup></Project>"
147
156
  )]
148
157
  public void ReferenceHintPathsCanBeNormalized(string originalXml, string expectedXml)
149
158
  {
@@ -20,7 +20,7 @@ public class CompatibilityCheckerFacts
20
20
  [InlineData("net4.8", "netstandard1.3")]
21
21
  public void PackageContainsCompatibleFramework(string projectTfm, string packageTfm)
22
22
  {
23
- var result = CompatibilityChecker.IsCompatible(new[] { projectTfm }, new[] { packageTfm }, new Logger(verbose: true));
23
+ var result = CompatibilityChecker.IsCompatible([projectTfm], [packageTfm], new Logger(verbose: true));
24
24
 
25
25
  Assert.True(result);
26
26
  }
@@ -37,7 +37,7 @@ public class CompatibilityCheckerFacts
37
37
  [InlineData("net7.0", "net48")]
38
38
  public void PackageContainsIncompatibleFramework(string projectTfm, string packageTfm)
39
39
  {
40
- var result = CompatibilityChecker.IsCompatible(new[] { projectTfm }, new[] { packageTfm }, new Logger(verbose: true));
40
+ var result = CompatibilityChecker.IsCompatible([projectTfm], [packageTfm], new Logger(verbose: true));
41
41
 
42
42
  Assert.False(result);
43
43
  }