From 21bb84229c9eecc8b8a7592c5e1c0179adbb2847 Mon Sep 17 00:00:00 2001 From: Alfred Mazimbe Date: Fri, 24 Jan 2025 15:41:55 +0000 Subject: [PATCH] Add sorbet type checking to bundler/file_updater/gemspec_sanitizer.rb --- .../bundler/file_updater/gemspec_sanitizer.rb | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/bundler/lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb b/bundler/lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb index 095206e9b5b..f736698dd05 100644 --- a/bundler/lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb +++ b/bundler/lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "parser/current" @@ -8,6 +8,9 @@ module Dependabot module Bundler class FileUpdater class GemspecSanitizer + extend T::Sig + extend T::Helpers + UNNECESSARY_ASSIGNMENTS = %i( bindir= cert_chain= @@ -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 @@ -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 @@ -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, @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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= @@ -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) @@ -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) @@ -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= @@ -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) @@ -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= @@ -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) @@ -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) @@ -247,8 +279,9 @@ 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 @@ -256,6 +289,7 @@ def node_includes_heredoc?(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) @@ -271,6 +305,7 @@ 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) @@ -278,9 +313,10 @@ def unnecessary_assignment?(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 @@ -288,6 +324,7 @@ def node_is_version_constant?(node) 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 @@ -295,6 +332,7 @@ def node_calls_version_constant?(node) 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 @@ -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 @@ -318,18 +357,22 @@ 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, @@ -337,10 +380,12 @@ def replace_json_parse(node) ) 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