Skip to content

Commit

Permalink
Make RSpec/BeNil cop configurable
Browse files Browse the repository at this point in the history
Make `RSpec/BeNil` cop configurable with a `be_nil` and a `be` style.

Inspired by @marcandre's comment at
#1239 (comment)

    I see absolutely no reason to prefer `be_nil` to `be nil`. The
    latter is stricter, simpler and requires less knowledge of rspec.
  • Loading branch information
bquorning committed Mar 21, 2022
1 parent 072ffe9 commit 01ae44a
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Master (Unreleased)

* Fix a false positive for `RSpec/EmptyExampleGroup` when expectations in case statement. ([@ydah][])
* Make `RSpec/BeNil` cop configurable with a `be_nil` style and a `be` style. ([@bquorning][])

## 2.9.0 (2022-02-28)

Expand Down
7 changes: 6 additions & 1 deletion config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,14 @@ RSpec/BeEql:
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEql

RSpec/BeNil:
Description: Check that `be_nil` is used instead of `be(nil)`.
Description: Ensures a consistent style is used when matching `nil`.
Enabled: pending
EnforcedStyle: be_nil
SupportedStyles:
- be
- be_nil
VersionAdded: 2.9.0
VersionChanged: 2.10.0
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeNil

RSpec/BeforeAfterAll:
Expand Down
33 changes: 29 additions & 4 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -283,16 +283,20 @@ expect(foo).to be(nil)
| Yes
| Yes
| 2.9.0
| -
| 3.0.0
|===

Check that `be_nil` is used instead of `be(nil)`.
Ensures a consistent style is used when matching `nil`.

You can either use the more specific `be_nil` matcher, or the more
generic `be` matcher with a `nil` argument.

RSpec has a built-in `be_nil` matcher specifically for expecting `nil`.
For consistent specs, we recommend using that instead of `be(nil)`.
This cop can be configured using the `EnforcedStyle` option

=== Examples

==== `EnforcedStyle: be_nil`

[source,ruby]
----
# bad
Expand All @@ -302,6 +306,27 @@ expect(foo).to be(nil)
expect(foo).to be_nil
----

==== `EnforcedStyle: be`

[source,ruby]
----
# bad
expect(foo).to be_nil
# good
expect(foo).to be(nil)
----

=== Configurable attributes

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

| EnforcedStyle
| `be_nil`
| `be`, `be_nil`
|===

=== References

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeNil
Expand Down
48 changes: 41 additions & 7 deletions lib/rubocop/cop/rspec/be_nil.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,68 @@
module RuboCop
module Cop
module RSpec
# Check that `be_nil` is used instead of `be(nil)`.
# Ensures a consistent style is used when matching `nil`.
#
# RSpec has a built-in `be_nil` matcher specifically for expecting `nil`.
# For consistent specs, we recommend using that instead of `be(nil)`.
# You can either use the more specific `be_nil` matcher, or the more
# generic `be` matcher with a `nil` argument.
#
# @example
# This cop can be configured using the `EnforcedStyle` option
#
# @example `EnforcedStyle: be_nil`
# # bad
# expect(foo).to be(nil)
#
# # good
# expect(foo).to be_nil
#
# @example `EnforcedStyle: be`
# # bad
# expect(foo).to be_nil
#
# # good
# expect(foo).to be(nil)
#
class BeNil < Base
extend AutoCorrector
include ConfigurableEnforcedStyle

MSG = 'Prefer `be_nil` over `be(nil)`.'
RESTRICT_ON_SEND = %i[be].freeze
BE_MSG = 'Prefer `be(nil)` over `be_nil`.'
BE_NIL_MSG = 'Prefer `be_nil` over `be(nil)`.'
RESTRICT_ON_SEND = %i[be be_nil].freeze

# @!method be_nil_matcher?(node)
def_node_matcher :be_nil_matcher?, <<-PATTERN
(send nil? :be_nil)
PATTERN

# @!method nil_value_expectation?(node)
def_node_matcher :nil_value_expectation?, <<-PATTERN
(send nil? :be nil)
PATTERN

def on_send(node)
case style
when :be
check_be_style(node)
when :be_nil
check_be_nil_style(node)
end
end

private

def check_be_style(node)
return unless be_nil_matcher?(node)

add_offense(node, message: BE_MSG) do |corrector|
corrector.replace(node.loc.expression, 'be(nil)')
end
end

def check_be_nil_style(node)
return unless nil_value_expectation?(node)

add_offense(node) do |corrector|
add_offense(node, message: BE_NIL_MSG) do |corrector|
corrector.replace(node.loc.expression, 'be_nil')
end
end
Expand Down
79 changes: 58 additions & 21 deletions spec/rubocop/cop/rspec/be_nil_spec.rb
Original file line number Diff line number Diff line change
@@ -1,30 +1,67 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::BeNil do
it 'registers an offense when using `#be` for `nil` value' do
expect_offense(<<~RUBY)
expect(foo).to be(nil)
^^^^^^^ Prefer `be_nil` over `be(nil)`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to be_nil
RUBY
let(:cop_config) do
{ 'EnforcedStyle' => enforced_style }
end

it 'does not register an offense when using `#be_nil`' do
expect_no_offenses(<<~RUBY)
expect(foo).to be_nil
RUBY
context 'with EnforcedStyle `be_nil`' do
let(:enforced_style) { 'be_nil' }

it 'registers an offense when using `#be` for `nil` value' do
expect_offense(<<~RUBY)
expect(foo).to be(nil)
^^^^^^^ Prefer `be_nil` over `be(nil)`.
RUBY

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

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

it 'does not register an offense when using `#be` with other values' do
expect_no_offenses(<<~RUBY)
expect(foo).to be(true)
expect(foo).to be(false)
expect(foo).to be(1)
expect(foo).to be("yes")
expect(foo).to be(Class.new)
RUBY
end
end

it 'does not register an offense when using `#be` with other values' do
expect_no_offenses(<<~RUBY)
expect(foo).to be(true)
expect(foo).to be(false)
expect(foo).to be(1)
expect(foo).to be("yes")
expect(foo).to be(Class.new)
RUBY
context 'with EnforcedStyle `be`' do
let(:enforced_style) { 'be' }

it 'does not register an offense when using `#be` for `nil` value' do
expect_no_offenses(<<~RUBY)
expect(foo).to be(nil)
RUBY
end

it 'registers an offense when using `#be_nil`' do
expect_offense(<<~RUBY)
expect(foo).to be_nil
^^^^^^ Prefer `be(nil)` over `be_nil`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to be(nil)
RUBY
end

it 'does not register an offense when using other `#be_*` methods' do
expect_no_offenses(<<~RUBY)
expect(foo).to be_truthy
expect(foo).to be_falsey
expect(foo).to be_fooish
RUBY
end
end
end

0 comments on commit 01ae44a

Please sign in to comment.