dependabot-nuget 0.246.0 → 0.248.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +40 -6
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +27 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +18 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +110 -0
  6. data/lib/dependabot/nuget/cache_manager.rb +9 -3
  7. data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -12
  8. data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +13 -3
  9. data/lib/dependabot/nuget/file_fetcher.rb +89 -37
  10. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +10 -2
  11. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +10 -2
  12. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +11 -2
  13. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +140 -41
  14. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +57 -5
  15. data/lib/dependabot/nuget/file_parser.rb +13 -3
  16. data/lib/dependabot/nuget/file_updater/property_value_updater.rb +25 -8
  17. data/lib/dependabot/nuget/file_updater.rb +74 -38
  18. data/lib/dependabot/nuget/http_response_helpers.rb +6 -1
  19. data/lib/dependabot/nuget/metadata_finder.rb +27 -3
  20. data/lib/dependabot/nuget/nuget_client.rb +23 -0
  21. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +10 -1
  22. data/lib/dependabot/nuget/requirement.rb +21 -9
  23. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +26 -15
  24. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +87 -21
  25. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +25 -3
  26. data/lib/dependabot/nuget/update_checker/repository_finder.rb +25 -3
  27. data/lib/dependabot/nuget/update_checker/requirements_updater.rb +32 -9
  28. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +2 -2
  29. data/lib/dependabot/nuget/update_checker/version_finder.rb +178 -64
  30. data/lib/dependabot/nuget/update_checker.rb +76 -32
  31. data/lib/dependabot/nuget/version.rb +7 -2
  32. metadata +19 -5
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
@@ -11,25 +11,38 @@ module Dependabot
11
11
  module Nuget
12
12
  class FileUpdater
13
13
  class PropertyValueUpdater
14
+ extend T::Sig
15
+
16
+ sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
14
17
  def initialize(dependency_files:)
15
18
  @dependency_files = dependency_files
16
19
  end
17
20
 
21
+ sig do
22
+ params(property_name: String,
23
+ updated_value: String,
24
+ callsite_file: Dependabot::DependencyFile)
25
+ .returns(T::Array[Dependabot::DependencyFile])
26
+ end
18
27
  def update_files_for_property_change(property_name:, updated_value:,
19
28
  callsite_file:)
20
29
  declaration_details =
