Skip to content

Commit

Permalink
Add sorbet type checking to bundler/file_updater/gemspec_sanitizer.rb
Browse files Browse the repository at this point in the history
  • Loading branch information
amazimbe committed Jan 24, 2025
1 parent f3327e0 commit 21bb842
Showing 1 changed file with 49 additions and 4 deletions.
53 changes: 49 additions & 4 deletions bundler/lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb
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,9 @@ module Dependabot
module Bundler
class FileUpdater
class GemspecSanitizer
extend T::Sig
extend T::Helpers

UNNECESSARY_ASSIGNMENTS = %i(
bindir=
cert_chain=
Expand All @@ -23,12 +26,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 +53,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 +94,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,14 +113,17 @@ def wrap_require(node)
)
end

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

return replace_constant(node) if node_assigns_to_version_constant?(node)

# debugger
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 +134,7 @@ def replace_version_constant_references(node)
end
end

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

Expand All @@ -124,6 +143,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 +154,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 +167,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 +177,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 +186,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 +195,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 +205,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 +215,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 +225,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 +234,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 +244,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 +253,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 +263,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 +279,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 +305,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 +343,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 +357,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

0 comments on commit 21bb842

Please sign in to comment.