From 21063850cebc86953a74169ec007191d6cc3a6d0 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Thu, 14 Sep 2023 16:29:11 -0700 Subject: [PATCH] Add types to requirement classes --- bundler/lib/dependabot/bundler/requirement.rb | 6 ++- cargo/lib/dependabot/cargo/requirement.rb | 17 +++++-- common/lib/dependabot/utils.rb | 19 +++++--- common/lib/dependabot/version.rb | 7 ++- .../lib/dependabot/composer/requirement.rb | 20 +++++++-- docker/lib/dependabot/docker/requirement.rb | 6 ++- elm/lib/dependabot/elm/requirement.rb | 20 ++++++--- .../dependabot/git_submodules/requirement.rb | 6 ++- .../dependabot/github_actions/requirement.rb | 6 ++- .../lib/dependabot/go_modules/requirement.rb | 22 +++++++-- gradle/lib/dependabot/gradle/requirement.rb | 29 ++++++++---- hex/lib/dependabot/hex/requirement.rb | 19 ++++++-- maven/lib/dependabot/maven/requirement.rb | 29 ++++++++---- .../dependabot/npm_and_yarn/requirement.rb | 32 +++++++++---- pub/lib/dependabot/pub/requirement.rb | 21 +++++++-- .../python/language_version_manager.rb | 4 +- python/lib/dependabot/python/requirement.rb | 45 +++++++++++++------ .../dependabot/swift/native_requirement.rb | 5 ++- swift/lib/dependabot/swift/requirement.rb | 6 ++- .../lib/dependabot/terraform/requirement.rb | 11 +++-- 20 files changed, 252 insertions(+), 78 deletions(-) diff --git a/bundler/lib/dependabot/bundler/requirement.rb b/bundler/lib/dependabot/bundler/requirement.rb index bc0ff04a1cf3..503e15030464 100644 --- a/bundler/lib/dependabot/bundler/requirement.rb +++ b/bundler/lib/dependabot/bundler/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "dependabot/utils" @@ -6,15 +6,19 @@ module Dependabot module Bundler class Requirement < Gem::Requirement + extend T::Sig + # For consistency with other languages, we define a requirements array. # Ruby doesn't have an `OR` separator for requirements, so it always # contains a single element. + sig { params(requirement_string: String).returns(T::Array[Dependabot::Bundler::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip) diff --git a/cargo/lib/dependabot/cargo/requirement.rb b/cargo/lib/dependabot/cargo/requirement.rb index 35e073334d9e..554098a6db92 100644 --- a/cargo/lib/dependabot/cargo/requirement.rb +++ b/cargo/lib/dependabot/cargo/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true ################################################################################ @@ -7,20 +7,25 @@ # - https://steveklabnik.github.io/semver/semver/index.html # ################################################################################ +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/cargo/version" module Dependabot module Cargo class Requirement < Gem::Requirement + extend T::Sig + quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|") version_pattern = Cargo::Version::VERSION_PATTERN - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ # Use Cargo::Version rather than Gem::Version to ensure that # pre-release versions aren't transformed. + sig { override.params(obj: Object).returns([String, Dependabot::Cargo::Version]) } def self.parse(obj) return ["=", Cargo::Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -37,10 +42,12 @@ def self.parse(obj) # For consistency with other languages, we define a requirements array. # Rust doesn't have an `OR` separator for requirements, so it always # contains a single element. + sig { params(requirement_string: String).returns(T::Array[Dependabot::Cargo::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip).map do |r| @@ -53,6 +60,7 @@ def initialize(*requirements) private + sig { params(req_string: String).returns(T.any(T::Array[String], String)) } def convert_rust_constraint_to_ruby_constraint(req_string) if req_string.include?("*") ruby_range(req_string.gsub(/(?:\.|^)[*]/, "").gsub(/^[^\d]/, "")) @@ -64,6 +72,7 @@ def convert_rust_constraint_to_ruby_constraint(req_string) end end + sig { params(req_string: String).returns(String) } def convert_tilde_req(req_string) version = req_string.gsub(/^~/, "") parts = version.split(".") @@ -71,6 +80,7 @@ def convert_tilde_req(req_string) "~> #{parts.join('.')}" end + sig { params(req_string: String).returns(String) } def ruby_range(req_string) parts = req_string.split(".") @@ -85,6 +95,7 @@ def ruby_range(req_string) "~> #{parts.join('.')}" end + sig { params(req_string: String).returns(T::Array[String]) } def convert_caret_req(req_string) version = req_string.gsub(/^\^/, "") parts = version.split(".") @@ -92,7 +103,7 @@ def convert_caret_req(req_string) first_non_zero_index = first_non_zero ? parts.index(first_non_zero) : parts.count - 1 upper_bound = parts.map.with_index do |part, i| - if i < first_non_zero_index then part + if i < T.unsafe(first_non_zero_index) then part elsif i == first_non_zero_index then (part.to_i + 1).to_s else 0 diff --git a/common/lib/dependabot/utils.rb b/common/lib/dependabot/utils.rb index c57a42ed54a3..d1a06eecdfbc 100644 --- a/common/lib/dependabot/utils.rb +++ b/common/lib/dependabot/utils.rb @@ -1,18 +1,22 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "tmpdir" require "set" +require "sorbet-runtime" # TODO: in due course, these "registries" should live in a wrapper gem, not # dependabot-core. module Dependabot module Utils + extend T::Sig + BUMP_TMP_FILE_PREFIX = "dependabot_" - BUMP_TMP_DIR_PATH = File.expand_path(Dir::Tmpname.create("", "tmp") { nil }) + BUMP_TMP_DIR_PATH = T.let(File.expand_path(Dir::Tmpname.create("", "tmp") { nil }), String) - @version_classes = {} + @version_classes = T.let({}, T::Hash[String, T.class_of(Dependabot::Version)]) + sig { params(package_manager: String).returns(T.class_of(Dependabot::Version)) } def self.version_class_for_package_manager(package_manager) version_class = @version_classes[package_manager] return version_class if version_class @@ -20,12 +24,14 @@ def self.version_class_for_package_manager(package_manager) raise "Unsupported package_manager #{package_manager}" end + sig { params(package_manager: String, version_class: T.class_of(Dependabot::Version)).void } def self.register_version_class(package_manager, version_class) @version_classes[package_manager] = version_class end - @requirement_classes = {} + @requirement_classes = T.let({}, T::Hash[String, T.class_of(Gem::Requirement)]) + sig { params(package_manager: String).returns(T.class_of(Gem::Requirement)) } def self.requirement_class_for_package_manager(package_manager) requirement_class = @requirement_classes[package_manager] return requirement_class if requirement_class @@ -33,16 +39,19 @@ def self.requirement_class_for_package_manager(package_manager) raise "Unsupported package_manager #{package_manager}" end + sig { params(package_manager: String, requirement_class: T.class_of(Gem::Requirement)).void } def self.register_requirement_class(package_manager, requirement_class) @requirement_classes[package_manager] = requirement_class end - @cloning_package_managers = Set[] + @cloning_package_managers = T.let(Set[], T::Set[String]) + sig { params(package_manager: String).returns(T::Boolean) } def self.always_clone_for_package_manager?(package_manager) @cloning_package_managers.include?(package_manager) end + sig { params(package_manager: String).void } def self.register_always_clone(package_manager) @cloning_package_managers << package_manager end diff --git a/common/lib/dependabot/version.rb b/common/lib/dependabot/version.rb index a9b278c7d7f3..471ffb302056 100644 --- a/common/lib/dependabot/version.rb +++ b/common/lib/dependabot/version.rb @@ -1,8 +1,11 @@ -# typed: true +# typed: strong # frozen_string_literal: true module Dependabot class Version < Gem::Version + extend T::Sig + + sig { override.params(version: String).void } def initialize(version) @original_version = version @@ -10,12 +13,14 @@ def initialize(version) end # Opt-in to Rubygems 4 behavior + sig { override.params(version: Object).returns(T::Boolean) } def self.correct?(version) return false if version.nil? version.to_s.match?(ANCHORED_VERSION_PATTERN) end + sig { returns(String) } def to_semver @original_version end diff --git a/composer/lib/dependabot/composer/requirement.rb b/composer/lib/dependabot/composer/requirement.rb index ae6eeeff56e6..29890b39eb97 100644 --- a/composer/lib/dependabot/composer/requirement.rb +++ b/composer/lib/dependabot/composer/requirement.rb @@ -1,14 +1,19 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/utils" module Dependabot module Composer class Requirement < Gem::Requirement + extend T::Sig + AND_SEPARATOR = /(?<=[a-zA-Z0-9*])(?", "<") msg = "Illformed requirement [#{req_string.inspect}]" @@ -63,11 +72,13 @@ def convert_wildcard_req(req_string) "~> #{version}.0" end + sig { params(req_string: String).returns(String) } def convert_tilde_req(req_string) version = req_string.gsub(/^~/, "") "~> #{version}" end + sig { params(req_string: String).returns(T::Array[String]) } def convert_caret_req(req_string) version = req_string.gsub(/^\^/, "").gsub("x-dev", "0") parts = version.split(".") @@ -75,7 +86,7 @@ def convert_caret_req(req_string) first_non_zero_index = first_non_zero ? parts.index(first_non_zero) : parts.count - 1 upper_bound = parts.map.with_index do |part, i| - if i < first_non_zero_index then part + if i < T.unsafe(first_non_zero_index) then part elsif i == first_non_zero_index then (part.to_i + 1).to_s else 0 @@ -85,10 +96,11 @@ def convert_caret_req(req_string) [">= #{version}", "< #{upper_bound}"] end + sig { params(req_string: String).returns(T::Array[String]) } def convert_hyphen_req(req_string) lower_bound, upper_bound = req_string.split(/\s+-\s+/) - if upper_bound.split(".").count < 3 - upper_bound_parts = upper_bound.split(".") + if T.unsafe(upper_bound).split(".").count < 3 + upper_bound_parts = T.unsafe(upper_bound).split(".") upper_bound_parts[-1] = (upper_bound_parts[-1].to_i + 1).to_s upper_bound = upper_bound_parts.join(".") diff --git a/docker/lib/dependabot/docker/requirement.rb b/docker/lib/dependabot/docker/requirement.rb index 9fc1ea0d2462..fa5751f84391 100644 --- a/docker/lib/dependabot/docker/requirement.rb +++ b/docker/lib/dependabot/docker/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "dependabot/utils" @@ -7,19 +7,23 @@ module Dependabot module Docker # Lifted from the bundler package manager class Requirement < Gem::Requirement + extend T::Sig # For consistency with other languages, we define a requirements array. # Ruby doesn't have an `OR` separator for requirements, so it always # contains a single element. + sig { params(requirement_string: String).returns([Dependabot::Docker::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end + sig { override.params(version: Dependabot::Docker::Version).returns(T::Boolean) } def satisfied_by?(version) super(version.release_part) end # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip) diff --git a/elm/lib/dependabot/elm/requirement.rb b/elm/lib/dependabot/elm/requirement.rb index 50e2f0f7b51f..a9443f669967 100644 --- a/elm/lib/dependabot/elm/requirement.rb +++ b/elm/lib/dependabot/elm/requirement.rb @@ -1,24 +1,32 @@ -# typed: false +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/elm/version" module Dependabot module Elm class Requirement < Gem::Requirement + extend T::Sig + ELM_PATTERN_RAW = - "(#{Elm::Version::VERSION_PATTERN}) (<=?) v (<=?) " \ - "(#{Elm::Version::VERSION_PATTERN})".freeze + T.let( + "(#{Elm::Version::VERSION_PATTERN}) (<=?) v (<=?) (#{Elm::Version::VERSION_PATTERN})".freeze, + String + ) ELM_PATTERN = /\A#{ELM_PATTERN_RAW}\z/ ELM_EXACT_PATTERN = /\A#{Elm::Version::VERSION_PATTERN}\z/ # Returns an array of requirements. At least one requirement from the # returned array must be satisfied for a version to be valid. + sig { params(requirement_string: String).returns(T::Array[Dependabot::Elm::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end + sig { override.params(requirements: T.nilable(String)).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| raise BadRequirementError, "Nil requirement not supported in Elm" if req_string.nil? @@ -31,6 +39,7 @@ def initialize(*requirements) super(requirements) end + sig { override.params(version: T.any(String, Dependabot::Elm::Version)).returns(T::Boolean) } def satisfied_by?(version) version = Elm::Version.new(version.to_s) super @@ -40,6 +49,7 @@ def satisfied_by?(version) # Override the parser to create Elm::Versions and return an # array of parsed requirements + sig { params(obj: String).returns(T.any(String, T::Array[String])) } def convert_elm_constraint_to_ruby_constraint(obj) # If a version is given this is an equals requirement return obj if ELM_EXACT_PATTERN.match?(obj.to_s) @@ -48,10 +58,10 @@ def convert_elm_constraint_to_ruby_constraint(obj) # If the two versions specified are identical this is an equals # requirement - return matches[4] if matches[1] == matches[4] && matches[3] == "<=" + return T.unsafe(matches[4]) if matches[1] == matches[4] && matches[3] == "<=" [ - [matches[2].tr("<", ">"), matches[1]].join(" "), + [T.unsafe(matches[2]).tr("<", ">"), matches[1]].join(" "), [matches[3], matches[4]].join(" ") ] end diff --git a/git_submodules/lib/dependabot/git_submodules/requirement.rb b/git_submodules/lib/dependabot/git_submodules/requirement.rb index 1b19baa8df40..2c428f82b1ab 100644 --- a/git_submodules/lib/dependabot/git_submodules/requirement.rb +++ b/git_submodules/lib/dependabot/git_submodules/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "dependabot/utils" @@ -6,15 +6,19 @@ module Dependabot module GitSubmodules class Requirement < Gem::Requirement + extend T::Sig + # For consistency with other languages, we define a requirements array. # Ruby doesn't have an `OR` separator for requirements, so it always # contains a single element. + sig { params(requirement_string: String).returns(T::Array[Dependabot::GitSubmodules::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip) diff --git a/github_actions/lib/dependabot/github_actions/requirement.rb b/github_actions/lib/dependabot/github_actions/requirement.rb index f666f032a280..bf7759cea439 100644 --- a/github_actions/lib/dependabot/github_actions/requirement.rb +++ b/github_actions/lib/dependabot/github_actions/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "dependabot/utils" @@ -8,15 +8,19 @@ module Dependabot module GithubActions # Lifted from the bundler package manager class Requirement < Gem::Requirement + extend T::Sig + # For consistency with other languages, we define a requirements array. # Ruby doesn't have an `OR` separator for requirements, so it always # contains a single element. + sig { params(requirement_string: String).returns(T::Array[Dependabot::GithubActions::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip) diff --git a/go_modules/lib/dependabot/go_modules/requirement.rb b/go_modules/lib/dependabot/go_modules/requirement.rb index d79125bd4a93..900925fd3b8e 100644 --- a/go_modules/lib/dependabot/go_modules/requirement.rb +++ b/go_modules/lib/dependabot/go_modules/requirement.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: strict # frozen_string_literal: true ################################################################################ @@ -7,12 +7,16 @@ # - https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # ################################################################################ +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/go_modules/version" module Dependabot module GoModules class Requirement < Gem::Requirement + extend T::Sig + WILDCARD_REGEX = /(?:\.|^)[xX*]/ OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|{2}/ @@ -20,11 +24,12 @@ class Requirement < Gem::Requirement quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|") version_pattern = "v?#{Version::VERSION_PATTERN}" - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ # Use GoModules::Version rather than Gem::Version to ensure that # pre-release versions aren't transformed. + sig { params(obj: Object).returns(T::Array[T.any(String, Dependabot::GoModules::Version)]) } def self.parse(obj) return ["=", Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -40,6 +45,7 @@ def self.parse(obj) # Returns an array of requirements. At least one requirement from the # returned array must be satisfied for a version to be valid. + sig { params(requirement_string: String).returns(T::Array[Dependabot::GoModules::Requirement]) } def self.requirements_array(requirement_string) return [new(nil)] if requirement_string.nil? @@ -48,9 +54,10 @@ def self.requirements_array(requirement_string) end end + sig { override.params(requirements: T.nilable(String)).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| - req_string.split(",").map(&:strip).map do |r| + T.unsafe(req_string).split(",").map(&:strip).map do |r| convert_go_constraint_to_ruby_constraint(r.strip) end end @@ -60,6 +67,7 @@ def initialize(*requirements) private + sig { params(req_string: String).returns(T.any(T::Array[String], String)) } def convert_go_constraint_to_ruby_constraint(req_string) req_string = convert_wildcard_characters(req_string) @@ -74,6 +82,7 @@ def convert_go_constraint_to_ruby_constraint(req_string) end end + sig { params(req_string: String).returns(String) } def convert_wildcard_characters(req_string) if req_string.match?(/^[\dv^>~]/) replace_wildcard_in_lower_bound(req_string) @@ -90,8 +99,9 @@ def convert_wildcard_characters(req_string) end end + sig { params(req_string: String).returns(String) } def replace_wildcard_in_lower_bound(req_string) - after_wildcard = false + after_wildcard = T.let(false, T::Boolean) req_string = req_string.gsub(/(?:(?:\.|^)[xX*])(\.[xX*])+/, "") if req_string.start_with?("~") @@ -108,6 +118,7 @@ def replace_wildcard_in_lower_bound(req_string) end.join(".") end + sig { params(req_string: String).returns(String) } def convert_tilde_req(req_string) version = req_string.gsub(/^~/, "") parts = version.split(".") @@ -115,11 +126,13 @@ def convert_tilde_req(req_string) "~> #{parts.join('.')}" end + sig { params(req_string: String).returns(T::Array[String]) } def convert_hyphen_req(req_string) lower_bound, upper_bound = req_string.split(/\s+-\s+/) [">= #{lower_bound}", "<= #{upper_bound}"] end + sig { params(req_string: String).returns(String) } def ruby_range(req_string) parts = req_string.split(".") @@ -136,6 +149,7 @@ def ruby_range(req_string) # NOTE: Dep's caret notation implementation doesn't distinguish between # pre and post-1.0.0 requirements (unlike in JS) + sig { params(req_string: String).returns(T::Array[String]) } def convert_caret_req(req_string) version = req_string.gsub(/^\^?v?/, "") parts = version.split(".") diff --git a/gradle/lib/dependabot/gradle/requirement.rb b/gradle/lib/dependabot/gradle/requirement.rb index f446ba3fa17c..8c9c9ef91d96 100644 --- a/gradle/lib/dependabot/gradle/requirement.rb +++ b/gradle/lib/dependabot/gradle/requirement.rb @@ -1,6 +1,8 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/maven/requirement" require "dependabot/gradle/version" @@ -8,10 +10,13 @@ module Dependabot module Gradle class Requirement < Gem::Requirement + extend T::Sig + quoted = OPS.keys.map { |k| Regexp.quote k }.join("|") - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gradle::Version::VERSION_PATTERN})\\s*".freeze + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{Gradle::Version::VERSION_PATTERN})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ + sig { override.params(obj: Object).returns(T::Array[T.any(String, Dependabot::Gradle::Version)]) } def self.parse(obj) return ["=", Gradle::Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -25,12 +30,14 @@ def self.parse(obj) [matches[1] || "=", Gradle::Version.new(matches[2])] end + sig { params(requirement_string: String).returns(T::Array[Dependabot::Gradle::Requirement]) } def self.requirements_array(requirement_string) split_java_requirement(requirement_string).map do |str| new(str) end end + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| convert_java_constraint_to_ruby_constraint(req_string) @@ -39,6 +46,7 @@ def initialize(*requirements) super(requirements) end + sig { override.params(version: T.any(String, Dependabot::Gradle::Version)).returns(T::Boolean) } def satisfied_by?(version) version = Gradle::Version.new(version.to_s) super @@ -46,6 +54,7 @@ def satisfied_by?(version) private + sig { params(req_string: String).returns(T::Array[String]) } def self.split_java_requirement(req_string) return [req_string] unless req_string.match?(Maven::Requirement::OR_SYNTAX) @@ -53,11 +62,12 @@ def self.split_java_requirement(req_string) next str if str.start_with?("(", "[") exacts, *rest = str.split(/,(?=\[|\()/) - [*exacts.split(","), *rest] + [*exacts&.split(","), *rest] end end private_class_method :split_java_requirement + sig { params(req_string: T.nilable(String)).returns(T.nilable(T.any(String, T::Array[T.nilable(String)]))) } def convert_java_constraint_to_ruby_constraint(req_string) return unless req_string @@ -75,26 +85,28 @@ def convert_java_constraint_to_ruby_constraint(req_string) end end - def convert_java_range_to_ruby_range(req_string) + sig { params(req_string: String).returns(T.any(T::Array[String], String)) } + def convert_java_range_to_ruby_range(req_string) # rubocop:disable Metrics/PerceivedComplexity lower_b, upper_b = req_string.split(",").map(&:strip) lower_b = if ["(", "["].include?(lower_b) then nil - elsif lower_b.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}" + elsif lower_b&.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}" else - ">= #{lower_b.sub(/\[\s*/, '').strip}" + ">= #{lower_b&.sub(/\[\s*/, '')&.strip}" end upper_b = if [")", "]"].include?(upper_b) then nil - elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}" + elsif upper_b&.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}" else - "<= #{upper_b.sub(/\s*\]/, '').strip}" + "<= #{upper_b&.sub(/\s*\]/, '')&.strip}" end [lower_b, upper_b].compact end + sig { params(req_string: T.nilable(String)).returns(T.nilable(String)) } def convert_java_equals_req_to_ruby(req_string) return convert_wildcard_req(req_string) if req_string&.include?("+") @@ -104,6 +116,7 @@ def convert_java_equals_req_to_ruby(req_string) req_string.gsub(/[\[\]\(\)]/, "") end + sig { params(req_string: String).returns(String) } def convert_wildcard_req(req_string) version = req_string.split("+").first return ">= 0" if version.nil? || version.empty? diff --git a/hex/lib/dependabot/hex/requirement.rb b/hex/lib/dependabot/hex/requirement.rb index 475c8dfa066a..30e1affdb12e 100644 --- a/hex/lib/dependabot/hex/requirement.rb +++ b/hex/lib/dependabot/hex/requirement.rb @@ -1,34 +1,43 @@ # typed: true # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/hex/version" module Dependabot module Hex class Requirement < Gem::Requirement + extend T::Sig + AND_SEPARATOR = /\s+and\s+/ OR_SEPARATOR = /\s+or\s+/ # Add the double-equality matcher to the list of allowed operations - OPS = OPS.merge("==" => ->(v, r) { v == r }) + OPS = T.let( + OPS.merge("==" => ->(v, r) { v == r }), + T::Hash[String, T.proc.params(v: Hex::Version, r: Hex::Version).returns(T::Boolean)] + ) # Override the version pattern to allow local versions quoted = OPS.keys.map { |k| Regexp.quote k }.join "|" - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Hex::Version::VERSION_PATTERN})\\s*".freeze + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{Hex::Version::VERSION_PATTERN})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ # Returns an array of requirements. At least one requirement from the # returned array must be satisfied for a version to be valid. + sig { params(requirement_string: String).returns(T::Array[Dependabot::Hex::Requirement]) } def self.requirements_array(requirement_string) requirement_string.strip.split(OR_SEPARATOR).map do |req_string| requirements = req_string.strip.split(AND_SEPARATOR) - new(requirements) + new(T.unsafe(requirements)) end end # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip) @@ -38,6 +47,7 @@ def initialize(*requirements) end # Override the parser to create Hex::Versions + sig { override.params(obj: Object).returns([String, Dependabot::Hex::Version]) } def self.parse(obj) return ["=", Hex::Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -51,10 +61,11 @@ def self.parse(obj) [matches[1] || "=", Hex::Version.new(matches[2])] end + sig { override.params(version: T.any(String, Dependabot::Hex::Version)).returns(T::Boolean) } def satisfied_by?(version) version = Hex::Version.new(version.to_s) - requirements.all? { |op, rv| (OPS[op] || OPS["="]).call(version, rv) } + requirements.all? { |op, rv| T.unsafe(OPS[op] || OPS["="]).call(version, rv) } end end end diff --git a/maven/lib/dependabot/maven/requirement.rb b/maven/lib/dependabot/maven/requirement.rb index 6d6e8ef87c38..236b8b4cf6a4 100644 --- a/maven/lib/dependabot/maven/requirement.rb +++ b/maven/lib/dependabot/maven/requirement.rb @@ -1,17 +1,22 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/maven/version" module Dependabot module Maven class Requirement < Gem::Requirement + extend T::Sig + quoted = OPS.keys.map { |k| Regexp.quote k }.join("|") OR_SYNTAX = /(?<=\]|\)),/ - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Maven::Version::VERSION_PATTERN})\\s*".freeze + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{Maven::Version::VERSION_PATTERN})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ + sig { override.params(obj: Object).returns([String, Dependabot::Maven::Version]) } def self.parse(obj) return ["=", Maven::Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -25,12 +30,14 @@ def self.parse(obj) [matches[1] || "=", Maven::Version.new(matches[2])] end + sig { params(requirement_string: String).returns(T::Array[Dependabot::Maven::Requirement]) } def self.requirements_array(requirement_string) split_java_requirement(requirement_string).map do |str| new(str) end end + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| convert_java_constraint_to_ruby_constraint(req_string) @@ -39,6 +46,7 @@ def initialize(*requirements) super(requirements) end + sig { override.params(version: T.any(String, Dependabot::Maven::Version)).returns(T::Boolean) } def satisfied_by?(version) version = Maven::Version.new(version.to_s) super @@ -46,6 +54,7 @@ def satisfied_by?(version) private + sig { params(req_string: String).returns(T::Array[String]) } def self.split_java_requirement(req_string) return [req_string] unless req_string.match?(OR_SYNTAX) @@ -53,11 +62,12 @@ def self.split_java_requirement(req_string) next str if str.start_with?("(", "[") exacts, *rest = str.split(/,(?=\[|\()/) - [*exacts.split(","), *rest] + [*T.unsafe(exacts).split(","), *rest] end end private_class_method :split_java_requirement + sig { params(req_string: T.nilable(String)).returns(T.nilable(T::Array[T.nilable(String)])) } def convert_java_constraint_to_ruby_constraint(req_string) return unless req_string @@ -75,26 +85,28 @@ def convert_java_constraint_to_ruby_constraint(req_string) end end - def convert_java_range_to_ruby_range(req_string) + sig { params(req_string: String).returns(T::Array[String]) } + def convert_java_range_to_ruby_range(req_string) # rubocop:disable Metrics/PerceivedComplexity lower_b, upper_b = req_string.split(",").map(&:strip) lower_b = if ["(", "["].include?(lower_b) then nil - elsif lower_b.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}" + elsif lower_b&.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}" else - ">= #{lower_b.sub(/\[\s*/, '').strip}" + ">= #{lower_b&.sub(/\[\s*/, '')&.strip}" end upper_b = if [")", "]"].include?(upper_b) then nil - elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}" + elsif upper_b&.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}" else - "<= #{upper_b.sub(/\s*\]/, '').strip}" + "<= #{upper_b&.sub(/\s*\]/, '')&.strip}" end [lower_b, upper_b].compact end + sig { params(req_string: T.nilable(String)).returns(T.nilable(String)) } def convert_java_equals_req_to_ruby(req_string) return convert_wildcard_req(req_string) if req_string&.end_with?("+") @@ -104,6 +116,7 @@ def convert_java_equals_req_to_ruby(req_string) req_string.gsub(/[\[\]\(\)]/, "") end + sig { params(req_string: String).returns(String) } def convert_wildcard_req(req_string) version = req_string.split("+").first return ">= 0" if version.nil? || version.empty? diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/requirement.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/requirement.rb index dced005dd1b5..84e0deb60407 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/requirement.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/requirement.rb @@ -1,12 +1,16 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/npm_and_yarn/version" module Dependabot module NpmAndYarn class Requirement < Gem::Requirement + extend T::Sig + AND_SEPARATOR = /(?<=[a-zA-Z0-9*])\s+(?:&+\s+)?(?!\s*[|-])/ OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/ LATEST_REQUIREMENT = "latest" @@ -15,9 +19,14 @@ class Requirement < Gem::Requirement quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|") version_pattern = "v?#{NpmAndYarn::Version::VERSION_PATTERN}" - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ + sig do + override + .params(obj: Object) + .returns(T.any([String, NpmAndYarn::Version], [String, NilClass])) + end def self.parse(obj) return ["=", nil] if obj.is_a?(String) && obj.strip == LATEST_REQUIREMENT return ["=", NpmAndYarn::Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -34,6 +43,7 @@ def self.parse(obj) # Returns an array of requirements. At least one requirement from the # returned array must be satisfied for a version to be valid. + sig { params(requirement_string: String).returns(T::Array[Dependabot::NpmAndYarn::Requirement]) } def self.requirements_array(requirement_string) return [new(nil)] if requirement_string.nil? @@ -47,9 +57,10 @@ def self.requirements_array(requirement_string) end end + sig { override.params(requirements: T.any(NilClass, T::Array[String])).void } def initialize(*requirements) requirements = requirements.flatten - .flat_map { |req_string| req_string.split(",").map(&:strip) } + .flat_map { |req_string| T.unsafe(req_string).split(",").map(&:strip) } .flat_map { |req_string| convert_js_constraint_to_ruby_constraint(req_string) } super(requirements) @@ -57,6 +68,7 @@ def initialize(*requirements) private + sig { params(req_string: String).returns(T.any(T::Array[String], String)) } def convert_js_constraint_to_ruby_constraint(req_string) return req_string if req_string.match?(/^([A-Za-uw-z]|v[^\d])/) @@ -74,6 +86,7 @@ def convert_js_constraint_to_ruby_constraint(req_string) end end + sig { params(req_string: String).returns(String) } def convert_tilde_req(req_string) version = req_string.gsub(/^~\>?[\s=]*/, "") parts = version.split(".") @@ -81,12 +94,13 @@ def convert_tilde_req(req_string) "~> #{parts.join('.')}" end + sig { params(req_string: String).returns([String, String]) } def convert_hyphen_req(req_string) lower_bound, upper_bound = req_string.split(/\s+-\s+/) - lower_bound_parts = lower_bound.split(".") + lower_bound_parts = T.unsafe(lower_bound).split(".") lower_bound_parts.fill("0", lower_bound_parts.length...3) - upper_bound_parts = upper_bound.split(".") + upper_bound_parts = T.unsafe(upper_bound).split(".") upper_bound_range = if upper_bound_parts.length < 3 # When upper bound is a partial version treat these as an X-range @@ -100,6 +114,7 @@ def convert_hyphen_req(req_string) [">= #{lower_bound_parts.join('.')}", upper_bound_range] end + sig { params(req_string: String).returns(String) } def ruby_range(req_string) parts = req_string.split(".") # If we have three or more parts then this is an exact match @@ -110,6 +125,7 @@ def ruby_range(req_string) "~> #{parts.join('.')}" end + sig { params(req_string: String).returns(T::Array[String]) } def convert_caret_req(req_string) version = req_string.gsub(/^\^[\s=]*/, "") parts = version.split(".") @@ -119,11 +135,11 @@ def convert_caret_req(req_string) first_non_zero ? parts.index(first_non_zero) : parts.count - 1 # If the requirement has a blank minor or patch version increment the # previous index value with 1 - first_non_zero_index -= 1 if first_non_zero == "x" + first_non_zero_index = T.unsafe(first_non_zero_index) - 1 if first_non_zero == "x" upper_bound = parts.map.with_index do |part, i| - if i < first_non_zero_index then part + if i < T.unsafe(first_non_zero_index) then part elsif i == first_non_zero_index then (part.to_i + 1).to_s - elsif i > first_non_zero_index && i == 2 then "0.a" + elsif i > T.unsafe(first_non_zero_index) && i == 2 then "0.a" else 0 end diff --git a/pub/lib/dependabot/pub/requirement.rb b/pub/lib/dependabot/pub/requirement.rb index 9e61e8c18830..5d9fde33aac9 100644 --- a/pub/lib/dependabot/pub/requirement.rb +++ b/pub/lib/dependabot/pub/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true # For details on pub version constraints see: @@ -6,20 +6,25 @@ ################################################################### +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/pub/version" module Dependabot module Pub class Requirement < Gem::Requirement + extend T::Sig + quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|") version_pattern = Pub::Version::VERSION_PATTERN - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ # Use Pub::Version rather than Gem::Version to ensure that # pre-release versions aren't transformed. + sig { params(obj: Object).returns([String, Dependabot::Pub::Version]) } def self.parse(obj) return ["=", Pub::Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -36,10 +41,12 @@ def self.parse(obj) # For consistency with other languages, we define a requirements array. # Dart doesn't have an `OR` separator for requirements, so it always # contains a single element. + sig { params(requirement_string: String).returns(T::Array[Dependabot::Pub::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end + sig { params(requirements: String, raw_constraint: T.nilable(String)).void } def initialize(*requirements, raw_constraint: nil) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip).map do |r| @@ -51,6 +58,7 @@ def initialize(*requirements, raw_constraint: nil) @raw_constraint = raw_constraint end + sig { override.returns(String) } def to_s if @raw_constraint.nil? as_list.join " " @@ -61,6 +69,7 @@ def to_s private + sig { params(req_string: String).returns(T.any(T::Array[String], String)) } def convert_dart_constraint_to_ruby_constraint(req_string) if req_string.empty? || req_string == "any" then ">= 0" elsif req_string.match?(/^~[^>]/) then convert_tilde_req(req_string) @@ -71,18 +80,21 @@ def convert_dart_constraint_to_ruby_constraint(req_string) end end + sig { params(req_string: String).returns(String) } def convert_tilde_req(req_string) version = req_string.gsub(/^~/, "") parts = version.split(".") "~> #{parts.join('.')}" end + sig { params(req_string: String).returns(T::Array[String]) } def convert_range_req(req_string) req_string.scan( /((?:>|<|=|<=|>=)\s*#{Pub::Version::VERSION_PATTERN})\s*/o - ).map { |x| x[0].strip } + ).map { |x| T.unsafe(x[0]).strip } end + sig { params(req_string: String).returns(String) } def ruby_range(req_string) parts = req_string.split(".") @@ -97,6 +109,7 @@ def ruby_range(req_string) "~> #{parts.join('.')}" end + sig { params(req_string: String).returns(T::Array[String]) } def convert_caret_req(req_string) # Copied from Cargo::Requirement which allows less than 3 components # so we could be more strict in the parsing here. @@ -106,7 +119,7 @@ def convert_caret_req(req_string) first_non_zero_index = first_non_zero ? parts.index(first_non_zero) : parts.count - 1 upper_bound = parts.map.with_index do |part, i| - if i < first_non_zero_index then part + if i < T.unsafe(first_non_zero_index) then part elsif i == first_non_zero_index then (part.to_i + 1).to_s else 0 diff --git a/python/lib/dependabot/python/language_version_manager.rb b/python/lib/dependabot/python/language_version_manager.rb index 0739f08e798e..8b935a63ab1c 100644 --- a/python/lib/dependabot/python/language_version_manager.rb +++ b/python/lib/dependabot/python/language_version_manager.rb @@ -1,6 +1,8 @@ # typed: true # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/logger" require "dependabot/python/version" @@ -58,7 +60,7 @@ def python_version_from_supported_versions # Try to match one of our pre-installed Python versions requirement = Python::Requirement.requirements_array(requirement_string).first - version = PRE_INSTALLED_PYTHON_VERSIONS.find { |v| requirement.satisfied_by?(Python::Version.new(v)) } + version = PRE_INSTALLED_PYTHON_VERSIONS.find { |v| T.unsafe(requirement).satisfied_by?(Python::Version.new(v)) } return version if version # Otherwise we have to raise diff --git a/python/lib/dependabot/python/requirement.rb b/python/lib/dependabot/python/requirement.rb index d1903cbe725d..b7cfe7791171 100644 --- a/python/lib/dependabot/python/requirement.rb +++ b/python/lib/dependabot/python/requirement.rb @@ -1,28 +1,36 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/utils" require "dependabot/python/version" module Dependabot module Python class Requirement < Gem::Requirement + extend T::Sig + OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s*\|+/ # Add equality and arbitrary-equality matchers - OPS = OPS.merge( - "==" => ->(v, r) { v == r }, - "===" => ->(v, r) { v.to_s == r.to_s } + OPS = T.let( + OPS.merge( + "==" => ->(v, r) { v == r }, + "===" => ->(v, r) { v.to_s == r.to_s } + ), + T::Hash[String, T.proc.params(v: Python::Version, r: Python::Version).returns(T::Boolean)] ) quoted = OPS.keys.sort_by(&:length).reverse .map { |k| Regexp.quote(k) }.join("|") version_pattern = Python::Version::VERSION_PATTERN - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ PARENS_PATTERN = /\A\(([^)]+)\)\z/ + sig { override.params(obj: Object).returns([String, Python::Version]) } def self.parse(obj) return ["=", Python::Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -45,6 +53,7 @@ def self.parse(obj) # returned array must be satisfied for a version to be valid. # # NOTE: Or requirements are only valid for Poetry. + sig { params(requirement_string: T.nilable(String)).returns(T::Array[Dependabot::Python::Requirement]) } def self.requirements_array(requirement_string) return [new(nil)] if requirement_string.nil? @@ -52,11 +61,12 @@ def self.requirements_array(requirement_string) requirement_string = matches[1] end - requirement_string.strip.split(OR_SEPARATOR).map do |req_string| + T.unsafe(requirement_string).strip.split(OR_SEPARATOR).map do |req_string| new(req_string.strip) end end + sig { params(requirements: T.nilable(String)).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| next if req_string.nil? @@ -72,20 +82,23 @@ def initialize(*requirements) super(requirements) end + sig { params(version: T.any(String, Python::Version)).returns(T::Boolean) } def satisfied_by?(version) version = Python::Version.new(version.to_s) - requirements.all? { |op, rv| (OPS[op] || OPS["="]).call(version, rv) } + requirements.all? { |op, rv| T.unsafe(OPS[op] || OPS["="]).call(version, rv) } end + sig { returns(T::Boolean) } def exact? - return false unless @requirements.size == 1 + return false unless requirements.size == 1 - %w(= == ===).include?(@requirements[0][0]) + %w(= == ===).include?(requirements[0][0]) end private + sig { params(req_string: String).returns(T.nilable(T.any(String, T::Array[String]))) } def convert_python_constraint_to_ruby_constraint(req_string) return nil if req_string.nil? return nil if req_string == "*" @@ -103,6 +116,7 @@ def convert_python_constraint_to_ruby_constraint(req_string) # Poetry uses ~ requirements. # https://github.com/sdispater/poetry#tilde-requirements + sig { params(req_string: String).returns(T.nilable(String)) } def convert_tilde_req(req_string) version = req_string.gsub(/^~\>?/, "") parts = version.split(".") @@ -112,17 +126,18 @@ def convert_tilde_req(req_string) # Poetry uses ^ requirements # https://github.com/sdispater/poetry#caret-requirement + sig { params(req_string: String).returns([String, String]) } def convert_caret_req(req_string) version = req_string.gsub(/^\^/, "") parts = version.split(".") - parts.fill(0, parts.length...3) + parts.fill("0", parts.length...3) first_non_zero = parts.find { |d| d != "0" } first_non_zero_index = first_non_zero ? parts.index(first_non_zero) : parts.count - 1 upper_bound = parts.map.with_index do |part, i| - if i < first_non_zero_index then part + if i < T.unsafe(first_non_zero_index) then part elsif i == first_non_zero_index then (part.to_i + 1).to_s - elsif i > first_non_zero_index && i == 2 then "0.a" + elsif i > T.unsafe(first_non_zero_index) && i == 2 then "0.a" else 0 end @@ -131,18 +146,20 @@ def convert_caret_req(req_string) [">= #{version}", "< #{upper_bound}"] end + # Poetry uses * requirements + sig { params(req_string: String).returns(T.nilable(String)) } def convert_wildcard(req_string) # NOTE: This isn't perfect. It replaces the "!= 1.0.*" case with # "!= 1.0.0". There's no way to model this correctly in Ruby :'( quoted_ops = OPS.keys.sort_by(&:length).reverse .map { |k| Regexp.quote(k) }.join("|") op = req_string.match(/\A\s*(#{quoted_ops})?/) - .captures.first.to_s&.strip + &.captures&.first&.to_s&.strip exact_op = ["", "=", "==", "==="].include?(op) req_string.strip .split(".") - .first(req_string.split(".").index { |s| s.include?("*") } + 1) + .first(T.unsafe(req_string.split(".").index { |s| s.include?("*") }) + 1) .join(".") .gsub(/\*(?!$)/, "0") .gsub(/\*$/, "0.a") diff --git a/swift/lib/dependabot/swift/native_requirement.rb b/swift/lib/dependabot/swift/native_requirement.rb index 5c20dcf207e0..6b133f148fcb 100644 --- a/swift/lib/dependabot/swift/native_requirement.rb +++ b/swift/lib/dependabot/swift/native_requirement.rb @@ -7,6 +7,8 @@ module Dependabot module Swift class NativeRequirement + extend T::Sig + # TODO: Support pinning to specific revisions REGEXP = /(from.*|\.upToNextMajor.*|\.upToNextMinor.*|".*"\s*\.\.[\.<]\s*".*"|exact.*|\.exact.*)/ @@ -41,7 +43,7 @@ def initialize(declaration) @min = min @max = max - @requirement = Requirement.new(constraint) + @requirement = Requirement.new(T.unsafe(constraint)) end def to_s @@ -66,6 +68,7 @@ def update(version) private + sig { params(declaration: String).returns([String, String]) } def parse_declaration(declaration) if up_to_next_major? min = declaration.gsub(/\Afrom\s*:\s*"(\S+)"\s*\z/, '\1') diff --git a/swift/lib/dependabot/swift/requirement.rb b/swift/lib/dependabot/swift/requirement.rb index c8594a13c9c2..a7cbb07d60f4 100644 --- a/swift/lib/dependabot/swift/requirement.rb +++ b/swift/lib/dependabot/swift/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "dependabot/utils" @@ -6,15 +6,19 @@ module Dependabot module Swift class Requirement < Gem::Requirement + extend T::Sig + # For consistency with other languages, we define a requirements array. # Swift doesn't have an `OR` separator for requirements, so it # always contains a single element. + sig { params(requirement_string: String).returns(T::Array[Dependabot::Swift::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip) diff --git a/terraform/lib/dependabot/terraform/requirement.rb b/terraform/lib/dependabot/terraform/requirement.rb index 270aee76282e..c7af2600175e 100644 --- a/terraform/lib/dependabot/terraform/requirement.rb +++ b/terraform/lib/dependabot/terraform/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "dependabot/utils" @@ -8,13 +8,16 @@ module Dependabot module Terraform class Requirement < Gem::Requirement + extend T::Sig + # Override regex PATTERN from Gem::Requirement to add support for the # optional 'v' prefix to release tag names, which Terraform supports. # https://www.terraform.io/docs/registry/modules/publish.html#requirements - OPERATORS = OPS.keys.map { |key| Regexp.quote(key) }.join("|").freeze - PATTERN_RAW = "\\s*(#{OPERATORS})?\\s*v?(#{Gem::Version::VERSION_PATTERN})\\s*".freeze + OPERATORS = T.let(OPS.keys.map { |key| Regexp.quote(key) }.join("|").freeze, String) + PATTERN_RAW = T.let("\\s*(#{OPERATORS})?\\s*v?(#{Gem::Version::VERSION_PATTERN})\\s*".freeze, String) PATTERN = /\A#{PATTERN_RAW}\z/ + sig { override.params(obj: Object).returns([String, Dependabot::Terraform::Version]) } def self.parse(obj) return ["=", Version.new(obj.to_s)] if obj.is_a?(Gem::Version) @@ -31,12 +34,14 @@ def self.parse(obj) # For consistency with other languages, we define a requirements array. # Terraform doesn't have an `OR` separator for requirements, so it # always contains a single element. + sig { params(requirement_string: String).returns(T::Array[Dependabot::Terraform::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { override.params(requirements: String).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| req_string.split(",").map(&:strip)