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"
@@ -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,28 +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
- package_versions[name] = version
412
- 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
413
498
  end
414
- package_versions
415
499
  end
500
+ package_versions
416
501
  end
417
502
 
503
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
418
504
  def directory_packages_props_files
419
505
  dependency_files.select { |df| df.name.match?(/[Dd]irectory.[Pp]ackages.props/) }
420
506
  end
421
507
 
508
+ sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
422
509
  def dependency_version(dependency_node, project_file)
423
510
  requirement = dependency_requirement(dependency_node, project_file)
424
511
  return unless requirement
@@ -434,22 +521,24 @@ module Dependabot
434
521
  version
435
522
  end
436
523
 
524
+ sig { params(dependency_node: Nokogiri::XML::Node).returns(T.nilable(String)) }
437
525
  def req_property_name(dependency_node)
438
526
  raw_requirement = get_node_version_value(dependency_node)
439
527
  return unless raw_requirement
440
528
 
441
529
  return unless raw_requirement.match?(PROPERTY_REGEX)
442
530
 
443
- raw_requirement
444
- .match(PROPERTY_REGEX)
445
- .named_captures.fetch("property")
531
+ T.must(raw_requirement.match(PROPERTY_REGEX))
532
+ .named_captures.fetch("property")
446
533
  end
447
534
 
535
+ sig { params(node: Nokogiri::XML::Node).returns(T.nilable(String)) }
448
536
  def get_node_version_value(node)
449
537
  get_attribute_value(node, "Version") || get_attribute_value(node, "VersionOverride")
450
538
  end
451
539
 
452
540
  # rubocop:disable Metrics/PerceivedComplexity
541
+ sig { params(node: Nokogiri::XML::Node, attribute: String).returns(T.nilable(String)) }
453
542
  def get_attribute_value(node, attribute)
454
543
  value =
455
544
  node.attribute(attribute)&.value&.strip ||
@@ -461,20 +550,24 @@ module Dependabot
461
550
  end
462
551
  # rubocop:enable Metrics/PerceivedComplexity
463
552
 
553
+ sig { params(value: String, project_file: Dependabot::DependencyFile).returns(String) }
464
554
  def evaluated_value(value, project_file)
465
555
  return value unless value.match?(PROPERTY_REGEX)
466
556
 
467
- property_name = value.match(PROPERTY_REGEX)
468
- .named_captures.fetch("property")
557
+ property_name = T.must(value.match(PROPERTY_REGEX)&.named_captures&.fetch("property"))
469
558
  property_details = details_for_property(property_name, project_file)
470
559
 
471
560
  # Don't halt parsing for a missing property value until we're
472
561
  # confident we're fetching property values correctly
473
562
  return value unless property_details&.fetch(:value)
474
563
 
475
- value.gsub(PROPERTY_REGEX, property_details&.fetch(:value))
564
+ value.gsub(PROPERTY_REGEX, property_details.fetch(:value))
476
565
  end
477
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
478
571
  def details_for_property(property_name, project_file)
479
572
  property_value_finder
480
573
  .property_details(
@@ -483,11 +576,13 @@ module Dependabot
483
576
  )
484
577
  end
485
578
 
579
+ sig { returns(PropertyValueFinder) }
486
580
  def property_value_finder
487
581
  @property_value_finder ||=
488
- PropertyValueFinder.new(dependency_files: dependency_files)
582
+ T.let(PropertyValueFinder.new(dependency_files: dependency_files), T.nilable(PropertyValueFinder))
489
583
  end
490
584
 
585
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
491
586
  def project_import_files
492
587
  dependency_files -
493
588
  project_files -
@@ -497,22 +592,26 @@ module Dependabot
497
592
  [dotnet_tools_json]
498
593
  end
499
594
 
595
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
500
596
  def project_files
501
597
  dependency_files.select { |f| f.name.match?(/\.[a-z]{2}proj$/) }
502
598
  end
503
599
 
600
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
504
601
  def packages_config_files
505
602
  dependency_files.select do |f|
506
- f.name.split("/").last.casecmp("packages.config").zero?
603
+ f.name.split("/").last&.casecmp("packages.config")&.zero?
507
604
  end
508
605
  end
509
606
 
607
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
510
608
  def global_json
511
- dependency_files.find { |f| f.name.casecmp("global.json").zero? }
609
+ dependency_files.find { |f| f.name.casecmp("global.json")&.zero? }
512
610
  end
513
611
 
612
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
514
613
  def dotnet_tools_json
515
- 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? }
516
615
  end
517
616
  end
518
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,
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
@@ -44,6 +44,16 @@ module Dependabot
44
44
  Dependabot.logger.warn "Dependency '#{d.name}' excluded due to unparsable version: #{d.version}"
45
45
  end
46
46
 
47
+ dependency_info = dependencies.map do |d|
48
+ requirements_info = d.requirements.filter_map { |r| " file: #{r[:file]}, metadata: #{r[:metadata]}" }
49
+ .join("\n")
50
+ " name: #{d.name}, version: #{d.version}\n#{requirements_info}"
51
+ end.join("\n")
52
+
53
+ if dependencies.length.positive?
54
+ Dependabot.logger.info "The following dependencies were found:\n#{dependency_info}"
55
+ end
56
+
47
57
  dependencies
48
58
  end
49
59
 
@@ -77,14 +87,14 @@ module Dependabot
77
87
  def global_json_dependencies
78
88
  return DependencySet.new unless global_json
79
89
 
80
- GlobalJsonParser.new(global_json: global_json).dependency_set
90
+ GlobalJsonParser.new(global_json: T.must(global_json)).dependency_set
81
91
  end
82
92
 
83
93
  sig { returns(Dependabot::FileParsers::Base::DependencySet) }
84
94
  def dotnet_tools_json_dependencies
85
95
  return DependencySet.new unless dotnet_tools_json
86
96
 
87
- DotNetToolsJsonParser.new(dotnet_tools_json: dotnet_tools_json).dependency_set
97
+ DotNetToolsJsonParser.new(dotnet_tools_json: T.must(dotnet_tools_json)).dependency_set
88
98
  end
89
99
 
90
100
  sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) }