Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sorbet type checking to bundler/file_updater/gemspec_sanitizer.rb #11401

Merged
merged 4 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: strict
# typed: strong
# frozen_string_literal: true

require "sorbet-runtime"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "parser/current"
Expand All @@ -8,6 +8,8 @@ module Dependabot
module Bundler
class FileUpdater
class GemspecSanitizer
extend T::Sig

UNNECESSARY_ASSIGNMENTS = %i(
bindir=
cert_chain=
Expand All @@ -23,12 +25,15 @@ class GemspecSanitizer
rdoc_options=
).freeze

sig { returns(String) }
attr_reader :replacement_version

sig { params(replacement_version: T.any(String, Integer, Gem::Version)).void }
def initialize(replacement_version:)
@replacement_version = replacement_version
@replacement_version = T.let(replacement_version.to_s, String)
end

sig { params(content: String).returns(String) }
def rewrite(content)
buffer = Parser::Source::Buffer.new("(gemspec_content)")
buffer.source = content
Expand All @@ -47,10 +52,16 @@ def rewrite(content)
end

class Rewriter < Parser::TreeRewriter
extend T::Sig

ParserNode = T.type_alias { T.nilable(T.any(Parser::AST::Node, Symbol, Integer, String, Float)) }

sig { params(replacement_version: String).void }
def initialize(replacement_version:)
@replacement_version = replacement_version
end

sig { params(node: Parser::AST::Node).void }
def on_send(node)
# Wrap any `require` or `require_relative` calls in a rescue
# block, as we might not have the required files
Expand Down Expand Up @@ -82,12 +93,15 @@ def on_send(node)

private

sig { returns(String) }
attr_reader :replacement_version

sig { params(node: Parser::AST::Node).returns(T::Boolean) }
def requires_file?(node)
%i(require require_relative).include?(node.children[1])
end

sig { params(node: Parser::AST::Node).void }
def wrap_require(node)
replace(
node.loc.expression,
Expand All @@ -98,6 +112,7 @@ def wrap_require(node)
)
end

sig { params(node: ParserNode).void }
def replace_version_assignments(node)
return unless node.is_a?(Parser::AST::Node)

Expand All @@ -106,6 +121,7 @@ def replace_version_assignments(node)
node.children.each { |child| replace_version_assignments(child) }
end

sig { params(node: T.nilable(Parser::AST::Node)).void }
def replace_version_constant_references(node)
return unless node.is_a?(Parser::AST::Node)

Expand All @@ -116,6 +132,7 @@ def replace_version_constant_references(node)
end
end

sig { params(node: ParserNode).void }
def replace_file_assignments(node)
return unless node.is_a?(Parser::AST::Node)

Expand All @@ -124,6 +141,7 @@ def replace_file_assignments(node)
node.children.each { |child| replace_file_assignments(child) }
end

sig { params(node: ParserNode).void }
def replace_require_paths_assignments(node)
return unless node.is_a?(Parser::AST::Node)

Expand All @@ -134,6 +152,7 @@ def replace_require_paths_assignments(node)
end
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_assigns_to_version_constant?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.children.first.is_a?(Parser::AST::Node)
Expand All @@ -146,6 +165,7 @@ def node_assigns_to_version_constant?(node)
node_interpolates_version_constant?(node.children.last)
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_assigns_files_to_var?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.children.first.is_a?(Parser::AST::Node)
Expand All @@ -155,6 +175,7 @@ def node_assigns_files_to_var?(node)
node_dynamically_lists_files?(node.children[2])
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_dynamically_lists_files?(node)
return false unless node.is_a?(Parser::AST::Node)

Expand All @@ -163,6 +184,7 @@ def node_dynamically_lists_files?(node)
node.type == :block && node.children.first&.type == :send
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_assigns_require_paths?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.children.first.is_a?(Parser::AST::Node)
Expand All @@ -171,6 +193,7 @@ def node_assigns_require_paths?(node)
node.children[1] == :require_paths=
end

sig { params(node: T.nilable(T.any(Parser::AST::Node, Symbol, String))).void }
def replace_file_reads(node)
return unless node.is_a?(Parser::AST::Node)
return if node.children[1] == :version=
Expand All @@ -180,6 +203,7 @@ def replace_file_reads(node)
node.children.each { |child| replace_file_reads(child) }
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_reads_a_file?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.children.first.is_a?(Parser::AST::Node)
Expand All @@ -189,6 +213,7 @@ def node_reads_a_file?(node)
node.children[1] == :read
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_uses_readlines?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.children.first.is_a?(Parser::AST::Node)
Expand All @@ -198,6 +223,7 @@ def node_uses_readlines?(node)
node.children[1] == :readlines
end

