diff --git a/python/lib/dependabot/python/file_parser/pipfile_files_parser.rb b/python/lib/dependabot/python/file_parser/pipfile_files_parser.rb index c22c75b3b1..0a005c9c6f 100644 --- a/python/lib/dependabot/python/file_parser/pipfile_files_parser.rb +++ b/python/lib/dependabot/python/file_parser/pipfile_files_parser.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "toml-rb" @@ -13,7 +13,8 @@ module Dependabot module Python class FileParser class PipfileFilesParser - DEPENDENCY_GROUP_KEYS = [ + extend T::Sig + DEPENDENCY_GROUP_KEYS = T.let([ { pipfile: "packages", lockfile: "default" @@ -22,12 +23,14 @@ class PipfileFilesParser pipfile: "dev-packages", lockfile: "develop" } - ].freeze + ].freeze, T::Array[T::Hash[Symbol, String]]) + sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void } def initialize(dependency_files:) @dependency_files = dependency_files end + sig { returns(Dependabot::FileParsers::Base::DependencySet) } def dependency_set dependency_set = Dependabot::FileParsers::Base::DependencySet.new @@ -39,19 +42,21 @@ def dependency_set private + sig { returns(T::Array[Dependabot::DependencyFile]) } attr_reader :dependency_files + sig { returns(Dependabot::FileParsers::Base::DependencySet) } def pipfile_dependencies dependencies = Dependabot::FileParsers::Base::DependencySet.new DEPENDENCY_GROUP_KEYS.each do |keys| - next unless parsed_pipfile[keys[:pipfile]] + next unless parsed_pipfile[T.must(keys[:pipfile])] - parsed_pipfile[keys[:pipfile]].map do |dep_name, req| + parsed_pipfile[T.must(keys[:pipfile])].map do |dep_name, req| group = keys[:lockfile] next unless specifies_version?(req) next if git_or_path_requirement?(req) - next if pipfile_lock && !dependency_version(dep_name, req, group) + next if pipfile_lock && !dependency_version(dep_name, req, T.must(group)) # Empty requirements are not allowed in Dependabot::Dependency and # equivalent to "*" (latest available version) @@ -60,10 +65,10 @@ def pipfile_dependencies dependencies << Dependency.new( name: normalised_name(dep_name), - version: dependency_version(dep_name, req, group), + version: dependency_version(dep_name, req, T.must(group)), requirements: [{ requirement: req.is_a?(String) ? req : req["version"], - file: pipfile.name, + file: T.must(pipfile).name, source: nil, groups: [group] }], @@ -79,6 +84,7 @@ def pipfile_dependencies # Create a DependencySet where each element has no requirement. Any # requirements will be added when combining the DependencySet with # other DependencySets. + sig { returns(Dependabot::FileParsers::Base::DependencySet) } def pipfile_lock_dependencies dependencies = Dependabot::FileParsers::Base::DependencySet.new return dependencies unless pipfile_lock @@ -108,6 +114,10 @@ def pipfile_lock_dependencies dependencies end + sig do + params(dep_name: String, requirement: T.any(String, T::Hash[String, T.untyped]), + group: String).returns(T.nilable(String)) + end def dependency_version(dep_name, requirement, group) req = version_from_hash_or_string(requirement) @@ -117,11 +127,14 @@ def dependency_version(dep_name, requirement, group) version = version_from_hash_or_string(details) version&.gsub(/^===?/, "") - elsif req.start_with?("==") && !req.include?("*") - req.strip.gsub(/^===?/, "") + elsif T.must(req).start_with?("==") && !T.must(req).include?("*") + T.must(req).strip.gsub(/^===?/, "") end end + sig do + params(obj: T.any(String, NilClass, T::Array[String], T::Hash[String, T.untyped])).returns(T.nilable(String)) + end def version_from_hash_or_string(obj) case obj when String then obj.strip @@ -129,41 +142,49 @@ def version_from_hash_or_string(obj) end end + sig { params(req: T.any(String, T::Hash[String, T.untyped])).returns(T.any(T::Boolean, NilClass, String)) } def specifies_version?(req) return true if req.is_a?(String) req["version"] end + sig { params(req: T.any(String, T::Hash[String, T.untyped])).returns(T::Boolean) } def git_or_path_requirement?(req) return false unless req.is_a?(Hash) %w(git path).any? { |k| req.key?(k) } end - def normalised_name(name) - NameNormaliser.normalise(name) + sig { params(name: String, extras: T::Array[String]).returns(String) } + def normalised_name(name, extras = []) + NameNormaliser.normalise_including_extras(name, extras) end + sig { returns(T::Hash[String, T.untyped]) } def parsed_pipfile - @parsed_pipfile ||= TomlRB.parse(pipfile.content) + @parsed_pipfile ||= T.let(TomlRB.parse(T.must(pipfile).content), T.nilable(T::Hash[String, T.untyped])) rescue TomlRB::ParseError, TomlRB::ValueOverwriteError - raise Dependabot::DependencyFileNotParseable, pipfile.path + raise Dependabot::DependencyFileNotParseable, T.must(pipfile).path end + sig { returns(T::Hash[String, T.untyped]) } def parsed_pipfile_lock - @parsed_pipfile_lock ||= JSON.parse(pipfile_lock.content) + @parsed_pipfile_lock ||= T.let(JSON.parse(T.must(T.must(pipfile_lock).content)), + T.nilable(T::Hash[String, T.untyped])) rescue JSON::ParserError - raise Dependabot::DependencyFileNotParseable, pipfile_lock.path + raise Dependabot::DependencyFileNotParseable, T.must(pipfile_lock).path end + sig { returns(T.nilable(Dependabot::DependencyFile)) } def pipfile - @pipfile ||= dependency_files.find { |f| f.name == "Pipfile" } + @pipfile ||= T.let(dependency_files.find { |f| f.name == "Pipfile" }, T.nilable(Dependabot::DependencyFile)) end + sig { returns(T.nilable(Dependabot::DependencyFile)) } def pipfile_lock - @pipfile_lock ||= - dependency_files.find { |f| f.name == "Pipfile.lock" } + @pipfile_lock ||= T.let(dependency_files.find { |f| f.name == "Pipfile.lock" }, + T.nilable(Dependabot::DependencyFile)) end end end