dependabot-nuget 0.246.0 → 0.248.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +40 -6
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +27 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +18 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +110 -0
  6. data/lib/dependabot/nuget/cache_manager.rb +9 -3
  7. data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -12
  8. data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +13 -3
  9. data/lib/dependabot/nuget/file_fetcher.rb +89 -37
  10. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +10 -2
  11. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +10 -2
  12. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +11 -2
  13. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +140 -41
  14. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +57 -5
  15. data/lib/dependabot/nuget/file_parser.rb +13 -3
  16. data/lib/dependabot/nuget/file_updater/property_value_updater.rb +25 -8
  17. data/lib/dependabot/nuget/file_updater.rb +74 -38
  18. data/lib/dependabot/nuget/http_response_helpers.rb +6 -1
  19. data/lib/dependabot/nuget/metadata_finder.rb +27 -3
  20. data/lib/dependabot/nuget/nuget_client.rb +23 -0
  21. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +10 -1
  22. data/lib/dependabot/nuget/requirement.rb +21 -9
  23. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +26 -15
  24. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +87 -21
  25. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +25 -3
  26. data/lib/dependabot/nuget/update_checker/repository_finder.rb +25 -3
  27. data/lib/dependabot/nuget/update_checker/requirements_updater.rb +32 -9
  28. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +2 -2
  29. data/lib/dependabot/nuget/update_checker/version_finder.rb +178 -64
  30. data/lib/dependabot/nuget/update_checker.rb +76 -32
  31. data/lib/dependabot/nuget/version.rb +7 -2
  32. metadata +19 -5
@@ -1,4 +1,4 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/file_fetchers"
@@ -18,19 +18,40 @@ module Dependabot
18
18
 
19
19
  BUILD_FILE_NAMES = /^Directory\.Build\.(props|targets)$/i # Directory.Build.props, Directory.Build.targets
20
20
 
21
+ sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
21
22
  def self.required_files_in?(filenames)
22
23
  return true if filenames.any? { |f| f.match?(/^packages\.config$/i) }
23
24
  return true if filenames.any? { |f| f.end_with?(".sln") }
24
25
  return true if filenames.any? { |f| f.match?("^src$") }
25
26
  return true if filenames.any? { |f| f.end_with?(".proj") }
26
27
 
27
- filenames.any? { |name| name.match?(/\.[a-z]{2}proj$/) }
28
+ filenames.any? { |name| name.match?(/\.(cs|vb|fs)proj$/) }
28
29
  end
29
30
 
31
+ sig { override.returns(String) }
30
32
  def self.required_files_message
31
33
  "Repo must contain a .proj file, .(cs|vb|fs)proj file, or a packages.config."
32
34
  end
33
35
 
36
+ sig do
37
+ params(
38
+ source: Dependabot::Source,
39
+ credentials: T::Array[Credential],
40
+ repo_contents_path: T.nilable(String),
41
+ options: T::Hash[String, String]
42
+ ).void
43
+ end
44
+ def initialize(source:, credentials:, repo_contents_path: nil, options: {})
45
+ super(source: source, credentials: credentials, repo_contents_path: repo_contents_path, options: options)
46
+
47
+ @sln_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
48
+ @sln_project_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
49
+ @project_files = T.let([], T::Array[Dependabot::DependencyFile])
50
+ @fetched_files = T.let({}, T::Hash[String, T::Array[Dependabot::DependencyFile]])
51
+ @nuget_config_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
52
+ @packages_config_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
53
+ end
54
+
34
55
  sig { override.returns(T::Array[DependencyFile]) }
35
56
  def fetch_files
36
57
  fetched_files = []
@@ -50,12 +71,9 @@ module Dependabot
50
71
  end
51
72
 
52
73
  if project_files.none? && packages_config_files.none?
53
- raise @missing_sln_project_file_errors.first if @missing_sln_project_file_errors&.any?
74
+ raise T.must(@missing_sln_project_file_errors.first) if @missing_sln_project_file_errors&.any?
54
75
 
