dependabot-nuget 0.246.0 → 0.247.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) 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 +79 -31
  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 +3 -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/requirement.rb +4 -1
  22. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +26 -15
  23. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +11 -13
  24. data/lib/dependabot/nuget/update_checker/repository_finder.rb +25 -3
  25. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +2 -2
  26. data/lib/dependabot/nuget/update_checker/version_finder.rb +15 -6
  27. data/lib/dependabot/nuget/update_checker.rb +4 -4
  28. data/lib/dependabot/nuget/version.rb +7 -2
  29. 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
@@ -60,6 +60,7 @@ module Dependabot
60
60
  convert_wildcard_req(req_string)
61
61
  end
62
62
 
63
+ # rubocop:disable Metrics/PerceivedComplexity
63
64
  def convert_dotnet_range_to_ruby_range(req_string)
64
65
  lower_b, upper_b = req_string.split(",").map(&:strip).map do |bound|
65
66
  next convert_range_wildcard_req(bound) if bound.include?("*")
@@ -75,7 +76,8 @@ module Dependabot
75
76
  end
76
77
 
77
78
  upper_b =
78
- if [")", "]"].include?(upper_b) then nil
79
+ if !upper_b then nil
80
+ elsif [")", "]"].include?(upper_b) then nil
79
81
  elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}"
80
82
  else
81
83
  "<= #{upper_b.sub(/\s*\]/, '').strip}"
@@ -83,6 +85,7 @@ module Dependabot
83
85
 
84
86
  [lower_b, upper_b].compact
85
87
  end
88
+ # rubocop:enable Metrics/PerceivedComplexity
86
89
 
87
90
  def convert_range_wildcard_req(req_string)
88
91
  range_end = req_string[-1]
@@ -66,23 +66,34 @@ module Dependabot
66
66
  end
67
67
 
68
68
  def fetch_package_tfms(dependency_version)
69
- nupkg_buffer = NupkgFetcher.fetch_nupkg_buffer(dependency_urls, dependency.name, dependency_version)
70
- return [] unless nupkg_buffer
71
-
72
- # Parse tfms from the folders beneath the lib folder
73
- folder_name = "lib/"
74
- tfms = Set.new
75
- Zip::File.open_buffer(nupkg_buffer) do |zip|
76
- lib_file_entries = zip.select { |entry| entry.name.start_with?(folder_name) }
77
- # If there is no lib folder in this package, assume it is a development dependency
78
- return nil if lib_file_entries.empty?
79
-
80
- lib_file_entries.each do |entry|
81
- _, tfm = entry.name.split("/").first(2)
82
- tfms << tfm
69
+ cache = CacheManager.cache("compatibility_checker_tfms_cache")
70
+ key = "#{dependency.name}::#{dependency_version}"
71
+
72
+ cache[key] ||= begin
73
+ nupkg_buffer = NupkgFetcher.fetch_nupkg_buffer(dependency_urls, dependency.name, dependency_version)
74
+ return [] unless nupkg_buffer
75
+
76
+ # Parse tfms from the folders beneath the lib folder
77
+ folder_name = "lib/"
78
+ tfms = Set.new
79
+ Zip::File.open_buffer(nupkg_buffer) do |zip|
80
+ lib_file_entries = zip.select { |entry| entry.name.start_with?(folder_name) }
81
+ # If there is no lib folder in this package, assume it is a development dependency
82
+ return nil if lib_file_entries.empty?
83
+
84
+ lib_file_entries.each do |entry|
85
+ _, tfm = entry.name.split("/").first(2)
86
+
87
+ # some zip compressors create empty directory entries (in this case `lib/`) which can cause the string
88
+ # split to return `nil`, so we have to explicitly guard against that
89
+ tfms << tfm if tfm
90
+ end
83
91
  end
92
+
93
+ tfms.to_a
84
94
  end
85
- tfms.to_a
95
+
96
+ cache[key]
86
97
  end
87
98
  end
88
99
  end
@@ -111,25 +111,19 @@ module Dependabot
111
111
  current_redirects = 0
112
112
 
113
113
  loop do
114
- connection = Excon.new(current_url, persistent: true)
115
-
116
- package_data = StringIO.new
117
- response_block = lambda do |chunk, _remaining_bytes, _total_bytes|
118
- package_data.write(chunk)
119
- end
120
-
121
- response = connection.request(
122
- method: :get,
114
+ # Directly download the stream without any additional settings _except_ for `omit_default_port: true` which
115
+ # is necessary to not break the URL signing that some NuGet feeds use.
116
+ response = Excon.get(
117
+ current_url,
123
118
  headers: auth_header,
124
- response_block: response_block
119
+ omit_default_port: true
125
120
  )
126
121
 
127
122
  # redirect the HTTP response as appropriate based on documentation here:
128
123
  # https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
129
124
  case response.status
130
125
  when 200
131
- package_data.rewind
132
- return package_data
126
+ return response.body
133
127
  when 301, 302, 303, 307, 308
134
128
  current_redirects += 1
135
129
  return nil if current_redirects > max_redirects
@@ -142,10 +136,14 @@ module Dependabot
142
136
  end
143
137
 
144
138
  def self.fetch_url(url, repository_details)
139
+ fetch_url_with_auth(url, repository_details.fetch(:auth_header))
140
+ end
141
+
142
+ def self.fetch_url_with_auth(url, auth_header)
145
143
  cache = CacheManager.cache("nupkg_fetcher_cache")
146
144
  cache[url] ||= Dependabot::RegistryClient.get(
147
145
  url: url,
148
- headers: repository_details.fetch(:auth_header)
146
+ headers: auth_header
149
147
  )
150
148
 
151
149
  cache[url]