Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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