21
- property_value_finder
22
- .property_details(
30
+ property_value_finder.property_details(
23
31
  property_name: property_name,
24
32
  callsite_file: callsite_file
25
33
  )
34
+ throw "Unable to locate property details" unless declaration_details
26
35
 
36
+ declaration_filename = declaration_details.fetch(:file)
27
37
  declaration_file = dependency_files.find do |f|
28
- declaration_details.fetch(:file) == f.name
38
+ declaration_filename == f.name
29
39
  end
40
+ throw "Unable to locate declaration file" unless declaration_file
41
+
42
+ content = T.must(declaration_file.content)
30
43
  node = declaration_details.fetch(:node)
31
44
 
32
- updated_content = declaration_file.content.sub(
45
+ updated_content = content.sub(
33
46
  %r{(<#{Regexp.quote(node.name)}(?:\s[^>]*)?>)
34
47
  \s*#{Regexp.quote(node.content)}\s*
35
48
  </#{Regexp.quote(node.name)}>}xm,
@@ -37,21 +50,25 @@ module Dependabot
37
50
  )
38
51
 
39
52
  files = dependency_files.dup
40
- files[files.index(declaration_file)] =
53
+ file_index = T.must(files.index(declaration_file))
54
+ files[file_index] =
41
55
  update_file(file: declaration_file, content: updated_content)
42
56
  files
43
57
  end
44
58
 
45
59
  private
46
60
 
61
+ sig { returns(T::Array[DependencyFile]) }
47
62
  attr_reader :dependency_files
48
63
 
64
+ sig { returns(FileParser::PropertyValueFinder) }
49
65
  def property_value_finder
50
66
  @property_value_finder ||=
51
- Nuget::FileParser::PropertyValueFinder
52
- .new(dependency_files: dependency_files)
67
+ T.let(FileParser::PropertyValueFinder
68
+ .new(dependency_files: dependency_files), T.nilable(FileParser::PropertyValueFinder))
53
69
  end
54
70
 
71
+ sig { params(file: DependencyFile, content: String).returns(DependencyFile) }
55
72
  def update_file(file:, content:)
56
73
  updated_file = file.dup
57
74
  updated_file.content = content
@@ -1,10 +1,11 @@
1
- # typed: true
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  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 "dependabot/shared_helpers"
8
9
  require "sorbet-runtime"
9
10
 
10
11
  module Dependabot
@@ -17,6 +18,7 @@ module Dependabot
17
18
  require_relative "file_parser/dotnet_tools_json_parser"
18
19
  require_relative "file_parser/packages_config_parser"
19
20
 
21
+ sig { override.returns(T::Array[Regexp]) }
20
22
  def self.updated_files_regex
21
23
  [
22
24
  %r{^[^/]*\.([a-z]{2})?proj$},
@@ -29,32 +31,33 @@ module Dependabot
29
31
  ]
30
32
  end
31
33
 
34
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
32
35
  def updated_dependency_files
33
- dependencies.each do |dependency|
34
- try_update_projects(dependency) || try_update_json(dependency)
35
- end
36
+ base_dir = T.must(dependency_files.first).directory
37
+ SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
38
+ dependencies.each do |dependency|
39
+ try_update_projects(dependency) || try_update_json(dependency)
40
+ end
41
+ updated_files = dependency_files.filter_map do |f|
42
+ updated_content = File.read(dependency_file_path(f))
43
+ next if updated_content == f.content
36
44
 
37
- # update all with content from disk
38
- updated_files = dependency_files.filter_map do |f|
39
- updated_content = File.read(dependency_file_path(f))
40
- next if updated_content == f.content
45
+ normalized_content = normalize_content(f, updated_content)
46
+ next if normalized_content == f.content
41
47
 
42
- normalized_content = normalize_content(f, updated_content)
43
- next if normalized_content == f.content
48
+ next if only_deleted_lines?(f.content, normalized_content)
44
49
 
45
- puts "The contents of file [#{f.name}] were updated."
50
+ puts "The contents of file [#{f.name}] were updated."
46
51
 
47
- updated_file(file: f, content: normalized_content)
52
+ updated_file(file: f, content: normalized_content)
53
+ end
54
+ updated_files
48
55
  end
49
-
50
- # reset repo files
51
- SharedHelpers.reset_git_repo(T.cast(repo_contents_path, String)) if repo_contents_path
52
-
53
- updated_files
54
56
  end
55
57
 
56
58
  private
57
59
 
60
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
58
61
  def try_update_projects(dependency)
59
62
  update_ran = T.let(false, T::Boolean)
60
63
  checked_files = Set.new
@@ -64,7 +67,7 @@ module Dependabot
64
67
  project_dependencies = project_dependencies(project_file)
65
68
  proj_path = dependency_file_path(project_file)
66
69
 
67
- next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? }
70
+ next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? }
68
71
 
69
72
  next unless repo_contents_path
70
73
 
@@ -79,16 +82,16 @@ module Dependabot
79
82
  end
80
83
  update_ran = true
81
84
  end
82
-
83
85
  update_ran
84
86
  end
85
87
 
88
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
86
89
  def try_update_json(dependency)
87
- if dotnet_tools_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? } ||
88
- global_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? }
90
+ if dotnet_tools_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? } ||
91
+ global_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? }
89
92
 
90
93
  # We just need to feed the updater a project file, grab the first
91
- project_file = project_files.first
94
+ project_file = T.must(project_files.first)
92
95
  proj_path = dependency_file_path(project_file)
93
96
 
94
97
  return false unless repo_contents_path
@@ -109,13 +112,14 @@ module Dependabot
109
112
  # Tests need to track how many times we call the tooling updater to ensure we don't recurse needlessly
110
113
  # Ideally we should find a way to not run this code in prod
111
114
  # (or a better way to track calls made to NativeHelpers)
