dependabot-nuget 0.237.0 → 0.239.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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/nuget/cache_manager.rb +22 -0
  3. data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -0
  4. data/lib/dependabot/nuget/file_fetcher.rb +61 -64
  5. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +2 -1
  6. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +2 -1
  7. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +22 -4
  8. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +287 -15
  9. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +24 -52
  10. data/lib/dependabot/nuget/file_parser.rb +4 -1
  11. data/lib/dependabot/nuget/file_updater.rb +123 -117
  12. data/lib/dependabot/nuget/native_helpers.rb +94 -0
  13. data/lib/dependabot/nuget/requirement.rb +5 -1
  14. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +85 -0
  15. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +228 -0
  16. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +119 -0
  17. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +83 -0
  18. data/lib/dependabot/nuget/update_checker/property_updater.rb +30 -3
  19. data/lib/dependabot/nuget/update_checker/repository_finder.rb +36 -10
  20. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +31 -0
  21. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +127 -0
  22. data/lib/dependabot/nuget/update_checker/version_finder.rb +47 -6
  23. data/lib/dependabot/nuget/update_checker.rb +42 -8
  24. data/lib/dependabot/nuget.rb +2 -0
  25. metadata +35 -9
  26. data/lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb +0 -70
  27. data/lib/dependabot/nuget/file_updater/project_file_declaration_finder.rb +0 -183
@@ -0,0 +1,127 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "excon"
5
+ require "nokogiri"
6
+
7
+ require "dependabot/nuget/version"
8
+ require "dependabot/nuget/requirement"
9
+ require "dependabot/nuget/native_helpers"
10
+ require "dependabot/nuget/update_checker"
11
+ require "dependabot/shared_helpers"
12
+
13
+ module Dependabot
14
+ module Nuget
15
+ class UpdateChecker
16
+ class TfmFinder
17
+ require "dependabot/nuget/file_parser/packages_config_parser"
18
+ require "dependabot/nuget/file_parser/project_file_parser"
19
+
20
+ def initialize(dependency_files:, credentials:)
21
+ @dependency_files = dependency_files
22
+ @credentials = credentials
23
+ end
24
+
25
+ def frameworks(dependency)
26
+ tfms = Set.new
27
+ tfms += project_file_tfms(dependency)
28
+ tfms += project_import_file_tfms
29
+ tfms.to_a
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :dependency_files, :credentials
35
+
36
+ def project_file_tfms(dependency)
37
+ project_files_with_dependency(dependency).flat_map do |file|
38
+ project_file_parser.target_frameworks(project_file: file)
39
+ end
40
+ end
41
+
42
+ def project_files_with_dependency(dependency)
43
+ project_files.select do |file|
44
+ packages_config_contains_dependency?(file, dependency) ||
45
+ project_file_contains_dependency?(file, dependency)
46
+ end
47
+ end
48
+
49
+ def packages_config_contains_dependency?(file, dependency)
50
+ config_file = find_packages_config_file(file)
51
+ return false unless config_file
52
+
53
+ config_parser = FileParser::PackagesConfigParser.new(packages_config: config_file)
54
+ config_parser.dependency_set.dependencies.any? do |d|
55
+ d.name.casecmp(dependency.name).zero?
56
+ end
57
+ end
58
+
59
+ def project_file_contains_dependency?(file, dependency)
60
+ project_file_parser.dependency_set(project_file: file).dependencies.any? do |d|
61
+ d.name.casecmp(dependency.name).zero?
62
+ end
63
+ end
64
+
65
+ def find_packages_config_file(file)
66
+ return file if file.name.end_with?("packages.config")
67
+
68
+ filename = File.basename(file.name)
69
+ search_path = file.name.sub(filename, "packages.config")
70
+
71
+ dependency_files.find { |f| f.name.casecmp(search_path).zero? }
72
+ end
73
+
74
+ def project_import_file_tfms
75
+ @project_import_file_tfms ||= project_import_files.flat_map do |file|
76
+ project_file_parser.target_frameworks(project_file: file)
77
+ end
78
+ end
79
+
80
+ def project_file_parser
81
+ @project_file_parser ||=
82
+ FileParser::ProjectFileParser.new(
83
+ dependency_files: dependency_files,
84
+ credentials: credentials
85
+ )
86
+ end
87
+
88
+ def project_files
89
+ projfile = /\.[a-z]{2}proj$/
90
+ packageprops = /[Dd]irectory.[Pp]ackages.props/
91
+
92
+ dependency_files.select do |df|
93
+ df.name.match?(projfile) ||
94
+ df.name.match?(packageprops)
95
+ end
96
+ end
97
+
98
+ def packages_config_files
99
+ dependency_files.select do |f|
100
+ f.name.split("/").last.casecmp("packages.config").zero?
101
+ end
102
+ end
103
+
104
+ def project_import_files
105
+ dependency_files -
106
+ project_files -
107
+ packages_config_files -
108
+ nuget_configs -
109
+ [global_json] -
110
+ [dotnet_tools_json]
111
+ end
112
+
113
+ def nuget_configs
114
+ dependency_files.select { |f| f.name.match?(/nuget\.config$/i) }
115
+ end
116
+
117
+ def global_json
118
+ dependency_files.find { |f| f.name.casecmp("global.json").zero? }
119
+ end
120
+
121
+ def dotnet_tools_json
122
+ dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json").zero? }
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -1,19 +1,16 @@
1
1
  # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
