dependabot-nuget 0.247.0 → 0.249.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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +57 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +1 -1
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +25 -5
  5. data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +1 -0
  6. data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +2 -0
  7. data/lib/dependabot/nuget/file_fetcher.rb +12 -8
  8. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +1 -0
  9. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +1 -0
  10. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +1 -0
  11. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +1 -0
  12. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +2 -0
  13. data/lib/dependabot/nuget/file_parser.rb +42 -11
  14. data/lib/dependabot/nuget/file_updater/property_value_updater.rb +1 -0
  15. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +10 -1
  16. data/lib/dependabot/nuget/requirement.rb +17 -8
  17. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +28 -7
  18. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +70 -19
  19. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +76 -8
  20. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +25 -3
  21. data/lib/dependabot/nuget/update_checker/property_updater.rb +108 -44
  22. data/lib/dependabot/nuget/update_checker/repository_finder.rb +90 -18
  23. data/lib/dependabot/nuget/update_checker/requirements_updater.rb +32 -9
  24. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +8 -3
  25. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +51 -13
  26. data/lib/dependabot/nuget/update_checker/version_finder.rb +167 -62
  27. data/lib/dependabot/nuget/update_checker.rb +73 -29
  28. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e8dda5e47b38dec152ba8797986aaaca62fde7dbed194960864b779932d6ff7
4
- data.tar.gz: 4f1cb76b3dafc332d90ddf7acec0b39c6295d2d9749741e3229af6ad7d7e363d
3
+ metadata.gz: c23304dabfd9e4506478efc6188332716cc7ff097f8042b45c67bfda9cf1b6ae
4
+ data.tar.gz: 74f8d095b039666ca96e098913667e8c40552df5963187cf4ae66a86ac429ae1
5
5
  SHA512:
6
- metadata.gz: c350131e183da643b7caf57a76acc2cb954e9d7f0a4532b340db90a064f0e1b7d735a531d2bea8efea6ee483060b3dbb8efb4a8e1567d2c9d30d88bc907848f2
7
- data.tar.gz: 9f2a6505e1eca5a2fbe8369e8e21ce0b5eabc4c658bec9d628b5250c16f78f40a7c8b19bc0778b2b7fe6e0c3b215a71b2282407544eb0f6808bdfde3da35d8e0
6
+ metadata.gz: 904dae0561b8288c0cc8b319269a6352cdfa1d4d443e01b052e99bf3294277523f1a227944a7787024a216ad1661a21647330e116f6e5a9c66d95cbd630d7f47
7
+ data.tar.gz: 75cff9a26706bc60d8f5423d8e096fe4b1a1ccf20c3bb3fff5a5c6428ac6cfdd3b58e0a9ce490a9fb6577e852bd59417917bde19ad3697ed8d76aa789bdb4591
@@ -3,6 +3,8 @@ using System.IO;
3
3
  using System.Text;
4
4
  using System.Threading.Tasks;
5
5
 
6
+ using NuGetUpdater.Core;
7
+ using NuGetUpdater.Core.Test;
6
8
  using NuGetUpdater.Core.Test.Update;
7
9
 
8
10
  using Xunit;
@@ -291,6 +293,61 @@ public partial class EntryPointTests
291
293
  );
292
294
  }
293
295
 
