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
@@ -27,6 +27,8 @@ module Dependabot
27
27
 
28
28
  PROJECT_REFERENCE_SELECTOR = "ItemGroup > ProjectReference"
29
29
 
30
+ PROJECT_FILE_SELECTOR = "ItemGroup > ProjectFile"
31
+
30
32
  PACKAGE_REFERENCE_SELECTOR = "ItemGroup > PackageReference, " \
31
33
  "ItemGroup > GlobalPackageReference"
32
34
 
@@ -56,6 +58,24 @@ module Dependabot
56
58
  cache[key] ||= parse_dependencies(project_file)
57
59
  end
58
60
 
61
+ def downstream_file_references(project_file:)
62
+ file_set = Set.new
63
+
64
+ doc = Nokogiri::XML(project_file.content)
65
+ doc.remove_namespaces!
66
+ proj_refs = doc.css(PROJECT_REFERENCE_SELECTOR)
67
+ proj_files = doc.css(PROJECT_FILE_SELECTOR)
68
+ ref_nodes = proj_refs + proj_files
69
+ ref_nodes.each do |project_reference_node|
70
+ dep_file = get_attribute_value(project_reference_node, "Include")
71
+ full_project_path = full_path(project_file, dep_file)
72
+ full_project_path = full_project_path[1..-1] if full_project_path.start_with?("/")
73
+ file_set << full_project_path if full_project_path
74
+ end
75
+
76
+ file_set
77
+ end
78
+
59
79
  def target_frameworks(project_file:)
60
80
  target_framework = details_for_property("TargetFramework", project_file)
61
81
  return [target_framework&.fetch(:value)] if target_framework
@@ -80,6 +100,21 @@ module Dependabot
80
100
 
81
101
  attr_reader :dependency_files, :credentials
82
102
 
103
+ def full_path(project_file, ref_path)
104
+ project_file_directory = File.dirname(project_file.name)
105
+ is_rooted = project_file_directory.start_with?("/")
106
+ # Root the directory path to avoid expand_path prepending the working directory
107
+ project_file_directory = "/" + project_file_directory unless is_rooted
108
+
109
+ # normalize path separators
110
+ relative_path = ref_path.tr("\\", "/")
111
+ # path is relative to the project file directory
112
+ relative_path = File.join(project_file_directory, relative_path)
113
+ result = File.expand_path(relative_path)
114
+ result = result[1..-1] unless is_rooted
115
+ result
116
+ end
117
+
83
118
  def parse_dependencies(project_file)
84
119
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
85
120
 
@@ -131,27 +166,20 @@ module Dependabot
131
166
  end
132
167
 
133
168
  def add_transitive_dependencies_from_project_references(project_file, doc, dependency_set)
134
- project_file_directory = File.dirname(project_file.name)
135
- is_rooted = project_file_directory.start_with?("/")
136
- # Root the directory path to avoid expand_path prepending the working directory
137
- project_file_directory = "/" + project_file_directory unless is_rooted
138
-
139
169
  # Look for regular project references
140
- doc.css(PROJECT_REFERENCE_SELECTOR).each do |reference_node|
170
+ project_refs = doc.css(PROJECT_REFERENCE_SELECTOR)
171
+ # Look for ProjectFile references (dirs.proj)
172
+ project_files = doc.css(PROJECT_FILE_SELECTOR)
173
+ ref_nodes = project_refs + project_files
174
+
175
+ ref_nodes.each do |reference_node|
141
176
  relative_path = dependency_name(reference_node, project_file)
142
177
  # This could result from a <ProjectReference Remove="..." /> item.
143
178
  next unless relative_path
144
179
 
145
- # normalize path separators
146
- relative_path = relative_path.tr("\\", "/")
147
- # path is relative to the project file directory
148
- relative_path = File.join(project_file_directory, relative_path)
180
+ full_project_path = full_path(project_file, relative_path)
149
181
 
150
- # get absolute path
151
- full_path = File.expand_path(relative_path)
152
- full_path = full_path[1..-1] unless is_rooted
153
-
154
- referenced_file = dependency_files.find { |f| f.name == full_path }
182
+ referenced_file = dependency_files.find { |f| f.name == full_project_path }
155
183
  next unless referenced_file
