dependabot-common 0.242.1 → 0.243.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/lib/dependabot/clients/azure.rb +112 -24
  3. data/lib/dependabot/clients/bitbucket.rb +1 -1
  4. data/lib/dependabot/credential.rb +40 -0
  5. data/lib/dependabot/dependency_group.rb +8 -2
  6. data/lib/dependabot/file_fetchers/base.rb +35 -3
  7. data/lib/dependabot/file_parsers/base.rb +4 -3
  8. data/lib/dependabot/file_updaters/artifact_updater.rb +1 -1
  9. data/lib/dependabot/file_updaters/base.rb +4 -3
  10. data/lib/dependabot/file_updaters/vendor_updater.rb +1 -1
  11. data/lib/dependabot/git_commit_checker.rb +3 -2
  12. data/lib/dependabot/git_metadata_fetcher.rb +3 -2
  13. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +151 -56
  14. data/lib/dependabot/metadata_finders/base/changelog_pruner.rb +45 -14
  15. data/lib/dependabot/metadata_finders/base/commits_finder.rb +123 -53
  16. data/lib/dependabot/metadata_finders/base/release_finder.rb +84 -31
  17. data/lib/dependabot/metadata_finders/base.rb +4 -3
  18. data/lib/dependabot/pull_request_creator/azure.rb +1 -1
  19. data/lib/dependabot/pull_request_creator/branch_namer/solo_strategy.rb +2 -2
  20. data/lib/dependabot/pull_request_creator/labeler.rb +3 -2
  21. data/lib/dependabot/pull_request_creator/message_builder/issue_linker.rb +14 -6
  22. data/lib/dependabot/pull_request_creator/message_builder/metadata_presenter.rb +50 -9
  23. data/lib/dependabot/pull_request_creator/message_builder.rb +2 -2
  24. data/lib/dependabot/pull_request_creator/pr_name_prefixer.rb +164 -58
  25. data/lib/dependabot/pull_request_creator.rb +6 -5
  26. data/lib/dependabot/pull_request_updater.rb +3 -2
  27. data/lib/dependabot/security_advisory.rb +2 -2
  28. data/lib/dependabot/shared_helpers.rb +3 -2
  29. data/lib/dependabot/utils.rb +1 -13
  30. data/lib/dependabot/workspace/git.rb +1 -1
  31. data/lib/dependabot.rb +1 -1
  32. metadata +4 -3
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
@@ -9,9 +9,11 @@ require "dependabot/clients/gitlab_with_retries"
9
9
  require "dependabot/clients/bitbucket_with_retries"
10
10
  require "dependabot/shared_helpers"
11
11
  require "dependabot/metadata_finders/base"
12
+
12
13
  module Dependabot
13
14
  module MetadataFinders
14
15
  class Base
16
+ # rubocop:disable Metrics/ClassLength
15
17
  class ChangelogFinder
16
18
  extend T::Sig
17
19
 
@@ -19,24 +21,49 @@ module Dependabot
19
21
  require_relative "commits_finder"
20
22
 
21
23
  # Earlier entries are preferred
22
- CHANGELOG_NAMES = %w(
23
- changelog news changes history release whatsnew releases
24
- ).freeze
24
+ CHANGELOG_NAMES = T.let(
25
+ %w(changelog news changes history release whatsnew releases).freeze,
26
+ T::Array[String]
27
+ )
28
+
29
+ sig { returns(T.nilable(Dependabot::Source)) }
30
+ attr_reader :source
31
+
32
+ sig { returns(Dependabot::Dependency) }
33
+ attr_reader :dependency
34
+
35
+ sig { returns(T::Array[Dependabot::Credential]) }
36
+ attr_reader :credentials
25
37
 
26
- attr_reader :source, :dependency, :credentials, :suggested_changelog_url
38
+ sig { returns(T.nilable(String)) }
39
+ attr_reader :suggested_changelog_url
27
40
 