55
- raise(
56
- Dependabot::DependencyFileNotFound,
57
- File.join(directory, "<anything>.(cs|vb|fs)proj")
58
- )
76
+ raise_dependency_file_not_found
59
77
  end
60
78
 
61
79
  fetched_files
@@ -63,8 +81,11 @@ module Dependabot
63
81
 
64
82
  private
65
83
 
84
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
66
85
  def project_files
67
- @project_files ||=
86
+ return @project_files if @project_files.any?
87
+
88
+ @project_files =
68
89
  begin
69
90
  project_files = []
70
91
  project_files += csproj_file
@@ -78,19 +99,27 @@ module Dependabot
78
99
  project_files
79
100
  end
80
101
  rescue Octokit::NotFound, Gitlab::Error::NotFound
102
+ raise_dependency_file_not_found
103
+ end
104
+
105
+ sig { returns(T.noreturn) }
106
+ def raise_dependency_file_not_found
81
107
  raise(
82
- Dependabot::DependencyFileNotFound,
83
- File.join(directory, "<anything>.(cs|vb|fs)proj")
108
+ Dependabot::DependencyFileNotFound.new(
109
+ File.join(directory, "*.(sln|csproj|vbproj|fsproj|proj)"),
110
+ "Unable to find `*.sln`, `*.(cs|vb|fs)proj`, or `*.proj` in directory `#{directory}`"
111
+ )
84
112
  )
85
113
  end
86
114
 
115
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
87
116
  def packages_config_files
88
117
  return @packages_config_files if @packages_config_files
89
118
 
90
119
  candidate_paths =
91
120
  [*project_files.map { |f| File.dirname(f.name) }, "."].uniq
92
121
 
93
- @packages_config_files ||=
122
+ @packages_config_files =
94
123
  candidate_paths.filter_map do |dir|
95
124
  file = repo_contents(dir: dir)
96
125
  .find { |f| f.name.casecmp("packages.config").zero? }
@@ -99,6 +128,7 @@ module Dependabot
99
128
  end
100
129
 
101
130
  # rubocop:disable Metrics/PerceivedComplexity
131
+ sig { returns(T.nilable(T::Array[T.untyped])) }
102
132
  def sln_file_names
103
133
  sln_files = repo_contents.select { |f| f.name.end_with?(".sln") }
104
134
  src_dir = repo_contents.any? { |f| f.name == "src" && f.type == "dir" }
@@ -117,13 +147,15 @@ module Dependabot
117
147
  end
118
148
  # rubocop:enable Metrics/PerceivedComplexity
119
149
 
150
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
120
151
  def directory_build_files
121
- @directory_build_files ||= fetch_directory_build_files
152
+ @directory_build_files ||= T.let(fetch_directory_build_files, T.nilable(T::Array[Dependabot::DependencyFile]))
122
153
  end
123
154
 
155
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
124
156
  def fetch_directory_build_files
125
- attempted_dirs = []
126
- directory_build_files = []
157
+ attempted_dirs = T.let([], T::Array[Pathname])
158
+ directory_build_files = T.let([], T::Array[Dependabot::DependencyFile])
127
159
  directory_path = Pathname.new(directory)
128
160
 
129
161
  # find all build files (Directory.Build.props/.targets) relative to the given project file
@@ -145,12 +177,13 @@ module Dependabot
145
177
  directory_build_files
146
178
  end
147
179
 
180
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
148
181
  def sln_project_files
149
182
  return [] unless sln_files
150
183
 
151
184
  @sln_project_files ||=
152
185
  begin
153
- paths = sln_files.flat_map do |sln_file|
186
+ paths = T.must(sln_files).flat_map do |sln_file|
154
187
  SlnProjectPathsFinder
155
188
  .new(sln_file: sln_file)
156
189
  .project_paths
@@ -159,7 +192,7 @@ module Dependabot
159
192
  paths.filter_map do |path|
160
193
  fetch_file_from_host(path)
161
194
  rescue Dependabot::DependencyFileNotFound => e