156
184
 
157
185
  dependency_set(project_file: referenced_file).dependencies.each do |dep|
@@ -279,14 +307,12 @@ module Dependabot
279
307
  end
280
308
 
281
309
  def dependency_has_search_results?(dependency)
282
- dependency_urls = UpdateChecker::RepositoryFinder.new(
310
+ dependency_urls = RepositoryFinder.new(
283
311
  dependency: dependency,
284
312
  credentials: credentials,
285
313
  config_files: nuget_configs
286
314
  ).dependency_urls
287
- if dependency_urls.empty?
288
- dependency_urls = [UpdateChecker::RepositoryFinder.get_default_repository_details(dependency.name)]
289
- end
315
+ dependency_urls = [RepositoryFinder.get_default_repository_details(dependency.name)] if dependency_urls.empty?
290
316
  dependency_urls.any? do |dependency_url|
291
317
  dependency_url_has_matching_result?(dependency.name, dependency_url)
292
318
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
@@ -6,12 +6,15 @@ require "nokogiri"
6
6
  require "dependabot/dependency"
7
7
  require "dependabot/file_parsers"
8
8
  require "dependabot/file_parsers/base"
9
+ require "sorbet-runtime"
9
10
 
10
11
  # For details on how dotnet handles version constraints, see:
11
12
  # https://docs.microsoft.com/en-us/nuget/reference/package-versioning
12
13
  module Dependabot
13
14
  module Nuget
14
15
  class FileParser < Dependabot::FileParsers::Base
16
+ extend T::Sig
17
+
15
18
  require "dependabot/file_parsers/base/dependency_set"
16
19
  require_relative "file_parser/project_file_parser"
17
20
  require_relative "file_parser/packages_config_parser"
@@ -20,6 +23,7 @@ module Dependabot
20
23
 
21
24
  PACKAGE_CONF_DEPENDENCY_SELECTOR = "packages > packages"
22
25
 
26
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
23
27
  def parse
24
28
  dependency_set = DependencySet.new
25
29
  dependency_set += project_file_dependencies
@@ -31,6 +35,7 @@ module Dependabot
31
35
 
32
36
  private
33
37
 
38
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
34
39
  def project_file_dependencies
35
40
  dependency_set = DependencySet.new
36
41
 
@@ -42,6 +47,7 @@ module Dependabot
42
47
  dependency_set
43
48
  end
44
49
 
50
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
45
51
  def packages_config_dependencies
46
52
  dependency_set = DependencySet.new
47
53
 
@@ -53,26 +59,32 @@ module Dependabot
53
59
  dependency_set
54
60
  end
55
61
 
62
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
56
63
  def global_json_dependencies
57
64
  return DependencySet.new unless global_json
58
65
 
59
66
  GlobalJsonParser.new(global_json: global_json).dependency_set
60
67
  end
61
68
 
69
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
62
70
  def dotnet_tools_json_dependencies
63
71
  return DependencySet.new unless dotnet_tools_json
64
72
 
65
73
  DotNetToolsJsonParser.new(dotnet_tools_json: dotnet_tools_json).dependency_set
66
74
  end
67
75
 
76
+ sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) }
68
77
  def project_file_parser
69
- @project_file_parser ||=
78
+ @project_file_parser ||= T.let(
70
79
  ProjectFileParser.new(
71
80
  dependency_files: dependency_files,
72
81
  credentials: credentials
73
- )
82
+ ),
83
+ T.nilable(Dependabot::Nuget::FileParser::ProjectFileParser)
84
+ )
74
85
  end
75
86
 
87
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
76
88
  def project_files
77
89
  projfile = /\.([a-z]{2})?proj$/
78
90
  packageprops = /[Dd]irectory.[Pp]ackages.props/
@@ -83,12 +95,14 @@ module Dependabot
83
95
  end
84
96
  end
85
97
 
98
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
86
99
  def packages_config_files
87
100
  dependency_files.select do |f|
88
101
  f.name.split("/").last&.casecmp("packages.config")&.zero?
89
102
  end
90
103
  end
91
104
 
105
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
92
106
  def project_import_files
