dependabot-nuget 0.246.0 → 0.248.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/file_fetchers"
@@ -18,19 +18,40 @@ module Dependabot
18
18
 
19
19
  BUILD_FILE_NAMES = /^Directory\.Build\.(props|targets)$/i # Directory.Build.props, Directory.Build.targets
20
20
 
21
+ sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
21
22
  def self.required_files_in?(filenames)
22
23
  return true if filenames.any? { |f| f.match?(/^packages\.config$/i) }
23
24
  return true if filenames.any? { |f| f.end_with?(".sln") }
24
25
  return true if filenames.any? { |f| f.match?("^src$") }
25
26
  return true if filenames.any? { |f| f.end_with?(".proj") }
26
27
 
27
- filenames.any? { |name| name.match?(/\.[a-z]{2}proj$/) }
28
+ filenames.any? { |name| name.match?(/\.(cs|vb|fs)proj$/) }
28
29
  end
29
30
 
31
+ sig { override.returns(String) }
30
32
  def self.required_files_message
31
33
  "Repo must contain a .proj file, .(cs|vb|fs)proj file, or a packages.config."
32
34
  end
33
35
 
36
+ sig do
37
+ params(
38
+ source: Dependabot::Source,
39
+ credentials: T::Array[Credential],
40
+ repo_contents_path: T.nilable(String),
41
+ options: T::Hash[String, String]
42
+ ).void
43
+ end
44
+ def initialize(source:, credentials:, repo_contents_path: nil, options: {})
45
+ super(source: source, credentials: credentials, repo_contents_path: repo_contents_path, options: options)
46
+
47
+ @sln_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
48
+ @sln_project_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
49
+ @project_files = T.let([], T::Array[Dependabot::DependencyFile])
50
+ @fetched_files = T.let({}, T::Hash[String, T::Array[Dependabot::DependencyFile]])
51
+ @nuget_config_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
52
+ @packages_config_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
53
+ end
54
+
34
55
  sig { override.returns(T::Array[DependencyFile]) }
35
56
  def fetch_files
36
57
  fetched_files = []
@@ -50,12 +71,9 @@ module Dependabot
50
71
  end
51
72
 
52
73
  if project_files.none? && packages_config_files.none?
53
- raise @missing_sln_project_file_errors.first if @missing_sln_project_file_errors&.any?
74
+ raise T.must(@missing_sln_project_file_errors.first) if @missing_sln_project_file_errors&.any?
54
75
 
55
- raise(
56
- Dependabot::DependencyFileNotFound,
57
- File.join(directory, "<anything>.(cs|vb|fs)proj")
58
- )
76
+ raise_dependency_file_not_found
59
77
  end
60
78
 
61
79
  fetched_files
@@ -63,8 +81,11 @@ module Dependabot
63
81
 
64
82
  private
65
83
 
84
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
66
85
  def project_files
67
- @project_files ||=
86
+ return @project_files if @project_files.any?
87
+
88
+ @project_files =
68
89
  begin
69
90
  project_files = []
70
91
  project_files += csproj_file
@@ -78,19 +99,27 @@ module Dependabot
78
99
  project_files
79
100
  end
80
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
81
107
  raise(
82
- Dependabot::DependencyFileNotFound,
83
- 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
+ )
84
112
  )
85
113
  end
86
114
 
115
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
87
116
  def packages_config_files
88
117
  return @packages_config_files if @packages_config_files
89
118
 
90
119
  candidate_paths =
91
120
  [*project_files.map { |f| File.dirname(f.name) }, "."].uniq
92
121
 
93
- @packages_config_files ||=
122
+ @packages_config_files =
94
123
  candidate_paths.filter_map do |dir|
95
124
  file = repo_contents(dir: dir)
96
125
  .find { |f| f.name.casecmp("packages.config").zero? }
@@ -99,6 +128,7 @@ module Dependabot
99
128
  end
100
129
 
101
130
  # rubocop:disable Metrics/PerceivedComplexity
131
+ sig { returns(T.nilable(T::Array[T.untyped])) }
102
132
  def sln_file_names
103
133
  sln_files = repo_contents.select { |f| f.name.end_with?(".sln") }
104
134
  src_dir = repo_contents.any? { |f| f.name == "src" && f.type == "dir" }
@@ -117,13 +147,15 @@ module Dependabot
117
147
  end
118
148
  # rubocop:enable Metrics/PerceivedComplexity
119
149
 
150
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
120
151
  def directory_build_files