112
- @update_tooling_calls ||= {}
115
+ @update_tooling_calls ||= T.let({}, T.nilable(T::Hash[String, Integer]))
113
116
  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
117
+ @update_tooling_calls[key] =
118
+ if @update_tooling_calls[key]
119
+ T.must(@update_tooling_calls[key]) + 1
120
+ else
121
+ 1
122
+ end
119
123
  end
120
124
 
121
125
  # Don't call this from outside tests, we're only checking that we aren't recursing needlessly
@@ -124,6 +128,7 @@ module Dependabot
124
128
  @update_tooling_calls
125
129
  end
126
130
 
131
+ sig { params(project_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::Dependency]) }
127
132
  def project_dependencies(project_file)
128
133
  # Collect all dependencies from the project file and associated packages.config
129
134
  dependencies = project_file_parser.dependency_set(project_file: project_file).dependencies
@@ -134,38 +139,54 @@ module Dependabot
134
139
  .dependency_set.dependencies
135
140
  end
136
141
 
142
+ sig { params(project_file: Dependabot::DependencyFile).returns(T.nilable(Dependabot::DependencyFile)) }
137
143
  def find_packages_config(project_file)
138
144
  project_file_name = File.basename(project_file.name)
139
145
  packages_config_path = project_file.name.gsub(project_file_name, "packages.config")
140
146
  packages_config_files.find { |f| f.name == packages_config_path }
141
147
  end
142
148
 
149
+ sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) }
143
150
  def project_file_parser
144
151
  @project_file_parser ||=