41
+ sig do
42
+ params(
43
+ source: T.nilable(Dependabot::Source),
44
+ dependency: Dependabot::Dependency,
45
+ credentials: T::Array[Dependabot::Credential],
46
+ suggested_changelog_url: T.nilable(String)
47
+ )
48
+ .void
49
+ end
28
50
  def initialize(source:, dependency:, credentials:,
29
51
  suggested_changelog_url: nil)
30
52
  @source = source
31
53
  @dependency = dependency
32
54
  @credentials = credentials
33
55
  @suggested_changelog_url = suggested_changelog_url
56
+
57
+ @new_version = T.let(nil, T.nilable(String))
58
+ @changelog_from_suggested_url = T.let(nil, T.untyped)
34
59
  end
35
60
 
61
+ sig { returns(T.nilable(String)) }
36
62
  def changelog_url
37
63
  changelog&.html_url
38
64
  end
39
65
 
66
+ sig { returns(T.nilable(String)) }
40
67
  def changelog_text
41
68
  return unless full_changelog_text
42
69
 
@@ -46,19 +73,25 @@ module Dependabot
46
73
  ).pruned_text
47
74
  end
48
75
 
76
+ sig { returns(T.nilable(String)) }
49
77
  def upgrade_guide_url
50
78
  upgrade_guide&.html_url
51
79
  end
52
80
 
81
+ sig { returns(T.nilable(String)) }
53
82
  def upgrade_guide_text
54
83
  return unless upgrade_guide
55
84
 
56
- @upgrade_guide_text ||= fetch_file_text(upgrade_guide)
85
+ @upgrade_guide_text ||= T.let(
86
+ fetch_file_text(upgrade_guide),
87
+ T.nilable(String)
88
+ )
57
89
  end
58
90
 
59
91
  private
60
92
 
61
93
  # rubocop:disable Metrics/PerceivedComplexity
94
+ sig { returns(T.untyped) }
62
95
  def changelog
63
96
  return unless changelog_from_suggested_url || source
64
97
  return if git_source? && !ref_changed?
@@ -66,13 +99,13 @@ module Dependabot
66
99
 
67
100
  # If there is a changelog, and it includes the new version, return it
68
101
  if new_version && default_branch_changelog &&
69
- fetch_file_text(default_branch_changelog)&.include?(new_version)
102
+ fetch_file_text(default_branch_changelog)&.include?(T.must(new_version))
70
103
  return default_branch_changelog
71
104
  end
72
105
 
73
106
  # Otherwise, look for a changelog at the tag for this version
74
107
  if new_version && relevant_tag_changelog &&
75
- fetch_file_text(relevant_tag_changelog)&.include?(new_version)
108
+ fetch_file_text(relevant_tag_changelog)&.include?(T.must(new_version))
76
109
  return relevant_tag_changelog
77
110
  end
78
111
 
@@ -81,8 +114,9 @@ module Dependabot
81
114
  end
82
115
  # rubocop:enable Metrics/PerceivedComplexity
83
116
 
117
+ sig { returns(T.nilable(Sawyer::Resource)) }
84
118
  def changelog_from_suggested_url
85
- return @changelog_from_suggested_url if defined?(@changelog_from_suggested_url)
119
+ return @changelog_from_suggested_url unless @changelog_from_suggested_url.nil?
86
120
  return unless suggested_changelog_url
87
121
 
88
122
  # TODO: Support other providers
@@ -90,29 +124,40 @@ module Dependabot
90
124
  return unless suggested_source&.provider == "github"
91
125
 
92
126
  opts = { path: suggested_source&.directory, ref: suggested_source&.branch }.compact
93
- suggested_source_client = github_client_for_source(suggested_source)
94
- tmp_files = suggested_source_client.contents(suggested_source&.repo, opts)
127
+ suggested_source_client = github_client_for_source(T.must(suggested_source))
128
+ tmp_files = T.unsafe(suggested_source_client).contents(suggested_source&.repo, opts)
95
129
 
96
- filename = suggested_changelog_url.split("/").last.split("#").first
130
+ filename = T.must(T.must(suggested_changelog_url).split("/").last).split("#").first
97
131
  @changelog_from_suggested_url =