296
+ [Fact]
297
+ public async Task UpdaterDoesNotUseRepoGlobalJsonForMSBuildTasks()
298
+ {
299
+ // This is a _very_ specific scenario where the `NuGetUpdater.Cli` tool might pick up a `global.json` from
300
+ // the root of the repo under test and use it's `sdk` property when trying to locate MSBuild. To properly
301
+ // test this, it must be tested in a new process where MSBuild has not been loaded yet and the runner tool
302
+ // must be started with its working directory at the test repo's root.
303
+ using var tempDir = new TemporaryDirectory();
304
+ await File.WriteAllTextAsync(Path.Join(tempDir.DirectoryPath, "global.json"), """
305
+ {
306
+ "sdk": {
307
+ "version": "99.99.99"
308
+ }
309
+ }
310
+ """);
311
+ await File.WriteAllTextAsync(Path.Join(tempDir.DirectoryPath, "project.csproj"), """
312
+ <Project Sdk="Microsoft.NET.Sdk">
313
+ <PropertyGroup>
314
+ <TargetFramework>net8.0</TargetFramework>
315
+ </PropertyGroup>
316
+ <ItemGroup>
317
+ <PackageReference Include="Newtonsoft.Json" Version="7.0.1" />
318
+ </ItemGroup>
319
+ </Project>
320
+ """);
321
+ var executableName = $"NuGetUpdater.Cli{(Environment.OSVersion.Platform == PlatformID.Win32NT ? ".exe" : "")}";
322
+ var executableArgs = string.Join(" ",
323
+ [
324
+ "update",
325
+ "--repo-root",
326
+ tempDir.DirectoryPath,
327
+ "--solution-or-project",
328
+ Path.Join(tempDir.DirectoryPath, "project.csproj"),
329
+ "--dependency",
330
+ "Newtonsoft.Json",
331
+ "--new-version",
332
+ "13.0.1",
333
+ "--previous-version",
334
+ "7.0.1",
335
+ "--verbose"
336
+ ]);
337
+
338
+ // verify base run
339
+ var (exitCode, output, error) = await ProcessEx.RunAsync(executableName, executableArgs, workingDirectory: tempDir.DirectoryPath);
340
+ Assert.True(exitCode == 0, $"Error running update on unsupported SDK.\nSTDOUT:\n{output}\nSTDERR:\n{error}");
341
+
342
+ // verify project update
343
+ var updatedProjectContents = await File.ReadAllTextAsync(Path.Join(tempDir.DirectoryPath, "project.csproj"));
344
+ Assert.Contains("13.0.1", updatedProjectContents);
345
+
346
+ // verify `global.json` untouched
347
+ var updatedGlobalJsonContents = await File.ReadAllTextAsync(Path.Join(tempDir.DirectoryPath, "global.json"));
348
+ Assert.Contains("99.99.99", updatedGlobalJsonContents);
349
+ }
350
+
294
351
  private static async Task Run(Func<string, string[]> getArgs, (string Path, string Content)[] initialFiles, (string, string)[] expectedFiles)
