2828module Dependabot
2929 class Job # rubocop:disable Metrics/ClassLength
3030 extend T ::Sig
31- include Dependabot ::Updater ::UpdateTypeHelper
3231
3332 TOP_LEVEL_DEPENDENCY_TYPES = T . let ( %w( direct production development ) . freeze , T ::Array [ String ] )
3433 PERMITTED_KEYS = T . let (
@@ -301,7 +300,10 @@ def reject_external_code?
301300 # was vulnerable instead of the current version. This prevents security updates
302301 # from being filtered out after the dependency has already been updated in group scenarios.
303302 #
304- # rubocop:disable Metrics/AbcSize
303+ # NOTE: update-types (semver-based filtering) is handled in ignore_conditions_for,
304+ # not here — allowed_update? runs pre-resolution when only the current version is
305+ # known, and semver-level filtering needs version ranges computed from that version.
306+ #
305307 # rubocop:disable Metrics/PerceivedComplexity
306308 # rubocop:disable Metrics/CyclomaticComplexity
307309 sig { params ( dependency : Dependency , check_previous_version : T ::Boolean ) . returns ( T ::Boolean ) }
@@ -324,15 +326,6 @@ def allowed_update?(dependency, check_previous_version: false)
324326 condition_name = update . fetch ( "dependency-name" , dependency . name )
325327 next false unless name_match? ( condition_name , dependency . name )
326328
327- # Check update-types (semver-based filtering) - security updates bypass this
328- allowed_update_types = update . fetch ( "update-types" , nil )
329- if allowed_update_types . is_a? ( Array ) && !allowed_update_types . empty? && !security_update
330- dep_update_type = update_type_for_dependency ( dependency )
331- config_type = "version-update:semver-#{ dep_update_type } " if dep_update_type
332- normalized_types = allowed_update_types . filter_map { |t | t . is_a? ( String ) ? t . downcase . strip : nil }
333- next false if config_type && !normalized_types . include? ( config_type )
334- end
335-
336329 # Check the dependency-type (defaulting to all)
337330 dep_type = update . fetch ( "dependency-type" , "all" )
338331 next false if dep_type == "indirect" &&
@@ -348,7 +341,6 @@ def allowed_update?(dependency, check_previous_version: false)
348341 true
349342 end
350343 end
351- # rubocop:enable Metrics/AbcSize
352344 # rubocop:enable Metrics/PerceivedComplexity
353345 # rubocop:enable Metrics/CyclomaticComplexity
354346
@@ -443,10 +435,16 @@ def security_advisories_for(dependency)
443435
444436 sig { params ( dependency : Dependabot ::Dependency ) . returns ( T ::Array [ String ] ) }
445437 def ignore_conditions_for ( dependency )
446- update_config . ignored_versions_for (
438+ conditions = update_config . ignored_versions_for (
447439 dependency ,
448440 security_updates_only : security_updates_only?
449441 )
442+
443+ # Supplement with implicit ignore ranges derived from allow update-types.
444+ # allow update-types cannot be checked in allowed_update? because it runs
445+ # pre-resolution when only the current version is known. Version ranges
446+ # only need the current version — the same mechanism as ignore update-types.
447+ conditions + ignored_versions_from_allowed_update_types ( dependency )
450448 end
451449
452450 # TODO: Present Dependabot::Config::IgnoreCondition in calling code
@@ -485,6 +483,101 @@ def completely_ignored?(dependency)
485483 ignore_conditions_for ( dependency ) . any? ( Dependabot ::Config ::IgnoreCondition ::ALL_VERSIONS )
486484 end
487485
486+ # Derives implicit ignore version ranges from allow rules that specify update-types.
487+ # For example, if allow says only "semver-patch", this computes ignore ranges for
488+ # major and minor — using the same mechanism as IgnoreCondition#versions_by_type.
489+ sig { params ( dependency : Dependabot ::Dependency ) . returns ( T ::Array [ String ] ) }
490+ def ignored_versions_from_allowed_update_types ( dependency )
491+ return [ ] if security_updates_only?
492+
493+ permitted_types = collect_permitted_update_types ( dependency )
494+ return [ ] if permitted_types . empty?
495+
496+ disallowed_types = Dependabot ::Updater ::UpdateTypeHelper ::ALL_SEMVER_UPDATE_TYPES - permitted_types
497+
498+ version = version_for_dependency ( dependency )
499+ return [ ] unless version
500+
501+ disallowed_types . flat_map do |t |
502+ case t
503+ when Dependabot ::Config ::IgnoreCondition ::PATCH_VERSION_TYPE
504+ version . ignored_patch_versions
505+ when Dependabot ::Config ::IgnoreCondition ::MINOR_VERSION_TYPE
506+ version . ignored_minor_versions
507+ when Dependabot ::Config ::IgnoreCondition ::MAJOR_VERSION_TYPE
508+ version . ignored_major_versions
509+ else
510+ [ ]
511+ end
512+ end . compact
513+ end
514+
515+ # Collects the union of update-types from all matching allow rules for a dependency.
516+ # Returns empty if no matching rules specify update-types (meaning no filtering needed).
517+ # If any matching rule lacks update-types, it permits all types — returns empty.
518+ sig { params ( dependency : Dependabot ::Dependency ) . returns ( T ::Array [ String ] ) }
519+ def collect_permitted_update_types ( dependency )
520+ matching_rules = matching_allow_rules ( dependency )
521+ return [ ] if matching_rules . empty?
522+
523+ # If any matching rule lacks update-types, it permits all types
524+ return [ ] if matching_rules . any? { |r | allow_rule_permits_all_types? ( r ) }
525+
526+ matching_rules
527+ . flat_map { |r | r . fetch ( "update-types" , [ ] ) }
528+ . filter_map { |t | t . is_a? ( String ) ? t . downcase . strip : nil }
529+ . select { |t | Dependabot ::Updater ::UpdateTypeHelper ::ALL_SEMVER_UPDATE_TYPES . include? ( t ) }
530+ . uniq
531+ end
532+
533+ sig { params ( dependency : Dependabot ::Dependency ) . returns ( T ::Array [ T ::Hash [ String , T . untyped ] ] ) }
534+ def matching_allow_rules ( dependency )
535+ allowed_updates . select do |update |
536+ allow_rule_matches_dependency? ( update , dependency )
537+ end
538+ end
539+
540+ sig { params ( update : T ::Hash [ String , T . untyped ] , dependency : Dependabot ::Dependency ) . returns ( T ::Boolean ) }
541+ def allow_rule_matches_dependency? ( update , dependency )
542+ condition_name = update . fetch ( "dependency-name" , nil )
543+ return false if condition_name && !name_match? ( condition_name , dependency . name )
544+
545+ dep_type = update . fetch ( "dependency-type" , nil )
546+ return true if dep_type . nil? || dep_type == "all"
547+
548+ # Indirect deps don't match top-level type rules (matching allowed_update? behavior)
549+ return false if dependency . requirements . none? && TOP_LEVEL_DEPENDENCY_TYPES . include? ( dep_type )
550+
551+ case dep_type
552+ when "production" then dependency . production?
553+ when "development" then !dependency . production?
554+ when "direct" then dependency . requirements . any?
555+ when "indirect" then dependency . requirements . none?
556+ else true
557+ end
558+ end
559+
560+ sig { params ( rule : T ::Hash [ String , T . untyped ] ) . returns ( T ::Boolean ) }
561+ def allow_rule_permits_all_types? ( rule )
562+ !rule . key? ( "update-types" ) || !rule [ "update-types" ] . is_a? ( Array ) || rule [ "update-types" ] . empty?
563+ end
564+
565+ sig { params ( dependency : Dependabot ::Dependency ) . returns ( T . nilable ( Dependabot ::Version ) ) }
566+ def version_for_dependency ( dependency )
567+ version_str = dependency . version
568+ return nil if version_str . nil? || version_str . empty?
569+
570+ version_class = begin
571+ Dependabot ::Utils . version_class_for_package_manager ( dependency . package_manager )
572+ rescue StandardError
573+ Dependabot ::Version
574+ end
575+
576+ return nil unless version_class . correct? ( version_str )
577+
578+ version_class . new ( version_str )
579+ end
580+
488581 sig { void }
489582 def register_experiments
490583 experiments . entries . each do |name , value |
0 commit comments