98
132
  tmp_files.find { |f| f.name == filename }
99
133
  rescue Octokit::NotFound, Octokit::UnavailableForLegalReasons
100
134
  @changelog_from_suggested_url = nil
101
135
  end
102
136
 
137
+ sig { returns(T.nilable(T.any(OpenStruct, Sawyer::Resource))) }
103
138
  def default_branch_changelog
104
139
  return unless source
105
140
 
106
- @default_branch_changelog ||= changelog_from_ref(nil)
141
+ @default_branch_changelog ||=
142
+ T.let(
143
+ changelog_from_ref(nil),
144
+ T.nilable(T.any(OpenStruct, Sawyer::Resource))
145
+ )
107
146
  end
108
147
 
148
+ sig { returns(T.nilable(T.any(OpenStruct, Sawyer::Resource))) }
109
149
  def relevant_tag_changelog
110
150
  return unless source
111
151
  return unless tag_for_new_version
112
152
 
113
- @relevant_tag_changelog ||= changelog_from_ref(tag_for_new_version)
153
+ @relevant_tag_changelog ||=
154
+ T.let(
155
+ changelog_from_ref(tag_for_new_version),
156
+ T.nilable(T.any(OpenStruct, Sawyer::Resource))
157
+ )
114
158
  end
115
159
 
160
+ sig { params(ref: T.nilable(String)).returns(T.nilable(T.any(OpenStruct, Sawyer::Resource))) }
116
161
  def changelog_from_ref(ref)
117
162
  files =
118
163
  dependency_file_list(ref)
@@ -125,6 +170,7 @@ module Dependabot
125
170
  end
126
171
 
127
172
  # rubocop:disable Metrics/PerceivedComplexity
173
+ sig { params(files: T::Array[T.untyped]).returns(T.untyped) }
128
174
  def select_best_changelog(files)
129
175
  CHANGELOG_NAMES.each do |name|