295
352
  {
296
353
  var actualFiles = await RunUpdate(initialFiles, async path =>
@@ -231,7 +231,7 @@ internal static class SdkPackageUpdater
231
231
  logger.Log($" Adding [{dependencyName}/{newDependencyVersion}] as a top-level package reference.");
232
232
 
233
233
  // see https://learn.microsoft.com/nuget/consume-packages/install-use-packages-dotnet-cli
234
- var (exitCode, _, _) = await ProcessEx.RunAsync("dotnet", $"add {projectPath} package {dependencyName} --version {newDependencyVersion}");
234
+ var (exitCode, _, _) = await ProcessEx.RunAsync("dotnet", $"add {projectPath} package {dependencyName} --version {newDependencyVersion}", workingDirectory: Path.GetDirectoryName(projectPath));
235
235
  if (exitCode != 0)
236
236
  {
237
237
  logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.");
@@ -41,9 +41,29 @@ internal static partial class MSBuildHelper
41
41
  // Ensure MSBuild types are registered before calling a method that loads the types
42
42
  if (!IsMSBuildRegistered)
43
43
  {
44
- var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
45
- MSBuildPath = defaultInstance.MSBuildPath;
46
- MSBuildLocator.RegisterInstance(defaultInstance);
44
+ var globalJsonPath = "global.json";
45
+ var tempGlobalJsonPath = globalJsonPath + Guid.NewGuid().ToString();
46
+ var globalJsonExists = File.Exists(globalJsonPath);
47
+ try
48
+ {
49
+ if (globalJsonExists)
50
+ {
51
+ Console.WriteLine("Temporarily removing `global.json` for MSBuild detection.");
52
+ File.Move(globalJsonPath, tempGlobalJsonPath);
53
+ }
54
+
55
+ var defaultInstance = MSBuildLocator.QueryVisualStudioInstances().First();
56
+ MSBuildPath = defaultInstance.MSBuildPath;
57
+ MSBuildLocator.RegisterInstance(defaultInstance);
58
+ }
59
+ finally
60
+ {
61
+ if (globalJsonExists)
62
+ {
63
+ Console.WriteLine("Restoring `global.json` after MSBuild detection.");
64
+ File.Move(tempGlobalJsonPath, globalJsonPath);
65
+ }
66
+ }
47
67
  }
48
68
  }
49
69
 
@@ -311,7 +331,7 @@ internal static partial class MSBuildHelper
311
331
  try
312
332
  {
313
333
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
314
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"");
334
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
315
335
 
316
336
  // NU1608: Detected package version outside of dependency constraint
317
337
 
@@ -451,7 +471,7 @@ internal static partial class MSBuildHelper
451
471
  {
452
472
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
453
473
 
454
- var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"build \"{tempProjectPath}\" /t:_ReportDependencies");
474
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"build \"{tempProjectPath}\" /t:_ReportDependencies", workingDirectory: tempDirectory.FullName);
455
475
 
456
476
  if (exitCode == 0)
457
477
  {
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "nokogiri"
5
5
  require "pathname"
6
+ require "sorbet-runtime"
6
7
 
7
8
  require "dependabot/nuget/file_fetcher"
8
9
 
@@ -2,6 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "pathname"
5
+ require "sorbet-runtime"
6
+
5
7
  require "dependabot/nuget/file_fetcher"
6
8
 
7
9
  module Dependabot
@@ -25,7 +25,7 @@ module Dependabot
25
25
  return true if filenames.any? { |f| f.match?("^src$") }
26
26
  return true if filenames.any? { |f| f.end_with?(".proj") }
27
27
 
28
- filenames.any? { |name| name.match?(/\.[a-z]{2}proj$/) }
28
+ filenames.any? { |name| name.match?(/\.(cs|vb|fs)proj$/) }
29
29
  end
30
30
 
31
31
  sig { override.returns(String) }
@@ -53,7 +53,7 @@ module Dependabot
53
53
  end
54
54
 
55
55
  sig { override.returns(T::Array[DependencyFile]) }
56
- def fetch_files # rubocop:disable Metrics/AbcSize
56
+ def fetch_files
57
57
  fetched_files = []
58
58
  fetched_files += project_files
59
59
  fetched_files += directory_build_files
@@ -73,10 +73,7 @@ module Dependabot
73
73
  if project_files.none? && packages_config_files.none?
74
74
  raise T.must(@missing_sln_project_file_errors.first) if @missing_sln_project_file_errors&.any?
75
75
 
76
- raise(
77
- Dependabot::DependencyFileNotFound,
78
- File.join(directory, "<anything>.(cs|vb|fs)proj")
79
- )
76
+ raise_dependency_file_not_found
80
77
  end
81
78
 
82
79
  fetched_files
@@ -102,9 +99,16 @@ module Dependabot
102
99
  project_files
103
100
  end
104
101
  rescue Octokit::NotFound, Gitlab::Error::NotFound
102
+ raise_dependency_file_not_found
103
+ end
104
+
105
+ sig { returns(T.noreturn) }
106
+ def raise_dependency_file_not_found
105
107
  raise(
106
- Dependabot::DependencyFileNotFound,
107
- File.join(directory, "<anything>.(cs|vb|fs)proj")
108
+ Dependabot::DependencyFileNotFound.new(
109
+ File.join(directory, "*.(sln|csproj|vbproj|fsproj|proj)"),
110
+ "Unable to find `*.sln`, `*.(cs|vb|fs)proj`, or `*.proj` in directory `#{directory}`"
111
+ )
108
112
  )
109
113
  end
110
114
 
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "json"
5
+ require "sorbet-runtime"
5
6
 
6
7
  require "dependabot/dependency"
7
8
  require "dependabot/nuget/file_parser"
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "json"
5
+ require "sorbet-runtime"
5
6
 
6
7
  require "dependabot/dependency"
7
8
  require "dependabot/nuget/file_parser"
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
5
+ require "sorbet-runtime"
5
6
 
6
7
  require "dependabot/dependency"
7
8
  require "dependabot/nuget/file_parser"
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
5
+ require "sorbet-runtime"
5
6
 
6
7
  require "dependabot/dependency"
7
8
  require "dependabot/nuget/file_parser"
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  require "dependabot/nuget/file_fetcher/import_paths_finder"
5
7
  require "dependabot/nuget/file_parser"
6
8
 
@@ -44,6 +44,16 @@ module Dependabot
44
44
  Dependabot.logger.warn "Dependency '#{d.name}' excluded due to unparsable version: #{d.version}"
45
45
  end
46
46
 
47
+ dependency_info = dependencies.map do |d|
48
+ requirements_info = d.requirements.filter_map { |r| " file: #{r[:file]}, metadata: #{r[:metadata]}" }
49
+ .join("\n")
50
+ " name: #{d.name}, version: #{d.version}\n#{requirements_info}"
51
+ end.join("\n")
52
+
53
+ if dependencies.length.positive?
54
+ Dependabot.logger.info "The following dependencies were found:\n#{dependency_info}"
55
+ end
56
+
47
57
  dependencies
48
58
  end
49
59
 
@@ -53,9 +63,18 @@ module Dependabot
53
63
  def project_file_dependencies
54
64
  dependency_set = DependencySet.new
55
65
 
56
- (project_files + project_import_files).each do |file|
57
- parser = project_file_parser
58
- dependency_set += parser.dependency_set(project_file: file)
66
+ project_files.each do |project_file|
67
+ tfms = project_file_parser.target_frameworks(project_file: project_file)
68
+ unless tfms.any?
69
+ Dependabot.logger.warn "Excluding project file '#{project_file.name}' due to unresolvable target framework"
70
+ next
71
+ end
72
+
73
+ dependency_set += project_file_parser.dependency_set(project_file: project_file)
74
+ end
75
+
76
+ proj_files.each do |proj_file|
77
+ dependency_set += project_file_parser.dependency_set(project_file: proj_file)
59
78
  end
60
79
 
61
80
  dependency_set
@@ -99,14 +118,21 @@ module Dependabot
99
118
  )
100
119
  end
101
120
 
121
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
122
+ def proj_files
123
+ projfile = /\.proj$/
124
+
125
+ dependency_files.select do |df|
126
+ df.name.match?(projfile)
127
+ end
128
+ end
129
+
102
130
  sig { returns(T::Array[Dependabot::DependencyFile]) }
103
131
  def project_files
104
- projfile = /\.([a-z]{2})?proj$/
105
- packageprops = /[Dd]irectory.[Pp]ackages.props/
132
+ projectfile = /\.(cs|vb|fs)proj$/
106
133
 
107
134
  dependency_files.select do |df|
108
- df.name.match?(projfile) ||
109
- df.name.match?(packageprops)
135
+ df.name.match?(projectfile)
110
136
  end
111
137
  end
112
138
 
@@ -134,19 +160,24 @@ module Dependabot
134
160
 
135
161
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
136
162
  def global_json
137
- dependency_files.find { |f| f.name.casecmp("global.json")&.zero? }
163
+ dependency_files.find { |f| f.name.casecmp?("global.json") }
138
164
  end
139
165
 
140
166
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
141
167
  def dotnet_tools_json
142
- dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json")&.zero? }
168
+ dependency_files.find { |f| f.name.casecmp?(".config/dotnet-tools.json") }
143
169
  end
144
170
 
145
171
  sig { override.void }
146
172
  def check_required_files
147
- return if project_files.any? || packages_config_files.any?
173
+ if project_files.any? || proj_files.any? || packages_config_files.any? || global_json || dotnet_tools_json
174
+ return
175
+ end
148
176
 
149
- raise "No project file or packages.config!"
177
+ raise Dependabot::DependencyFileNotFound.new(
178
+ "*.(cs|vb|fs)proj, *.proj, packages.config, global.json, dotnet-tools.json",
179
+ "No project file, *.proj, packages.config, global.json, or dotnet-tools.json!"
180
+ )
150
181
  end
151
182
  end
152
183
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
5
+ require "sorbet-runtime"
5
6
 
6
7
  require "dependabot/dependency_file"
7
8
  require "dependabot/nuget/file_updater"
@@ -1,18 +1,25 @@
1
- # typed: true
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  module Dependabot
5
7
  module Nuget
6
8
  module NuGetConfigCredentialHelpers
9
+ extend T::Sig
10
+
11
+ sig { returns(String) }
7
12
  def self.user_nuget_config_path
8
13
  home_directory = Dir.home
9
14
  File.join(home_directory, ".nuget", "NuGet", "NuGet.Config")
10
15
  end
11
16
 
17
+ sig { returns(String) }
12
18
  def self.temporary_nuget_config_path
13
19
  user_nuget_config_path + "_ORIGINAL"
14
20
  end
15
21
 
22
+ sig { params(credentials: T::Array[Dependabot::Credential]).void }
16
23
  def self.add_credentials_to_nuget_config(credentials)
17
24
  return unless File.exist?(user_nuget_config_path)
18
25
 
@@ -48,6 +55,7 @@ module Dependabot
48
55
  File.write(user_nuget_config_path, nuget_config)
49
56
  end
50
57
 
58
+ sig { void }
51
59
  def self.restore_user_nuget_config
52
60
  return unless File.exist?(temporary_nuget_config_path)
53
61
 
@@ -55,6 +63,7 @@ module Dependabot
55
63
  File.rename(temporary_nuget_config_path, user_nuget_config_path)
56
64
  end
57
65
 
66
+ sig { params(credentials: T::Array[Dependabot::Credential], _block: T.proc.void).void }
58
67
  def self.patch_nuget_config_for_action(credentials, &_block)
59
68
  add_credentials_to_nuget_config(credentials)
60
69
  begin
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -12,6 +12,9 @@ require "dependabot/nuget/version"
12
12
  module Dependabot
13
13
  module Nuget
14
14
  class Requirement < Dependabot::Requirement
15
+ extend T::Sig
16
+
17
+ sig { override.params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
15
18
  def self.parse(obj)
16
19
  return ["=", Nuget::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
17
20
 
@@ -28,11 +31,12 @@ module Dependabot
28
31
  # For consistency with other languages, we define a requirements array.
29
32
  # Dotnet doesn't have an `OR` separator for requirements, so it always
30
33
  # contains a single element.
31
- sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
34
+ sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Dependabot::Requirement]) }
32
35
  def self.requirements_array(requirement_string)
