dependabot-nuget 0.250.0 → 0.252.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Common.props +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Packages.props +26 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +35 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/NuGetUpdater.Cli.csproj +1 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +4 -7
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +251 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/NuGetUpdater.Cli.Test.csproj +3 -3
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Dependency.cs +56 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyType.cs +1 -1
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs +69 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscoveryResult.cs +11 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +217 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DotNetToolsJsonDiscovery.cs +30 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DotNetToolsJsonDiscoveryResult.cs +10 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/GlobalJsonDiscovery.cs +30 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/GlobalJsonDiscoveryResult.cs +10 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/IDiscoveryResult.cs +14 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +29 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscoveryResult.cs +10 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +13 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +127 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +13 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EvaluationResult.cs +8 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EvaluationResultType.cs +9 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/BuildFile.cs +6 -8
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/DotNetToolsJsonBuildFile.cs +4 -7
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +24 -17
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +2 -2
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +8 -13
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +100 -19
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/XmlBuildFile.cs +2 -2
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +6 -6
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Property.cs +6 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +23 -36
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +5 -10
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +16 -21
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +4 -19
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/HashSetExtensions.cs +14 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ImmutableArrayExtensions.cs +18 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +0 -1
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +121 -67
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +27 -4
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +117 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +91 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +71 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +59 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +380 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +306 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +36 -0
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/DotNetToolsJsonBuildFileTests.cs +1 -2
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +2 -3
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/PackagesConfigBuildFileTests.cs +4 -6
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +6 -5
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +4 -3
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +38 -6
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +12 -40
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +30 -0
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/AssertEx.cs +272 -0
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/DiffUtil.cs +266 -0
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +195 -152
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs +7 -11
  63. data/lib/dependabot/nuget/discovery/dependency_details.rb +95 -0
  64. data/lib/dependabot/nuget/discovery/dependency_file_discovery.rb +126 -0
  65. data/lib/dependabot/nuget/discovery/directory_packages_props_discovery.rb +43 -0
  66. data/lib/dependabot/nuget/discovery/discovery_json_reader.rb +83 -0
  67. data/lib/dependabot/nuget/discovery/evaluation_details.rb +63 -0
  68. data/lib/dependabot/nuget/discovery/project_discovery.rb +71 -0
  69. data/lib/dependabot/nuget/discovery/property_details.rb +43 -0
  70. data/lib/dependabot/nuget/discovery/workspace_discovery.rb +66 -0
  71. data/lib/dependabot/nuget/file_parser.rb +19 -128
  72. data/lib/dependabot/nuget/file_updater.rb +28 -60
  73. data/lib/dependabot/nuget/native_helpers.rb +55 -0
  74. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +3 -8
  75. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +1 -0
  76. data/lib/dependabot/nuget/update_checker/property_updater.rb +1 -0
  77. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +17 -152
  78. data/lib/dependabot/nuget/update_checker/version_finder.rb +1 -6
  79. data/lib/dependabot/nuget/update_checker.rb +4 -1
  80. metadata +43 -11
  81. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +0 -71
  82. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +0 -68
  83. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +0 -92
  84. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +0 -620
  85. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +0 -225
  86. data/lib/dependabot/nuget/file_updater/property_value_updater.rb +0 -81