145
- FileParser::ProjectFileParser.new(
146
- dependency_files: dependency_files,
147
- credentials: credentials,
148
- repo_contents_path: repo_contents_path
152
+ T.let(
153
+ FileParser::ProjectFileParser.new(
154
+ dependency_files: dependency_files,
155
+ credentials: credentials,
156
+ repo_contents_path: repo_contents_path
157
+ ),
158
+ T.nilable(Dependabot::Nuget::FileParser::ProjectFileParser)
149
159
  )
150
160
  end
151
161
 
162
+ sig { returns(T::Array[Dependabot::Dependency]) }
152
163
  def global_json_dependencies
153
164
  return [] unless global_json
154
165
 
155
166
  @global_json_dependencies ||=
156
- FileParser::GlobalJsonParser.new(global_json: global_json).dependency_set.dependencies
167
+ T.let(
168
+ FileParser::GlobalJsonParser.new(global_json: T.must(global_json)).dependency_set.dependencies,
169
+ T.nilable(T::Array[Dependabot::Dependency])
170
+ )
157
171
  end
158
172
 
173
+ sig { returns(T::Array[Dependabot::Dependency]) }
159
174
  def dotnet_tools_json_dependencies
160
175
  return [] unless dotnet_tools_json
161
176
 
162
177
  @dotnet_tools_json_dependencies ||=
163
- FileParser::DotNetToolsJsonParser.new(dotnet_tools_json: dotnet_tools_json).dependency_set.dependencies
178
+ T.let(
179
+ FileParser::DotNetToolsJsonParser.new(dotnet_tools_json: T.must(dotnet_tools_json))
180
+ .dependency_set.dependencies,
181
+ T.nilable(T::Array[Dependabot::Dependency])
182
+ )
164
183
  end
165
184
 
185
+ # rubocop:disable Metrics/PerceivedComplexity
186
+ sig { params(dependency_file: Dependabot::DependencyFile, updated_content: String).returns(String) }
166
187
  def normalize_content(dependency_file, updated_content)
167
188
  # Fix up line endings
168
- if dependency_file.content.include?("\r\n") && updated_content.match?(/(?<!\r)\n/)
189
+ if dependency_file.content&.include?("\r\n") && updated_content.match?(/(?<!\r)\n/)
169
190
  # The original content contain windows style newlines.
170
191
  # Ensure the updated content also uses windows style newlines.
171
192
  updated_content = updated_content.gsub(/(?<!\r)\n/, "\r\n")
@@ -178,19 +199,21 @@ module Dependabot
178
199
  end
179
200
 
180
201
  # Fix up BOM
181
- if !dependency_file.content.start_with?("\uFEFF") && updated_content.start_with?("\uFEFF")
202
+ if !dependency_file.content&.start_with?("\uFEFF") && updated_content.start_with?("\uFEFF")
182
203
  updated_content = updated_content.delete_prefix("\uFEFF")
183
204
  puts "Removing BOM from [#{dependency_file.name}]."
184
- elsif dependency_file.content.start_with?("\uFEFF") && !updated_content.start_with?("\uFEFF")
205
+ elsif dependency_file.content&.start_with?("\uFEFF") && !updated_content.start_with?("\uFEFF")
185
206
  updated_content = "\uFEFF" + updated_content
186
207
  puts "Adding BOM to [#{dependency_file.name}]."
187
208
  end
188
209
 
189
210
  updated_content
190
211
  end
212
+ # rubocop:enable Metrics/PerceivedComplexity
191
213
 
214
+ sig { params(dependency_file: Dependabot::DependencyFile).returns(String) }
192
215
  def dependency_file_path(dependency_file)
193
- if dependency_file.directory.start_with?(repo_contents_path)
216
+ if dependency_file.directory.start_with?(T.must(repo_contents_path))
194
217
  File.join(dependency_file.directory, dependency_file.name)
195
218
  else
196
219
  file_directory = dependency_file.directory
@@ -199,29 +222,42 @@ module Dependabot
199
222
  end
200
223
  end
201
224
 
225
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
202
226
  def project_files
203
227
  dependency_files.select { |df| df.name.match?(/\.([a-z]{2})?proj$/) }
204
228
  end
205
229
 
230
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
206
231
  def packages_config_files
207
232
  dependency_files.select do |f|
208
233
  T.must(T.must(f.name.split("/").last).casecmp("packages.config")).zero?
209
234
  end
210
235
  end
211
236
 
237
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
212
238
  def global_json
213
239
  dependency_files.find { |f| T.must(f.name.casecmp("global.json")).zero? }
214
240
  end
215
241
 
242
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
216
243
  def dotnet_tools_json
217
244
  dependency_files.find { |f| T.must(f.name.casecmp(".config/dotnet-tools.json")).zero? }
218
245
  end
219
246
 
247
+ sig { override.void }
220
248
  def check_required_files
221
249
  return if project_files.any? || packages_config_files.any?
222
250
 
223
251
  raise "No project file or packages.config!"
224
252
  end
253
+
254
+ sig { params(original_content: T.nilable(String), updated_content: String).returns(T::Boolean) }
255
+ def only_deleted_lines?(original_content, updated_content)
256
+ original_lines = original_content&.lines || []
257
+ updated_lines = updated_content.lines
258
+
259
+ original_lines.count > updated_lines.count
260
+ end
225
261
  end
226
262
  end
227
263
  end
@@ -1,9 +1,14 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  module Dependabot
5
7
  module Nuget
6
8
  module HttpResponseHelpers
9
+ extend T::Sig
10
+
11
+ sig { params(string: String).returns(String) }
7
12
  def self.remove_wrapping_zero_width_chars(string)
8
13
  string.force_encoding("UTF-8").encode
9
14
  .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
@@ -12,13 +12,28 @@ module Dependabot
12
12
  class MetadataFinder < Dependabot::MetadataFinders::Base
13
13
  extend T::Sig
14
14
 
15
+ sig do
16
+ override
17
+ .params(
18
+ dependency: Dependabot::Dependency,
19
+ credentials: T::Array[Dependabot::Credential]
20
+ )
21
+ .void
22
+ end
23
+ def initialize(dependency:, credentials:)
24
+ @dependency_nuspec_file = T.let(nil, T.nilable(Nokogiri::XML::Document))
25
+
26
+ super
27
+ end
28
+
15
29
  private
16
30
 
31
+ sig { override.returns(T.nilable(Dependabot::Source)) }
17
32
  def look_up_source
18
33
  return Source.from_url(dependency_source_url) if dependency_source_url
19
34
 
20
35
  if dependency_nuspec_file
21
- src_repo = look_up_source_in_nuspec(dependency_nuspec_file)
36
+ src_repo = look_up_source_in_nuspec(T.must(dependency_nuspec_file))
22
37
  return src_repo if src_repo
23
38
  end
24
39
 
@@ -33,6 +48,7 @@ module Dependabot
33
48
  nil
34
49
  end
35
50
 
51
+ sig { returns(T.nilable(Dependabot::Source)) }
36
52
  def src_repo_from_project
37
53
  source = dependency.requirements.find { |r| r.fetch(:source) }&.fetch(:source)
38
54
  return unless source
@@ -60,6 +76,7 @@ module Dependabot
60
76
  # Ignored, this is expected for some registries that don't handle these request.
61
77
  end
62
78
 
79
+ sig { params(body: String).returns(T.nilable(String)) }
63
80
  def extract_search_url(body)
64
81
  JSON.parse(body)
65
82
  .fetch("resources", [])
@@ -67,6 +84,7 @@ module Dependabot
67
84
  &.fetch("@id")
68
85
  end
69
86
 
87
+ sig { params(body: String).returns(T.nilable(Dependabot::Source)) }
70
88
  def extract_source_repo(body)
71
89
  JSON.parse(body).fetch("data", []).each do |search_result|
72
90
  next unless search_result["id"].casecmp(dependency.name).zero?
@@ -84,6 +102,7 @@ module Dependabot
84
102
  nil
85
103
  end
86
104
 
105
+ sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(Dependabot::Source)) }
87
106
  def look_up_source_in_nuspec(nuspec)