33
36
  [new(requirement_string)]
34
37
  end
35
38
 
39
+ sig { params(requirements: T.any(T.nilable(String), T::Array[T.nilable(String)])).void }
36
40
  def initialize(*requirements)
37
41
  requirements = requirements.flatten.flat_map do |req_string|
38
42
  convert_dotnet_constraint_to_ruby_constraint(req_string)
@@ -41,6 +45,7 @@ module Dependabot
41
45
  super(requirements)
42
46
  end
43
47
 
48
+ sig { override.params(version: Gem::Version).returns(T::Boolean) }
44
49
  def satisfied_by?(version)
45
50
  version = Nuget::Version.new(version.to_s)
46
51
  super
@@ -48,10 +53,11 @@ module Dependabot
48
53
 
49
54
  private
50
55
 
56
+ sig { params(req_string: T.nilable(String)).returns(T.nilable(T.any(String, T::Array[String]))) }
51
57
  def convert_dotnet_constraint_to_ruby_constraint(req_string)
52
58
  return unless req_string
53
59
 
54
- return convert_dotnet_range_to_ruby_range(req_string) if req_string&.start_with?("(", "[")
60
+ return convert_dotnet_range_to_ruby_range(req_string) if req_string.start_with?("(", "[")
55
61
 
56
62
  return req_string.split(",").map(&:strip) if req_string.include?(",")
