dependabot-nuget 0.245.0 → 0.247.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +42 -7
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +164 -90
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +38 -2
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +92 -18
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +27 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +115 -14
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/{UpdateWorker.DirsProj.cs → UpdateWorkerTests.DirsProj.cs} +22 -24
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +66 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +373 -83
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +117 -4
  13. data/lib/dependabot/nuget/cache_manager.rb +9 -3
  14. data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -12
  15. data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +13 -3
  16. data/lib/dependabot/nuget/file_fetcher.rb +79 -31
  17. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +10 -2
  18. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +10 -2
  19. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +11 -2
  20. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +140 -45
  21. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +57 -5
  22. data/lib/dependabot/nuget/file_parser.rb +18 -4
  23. data/lib/dependabot/nuget/file_updater/property_value_updater.rb +25 -8
  24. data/lib/dependabot/nuget/file_updater.rb +74 -38
  25. data/lib/dependabot/nuget/http_response_helpers.rb +19 -0
  26. data/lib/dependabot/nuget/metadata_finder.rb +32 -4
  27. data/lib/dependabot/nuget/nuget_client.rb +31 -13
  28. data/lib/dependabot/nuget/requirement.rb +4 -1
  29. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +26 -15
  30. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +23 -13
  31. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +83 -21
  32. data/lib/dependabot/nuget/update_checker/repository_finder.rb +29 -13
  33. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +2 -2
  34. data/lib/dependabot/nuget/update_checker/version_finder.rb +15 -6
  35. data/lib/dependabot/nuget/update_checker.rb +6 -7
  36. data/lib/dependabot/nuget/version.rb +7 -2
  37. metadata +21 -7
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +0 -317
@@ -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
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
@@ -40,20 +40,31 @@ module Dependabot
40
40
  PROPERTY_REGEX = /\$\((?<property>.*?)\)/
41
41
  ITEM_REGEX = /\@\((?<property>.*?)\)/
42
42
 
43
+ sig { returns(T::Hash[String, Dependabot::FileParsers::Base::DependencySet]) }
43
44
  def self.dependency_set_cache
44
45
  CacheManager.cache("project_file_dependency_set")
45
46
  end
46
47
 
48
+ sig { returns(T::Hash[String, T.untyped]) }
47
49
  def self.dependency_url_search_cache
48
50
  CacheManager.cache("dependency_url_search_cache")
49
51
  end
50
52
 
53
+ sig do
54
+ params(dependency_files: T::Array[DependencyFile],
55
+ credentials: T::Array[Credential],
56
+ repo_contents_path: T.nilable(String)).void
57
+ end
51
58
  def initialize(dependency_files:, credentials:, repo_contents_path:)
52
59
  @dependency_files = dependency_files
53
60
  @credentials = credentials
54
61
  @repo_contents_path = repo_contents_path
55
62
  end
56
63
 
64
+ sig do
65
+ params(project_file: DependencyFile, visited_project_files: T::Set[String])
66
+ .returns(Dependabot::FileParsers::Base::DependencySet)
67
+ end
57
68
  def dependency_set(project_file:, visited_project_files: Set.new)
58
69
  key = "#{project_file.name.downcase}::#{project_file.content.hash}"
59
70
  cache = ProjectFileParser.dependency_set_cache
@@ -64,8 +75,9 @@ module Dependabot
64
75
  cache[key] ||= parse_dependencies(project_file, visited_project_files)
65
76
  end
66
77
 
78
+ sig { params(project_file: DependencyFile).returns(T::Set[String]) }
67
79
  def downstream_file_references(project_file:)
68
- file_set = Set.new
80
+ file_set = T.let(Set.new, T::Set[String])
69
81
 
70
82
  doc = Nokogiri::XML(project_file.content)
71
83
  doc.remove_namespaces!
@@ -74,9 +86,11 @@ module Dependabot
74
86
  ref_nodes = proj_refs + proj_files
75
87
  ref_nodes.each do |project_reference_node|
76
88
  dep_file = get_attribute_value(project_reference_node, "Include")