130
176
  candidates = files.select { |f| f.name =~ /#{name}/i }
@@ -150,15 +196,20 @@ module Dependabot
150
196
  end
151
197
  # rubocop:enable Metrics/PerceivedComplexity
152
198
 
199
+ sig { returns(T.nilable(String)) }
153
200
  def tag_for_new_version
154
201
  @tag_for_new_version ||=
155
- CommitsFinder.new(
156
- dependency: dependency,
157
- source: source,
158
- credentials: credentials
159
- ).new_tag
202
+ T.let(
203
+ CommitsFinder.new(
204
+ dependency: dependency,
205
+ source: source,
206
+ credentials: credentials
207
+ ).new_tag,
208
+ T.nilable(String)
209
+ )
160
210
  end
161
211
 
212
+ sig { returns(T.nilable(String)) }
162
213
  def full_changelog_text
163
214
  return unless changelog
164
215
 
@@ -167,7 +218,7 @@ module Dependabot
167
218
 
168
219
  sig { params(file: T.untyped).returns(T.nilable(String)) }
169
220
  def fetch_file_text(file)
170
- @file_text ||= {}
221
+ @file_text ||= T.let({}, T.nilable(T::Hash[String, T.untyped]))
171
222
 
172
223
  unless @file_text.key?(file.download_url)
173
224
  file_source = T.must(Source.from_url(file.html_url))
@@ -187,12 +238,14 @@ module Dependabot
187
238
  @file_text[file.download_url].rstrip
188
239
  end
189
240
 
241
+ sig { params(file_source: Dependabot::Source, file: T.untyped).returns(String) }
190
242
  def fetch_github_file(file_source, file)
191
243
  # Hitting the download URL directly causes encoding problems
192
- raw_content = github_client_for_source(file_source).get(file.url).content
244
+ raw_content = T.unsafe(github_client_for_source(file_source)).get(file.url).content
193
245
  Base64.decode64(raw_content).force_encoding("UTF-8").encode
194
246
  end
195
247
 
248
+ sig { params(file: T.untyped).returns(String) }
196
249
  def fetch_gitlab_file(file)
197
250
  Excon.get(
198
251
  file.download_url,
@@ -201,16 +254,19 @@ module Dependabot
201
254
  ).body.force_encoding("UTF-8").encode
202
255
  end
203
256
 
257
+ sig { params(file: T.untyped).returns(String) }
204
258
  def fetch_bitbucket_file(file)
205
- bitbucket_client.get(file.download_url).body
206
- .force_encoding("UTF-8").encode
259
+ T.unsafe(bitbucket_client).get(file.download_url).body
260
+ .force_encoding("UTF-8").encode
207
261
  end
208
262
 
263
+ sig { params(file: T.untyped).returns(String) }
209
264
  def fetch_azure_file(file)
210
265
  azure_client.get(file.download_url).body
211
266
  .force_encoding("UTF-8").encode
212
267
  end
213
268
 
269
+ sig { returns(T.untyped) }
214
270
  def upgrade_guide
215
271
  return unless source
216
272
 
@@ -225,49 +281,55 @@ module Dependabot
225
281
  .max_by(&:size)
226
282
  end
227
283
 
284
+ sig { params(ref: T.nilable(String)).returns(T.untyped) }
228
285
  def dependency_file_list(ref = nil)
229
- @dependency_file_list ||= {}
286
+ @dependency_file_list ||= T.let({}, T.nilable(T::Hash[T.nilable(String), T.untyped]))
230
287
  @dependency_file_list[ref] ||= fetch_dependency_file_list(ref)
231
288
  end
232
289
 
290
+ sig { params(ref: T.nilable(String)).returns(T::Array[T.untyped,]) }
233
291
  def fetch_dependency_file_list(ref)
234
- case source.provider
292
+ case T.must(source).provider
235
293
  when "github" then fetch_github_file_list(ref)
236
294
  when "bitbucket" then fetch_bitbucket_file_list
237
295
  when "gitlab" then fetch_gitlab_file_list
238
296
  when "azure" then fetch_azure_file_list
239
297
  when "codecommit" then [] # TODO: Fetch Files from Codecommit
240
- else raise "Unexpected repo provider '#{source.provider}'"
298
+ else raise "Unexpected repo provider '#{T.must(source).provider}'"
241
299
  end
242
300
  end
243
301
 
302
+ # rubocop:disable Metrics/AbcSize
303
+ sig { params(ref: T.nilable(String)).returns(T::Array[T.untyped]) }
244
304
  def fetch_github_file_list(ref)
245
305
  files = []
246
306
 
247
- if source.directory
248
- opts = { path: source.directory, ref: ref }.compact
249
- tmp_files = github_client.contents(source.repo, opts)
307
+ if T.must(source).directory
308
+ opts = { path: T.must(source).directory, ref: ref }.compact
309
+ tmp_files = T.unsafe(github_client).contents(T.must(source).repo, opts)
250
310
  files += tmp_files if tmp_files.is_a?(Array)
251
311
  end
252
312
 
253
313
  opts = { ref: ref }.compact
254
- files += github_client.contents(source.repo, opts)
314
+ files += T.unsafe(github_client).contents(T.must(source).repo, opts)
255
315
 
256
316
  files.uniq.each do |f|
257
317
  next unless f.type == "dir" && f.name.match?(/docs?/o)
258
318
 
259
319
  opts = { path: f.path, ref: ref }.compact
260
- files += github_client.contents(source.repo, opts)
320
+ files += T.unsafe(github_client).contents(T.must(source).repo, opts)
261
321
  end
262
322
 
263
323
  files
264
324
  rescue Octokit::NotFound, Octokit::UnavailableForLegalReasons
265
325
  []
266
326
  end
327
+ # rubocop:enable Metrics/AbcSize
267
328
 
329
+ sig { returns(T.untyped) }
268
330
  def fetch_bitbucket_file_list
269
331
  branch = default_bitbucket_branch
270
- bitbucket_client.fetch_repo_contents(source.repo).map do |file|
332
+ T.unsafe(bitbucket_client).fetch_repo_contents(T.must(source).repo).map do |file|
271
333
  type = case file.fetch("type")
272
334
  when "commit_file" then "file"
273
335
  when "commit_directory" then "dir"
@@ -277,8 +339,8 @@ module Dependabot
277
339
  name: file.fetch("path").split("/").last,
278
340
  type: type,
279
341
  size: file.fetch("size", 100),
280
- html_url: "#{source.url}/src/#{branch}/#{file['path']}",
281
- download_url: "#{source.url}/raw/#{branch}/#{file['path']}"
342
+ html_url: "#{T.must(source).url}/src/#{branch}/#{file['path']}",
343
+ download_url: "#{T.must(source).url}/raw/#{branch}/#{file['path']}"
282
344
  )
