Class: RuboCop::Cop::RSpec::ChangeByZero

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
RangeHelp
Defined in:
lib/rubocop/cop/rspec/change_by_zero.rb

Overview

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.

Examples:

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 }

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 }

Constant Summary collapse

MSG =
'Prefer `not_to change` over `to %<method>s.by(0)`.'
MSG_COMPOUND =
'Prefer %<preferred>s with compound expectations ' \
'over `%<method>s.by(0)`.'
CHANGE_METHODS =
Set[:change, :a_block_changing, :changing].freeze
RESTRICT_ON_SEND =
CHANGE_METHODS.freeze

Instance Method Summary collapse

Methods inherited from Base

inherited, #on_new_investigation

Methods included from RSpec::Language::NodePattern

#block_or_numblock_pattern, #block_pattern, #numblock_pattern, #send_pattern

Methods included from RSpec::Language

#example?, #example_group?, #example_group_with_body?, #explicit_rspec?, #hook?, #include?, #let?, #rspec?, #shared_group?, #spec_group?, #subject?

Instance Method Details

#change_nodes(node) ⇒ Object



88
89
90
# File 'lib/rubocop/cop/rspec/change_by_zero.rb', line 88

def_node_search :change_nodes, <<~PATTERN
  $(send nil? CHANGE_METHODS ...)
PATTERN

#expect_change_with_arguments(node) ⇒ Object



71
72
73
74
75
# File 'lib/rubocop/cop/rspec/change_by_zero.rb', line 71

def_node_matcher :expect_change_with_arguments, <<~PATTERN
  (send
    $(send nil? CHANGE_METHODS ...) :by
    (int 0))
PATTERN

#expect_change_with_block(node) ⇒ Object



78
79
80
81
82
83
84
85
# File 'lib/rubocop/cop/rspec/change_by_zero.rb', line 78

def_node_matcher :expect_change_with_block, <<~PATTERN
  (send
    (block
      $(send nil? CHANGE_METHODS)
      (args)
      (send (...) _)) :by
    (int 0))
PATTERN

#on_send(node) ⇒ Object



92
93
94
95
96
97
98
99
100
# 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