class RuboCop::Cop::RSpec::ChangeByZero


.and not_change { Foo.baz }
.to not_change { Foo.bar }
expect { run }
.and not_change(Foo, :baz)
.to not_change(Foo, :bar)
expect { run }
define_negated_matcher :not_change, :change
# good
.and change { Foo.baz }.by(0)
.to change { Foo.bar }.by(0)
expect { run }
.and change(Foo, :baz).by(0)
.to change(Foo, :bar).by(0)
expect { run }
# bad (support autocorrection to good case)
@example NegatedMatcher: not_change
.and not_change { Foo.baz }
.to not_change { Foo.bar }
expect { run }
.and not_change(Foo, :baz)
.to not_change(Foo, :bar)
expect { run }
define_negated_matcher :not_change, :change
# good - compound expectations
expect { run }.not_to change { Foo.bar }
expect { run }.not_to change(Foo, :bar)
# good
.and change { Foo.baz }.by(0)
.to change { Foo.bar }.by(0)
expect { run }
.and change(Foo, :baz).by(0)
.to change(Foo, :bar).by(0)
expect { run }
# bad - compound expectations (does not support autocorrection)
expect { run }.to change { Foo.bar }.by(0)
expect { run }.to change(Foo, :bar).by(0)
# bad
@example NegatedMatcher: ~ (default)
the ‘NegatedMatcher` option, the cop will perform the autocorrection.
negated matcher for `change`, e.g. `not_change` with
compound expectations, but if you set the
By default the cop does not support autocorrect of
negation matchers of `RSpec::Matchers#change`.
In the case of composite expectations, cop suggest using the
Prefer negated matchers over `to change.by(0)`.

def autocorrect(corrector, node, change_node)

def autocorrect(corrector, node, change_node)
  corrector.replace(node.parent.loc.selector, 'not_to')
  corrector.replace(change_node.loc.selector, 'change')
  range = node.loc.dot.with(end_pos: node.source_range.end_pos)
  corrector.remove(range)
end

def autocorrect_compound(corrector, node)

def autocorrect_compound(corrector, node)
  return unless negated_matcher
  change_nodes(node) do |change_node|
    corrector.replace(change_node.loc.selector, negated_matcher)
    insert_operator(corrector, node, change_node)
    remove_by_zero(corrector, node, change_node)
  end
end

def compound_expectations?(node)

def compound_expectations?(node)
  %i[and or & |].include?(node.parent.method_name)
end

def insert_operator(corrector, node, change_node)

def insert_operator(corrector, node, change_node)
  operator = node.right_siblings.first
  return unless %i[& |].include?(operator)
  corrector.insert_after(
    replace_node(node, change_node), " #{operator}"
  )
end

def message(change_node)

def message(change_node)
  format(MSG, method: change_node.method_name)
end

def message_compound(change_node)

def message_compound(change_node)
  format(MSG_COMPOUND, preferred: preferred_method,
                       method: change_node.method_name)
end

def negated_matcher

def negated_matcher
  cop_config['NegatedMatcher']
end

def on_send(node)

def on_send(node)
  expect_change_with_arguments(node.parent) do |change|
    register_offense(node.parent, change)
  end
  expect_change_with_block(node.parent.parent) do |change|
    register_offense(node.parent.parent, change)
  end
end

def preferred_method

def preferred_method
  negated_matcher ? "`#{negated_matcher}`" : 'negated matchers'
end

def register_offense(node, change_node)

def register_offense(node, change_node)
  return unless node.parent.send_type?
  if compound_expectations?(node)
    add_offense(node,
                message: message_compound(change_node)) do |corrector|
      autocorrect_compound(corrector, node)
    end
  else
    add_offense(node,
                message: message(change_node)) do |corrector|
      autocorrect(corrector, node, change_node)
    end
  end
end

def remove_by_zero(corrector, node, change_node)

def remove_by_zero(corrector, node, change_node)
  range = node.loc.dot.with(end_pos: node.source_range.end_pos)
  if change_node.loc.line == range.line
    corrector.remove(range)
  else
    corrector.remove(
      range_by_whole_lines(range, include_final_newline: true)
    )
  end
end

def replace_node(node, change_node)

def replace_node(node, change_node)
  expect_change_with_arguments(node) ? change_node : change_node.parent
end