dependabot-nuget 0.246.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 (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"
@@ -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"
@@ -77,14 +77,14 @@ module Dependabot
77
77
  def global_json_dependencies
78
78
  return DependencySet.new unless global_json
79
79
 
80
- GlobalJsonParser.new(global_json: global_json).dependency_set
80
+ GlobalJsonParser.new(global_json: T.must(global_json)).dependency_set
81
81
  end
82
82
 
83
83
  sig { returns(Dependabot::FileParsers::Base::DependencySet) }
84
84
  def dotnet_tools_json_dependencies
85
85
  return DependencySet.new unless dotnet_tools_json
86
86
 
87
- DotNetToolsJsonParser.new(dotnet_tools_json: dotnet_tools_json).dependency_set
87
+ DotNetToolsJsonParser.new(dotnet_tools_json: T.must(dotnet_tools_json)).dependency_set
88
88
  end
89
89
 
90
90
  sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) }