dependabot-nuget 0.236.0 → 0.238.0

Sign up to get free protection for your applications and to get access to all the features.
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 +72 -63
  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/metadata_finder.rb +4 -1
  13. data/lib/dependabot/nuget/native_helpers.rb +94 -0
  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 +114 -0
  17. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +86 -0
  18. data/lib/dependabot/nuget/update_checker/property_updater.rb +30 -3
  19. data/lib/dependabot/nuget/update_checker/repository_finder.rb +32 -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 +49 -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.236.0
4
+ version: 0.238.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-10-26 00:00:00.000000000 Z
11
+ date: 2023-12-07 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.236.0
19
+ version: 0.238.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.236.0
26
+ version: 0.238.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
@@ -94,20 +114,34 @@ dependencies:
94
114
  - - "~>"
95
115
  - !ruby/object:Gem::Version
96
116
  version: '1.3'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rspec-sorbet
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 1.9.2
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 1.9.2
97
131
  - !ruby/object:Gem::Dependency
98
132
  name: rubocop
99
133
  requirement: !ruby/object:Gem::Requirement
100
134
  requirements:
101
135
  - - "~>"
102
136
  - !ruby/object:Gem::Version
103
- version: 1.56.0
137
+ version: 1.57.2
104
138
  type: :development
105
139
  prerelease: false
106
140
  version_requirements: !ruby/object:Gem::Requirement
107
141
  requirements:
108
142
  - - "~>"
109
143
  - !ruby/object:Gem::Version
110
- version: 1.56.0
144
+ version: 1.57.2
111
145
  - !ruby/object:Gem::Dependency
112
146
  name: rubocop-performance
113
147
  requirement: !ruby/object:Gem::Requirement
@@ -201,6 +235,7 @@ extensions: []
201
235
  extra_rdoc_files: []
202
236
  files:
203
237
  - lib/dependabot/nuget.rb
238
+ - lib/dependabot/nuget/cache_manager.rb
204
239
  - lib/dependabot/nuget/file_fetcher.rb
205
240
  - lib/dependabot/nuget/file_fetcher/import_paths_finder.rb
206
241
  - lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb
@@ -211,15 +246,20 @@ files:
211
246
  - lib/dependabot/nuget/file_parser/project_file_parser.rb
212
247
  - lib/dependabot/nuget/file_parser/property_value_finder.rb
213
248
  - lib/dependabot/nuget/file_updater.rb
214
- - lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb
215
- - lib/dependabot/nuget/file_updater/project_file_declaration_finder.rb
216
249
  - lib/dependabot/nuget/file_updater/property_value_updater.rb
217
250
  - lib/dependabot/nuget/metadata_finder.rb
251
+ - lib/dependabot/nuget/native_helpers.rb
218
252
  - lib/dependabot/nuget/requirement.rb
219
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
220
258
  - lib/dependabot/nuget/update_checker/property_updater.rb
221
259
  - lib/dependabot/nuget/update_checker/repository_finder.rb
222
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
223
263
  - lib/dependabot/nuget/update_checker/version_finder.rb
224
264
  - lib/dependabot/nuget/version.rb
225
265
  homepage: https://github.com/dependabot/dependabot-core
@@ -227,7 +267,7 @@ licenses:
227
267
  - Nonstandard
228
268
  metadata:
229
269
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
230
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.236.0
270
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.238.0
231
271
  post_install_message:
232
272
  rdoc_options: []
233
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