- require "excon"
5
- require "nokogiri"
6
-
7
4
  require "dependabot/nuget/version"
8
5
  require "dependabot/nuget/requirement"
9
6
  require "dependabot/update_checkers/version_filters"
10
7
  require "dependabot/nuget/update_checker"
11
- require "dependabot/shared_helpers"
12
8
 
13
9
  module Dependabot
14
10
  module Nuget
15
11
  class UpdateChecker
16
12
  class VersionFinder
13
+ require_relative "compatibility_checker"
17
14
  require_relative "repository_finder"
18
15
 
19
16
  NUGET_RANGE_REGEX = /[\(\[].*,.*[\)\]]/
@@ -35,7 +32,8 @@ module Dependabot
35
32
  possible_versions = versions
36
33
  possible_versions = filter_prereleases(possible_versions)
37
34
  possible_versions = filter_ignored_versions(possible_versions)
38
- possible_versions.max_by { |hash| hash.fetch(:version) }
35
+
36
+ find_highest_compatible_version(possible_versions)
39
37
  end
40
38
  end
41
39
 
@@ -50,7 +48,7 @@ module Dependabot
50
48
  possible_versions = filter_ignored_versions(possible_versions)
51
49
  possible_versions = filter_lower_versions(possible_versions)
52
50
 
53
- possible_versions.min_by { |hash| hash.fetch(:version) }
51
+ find_lowest_compatible_version(possible_versions)
54
52
  end
55
53
  end
56
54
 
@@ -63,6 +61,49 @@ module Dependabot
63
61
 
64
62
  private
65
63
 
64
+ def find_highest_compatible_version(possible_versions)
65
+ # sorted versions descending
66
+ sorted_versions = possible_versions.sort_by { |v| v.fetch(:version) }.reverse
67
+ find_compatible_version(sorted_versions)
68
+ end
69
+
70
+ def find_lowest_compatible_version(possible_versions)
71
+ # sorted versions ascending
72
+ sorted_versions = possible_versions.sort_by { |v| v.fetch(:version) }
73
+ find_compatible_version(sorted_versions)
74
+ end
75
+
76
+ def find_compatible_version(sorted_versions)
77
+ # By checking the first version separately, we can avoid additional network requests
78
+ first_version = sorted_versions.first
79
+ return unless first_version
80
+ # If the current package version is incompatible, then we don't enforce compatibility.
81
+ # It could appear incompatible because they are ignoring NU1701 or the package is poorly authored.
82
+ return first_version unless version_compatible?(dependency.version)
83
+ return first_version if version_compatible?(first_version.fetch(:version))
84
+
85
+ sorted_versions.bsearch { |v| version_compatible?(v.fetch(:version)) }
86
+ end
87
+
88
+ def version_compatible?(version)
89
+ str_version_compatible?(version.to_s)
90
+ end
91
+
92
+ def str_version_compatible?(version)
93
+ compatibility_checker.compatible?(version)
94
+ end
95
+
96
+ def compatibility_checker
97
+ @compatibility_checker ||= CompatibilityChecker.new(
98
+ dependency_urls: dependency_urls,
99
+ dependency: dependency,
100
+ tfm_finder: TfmFinder.new(
101
+ dependency_files: dependency_files,
102
+ credentials: credentials
103
+ )
104
+ )
105
+ end
106
+
66
107
  def filter_prereleases(possible_versions)
