Skip to content

Commit

Permalink
Add new RSpec/Rails/NegationBeValid cop
Browse files Browse the repository at this point in the history
Fix: #1660
  • Loading branch information
ydah committed Jul 20, 2023
1 parent 1da31d1 commit b98d41f
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .simplecov
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

SimpleCov.start do
enable_coverage :branch
minimum_coverage line: 99.60, branch: 94.84
minimum_coverage line: 99.60, branch: 94.77
add_filter '/spec/'
add_filter '/vendor/bundle/'
end
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Master (Unreleased)

- Add new `RSpec/Rails/NegationBeValid` cop. ([@ydah])
- Fix a false negative for `RSpec/ExcessiveDocstringSpacing` when finds description with em space. ([@ydah])
- Fix a false positive for `RSpec/EmptyExampleGroup` when example group with examples defined in `if` branch inside iterator. ([@ydah])
- Update the message output of `RSpec/ExpectActual` to include the word 'value'. ([@corydiamand])
Expand Down
10 changes: 10 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,16 @@ RSpec/Rails/MinitestAssertions:
VersionAdded: '2.17'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/MinitestAssertions

RSpec/Rails/NegationBeValid:
Description: Enforces use of `be_invalid` or `not_to` for negated be_valid.
EnforcedStyle: not_to
SupportedStyles:
- not_to
- be_invalid
Enabled: pending
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/NegationBeValid

RSpec/Rails/TravelAround:
Description: Prefer to travel in `before` rather than `around`.
Enabled: pending
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 @@ -128,6 +128,7 @@
* xref:cops_rspec_rails.adoc#rspecrails/httpstatus[RSpec/Rails/HttpStatus]
* xref:cops_rspec_rails.adoc#rspecrails/inferredspectype[RSpec/Rails/InferredSpecType]
* xref:cops_rspec_rails.adoc#rspecrails/minitestassertions[RSpec/Rails/MinitestAssertions]
* xref:cops_rspec_rails.adoc#rspecrails/negationbevalid[RSpec/Rails/NegationBeValid]
* xref:cops_rspec_rails.adoc#rspecrails/travelaround[RSpec/Rails/TravelAround]

// END_COP_LIST
52 changes: 52 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec_rails.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,58 @@ expect(b).not_to eq(a)

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/MinitestAssertions

== RSpec/Rails/NegationBeValid

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Pending
| Yes
| Yes
| <<next>>
| -
|===

Enforces use of `be_invalid` or `not_to` for negated be_valid.

=== Examples

==== EnforcedStyle: not_to (default)

[source,ruby]
----
# bad
expect(foo).to be_invalid
# good
expect(foo).not_to be_valid
----

==== EnforcedStyle: be_invalid

[source,ruby]
----
# bad
expect(foo).not_to be_valid
# good
expect(foo).to be_invalid
----

=== Configurable attributes

|===
| Name | Default value | Configurable values

| EnforcedStyle
| `not_to`
| `not_to`, `be_invalid`
|===

=== References

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/NegationBeValid

== RSpec/Rails/TravelAround

|===
Expand Down
92 changes: 92 additions & 0 deletions lib/rubocop/cop/rspec/rails/negation_be_valid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
module Rails
# Enforces use of `be_invalid` or `not_to` for negated be_valid.
#
# @example EnforcedStyle: not_to (default)
# # bad
# expect(foo).to be_invalid
#
# # good
# expect(foo).not_to be_valid
#
# @example EnforcedStyle: be_invalid
# # bad
# expect(foo).not_to be_valid
#
# # good
# expect(foo).to be_invalid
#
class NegationBeValid < Base
extend AutoCorrector
include ConfigurableEnforcedStyle

MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
RESTRICT_ON_SEND = %i[be_valid be_invalid].freeze

# @!method not_to?(node)
def_node_matcher :not_to?, <<~PATTERN
(send ... :not_to (send nil? :be_valid ...))
PATTERN

# @!method be_invalid?(node)
def_node_matcher :be_invalid?, <<~PATTERN
(send ... :to (send nil? :be_invalid ...))
PATTERN

def on_send(node)
return unless offense?(node.parent)

add_offense(offense_range(node),
message: message(node.method_name)) do |corrector|
corrector.replace(node.parent.loc.selector, replaced_runner)
corrector.replace(node.loc.selector, replaced_matcher)
end
end

private

def offense?(node)
case style
when :not_to
be_invalid?(node)
when :be_invalid
not_to?(node)
end
end

def offense_range(node)
node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
end

def message(_matcher)
format(MSG,
runner: replaced_runner,
matcher: replaced_matcher)
end

def replaced_runner
case style
when :not_to
'not_to'
when :be_invalid
'to'
end
end

def replaced_matcher
case style
when :not_to
'be_valid'
when :be_invalid
'be_invalid'
end
end
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 @@ -18,6 +18,7 @@

require_relative 'rspec/rails/avoid_setup_hook'
require_relative 'rspec/rails/have_http_status'
require_relative 'rspec/rails/negation_be_valid'
begin
require_relative 'rspec/rails/http_status'
rescue LoadError
Expand Down
57 changes: 57 additions & 0 deletions spec/rubocop/cop/rspec/rails/negation_be_valid_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::Rails::NegationBeValid do
let(:cop_config) { { 'EnforcedStyle' => enforced_style } }

context 'with EnforcedStyle `not_to`' do
let(:enforced_style) { 'not_to' }

it 'registers an offense when using ' \
'`expect(...).to be_invalid`' do
expect_offense(<<~RUBY)
expect(foo).to be_invalid
^^^^^^^^^^^^^ Use `expect(...).not_to be_valid`.
RUBY
end

it 'does not register an offense when using ' \
'`expect(...).not_to be_valid`' do
expect_no_offenses(<<~RUBY)
expect(foo).not_to be_valid
RUBY
end

it 'does not register an offense when using ' \
'`expect(...).to be_valid`' do
expect_no_offenses(<<~RUBY)
expect(foo).to be_valid
RUBY
end
end

context 'with EnforcedStyle `be_invalid`' do
let(:enforced_style) { 'be_invalid' }

it 'registers an offense when using ' \
'`expect(...).not_to be_valid`' do
expect_offense(<<~RUBY)
expect(foo).not_to be_valid
^^^^^^^^^^^^^^^ Use `expect(...).to be_invalid`.
RUBY
end

it 'does not register an offense when using ' \
'`expect(...).to be_invalid`' do
expect_no_offenses(<<~RUBY)
expect(foo).to be_invalid
RUBY
end

it 'does not register an offense when using ' \
'`expect(...).to be_valid`' do
expect_no_offenses(<<~RUBY)
expect(foo).to be_valid
RUBY
end
end
end

0 comments on commit b98d41f

Please sign in to comment.