283
345
  end
284
346
  rescue Dependabot::Clients::Bitbucket::NotFound,
@@ -287,9 +349,10 @@ module Dependabot
287
349
  []
288
350
  end
289
351
 
352
+ sig { returns(T.untyped) }
290
353
  def fetch_gitlab_file_list
291
354
  branch = default_gitlab_branch
292
- gitlab_client.repo_tree(source.repo).map do |file|
355
+ T.unsafe(gitlab_client).repo_tree(T.must(source).repo).map do |file|
293
356
  type = case file.type
294
357
  when "blob" then "file"
295
358
  when "tree" then "dir"
@@ -299,14 +362,15 @@ module Dependabot
299
362
  name: file.name,
300
363
  type: type,
301
364
  size: 100, # GitLab doesn't return file size
302
- html_url: "#{source.url}/blob/#{branch}/#{file.path}",
303
- download_url: "#{source.url}/raw/#{branch}/#{file.path}"
365
+ html_url: "#{T.must(source).url}/blob/#{branch}/#{file.path}",
366
+ download_url: "#{T.must(source).url}/raw/#{branch}/#{file.path}"
304
367
  )
305
368
  end
306
369
  rescue Gitlab::Error::NotFound
307
370
  []
308
371
  end
309
372
 
373
+ sig { returns(T.untyped) }
310
374
  def fetch_azure_file_list
311
375
  azure_client.fetch_repo_contents.map do |entry|
312
376
  type = case entry.fetch("gitObjectType")
@@ -320,7 +384,7 @@ module Dependabot
320
384
  type: type,
321
385
  size: entry.fetch("size"),
322
386
  path: entry.fetch("relativePath"),
323
- html_url: "#{source.url}?path=/#{entry.fetch('relativePath')}",
387
+ html_url: "#{T.must(source).url}?path=/#{entry.fetch('relativePath')}",
324
388
  download_url: entry.fetch("url")
325
389
  )
326
390
  end
@@ -330,20 +394,23 @@ module Dependabot
330
394
  []
331
395
  end
332
396
 
397
+ sig { returns(T.nilable(String)) }
333
398
  def new_version
334
- return @new_version if defined?(@new_version)
399
+ return @new_version unless @new_version.nil?
335
400
 
336
401
  new_version = git_source? && new_ref ? new_ref : dependency.version
337
402
  @new_version = new_version&.gsub(/^v/, "")
338
403
  end
339
404
 
405
+ sig { returns(T.nilable(String)) }
340
406
  def previous_ref
341
- previous_refs = dependency.previous_requirements.filter_map do |r|
407
+ previous_refs = dependency.previous_requirements&.filter_map do |r|
342
408
  r.dig(:source, "ref") || r.dig(:source, :ref)
343
- end.uniq
344
- previous_refs.first if previous_refs.count == 1
409
+ end&.uniq
410
+ previous_refs&.first if previous_refs&.count == 1
345
411
  end
346
412
 
413
+ sig { returns(T.nilable(String)) }
347
414
  def new_ref
348
415
  new_refs = dependency.requirements.filter_map do |r|
349
416
  r.dig(:source, "ref") || r.dig(:source, :ref)
