diff --git a/bun/lib/dependabot/bun/update_checker/latest_version_finder.rb b/bun/lib/dependabot/bun/update_checker/latest_version_finder.rb index 8f914e82d54..20588b355e7 100644 --- a/bun/lib/dependabot/bun/update_checker/latest_version_finder.rb +++ b/bun/lib/dependabot/bun/update_checker/latest_version_finder.rb @@ -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 @@ -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? @@ -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 @@ -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 @@ -172,7 +180,7 @@ 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? @@ -180,7 +188,7 @@ def fetch_lowest_security_fix_version(language_version:) # rubocop:disable Lint/ if specified_dist_tag_requirement? [version_from_dist_tags].compact else - possible_versions(filter_ignored: false) + possible_releases(filter_ignored: false) end secure_versions = @@ -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 @@ -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 @@ -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 diff --git a/bundler/lib/dependabot/bundler/update_checker/latest_version_finder.rb b/bundler/lib/dependabot/bundler/update_checker/latest_version_finder.rb index 9dffbe36f53..81f14572ac0 100644 --- a/bundler/lib/dependabot/bundler/update_checker/latest_version_finder.rb +++ b/bundler/lib/dependabot/bundler/update_checker/latest_version_finder.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "excon" @@ -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( @@ -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) @@ -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| @@ -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( @@ -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) } @@ -99,7 +150,7 @@ def wants_prerelease? ) end - # sig { returns(DependencySource) } + sig { returns(DependencySource) } def dependency_source @dependency_source ||= T.let( DependencySource.new( diff --git a/cargo/lib/dependabot/cargo/update_checker/latest_version_finder.rb b/cargo/lib/dependabot/cargo/update_checker/latest_version_finder.rb index 3cc89b6ee32..382018094ab 100644 --- a/cargo/lib/dependabot/cargo/update_checker/latest_version_finder.rb +++ b/cargo/lib/dependabot/cargo/update_checker/latest_version_finder.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "excon" @@ -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 @@ -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 diff --git a/common/lib/dependabot/package/package_latest_version_finder.rb b/common/lib/dependabot/package/package_latest_version_finder.rb index 96184247425..84685139c22 100644 --- a/common/lib/dependabot/package/package_latest_version_finder.rb +++ b/common/lib/dependabot/package/package_latest_version_finder.rb @@ -121,24 +121,16 @@ def available_versions .returns(T.nilable(Dependabot::Version)) end def fetch_latest_version(language_version: nil) - version_hashes = available_versions - return unless version_hashes + releases = available_versions + return unless releases - version_hashes = filter_yanked_versions(version_hashes) - version_hashes = filter_by_cooldown(version_hashes) - versions = filter_unsupported_versions(version_hashes, language_version) - versions = filter_prerelease_versions(versions) - versions = filter_ignored_versions(versions) - versions = apply_post_fetch_latest_versions_filter(versions) - versions.max - end - - sig do - params(versions: T::Array[Dependabot::Version]) - .returns(T::Array[Dependabot::Version]) - end - def apply_post_fetch_latest_versions_filter(versions) - versions + releases = filter_yanked_versions(releases) + releases = filter_by_cooldown(releases) + releases = filter_unsupported_versions(releases, language_version) + releases = filter_prerelease_versions(releases) + releases = filter_ignored_versions(releases) + releases = apply_post_fetch_latest_versions_filter(releases) + releases.max_by(&:version)&.version end sig do @@ -146,45 +138,57 @@ def apply_post_fetch_latest_versions_filter(versions) .returns(T.nilable(Dependabot::Version)) end def fetch_latest_version_with_no_unlock(language_version:) - version_hashes = available_versions - return unless version_hashes + releases = available_versions + return unless releases - version_hashes = filter_yanked_versions(version_hashes) - version_hashes = filter_by_cooldown(version_hashes) - versions = filter_unsupported_versions(version_hashes, language_version) - versions = filter_prerelease_versions(versions) - versions = filter_ignored_versions(versions) - versions = filter_out_of_range_versions(versions) - versions = apply_post_fetch_latest_versions_filter(versions) - versions.max + releases = filter_yanked_versions(releases) + releases = filter_by_cooldown(releases) + releases = filter_unsupported_versions(releases, language_version) + releases = filter_prerelease_versions(releases) + releases = filter_ignored_versions(releases) + releases = filter_out_of_range_versions(releases) + releases = apply_post_fetch_latest_versions_filter(releases) + releases.max_by(&:version)&.version end 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:) - version_hashes = available_versions - return unless version_hashes + def fetch_lowest_security_fix_version(language_version: nil) + releases = available_versions + return unless releases - version_hashes = filter_yanked_versions(version_hashes) - version_hashes = filter_by_cooldown(version_hashes) - versions = filter_unsupported_versions(version_hashes, language_version) + releases = filter_yanked_versions(releases) + releases = filter_by_cooldown(releases) + releases = filter_unsupported_versions(releases, language_version) # versions = filter_prerelease_versions(versions) - versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions( - versions, - security_advisories - ) - versions = filter_ignored_versions(versions) - versions = filter_lower_versions(versions) - versions = apply_post_fetch_lowest_security_fix_versions_filter(versions) + releases = Dependabot::UpdateCheckers::VersionFilters + .filter_vulnerable_versions( + releases, + security_advisories + ) + releases = filter_ignored_versions(releases) + releases = filter_lower_versions(releases) + releases = apply_post_fetch_lowest_security_fix_versions_filter(releases) - versions.min + releases.min_by(&:version)&.version end - sig { params(versions: T::Array[Dependabot::Version]).returns(T::Array[Dependabot::Version]) } - def apply_post_fetch_lowest_security_fix_versions_filter(versions) - versions + sig do + params(releases: T::Array[Dependabot::Package::PackageRelease]) + .returns(T::Array[Dependabot::Package::PackageRelease]) + end + def apply_post_fetch_latest_versions_filter(releases) + releases + end + + sig do + params(releases: T::Array[Dependabot::Package::PackageRelease]) + .returns(T::Array[Dependabot::Package::PackageRelease]) + end + def apply_post_fetch_lowest_security_fix_versions_filter(releases) + releases end sig do @@ -227,16 +231,16 @@ def filter_by_cooldown(releases) releases: T::Array[Dependabot::Package::PackageRelease], language_version: T.nilable(T.any(String, Dependabot::Version)) ) - .returns(T::Array[Dependabot::Version]) + .returns(T::Array[Dependabot::Package::PackageRelease]) end def filter_unsupported_versions(releases, language_version) filtered = releases.filter_map do |release| language_requirement = release.language&.requirement - next release.version unless language_version - next release.version unless language_requirement + next release unless language_version + next release unless language_requirement next unless language_requirement.satisfied_by?(language_version) - release.version + release end if releases.count > filtered.count delta = releases.count - filtered.count @@ -246,61 +250,69 @@ def filter_unsupported_versions(releases, language_version) 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) - return versions_array if wants_prerelease? + def filter_prerelease_versions(releases) + return releases if wants_prerelease? - filtered = versions_array.reject(&:prerelease?) + filtered = releases.reject { |release| release.version.prerelease? } - if versions_array.count > filtered.count - Dependabot.logger.info("Filtered out #{versions_array.count - filtered.count} pre-release versions") + if releases.count > filtered.count + Dependabot.logger.info("Filtered out #{releases.count - filtered.count} pre-release versions") end filtered 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_ignored_versions(versions_array) - filtered = versions_array - .reject { |v| ignore_requirements.any? { |r| r.satisfied_by?(v) } } - if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions_array).any? + def filter_ignored_versions(releases) + filtered = releases + .reject do |release| + ignore_requirements.any? do |r| + r.satisfied_by?(release.version) + end + end + if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(releases).any? raise Dependabot::AllVersionsIgnored end - if versions_array.count > filtered.count - Dependabot.logger.info("Filtered out #{versions_array.count - filtered.count} ignored versions") + if releases.count > filtered.count + Dependabot.logger.info("Filtered out #{releases.count - filtered.count} ignored versions") end filtered 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_lower_versions(versions_array) - return versions_array unless dependency.numeric_version + def filter_lower_versions(releases) + return releases unless dependency.numeric_version - versions_array.select { |version| version > dependency.numeric_version } + releases.select { |release| release.version > dependency.numeric_version } 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_out_of_range_versions(versions_array) + def filter_out_of_range_versions(releases) reqs = dependency.requirements.filter_map do |r| next if r.fetch(:requirement).nil? requirement_class.requirements_array(r.fetch(:requirement)) end - versions_array - .select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } } + releases + .select do |release| + reqs.all? do |r| + r.any? { |o| o.satisfied_by?(release.version) } + end + end end sig { returns(T::Boolean) } @@ -338,6 +350,7 @@ def cooldown_days_for(current_version, new_version) cooldown.default_days end + sig { returns(T::Boolean) } def wants_prerelease? return version_class.new(dependency.version).prerelease? if dependency.version diff --git a/common/lib/dependabot/package/package_release.rb b/common/lib/dependabot/package/package_release.rb index ae202deb9d1..0ee9cab815c 100644 --- a/common/lib/dependabot/package/package_release.rb +++ b/common/lib/dependabot/package/package_release.rb @@ -84,6 +84,18 @@ def initialize( def yanked? @yanked end + + sig { params(other: Object).returns(T::Boolean) } + def ==(other) + return false unless other.is_a?(PackageRelease) + + version == other.version + end + + sig { returns(String) } + def to_s + version.to_s + end end end end diff --git a/common/spec/dependabot/package/package_latest_version_finder_spec.rb b/common/spec/dependabot/package/package_latest_version_finder_spec.rb index 8de2d461242..a1e149b4927 100644 --- a/common/spec/dependabot/package/package_latest_version_finder_spec.rb +++ b/common/spec/dependabot/package/package_latest_version_finder_spec.rb @@ -42,7 +42,7 @@ def package_details language = if release[:language] Dependabot::Package::PackageLanguage.new( name: release[:language].fetch(:name, ""), - version: release[:language].fetch(:version, nil)&.then { |v| Dependabot::Version.new(v) }, + version: release[:language].fetch(:version, nil)&.then { |v| TestVersion.new(v) }, requirement: release[:language].fetch(:requirement, nil)&.then do |r| TestRequirement.new(r) end @@ -266,7 +266,7 @@ def package_details describe "#latest_version" do subject(:latest_version) { finder.latest_version } - it { is_expected.to eq(Gem::Version.new("7.0.0")) } + it { is_expected.to eq(TestVersion.new("7.0.0")) } context "when all supported versions are ignored" do let(:ignored_versions) { ["7.0.0", "6.1.4", "6.0.2", "6.0.0"] } @@ -286,7 +286,7 @@ def package_details end it "ignores prerelease versions" do - expect(latest_version).to eq(Gem::Version.new("7.0.0")) + expect(latest_version).to eq(TestVersion.new("7.0.0")) end context "when prereleases are allowed" do @@ -295,7 +295,7 @@ def package_details end it "selects the highest prerelease version" do - expect(latest_version).to eq(Gem::Version.new("7.0.0")) + expect(latest_version).to eq(TestVersion.new("7.0.0")) end end end @@ -305,7 +305,7 @@ def package_details subject(:latest_version_with_no_unlock) { finder.latest_version_with_no_unlock } context "when no constraints are present" do - it { is_expected.to eq(Gem::Version.new("7.0.0")) } + it { is_expected.to eq(TestVersion.new("7.0.0")) } end context "with an exact version requirement" do @@ -313,7 +313,7 @@ def package_details [{ file: "Gemfile", requirement: "=6.0.2", groups: [], source: nil }] end - it { is_expected.to eq(Gem::Version.new("6.0.2")) } + it { is_expected.to eq(TestVersion.new("6.0.2")) } end context "with an upper bound restriction" do @@ -321,13 +321,13 @@ def package_details [{ file: "Gemfile", requirement: ">=6.0.0,<7.0.0", groups: [], source: nil }] end - it { is_expected.to eq(Gem::Version.new("6.1.4")) } + it { is_expected.to eq(TestVersion.new("6.1.4")) } end context "when ignored versions affect the latest selection" do let(:ignored_versions) { ["7.0.0"] } - it { is_expected.to eq(Gem::Version.new("6.1.4")) } + it { is_expected.to eq(TestVersion.new("6.1.4")) } end end @@ -344,7 +344,7 @@ def package_details ] end - it { is_expected.to eq(Gem::Version.new("6.0.2")) } + it { is_expected.to eq(TestVersion.new("6.0.2")) } context "when no non-vulnerable versions exist" do let(:available_releases) do @@ -356,15 +356,18 @@ def package_details end describe "version filtering" do - subject(:filtered_versions) { finder.send(:filter_ignored_versions, versions) } + subject(:filtered_versions) { finder.send(:filter_ignored_versions, releases) } - let(:versions) { [Gem::Version.new("7.0.0"), Gem::Version.new("6.1.4"), Gem::Version.new("6.0.2")] } + let(:r1) { Dependabot::Package::PackageRelease.new(version: TestVersion.new("7.0.0")) } + let(:r2) { Dependabot::Package::PackageRelease.new(version: TestVersion.new("6.1.4")) } + let(:r3) { Dependabot::Package::PackageRelease.new(version: TestVersion.new("6.0.2")) } + let(:releases) { [r1, r2, r3] } context "when no ignored versions are specified" do let(:ignored_versions) { [] } it "returns all versions" do - expect(filtered_versions).to eq(versions) + expect(filtered_versions).to eq(releases) end end @@ -372,7 +375,7 @@ def package_details let(:ignored_versions) { ["7.0.0"] } it "removes the ignored version" do - expect(filtered_versions).to eq([Gem::Version.new("6.1.4"), Gem::Version.new("6.0.2")]) + expect(filtered_versions).to eq([r2, r3]) end end diff --git a/common/spec/spec_helper.rb b/common/spec/spec_helper.rb index 41c30586705..a13a8e38e79 100644 --- a/common/spec/spec_helper.rb +++ b/common/spec/spec_helper.rb @@ -211,3 +211,8 @@ def initialize(constraint_string) super(requirements) end end + +# Define an anonymous subclass of Dependabot::Requirement for testing purposes +TestVersion = Class.new(Dependabot::Version) do + # Initialize with a version string +end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb index 4a34257b5c6..3df06a74c80 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb @@ -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 @@ -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? @@ -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 @@ -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 @@ -172,7 +180,7 @@ 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? @@ -180,7 +188,7 @@ def fetch_lowest_security_fix_version(language_version:) # rubocop:disable Lint/ if specified_dist_tag_requirement? [version_from_dist_tags].compact else - possible_versions(filter_ignored: false) + possible_releases(filter_ignored: false) end secure_versions = @@ -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 @@ -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 @@ -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