Skip to content

Refactor: Consistent Filtering of Releases in PackageLatestVersionFinder #12061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
64 changes: 36 additions & 28 deletions bun/lib/dependabot/bun/update_checker/latest_version_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def latest_version_from_registry
def latest_version_with_no_unlock(language_version: nil)
with_custom_registry_rescue do
return unless valid_npm_details?
return version_from_dist_tags if specified_dist_tag_requirement?
return version_from_dist_tags&.version if specified_dist_tag_requirement?

super
end
Expand All @@ -100,8 +100,8 @@ def fetch_latest_version(language_version: nil)
with_custom_registry_rescue do
return unless valid_npm_details?

tag_version = version_from_dist_tags
return tag_version if tag_version
tag_release = version_from_dist_tags
return tag_release.version if tag_release

return if specified_dist_tag_requirement?

Expand All @@ -117,16 +117,20 @@ def fetch_latest_version(language_version: nil)
def fetch_latest_version_with_no_unlock(language_version: nil)
with_custom_registry_rescue do
return unless valid_npm_details?
return version_from_dist_tags if specified_dist_tag_requirement?
return version_from_dist_tags&.version if specified_dist_tag_requirement?

super
end
end

sig { override.params(versions: T::Array[Dependabot::Version]).returns(T::Array[Dependabot::Version]) }
def apply_post_fetch_latest_versions_filter(versions)
original_count = versions.count
filtered_versions = lazy_filter_yanked_versions_by_min_max(versions, check_max: true)
sig do
override
.params(releases: T::Array[Dependabot::Package::PackageRelease])
.returns(T::Array[Dependabot::Package::PackageRelease])
end
def apply_post_fetch_latest_versions_filter(releases)
original_count = releases.count
filtered_versions = lazy_filter_yanked_versions_by_min_max(releases, check_max: true)

# Log the filter if any versions were removed
if original_count > filtered_versions.count
Expand All @@ -141,26 +145,30 @@ def apply_post_fetch_latest_versions_filter(versions)

sig do
params(
versions: T::Array[Dependabot::Version],
releases: T::Array[Dependabot::Package::PackageRelease],
check_max: T::Boolean
).returns(T::Array[Dependabot::Version])
).returns(T::Array[Dependabot::Package::PackageRelease])
end
def lazy_filter_yanked_versions_by_min_max(versions, check_max: true)
def lazy_filter_yanked_versions_by_min_max(releases, check_max: true)
# Sort the versions based on the check_max flag (max -> descending, min -> ascending)
sorted_versions = check_max ? versions.sort.reverse : versions.sort
sorted_releases = if check_max
releases.sort_by(&:version).reverse
else
releases.sort_by(&:version)
end

filtered_versions = []

not_yanked = T.let(false, T::Boolean)

# Iterate through the sorted versions lazily, filtering out yanked versions
sorted_versions.each do |version|
next if !not_yanked && yanked_version?(version)
sorted_releases.each do |release|
next if !not_yanked && yanked_version?(release.version)

not_yanked = true

# Once we find a valid (non-yanked) version, add it to the filtered list
filtered_versions << version
filtered_versions << release
break
end

Expand All @@ -172,15 +180,15 @@ def lazy_filter_yanked_versions_by_min_max(versions, check_max: true)
.params(language_version: T.nilable(T.any(String, Dependabot::Version)))
.returns(T.nilable(Dependabot::Version))
end
def fetch_lowest_security_fix_version(language_version:) # rubocop:disable Lint/UnusedMethodArgument
def fetch_lowest_security_fix_version(language_version: nil) # rubocop:disable Lint/UnusedMethodArgument
with_custom_registry_rescue do
return unless valid_npm_details?

secure_versions =
if specified_dist_tag_requirement?
[version_from_dist_tags].compact
else
possible_versions(filter_ignored: false)
possible_releases(filter_ignored: false)
end

secure_versions =
Expand All @@ -196,22 +204,22 @@ def fetch_lowest_security_fix_version(language_version:) # rubocop:disable Lint/
secure_versions = lazy_filter_yanked_versions_by_min_max(secure_versions, check_max: false)

# Return the lowest non-yanked version
secure_versions.max
secure_versions.max_by(&:version)&.version
end
end

sig do
params(versions_array: T::Array[Dependabot::Version])
.returns(T::Array[Dependabot::Version])
params(releases: T::Array[Dependabot::Package::PackageRelease])
.returns(T::Array[Dependabot::Package::PackageRelease])
end
def filter_prerelease_versions(versions_array)
filtered = versions_array.reject do |v|
v.prerelease? && !related_to_current_pre?(v)
def filter_prerelease_versions(releases)
filtered = releases.reject do |release|
release.version.prerelease? && !related_to_current_pre?(release.version)
end

if versions_array.count > filtered.count
if releases.count > filtered.count
Dependabot.logger.info(
"Filtered out #{versions_array.count - filtered.count} unrelated pre-release versions"
"Filtered out #{releases.count - filtered.count} unrelated pre-release versions"
)
end

Expand Down Expand Up @@ -317,7 +325,7 @@ def valid_npm_details?
!!package_details&.releases&.any?
end

sig { returns(T.nilable(Dependabot::Version)) }
sig { returns(T.nilable(Dependabot::Package::PackageRelease)) }
def version_from_dist_tags # rubocop:disable Metrics/PerceivedComplexity
dist_tags = package_details&.dist_tags
return nil unless dist_tags
Expand All @@ -332,14 +340,14 @@ def version_from_dist_tags # rubocop:disable Metrics/PerceivedComplexity

if dist_tag_req
release = find_dist_tag_release(dist_tag_req, releases)
return release.version if release && !release.yanked?
return release if release && !release.yanked?
end