67
108
  possible_versions.reject do |d|
68
109
  version = d.fetch(:version)
@@ -11,16 +11,19 @@ module Dependabot
11
11
  require_relative "update_checker/version_finder"
12
12
  require_relative "update_checker/property_updater"
13
13
  require_relative "update_checker/requirements_updater"
14
+ require_relative "update_checker/dependency_finder"
14
15
 
15
16
  def latest_version
17
+ # No need to find latest version for transitive dependencies unless they have a vulnerability.
18
+ return dependency.version if !dependency.top_level? && !vulnerable?
19
+
16
20
  @latest_version = latest_version_details&.fetch(:version)
17
21
  end
18
22
 
19
23
  def latest_resolvable_version
20
- # TODO: Check version resolution!
21
- return nil if version_comes_from_multi_dependency_property?
22
-
23
- latest_version
24
+ # We always want a full unlock since any package update could update peer dependencies as well.
25
+ # To force a full unlock instead of an own unlock, we return nil.
26
+ nil
24
27
  end
25
28
 
26
29
  def lowest_security_fix_version
@@ -41,13 +44,16 @@ module Dependabot
41
44
  def updated_requirements
42
45
  RequirementsUpdater.new(
43
46
  requirements: dependency.requirements,
44
- latest_version: preferred_resolvable_version&.to_s,
45
- source_details: preferred_version_details
47
+ latest_version: preferred_resolvable_version_details.fetch(:version)&.to_s,
48
+ source_details: preferred_resolvable_version_details
46
49
  &.slice(:nuspec_url, :repo_url, :source_url)
47
50
  ).updated_requirements
48
51
  end
49
52
 
50
53
  def up_to_date?
54
+ # No need to update transitive dependencies unless they have a vulnerability.
55
+ return true if !dependency.top_level? && !vulnerable?
56
+
51
57
  # If any requirements have an uninterpolated property in them then
52
58
  # that property couldn't be found, and we assume that the dependency
53
59
  # is up-to-date
@@ -68,14 +74,42 @@ module Dependabot
68
74
 
69
75
  private
70
76
 
77
+ def preferred_resolvable_version_details
78
+ # If this dependency is vulnerable, prefer trying to update to the
79
+ # lowest_resolvable_security_fix_version. Otherwise update all the way
80
+ # to the latest_resolvable_version.
81
+ return lowest_security_fix_version_details if vulnerable?
82
+
83
+ latest_version_details
84
+ end
85
+
71
86
  def latest_version_resolvable_with_full_unlock?
72
- return false unless version_comes_from_multi_dependency_property?
87
+ # We always want a full unlock since any package update could update peer dependencies as well.
88
+ return true unless version_comes_from_multi_dependency_property?
73
89
 
74
90
  property_updater.update_possible?
75
91
  end
76
92
 
77
93
  def updated_dependencies_after_full_unlock
78
- property_updater.updated_dependencies
94
+ return property_updater.updated_dependencies if version_comes_from_multi_dependency_property?
95
+
96
+ puts "Finding updated dependencies for #{dependency.name}."
97
+
98
+ updated_dependency = Dependency.new(
99
+ name: dependency.name,
100
+ version: latest_version&.to_s,
101
+ requirements: updated_requirements,
102
+ previous_version: dependency.version,
103
+ previous_requirements: dependency.requirements,
104
+ package_manager: dependency.package_manager
105
+ )
106
+ updated_dependencies = [updated_dependency]
107
+ updated_dependencies += DependencyFinder.new(
108
+ dependency: updated_dependency,
109
+ dependency_files: dependency_files,
110
+ credentials: credentials
111
+ ).updated_peer_dependencies
112
+ updated_dependencies
79
113
  end
80
114
 
81
115
  def preferred_version_details
@@ -24,3 +24,5 @@ Dependabot::Dependency.register_production_check(
24
24
  groups.include?("dependencies")
25
25
  end
26
26
  )