93
107
  dependency_files -
94
108
  project_files -
@@ -98,18 +112,22 @@ module Dependabot
98
112
  [dotnet_tools_json]
99
113
  end
100
114
 
115
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
101
116
  def nuget_configs
102
117
  dependency_files.select { |f| f.name.match?(/nuget\.config$/i) }
103
118
  end
104
119
 
120
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
105
121
  def global_json
106
122
  dependency_files.find { |f| f.name.casecmp("global.json")&.zero? }
107
123
  end
108
124
 
125
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
109
126
  def dotnet_tools_json
110
127
  dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json")&.zero? }
111
128
  end
112
129
 
130
+ sig { override.void }
113
131
  def check_required_files
114
132
  return if project_files.any? || packages_config_files.any?
115
133
 
@@ -5,10 +5,13 @@ require "dependabot/dependency_file"
5
5
  require "dependabot/file_updaters"
6
6
  require "dependabot/file_updaters/base"
7
7
  require "dependabot/nuget/native_helpers"
8
+ require "sorbet-runtime"
8
9
 
9
10
  module Dependabot
10
11
  module Nuget
11
12
  class FileUpdater < Dependabot::FileUpdaters::Base
13
+ extend T::Sig
14
+
12
15
  require_relative "file_updater/property_value_updater"
13
16
  require_relative "file_parser/project_file_parser"
14
17
  require_relative "file_parser/dotnet_tools_json_parser"
@@ -54,6 +57,7 @@ module Dependabot
54
57
 
55
58
  def try_update_projects(dependency)
56
59
  update_ran = T.let(false, T::Boolean)
60
+ checked_files = Set.new
57
61
 
58
62
  # run update for each project file
59
63
  project_files.each do |project_file|
@@ -62,9 +66,17 @@ module Dependabot
62
66
 
63
67
  next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? }
64
68
 
65
- NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path,
66
- dependency: dependency, is_transitive: !dependency.top_level?,
67
- credentials: credentials)
69
+ next unless repo_contents_path
70
+
71
+ checked_key = "#{project_file.name}-#{dependency.name}#{dependency.version}"
72
+ call_nuget_updater_tool(dependency, proj_path) unless checked_files.include?(checked_key)
73
+
74
+ checked_files.add(checked_key)
75
+ # We need to check the downstream references even though we're already evaluated the file
76
+ downstream_files = project_file_parser.downstream_file_references(project_file: project_file)
77
+ downstream_files.each do |downstream_file|
78
+ checked_files.add("#{downstream_file}-#{dependency.name}#{dependency.version}")
79
+ end
68
80
  update_ran = true
69
81
  end
70
82
 
@@ -79,15 +91,39 @@ module Dependabot
79
91
  project_file = project_files.first
80
92
  proj_path = dependency_file_path(project_file)
81
93
 
82
- NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path,
83
- dependency: dependency, is_transitive: !dependency.top_level?,
84
- credentials: credentials)
94
+ return false unless repo_contents_path
95
+
96
+ call_nuget_updater_tool(dependency, proj_path)
85
97
  return true
86
98
  end
87
99
 
88
100
  false
89
101
  end
90
102
 
103
+ sig { params(dependency: Dependency, proj_path: String).void }
104
+ def call_nuget_updater_tool(dependency, proj_path)
105
+ NativeHelpers.run_nuget_updater_tool(repo_root: T.must(repo_contents_path), proj_path: proj_path,
106
+ dependency: dependency, is_transitive: !dependency.top_level?,
107
+ credentials: credentials)
108
+
109
+ # Tests need to track how many times we call the tooling updater to ensure we don't recurse needlessly
110
+ # Ideally we should find a way to not run this code in prod
111
+ # (or a better way to track calls made to NativeHelpers)
112
+ @update_tooling_calls ||= {}
113
+ key = proj_path + dependency.name
114
+ if @update_tooling_calls[key]
115
+ @update_tooling_calls[key] += 1
116
+ else
117
+ @update_tooling_calls[key] = 1
118
+ end
119
+ end
120
+
121
+ # Don't call this from outside tests, we're only checking that we aren't recursing needlessly
122
+ sig { returns(T.nilable(T::Hash[String, Integer])) }
123
+ def testonly_update_tooling_calls
124
+ @update_tooling_calls
125
+ end
126
+
91
127
  def project_dependencies(project_file)