162
- @missing_sln_project_file_errors ||= []
195
+ @missing_sln_project_file_errors ||= T.let([], T.nilable(T::Array[Dependabot::DependencyFileNotFound]))
163
196
  @missing_sln_project_file_errors << e
164
197
  # Don't worry about missing files too much for now (at least
165
198
  # until we start resolving properties)
@@ -168,35 +201,42 @@ module Dependabot
168
201
  end
169
202
  end
170
203
 
204
+ sig { returns(T.nilable(T::Array[Dependabot::DependencyFile])) }
171
205
  def sln_files
172
206
  return unless sln_file_names
173
207
 
174
208
  @sln_files ||=
175
209
  sln_file_names
176
- .map { |sln_file_name| fetch_file_from_host(sln_file_name) }
177
- .select { |file| file.content.valid_encoding? }
210
+ &.map { |sln_file_name| fetch_file_from_host(sln_file_name) }
211
+ &.select { |file| file.content&.valid_encoding? }
178
212
  end
179
213
 
214
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
180
215
  def csproj_file
181
- @csproj_file ||= find_and_fetch_with_suffix(".csproj")
216
+ @csproj_file ||= T.let(find_and_fetch_with_suffix(".csproj"), T.nilable(T::Array[Dependabot::DependencyFile]))
182
217
  end
183
218
 
219
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
184
220
  def vbproj_file
185
- @vbproj_file ||= find_and_fetch_with_suffix(".vbproj")
221
+ @vbproj_file ||= T.let(find_and_fetch_with_suffix(".vbproj"), T.nilable(T::Array[Dependabot::DependencyFile]))
186
222
  end
187
223
 
224
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
188
225
  def fsproj_file
189
- @fsproj_file ||= find_and_fetch_with_suffix(".fsproj")
226
+ @fsproj_file ||= T.let(find_and_fetch_with_suffix(".fsproj"), T.nilable(T::Array[Dependabot::DependencyFile]))
190
227
  end
191
228
 
229
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
192
230
  def proj_files
193
- @proj_files ||= find_and_fetch_with_suffix(".proj")
231
+ @proj_files ||= T.let(find_and_fetch_with_suffix(".proj"), T.nilable(T::Array[Dependabot::DependencyFile]))
194
232
  end
195
233
 
234
+ sig { params(suffix: String).returns(T::Array[Dependabot::DependencyFile]) }
196
235
  def find_and_fetch_with_suffix(suffix)
197
236
  repo_contents.select { |f| f.name.end_with?(suffix) }.map { |f| fetch_file_from_host(f.name) }
198
237
  end
199
238
 
239
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
200
240
  def nuget_config_files
201
241
  return @nuget_config_files if @nuget_config_files
202
242
 
@@ -206,8 +246,15 @@ module Dependabot
206
246
  @nuget_config_files
207
247
  end
208
248
 
249
+ sig do
250
+ params(
251
+ project_file: Dependabot::DependencyFile,
252
+ expected_file_name: String
253
+ )
254
+ .returns(T.nilable(Dependabot::DependencyFile))
255
+ end
209
256
  def named_file_up_tree_from_project_file(project_file, expected_file_name)
210
- found_expected_file = nil
257
+ found_expected_file = T.let(nil, T.nilable(Dependabot::DependencyFile))
211
258
  directory_path = Pathname.new(directory)
212
259
  full_project_dir = Pathname.new(project_file.directory).join(project_file.name).dirname
213
260
  full_project_dir.ascend.each do |base|
@@ -228,26 +275,25 @@ module Dependabot
228
275
  found_expected_file
229
276
  end
230
277
 
278
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
231
279
  def global_json
232
- return @global_json if defined?(@global_json)
233
-
234
- @global_json = fetch_file_if_present("global.json")
280
+ @global_json ||= T.let(fetch_file_if_present("global.json"), T.nilable(Dependabot::DependencyFile))
235
281
  end
236
282
 
283
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
237
284
  def dotnet_tools_json
