class RuboCop::Cop::RSpec::ChangeByZero
Prefer negated matchers over ‘to change.by(0)`.
In the case of composite expectations, cop suggest using the negation matchers of ‘RSpec::Matchers#change`.
By default the cop does not support autocorrect of compound expectations, but if you set the negated matcher for ‘change`, e.g. `not_change` with the `NegatedMatcher` option, the cop will perform the autocorrection.
@example NegatedMatcher: ~ (default)
# bad expect { run }.to change(Foo, :bar).by(0) expect { run }.to change { Foo.bar }.by(0) # bad - compound expectations (does not support autocorrection) expect { run } .to change(Foo, :bar).by(0) .and change(Foo, :baz).by(0) expect { run } .to change { Foo.bar }.by(0) .and change { Foo.baz }.by(0) # good expect { run }.not_to change(Foo, :bar) expect { run }.not_to change { Foo.bar } # good - compound expectations define_negated_matcher :not_change, :change expect { run } .to not_change(Foo, :bar) .and not_change(Foo, :baz) expect { run } .to not_change { Foo.bar } .and not_change { Foo.baz }
@example NegatedMatcher: not_change
# bad (support autocorrection to good case) expect { run } .to change(Foo, :bar).by(0) .and change(Foo, :baz).by(0) expect { run } .to change { Foo.bar }.by(0) .and change { Foo.baz }.by(0) # good define_negated_matcher :not_change, :change expect { run } .to not_change(Foo, :bar) .and not_change(Foo, :baz) expect { run } .to not_change { Foo.bar } .and not_change { Foo.baz }
Constants
- CHANGE_METHODS
- MSG
- MSG_COMPOUND
- RESTRICT_ON_SEND
Public Instance Methods
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 92 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
Private Instance Methods
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 133 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
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 140 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
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 120 def compound_expectations?(node) %i[and or & |].include?(node.parent.method_name) end
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 150 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
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 124 def message(change_node) format(MSG, method: change_node.method_name) end
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 128 def message_compound(change_node) format(MSG_COMPOUND, preferred: preferred_method, method: change_node.method_name) end
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 174 def negated_matcher cop_config['NegatedMatcher'] end
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 178 def preferred_method negated_matcher ? "`#{negated_matcher}`" : 'negated matchers' end
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 104 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
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 163 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
Source
# File lib/rubocop/cop/rspec/change_by_zero.rb, line 159 def replace_node(node, change_node) expect_change_with_arguments(node) ? change_node : change_node.parent end