lib/rubocop/cop/rspec/change_by_zero.rb in rubocop-rspec-2.12.1 vs lib/rubocop/cop/rspec/change_by_zero.rb in rubocop-rspec-2.13.0

- old
+ new

@@ -3,16 +3,24 @@ module RuboCop module Cop module RSpec # Prefer negated matchers over `to change.by(0)`. # - # @example + # 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 + # # 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) @@ -29,14 +37,32 @@ # .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 } + # class ChangeByZero < Base extend AutoCorrector MSG = 'Prefer `not_to change` over `to change.by(0)`.' - MSG_COMPOUND = 'Prefer negated matchers with compound expectations ' \ + MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \ 'over `change.by(0)`.' RESTRICT_ON_SEND = %i[change].freeze # @!method expect_change_with_arguments(node) def_node_matcher :expect_change_with_arguments, <<-PATTERN @@ -53,10 +79,15 @@ (args) (send (...) $_)) :by (int 0)) PATTERN + # @!method change_nodes(node) + def_node_search :change_nodes, <<-PATTERN + $(send nil? :change ...) + PATTERN + def on_send(node) expect_change_with_arguments(node.parent) do check_offense(node.parent) end @@ -68,25 +99,49 @@ private def check_offense(node) expression = node.loc.expression if compound_expectations?(node) - add_offense(expression, message: MSG_COMPOUND) + add_offense(expression, message: message_compound) do |corrector| + autocorrect_compound(corrector, node) + end else add_offense(expression) do |corrector| autocorrect(corrector, node) end end end def compound_expectations?(node) - %i[and or].include?(node.parent.method_name) + %i[and or & |].include?(node.parent.method_name) end def autocorrect(corrector, node) corrector.replace(node.parent.loc.selector, 'not_to') range = node.loc.dot.with(end_pos: node.loc.expression.end_pos) corrector.remove(range) + end + + def autocorrect_compound(corrector, node) + return unless negated_matcher + + change_nodes(node) do |change_node| + corrector.replace(change_node.loc.selector, negated_matcher) + range = node.loc.dot.with(end_pos: node.loc.expression.end_pos) + corrector.remove(range) + end + end + + def negated_matcher + cop_config['NegatedMatcher'] + end + + def message_compound + format(MSG_COMPOUND, preferred: preferred_method) + end + + def preferred_method + negated_matcher ? "`#{negated_matcher}`" : 'negated matchers' end end end end end