27
+
28
+ Dependabot::Utils.register_always_clone("nuget")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.237.0
4
+ version: 0.239.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-21 00:00:00.000000000 Z
11
+ date: 2023-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,34 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.237.0
19
+ version: 0.239.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.237.0
26
+ version: 0.239.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubyzip
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.3.2
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '3.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.3.2
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
27
47
  - !ruby/object:Gem::Dependency
28
48
  name: debug
29
49
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +134,14 @@ dependencies:
114
134
  requirements:
115
135
  - - "~>"
116
136
  - !ruby/object:Gem::Version
117
- version: 1.57.2
137
+ version: 1.58.0
118
138
  type: :development
119
139
  prerelease: false
120
140
  version_requirements: !ruby/object:Gem::Requirement
121
141
  requirements:
122
142
  - - "~>"
123
143
  - !ruby/object:Gem::Version
124
- version: 1.57.2
144
+ version: 1.58.0
125
145
  - !ruby/object:Gem::Dependency
126
146
  name: rubocop-performance
127
147
  requirement: !ruby/object:Gem::Requirement
@@ -215,6 +235,7 @@ extensions: []
215
235
  extra_rdoc_files: []
216
236
  files:
217
237
  - lib/dependabot/nuget.rb
238
+ - lib/dependabot/nuget/cache_manager.rb
218
239
  - lib/dependabot/nuget/file_fetcher.rb
219
240
  - lib/dependabot/nuget/file_fetcher/import_paths_finder.rb
220
241
  - lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb
@@ -225,15 +246,20 @@ files:
225
246
  - lib/dependabot/nuget/file_parser/project_file_parser.rb
226
247
  - lib/dependabot/nuget/file_parser/property_value_finder.rb
227
248
  - lib/dependabot/nuget/file_updater.rb
228
- - lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb
229
- - lib/dependabot/nuget/file_updater/project_file_declaration_finder.rb
230
249
  - lib/dependabot/nuget/file_updater/property_value_updater.rb
231
250
  - lib/dependabot/nuget/metadata_finder.rb
251
+ - lib/dependabot/nuget/native_helpers.rb
232
252
  - lib/dependabot/nuget/requirement.rb
233
253
  - lib/dependabot/nuget/update_checker.rb
254
+ - lib/dependabot/nuget/update_checker/compatibility_checker.rb
255
+ - lib/dependabot/nuget/update_checker/dependency_finder.rb
256
+ - lib/dependabot/nuget/update_checker/nupkg_fetcher.rb
257
+ - lib/dependabot/nuget/update_checker/nuspec_fetcher.rb
234
258
  - lib/dependabot/nuget/update_checker/property_updater.rb
235
259
  - lib/dependabot/nuget/update_checker/repository_finder.rb
236
260
  - lib/dependabot/nuget/update_checker/requirements_updater.rb
261
+ - lib/dependabot/nuget/update_checker/tfm_comparer.rb
262
+ - lib/dependabot/nuget/update_checker/tfm_finder.rb
237
263
  - lib/dependabot/nuget/update_checker/version_finder.rb
238
264
  - lib/dependabot/nuget/version.rb
239
265
  homepage: https://github.com/dependabot/dependabot-core
@@ -241,7 +267,7 @@ licenses:
241
267
  - Nonstandard
242
268
  metadata:
243
269
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
244
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.237.0
270
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.239.0
245
271
  post_install_message:
246
272
  rdoc_options: []
247
273
  require_paths:
