class RuboCop::Cop::RSpec::ReceiveMessages

Checks for multiple messages stubbed on the same object.

@safety

The autocorrection is marked as unsafe, because it may change the
order of stubs. This in turn may cause e.g. variables to be called
before they are defined.

@example

# bad
before do
  allow(Service).to receive(:foo).and_return(bar)
  allow(Service).to receive(:baz).and_return(qux)
end

# good
before do
  allow(Service).to receive_messages(foo: bar, baz: qux)
end

# good - ignore same message
before do
  allow(Service).to receive(:foo).and_return(bar)
  allow(Service).to receive(:foo).and_return(qux)
end

Constants

MSG

Public Instance Methods

on_begin(node) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 63
def on_begin(node)
  repeated_receive_message(node).each do |item, repeated_lines, args|
    next if repeated_lines.empty?

    register_offense(item, repeated_lines, args)
  end
end

Private Instance Methods

add_repeated_lines_and_arguments(items) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 83
def add_repeated_lines_and_arguments(items)
  uniq_items = uniq_items(items)
  repeated_lines = uniq_items.map(&:first_line)
  uniq_items.map do |item|
    [item, repeated_lines - [item.first_line], arguments(uniq_items)]
  end
end
arguments(items) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 100
def arguments(items)
  items.map do |item|
    receive_and_return_argument(item) do |receive_arg, return_arg|
      "#{normalize_receive_arg(receive_arg)}: " \
        "#{normalize_return_arg(return_arg)}"
    end
  end
end
heredoc_or_splat?(node) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 150
def heredoc_or_splat?(node)
  ((node.str_type? || node.dstr_type?) && node.heredoc?) ||
    node.splat_type?
end
item_range_by_whole_lines(item) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 146
def item_range_by_whole_lines(item)
  range_by_whole_lines(item.source_range, include_final_newline: true)
end
message(repeated_lines) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 135
def message(repeated_lines)
  format(MSG, loc: repeated_lines)
end
normalize_receive_arg(receive_arg) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 109
def normalize_receive_arg(receive_arg)
  if requires_quotes?(receive_arg)
    "'#{receive_arg}'"
  else
    receive_arg
  end
end
normalize_return_arg(return_arg) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 117
def normalize_return_arg(return_arg)
  if return_arg.hash_type? && !return_arg.braces?
    "{ #{return_arg.source} }"
  else
    return_arg.source
  end
end
register_offense(item, repeated_lines, args) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 125
def register_offense(item, repeated_lines, args)
  add_offense(item, message: message(repeated_lines)) do |corrector|
    if item.loc.line > repeated_lines.max
      replace_to_receive_messages(corrector, item, args)
    else
      corrector.remove(item_range_by_whole_lines(item))
    end
  end
end
repeated_receive_message(node) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 73
def repeated_receive_message(node)
  node
    .children
    .select { |child| allow_receive_message?(child) }
    .group_by { |child| allow_argument(child) }
    .values
    .reject(&:one?)
    .flat_map { |items| add_repeated_lines_and_arguments(items) }
end
replace_to_receive_messages(corrector, item, args) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 139
def replace_to_receive_messages(corrector, item, args)
  receive_node(item) do |node|
    corrector.replace(node,
                      "receive_messages(#{args.join(', ')})")
  end
end
requires_quotes?(value) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 155
def requires_quotes?(value)
  value.match?(/^:".*?"|=$|^\W+$/)
end
uniq_items(items) click to toggle source
# File lib/rubocop/cop/rspec/receive_messages.rb, line 91
def uniq_items(items)
  items.select do |item|
    items.none? do |i|
      receive_arg(item).first == receive_arg(i).first &&
        !same_line?(item, i)
    end
  end
end