92
128
  # Collect all dependencies from the project file and associated packages.config
93
129
  dependencies = project_file_parser.dependency_set(project_file: project_file).dependencies
@@ -1,11 +1,17 @@
1
- # typed: true
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "shellwords"
5
+ require "sorbet-runtime"
6
+
4
7
  require_relative "nuget_config_credential_helpers"
5
8
 
6
9
  module Dependabot
7
10
  module Nuget
8
11
  module NativeHelpers
12
+ extend T::Sig
13
+
14
+ sig { returns(String) }
9
15
  def self.native_helpers_root
10
16
  helpers_root = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil)
11
17
  return File.join(helpers_root, "nuget") unless helpers_root.nil?
@@ -13,9 +19,10 @@ module Dependabot
13
19
  File.expand_path("../../../helpers", __dir__)
14
20
  end
15
21
 
22
+ sig { params(project_tfms: T::Array[String], package_tfms: T::Array[String]).returns(T::Boolean) }
16
23
  def self.run_nuget_framework_check(project_tfms, package_tfms)
17
24
  exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli")
18
- command = [
25
+ command_parts = [
19
26
  exe_path,
20
27
  "framework-check",
21
28
  "--project-tfms",
@@ -23,7 +30,8 @@ module Dependabot
23
30
  "--package-tfms",
24
31
  *package_tfms,
25
32
  "--verbose"
26
- ].join(" ")
33
+ ]
34
+ command = Shellwords.join(command_parts)
27
35
 
28
36
  fingerprint = [
29
37
  exe_path,
@@ -48,9 +56,18 @@ module Dependabot
48
56
  end
49
57
 
50
58
  # rubocop:disable Metrics/MethodLength
59
+ sig do
60
+ params(
61
+ repo_root: String,
62
+ proj_path: String,
63
+ dependency: Dependency,
64
+ is_transitive: T::Boolean,
65
+ credentials: T::Array[T.untyped]
66
+ ).void
67
+ end
51
68
  def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency:, is_transitive:, credentials:)
52
69
  exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli")
53
- command = [
70
+ command_parts = [
54
71
  exe_path,
55
72
  "update",
56
73
  "--repo-root",
@@ -63,9 +80,11 @@ module Dependabot
63
80
  dependency.version,
64
81
  "--previous-version",
65
82
  dependency.previous_version,
66
- is_transitive ? "--transitive" : "",
83
+ is_transitive ? "--transitive" : nil,
67
84
  "--verbose"
68
- ].join(" ")
85
+ ].compact
86
+
87
+ command = Shellwords.join(command_parts)
69
88
 
70
89
  fingerprint = [
71
90
  exe_path,
@@ -80,9 +99,9 @@ module Dependabot
80
99
  "<new-version>",
81
100
  "--previous-version",
82
101
  "<previous-version>",
83
- is_transitive ? "--transitive" : "",
102
+ is_transitive ? "--transitive" : nil,
84
103
  "--verbose"
85
- ].join(" ")
104
+ ].compact.join(" ")
86
105
 
87
106
  puts "running NuGet updater:\n" + command
88
107
 
@@ -1,12 +1,19 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/nuget/cache_manager"
5
5
  require "dependabot/nuget/update_checker/repository_finder"
6
+ require "sorbet-runtime"
6
7
 
7
8
  module Dependabot
8
9
  module Nuget
9
10
  class NugetClient
11
+ extend T::Sig
12
+
13
+ sig do
14
+ params(dependency_name: String, repository_details: T::Hash[Symbol, String])
15
+ .returns(T.nilable(T::Set[String]))
16
+ end
10
17
  def self.get_package_versions(dependency_name, repository_details)
11
18
  repository_type = repository_details.fetch(:repository_type)
12
19
  if repository_type == "v3"
@@ -18,6 +25,10 @@ module Dependabot
18
25
  end
19
26
  end
20
27
 