89
+ next unless dep_file
90
+
77
91
  full_project_path = full_path(project_file, dep_file)
78
92
  full_project_path = full_project_path[1..-1] if full_project_path.start_with?("/")
79
- full_project_paths = expand_wildcards_in_project_reference_path(full_project_path)
93
+ full_project_paths = expand_wildcards_in_project_reference_path(T.must(full_project_path))
80
94
  full_project_paths.each do |full_project_path_expanded|
81
95
  file_set << full_project_path_expanded if full_project_path_expanded
82
96
  end
@@ -85,30 +99,37 @@ module Dependabot
85
99
  file_set
86
100
  end
87
101
 
102
+ sig { params(project_file: DependencyFile).returns(T::Array[String]) }
88
103
  def target_frameworks(project_file:)
89
104
  target_framework = details_for_property("TargetFramework", project_file)
90
- return [target_framework&.fetch(:value)] if target_framework
105
+ return [target_framework.fetch(:value)] if target_framework
91
106
 
92
107
  target_frameworks = details_for_property("TargetFrameworks", project_file)
93
- return target_frameworks&.fetch(:value)&.split(";") if target_frameworks
108
+ return target_frameworks.fetch(:value)&.split(";") if target_frameworks
94
109
 
95
110
  target_framework = details_for_property("TargetFrameworkVersion", project_file)
96
111
  return [] unless target_framework
97
112
 
98
113
  # TargetFrameworkVersion is a string like "v4.7.2"
99
- value = target_framework&.fetch(:value)
114
+ value = target_framework.fetch(:value)
100
115
  # convert it to a string like "net472"
101
116
  ["net#{value[1..-1].delete('.')}"]
102
117
  end
103
118
 
119
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
104
120
  def nuget_configs
105
121
  dependency_files.select { |f| f.name.match?(%r{(^|/)nuget\.config$}i) }
106
122
  end
107
123
 
108
124
  private
109
125
 
110
- attr_reader :dependency_files, :credentials
126
+ sig { returns(T::Array[DependencyFile]) }
127
+ attr_reader :dependency_files
128
+
129
+ sig { returns(T::Array[Credential]) }
130
+ attr_reader :credentials
111
131
 
132
+ sig { params(project_file: DependencyFile, ref_path: String).returns(String) }
112
133
  def full_path(project_file, ref_path)
113
134
  project_file_directory = File.dirname(project_file.name)
114
135
  is_rooted = project_file_directory.start_with?("/")
@@ -121,9 +142,13 @@ module Dependabot
121
142
  relative_path = File.join(project_file_directory, relative_path)
122
143
  result = File.expand_path(relative_path)
123
144
  result = result[1..-1] unless is_rooted
124
- result
145
+ T.must(result)
125
146
  end
126
147
 
148
+ sig do
149
+ params(project_file: DependencyFile, visited_project_files: T.untyped)
150
+ .returns(Dependabot::FileParsers::Base::DependencySet)
151
+ end
127
152
  def parse_dependencies(project_file, visited_project_files)
128
153
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
129
154
 
@@ -152,6 +177,7 @@ module Dependabot
152
177
  dependency_set
153
178
  end
154
179
 
180
+ sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).void }
155
181
  def add_global_package_references(dependency_set)
156
182
  project_import_files.each do |file|
157
183
  doc = Nokogiri::XML(file.content)
@@ -169,11 +195,25 @@ module Dependabot
169
195
  end
170
196
  end
171
197
 
198
+ sig do
199
+ params(project_file: DependencyFile,
200
+ doc: Nokogiri::XML::Document,
201
+ dependency_set: Dependabot::FileParsers::Base::DependencySet,
202
+ visited_project_files: T::Set[String])
203
+ .void
204
+ end
172
205
  def add_transitive_dependencies(project_file, doc, dependency_set, visited_project_files)
173
206
  add_transitive_dependencies_from_packages(dependency_set)
174
207
  add_transitive_dependencies_from_project_references(project_file, doc, dependency_set, visited_project_files)
175
208
  end
176
209
 