@@ -351,12 +418,14 @@ module Dependabot
351
418
  new_refs.first if new_refs.count == 1
352
419
  end
353
420
 
421
+ sig { returns(T::Boolean) }
354
422
  def ref_changed?
355
423
  # We could go from multiple previous refs (nil) to a single new ref
356
424
  previous_ref != new_ref
357
425
  end
358
426
 
359
427
  # TODO: Refactor me so that Composer doesn't need to be special cased
428
+ sig { returns(T::Boolean) }
360
429
  def git_source?
361
430
  # Special case Composer, which uses git as a source but handles tags
362
431
  # internally
@@ -369,51 +438,77 @@ module Dependabot
369
438
  sources.all? { |s| s[:type] == "git" || s["type"] == "git" }
370
439
  end
371
440
 
441
+ sig { returns(T::Boolean) }
372
442
  def major_version_upgrade?
373
443
  return false unless dependency.version&.match?(/^\d/)
374
444
  return false unless dependency.previous_version&.match?(/^\d/)
375
445
 
376
- dependency.version.split(".").first.to_i -
377
- dependency.previous_version.split(".").first.to_i >= 1
446
+ T.must(dependency.version).split(".").first.to_i -
447
+ T.must(dependency.previous_version).split(".").first.to_i >= 1
378
448
  end
379
449
 
450
+ sig { returns(Dependabot::Clients::GitlabWithRetries) }
380
451
  def gitlab_client
381
- @gitlab_client ||= Dependabot::Clients::GitlabWithRetries
382
- .for_gitlab_dot_com(credentials: credentials)
452
+ @gitlab_client ||=
453
+ T.let(
454
+ Dependabot::Clients::GitlabWithRetries.for_gitlab_dot_com(credentials: credentials),
455
+ T.nilable(Dependabot::Clients::GitlabWithRetries)
456
+ )
383
457
  end
384
458
 
459
+ sig { returns(Dependabot::Clients::GithubWithRetries) }
385
460
  def github_client
386
- @github_client ||= Dependabot::Clients::GithubWithRetries
387
- .for_source(source: source, credentials: credentials)
461
+ @github_client ||=
462
+ T.let(
463
+ Dependabot::Clients::GithubWithRetries.for_source(source: source, credentials: credentials),
464
+ T.nilable(Dependabot::Clients::GithubWithRetries)
465
+ )
388
466
  end
389
467
 
468
+ sig { returns(Dependabot::Clients::Azure) }
390
469
  def azure_client
391
- @azure_client ||= Dependabot::Clients::Azure
392
- .for_source(source: source, credentials: credentials)
470
+ @azure_client ||=
471
+ T.let(
472
+ Dependabot::Clients::Azure.for_source(source: T.must(source), credentials: credentials),
473
+ T.nilable(Dependabot::Clients::Azure)
474
+ )
393
475
  end
394
476
 
477
+ sig { params(client_source: Dependabot::Source).returns(Dependabot::Clients::GithubWithRetries) }
395
478
  def github_client_for_source(client_source)
396
479
  return github_client if client_source == source
397
480
 
398
- Dependabot::Clients::GithubWithRetries
399
- .for_source(source: client_source, credentials: credentials)
481
+ Dependabot::Clients::GithubWithRetries.for_source(source: client_source, credentials: credentials)
400
482
  end
401
483
 
484
+ sig { returns(Dependabot::Clients::BitbucketWithRetries) }
402
485
  def bitbucket_client
403
- @bitbucket_client ||= Dependabot::Clients::BitbucketWithRetries
404
- .for_bitbucket_dot_org(credentials: credentials)
486
+ @bitbucket_client ||=
487
+ T.let(
488
+ Dependabot::Clients::BitbucketWithRetries.for_bitbucket_dot_org(credentials: credentials),
489
+ T.nilable(Dependabot::Clients::BitbucketWithRetries)
490
+ )
405
491
  end
406
492
 
493
+ sig { returns(String) }
407
494
  def default_bitbucket_branch
408
495
  @default_bitbucket_branch ||=