121
- @directory_build_files ||= fetch_directory_build_files
152
+ @directory_build_files ||= T.let(fetch_directory_build_files, T.nilable(T::Array[Dependabot::DependencyFile]))
122
153
  end
123
154
 
155
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
124
156
  def fetch_directory_build_files
125
- attempted_dirs = []
126
- directory_build_files = []
157
+ attempted_dirs = T.let([], T::Array[Pathname])
158
+ directory_build_files = T.let([], T::Array[Dependabot::DependencyFile])
127
159
  directory_path = Pathname.new(directory)
128
160
 
129
161
  # find all build files (Directory.Build.props/.targets) relative to the given project file
@@ -145,12 +177,13 @@ module Dependabot
145
177
  directory_build_files
146
178
  end
147
179
 
180
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
148
181
  def sln_project_files
149
182
  return [] unless sln_files
150
183
 
151
184
  @sln_project_files ||=
152
185
  begin
153
- paths = sln_files.flat_map do |sln_file|
186
+ paths = T.must(sln_files).flat_map do |sln_file|
154
187
  SlnProjectPathsFinder
155
188
  .new(sln_file: sln_file)
156
189
  .project_paths
@@ -159,7 +192,7 @@ module Dependabot
159
192
  paths.filter_map do |path|
160
193
  fetch_file_from_host(path)
161
194
  rescue Dependabot::DependencyFileNotFound => e
162
- @missing_sln_project_file_errors ||= []
195
+ @missing_sln_project_file_errors ||= T.let([], T.nilable(T::Array[Dependabot::DependencyFileNotFound]))
163
196
  @missing_sln_project_file_errors << e
164
197
  # Don't worry about missing files too much for now (at least
165
198
  # until we start resolving properties)
@@ -168,35 +201,42 @@ module Dependabot
168
201
  end
169
202
  end
170
203
 
204
+ sig { returns(T.nilable(T::Array[Dependabot::DependencyFile])) }
171
205
  def sln_files
172
206
  return unless sln_file_names
173
207
 
174
208
  @sln_files ||=
175
209
  sln_file_names
176
- .map { |sln_file_name| fetch_file_from_host(sln_file_name) }
177
- .select { |file| file.content.valid_encoding? }
210
+ &.map { |sln_file_name| fetch_file_from_host(sln_file_name) }
211
+ &.select { |file| file.content&.valid_encoding? }
178
212
  end
179
213
 
214
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
180
215
  def csproj_file
181
- @csproj_file ||= find_and_fetch_with_suffix(".csproj")
216
+ @csproj_file ||= T.let(find_and_fetch_with_suffix(".csproj"), T.nilable(T::Array[Dependabot::DependencyFile]))
182
217
  end
183
218
 
219
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
184
220
  def vbproj_file
185
- @vbproj_file ||= find_and_fetch_with_suffix(".vbproj")
221
+ @vbproj_file ||= T.let(find_and_fetch_with_suffix(".vbproj"), T.nilable(T::Array[Dependabot::DependencyFile]))
186
222
  end
187
223
 
224
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
188
225
  def fsproj_file
189
- @fsproj_file ||= find_and_fetch_with_suffix(".fsproj")
226
+ @fsproj_file ||= T.let(find_and_fetch_with_suffix(".fsproj"), T.nilable(T::Array[Dependabot::DependencyFile]))
190
227
  end
191
228
 
229
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
192
230
  def proj_files
193
- @proj_files ||= find_and_fetch_with_suffix(".proj")
231
+ @proj_files ||= T.let(find_and_fetch_with_suffix(".proj"), T.nilable(T::Array[Dependabot::DependencyFile]))
194
232
  end
195
233
 
234
+ sig { params(suffix: String).returns(T::Array[Dependabot::DependencyFile]) }
196
235
  def find_and_fetch_with_suffix(suffix)
197
236
  repo_contents.select { |f| f.name.end_with?(suffix) }.map { |f| fetch_file_from_host(f.name) }
198
237
  end
199
238
 
239
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
200
240
  def nuget_config_files
201
241
  return @nuget_config_files if @nuget_config_files
202
242
 
@@ -206,8 +246,15 @@ module Dependabot
206
246
  @nuget_config_files
207
247
  end
208
248
 
249
+ sig do
250
+ params(
251
+ project_file: Dependabot::DependencyFile,
252
+ expected_file_name: String
253
+ )
254
+ .returns(T.nilable(Dependabot::DependencyFile))
255
+ end
209
256
  def named_file_up_tree_from_project_file(project_file, expected_file_name)