210
+ sig do
211
+ params(project_file: DependencyFile,
212
+ doc: Nokogiri::XML::Document,
213
+ dependency_set: Dependabot::FileParsers::Base::DependencySet,
214
+ visited_project_files: T::Set[String])
215
+ .void
216
+ end
177
217
  def add_transitive_dependencies_from_project_references(project_file, doc, dependency_set,
178
218
  visited_project_files)
179
219
 
@@ -216,31 +256,33 @@ module Dependabot
216
256
  end
217
257
  end
218
258
 
219
- sig { params(full_path: T.untyped).returns(T::Array[T.nilable(String)]) }
259
+ sig { params(full_path: String).returns(T::Array[T.nilable(String)]) }
220
260
  def expand_wildcards_in_project_reference_path(full_path)
221
- full_path = T.let(File.join(@repo_contents_path, full_path), T.nilable(String))
222
- expanded_wildcard = Dir.glob(T.must(full_path))
223
-
224
- filtered_paths = []
261
+ full_path = File.join(@repo_contents_path, full_path)
225
262
 
226
263
  # For each expanded path, remove the @repo_contents_path prefix and leading slash
227
- expanded_wildcard.map do |path|
264
+ filtered_paths = Dir.glob(full_path).map do |path|
228
265
  # Remove @repo_contents_path prefix
229
- path = path.sub(@repo_contents_path, "")
266
+ path = path.sub(@repo_contents_path, "") if @repo_contents_path
230
267
  # Remove leading slash
231
268
  path = path[1..-1] if path.start_with?("/")
232
- filtered_paths << path
233
269
  path # Return the modified path
234
270
  end
235
271
 
272
+ return filtered_paths if filtered_paths.any?
273
+
236
274
  # If the wildcard didn't match anything, strip the @repo_contents_path prefix and return the original path.
237
- filtered_paths.any? ? filtered_paths : [T.must(full_path).sub(@repo_contents_path, "")[1..-1]]
275
+ full_path = full_path.sub(@repo_contents_path, "") if @repo_contents_path
276
+ full_path = full_path[1..-1] if full_path.start_with?("/")
277
+ [full_path]
238
278
  end
239
279
 
280
+ sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).void }
240
281
  def add_transitive_dependencies_from_packages(dependency_set)
241
282
  transitive_dependencies_from_packages(dependency_set.dependencies).each { |dep| dependency_set << dep }
242
283
  end
243
284
 
285
+ sig { params(dependencies: T::Array[Dependency]).returns(T::Array[Dependency]) }
244
286
  def transitive_dependencies_from_packages(dependencies)
245
287
  transitive_dependencies = {}
246
288
 
@@ -261,6 +303,11 @@ module Dependabot
261
303
  transitive_dependencies.values
262
304
  end
263
305
 
306
+ sig do
307
+ params(doc: Nokogiri::XML::Document,
308
+ dependency_set: Dependabot::FileParsers::Base::DependencySet,
309
+ project_file: DependencyFile).void
310
+ end
264
311
  def add_sdk_references(doc, dependency_set, project_file)
265
312
  # These come in 3 flavours:
266
313
  # - <Project Sdk="Name/Version">
@@ -273,8 +320,13 @@ module Dependabot
273
320
  add_sdk_refs_from_import_tags(doc, dependency_set, project_file)
274
321
  end
275
322
 
323
+ sig do
324
+ params(sdk_references: String,
325
+ dependency_set: Dependabot::FileParsers::Base::DependencySet,
326
+ project_file: DependencyFile).void
327
+ end
276
328
  def add_sdk_ref_from_project(sdk_references, dependency_set, project_file)
277
- sdk_references.split(";")&.each do |sdk_reference|
329
+ sdk_references.split(";").each do |sdk_reference|
278
330
  m = sdk_reference.match(PROJECT_SDK_REGEX)
279
331
  if m
280
332
  dependency = build_dependency(m[1], m[2], m[2], nil, project_file)
@@ -283,6 +335,11 @@ module Dependabot
283
335
  end
284
336
  end
285
337
 