238
- return @dotnet_tools_json if defined?(@dotnet_tools_json)
239
-
240
- @dotnet_tools_json = fetch_file_if_present(".config/dotnet-tools.json")
285
+ @dotnet_tools_json ||= T.let(fetch_file_if_present(".config/dotnet-tools.json"),
286
+ T.nilable(Dependabot::DependencyFile))
241
287
  end
242
288
 
289
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
243
290
  def packages_props
244
- return @packages_props if defined?(@packages_props)
245
-
246
- @packages_props = fetch_file_if_present("Packages.props")
291
+ @packages_props ||= T.let(fetch_file_if_present("Packages.props"), T.nilable(Dependabot::DependencyFile))
247
292
  end
248
293
 
294
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
249
295
  def imported_property_files
250
- imported_property_files = []
296
+ imported_property_files = T.let([], T::Array[Dependabot::DependencyFile])
251
297
 
252
298
  files = [*project_files, *directory_build_files]
253
299
 
@@ -263,18 +309,24 @@ module Dependabot
263
309
  imported_property_files
264
310
  end
265
311
 
312
+ sig do
313
+ params(
314
+ file: Dependabot::DependencyFile,
315
+ previously_fetched_files: T::Array[Dependabot::DependencyFile]
316
+ )
317
+ .returns(T::Array[Dependabot::DependencyFile])
318
+ end
266
319
  def fetch_imported_property_files(file:, previously_fetched_files:)
267
320
  file_id = file.directory + "/" + file.name
268
- @fetched_files ||= {}
269
321
  if @fetched_files[file_id]
270
- @fetched_files[file_id]
322
+ T.must(@fetched_files[file_id])
271
323
  else
272
324
  paths =
273
325
  ImportPathsFinder.new(project_file: file).import_paths +
274
326
  ImportPathsFinder.new(project_file: file).project_reference_paths +
275
327
  ImportPathsFinder.new(project_file: file).project_file_paths
276
328
 
277
- paths.flat_map do |path|
329
+ paths.filter_map do |path|
278
330
  next if previously_fetched_files.map(&:name).include?(path)
279
331
  next if file.name == path
280
332
  next if path.include?("$(")
@@ -290,7 +342,7 @@ module Dependabot
290
342
  # Don't worry about missing files too much for now (at least
291
343
  # until we start resolving properties)
292
344
  nil
293
- end.compact
345
+ end.flatten
294
346
  end
295
347
  end
296
348
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "json"
@@ -12,12 +12,17 @@ module Dependabot
12
12
  module Nuget
13
13
  class FileParser
14
14
  class DotNetToolsJsonParser
15
+ extend T::Sig
16
+
15
17
  require "dependabot/file_parsers/base/dependency_set"
16
18
 
19
+ sig { params(dotnet_tools_json: Dependabot::DependencyFile).void }
17
20
  def initialize(dotnet_tools_json:)
18
21
  @dotnet_tools_json = dotnet_tools_json
22
+ @parsed_dotnet_tools_json = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
19
23
  end
20
24
 
25
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
21
26
  def dependency_set
22
27
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
23
28
 
@@ -48,11 +53,14 @@ module Dependabot
48
53
 
49
54
  private
50
55
 
56
+ sig { returns(Dependabot::DependencyFile) }
51
57
  attr_reader :dotnet_tools_json
52
58
 
59
+ sig { returns(T::Hash[String, T.untyped]) }
53
60
  def parsed_dotnet_tools_json
54
61
  # Remove BOM if present as JSON should be UTF-8
55
- @parsed_dotnet_tools_json ||= JSON.parse(dotnet_tools_json.content.delete_prefix("\uFEFF"))
62
+ content = T.must(dotnet_tools_json.content)
63
+ @parsed_dotnet_tools_json ||= JSON.parse(content.delete_prefix("\uFEFF"))
56
64
  rescue JSON::ParserError
57
65
  raise Dependabot::DependencyFileNotParseable, dotnet_tools_json.path
58
66
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "json"
@@ -12,12 +12,17 @@ module Dependabot
12
12
  module Nuget
13
13
  class FileParser
14
14
  class GlobalJsonParser
15
+ extend T::Sig
16
+
15
17
  require "dependabot/file_parsers/base/dependency_set"