210
- found_expected_file = nil
257
+ found_expected_file = T.let(nil, T.nilable(Dependabot::DependencyFile))
211
258
  directory_path = Pathname.new(directory)
212
259
  full_project_dir = Pathname.new(project_file.directory).join(project_file.name).dirname
213
260
  full_project_dir.ascend.each do |base|
@@ -228,26 +275,25 @@ module Dependabot
228
275
  found_expected_file
229
276
  end
230
277
 
278
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
231
279
  def global_json
232
- return @global_json if defined?(@global_json)
233
-
234
- @global_json = fetch_file_if_present("global.json")
280
+ @global_json ||= T.let(fetch_file_if_present("global.json"), T.nilable(Dependabot::DependencyFile))
235
281
  end
236
282
 
283
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
237
284
  def dotnet_tools_json
238
- return @dotnet_tools_json if defined?(@dotnet_tools_json)
239
-
240
- @dotnet_tools_json = fetch_file_if_present(".config/dotnet-tools.json")
285
+ @dotnet_tools_json ||= T.let(fetch_file_if_present(".config/dotnet-tools.json"),
286
+ T.nilable(Dependabot::DependencyFile))
241
287
  end
242
288
 
289
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
243
290
  def packages_props
244
- return @packages_props if defined?(@packages_props)
245
-
246
- @packages_props = fetch_file_if_present("Packages.props")
291
+ @packages_props ||= T.let(fetch_file_if_present("Packages.props"), T.nilable(Dependabot::DependencyFile))
247
292
  end
248
293
 
294
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
249
295
  def imported_property_files
250
- imported_property_files = []
296
+ imported_property_files = T.let([], T::Array[Dependabot::DependencyFile])
251
297
 
252
298
  files = [*project_files, *directory_build_files]
253
299
 
@@ -263,18 +309,24 @@ module Dependabot
263
309
  imported_property_files
264
310
  end
265
311
 
312
+ sig do
313
+ params(
314
+ file: Dependabot::DependencyFile,
315
+ previously_fetched_files: T::Array[Dependabot::DependencyFile]
316
+ )
317
+ .returns(T::Array[Dependabot::DependencyFile])
318
+ end
266
319
  def fetch_imported_property_files(file:, previously_fetched_files:)
267
320
  file_id = file.directory + "/" + file.name
268
- @fetched_files ||= {}
269
321
  if @fetched_files[file_id]
270
- @fetched_files[file_id]
322
+ T.must(@fetched_files[file_id])
271
323
  else
272
324
  paths =
273
325
  ImportPathsFinder.new(project_file: file).import_paths +
274
326
  ImportPathsFinder.new(project_file: file).project_reference_paths +
275
327
  ImportPathsFinder.new(project_file: file).project_file_paths
276
328
 
277
- paths.flat_map do |path|
329
+ paths.filter_map do |path|
278
330
  next if previously_fetched_files.map(&:name).include?(path)
279
331
  next if file.name == path
280
332
  next if path.include?("$(")
@@ -290,7 +342,7 @@ module Dependabot
290
342
  # Don't worry about missing files too much for now (at least
291
343
  # until we start resolving properties)
292
344
  nil
293
- end.compact
345
+ end.flatten
294
346
  end
295
347
  end
296
348
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "json"
@@ -12,12 +12,17 @@ module Dependabot
12
12
  module Nuget
13
13
  class FileParser
14
14
  class DotNetToolsJsonParser
15
+ extend T::Sig
16
+
15
17
  require "dependabot/file_parsers/base/dependency_set"
16
18
 
19
+ sig { params(dotnet_tools_json: Dependabot::DependencyFile).void }
17
20
  def initialize(dotnet_tools_json:)
18
21
  @dotnet_tools_json = dotnet_tools_json
22
+ @parsed_dotnet_tools_json = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
19
23
  end
20
24
 
25
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
21
26
  def dependency_set
22
27
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
23
28
 
@@ -48,11 +53,14 @@ module Dependabot
48
53
 
49
54
  private
50
55
 
56
+ sig { returns(Dependabot::DependencyFile) }
51
57
  attr_reader :dotnet_tools_json
52
58
 
59
+ sig { returns(T::Hash[String, T.untyped]) }
53
60
  def parsed_dotnet_tools_json
54
61
  # Remove BOM if present as JSON should be UTF-8
55
- @parsed_dotnet_tools_json ||= JSON.parse(dotnet_tools_json.content.delete_prefix("\uFEFF"))
62
+ content = T.must(dotnet_tools_json.content)
63
+ @parsed_dotnet_tools_json ||= JSON.parse(content.delete_prefix("\uFEFF"))
56
64
  rescue JSON::ParserError