@@ -1,620 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "nokogiri"
5
- require "sorbet-runtime"
6
-
7
- require "dependabot/dependency"
8
- require "dependabot/nuget/file_parser"
9
- require "dependabot/nuget/update_checker"
10
- require "dependabot/nuget/cache_manager"
11
- require "dependabot/nuget/nuget_client"
12
-
13
- # For details on how dotnet handles version constraints, see:
14
- # https://docs.microsoft.com/en-us/nuget/reference/package-versioning
15
- module Dependabot
16
- module Nuget
17
- class FileParser
18
- class ProjectFileParser # rubocop:disable Metrics/ClassLength
19
- extend T::Sig
20
-
21
- require "dependabot/file_parsers/base/dependency_set"
22
- require_relative "property_value_finder"
23
- require_relative "../update_checker/repository_finder"
24
-
25
- DEPENDENCY_SELECTOR = "ItemGroup > PackageReference, " \
26
- "ItemGroup > GlobalPackageReference, " \
27
- "ItemGroup > PackageVersion, " \
28
- "ItemGroup > Dependency, " \
29
- "ItemGroup > DevelopmentDependency"
30
-
31
- PROJECT_REFERENCE_SELECTOR = "ItemGroup > ProjectReference"
32
-
33
- PROJECT_FILE_SELECTOR = "ItemGroup > ProjectFile"
34
-
35
- PACKAGE_REFERENCE_SELECTOR = "ItemGroup > PackageReference, " \
36
- "ItemGroup > GlobalPackageReference"
37
-
38
- PACKAGE_VERSION_SELECTOR = "ItemGroup > PackageVersion"
39
-
40
- PROJECT_SDK_REGEX = %r{^([^/]+)/(\d+(?:[.]\d+(?:[.]\d+)?)?(?:[+-].*)?)$}
41
- PROPERTY_REGEX = /\$\((?<property>.*?)\)/
42
- ITEM_REGEX = /\@\((?<property>.*?)\)/
43
-
44
- sig { returns(T::Hash[String, Dependabot::FileParsers::Base::DependencySet]) }
45
- def self.dependency_set_cache
46
- CacheManager.cache("project_file_dependency_set")
47
- end
48
-
49
- sig { returns(T::Hash[String, T.untyped]) }
50
- def self.dependency_url_search_cache
51
- CacheManager.cache("dependency_url_search_cache")
52
- end
53
-
54
- sig do
55
- params(dependency_files: T::Array[DependencyFile],
56
- credentials: T::Array[Credential],
57
- repo_contents_path: T.nilable(String)).void
58
- end
59
- def initialize(dependency_files:, credentials:, repo_contents_path:)
60
- @dependency_files = dependency_files
61
- @credentials = credentials
62
- @repo_contents_path = repo_contents_path
63
- end
64
-
65
- sig do
66
- params(project_file: DependencyFile, visited_project_files: T::Set[String])
67
- .returns(Dependabot::FileParsers::Base::DependencySet)
68
- end
69
- def dependency_set(project_file:, visited_project_files: Set.new)
70
- key = "#{project_file.name.downcase}::#{project_file.content.hash}"
71
- cache = ProjectFileParser.dependency_set_cache
72
-
73
- visited_project_files.add(cache[key])
74
-
75
- # Pass the visited_project_files set to parse_dependencies
76
- cache[key] ||= parse_dependencies(project_file, visited_project_files)
77
- end
78
-
79
- sig { params(project_file: DependencyFile).returns(T::Set[String]) }
80
- def downstream_file_references(project_file:)
81
- file_set = T.let(Set.new, T::Set[String])
82
-
83
- doc = Nokogiri::XML(project_file.content)
84
- doc.remove_namespaces!
85
- proj_refs = doc.css(PROJECT_REFERENCE_SELECTOR)
86
- proj_files = doc.css(PROJECT_FILE_SELECTOR)
87
- ref_nodes = proj_refs + proj_files
88
- ref_nodes.each do |project_reference_node|
89
- dep_file = get_attribute_value(project_reference_node, "Include")
90
- next unless dep_file
91
-
92
- full_project_path = full_path(project_file, dep_file)
93
- full_project_path = full_project_path[1..-1] if full_project_path.start_with?("/")
94
- full_project_paths = expand_wildcards_in_project_reference_path(T.must(full_project_path))
95
- full_project_paths.each do |full_project_path_expanded|
96
- file_set << full_project_path_expanded if full_project_path_expanded
97
- end
98
- end
99
-
100
- file_set
101
- end
102
-
103
- sig { params(project_file: DependencyFile).returns(T::Array[String]) }
104
- def target_frameworks(project_file:)
105
- target_framework = details_for_property("TargetFramework", project_file)
106
- return [target_framework.fetch(:value)] if target_framework
107
-
108
- target_frameworks = details_for_property("TargetFrameworks", project_file)
109
- return target_frameworks.fetch(:value)&.split(";") if target_frameworks
110
-
111
- target_framework = details_for_property("TargetFrameworkVersion", project_file)
112
- return [] unless target_framework
113
-
114
- # TargetFrameworkVersion is a string like "v4.7.2"
115
- value = target_framework.fetch(:value)
116
- # convert it to a string like "net472"
117
- ["net#{value[1..-1].delete('.')}"]
118
- end
119
-
120
- sig { returns(T::Array[Dependabot::DependencyFile]) }
121
- def nuget_configs
122
- dependency_files.select { |f| f.name.match?(%r{(^|/)nuget\.config$}i) }
123
- end
124
-
125
- private
126
-
127
- sig { returns(T::Array[DependencyFile]) }
128
- attr_reader :dependency_files
129
-
130
- sig { returns(T::Array[Credential]) }
131
- attr_reader :credentials
132
-
133
- sig { params(project_file: DependencyFile, ref_path: String).returns(String) }
134
- def full_path(project_file, ref_path)
135
- project_file_directory = File.dirname(project_file.name)
136
- is_rooted = project_file_directory.start_with?("/")
137
- # Root the directory path to avoid expand_path prepending the working directory
138
- project_file_directory = "/" + project_file_directory unless is_rooted
139
-
140
- # normalize path separators
141
- relative_path = ref_path.tr("\\", "/")
142
- # path is relative to the project file directory
143
- relative_path = File.join(project_file_directory, relative_path)
144
- result = File.expand_path(relative_path)
145
- result = result[1..-1] unless is_rooted
146
- T.must(result)
147
- end
148
-
149
- sig do
150
- params(project_file: DependencyFile, visited_project_files: T.untyped)
151
- .returns(Dependabot::FileParsers::Base::DependencySet)
152
- end
153
- def parse_dependencies(project_file, visited_project_files)
154
- dependency_set = Dependabot::FileParsers::Base::DependencySet.new
155
-
156
- doc = Nokogiri::XML(project_file.content)
157
- doc.remove_namespaces!
158
- # Look for regular package references
159
- doc.css(DEPENDENCY_SELECTOR).each do |dependency_node|
160
- name = dependency_name(dependency_node, project_file)
161
- req = dependency_requirement(dependency_node, project_file)
162
- version = dependency_version(dependency_node, project_file)
163
- prop_name = req_property_name(dependency_node)
164
- is_dev = dependency_node.name == "DevelopmentDependency"
165
-
166
- dependency = build_dependency(name, req, version, prop_name, project_file, dev: is_dev)
167
- dependency_set << dependency if dependency
168
- end
169
-
170
- add_global_package_references(dependency_set)
171
-
172
- add_transitive_dependencies(project_file, doc, dependency_set, visited_project_files)
173
-
174
- # Look for SDK references; see:
175
- # https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk
176
- add_sdk_references(doc, dependency_set, project_file)
177
-
178
- dependency_set
179
- end
180
-
181
- sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).void }
182
- def add_global_package_references(dependency_set)
183
- project_import_files.each do |file|
184
- doc = Nokogiri::XML(file.content)
185
- doc.remove_namespaces!
186
-
187
- doc.css(PACKAGE_REFERENCE_SELECTOR).each do |dependency_node|
188
- name = dependency_name(dependency_node, file)
189
- req = dependency_requirement(dependency_node, file)
190
- version = dependency_version(dependency_node, file)
191
- prop_name = req_property_name(dependency_node)
192
-
193
- dependency = build_dependency(name, req, version, prop_name, file)
194
- dependency_set << dependency if dependency
195
- end
196
- end
197
- end
198
-
199
- sig do
200
- params(project_file: DependencyFile,
201
- doc: Nokogiri::XML::Document,
202
- dependency_set: Dependabot::FileParsers::Base::DependencySet,
203
- visited_project_files: T::Set[String])
204
- .void
205
- end
206
- def add_transitive_dependencies(project_file, doc, dependency_set, visited_project_files)
207
- add_transitive_dependencies_from_packages(dependency_set)
208
- add_transitive_dependencies_from_project_references(project_file, doc, dependency_set, visited_project_files)
209
- end
210
-
211
- sig do
212
- params(project_file: DependencyFile,
213
- doc: Nokogiri::XML::Document,
214
- dependency_set: Dependabot::FileParsers::Base::DependencySet,
215
- visited_project_files: T::Set[String])
216
- .void
217
- end
218
- def add_transitive_dependencies_from_project_references(project_file, doc, dependency_set,
219
- visited_project_files)
220
-
221
- # if visited_project_files is an empty set then new up a new set
222
- visited_project_files = Set.new if visited_project_files.nil?
223
- # Look for regular project references
224
- project_refs = doc.css(PROJECT_REFERENCE_SELECTOR)
225
- # Look for ProjectFile references (dirs.proj)
226
- project_files = doc.css(PROJECT_FILE_SELECTOR)
227
- ref_nodes = project_refs + project_files
228
-
229
- ref_nodes.each do |reference_node|
230
- relative_path = dependency_name(reference_node, project_file)
231
- # This could result from a <ProjectReference Remove="..." /> item.
232
- next unless relative_path
233
-
234
- full_project_path = full_path(project_file, relative_path)
235
-
236
- full_project_paths = expand_wildcards_in_project_reference_path(full_project_path)
237
-
238
- full_project_paths.each do |path|
239
- # Check if we've already visited this project file
240
- next if visited_project_files.include?(path)
241
-
242
- visited_project_files.add(path)
243
- referenced_file = dependency_files.find { |f| f.name == path }
244
- next unless referenced_file
245
-
246
- dependency_set(project_file: referenced_file,
247
- visited_project_files: visited_project_files).dependencies.each do |dep|
248
- dependency = Dependency.new(
249
- name: dep.name,
250
- version: dep.version,
251
- package_manager: dep.package_manager,
252
- requirements: []
253
- )
254
- dependency_set << dependency
255
- end
256
- end
257
- end
258
- end
259
-
260
- sig { params(full_path: String).returns(T::Array[T.nilable(String)]) }
261
- def expand_wildcards_in_project_reference_path(full_path)
262
- full_path = File.join(@repo_contents_path, full_path)
263
-
264
- # For each expanded path, remove the @repo_contents_path prefix and leading slash
265
- filtered_paths = Dir.glob(full_path).map do |path|
266
- # Remove @repo_contents_path prefix
267
- path = path.sub(@repo_contents_path, "") if @repo_contents_path
268
- # Remove leading slash
269
- path = path[1..-1] if path.start_with?("/")
270
- path # Return the modified path
271
- end
272
-
273
- return filtered_paths if filtered_paths.any?
274
-
275
- # If the wildcard didn't match anything, strip the @repo_contents_path prefix and return the original path.
276
- full_path = full_path.sub(@repo_contents_path, "") if @repo_contents_path
277
- full_path = full_path[1..-1] if full_path.start_with?("/")
278
- [full_path]
279
- end
280
-
281
- sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).void }
282
- def add_transitive_dependencies_from_packages(dependency_set)
283
- transitive_dependencies_from_packages(dependency_set.dependencies).each { |dep| dependency_set << dep }
284
- end
285
-
286
- sig { params(dependencies: T::Array[Dependency]).returns(T::Array[Dependency]) }
287
- def transitive_dependencies_from_packages(dependencies)
288
- transitive_dependencies = {}
289
-
290
- dependencies.each do |dependency|
291
- UpdateChecker::DependencyFinder.new(
292
- dependency: dependency,
293
- dependency_files: dependency_files,
294
- credentials: credentials,
295
- repo_contents_path: @repo_contents_path
296
- ).transitive_dependencies.each do |transitive_dep|
297
- visited_dep = transitive_dependencies[transitive_dep.name.downcase]
298
- next if !visited_dep.nil? && visited_dep.numeric_version > transitive_dep.numeric_version
299
-
300
- transitive_dependencies[transitive_dep.name.downcase] = transitive_dep
301
- end
302
- end
303
-
304
- transitive_dependencies.values
305
- end
306
-
307
- sig do
308
- params(doc: Nokogiri::XML::Document,
309
- dependency_set: Dependabot::FileParsers::Base::DependencySet,
310
- project_file: DependencyFile).void
311
- end
312
- def add_sdk_references(doc, dependency_set, project_file)
313
- # These come in 3 flavours:
314
- # - <Project Sdk="Name/Version">
315
- # - <Sdk Name="Name" Version="Version" />
316
- # - <Import Project="..." Sdk="Name" Version="Version" />
317
- # None of these support the use of properties, nor do they allow child
318
- # elements instead of attributes.
319
- add_sdk_refs_from_project(doc, dependency_set, project_file)
320
- add_sdk_refs_from_sdk_tags(doc, dependency_set, project_file)
321
- add_sdk_refs_from_import_tags(doc, dependency_set, project_file)
322
- end
323
-
324
- sig do
325
- params(sdk_references: String,
326
- dependency_set: Dependabot::FileParsers::Base::DependencySet,
327
- project_file: DependencyFile).void
328
- end
329
- def add_sdk_ref_from_project(sdk_references, dependency_set, project_file)
330
- sdk_references.split(";").each do |sdk_reference|
331
- m = sdk_reference.match(PROJECT_SDK_REGEX)
332
- if m
333
- dependency = build_dependency(m[1], m[2], m[2], nil, project_file)
334
- dependency_set << dependency if dependency
335
- end
336
- end
337
- end
338
-
339
- sig do
340
- params(doc: Nokogiri::XML::Document,
341
- dependency_set: Dependabot::FileParsers::Base::DependencySet,
342
- project_file: DependencyFile).void
343
- end
344
- def add_sdk_refs_from_import_tags(doc, dependency_set, project_file)
345
- doc.xpath("/Project/Import").each do |import_node|
346
- next unless import_node.attribute("Sdk") && import_node.attribute("Version")
347
-
348
- name = import_node.attribute("Sdk")&.value&.strip
349
- version = import_node.attribute("Version")&.value&.strip
350
-
351
- dependency = build_dependency(name, version, version, nil, project_file)
352
- dependency_set << dependency if dependency
353
- end
354
- end
355
-
356
- sig do
357
- params(doc: Nokogiri::XML::Document,
358
- dependency_set: Dependabot::FileParsers::Base::DependencySet,
359
- project_file: DependencyFile).void
360
- end
361
- def add_sdk_refs_from_project(doc, dependency_set, project_file)
362
- doc.xpath("/Project").each do |project_node|
363
- sdk_references = project_node.attribute("Sdk")&.value&.strip
364
- next unless sdk_references
365
-
366
- add_sdk_ref_from_project(sdk_references, dependency_set, project_file)
367
- end
368
- end
369
-
370
- sig do
371
- params(doc: Nokogiri::XML::Document,
372
- dependency_set: Dependabot::FileParsers::Base::DependencySet,
373
- project_file: DependencyFile).void
374
- end
375
- def add_sdk_refs_from_sdk_tags(doc, dependency_set, project_file)
376
- doc.xpath("/Project/Sdk").each do |sdk_node|
377
- next unless sdk_node.attribute("Version")
378
-
379
- name = sdk_node.attribute("Name")&.value&.strip
380
- version = sdk_node.attribute("Version")&.value&.strip
381
-
382
- dependency = build_dependency(name, version, version, nil, project_file)
383
- dependency_set << dependency if dependency
384
- end
385
- end
386
-
387
- sig do
388
- params(name: T.nilable(String),
389
- req: T.nilable(String),
390
- version: T.nilable(String),
391
- prop_name: T.nilable(String),
392
- project_file: Dependabot::DependencyFile,
393
- dev: T.untyped)
394
- .returns(T.nilable(Dependabot::Dependency))
395
- end
396
- def build_dependency(name, req, version, prop_name, project_file, dev: false)
397
- return unless name
398
-
399
- # Exclude any dependencies specified using interpolation
400
- return if [name, req, version].any? { |s| s&.include?("%(") }
401
-
402
- requirement = {
403
- requirement: req,
404
- file: project_file.name,
405
- groups: [dev ? "devDependencies" : "dependencies"],
406
- source: nil
407
- }
408
-
409
- if prop_name
410
- # Get the root property name unless no details could be found,
411
- # in which case use the top-level name to ease debugging
412
- root_prop_name = details_for_property(prop_name, project_file)
413
- &.fetch(:root_property_name) || prop_name
414
- requirement[:metadata] = { property_name: root_prop_name }
415
- end
416
-
417
- dependency = Dependency.new(
418
- name: name,
419
- version: version,
420
- package_manager: "nuget",
421
- requirements: [requirement]
422
- )
423
-
424
- # only include dependency if one of the sources has it
425
- return unless dependency_has_search_results?(dependency)
426
-
427
- dependency
428
- end
429
-
430
- sig { params(dependency: Dependency).returns(T::Boolean) }
431
- def dependency_has_search_results?(dependency)
432
- dependency_urls = RepositoryFinder.new(
433
- dependency: dependency,
434
- credentials: credentials,
435
- config_files: nuget_configs
436
- ).dependency_urls
437
- dependency_urls = [RepositoryFinder.get_default_repository_details(dependency.name)] if dependency_urls.empty?
438
- dependency_urls.any? do |dependency_url|
439
- dependency_url_has_matching_result?(dependency.name, dependency_url)
440
- end
441
- end
442
-
443
- sig { params(dependency_name: String, dependency_url: T::Hash[Symbol, String]).returns(T.nilable(T::Boolean)) }
444
- def dependency_url_has_matching_result?(dependency_name, dependency_url)
445
- versions = NugetClient.get_package_versions(dependency_name, dependency_url)
446
- versions&.any?
447
- end
448
-
449
- sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
450
- def dependency_name(dependency_node, project_file)
451
- raw_name = get_attribute_value(dependency_node, "Include") ||
452
- get_attribute_value(dependency_node, "Update")
453
- return unless raw_name
454
-
455
- # If the item contains @(ItemGroup) then ignore as it
456
- # updates a set of ItemGroup elements
457
- return if raw_name.match?(ITEM_REGEX)
458
-
459
- evaluated_value(raw_name, project_file)
460
- end
461
-
462
- sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
463
- def dependency_requirement(dependency_node, project_file)
464
- raw_requirement = get_node_version_value(dependency_node) ||
465
- find_package_version(dependency_node, project_file)
466
- return unless raw_requirement
467
-
468
- evaluated_value(raw_requirement, project_file)
469
- end
470
-
471
- sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
472
- def find_package_version(dependency_node, project_file)
473
- name = dependency_name(dependency_node, project_file)
474
- return unless name
475
-
476
- package_version_string = package_versions[name].to_s
477
- return unless package_version_string != ""
478
-
479
- package_version_string
480
- end
481
-
482
- sig { returns(T::Hash[String, String]) }
483
- def package_versions
484
- @package_versions ||= T.let(parse_package_versions, T.nilable(T::Hash[String, String]))
485
- end
486
-
487
- sig { returns(T::Hash[String, String]) }
488
- def parse_package_versions
489
- package_versions = T.let({}, T::Hash[String, String])
490
- directory_packages_props_files.each do |file|
491
- doc = Nokogiri::XML(file.content)
492
- doc.remove_namespaces!
493
- doc.css(PACKAGE_VERSION_SELECTOR).each do |package_node|
494
- name = dependency_name(package_node, file)
495
- version = dependency_version(package_node, file)
496
- next unless name && version
497
-
498
- package_versions[name] = version
499
- end
500
- end
501
- package_versions
502
- end
503
-
504
- sig { returns(T::Array[Dependabot::DependencyFile]) }
505
- def directory_packages_props_files
506
- dependency_files.select { |df| df.name.match?(/[Dd]irectory.[Pp]ackages.props/) }
507
- end
508
-
509
- sig { params(dependency_node: Nokogiri::XML::Node, project_file: DependencyFile).returns(T.nilable(String)) }
510
- def dependency_version(dependency_node, project_file)
511
- requirement = dependency_requirement(dependency_node, project_file)
512
- return unless requirement
513
-
514
- # Remove brackets if present
515
- version = requirement.gsub(/[\(\)\[\]]/, "").strip
516
-
517
- # We don't know the version for range requirements or wildcard
518
- # requirements, so return `nil` for these.
519
- return if version.include?(",") || version.include?("*") ||
520
- version == ""
521
-
522
- version
523
- end
524
-
525
- sig { params(dependency_node: Nokogiri::XML::Node).returns(T.nilable(String)) }
526
- def req_property_name(dependency_node)
527
- raw_requirement = get_node_version_value(dependency_node)
528
- return unless raw_requirement
529
-
530
- return unless raw_requirement.match?(PROPERTY_REGEX)
531
-
532
- T.must(raw_requirement.match(PROPERTY_REGEX))
533
- .named_captures.fetch("property")
534
- end
535
-
536
- sig { params(node: Nokogiri::XML::Node).returns(T.nilable(String)) }
537
- def get_node_version_value(node)
538
- get_attribute_value(node, "Version") || get_attribute_value(node, "VersionOverride")
539
- end
540
-
541
- # rubocop:disable Metrics/PerceivedComplexity
542
- sig { params(node: Nokogiri::XML::Node, attribute: String).returns(T.nilable(String)) }
543
- def get_attribute_value(node, attribute)
544
- value =
545
- node.attribute(attribute)&.value&.strip ||
546
- node.at_xpath("./#{attribute}")&.content&.strip ||
547
- node.attribute(attribute.downcase)&.value&.strip ||
548
- node.at_xpath("./#{attribute.downcase}")&.content&.strip
549
-
550
- value == "" ? nil : value
551
- end
552
- # rubocop:enable Metrics/PerceivedComplexity
553
-
554
- sig { params(value: String, project_file: Dependabot::DependencyFile).returns(String) }
555
- def evaluated_value(value, project_file)
556
- return value unless value.match?(PROPERTY_REGEX)
557
-
558
- property_name = T.must(value.match(PROPERTY_REGEX)&.named_captures&.fetch("property"))
559
- property_details = details_for_property(property_name, project_file)
560
-
561
- # Don't halt parsing for a missing property value until we're
562
- # confident we're fetching property values correctly
563
- return value unless property_details&.fetch(:value)
564
-
565
- value.gsub(PROPERTY_REGEX, property_details.fetch(:value))
566
- end
567
-
568
- sig do
569
- params(property_name: String, project_file: Dependabot::DependencyFile)
570
- .returns(T.nilable(T::Hash[T.untyped, T.untyped]))
571
- end
572
- def details_for_property(property_name, project_file)
573
- property_value_finder
574
- .property_details(
575
- property_name: property_name,
576
- callsite_file: project_file
577
- )
578
- end
579
-
580
- sig { returns(PropertyValueFinder) }
581
- def property_value_finder
582
- @property_value_finder ||=
583
- T.let(PropertyValueFinder.new(dependency_files: dependency_files), T.nilable(PropertyValueFinder))
584
- end
585
-
586
- sig { returns(T::Array[Dependabot::DependencyFile]) }
587
- def project_import_files
588
- dependency_files -
589
- project_files -
590
- packages_config_files -
591
- nuget_configs -
592
- [global_json] -
593
- [dotnet_tools_json]
594
- end
595
-
596
- sig { returns(T::Array[Dependabot::DependencyFile]) }
597
- def project_files
598
- dependency_files.select { |f| f.name.match?(/\.[a-z]{2}proj$/) }
599
- end
600
-
601
- sig { returns(T::Array[Dependabot::DependencyFile]) }
602
- def packages_config_files
603
- dependency_files.select do |f|
604
- f.name.split("/").last&.casecmp("packages.config")&.zero?
605
- end
606
- end
607
-
608
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
609
- def global_json
610
- dependency_files.find { |f| f.name.casecmp("global.json")&.zero? }
611
- end
612
-
613
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
614
- def dotnet_tools_json
615
- dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json")&.zero? }
616
- end
617
- end
618
- end
619
- end
620
- end