dependabot-common 0.245.0 → 0.246.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,9 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "pathname"
5
+ require "sorbet-runtime"
6
+
5
7
  require "dependabot/clients/github_with_retries"
6
8
  require "dependabot/clients/gitlab_with_retries"
7
9
  require "dependabot/dependency_group"
@@ -15,19 +17,72 @@ module Dependabot
15
17
  class PullRequestCreator
16
18
  # MessageBuilder builds PR message for a dependency update
17
19
  class MessageBuilder
20
+ extend T::Sig
21
+
18
22
  require_relative "message_builder/metadata_presenter"
19
23
  require_relative "message_builder/issue_linker"
20
24
  require_relative "message_builder/link_and_mention_sanitizer"
21
25
  require_relative "pr_name_prefixer"
22
26
 
23
- attr_reader :source, :dependencies, :files, :credentials,
24
- :pr_message_header, :pr_message_footer,
25
- :commit_message_options, :vulnerabilities_fixed,
26
- :github_redirection_service, :dependency_group, :pr_message_max_length,
27
- :pr_message_encoding, :ignore_conditions
27
+ sig { returns(Dependabot::Source) }
28
+ attr_reader :source
29
+
30
+ sig { returns(T::Array[Dependabot::Dependency]) }
31
+ attr_reader :dependencies
32
+
33
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
34
+ attr_reader :files
35
+
36
+ sig { returns(T::Array[Dependabot::Credential]) }
37
+ attr_reader :credentials
38
+
39
+ sig { returns(T.nilable(String)) }
40
+ attr_reader :pr_message_header
41
+
42
+ sig { returns(T.nilable(String)) }
43
+ attr_reader :pr_message_footer
44
+
45
+ sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
46
+ attr_reader :commit_message_options
47
+
48
+ sig { returns(T::Hash[String, T.untyped]) }
49
+ attr_reader :vulnerabilities_fixed
50
+
51
+ sig { returns(T.nilable(String)) }
52
+ attr_reader :github_redirection_service
53
+
54
+ sig { returns(T.nilable(Dependabot::DependencyGroup)) }
55
+ attr_reader :dependency_group
56
+
57
+ sig { returns(T.nilable(Integer)) }
58
+ attr_reader :pr_message_max_length
59
+
60
+ sig { returns(T.nilable(Encoding)) }
61
+ attr_reader :pr_message_encoding
62
+
63
+ sig { returns(T::Array[T::Hash[String, String]]) }
64
+ attr_reader :ignore_conditions
28
65
 
29
66
  TRUNCATED_MSG = "...\n\n_Description has been truncated_"
30
67
 