57
63
 
@@ -61,6 +67,7 @@ module Dependabot
61
67
  end
62
68
 
63
69
  # rubocop:disable Metrics/PerceivedComplexity
70
+ sig { params(req_string: String).returns(T::Array[String]) }
64
71
  def convert_dotnet_range_to_ruby_range(req_string)
65
72
  lower_b, upper_b = req_string.split(",").map(&:strip).map do |bound|
66
73
  next convert_range_wildcard_req(bound) if bound.include?("*")
@@ -70,9 +77,9 @@ module Dependabot
70
77
 
71
78
  lower_b =
72
79
  if ["(", "["].include?(lower_b) then nil
73
- elsif lower_b.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}"
80
+ elsif T.must(lower_b).start_with?("(") then "> #{T.must(lower_b).sub(/\(\s*/, '')}"
74
81
  else
75
- ">= #{lower_b.sub(/\[\s*/, '').strip}"
82
+ ">= #{T.must(lower_b).sub(/\[\s*/, '').strip}"
76
83
  end
77
84
 
78
85
  upper_b =
@@ -87,20 +94,22 @@ module Dependabot
87
94
  end
88
95
  # rubocop:enable Metrics/PerceivedComplexity
89
96
 
97
+ sig { params(req_string: String).returns(String) }
90
98
  def convert_range_wildcard_req(req_string)
91
- range_end = req_string[-1]
92
- defined_part = req_string.split("*").first
99
+ range_end = T.must(req_string[-1])
100
+ defined_part = T.must(req_string.split("*").first)
93
101
  version = defined_part + "0"
