Skip to content

Commit

Permalink
Add new RSpec/NegatedExpectation cop
Browse files Browse the repository at this point in the history
This cop checks for expectations with `!`.
The autocorrection is marked as unsafe, because it may change the expectation from a positive to a negative one, or vice versa.

```ruby
!expect(foo).to be_valid

expect(foo).not_to be_valid
```
  • Loading branch information
ydah committed Mar 19, 2024
1 parent 879e116 commit fd112e0
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ RSpec/MatchArray:
Enabled: true
RSpec/MetadataStyle:
Enabled: true
RSpec/NegatedExpectation:
Enabled: true
RSpec/NoExpectationExample:
Enabled: true
RSpec/PendingWithoutReason:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Master (Unreleased)

- Add new `RSpec/NegatedExpectation` cop. ([@ydah])

## 2.27.1 (2024-03-03)

- Fix a false positive for `RSpec/RepeatedSubjectCall` when `subject.method_call`. ([@ydah])
Expand Down
7 changes: 7 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,13 @@ RSpec/NamedSubject:
StyleGuide: https://rspec.rubystyle.guide/#use-subject
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject

RSpec/NegatedExpectation:
Description: Checks for expectations with `!`.
Enabled: pending
SafeAutoCorrect: false
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NegatedExpectation

RSpec/NestedGroups:
Description: Checks for nested example groups.
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
* xref:cops_rspec.adoc#rspecmultiplememoizedhelpers[RSpec/MultipleMemoizedHelpers]
* xref:cops_rspec.adoc#rspecmultiplesubjects[RSpec/MultipleSubjects]
* xref:cops_rspec.adoc#rspecnamedsubject[RSpec/NamedSubject]
* xref:cops_rspec.adoc#rspecnegatedexpectation[RSpec/NegatedExpectation]
* xref:cops_rspec.adoc#rspecnestedgroups[RSpec/NestedGroups]
* xref:cops_rspec.adoc#rspecnoexpectationexample[RSpec/NoExpectationExample]
* xref:cops_rspec.adoc#rspecnottonot[RSpec/NotToNot]
Expand Down
34 changes: 34 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3888,6 +3888,40 @@ end
* https://rspec.rubystyle.guide/#use-subject
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject
== RSpec/NegatedExpectation
|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
| Pending
| Yes
| Always (Unsafe)
| <<next>>
| -
|===
Checks for expectations with `!`.
=== Safety
The autocorrection is marked as unsafe, because it may change the
expectation from a positive to a negative one, or vice versa.
=== Examples
[source,ruby]
----
# bad
!expect(foo).to be_valid
# good
expect(foo).not_to be_valid
----
=== References
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NegatedExpectation
== RSpec/NestedGroups
|===
Expand Down
51 changes: 51 additions & 0 deletions lib/rubocop/cop/rspec/negated_expectation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Checks for expectations with `!`.
#
# @safety
# The autocorrection is marked as unsafe, because it may change the
# expectation from a positive to a negative one, or vice versa.
#
# @example
# # bad
# !expect(foo).to be_valid
#
# # good
# expect(foo).not_to be_valid
#
class NegatedExpectation < Base
extend AutoCorrector

MSG = 'Use `expect(...).%<replaced>s` instead of ' \
'`!expect(...)`.%<runner>s'
RESTRICT_ON_SEND = Runners.all

def on_send(node)
return unless node.parent&.send_type?
return unless node.parent.method?(:!)

replaced = replaced(node)
add_offense(node.parent,
message: message(node, replaced)) do |corrector|
corrector.remove(node.parent.loc.selector)
corrector.replace(node.loc.selector, replaced)
end
end

private

def message(node, replaced)
format(MSG, runner: node.loc.selector.source, replaced: replaced)
end

def replaced(node)
runner = node.loc.selector.source
runner == 'to' ? 'not_to' : 'to'
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
require_relative 'rspec/multiple_memoized_helpers'
require_relative 'rspec/multiple_subjects'
require_relative 'rspec/named_subject'
require_relative 'rspec/negated_expectation'
require_relative 'rspec/nested_groups'
require_relative 'rspec/no_expectation_example'
require_relative 'rspec/not_to_not'
Expand Down
42 changes: 42 additions & 0 deletions spec/rubocop/cop/rspec/negated_expectations_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::NegatedExpectation, :config do
it 'registers an offense when using redundant negation with `.to`' do
expect_offense(<<~RUBY)
!expect(foo).to be_valid
^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(...).not_to` instead of `!expect(...)`.to
RUBY

expect_correction(<<~RUBY)
expect(foo).not_to be_valid
RUBY
end

it 'registers an offense when using redundant negation with `.not_to`' do
expect_offense(<<~RUBY)
!expect(foo).not_to be_valid
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(...).to` instead of `!expect(...)`.not_to
RUBY

expect_correction(<<~RUBY)
expect(foo).to be_valid
RUBY
end

it 'registers an offense when using redundant negation with `.to_not`' do
expect_offense(<<~RUBY)
!expect(foo).to_not be_valid
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(...).to` instead of `!expect(...)`.to_not
RUBY

expect_correction(<<~RUBY)
expect(foo).to be_valid
RUBY
end

it 'does not register an offense when without redundant negation' do
expect_no_offenses(<<~RUBY)
expect(foo).not_to be_valid
RUBY
end
end

0 comments on commit fd112e0

Please sign in to comment.