338
+ sig do
339
+ params(doc: Nokogiri::XML::Document,
340
+ dependency_set: Dependabot::FileParsers::Base::DependencySet,
341
+ project_file: DependencyFile).void
342
+ end
286
343
  def add_sdk_refs_from_import_tags(doc, dependency_set, project_file)
287
344
  doc.xpath("/Project/Import").each do |import_node|
288
345
  next unless import_node.attribute("Sdk") && import_node.attribute("Version")
@@ -295,6 +352,11 @@ module Dependabot
295
352
  end
296
353
  end
297
354
 
355
+ sig do
356
+ params(doc: Nokogiri::XML::Document,
357
+ dependency_set: Dependabot::FileParsers::Base::DependencySet,
358
+ project_file: DependencyFile).void
359
+ end
298
360
  def add_sdk_refs_from_project(doc, dependency_set, project_file)
299
361
  doc.xpath("/Project").each do |project_node|
300
362
  sdk_references = project_node.attribute("Sdk")&.value&.strip
@@ -304,6 +366,11 @@ module Dependabot
304
366
  end
305
367
  end
306
368
 
369
+ sig do
370
+ params(doc: Nokogiri::XML::Document,
371
+ dependency_set: Dependabot::FileParsers::Base::DependencySet,
372
+ project_file: DependencyFile).void
373
+ end
307
374
  def add_sdk_refs_from_sdk_tags(doc, dependency_set, project_file)
308
375
  doc.xpath("/Project/Sdk").each do |sdk_node|
309
376
  next unless sdk_node.attribute("Version")
@@ -316,6 +383,15 @@ module Dependabot
316
383
  end
317
384
  end
318
385
 
386
+ sig do
387
+ params(name: T.nilable(String),
388
+ req: T.nilable(String),
389
+ version: T.nilable(String),
390
+ prop_name: T.nilable(String),
391
+ project_file: Dependabot::DependencyFile,
392
+ dev: T.untyped)
393
+ .returns(T.nilable(Dependabot::Dependency))
394
+ end
319
395
  def build_dependency(name, req, version, prop_name, project_file, dev: false)
320
396
  return unless name
321
397
 
@@ -350,6 +426,7 @@ module Dependabot
350
426
  dependency
351
427
  end
352
428
 
429
+ sig { params(dependency: Dependency).returns(T::Boolean) }
353
430
  def dependency_has_search_results?(dependency)