68
+ sig do
69
+ params(
70
+ source: Dependabot::Source,
71
+ dependencies: T::Array[Dependabot::Dependency],
72
+ files: T::Array[Dependabot::DependencyFile],
73
+ credentials: T::Array[Dependabot::Credential],
74
+ pr_message_header: T.nilable(String),
75
+ pr_message_footer: T.nilable(String),
76
+ commit_message_options: T.nilable(T::Hash[Symbol, T.untyped]),
77
+ vulnerabilities_fixed: T::Hash[String, T.untyped],
78
+ github_redirection_service: T.nilable(String),
79
+ dependency_group: T.nilable(Dependabot::DependencyGroup),
80
+ pr_message_max_length: T.nilable(Integer),
81
+ pr_message_encoding: T.nilable(Encoding),
82
+ ignore_conditions: T::Array[T::Hash[String, String]]
83
+ )
84
+ .void
85
+ end
31
86
  def initialize(source:, dependencies:, files:, credentials:,
32
87
  pr_message_header: nil, pr_message_footer: nil,
33
88
  commit_message_options: {}, vulnerabilities_fixed: {},
@@ -48,16 +103,20 @@ module Dependabot
48
103
  @ignore_conditions = ignore_conditions
49
104
  end
50
105
 
106
+ sig { params(pr_message_max_length: Integer).returns(Integer) }
51
107
  attr_writer :pr_message_max_length
52
108
 
109
+ sig { params(pr_message_encoding: Encoding).returns(Encoding) }
53
110
  attr_writer :pr_message_encoding
54
111
 
112
+ sig { returns(String) }
55
113
  def pr_name
56
114
  name = dependency_group ? group_pr_name : solo_pr_name
57
- name[0] = name[0].capitalize if pr_name_prefixer.capitalize_first_word?
115
+ name[0] = T.must(name[0]).capitalize if pr_name_prefixer.capitalize_first_word?
58
116
  "#{pr_name_prefix}#{name}"
59
117
  end
60
118
 
119
+ sig { returns(String) }
61
120
  def pr_message
62
121
  msg = "#{suffixed_pr_message_header}" \
63
122
  "#{commit_message_intro}" \
@@ -73,35 +132,42 @@ module Dependabot
73
132
 
74
133
  # Truncate PR message as determined by the pr_message_max_length and pr_message_encoding instance variables
75
134
  # The encoding is used when calculating length, all messages are returned as ruby UTF_8 encoded string
135
+ sig { params(msg: String).returns(String) }
76
136
  def truncate_pr_message(msg)
77
137
  return msg if pr_message_max_length.nil?
78
138
 
79
139
  msg = msg.dup
80
- msg = msg.force_encoding(pr_message_encoding) unless pr_message_encoding.nil?
140
+ msg = msg.force_encoding(T.must(pr_message_encoding)) unless pr_message_encoding.nil?
81
141
 
82
- if msg.length > pr_message_max_length
83
- tr_msg = pr_message_encoding.nil? ? TRUNCATED_MSG : (+TRUNCATED_MSG).dup.force_encoding(pr_message_encoding)
84
- trunc_length = pr_message_max_length - tr_msg.length
85
- msg = (msg[0..trunc_length] + tr_msg)
142
+ if msg.length > T.must(pr_message_max_length)
143
+ tr_msg = if pr_message_encoding.nil?
144
+ TRUNCATED_MSG
145
+ else
146
+ (+TRUNCATED_MSG).dup.force_encoding(T.must(pr_message_encoding))
147
+ end
148
+ trunc_length = T.must(pr_message_max_length) - tr_msg.length
149
+ msg = (T.must(msg[0..trunc_length]) + tr_msg)
86
150
  end
87
151
  # if we used a custom encoding for calculating length, then we need to force back to UTF-8
88
152
  msg = msg.encode("utf-8", "binary", invalid: :replace, undef: :replace) unless pr_message_encoding.nil?
89
153
  msg
90
154
  end
91
155
 
156
+ sig { returns(String) }
92
157
  def commit_message
93
158
  message = commit_subject + "\n\n"
94
159
  message += commit_message_intro
95
160
  message += metadata_links
96
- message += "\n\n" + message_trailers if message_trailers
161
+ message += "\n\n" + T.must(message_trailers) if message_trailers
97
162
  message
98
163
  rescue StandardError => e
99
164
  Dependabot.logger.error("Error while generating commit message: #{e.message}")
100
165
  message = commit_subject
101
- message += "\n\n" + message_trailers if message_trailers
166
+ message += "\n\n" + T.must(message_trailers) if message_trailers
102
167
  message
103
168
  end
104
169
 
170
+ sig { returns(Dependabot::PullRequestCreator::Message) }
105
171
  def message
106
172
  Dependabot::PullRequestCreator::Message.new(
107
173
  pr_name: pr_name,
@@ -112,65 +178,74 @@ module Dependabot
112
178
 
113
179
  private
114
180
 
181
+ sig { returns(String) }
115
182
  def solo_pr_name
116
183
  name = library? ? library_pr_name : application_pr_name
117
184
  "#{name}#{pr_name_directory}"
118
185
  end
119
186
 
187
+ sig { returns(String) }
120
188
  def library_pr_name
121
189
  "update " +
122
190
  if dependencies.count == 1
123
- "#{dependencies.first.display_name} requirement " \
124
- "#{from_version_msg(old_library_requirement(dependencies.first))}" \
125
- "to #{new_library_requirement(dependencies.first)}"
191
+ "#{T.must(dependencies.first).display_name} requirement " \
192
+ "#{from_version_msg(old_library_requirement(T.must(dependencies.first)))}" \
193
+ "to #{new_library_requirement(T.must(dependencies.first))}"
126
194
  else
127
195
  names = dependencies.map(&:name).uniq
128
196
  if names.count == 1
129
197
  "requirements for #{names.first}"
130
198
  else
131
- "requirements for #{names[0..-2].join(', ')} and #{names[-1]}"
199
+ "requirements for #{T.must(names[0..-2]).join(', ')} and #{names[-1]}"
132
200
  end
133
201
  end
134
202
  end
135
203
 
204
+ # rubocop:disable Metrics/AbcSize
205
+ sig { returns(String) }
136
206
  def application_pr_name
137
207
  "bump " +
138
208
  if dependencies.count == 1
139
209
  dependency = dependencies.first
140
- "#{dependency.display_name} " \
141
- "#{from_version_msg(dependency.humanized_previous_version)}" \
142
- "to #{dependency.humanized_version}"
210
+ "#{T.must(dependency).display_name} " \
211
+ "#{from_version_msg(T.must(dependency).humanized_previous_version)}" \
212
+ "to #{T.must(dependency).humanized_version}"
143
213
  elsif updating_a_property?
144
214
  dependency = dependencies.first
145
215
  "#{property_name} " \
146
- "#{from_version_msg(dependency.humanized_previous_version)}" \
147
- "to #{dependency.humanized_version}"
216
+ "#{from_version_msg(T.must(dependency).humanized_previous_version)}" \
217
+ "to #{T.must(dependency).humanized_version}"
148
218
  elsif updating_a_dependency_set?
149
219
  dependency = dependencies.first
150
220
  "#{dependency_set.fetch(:group)} dependency set " \
151
- "#{from_version_msg(dependency.humanized_previous_version)}" \
152
- "to #{dependency.humanized_version}"
221
+ "#{from_version_msg(T.must(dependency).humanized_previous_version)}" \
222
+ "to #{T.must(dependency).humanized_version}"
153
223
  else
154
224
  names = dependencies.map(&:name).uniq
155
225
  if names.count == 1
156
- names.first
226
+ T.must(names.first)
157
227
  else
158
- "#{names[0..-2].join(', ')} and #{names[-1]}"
228
+ "#{T.must(names[0..-2]).join(', ')} and #{names[-1]}"
159
229
  end
160
230
  end
161
231
  end
232
+ # rubocop:enable Metrics/AbcSize
162
233
 
234
+ sig { returns(String) }
163
235
  def group_pr_name
164
236
  updates = dependencies.map(&:name).uniq.count
165
237
 
166
- if source&.directories
167
- "bump the #{dependency_group.name} across #{source.directories.count} directories " \
238
+ if source.directories
239
+ "bump the #{T.must(dependency_group).name} across #{T.must(source.directories).count} directories " \
168
240
  "with #{updates} update#{'s' if updates > 1}"
169
241
  else
170
- "bump the #{dependency_group.name} group#{pr_name_directory} with #{updates} update#{'s' if updates > 1}"
242
+ "bump the #{T.must(dependency_group).name} group#{pr_name_directory} with #{updates} update#{if updates > 1
243
+ 's'
244
+ end}"
171
245
  end
172
246
  end
173
247
 
248
+ sig { returns(String) }
174
249
  def pr_name_prefix
175
250
  pr_name_prefixer.pr_name_prefix
176
251
  rescue StandardError => e
@@ -178,12 +253,14 @@ module Dependabot
178
253
  ""
179
254
  end
180
255
 
256
+ sig { returns(String) }
181
257
  def pr_name_directory
182
- return "" if files.first.directory == "/"
258
+ return "" if T.must(files.first).directory == "/"
183
259
 
184
- " in #{files.first.directory}"
260
+ " in #{T.must(files.first).directory}"
185
261
  end
186
262
 
263
+ sig { returns(String) }
187
264
  def commit_subject
188
265
  subject = pr_name.gsub("⬆️", ":arrow_up:").gsub("🔒", ":lock:")
189
266
  return subject unless subject.length > 72
@@ -191,57 +268,65 @@ module Dependabot
191
268
  subject = subject.gsub(/ from [^\s]*? to [^\s]*/, "")
192
269
  return subject unless subject.length > 72
193
270
 
194
- subject.split(" in ").first
271
+ T.must(subject.split(" in ").first)
195
272
  end
196
273
 
274
+ sig { returns(String) }
197
275
  def commit_message_intro
198
276
  return requirement_commit_message_intro if library?
199
277
 
200
278
  version_commit_message_intro
201
279
  end
202
280
 
281
+ sig { returns(String) }
203
282
  def prefixed_pr_message_footer
204
283
  return "" unless pr_message_footer
205
284
 
206
285
  "\n\n#{pr_message_footer}"
207
286
  end
208
287
 
288
+ sig { returns(String) }
209
289
  def suffixed_pr_message_header
210
290
  return "" unless pr_message_header
211
291
 
212
292
  "#{pr_message_header}\n\n"
213
293
  end
214
294
 
295
+ sig { returns(T.nilable(String)) }
215
296
  def message_trailers
216
297
  return unless signoff_trailers || custom_trailers
217
298
 
218
299
  [signoff_trailers, custom_trailers].compact.join("\n")
219
300
  end
220
301
 
302
+ sig { returns(T.nilable(String)) }
221
303
  def custom_trailers
222
- trailers = commit_message_options[:trailers]
304
+ trailers = commit_message_options&.dig(:trailers)
223
305
  return if trailers.nil?
224
306
  raise("Commit trailers must be a Hash object") unless trailers.is_a?(Hash)
225
307
 
226
308
  trailers.compact.map { |k, v| "#{k}: #{v}" }.join("\n")
227
309
  end
228
310
 
311
+ sig { returns(T.nilable(String)) }
229
312
  def signoff_trailers
230
313
  return unless on_behalf_of_message || signoff_message
231
314
 
232
315
  [on_behalf_of_message, signoff_message].compact.join("\n")
233
316
  end
234
317
 
318
+ sig { returns(T.nilable(String)) }
235
319
  def signoff_message
236
- signoff_details = commit_message_options[:signoff_details]
320
+ signoff_details = commit_message_options&.dig(:signoff_details)
237
321
  return unless signoff_details.is_a?(Hash)
238
322
  return unless signoff_details[:name] && signoff_details[:email]
239
323
 
240
324
  "Signed-off-by: #{signoff_details[:name]} <#{signoff_details[:email]}>"
241
325
  end
242
326
 
327
+ sig { returns(T.nilable(String)) }
243
328
  def on_behalf_of_message
244
- signoff_details = commit_message_options[:signoff_details]
329
+ signoff_details = commit_message_options&.dig(:signoff_details)
245
330
  return unless signoff_details.is_a?(Hash)
246
331
  return unless signoff_details[:org_name] && signoff_details[:org_email]
247
332
 
@@ -249,6 +334,7 @@ module Dependabot
249
334
  "<#{signoff_details[:org_email]}>"
250
335
  end
251
336
 
337
+ sig { returns(String) }
252
338
  def requirement_commit_message_intro
253
339
  msg = "Updates the requirements on "
254
340
 
@@ -256,7 +342,7 @@ module Dependabot
256
342
  if dependencies.count == 1
257
343
  "#{dependency_links.first} "
258
344
  else
259
- "#{dependency_links[0..-2].join(', ')} and #{dependency_links[-1]} "
345
+ "#{T.must(dependency_links[0..-2]).join(', ')} and #{dependency_links[-1]} "
260
346
  end
261
347
 
262
348
  msg + "to permit the latest version."
@@ -265,8 +351,9 @@ module Dependabot
265
351
  # rubocop:disable Metrics/CyclomaticComplexity
266
352
  # rubocop:disable Metrics/PerceivedComplexity
267
353
  # rubocop:disable Metrics/AbcSize
354
+ sig { returns(String) }
268
355
  def version_commit_message_intro
269
- return multi_directory_group_intro if dependency_group && source&.directories
356
+ return multi_directory_group_intro if dependency_group && source.directories
270
357
 
271
358
  return group_intro if dependency_group
272
359
 
@@ -283,14 +370,16 @@ module Dependabot
283
370
 
284
371
  dependency = dependencies.first
285
372
  msg = "Bumps #{dependency_links.first} " \
286
- "#{from_version_msg(dependency.humanized_previous_version)}" \
287
- "to #{dependency.humanized_version}."
373
+ "#{from_version_msg(T.must(dependency).humanized_previous_version)}" \
374
+ "to #{T.must(dependency).humanized_version}."
288
375
 
289
- msg += " This release includes the previously tagged commit." if switching_from_ref_to_release?(dependency)
376
+ if switching_from_ref_to_release?(T.must(dependency))
377
+ msg += " This release includes the previously tagged commit."
378
+ end
290
379
 
291
- if vulnerabilities_fixed[dependency.name]&.one?
380
+ if vulnerabilities_fixed[T.must(dependency).name]&.one?
292
381
  msg += " **This update includes a security fix.**"
293
- elsif vulnerabilities_fixed[dependency.name]&.any?
382
+ elsif vulnerabilities_fixed[T.must(dependency).name]&.any?
294
383
  msg += " **This update includes security fixes.**"
295
384
  end
296
385
 
@@ -300,35 +389,39 @@ module Dependabot
300
389
  # rubocop:enable Metrics/PerceivedComplexity
301
390
  # rubocop:enable Metrics/AbcSize
302
391
 
392
+ sig { returns(String) }
303
393
  def multidependency_property_intro
304
394
  dependency = dependencies.first
305
395
 
306
396
  "Bumps `#{property_name}` " \
307
- "#{from_version_msg(dependency.humanized_previous_version)}" \
308
- "to #{dependency.humanized_version}."
397
+ "#{from_version_msg(T.must(dependency).humanized_previous_version)}" \
398
+ "to #{T.must(dependency).humanized_version}."
309
399
  end
310
400
 
401
+ sig { returns(String) }
311
402
  def dependency_set_intro
312
403
  dependency = dependencies.first
313
404
 
314
405
  "Bumps `#{dependency_set.fetch(:group)}` " \
315
- "dependency set #{from_version_msg(dependency.humanized_previous_version)}" \
316
- "to #{dependency.humanized_version}."
406
+ "dependency set #{from_version_msg(T.must(dependency).humanized_previous_version)}" \
407
+ "to #{T.must(dependency).humanized_version}."
317
408
  end
318
409
 
410
+ sig { returns(String) }
319
411
  def multidependency_intro
320
- "Bumps #{dependency_links[0..-2].join(', ')} " \
412
+ "Bumps #{T.must(dependency_links[0..-2]).join(', ')} " \
321
413
  "and #{dependency_links[-1]}. These " \
322
414
  "dependencies needed to be updated together."
323
415
  end
324
416
 
417
+ sig { returns(String) }
325
418
  def transitive_multidependency_intro
326
419
  dependency = dependencies.first
327
420
 
328
- msg = "Bumps #{dependency_links[0]} to #{dependency.humanized_version}"
421
+ msg = "Bumps #{dependency_links[0]} to #{T.must(dependency).humanized_version}"
329
422
 
330
423
  msg += if dependencies.count > 2
331
- " and updates ancestor dependencies #{dependency_links[0..-2].join(', ')} " \
424
+ " and updates ancestor dependencies #{T.must(dependency_links[0..-2]).join(', ')} " \
332
425
  "and #{dependency_links[-1]}. "
333
426
  else
334
427
  " and updates ancestor dependency #{dependency_links[1]}. "
@@ -339,11 +432,12 @@ module Dependabot
339
432
  msg
340
433
  end
341
434
 
435
+ sig { returns(String) }
342
436
  def transitive_removed_dependency_intro
343
437
  msg = "Removes #{dependency_links[0]}. It's no longer used after updating"
344
438
 
345
439
  msg += if dependencies.count > 2
346
- " ancestor dependencies #{dependency_links[0..-2].join(', ')} " \
440
+ " ancestor dependencies #{T.must(dependency_links[0..-2]).join(', ')} " \
347
441
  "and #{dependency_links[-1]}. "
348
442
  else
349
443
  " ancestor dependency #{dependency_links[1]}. "
@@ -354,16 +448,18 @@ module Dependabot
354
448
  msg
355
449
  end
356
450
 
451
+ # rubocop:disable Metrics/AbcSize
452
+ sig { returns(String) }
357
453
  def multi_directory_group_intro
358
454
  msg = ""
359
455
 
360
- source.directories.each do |directory|
456
+ T.must(source.directories).each do |directory|
361
457
  dependencies_in_directory = dependencies.select { |dep| dep.metadata[:directory] == directory }
362
458
  next unless dependencies_in_directory.any?
363
459
 
364
460
  update_count = dependencies_in_directory.map(&:name).uniq.count
365
461
 
366
- msg += "Bumps the #{dependency_group.name} " \
462
+ msg += "Bumps the #{T.must(dependency_group).name} " \
367
463
  "with #{update_count} update#{update_count > 1 ? 's' : ''} in the #{directory} directory:"
368
464
 
369
465
  msg += if update_count >= 5
@@ -378,10 +474,11 @@ module Dependabot
378
474
  "\n\n#{table([header] + rows)}"
379
475
  elsif update_count > 1
380
476
  dependency_links_in_directory = dependency_links_for_directory(directory)
381
- " #{dependency_links_in_directory[0..-2].join(', ')} and #{dependency_links_in_directory[-1]}."
477
+ " #{T.must(T.must(dependency_links_in_directory)[0..-2]).join(', ')}" \
478
+ " and #{T.must(dependency_links_in_directory)[-1]}."
382
479
  else
383
480
  dependency_links_in_directory = dependency_links_for_directory(directory)
384
- " #{dependency_links_in_directory.first}."
481
+ " #{T.must(dependency_links_in_directory).first}."
385
482
  end
386
483
 
387
484
  msg += "\n"
@@ -389,11 +486,13 @@ module Dependabot
389
486
 
390
487
  msg
391
488
  end
489
+ # rubocop:enable Metrics/AbcSize
392
490
 
491
+ sig { returns(String) }
393
492
  def group_intro
394
493
  update_count = dependencies.map(&:name).uniq.count
395
494
 
396
- msg = "Bumps the #{dependency_group.name} group#{pr_name_directory} " \
495
+ msg = "Bumps the #{T.must(dependency_group).name} group#{pr_name_directory} " \
397
496
  "with #{update_count} update#{update_count > 1 ? 's' : ''}:"
398
497
 
399
498
  msg += if update_count >= 5
@@ -407,7 +506,7 @@ module Dependabot
407
506
  end
408
507
  "\n\n#{table([header] + rows)}"
409
508
  elsif update_count > 1
410
- " #{dependency_links[0..-2].join(', ')} and #{dependency_links[-1]}."
509
+ " #{T.must(dependency_links[0..-2]).join(', ')} and #{dependency_links[-1]}."
411
510
  else
412
511
  " #{dependency_links.first}."
413
512
  end
@@ -417,66 +516,86 @@ module Dependabot
417
516
  msg
418
517
  end
419
518
 
519
+ sig { params(previous_version: T.nilable(String)).returns(String) }
420
520
  def from_version_msg(previous_version)
421
521
  return "" unless previous_version
422
522
 
423
523
  "from #{previous_version} "
424
524
  end
425
525
 
526
+ sig { returns(T::Boolean) }
426
527
  def updating_a_property?
427
- dependencies.first
428
- .requirements
429
- .any? { |r| r.dig(:metadata, :property_name) }
528
+ T.must(dependencies.first)
529
+ .requirements
530
+ .any? { |r| r.dig(:metadata, :property_name) }
430
531
  end
431
532
 
533
+ sig { returns(T::Boolean) }
432
534
  def updating_a_dependency_set?
433
- dependencies.first
434
- .requirements
435
- .any? { |r| r.dig(:metadata, :dependency_set) }
535
+ T.must(dependencies.first)
536
+ .requirements
537
+ .any? { |r| r.dig(:metadata, :dependency_set) }
436
538
  end
437
539
 
540
+ sig { returns(T::Boolean) }
438
541
  def removing_a_transitive_dependency?
439
542
  dependencies.any?(&:removed?)
440
543
  end
441
544
 
545
+ sig { returns(T::Boolean) }
442
546
  def updating_top_level_and_transitive_dependencies?
443
547
  dependencies.any?(&:top_level?) &&
444
548
  dependencies.any? { |dep| !dep.top_level? }
445
549
  end
446
550
 
551
+ sig { returns(String) }
447
552
  def property_name
448
- @property_name ||= dependencies.first.requirements
449
- .find { |r| r.dig(:metadata, :property_name) }
450
- &.dig(:metadata, :property_name)
553
+ @property_name ||=
554
+ T.let(
555
+ dependencies.first
556
+ &.requirements
557
+ &.find { |r| r.dig(:metadata, :property_name) }
558
+ &.dig(:metadata, :property_name),
559
+ T.nilable(String)
560
+ )
451
561
 
452
562
  raise "No property name!" unless @property_name
453
563
 
454
564
  @property_name
455
565
  end
456
566
 
567
+ sig { returns(T::Hash[Symbol, String]) }
457
568
  def dependency_set
458
- @dependency_set ||= dependencies.first.requirements
459
- .find { |r| r.dig(:metadata, :dependency_set) }
460
- &.dig(:metadata, :dependency_set)
569
+ @dependency_set ||=
570
+ T.let(
571
+ dependencies.first
572
+ &.requirements
573
+ &.find { |r| r.dig(:metadata, :dependency_set) }
574
+ &.dig(:metadata, :dependency_set),
575
+ T.nilable(T.nilable(T::Hash[Symbol, String]))
576
+ )
461
577
 
462
578
  raise "No dependency set!" unless @dependency_set
463
579
 
464
580
  @dependency_set
465
581
  end
466
582
 
583
+ sig { returns(T::Array[String]) }
467
584
  def dependency_links
468
- return @dependency_links if defined?(@dependency_links)
585
+ return T.must(@dependency_links) if defined?(@dependency_links)
469
586
 
470
587
  uniq_deps = dependencies.each_with_object({}) { |dep, memo| memo[dep.name] ||= dep }.values
471
588
  @dependency_links = uniq_deps.map { |dep| dependency_link(dep) }
472
589
  end
473
590
 
591
+ sig { params(directory: String).returns(T.nilable(T::Array[String])) }
474
592
  def dependency_links_for_directory(directory)
475
593
  dependencies_in_directory = dependencies.select { |dep| dep.metadata[:directory] == directory }
476
594
  uniq_deps = dependencies_in_directory.each_with_object({}) { |dep, memo| memo[dep.name] ||= dep }.values
477
- @dependency_links = uniq_deps.map { |dep| dependency_link(dep) }
595
+ @dependency_links = T.let(uniq_deps.map { |dep| dependency_link(dep) }, T.nilable(T::Array[String]))
478
596
  end
479
597
 
598
+ sig { params(dependency: Dependabot::Dependency).returns(String) }
480
599
  def dependency_link(dependency)
481
600
  if source_url(dependency)
482
601
  "[#{dependency.display_name}](#{source_url(dependency)})"
@@ -487,12 +606,14 @@ module Dependabot
487
606
  end
488
607
  end
489
608
 
609
+ sig { params(dependency: Dependabot::Dependency).returns(String) }
490
610
  def dependency_version_update(dependency)
491
611
  "#{dependency.humanized_previous_version} to #{dependency.humanized_version}"
492
612
  end
493
613
 
614
+ sig { returns(String) }
494
615
  def metadata_links
495
- return metadata_links_for_dep(dependencies.first) if dependencies.count == 1 && dependency_group.nil?
616
+ return metadata_links_for_dep(T.must(dependencies.first)) if dependencies.count == 1 && dependency_group.nil?
496
617
 
497
618
  dependencies.map do |dep|
498
619
  if dep.removed?
@@ -506,6 +627,7 @@ module Dependabot
506
627
  end.join
507
628
  end
508
629
 
630
+ sig { params(dep: Dependabot::Dependency).returns(String) }
509
631
  def metadata_links_for_dep(dep)
510
632
  msg = ""
511
633
  msg += "\n- [Release notes](#{releases_url(dep)})" if releases_url(dep)
@@ -515,13 +637,15 @@ module Dependabot
515
637
  msg
516
638
  end
517
639
 
640
+ sig { params(rows: T::Array[T::Array[String]]).returns(String) }
518
641
  def table(rows)
519
642
  [
520
- table_header(rows[0]),
521
- rows[1..].map { |r| table_row(r) }
643
+ table_header(T.must(rows[0])),
644
+ T.must(rows[1..]).map { |r| table_row(r) }
522
645
  ].join("\n")
523
646
  end
524
647
 
648
+ sig { params(row: T::Array[String]).returns(String) }
525
649
  def table_header(row)
526
650
  [
527
651
  table_row(row),
@@ -529,12 +653,14 @@ module Dependabot
529
653
  ].join("\n")
530
654
  end
531
655
 
656
+ sig { params(row: T::Array[String]).returns(String) }
532
657
  def table_row(row)
533
658
  "| #{row.join(' | ')} |"
534
659
  end
535
660
 
661
+ sig { returns(String) }
536
662
  def metadata_cascades # rubocop:disable Metrics/PerceivedComplexity
537
- return metadata_cascades_for_dep(dependencies.first) if dependencies.one? && !dependency_group
663
+ return metadata_cascades_for_dep(T.must(dependencies.first)) if dependencies.one? && !dependency_group
538
664
 
539
665
  dependencies.map do |dep|
540
666
  msg = if dep.removed?
@@ -555,6 +681,7 @@ module Dependabot
555
681
  end.join
556
682
  end
557
683
 
684
+ sig { params(dependency: Dependabot::Dependency).returns(String) }
558
685
  def metadata_cascades_for_dep(dependency)
559
686
  return "" if dependency.removed?
560
687
 
@@ -567,6 +694,7 @@ module Dependabot
567
694
  ).to_s
