Skip to content

Commit

Permalink
Start strict typing gradle (#9346)
Browse files Browse the repository at this point in the history
* Start strict typing `gradle`

* Strict type `Dependabot::Gradle::FileUpdater::DependencySetUpdater`

* Strict type `Dependabot::Gradle::UpdateChecker::RequirementsUpdater`
  • Loading branch information
JamieMagee authored Mar 22, 2024
1 parent 4a83645 commit 1b179eb
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 32 deletions.
28 changes: 28 additions & 0 deletions common/lib/dependabot/requirements_updater/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# typed: strong
# frozen_string_literal: true

require "sorbet-runtime"

module Dependabot
module RequirementsUpdater
module Base
extend T::Sig
extend T::Helpers
extend T::Generic

Version = type_member { { upper: Gem::Version } }
Requirement = type_member { { upper: Gem::Requirement } }

interface!

sig { abstract.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def updated_requirements; end

sig { abstract.returns(T::Class[Version]) }
def version_class; end

sig { abstract.returns(T::Class[Requirement]) }
def requirement_class; end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "sorbet-runtime"

require "dependabot/gradle/file_parser"
require "dependabot/gradle/file_updater"

module Dependabot
module Gradle
class FileUpdater
class DependencySetUpdater
extend T::Sig

sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
def initialize(dependency_files:)
@dependency_files = dependency_files
end

sig do
params(
dependency_set: T::Hash[Symbol, String],
buildfile: Dependabot::DependencyFile,
previous_requirement: String,
updated_requirement: String
).returns(T::Array[Dependabot::DependencyFile])
end
def update_files_for_dep_set_change(dependency_set:,
buildfile:,
previous_requirement:,
Expand All @@ -21,7 +34,7 @@ def update_files_for_dep_set_change(dependency_set:,

return dependency_files unless declaration_string

updated_content = buildfile.content.sub(
updated_content = T.must(buildfile.content).sub(
declaration_string,
declaration_string.sub(
previous_requirement,
Expand All @@ -30,30 +43,39 @@ def update_files_for_dep_set_change(dependency_set:,
)

updated_files = dependency_files.dup
updated_files[updated_files.index(buildfile)] =
updated_files[T.must(updated_files.index(buildfile))] =
update_file(file: buildfile, content: updated_content)

updated_files
end

private

sig { returns(T::Array[Dependabot::DependencyFile]) }
attr_reader :dependency_files

sig do
params(
dependency_set: T::Hash[Symbol, String],
buildfile: Dependabot::DependencyFile
)
.returns(T.nilable(String))
end
def original_declaration_string(dependency_set, buildfile)
regex = Gradle::FileParser::DEPENDENCY_SET_DECLARATION_REGEX
dependency_sets = []
buildfile.content.scan(regex) do
dependency_sets = T.let([], T::Array[String])
T.must(buildfile.content).scan(regex) do
dependency_sets << Regexp.last_match.to_s
end

dependency_sets.find do |mtch|
next unless mtch.include?(dependency_set[:group])
next unless mtch.include?(T.must(dependency_set[:group]))

mtch.include?(dependency_set[:version])
mtch.include?(T.must(dependency_set[:version]))
end
end

sig { params(file: Dependabot::DependencyFile, content: String).returns(Dependabot::DependencyFile) }
def update_file(file:, content:)
updated_file = file.dup
updated_file.content = content
Expand Down
41 changes: 29 additions & 12 deletions gradle/lib/dependabot/gradle/metadata_finder.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "nokogiri"
require "sorbet-runtime"
require "dependabot/metadata_finders"
require "dependabot/metadata_finders/base"

require "dependabot/file_fetchers/base"
require "dependabot/gradle/file_fetcher"
require "dependabot/gradle/file_parser/repositories_finder"
require "dependabot/maven/utils/auth_headers_finder"
require "dependabot/metadata_finders"
require "dependabot/metadata_finders/base"
require "dependabot/registry_client"

module Dependabot
Expand All @@ -21,6 +23,7 @@ class MetadataFinder < Dependabot::MetadataFinders::Base

private

sig { override.returns(T.nilable(Dependabot::Source)) }
def look_up_source
tmp_source = look_up_source_in_pom(dependency_pom_file)
return tmp_source if tmp_source
Expand All @@ -31,14 +34,15 @@ def look_up_source
return unless tmp_source

artifact = dependency.name.split(":").last
return tmp_source if tmp_source.repo.end_with?(artifact)
return tmp_source if tmp_source.repo.end_with?(T.must(artifact))

tmp_source if repo_has_subdir_for_dep?(tmp_source)
end

sig { params(tmp_source: Dependabot::Source).returns(T::Boolean) }
def repo_has_subdir_for_dep?(tmp_source)
@repo_has_subdir_for_dep ||= {}
return @repo_has_subdir_for_dep[tmp_source] if @repo_has_subdir_for_dep.key?(tmp_source)
@repo_has_subdir_for_dep ||= T.let({}, T.nilable(T::Hash[Dependabot::Source, T::Boolean]))
return T.must(@repo_has_subdir_for_dep[tmp_source]) if @repo_has_subdir_for_dep.key?(tmp_source)

artifact = dependency.name.split(":").last
fetcher =
Expand All @@ -52,9 +56,10 @@ def repo_has_subdir_for_dep?(tmp_source)
tmp_source.branch = nil
retry
rescue Dependabot::RepoNotFound
@repo_has_subdir_for_dep[tmp_source] = false
T.must(@repo_has_subdir_for_dep)[tmp_source] = false
end

sig { params(pom: Nokogiri::XML::Document).returns(T.nilable(Dependabot::Source)) }
def look_up_source_in_pom(pom)
potential_source_urls = [
pom.at_css("project > url")&.content,
Expand All @@ -69,15 +74,16 @@ def look_up_source_in_pom(pom)
Source.from_url(source_url)
end

sig { params(source_url: T.nilable(String), pom: Nokogiri::XML::Document).returns(T.nilable(String)) }
def substitute_property_in_source_url(source_url, pom)
return unless source_url
return source_url unless source_url.include?("${")

regex = PROPERTY_REGEX
property_name = source_url.match(regex).named_captures["property"]
property_name = T.must(source_url.match(regex)).named_captures["property"]
doc = pom.dup
doc.remove_namespaces!
nm = property_name.sub(/^pom\./, "").sub(/^project\./, "")
nm = T.must(property_name).sub(/^pom\./, "").sub(/^project\./, "")
property_value =
loop do
candidate_node =
Expand All @@ -93,6 +99,7 @@ def substitute_property_in_source_url(source_url, pom)
source_url.gsub("${#{property_name}}", property_value)
end

sig { params(pom: T.any(String, Nokogiri::XML::Document)).returns(T.nilable(String)) }
def source_from_anywhere_in_pom(pom)
github_urls = []
pom.to_s.scan(Source::SOURCE_REGEX) do
Expand All @@ -105,6 +112,7 @@ def source_from_anywhere_in_pom(pom)
end
end

sig { returns(Nokogiri::XML::Document) }
def dependency_pom_file
return @dependency_pom_file unless @dependency_pom_file.nil?

Expand All @@ -120,11 +128,12 @@ def dependency_pom_file
headers: auth_headers
)

@dependency_pom_file = Nokogiri::XML(response.body)
@dependency_pom_file = T.let(Nokogiri::XML(response.body), T.nilable(Nokogiri::XML::Document))
rescue Excon::Error::Timeout
@dependency_pom_file = Nokogiri::XML("")
@dependency_pom_file ||= T.let(Nokogiri::XML(""), T.nilable(Nokogiri::XML::Document))
end

sig { params(pom: Nokogiri::XML::Document).returns(T.nilable(Nokogiri::XML::Document)) }
def parent_pom_file(pom)
doc = pom.dup
doc.remove_namespaces!
Expand All @@ -143,6 +152,7 @@ def parent_pom_file(pom)
Nokogiri::XML(response.body)
end

sig { returns(String) }
def maven_repo_url
source = dependency.requirements
.find { |r| r.fetch(:source) }&.fetch(:source)
Expand All @@ -152,6 +162,7 @@ def maven_repo_url
Gradle::FileParser::RepositoriesFinder::CENTRAL_REPO_URL
end

sig { returns(String) }
def maven_repo_dependency_url
group_id, artifact_id =
if kotlin_plugin?
Expand All @@ -165,16 +176,22 @@ def maven_repo_dependency_url
"#{maven_repo_url}/#{group_id&.tr('.', '/')}/#{artifact_id}"
end

sig { returns(T::Boolean) }
def plugin?
dependency.requirements.any? { |r| r.fetch(:groups).include? "plugins" }
end

sig { returns(T::Boolean) }
def kotlin_plugin?
plugin? && dependency.requirements.any? { |r| r.fetch(:groups).include? "kotlin" }
end

sig { returns(T::Hash[String, String]) }
def auth_headers
@auth_headers ||= Dependabot::Maven::Utils::AuthHeadersFinder.new(credentials).auth_headers(maven_repo_url)
@auth_headers ||= T.let(
Dependabot::Maven::Utils::AuthHeadersFinder.new(credentials).auth_headers(maven_repo_url),
T.nilable(T::Hash[String, String])
)
end
end
end
Expand Down
28 changes: 18 additions & 10 deletions gradle/lib/dependabot/gradle/requirement.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "sorbet-runtime"
Expand All @@ -14,9 +14,10 @@ class Requirement < Dependabot::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: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
def self.parse(obj)
return ["=", Gradle::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)

Expand All @@ -37,6 +38,7 @@ def self.requirements_array(requirement_string)
end
end

sig { params(requirements: T.any(T.nilable(String), T::Array[T.nilable(String)])).void }
def initialize(*requirements)
requirements = requirements.flatten.flat_map do |req_string|
convert_java_constraint_to_ruby_constraint(req_string)
Expand All @@ -45,25 +47,28 @@ def initialize(*requirements)
super(requirements)
end

sig { override.params(version: Gem::Version).returns(T::Boolean) }
def satisfied_by?(version)
version = Gradle::Version.new(version.to_s)
super
end

private

sig { params(req_string: T.nilable(String)).returns(T::Array[T.nilable(String)]) }
def self.split_java_requirement(req_string)
return [req_string] unless req_string.match?(Maven::Requirement::OR_SYNTAX)
return [req_string] unless req_string&.match?(Maven::Requirement::OR_SYNTAX)

req_string.split(Maven::Requirement::OR_SYNTAX).flat_map do |str|
next str if str.start_with?("(", "[")

exacts, *rest = str.split(/,(?=\[|\()/)
[*exacts.split(","), *rest]
[*T.must(exacts).split(","), *rest]
end
end
private_class_method :split_java_requirement

sig { params(req_string: T.nilable(String)).returns(T.nilable(T::Array[String])) }
def convert_java_constraint_to_ruby_constraint(req_string)
return unless req_string

Expand All @@ -81,35 +86,38 @@ def convert_java_constraint_to_ruby_constraint(req_string)
end
end

sig { params(req_string: String).returns(T::Array[String]) }
def convert_java_range_to_ruby_range(req_string)
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 T.must(lower_b).start_with?("(") then "> #{T.must(lower_b).sub(/\(\s*/, '')}"
else
">= #{lower_b.sub(/\[\s*/, '').strip}"
">= #{T.must(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 T.must(upper_b).end_with?(")") then "< #{T.must(upper_b).sub(/\s*\)/, '')}"
else
"<= #{upper_b.sub(/\s*\]/, '').strip}"
"<= #{T.must(upper_b).sub(/\s*\]/, '').strip}"
end

[lower_b, upper_b].compact
end

sig { params(req_string: String).returns(String) }
def convert_java_equals_req_to_ruby(req_string)
return convert_wildcard_req(req_string) if req_string&.include?("+")
return convert_wildcard_req(req_string) if req_string.include?("+")

# If a soft requirement is being used, treat it as an equality matcher
return req_string unless req_string&.start_with?("[")
return req_string unless req_string.start_with?("[")

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?
Expand Down
Loading

0 comments on commit 1b179eb

Please sign in to comment.