16
18
 
19
+ sig { params(global_json: Dependabot::DependencyFile).void }
17
20
  def initialize(global_json:)
18
21
  @global_json = global_json
22
+ @parsed_global_json = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
19
23
  end
20
24
 
25
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
21
26
  def dependency_set
22
27
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
23
28
 
@@ -45,11 +50,14 @@ module Dependabot
45
50
 
46
51
  private
47
52
 
53
+ sig { returns(Dependabot::DependencyFile) }
48
54
  attr_reader :global_json
49
55
 
56
+ sig { returns(T::Hash[String, T.untyped]) }
50
57
  def parsed_global_json
51
58
  # Remove BOM if present as JSON should be UTF-8
52
- @parsed_global_json ||= JSON.parse(global_json.content.delete_prefix("\uFEFF"))
59
+ content = T.must(global_json.content)
60
+ @parsed_global_json ||= JSON.parse(content.delete_prefix("\uFEFF"))
53
61
  rescue JSON::ParserError
54
62
  raise Dependabot::DependencyFileNotParseable, global_json.path
55
63
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
@@ -13,18 +13,22 @@ module Dependabot
13
13
  module Nuget
14
14
  class FileParser
15
15
  class PackagesConfigParser
16
+ extend T::Sig
16
17
  require "dependabot/file_parsers/base/dependency_set"
17
18
 
18
19
  DEPENDENCY_SELECTOR = "packages > package"
19
20
 
21
+ sig { returns(T::Hash[String, Dependabot::FileParsers::Base::DependencySet]) }
20
22
  def self.dependency_set_cache
21
23
  CacheManager.cache("packages_config_dependency_set")
22
24
  end
23
25
 
26
+ sig { params(packages_config: Dependabot::DependencyFile).void }
24
27
  def initialize(packages_config:)
25
28
  @packages_config = packages_config
26
29
  end
27
30
 
31
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
28
32
  def dependency_set
29
33
  key = "#{packages_config.name.downcase}::#{packages_config.content.hash}"
30
34
  cache = PackagesConfigParser.dependency_set_cache
@@ -34,8 +38,10 @@ module Dependabot
34
38
 
35
39
  private
36
40
 
41
+ sig { returns(Dependabot::DependencyFile) }
37
42
  attr_reader :packages_config
38
43
 
44
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
39
45
  def parse_dependencies
40
46
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
41
47
 
@@ -44,7 +50,7 @@ module Dependabot
44
50
  doc.css(DEPENDENCY_SELECTOR).each do |dependency_node|
45
51
  dependency_set <<
46
52
  Dependency.new(
47
- name: dependency_name(dependency_node),
53
+ name: T.must(dependency_name(dependency_node)),
48
54
  version: dependency_version(dependency_node),
49
55
  package_manager: "nuget",
50
56
  requirements: [{
@@ -59,11 +65,13 @@ module Dependabot
59
65
  dependency_set
60
66
  end
61
67
 
68
+ sig { params(dependency_node: Nokogiri::XML::Node).returns(T.nilable(String)) }
62
69
  def dependency_name(dependency_node)
63
70
  dependency_node.attribute("id")&.value&.strip ||
64
71
  dependency_node.at_xpath("./id")&.content&.strip
65
72
  end
66
73
 
74
+ sig { params(dependency_node: Nokogiri::XML::Node).returns(T.nilable(String)) }
67
75
  def dependency_version(dependency_node)
68
76
  # Ranges and wildcards aren't allowed in a packages.config - the
69
77
  # specified requirement is always an exact version.
@@ -71,6 +79,7 @@ module Dependabot
71
79
  dependency_node.at_xpath("./version")&.content&.strip
72
80
  end
73
81
 
82
+ sig { params(dependency_node: Nokogiri::XML::Node).returns(String) }
74
83
  def dependency_type(dependency_node)
75
84
  val = dependency_node.attribute("developmentDependency")&.value&.strip ||
76
85
  dependency_node.at_xpath("./developmentDependency")&.content&.strip