409
- bitbucket_client.fetch_default_branch(source.repo)
496
+ T.let(
497
+ T.unsafe(bitbucket_client).fetch_default_branch(T.must(source).repo),
498
+ T.nilable(String)
499
+ )
410
500
  end
411
501
 
502
+ sig { returns(String) }
412
503
  def default_gitlab_branch
413
504
  @default_gitlab_branch ||=
414
- gitlab_client.fetch_default_branch(source.repo)
505
+ T.let(
506
+ gitlab_client.fetch_default_branch(T.must(source).repo),
507
+ T.nilable(String)
508
+ )
415
509
  end
416
510
  end
511
+ # rubocop:enable Metrics/ClassLength
417
512
  end
418
513
  end
419
514
  end
@@ -1,43 +1,60 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
4
5
  require "dependabot/metadata_finders/base"
5
6
 
6
7
  module Dependabot
7
8
  module MetadataFinders
8
9
  class Base
9
10
  class ChangelogPruner
10
- attr_reader :dependency, :changelog_text
11
+ extend T::Sig
11
12
 
13
+ sig { returns(Dependabot::Dependency) }
14
+ attr_reader :dependency
15
+
16
+ sig { returns(T.nilable(String)) }
17
+ attr_reader :changelog_text
18
+
19
+ sig do
20
+ params(
21
+ dependency: Dependabot::Dependency,
22
+ changelog_text: T.nilable(String)
23
+ )
24
+ .void
25
+ end
12
26
  def initialize(dependency:, changelog_text:)
13
27
  @dependency = dependency
14
28
  @changelog_text = changelog_text
15
29
  end
16
30
 
31
+ sig { returns(T::Boolean) }
17
32
  def includes_new_version?
18
33
  !new_version_changelog_line.nil?
19
34
  end
20
35
 
36
+ sig { returns(T::Boolean) }
21
37
  def includes_previous_version?
22
38
  !old_version_changelog_line.nil?
23
39
  end
24
40
 
25
- def pruned_text
26
- changelog_lines = changelog_text.split("\n")
41
+ sig { returns(T.nilable(String)) }
42
+ def pruned_text # rubocop:disable Metrics/PerceivedComplexity
43
+ changelog_lines = changelog_text&.split("\n")
27
44
 
28
45
  slice_range =
29
46
  if old_version_changelog_line && new_version_changelog_line
30
- if old_version_changelog_line < new_version_changelog_line
47
+ if T.must(old_version_changelog_line) < T.must(new_version_changelog_line)
31
48
  Range.new(old_version_changelog_line, -1)
32
49
  else
33
50
  Range.new(new_version_changelog_line,
34
- old_version_changelog_line - 1)
51
+ T.must(old_version_changelog_line) - 1)
35
52
  end
36
53
  elsif old_version_changelog_line
37
- return if old_version_changelog_line.zero?
54
+ return if T.must(old_version_changelog_line).zero?
38
55
 
39
56
  # Assumes changelog is in descending order
40
- Range.new(0, old_version_changelog_line - 1)
57
+ Range.new(0, T.must(old_version_changelog_line) - 1)
41
58
  elsif new_version_changelog_line
42
59
  # Assumes changelog is in descending order
43
60
  Range.new(new_version_changelog_line, -1)
@@ -49,11 +66,12 @@ module Dependabot
49
66
  Range.new(0, -1)
50
67
  end
51
68
 
52
- changelog_lines.slice(slice_range).join("\n").rstrip
69
+ changelog_lines&.slice(slice_range)&.join("\n")&.rstrip
53
70
  end
54
71
 
55
72
  private
56
73
 
74
+ sig { returns(T.nilable(Integer)) }
57
75
  def old_version_changelog_line
58
76
  old_version = git_source? ? previous_ref : dependency.previous_version
59
77
  return nil unless old_version
@@ -61,6 +79,7 @@ module Dependabot
61
79
  changelog_line_for_version(old_version)
62
80
  end
63
81
 
82
+ sig { returns(T.nilable(Integer)) }
64
83
  def new_version_changelog_line