latest_release = find_dist_tag_release("latest", releases)

return nil unless latest_release

return latest_release.version if wants_latest_dist_tag?(latest_release.version) && !latest_release.yanked?
return latest_release if wants_latest_dist_tag?(latest_release.version) && !latest_release.yanked?

nil
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "excon"
Expand All @@ -20,6 +20,34 @@ class UpdateChecker
class LatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
extend T::Sig

sig do
params(
dependency: Dependabot::Dependency,
dependency_files: T::Array[Dependabot::DependencyFile],
credentials: T::Array[Dependabot::Credential],
ignored_versions: T::Array[String],
security_advisories: T::Array[Dependabot::SecurityAdvisory],
cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions),
raise_on_ignored: T::Boolean,
options: T::Hash[Symbol, T.untyped]
).void
end
def initialize(
dependency:,
dependency_files:,
credentials:,
ignored_versions:,
security_advisories:,
cooldown_options: nil,
raise_on_ignored: false,
options: {}
)
@package_details = T.let(nil, T.nilable(Dependabot::Package::PackageDetails))
@latest_version_details = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped]))
@releases_from_dependency_source = T.let(nil, T.nilable(T::Array[Dependabot::Package::PackageRelease]))
super
end

sig { override.returns(T.nilable(Dependabot::Package::PackageDetails)) }
def package_details
@package_details ||= Package::PackageDetailsFetcher.new(
Expand All @@ -29,6 +57,7 @@ def package_details
).fetch
end

sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
def latest_version_details
@latest_version_details ||= if cooldown_enabled?
latest_version = fetch_latest_version(language_version: nil)
Expand All @@ -47,7 +76,7 @@ def cooldown_enabled?
def available_versions
return nil if package_details&.releases.nil?

source_versions = dependency_source.versions
source_versions = releases_from_dependency_source
return [] if source_versions.empty?

T.must(package_details).releases.select do |release|
Expand All @@ -57,20 +86,29 @@ def available_versions

private

sig { returns(T.nilable(T::Hash[Symbol, Dependabot::Version])) }
def fetch_latest_version_details
return dependency_source.latest_git_version_details if dependency_source.git?

relevant_versions = dependency_source.versions
relevant_versions = releases_from_dependency_source
relevant_versions = filter_prerelease_versions(relevant_versions)
relevant_versions = filter_ignored_versions(relevant_versions)

relevant_versions.empty? ? nil : { version: relevant_versions.max }
return if relevant_versions.empty?

release = relevant_versions.max_by(&:version)

{ version: release&.version }
end

def fetch_lowest_security_fix_version(*)
sig do
params(language_version: T.nilable(T.any(String, Dependabot::Version)))
.returns(T.nilable(Dependabot::Version))
end
def fetch_lowest_security_fix_version(language_version: nil) # rubocop:disable Lint/UnusedMethodArgument
return if dependency_source.git?

relevant_versions = dependency_source.versions
relevant_versions = releases_from_dependency_source
relevant_versions = filter_prerelease_versions(relevant_versions)
relevant_versions = Dependabot::UpdateCheckers::VersionFilters
.filter_vulnerable_versions(
Expand All @@ -80,7 +118,20 @@ def fetch_lowest_security_fix_version(*)
relevant_versions = filter_ignored_versions(relevant_versions)
relevant_versions = filter_lower_versions(relevant_versions)

relevant_versions.min
relevant_versions.min_by(&:version)&.version
end

sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
def releases_from_dependency_source
return @releases_from_dependency_source if @releases_from_dependency_source

@releases_from_dependency_source =
dependency_source.versions.map do |version|
Dependabot::Package::PackageRelease.new(
version: version
)
end
@releases_from_dependency_source
end

sig { returns(T::Boolean) }
Expand All @@ -99,7 +150,7 @@ def wants_prerelease?
)
end

# sig { returns(DependencySource) }
sig { returns(DependencySource) }
def dependency_source
@dependency_source ||= T.let(
DependencySource.new(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "excon"
Expand Down Expand Up @@ -26,12 +26,20 @@ def package_details
).fetch
end

def latest_version
@latest_version ||= fetch_latest_version
sig do
override.params(language_version: T.nilable(T.any(String, Dependabot::Version)))
.returns(T.nilable(Dependabot::Version))
end
def latest_version(language_version: nil)
@latest_version ||= fetch_latest_version(language_version: language_version)
end

def lowest_security_fix_version
@lowest_security_fix_version ||= fetch_lowest_security_fix_version(language_version: nil)
sig do
override.params(language_version: T.nilable(T.any(String, Dependabot::Version)))
.returns(T.nilable(Dependabot::Version))
end
def lowest_security_fix_version(language_version: nil)
@lowest_security_fix_version ||= fetch_lowest_security_fix_version(language_version: language_version)
end

protected
Expand All @@ -53,14 +61,23 @@ def cooldown_enabled?

private

sig { returns(Dependabot::Dependency) }
attr_reader :dependency
sig { returns(T::Array[Dependabot::DependencyFile]) }
attr_reader :dependency_files
sig { returns(T::Array[Dependabot::Credential]) }
attr_reader :credentials
sig { returns(T::Array[String]) }
attr_reader :ignored_versions
sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
attr_reader :security_advisories

def apply_post_fetch_lowest_security_fix_versions_filter(versions)
filter_prerelease_versions(versions)
sig do
override.params(releases: T::Array[Dependabot::Package::PackageRelease])
.returns(T::Array[Dependabot::Package::PackageRelease])
end
def apply_post_fetch_lowest_security_fix_versions_filter(releases)
filter_prerelease_versions(releases)
end
end
end
Expand Down
Loading
Loading