class Dependabot::Bundler::FileUpdater::GemspecSanitizer::Rewriter

Attributes

replacement_version[R]

Public Class Methods

new(replacement_version:) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 49
def initialize(replacement_version:)
  @replacement_version = replacement_version
end

Public Instance Methods

on_send(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 53
def on_send(node)
  # Wrap any `require` or `require_relative` calls in a rescue
  # block, as we might not have the required files
  wrap_require(node) if requires_file?(node)

  # Remove any assignments to a VERSION constant (or similar), as
  # that constant probably comes from a required file
  replace_version_assignments(node)

  # Replace the `s.files= ...` assignment with a blank array, as
  # occasionally a File.open(..).readlines pattern is used
  replace_file_assignments(node)

  # Replace the `s.require_path= ...` assignment, as
  # occasionally a Dir['lib'] pattern is used
  replace_require_paths_assignments(node)

  # Replace any `File.read(...)` calls with a dummy string
  replace_file_reads(node)

  # Replace any `JSON.parse(...)` calls with a dummy hash
  replace_json_parses(node)

  # Remove the arguments from any `Find.find(...)` calls
  remove_find_dot_find_args(node)

  remove_unnecessary_assignments(node)
end

Private Instance Methods

find_heredoc_end_range(node) click to toggle source

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.
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 258
def find_heredoc_end_range(node)
  return unless node.is_a?(Parser::AST::Node)

  node.children.each do |child|
    next unless child.is_a?(Parser::AST::Node)

    return child.location.heredoc_end if child.location.respond_to?(:heredoc_end)

    range = find_heredoc_end_range(child)
    return range if range
  end

  nil
end
node_assigns_files_to_var?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 148
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)
  return false unless node.children.first&.type == :lvar
  return false unless node.children[1] == :files=

  node_dynamically_lists_files?(node.children[2])
end
node_assigns_require_paths?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 165
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)
  return false unless node.children.first&.type == :lvar

  node.children[1] == :require_paths=
end
node_assigns_to_version_constant?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 136
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)
  return false unless node.children.first&.type == :lvar

  return true if node.children[1] == :version=
  return true if node_is_version_constant?(node.children.last)
  return true if node_calls_version_constant?(node.children.last)

  node_interpolates_version_constant?(node.children.last)
end
node_calls_find_dot_find?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 225
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)
  return false unless node.children.first&.type == :const
  return false unless node.children.first.children.last == :Find

  node.children[1] == :find
end
node_calls_version_constant?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 290
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
node_dynamically_lists_files?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 157
def node_dynamically_lists_files?(node)
  return false unless node.is_a?(Parser::AST::Node)

  return true if node.type == :send

  node.type == :block && node.children.first&.type == :send
end
node_includes_heredoc?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 249
def node_includes_heredoc?(node)
  find_heredoc_end_range(node)
end
node_interpolates_version_constant?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 297
def node_interpolates_version_constant?(node)
  return false unless node.is_a?(Parser::AST::Node)
  return false unless node.type == :dstr

  node.children.
    select { |n| n.type == :begin }.
    flat_map(&:children).
    any? { |n| node_is_version_constant?(n) }
end
node_is_version_constant?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 283
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
node_parses_json?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 208
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)
  return false unless node.children.first&.type == :const
  return false unless node.children.first.children.last == :JSON

  node.children[1] == :parse
end
node_reads_a_file?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 182
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)
  return false unless node.children.first&.type == :const
  return false unless node.children.first.children.last == :File

  node.children[1] == :read
end
node_uses_readlines?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 191
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)
  return false unless node.children.first&.type == :const
  return false unless node.children.first.children.last == :File

  node.children[1] == :readlines
