class Dependabot::Bundler::FileUpdater::RequirementReplacer::Rewriter

Constants

EQUALITY_OPERATOR
SKIPPED_TYPES

TODO: Ideally we wouldn't have to ignore all of these, but implementing each one will be tricky.

Attributes

dependency[R]
file_type[R]
updated_requirement[R]

Public Class Methods

new(dependency:, file_type:, updated_requirement:, insert_if_bare:) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 78
def initialize(dependency:, file_type:, updated_requirement:,
               insert_if_bare:)
  @dependency          = dependency
  @file_type           = file_type
  @updated_requirement = updated_requirement
  @insert_if_bare      = insert_if_bare

  return if %i(gemfile gemspec).include?(file_type)

  raise "File type must be :gemfile or :gemspec. Got #{file_type}."
end

Public Instance Methods

on_send(node) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 90
def on_send(node)
  return unless declares_targeted_gem?(node)

  req_nodes = node.children[3..-1]
  req_nodes = req_nodes.reject { |child| child.type == :hash }

  return if req_nodes.none? && !insert_if_bare?
  return if req_nodes.any? { |n| SKIPPED_TYPES.include?(n.type) }

  quote_characters = extract_quote_characters_from(req_nodes)
  space_after_specifier = space_after_specifier?(req_nodes)
  use_equality_operator = use_equality_operator?(req_nodes)

  new_req = new_requirement_string(
    quote_characters: quote_characters,
    space_after_specifier: space_after_specifier,
    use_equality_operator: use_equality_operator
  )
  if req_nodes.any?
    replace(range_for(req_nodes), new_req)
  else
    insert_after(range_for(node.children[2..2]), ", #{new_req}")
  end
end

Private Instance Methods

declaration_methods() click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 123
def declaration_methods
  return %i(gem) if file_type == :gemfile

  %i(add_dependency add_runtime_dependency
     add_development_dependency)
end
declares_targeted_gem?(node) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 130
def declares_targeted_gem?(node)
  return false unless declaration_methods.include?(node.children[1])

  node.children[2].children.first == dependency.name
end
extract_quote_characters_from(requirement_nodes) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 136
def extract_quote_characters_from(requirement_nodes)
  return ['"', '"'] if requirement_nodes.none?

  case requirement_nodes.first.type
  when :str, :dstr, :sym
    [
      requirement_nodes.first.loc.begin.source,
      requirement_nodes.first.loc.end.source
    ]
  else
    [
      requirement_nodes.first.children.first.loc.begin.source,
      requirement_nodes.first.children.first.loc.end.source
    ]
  end
end
insert_if_bare?() click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 119
def insert_if_bare?
  @insert_if_bare
end
new_requirement_string(quote_characters:, space_after_specifier:, use_equality_operator:) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 186
def new_requirement_string(quote_characters:,
                           space_after_specifier:,
                           use_equality_operator:)
  open_quote, close_quote = quote_characters
  new_requirement_string =
    updated_requirement.split(",").
    map do |r|
      req_string = serialized_req(r, use_equality_operator)
      req_string = %(#{open_quote}#{req_string}#{close_quote})
      req_string = req_string.delete(" ") unless space_after_specifier
      req_string
    end.join(", ")

  new_requirement_string
end
range_for(nodes) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 213
def range_for(nodes)
  nodes.first.loc.begin.begin.join(nodes.last.loc.expression)
end
serialized_req(req, use_equality_operator) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 202
def serialized_req(req, use_equality_operator)
  tmp_req = req

  # Gem::Requirement serializes exact matches as a string starting
  # with `=`. We may need to remove that equality operator if it
  # wasn't used originally.
  tmp_req = tmp_req.gsub(EQUALITY_OPERATOR, "") unless use_equality_operator

  tmp_req.strip
end
space_after_specifier?(requirement_nodes) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 153
def space_after_specifier?(requirement_nodes)
  return true if requirement_nodes.none?

  req_string =
    case requirement_nodes.first.type
    when :str, :dstr, :sym
      requirement_nodes.first.loc.expression.source
    else
      requirement_nodes.first.children.first.loc.expression.source
    end

  ops = Gem::Requirement::OPS.keys
  return true if ops.none? { |op| req_string.include?(op) }

  req_string.include?(" ")
end
use_equality_operator?(requirement_nodes) click to toggle source
# File lib/dependabot/bundler/file_updater/requirement_replacer.rb, line 172
def use_equality_operator?(requirement_nodes)
  return true if requirement_nodes.none?

  req_string =
    case requirement_nodes.first.type
    when :str, :dstr, :sym
      requirement_nodes.first.loc.expression.source
    else
      requirement_nodes.first.children.first.loc.expression.source
    end

  req_string.match?(EQUALITY_OPERATOR)
end