568
695
  end
569
696
 
697
+ sig { returns(String) }
570
698
  def ignore_conditions_table
571
699
  # Return an empty string if ignore_conditions is empty
572
700
  return "" if @ignore_conditions.empty?
@@ -580,7 +708,9 @@ module Dependabot
580
708
  return "" if valid_ignore_conditions.empty?
581
709
 
582
710
  # Sort them by updated_at (or created_at if updated_at is nil), taking the latest 20
583
- sorted_ignore_conditions = valid_ignore_conditions.sort_by { |ic| ic["updated-at"] }.last(20)
711
+ sorted_ignore_conditions = valid_ignore_conditions.sort_by do |ic|
712
+ ic["updated_at"].nil? ? T.must(ic["created_at"]) : T.must(ic["updated_at"])
713
+ end.last(20)
584
714
 
585
715
  # Map each condition to a row string
586
716
  table_rows = sorted_ignore_conditions.map do |ic|
@@ -591,6 +721,7 @@ module Dependabot
591
721
  build_table(summary, table_rows)
592
722
  end
593
723
 
724
+ sig { params(summary: String, rows: T::Array[String]).returns(String) }
594
725
  def build_table(summary, rows)
595
726
  table_header = "| Dependency Name | Ignore Conditions |"
596
727
  table_divider = "| --- | --- |"