end
remove_find_args(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 343
def remove_find_args(node)
  last_arg = node.children.last

  range_to_remove =
    last_arg.loc.expression.join(node.children[2].loc.begin.begin)

  remove(range_to_remove)
end
remove_find_dot_find_args(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 217
def remove_find_dot_find_args(node)
  return unless node.is_a?(Parser::AST::Node)
  return if node.children[1] == :version=
  return remove_find_args(node) if node_calls_find_dot_find?(node)

  node.children.each { |child| remove_find_dot_find_args(child) }
end
remove_unnecessary_assignments(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 234
def remove_unnecessary_assignments(node)
  return unless node.is_a?(Parser::AST::Node)

  if unnecessary_assignment?(node) && node_includes_heredoc?(node)
    range_to_remove = node.loc.expression.join(find_heredoc_end_range(node))
    return replace(range_to_remove, '"sanitized"')
  elsif unnecessary_assignment?(node)
    return replace(node.loc.expression, '"sanitized"')
  end

  node.children.each do |child|
    remove_unnecessary_assignments(child)
  end
end
replace_constant(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 307
def replace_constant(node)
  case node.children.last&.type
  when :str, :int then nil # no-op
  when :float, :const, :send, :lvar, :if, :dstr
    replace(
      node.children.last.loc.expression,
      %("#{replacement_version}")
    )
  else
    raise "Unexpected node type #{node.children.last&.type}"
  end
end
replace_file_assignment(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 320
def replace_file_assignment(node)
  replace(node.children.last.loc.expression, "[]")
end
replace_file_assignments(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 118
def replace_file_assignments(node)
  return unless node.is_a?(Parser::AST::Node)

  return replace_file_assignment(node) if node_assigns_files_to_var?(node)

  node.children.each { |child| replace_file_assignments(child) }
end
replace_file_read(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 328
def replace_file_read(node)
  replace(node.loc.expression, %("#{replacement_version}"))
end
replace_file_readlines(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 339
def replace_file_readlines(node)
  replace(node.loc.expression, %(["#{replacement_version}"]))
end
replace_file_reads(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 173
def replace_file_reads(node)
  return unless node.is_a?(Parser::AST::Node)
  return if node.children[1] == :version=
  return replace_file_read(node) if node_reads_a_file?(node)
  return replace_file_readlines(node) if node_uses_readlines?(node)

  node.children.each { |child| replace_file_reads(child) }
end
replace_json_parse(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 332
def replace_json_parse(node)
  replace(
    node.loc.expression,
    %({ "version" => "#{replacement_version}" })
  )
end
replace_json_parses(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 200
def replace_json_parses(node)
  return unless node.is_a?(Parser::AST::Node)
  return if node.children[1] == :version=
  return replace_json_parse(node) if node_parses_json?(node)

  node.children.each { |child| replace_json_parses(child) }
end
replace_require_paths_assignment(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 324
def replace_require_paths_assignment(node)
  replace(node.children.last.loc.expression, "['lib']")
end
replace_require_paths_assignments(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 126
def replace_require_paths_assignments(node)
  return unless node.is_a?(Parser::AST::Node)

  return replace_require_paths_assignment(node) if node_assigns_require_paths?(node)

  node.children.each do |child|
    replace_require_paths_assignments(child)
  end
end
replace_version_assignments(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 100
def replace_version_assignments(node)
  return unless node.is_a?(Parser::AST::Node)

  return replace_constant(node) if node_assigns_to_version_constant?(node)

  node.children.each { |child| replace_version_assignments(child) }
end
replace_version_constant_references(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 108
def replace_version_constant_references(node)
  return unless node.is_a?(Parser::AST::Node)

  return replace(node.loc.expression, %("#{replacement_version}")) if node_is_version_constant?(node)

  node.children.each do |child|
    replace_version_constant_references(child)
  end
end
requires_file?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 86
def requires_file?(node)
  %i(require require_relative).include?(node.children[1])
end
unnecessary_assignment?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 273
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
end
wrap_require(node) click to toggle source
# File lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb, line 90
def wrap_require(node)
  replace(
    node.loc.expression,
    "begin\n"\
    "#{node.loc.expression.source_line}\n"\
    "rescue LoadError\n"\
    "end"
  )
end