88
107
  potential_source_urls = [
89
108
  nuspec.at_css("package > metadata > repository")
@@ -99,6 +118,7 @@ module Dependabot
99
118
  Source.from_url(source_url)
100
119
  end
101
120
 
121
+ sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(String)) }
102
122
  def source_from_anywhere_in_nuspec(nuspec)
103
123
  github_urls = []
104
124
  nuspec.to_s.force_encoding(Encoding::UTF_8)
@@ -112,19 +132,21 @@ module Dependabot
112
132
  end
113
133
  end
114
134
 
135
+ sig { returns(T.nilable(Nokogiri::XML::Document)) }
115
136
  def dependency_nuspec_file
116
137
  return @dependency_nuspec_file unless @dependency_nuspec_file.nil?
117
138
 
118
139
  return if dependency_nuspec_url.nil?
119
140
 
120
141
  response = Dependabot::RegistryClient.get(
121
- url: dependency_nuspec_url,
142
+ url: T.must(dependency_nuspec_url),
122
143
  headers: auth_header
123
144
  )
124
145
 
125
146
  @dependency_nuspec_file = Nokogiri::XML(response.body)
126
147
  end
127
148
 
149
+ sig { returns(T.nilable(String)) }
128
150
  def dependency_nuspec_url
129
151
  source = dependency.requirements
130
152
  .find { |r| r.fetch(:source) }&.fetch(:source)
@@ -132,6 +154,7 @@ module Dependabot
132
154
  source.fetch(:nuspec_url) if source&.key?(:nuspec_url)
133
155
  end
134
156
 
157
+ sig { returns(T.nilable(String)) }
135
158
  def dependency_source_url
136
159
  source = dependency.requirements
137
160
  .find { |r| r.fetch(:source) }&.fetch(:source)
@@ -143,6 +166,7 @@ module Dependabot
143
166
  end
144
167
 
145
168
  # rubocop:disable Metrics/PerceivedComplexity
169
+ sig { returns(T::Hash[String, String]) }
146
170
  def auth_header
147
171
  source = dependency.requirements
148
172
  .find { |r| r.fetch(:source) }&.fetch(:source)
@@ -21,11 +21,33 @@ module Dependabot
21
21
  get_package_versions_v3(dependency_name, repository_details)
22
22
  elsif repository_type == "v2"
23
23
  get_package_versions_v2(dependency_name, repository_details)