28
+ sig do
29
+ params(dependency_name: String, repository_details: T::Hash[Symbol, String])
30
+ .returns(T.nilable(T::Set[String]))
31
+ end
21
32
  private_class_method def self.get_package_versions_v3(dependency_name, repository_details)
22
33
  # Use the registration URL if possible because it is fast and correct
23
34
  if repository_details[:registration_url]
@@ -28,9 +39,15 @@ module Dependabot
28
39
  # Otherwise, use the versions URL (fast but wrong because it includes unlisted versions)
29
40
  elsif repository_details[:versions_url]
30
41
  get_versions_from_versions_url_v3(repository_details)
42
+ else
43
+ raise "No version sources were available for #{dependency_name} in #{repository_details}"
31
44
  end
32
45
  end
33
46
 
47
+ sig do
48
+ params(dependency_name: String, repository_details: T::Hash[Symbol, String])
49
+ .returns(T.nilable(T::Set[String]))
50
+ end
34
51
  private_class_method def self.get_package_versions_v2(dependency_name, repository_details)
35
52
  doc = execute_xml_nuget_request(repository_details.fetch(:versions_url), repository_details)
36
53
  return unless doc
@@ -49,34 +66,33 @@ module Dependabot
49
66
  matching_versions
50
67
  end
51
68
 
69
+ sig { params(repository_details: T::Hash[Symbol, String]).returns(T.nilable(T::Set[String])) }
52
70
  private_class_method def self.get_versions_from_versions_url_v3(repository_details)
53
- body = execute_json_nuget_request(repository_details[:versions_url], repository_details)
54
- body&.fetch("versions")
71
+ body = execute_json_nuget_request(repository_details.fetch(:versions_url), repository_details)
72
+ ver_array = T.let(body&.fetch("versions"), T.nilable(T::Array[String]))
73
+ ver_array&.to_set
55
74
  end
56
75
 
76
+ sig { params(repository_details: T::Hash[Symbol, String]).returns(T.nilable(T::Set[String])) }
57
77
  private_class_method def self.get_versions_from_registration_v3(repository_details)
58
- url = repository_details[:registration_url]
78
+ url = repository_details.fetch(:registration_url)
59
79
  body = execute_json_nuget_request(url, repository_details)
60
80
 
61
81
  return unless body
62
82
 
63
83
  pages = body.fetch("items")
64
- versions = Set.new
84
+ versions = T.let(Set.new, T::Set[String])
65
85
  pages.each do |page|
66
86
  items = page["items"]
67
87
  if items
68
88
  # inlined entries
69
- items.each do |item|
70
- catalog_entry = item["catalogEntry"]
71
- if catalog_entry["listed"] == true
72
- vers = catalog_entry["version"]
73
- versions << vers
74
- end
75
- end
89
+ get_versions_from_inline_page(items, versions)
76
90
  else
77
91
  # paged entries
78
92
  page_url = page["@id"]
79
93
  page_body = execute_json_nuget_request(page_url, repository_details)
94
+ next unless page_body
95
+
80
96
  items = page_body.fetch("items")
81
97
  items.each do |item|
82
98
  catalog_entry = item.fetch("catalogEntry")
@@ -88,8 +104,27 @@ module Dependabot
88
104
  versions
89
105
  end
90
106
 
107
+ sig { params(items: T::Array[T::Hash[String, T.untyped]], versions: T::Set[String]).void }
108
+ private_class_method def self.get_versions_from_inline_page(items, versions)
109
+ items.each do |item|
110
+ catalog_entry = item["catalogEntry"]
111
+
112
+ # a package is considered listed if the `listed` property is either `true` or missing
113
+ listed_property = catalog_entry["listed"]
114
+ is_listed = listed_property.nil? || listed_property == true
115
+ if is_listed
116
+ vers = catalog_entry["version"]
117
+ versions << vers
118
+ end
119
+ end
120
+ end
121
+
122
+ sig do
123
+ params(repository_details: T::Hash[Symbol, String], dependency_name: String)
124
+ .returns(T.nilable(T::Set[String]))
125
+ end
91
126
  private_class_method def self.get_versions_from_search_url_v3(repository_details, dependency_name)