57
65
  raise Dependabot::DependencyFileNotParseable, dotnet_tools_json.path
58
66
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "json"
@@ -12,12 +12,17 @@ module Dependabot
12
12
  module Nuget
13
13
  class FileParser
14
14
  class GlobalJsonParser
15
+ extend T::Sig
16
+
15
17
  require "dependabot/file_parsers/base/dependency_set"
16
18
 
19
+ sig { params(global_json: Dependabot::DependencyFile).void }
17
20
  def initialize(global_json:)
18
21
  @global_json = global_json
22
+ @parsed_global_json = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
19
23
  end
20
24
 
25
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
21
26
  def dependency_set
22
27
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
23
28
 
@@ -45,11 +50,14 @@ module Dependabot
45
50
 
46
51
  private
47
52
 
53
+ sig { returns(Dependabot::DependencyFile) }
48
54
  attr_reader :global_json
49
55
 
56
+ sig { returns(T::Hash[String, T.untyped]) }
50
57
  def parsed_global_json
51
58
  # Remove BOM if present as JSON should be UTF-8
52
- @parsed_global_json ||= JSON.parse(global_json.content.delete_prefix("\uFEFF"))
59
+ content = T.must(global_json.content)
60
+ @parsed_global_json ||= JSON.parse(content.delete_prefix("\uFEFF"))
53
61
  rescue JSON::ParserError
54
62
  raise Dependabot::DependencyFileNotParseable, global_json.path
55
63
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
@@ -13,18 +13,22 @@ module Dependabot
13
13
  module Nuget
14
14
  class FileParser
15
15
  class PackagesConfigParser
16
+ extend T::Sig
16
17
  require "dependabot/file_parsers/base/dependency_set"
17
18
 
18
19
  DEPENDENCY_SELECTOR = "packages > package"
19
20
 
21
+ sig { returns(T::Hash[String, Dependabot::FileParsers::Base::DependencySet]) }
20
22
  def self.dependency_set_cache
21
23
  CacheManager.cache("packages_config_dependency_set")
22
24
  end
23
25
 
26
+ sig { params(packages_config: Dependabot::DependencyFile).void }
24
27
  def initialize(packages_config:)
25
28
  @packages_config = packages_config
26
29
  end
27
30
 
31
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
28
32
  def dependency_set
29
33
  key = "#{packages_config.name.downcase}::#{packages_config.content.hash}"
30
34
  cache = PackagesConfigParser.dependency_set_cache
@@ -34,8 +38,10 @@ module Dependabot
34
38
 
35
39
  private
36
40
 
41
+ sig { returns(Dependabot::DependencyFile) }
37
42
  attr_reader :packages_config
38
43
 
44
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
39
45
  def parse_dependencies
40
46
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
41
47
 
@@ -44,7 +50,7 @@ module Dependabot
44
50
  doc.css(DEPENDENCY_SELECTOR).each do |dependency_node|
45
51
  dependency_set <<
46
52
  Dependency.new(
47
- name: dependency_name(dependency_node),
53
+ name: T.must(dependency_name(dependency_node)),
48
54
  version: dependency_version(dependency_node),
49
55
  package_manager: "nuget",
50
56
  requirements: [{
@@ -59,11 +65,13 @@ module Dependabot
59
65
  dependency_set
60
66
  end
61
67
 
68
+ sig { params(dependency_node: Nokogiri::XML::Node).returns(T.nilable(String)) }
62
69
  def dependency_name(dependency_node)
63
70
  dependency_node.attribute("id")&.value&.strip ||
64
71
  dependency_node.at_xpath("./id")&.content&.strip
65
72
  end
66
73
 
74
+ sig { params(dependency_node: Nokogiri::XML::Node).returns(T.nilable(String)) }
67
75
  def dependency_version(dependency_node)
68
76
  # Ranges and wildcards aren't allowed in a packages.config - the
69
77
  # specified requirement is always an exact version.
@@ -71,6 +79,7 @@ module Dependabot
71
79
  dependency_node.at_xpath("./version")&.content&.strip
72
80
  end
73
81
 
82
+ sig { params(dependency_node: Nokogiri::XML::Node).returns(String) }
74
83
  def dependency_type(dependency_node)
75
84
  val = dependency_node.attribute("developmentDependency")&.value&.strip ||
76
85
  dependency_node.at_xpath("./developmentDependency")&.content&.strip