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