92
- search_url = repository_details[:search_url]
127
+ search_url = repository_details.fetch(:search_url)
93
128
  body = execute_json_nuget_request(search_url, repository_details)
94
129
 
95
130
  body&.fetch("data")
@@ -98,11 +133,14 @@ module Dependabot
98
133
  &.map { |d| d.fetch("version") }
99
134
  end
100
135
 
136
+ sig do
137
+ params(url: String, repository_details: T::Hash[Symbol, T.untyped]).returns(T.nilable(Nokogiri::XML::Document))
138
+ end
101
139
  private_class_method def self.execute_xml_nuget_request(url, repository_details)
102
140
  response = execute_nuget_request_internal(
103
141
  url: url,
104
- auth_header: repository_details[:auth_header],
105
- repository_url: repository_details[:repository_url]
142
+ auth_header: repository_details.fetch(:auth_header),
143
+ repository_url: repository_details.fetch(:repository_url)
106
144
  )
107
145
  return unless response.status == 200
108
146
 
@@ -111,11 +149,16 @@ module Dependabot
111
149
  doc
112
150
  end
113
151
 
152
+ sig do
153
+ params(url: String,
154
+ repository_details: T::Hash[Symbol, T.untyped])
155
+ .returns(T.nilable(T::Hash[T.untyped, T.untyped]))
156
+ end
114
157
  private_class_method def self.execute_json_nuget_request(url, repository_details)
115
158
  response = execute_nuget_request_internal(
116
159
  url: url,
117
- auth_header: repository_details[:auth_header],
118
- repository_url: repository_details[:repository_url]
160
+ auth_header: repository_details.fetch(:auth_header),
161
+ repository_url: repository_details.fetch(:repository_url)
119
162
  )
120
163
  return unless response.status == 200
121
164
 
@@ -123,11 +166,10 @@ module Dependabot
123
166
  JSON.parse(body)
124
167
  end
125
168
 
126
- private_class_method def self.execute_nuget_request_internal(
127
- url: String,
128
- auth_header: String,
129
- repository_url: String
130
- )
169
+ sig do
170
+ params(url: String, auth_header: T::Hash[Symbol, T.untyped], repository_url: String).returns(Excon::Response)
171
+ end
172
+ private_class_method def self.execute_nuget_request_internal(url:, auth_header:, repository_url:)
131
173
  cache = CacheManager.cache("dependency_url_search_cache")
132
174
  if cache[url].nil?
133
175
  response = Dependabot::RegistryClient.get(
@@ -147,11 +189,12 @@ module Dependabot
147
189
  response
148
190
  rescue Excon::Error::Timeout, Excon::Error::Socket
149
191
  repo_url = repository_url
150
- raise if repo_url == Dependabot::Nuget::UpdateChecker::RepositoryFinder::DEFAULT_REPOSITORY_URL
192
+ raise if repo_url == Dependabot::Nuget::RepositoryFinder::DEFAULT_REPOSITORY_URL
151
193
 
152
194
  raise PrivateSourceTimedOut, repo_url
153
195
  end
154
196
 
197
+ sig { params(string: String).returns(String) }
155
198
  private_class_method def self.remove_wrapping_zero_width_chars(string)
156
199
  string.force_encoding("UTF-8").encode
157
200
  .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
@@ -55,17 +55,21 @@ module Dependabot
55
55
  File.rename(temporary_nuget_config_path, user_nuget_config_path)
56
56
  end
57
57
 
58
- # rubocop:disable Lint/SuppressedException
59
58
  def self.patch_nuget_config_for_action(credentials, &_block)
60
59
  add_credentials_to_nuget_config(credentials)
61
60
  begin
62
61
  yield
63
- rescue StandardError
62
+ rescue StandardError => e
63
+ Dependabot.logger.error(
64
+ <<~LOG_MESSAGE
65
+ Block argument of NuGetConfigCredentialHelpers::patch_nuget_config_for_action causes an exception #{e}:
66
+ #{e.message}
67
+ LOG_MESSAGE
68
+ )
64
69
  ensure
65
70
  restore_user_nuget_config
66
71
  end
67
72
  end
68
- # rubocop:enable Lint/SuppressedException
69
73
  end
70
74
  end
71
75
  end