@@ -607,74 +738,87 @@ module Dependabot
607
738
  end
608
739
  end
609
740
 
741
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(String)) }
610
742
  def changelog_url(dependency)
611
743
  metadata_finder(dependency).changelog_url
612
744
  end
613
745
 
746
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(String)) }
614
747
  def commits_url(dependency)
615
748
  metadata_finder(dependency).commits_url
616
749
  end
617
750
 
751
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(String)) }
618
752
  def homepage_url(dependency)
619
753
  metadata_finder(dependency).homepage_url
620
754
  end
621
755
 
756
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(String)) }
622
757
  def releases_url(dependency)
623
758
  metadata_finder(dependency).releases_url
624
759
  end
625
760
 
761
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(String)) }
626
762
  def source_url(dependency)
627
763
  metadata_finder(dependency).source_url
628
764
  end
629
765
 
766
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(String)) }
630
767
  def upgrade_url(dependency)
631
768
  metadata_finder(dependency).upgrade_guide_url
632
769
  end
633
770
 
771
+ sig { params(dependency: Dependabot::Dependency).returns(Dependabot::MetadataFinders::Base) }
634
772
  def metadata_finder(dependency)
635
- @metadata_finder ||= {}
773
+ @metadata_finder ||= T.let({}, T.nilable(T::Hash[String, Dependabot::MetadataFinders::Base]))
636
774
  @metadata_finder[dependency.name] ||=