354
431
  dependency_urls = RepositoryFinder.new(
355
432
  dependency: dependency,
@@ -362,11 +439,13 @@ module Dependabot
362
439
  end
363
440
  end
364
441
 
442
+ sig { params(dependency_name: String, dependency_url: T::Hash[Symbol, String]).returns(T.nilable(T::Boolean)) }
365
443
  def dependency_url_has_matching_result?(dependency_name, dependency_url)
366
444
  versions = NugetClient.get_package_versions(dependency_name, dependency_url)
367
445
  versions&.any?
368
446
  end
369
447
 
448
+ sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
370
449
  def dependency_name(dependency_node, project_file)
371
450
  raw_name = get_attribute_value(dependency_node, "Include") ||
372
451
  get_attribute_value(dependency_node, "Update")
@@ -379,6 +458,7 @@ module Dependabot
379
458
  evaluated_value(raw_name, project_file)
380
459
  end
381
460
 
461
+ sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
382
462
  def dependency_requirement(dependency_node, project_file)
383
463
  raw_requirement = get_node_version_value(dependency_node) ||
384
464
  find_package_version(dependency_node, project_file)
@@ -387,6 +467,7 @@ module Dependabot
387
467
  evaluated_value(raw_requirement, project_file)
388
468
  end
389
469
 
470
+ sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
390
471
  def find_package_version(dependency_node, project_file)
391
472
  name = dependency_name(dependency_node, project_file)
392
473
  return unless name
@@ -397,32 +478,34 @@ module Dependabot
397
478
  package_version_string
398
479
  end
399
480
 
481
+ sig { returns(T::Hash[String, String]) }
400
482
  def package_versions
401
- @package_versions ||= begin
402
- package_versions = {}
403
- directory_packages_props_files.each do |file|
404
- doc = Nokogiri::XML(file.content)
405
- doc.remove_namespaces!
406
- doc.css(PACKAGE_VERSION_SELECTOR).each do |package_node|
407
- name = dependency_name(package_node, file)
408
- version = dependency_version(package_node, file)
409
- next unless name && version
410
-
411
- version = Version.new(version)
412
- existing_version = package_versions[name]
413
- next if existing_version && existing_version > version
414
-
415
- package_versions[name] = version
416
- end
483
+ @package_versions ||= T.let(parse_package_versions, T.nilable(T::Hash[String, String]))
484
+ end
485
+
486
+ sig { returns(T::Hash[String, String]) }
487
+ def parse_package_versions
488
+ package_versions = T.let({}, T::Hash[String, String])
489
+ directory_packages_props_files.each do |file|
490
+ doc = Nokogiri::XML(file.content)
491
+ doc.remove_namespaces!
492
+ doc.css(PACKAGE_VERSION_SELECTOR).each do |package_node|
493
+ name = dependency_name(package_node, file)
494
+ version = dependency_version(package_node, file)
495
+ next unless name && version
496
+
497
+ package_versions[name] = version
417
498
  end
418
- package_versions
419
499
  end
500
+ package_versions
420
501
  end
421
502
 
503
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
422
504
  def directory_packages_props_files
423
505
  dependency_files.select { |df| df.name.match?(/[Dd]irectory.[Pp]ackages.props/) }
424
506
  end
425
507
 
508
+ sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
426
509
  def dependency_version(dependency_node, project_file)
427
510
  requirement = dependency_requirement(dependency_node, project_file)
428
511
  return unless requirement
@@ -438,22 +521,24 @@ module Dependabot
438
521
  version
439
522
  end
440
523
 
524
+ sig { params(dependency_node: Nokogiri::XML::Node).returns(T.nilable(String)) }
441
525
  def req_property_name(dependency_node)
442
526
  raw_requirement = get_node_version_value(dependency_node)
443
527
  return unless raw_requirement
444
528
 
445
529
  return unless raw_requirement.match?(PROPERTY_REGEX)
446
530
 
447
- raw_requirement
448
- .match(PROPERTY_REGEX)
449
- .named_captures.fetch("property")
531
+ T.must(raw_requirement.match(PROPERTY_REGEX))
532
+ .named_captures.fetch("property")
450
533
  end
451
534
 
535
+ sig { params(node: Nokogiri::XML::Node).returns(T.nilable(String)) }
452
536
  def get_node_version_value(node)
453
537
  get_attribute_value(node, "Version") || get_attribute_value(node, "VersionOverride")
454
538
  end
455
539
 
456
540
  # rubocop:disable Metrics/PerceivedComplexity
541
+ sig { params(node: Nokogiri::XML::Node, attribute: String).returns(T.nilable(String)) }
457
542
  def get_attribute_value(node, attribute)
458
543
  value =
459
544
  node.attribute(attribute)&.value&.strip ||
@@ -465,20 +550,24 @@ module Dependabot
465
550
  end
466
551
  # rubocop:enable Metrics/PerceivedComplexity
467
552
 
553
+ sig { params(value: String, project_file: Dependabot::DependencyFile).returns(String) }
468
554
  def evaluated_value(value, project_file)
469
555
  return value unless value.match?(PROPERTY_REGEX)
470
556
 
471
- property_name = value.match(PROPERTY_REGEX)
472
- .named_captures.fetch("property")
557
+ property_name = T.must(value.match(PROPERTY_REGEX)&.named_captures&.fetch("property"))
473
558
  property_details = details_for_property(property_name, project_file)
474
559
 
475
560
  # Don't halt parsing for a missing property value until we're
476
561
  # confident we're fetching property values correctly
477
562
  return value unless property_details&.fetch(:value)
478
563
 
479
- value.gsub(PROPERTY_REGEX, property_details&.fetch(:value))
564
+ value.gsub(PROPERTY_REGEX, property_details.fetch(:value))
480
565
  end
481
566
 
567
+ sig do
568
+ params(property_name: String, project_file: Dependabot::DependencyFile)
569
+ .returns(T.nilable(T::Hash[T.untyped, T.untyped]))
570
+ end
482
571
  def details_for_property(property_name, project_file)
483
572
  property_value_finder
484
573
  .property_details(
@@ -487,11 +576,13 @@ module Dependabot
487
576
  )
488
577
  end
489
578
 
579
+ sig { returns(PropertyValueFinder) }
490
580
  def property_value_finder
491
581
  @property_value_finder ||=
492
- PropertyValueFinder.new(dependency_files: dependency_files)
582
+ T.let(PropertyValueFinder.new(dependency_files: dependency_files), T.nilable(PropertyValueFinder))
493
583
  end
494
584
 
585
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
495
586
  def project_import_files
496
587
  dependency_files -
497
588
  project_files -
@@ -501,22 +592,26 @@ module Dependabot
501
592
  [dotnet_tools_json]
502
593
  end
503
594
 
595
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
504
596
  def project_files
505
597
  dependency_files.select { |f| f.name.match?(/\.[a-z]{2}proj$/) }
506
598
  end
507
599
 
600
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
508
601
  def packages_config_files
509
602
  dependency_files.select do |f|
510
- f.name.split("/").last.casecmp("packages.config").zero?
603
+ f.name.split("/").last&.casecmp("packages.config")&.zero?
511
604
  end
512
605
  end
513
606
 
607
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
514
608
  def global_json
515
- dependency_files.find { |f| f.name.casecmp("global.json").zero? }
609
+ dependency_files.find { |f| f.name.casecmp("global.json")&.zero? }
516
610
  end
517
611
 
612
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
518
613
  def dotnet_tools_json
519
- dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json").zero? }
614
+ dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json")&.zero? }
520
615
  end
521
616
  end
522
617
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/nuget/file_fetcher/import_paths_finder"
@@ -11,12 +11,21 @@ module Dependabot
11
11
  module Nuget
12
12
  class FileParser
13
13
  class PropertyValueFinder
14
+ extend T::Sig
15
+
14
16
  PROPERTY_REGEX = /\$\((?<property>.*?)\)/
15
17
 
18
+ sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
16
19
  def initialize(dependency_files:)
17
20
  @dependency_files = dependency_files
18
21
  end
19
22
 
23
+ sig do
24
+ params(property_name: String,
25
+ callsite_file: Dependabot::DependencyFile,
26
+ stack: T::Array[[String, String]])
27
+ .returns(T.nilable(T::Hash[T.untyped, T.untyped]))
28
+ end
20
29
  def property_details(property_name:, callsite_file:, stack: [])
21
30
  stack += [[property_name, callsite_file.name]]
22
31
  return if property_name.include?("(")
@@ -53,12 +62,18 @@ module Dependabot
53
62
  check_next_level_of_stack(node_details, stack)
54
63
  end
55
64
 
65
+ sig do
66
+ params(node_details: T.untyped,
67
+ stack: T::Array[[String, String]])
68
+ .returns(T.nilable(T::Hash[T.untyped, T.untyped]))
69
+ end
56
70
  def check_next_level_of_stack(node_details, stack)
57
71
  property_name = node_details.fetch(:value)
58
72
  .match(PROPERTY_REGEX)
59
73
  .named_captures.fetch("property")
60
74
  callsite_file = dependency_files
61
75
  .find { |f| f.name == node_details.fetch(:file) }
76
+ return unless callsite_file
62
77
 
63
78
  raise "Circular reference!" if stack.include?([property_name, callsite_file.name])
64
79
 
@@ -71,8 +86,14 @@ module Dependabot
71
86
 
72
87
  private
73
88
 
89
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
74
90
  attr_reader :dependency_files
75
91
 
92
+ sig do
93
+ params(property: String,
94
+ file: Dependabot::DependencyFile)
95
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
96
+ end
76
97
  def deep_find_prop_node(property:, file:)
77
98
  doc = Nokogiri::XML(file.content)
78
99
  doc.remove_namespaces!
@@ -100,21 +121,34 @@ module Dependabot
100
121
  deep_find_prop_node(property: property, file: file)
101
122
  end
102
123
 
124
+ sig do
125
+ params(property: String, callsite_file: Dependabot::DependencyFile)
126
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
127
+ end
103
128
  def find_property_in_directory_build_targets(property:, callsite_file:)
104
129
  find_property_in_up_tree_files(property: property, callsite_file: callsite_file,
105
130
  expected_file_name: "Directory.Build.targets")
106
131
  end
107
132
 
133
+ sig do
134
+ params(property: String, callsite_file: Dependabot::DependencyFile)
135
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
136
+ end
108
137
  def find_property_in_directory_build_props(property:, callsite_file:)
109
138
  find_property_in_up_tree_files(property: property, callsite_file: callsite_file,
110
139
  expected_file_name: "Directory.Build.props")
111
140
  end
112
141
 
142
+ sig do
143
+ params(property: String, callsite_file: Dependabot::DependencyFile)
144
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
145
+ end
113
146
  def find_property_in_directory_packages_props(property:, callsite_file:)
114
147
  find_property_in_up_tree_files(property: property, callsite_file: callsite_file,
115
148
  expected_file_name: "Directory.Packages.props")
116
149
  end
117
150
 
151
+ sig { params(property: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
118
152
  def find_property_in_packages_props(property:)
119
153
  file = packages_props_file
120
154
  return unless file
@@ -122,15 +156,25 @@ module Dependabot
122
156
  deep_find_prop_node(property: property, file: file)
123
157
  end
124
158
 
159
+ sig do
160
+ params(property: String,
161
+ callsite_file: Dependabot::DependencyFile,
162
+ expected_file_name: String)
163
+ .returns(T.untyped)
164
+ end
125
165
  def find_property_in_up_tree_files(property:, callsite_file:, expected_file_name:)
126
166
  files = up_tree_files_for_project(callsite_file, expected_file_name)
127
- return unless files
128
167
  return if files.empty?
129
168
 
130
169
  # first file where we were able to find the node
131
- files.reduce(nil) { |acc, file| acc || deep_find_prop_node(property: property, file: file) }
170
+ files.reduce(T.let(nil, T.nilable(String))) do |acc, file|
171
+ acc || deep_find_prop_node(property: property, file: file)
172
+ end
132
173
  end
133
174
 
175
+ sig do
176
+ params(project_file: DependencyFile, expected_file_name: String).returns(T::Array[Dependabot::DependencyFile])
177
+ end
134
178
  def up_tree_files_for_project(project_file, expected_file_name)
135
179
  dir = File.dirname(project_file.name)
136
180
 
@@ -142,21 +186,29 @@ module Dependabot
142
186
 
143
187
  paths =
144
188
  possible_paths.uniq
145
- .select { |p| dependency_files.find { |f| f.name.casecmp(p).zero? } }
189
+ .select { |p| dependency_files.find { |f| f.name.casecmp(p)&.zero? } }
146
190
 
147
191
  dependency_files.select { |f| paths.include?(f.name) }
148
192
  end
149
193
 
194
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
150
195
  def packages_props_file
151
- dependency_files.find { |f| f.name.casecmp("Packages.props").zero? }
196
+ dependency_files.find { |f| f.name.casecmp("Packages.props")&.zero? }
152
197
  end
153
198
 
199
+ sig { params(property_name: String).returns(String) }
154
200
  def property_xpath(property_name)
155
201
  # only return properties that don't have a `Condition` attribute or the `Condition` attribute is checking for
156
202
  # an empty string, e.g., Condition="$(SomeProperty) == ''"
157
203
  %{/Project/PropertyGroup/#{property_name}[not(@Condition) or @Condition="$(#{property_name}) == ''"]}
158
204
  end
159
205
 
206
+ sig do
207
+ params(file: DependencyFile,
208
+ node: Nokogiri::XML::Node,
209
+ property: String)
210
+ .returns(T::Hash[Symbol, T.untyped])
211
+ end
160
212
  def node_details(file:, node:, property:)
161
213
  {
162
214
  file: file.name,