sig { params(node: T.nilable(T.any(Parser::AST::Node, Symbol, String))).void }
def replace_json_parses(node)
return unless node.is_a?(Parser::AST::Node)
return if node.children[1] == :version=
Expand All @@ -206,6 +232,7 @@ def replace_json_parses(node)
node.children.each { |child| replace_json_parses(child) }
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_parses_json?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.children.first.is_a?(Parser::AST::Node)
Expand All @@ -215,6 +242,7 @@ def node_parses_json?(node)
node.children[1] == :parse
end

sig { params(node: T.nilable(T.any(Parser::AST::Node, Symbol, String))).void }
def remove_find_dot_find_args(node)
return unless node.is_a?(Parser::AST::Node)
return if node.children[1] == :version=
Expand All @@ -223,6 +251,7 @@ def remove_find_dot_find_args(node)
node.children.each { |child| remove_find_dot_find_args(child) }
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_calls_find_dot_find?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.children.first.is_a?(Parser::AST::Node)
Expand All @@ -232,6 +261,7 @@ def node_calls_find_dot_find?(node)
node.children[1] == :find
end

sig { params(node: ParserNode).void }
def remove_unnecessary_assignments(node)
return unless node.is_a?(Parser::AST::Node)

Expand All @@ -247,15 +277,17 @@ def remove_unnecessary_assignments(node)
end
end

sig { params(node: T.nilable(Parser::AST::Node)).returns(T::Boolean) }
def node_includes_heredoc?(node)
find_heredoc_end_range(node)
!!find_heredoc_end_range(node)
end

# Performs a depth-first search for the first heredoc in the given
# Parser::AST::Node.
#
# Returns a Parser::Source::Range identifying the location of the end
# of the heredoc, or nil if no heredoc was found.
sig { params(node: T.nilable(Parser::AST::Node)).returns(T.nilable(Parser::Source::Range)) }
def find_heredoc_end_range(node)
return unless node.is_a?(Parser::AST::Node)

Expand All @@ -271,30 +303,34 @@ def find_heredoc_end_range(node)
nil
end

sig { params(node: ParserNode).returns(T::Boolean) }
def unnecessary_assignment?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.children.first.is_a?(Parser::AST::Node)

return true if node.children.first.type == :lvar &&
UNNECESSARY_ASSIGNMENTS.include?(node.children[1])

node.children[1] == :[]= && node.children.first.children.last
!!(node.children[1] == :[]= && node.children.first.children.last)
end

sig { params(node: ParserNode).returns(T::Boolean) }
def node_is_version_constant?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.type == :const

node.children.last.to_s.match?(/version/i)
end

sig { params(node: ParserNode).returns(T::Boolean) }
def node_calls_version_constant?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.type == :send

node.children.any? { |n| node_is_version_constant?(n) }
end

sig { params(node: ParserNode).returns(T::Boolean) }
def node_interpolates_version_constant?(node)
return false unless node.is_a?(Parser::AST::Node)
return false unless node.type == :dstr
Expand All @@ -305,6 +341,7 @@ def node_interpolates_version_constant?(node)
.any? { |n| node_is_version_constant?(n) }
end

sig { params(node: Parser::AST::Node).void }
def replace_constant(node)
case node.children.last&.type
when :str, :int then nil # no-op
Expand All @@ -318,29 +355,35 @@ def replace_constant(node)
end
end

sig { params(node: Parser::AST::Node).void }
def replace_file_assignment(node)
replace(node.children.last.loc.expression, "[]")
end

sig { params(node: Parser::AST::Node).void }
def replace_require_paths_assignment(node)
replace(node.children.last.loc.expression, "['lib']")
end

sig { params(node: Parser::AST::Node).void }
def replace_file_read(node)
replace(node.loc.expression, %("#{replacement_version}"))
end

sig { params(node: Parser::AST::Node).void }
def replace_json_parse(node)
replace(
node.loc.expression,
%({ "version" => "#{replacement_version}" })
)
end

sig { params(node: Parser::AST::Node).void }
def replace_file_readlines(node)
replace(node.loc.expression, %(["#{replacement_version}"]))
end

sig { params(node: Parser::AST::Node).void }
def remove_find_args(node)
last_arg = node.children.last

Expand Down
Loading