@@ -1,70 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "nokogiri"
5
- require "dependabot/nuget/file_updater"
6
-
7
- module Dependabot
8
- module Nuget
9
- class FileUpdater
10
- class PackagesConfigDeclarationFinder
11
- DECLARATION_REGEX =
12
- %r{<package\s[^>]*?/>|
13
- <package\s[^>]*?[^/]>.*?</package>}mx
14
-
15
- attr_reader :dependency_name, :declaring_requirement,
16
- :packages_config
17
-
18
- def initialize(dependency_name:, packages_config:,
19
- declaring_requirement:)
20
- @dependency_name = dependency_name
21
- @packages_config = packages_config
22
- @declaring_requirement = declaring_requirement
23
-
24
- if declaring_requirement[:file].split("/").last
25
- .casecmp("packages.config").zero?
26
- return
27
- end
28
-
29
- raise "Requirement not from packages.config!"
30
- end
31
-
32
- def declaration_strings
33
- @declaration_strings ||= fetch_declaration_strings
34
- end
35
-
36
- def declaration_nodes
37
- declaration_strings.map do |declaration_string|
38
- Nokogiri::XML(declaration_string)
39
- end
40
- end
41
-
42
- private
43
-
44
- # rubocop:disable Metrics/PerceivedComplexity
45
- def fetch_declaration_strings
46
- deep_find_declarations(packages_config.content).select do |nd|
47
- node = Nokogiri::XML(nd)
48
- node.remove_namespaces!
49
- node = node.at_xpath("/package")
50
-
51
- node_name = node.attribute("id")&.value&.strip ||
52
- node.at_xpath("./id")&.content&.strip
53
- next false unless node_name&.downcase == dependency_name&.downcase
54
-
55
- node_requirement = node.attribute("version")&.value&.strip ||
56
- node.at_xpath("./version")&.content&.strip
57
- node_requirement == declaring_requirement.fetch(:requirement)
58
- end
59
- end
60
- # rubocop:enable Metrics/PerceivedComplexity
61
-
62
- def deep_find_declarations(string)
63
- string.scan(DECLARATION_REGEX).flat_map do |matching_node|
64
- [matching_node, *deep_find_declarations(matching_node[0..-2])]
65
- end
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,183 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "nokogiri"
5
- require "dependabot/nuget/file_updater"
6
-
7
- module Dependabot
8
- module Nuget
9
- class FileUpdater
10
- class ProjectFileDeclarationFinder
11
- DECLARATION_REGEX =
12
- %r{
13
- <PackageReference [^>]*?/>|
14
- <PackageReference [^>]*?[^/]>.*?</PackageReference>|
15
- <GlobalPackageReference [^>]*?/>|
16
- <GlobalPackageReference [^>]*?[^/]>.*?</GlobalPackageReference>|
17
- <PackageVersion [^>]*?/>|
18
- <PackageVersion [^>]*?[^/]>.*?</PackageVersion>|
19
- <Dependency [^>]*?/>|
20
- <Dependency [^>]*?[^/]>.*?</Dependency>|
21
- <DevelopmentDependency [^>]*?/>|
22
- <DevelopmentDependency [^>]*?[^/]>.*?</DevelopmentDependency>
23
- }mx
24
- SDK_IMPORT_REGEX =
25
- / <Import [^>]*?Sdk="[^"]*?"[^>]*?Version="[^"]*?"[^>]*?>
26
- | <Import [^>]*?Version="[^"]*?"[^>]*?Sdk="[^"]*?"[^>]*?>
27
- /mx
28
- SDK_PROJECT_REGEX =
29
- / <Project [^>]*?Sdk="[^"]*?"[^>]*?>
30
- /mx
31
- SDK_SDK_REGEX =
32
- / <Sdk [^>]*?Name="[^"]*?"[^>]*?Version="[^"]*?"[^>]*?>
33
- | <Sdk [^>]*?Version="[^"]*?"[^>]*?Name="[^"]*?"[^>]*?>
34
- /mx
35
-
36
- attr_reader :dependency_name, :declaring_requirement,
37
- :dependency_files
38
-
39
- def initialize(dependency_name:, dependency_files:,
40
- declaring_requirement:)
41
- @dependency_name = dependency_name
42
- @dependency_files = dependency_files
43
- @declaring_requirement = declaring_requirement
44
- end
45
-
46
- def declaration_strings
47
- @declaration_strings ||= fetch_declaration_strings
48
- @declaration_strings += fetch_sdk_strings
49
- end
50
-
51
- def declaration_nodes
52
- declaration_strings.map do |declaration_string|
53
- Nokogiri::XML(declaration_string)
54
- end
55
- end
56
-
57
- private
58
-
59
- def get_element_from_node(node)
60
- node.at_xpath("/PackageReference") ||
61
- node.at_xpath("/GlobalPackageReference") ||
62
- node.at_xpath("/PackageVersion") ||
63
- node.at_xpath("/Dependency") ||
64
- node.at_xpath("/DevelopmentDependency")
65
- end
66
-
67
- # rubocop:disable Metrics/CyclomaticComplexity
68
- # rubocop:disable Metrics/PerceivedComplexity
69
- def fetch_declaration_strings
70
- deep_find_declarations(declaring_file.content).select do |nd|
71
- node = Nokogiri::XML(nd)
72
- node.remove_namespaces!
73
- node = get_element_from_node(node)
74
-
75
- node_name = node.attribute("Include")&.value&.strip ||
76
- node.at_xpath("./Include")&.content&.strip ||
77
- node.attribute("Update")&.value&.strip ||
78
- node.at_xpath("./Update")&.content&.strip
79
- next false unless node_name&.downcase == dependency_name&.downcase
80
-
81
- node_requirement = get_node_version_value(node)
82
- node_requirement == declaring_requirement.fetch(:requirement)
83
- end
84
- end
85
- # rubocop:enable Metrics/PerceivedComplexity
86
- # rubocop:enable Metrics/CyclomaticComplexity
87
-
88
- def fetch_sdk_strings
89
- sdk_project_strings + sdk_sdk_strings + sdk_import_strings
90
- end
91
-
92
- # rubocop:disable Metrics/PerceivedComplexity
93
- def get_node_version_value(node)
94
- attribute = "Version"
95
- node.attribute(attribute)&.value&.strip ||
96
- node.at_xpath("./#{attribute}")&.content&.strip ||
97
- node.attribute(attribute.downcase)&.value&.strip ||
98
- node.at_xpath("./#{attribute.downcase}")&.content&.strip
99
- end
100
- # rubocop:enable Metrics/PerceivedComplexity
101
-
102
- def deep_find_declarations(string)
103
- string.scan(DECLARATION_REGEX).flat_map do |matching_node|
104
- [matching_node, *deep_find_declarations(matching_node[0..-2])]
105
- end
106
- end
107
-
108
- def declaring_file
109
- filename = declaring_requirement.fetch(:file)
110
- declaring_file = dependency_files.find { |f| f.name == filename }
111
- return declaring_file if declaring_file
112
-
113
- raise "No file found with name #{filename}!"
114
- end
115
-
116
- def sdk_import_strings
117
- sdk_strings(SDK_IMPORT_REGEX, "Import", "Sdk", "Version")
118
- end
119
-
120
- def parse_element(string, name)
121
- xml = string
122
- xml += "</#{name}>" unless string.end_with?("/>")
123
- node = Nokogiri::XML(xml)
124
- node.remove_namespaces!
125
- node.at_xpath("/#{name}")
126
- end
127
-
128
- def get_attribute_value_nocase(element, name)
129
- value = element.attribute(name)&.value ||
130
- element.attribute(name.downcase)&.value ||
131
- element.attribute(name.upcase)&.value
132
- value&.strip
133
- end
134
-
135
- def desired_sdk_reference?(sdk_reference, dep_name, dep_version)
136
- parts = sdk_reference.split("/")
137
- parts.length == 2 && parts[0]&.downcase == dep_name && parts[1] == dep_version
138
- end
139
-
140
- def sdk_project_strings
141
- dep_name = dependency_name&.downcase
142
- dep_version = declaring_requirement.fetch(:requirement)
143
- strings = []
144
- declaring_file.content.scan(SDK_PROJECT_REGEX).each do |string|
145
- element = parse_element(string, "Project")
146
- next unless element
147
-
148
- sdk_references = get_attribute_value_nocase(element, "Sdk")
149
- next unless sdk_references&.include?("/")
150
-
151
- sdk_references.split(";").each do |sdk_reference|
152
- strings << sdk_reference if desired_sdk_reference?(sdk_reference, dep_name, dep_version)
153
- end
154
- end
155
- strings.uniq
156
- end
157
-
158
- def sdk_sdk_strings
159
- sdk_strings(SDK_SDK_REGEX, "Sdk", "Name", "Version")
160
- end
161
-
162
- def sdk_strings(regex, element_name, name_attribute, version_attribute)
163
- dep_name = dependency_name&.downcase
164
- dep_version = declaring_requirement.fetch(:requirement)
165
- strings = []
166
- declaring_file.content.scan(regex).each do |string|
167
- element = parse_element(string, element_name)
168
- next unless element
169
-
170
- node_name = get_attribute_value_nocase(element, name_attribute)&.downcase
171
- next unless node_name == dep_name
172
-
173
- node_version = get_attribute_value_nocase(element, version_attribute)
174
- next unless node_version == dep_version
175
-
176
- strings << string
177
- end
178
- strings
179
- end
180
- end
181
- end
182
- end
183
- end