dependabot-nuget 0.247.0 → 0.249.0

Sign up to get free protection for your applications and to get access to all the features.
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}"