637
775
  MetadataFinders
638
776
  .for_package_manager(dependency.package_manager)
639
777
  .new(dependency: dependency, credentials: credentials)
640
778
  end
641
779
 
780
+ sig { returns(Dependabot::PullRequestCreator::PrNamePrefixer) }
642
781
  def pr_name_prefixer
643
782
  @pr_name_prefixer ||=
644
- PrNamePrefixer.new(
645
- source: source,
646
- dependencies: dependencies,
647
- credentials: credentials,
648
- commit_message_options: commit_message_options,
649
- security_fix: vulnerabilities_fixed.values.flatten.any?
783
+ T.let(
784
+ PrNamePrefixer.new(
785
+ source: source,
786
+ dependencies: dependencies,
787
+ credentials: credentials,
788
+ commit_message_options: commit_message_options,
789
+ security_fix: vulnerabilities_fixed.values.flatten.any?
790
+ ),
791
+ T.nilable(Dependabot::PullRequestCreator::PrNamePrefixer)
650
792
  )
651
793
  end
652
794
 
795
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(String)) }
653
796
  def old_library_requirement(dependency)
654
797
  old_reqs =
655
- dependency.previous_requirements - dependency.requirements
798
+ T.must(dependency.previous_requirements) - dependency.requirements
656
799
 