24
+ elsif repository_type == "local"
25
+ get_package_versions_local(dependency_name, repository_details)
24
26
  else
25
27
  raise "Unknown repository type: #{repository_type}"
26
28
  end
27
29
  end
28
30
 
31
+ sig do
32
+ params(dependency_name: String, repository_details: T::Hash[Symbol, String])
33
+ .returns(T.nilable(T::Set[String]))
34
+ end
35
+ private_class_method def self.get_package_versions_local(dependency_name, repository_details)
36
+ url = repository_details.fetch(:base_url)
37
+ raise "Local repo #{url} doesn't exist or isn't a directory" unless File.exist?(url) && File.directory?(url)
38
+
39
+ package_dir = File.join(url, dependency_name)
40
+
41
+ versions = Set.new
42
+ return versions unless File.exist?(package_dir) && File.directory?(package_dir)
43
+
44
+ Dir.each_child(package_dir) do |child|
45
+ versions.add(child) if File.directory?(File.join(package_dir, child))
46
+ end
47
+
48
+ versions
49
+ end
50
+
29
51
  sig do
30
52
  params(dependency_name: String, repository_details: T::Hash[Symbol, String])
31
53
  .returns(T.nilable(T::Set[String]))
@@ -133,6 +155,7 @@ module Dependabot
133
155
  &.find { |d| d.fetch("id").casecmp(dependency_name.downcase).zero? }
134
156
  &.fetch("versions")
135
157
  &.map { |d| d.fetch("version") }
158
+ &.to_set
136
159
  end
137
160
 
138
161
  sig do
@@ -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
 
@@ -60,6 +66,8 @@ module Dependabot
60
66
  convert_wildcard_req(req_string)
61
67
  end
62
68
 
69
+ # rubocop:disable Metrics/PerceivedComplexity
70
+ sig { params(req_string: String).returns(T::Array[String]) }
63
71
  def convert_dotnet_range_to_ruby_range(req_string)
64
72
  lower_b, upper_b = req_string.split(",").map(&:strip).map do |bound|
65
73
  next convert_range_wildcard_req(bound) if bound.include?("*")
@@ -69,13 +77,14 @@ module Dependabot
69
77
 
70
78
  lower_b =
71
79
  if ["(", "["].include?(lower_b) then nil
72
- 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*/, '')}"
73
81
  else
74
- ">= #{lower_b.sub(/\[\s*/, '').strip}"
82
+ ">= #{T.must(lower_b).sub(/\[\s*/, '').strip}"
75
83
  end
76
84
 
77
85
  upper_b =
78
- if [")", "]"].include?(upper_b) then nil
86
+ if !upper_b then nil
87
+ elsif [")", "]"].include?(upper_b) then nil
79
88
  elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}"
80
89
  else
81
90
  "<= #{upper_b.sub(/\s*\]/, '').strip}"
@@ -83,21 +92,24 @@ module Dependabot
83
92
 
84
93
  [lower_b, upper_b].compact
85
94
  end
95
+ # rubocop:enable Metrics/PerceivedComplexity
86
96
 
97
+ sig { params(req_string: String).returns(String) }
87
98
  def convert_range_wildcard_req(req_string)
88
- range_end = req_string[-1]
89
- defined_part = req_string.split("*").first
99
+ range_end = T.must(req_string[-1])
100
+ defined_part = T.must(req_string.split("*").first)
90
101
  version = defined_part + "0"
91
102
  version += range_end if [")", "]"].include?(range_end)
92
103
  version
93
104
  end
94
105
 
106
+ sig { params(req_string: String).returns(String) }
95
107
  def convert_wildcard_req(req_string)
96
108
  return ">= 0-a" if req_string == "*-*"
97
109
 
98
110
  return ">= 0" if req_string.start_with?("*")
99
111
 
100
- defined_part = req_string.split("*").first
112
+ defined_part = T.must(req_string.split("*").first)
101
113
  suffix = defined_part.end_with?(".") ? "0" : "a"
102
114
  version = defined_part + suffix
103
115
  "~> #{version}"