94
102
  version += range_end if [")", "]"].include?(range_end)
95
103
  version
96
104
  end
97
105
 
106
+ sig { params(req_string: String).returns(String) }
98
107
  def convert_wildcard_req(req_string)
99
108
  return ">= 0-a" if req_string == "*-*"
100
109
 
101
110
  return ">= 0" if req_string.start_with?("*")
102
111
 
103
- defined_part = req_string.split("*").first
112
+ defined_part = T.must(req_string.split("*").first)
104
113
  suffix = defined_part.end_with?(".") ? "0" : "a"
105
114
  version = defined_part + suffix
106
115
  "~> #{version}"
@@ -1,22 +1,34 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  require "dependabot/update_checkers/base"
5
7
 
6
8
  module Dependabot
7
9
  module Nuget
8
10
  class CompatibilityChecker
11
+ extend T::Sig
12
+
9
13
  require_relative "nuspec_fetcher"
10
14
  require_relative "nupkg_fetcher"
11
15
  require_relative "tfm_finder"
12
16
  require_relative "tfm_comparer"
13
17
 
18
+ sig do
19
+ params(
20
+ dependency_urls: T::Array[T::Hash[Symbol, String]],
21
+ dependency: Dependabot::Dependency,
22
+ tfm_finder: Dependabot::Nuget::TfmFinder
23
+ ).void
24
+ end
14
25
  def initialize(dependency_urls:, dependency:, tfm_finder:)
15
26
  @dependency_urls = dependency_urls
16
27
  @dependency = dependency
17
28
  @tfm_finder = tfm_finder
18
29
  end
19
30
 
31
+ sig { params(version: String).returns(T::Boolean) }
20
32
  def compatible?(version)
21
33
  nuspec_xml = NuspecFetcher.fetch_nuspec(dependency_urls, dependency.name, version)
22
34
  return false unless nuspec_xml
@@ -32,15 +44,23 @@ module Dependabot
32
44
  return true if package_tfms.nil?
33
45
  return false if package_tfms.empty?
34
46
 
35
- return false if project_tfms.nil? || project_tfms.empty?
47
+ return false if project_tfms.nil? || project_tfms&.empty?
36
48
 
37
- TfmComparer.are_frameworks_compatible?(project_tfms, package_tfms)
49
+ TfmComparer.are_frameworks_compatible?(T.must(project_tfms), package_tfms)
38
50
  end
39
51
 
40
52
  private
41
53
 
42
- attr_reader :dependency_urls, :dependency, :tfm_finder
54
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
55
+ attr_reader :dependency_urls
43
56
 
57
+ sig { returns(Dependabot::Dependency) }
58
+ attr_reader :dependency
59
+
60
+ sig { returns(Dependabot::Nuget::TfmFinder) }
61
+ attr_reader :tfm_finder
62
+
63
+ sig { params(nuspec_xml: Nokogiri::XML::Document).returns(T::Boolean) }
44
64
  def pure_development_dependency?(nuspec_xml)
45
65
  contents = nuspec_xml.at_xpath("package/metadata/developmentDependency")&.content&.strip
46
66
  return false unless contents # no `developmentDependency` element
@@ -55,16 +75,17 @@ module Dependabot
55
75
  dependency_groups_with_target_framework.to_a.empty?
56
76
  end
57
77
 
78
+ sig { params(nuspec_xml: Nokogiri::XML::Document).returns(T::Array[String]) }
58
79
  def parse_package_tfms(nuspec_xml)
59
80
  nuspec_xml.xpath("//dependencies/group").filter_map { |group| group.attribute("targetFramework") }
60
81
  end
61
82
 
83
+ sig { returns(T.nilable(T::Array[String])) }
62
84
  def project_tfms
63
- return @project_tfms if defined?(@project_tfms)
64
-
65
- @project_tfms = tfm_finder.frameworks(dependency)
85
+ @project_tfms ||= T.let(tfm_finder.frameworks(dependency), T.nilable(T::Array[String]))
66
86
  end
67
87
 
88
+ sig { params(dependency_version: String).returns(T.nilable(T::Array[String])) }
68
89
  def fetch_package_tfms(dependency_version)
69
90
  cache = CacheManager.cache("compatibility_checker_tfms_cache")
70
91
  key = "#{dependency.name}::#{dependency_version}"