657
800
  gemspec =
658
801
  old_reqs.find { |r| r[:file].match?(%r{^[^/]*\.gemspec$}) }
659
802
  return gemspec.fetch(:requirement) if gemspec
660
803
 
661
- req = old_reqs.first.fetch(:requirement)
804
+ req = T.must(old_reqs.first).fetch(:requirement)
662
805
  return req if req
663
806
 
664
807
  dependency.previous_ref if dependency.ref_changed?
665
808
  end
666
809
 
810
+ sig { params(dependency: Dependabot::Dependency).returns(String) }
667
811
  def new_library_requirement(dependency)
668
812
  updated_reqs =
669
- dependency.requirements - dependency.previous_requirements
813
+ dependency.requirements - T.must(dependency.previous_requirements)
670
814
 
671
815
  gemspec =
672
816
  updated_reqs.find { |r| r[:file].match?(%r{^[^/]*\.gemspec$}) }
673
817
  return gemspec.fetch(:requirement) if gemspec
674
818
 
675
- req = updated_reqs.first.fetch(:requirement)
819
+ req = T.must(updated_reqs.first).fetch(:requirement)
676
820
  return req if req
677
- return dependency.new_ref if dependency.ref_changed? && dependency.new_ref
821
+ return T.must(dependency.new_ref) if dependency.ref_changed? && dependency.new_ref
678
822
 