65
84
  return nil unless new_version
66
85
 
@@ -68,6 +87,7 @@ module Dependabot
68
87
  end
69
88
 
70
89
  # rubocop:disable Metrics/PerceivedComplexity
90
+ sig { params(version: T.nilable(String)).returns(T.nilable(Integer)) }
71
91
  def changelog_line_for_version(version)
72
92
  raise "No changelog text" unless changelog_text
73
93
  return nil unless version
@@ -75,7 +95,7 @@ module Dependabot
75
95
  version = version.gsub(/^v/, "")
76
96
  escaped_version = Regexp.escape(version)
77
97
 
78
- changelog_lines = changelog_text.split("\n")
98
+ changelog_lines = T.must(changelog_text).split("\n")
79
99
 
80
100
  changelog_lines.find_index.with_index do |line, index|
81
101
  next false unless line.match?(/(?<!\.)#{escaped_version}(?![.\-])/)
@@ -92,13 +112,14 @@ module Dependabot
92
112
 
93
113
  # rubocop:enable Metrics/PerceivedComplexity
94
114
 
115
+ sig { returns(T::Boolean) }
95
116
  def changelog_contains_relevant_versions?
96
117
  # Assume the changelog is relevant if we can't parse the new version
97
118
  return true unless version_class.correct?(dependency.version)
98
119
 
99
120
  # Assume the changelog is relevant if it mentions the new version
100
121
  # anywhere
101
- return true if changelog_text.include?(dependency.version)
122
+ return true if changelog_text&.include?(T.must(dependency.version))
102
123
 
103
124
  # Otherwise check if any intermediate versions are included in headers
104
125
  versions_in_changelog_headers.any? do |version|
@@ -110,8 +131,9 @@ module Dependabot
110
131
  end
111
132
  end
112
133
 
134
+ sig { returns(T::Array[String]) }
113
135
  def versions_in_changelog_headers
114
- changelog_lines = changelog_text.split("\n")
136
+ changelog_lines = T.must(changelog_text).split("\n")
115
137
  header_lines =
116
138
  changelog_lines.select.with_index do |line, index|
117
139
  next true if line.start_with?("#", "!")
@@ -132,18 +154,25 @@ module Dependabot
132
154
  versions
133
155
  end
134
156
 
157
+ sig { returns(T.nilable(String)) }
135
158
  def new_version
136
- @new_version ||= git_source? ? new_ref : dependency.version
159
+ @new_version ||=
160
+ T.let(
161
+ git_source? ? new_ref : dependency.version,
162
+ T.nilable(String)
163
+ )
137
164
  @new_version&.gsub(/^v/, "")
138
165
  end
139
166
 
167
+ sig { returns(T.nilable(String)) }
140
168
  def previous_ref
141
- previous_refs = dependency.previous_requirements.filter_map do |r|
169
+ previous_refs = T.must(dependency.previous_requirements).filter_map do |r|
142
170
  r.dig(:source, "ref") || r.dig(:source, :ref)
143
171
  end.uniq
144
172
  previous_refs.first if previous_refs.count == 1
145
173
  end
146
174
 
175
+ sig { returns(T.nilable(String)) }
147
176
  def new_ref
148
177
  new_refs = dependency.requirements.filter_map do |r|
149
178
  r.dig(:source, "ref") || r.dig(:source, :ref)
@@ -152,6 +181,7 @@ module Dependabot
152
181
  end
153
182
 
154
183
  # TODO: Refactor me so that Composer doesn't need to be special cased
184
+ sig { returns(T::Boolean) }
155
185
  def git_source?
156
186
  # Special case Composer, which uses git as a source but handles tags
157
187
  # internally
@@ -164,6 +194,7 @@ module Dependabot
164
194
  sources.all? { |s| s[:type] == "git" || s["type"] == "git" }
165
195
  end
166
196
 
197
+ sig { returns(T.class_of(Dependabot::Version)) }
167
198
  def version_class
168
199
  dependency.version_class
169
200
  end