679
823
  raise "No new requirement!"
680
824
  end
@@ -684,6 +828,7 @@ module Dependabot
684
828
  # `requirements_update_strategy`.
685
829
  #
686
830
  # TODO reuse in BranchNamer
831
+ sig { returns(T::Boolean) }
687
832
  def library?
688
833
  # Reject any nested child gemspecs/vendored git dependencies
689
834
  root_files = files.map(&:name)
@@ -693,6 +838,7 @@ module Dependabot
693
838
  dependencies.any? { |d| d.humanized_previous_version.nil? }
694
839
  end
695
840
 
841
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
696
842
  def switching_from_ref_to_release?(dependency)
697
843
  unless dependency.previous_version&.match?(/^[0-9a-f]{40}$/) ||
698
844
  (dependency.previous_version.nil? && dependency.previous_ref)
@@ -702,8 +848,12 @@ module Dependabot
702
848
  Gem::Version.correct?(dependency.version)
703
849
  end
704
850
 
851
+ sig { returns(String) }
705
852
  def package_manager
706
- @package_manager ||= dependencies.first.package_manager
853
+ @package_manager ||= T.let(
854
+ T.must(dependencies.first).package_manager,
855
+ T.nilable(String)
856
+ )
707
857
  end
708
858
  end
709
859
  end