Skip to content

Commit

Permalink
Add new RSpec/RedundantPredicateMatcher cop
Browse files Browse the repository at this point in the history
Fix: #1689
  • Loading branch information
ydah committed Oct 2, 2023
1 parent 7b930a0 commit ea4c936
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ RSpec/PendingWithoutReason:
Enabled: true
RSpec/RedundantAround:
Enabled: true
RSpec/RedundantPredicateMatcher:
Enabled: true
RSpec/SkipBlockInsideExample:
Enabled: true
RSpec/SortMetadata:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Master (Unreleased)

- Add support single quoted string and percent string and heredoc for `RSpec/Rails/HttpStatus`. ([@ydah])
- Add new `RSpec/RedundantPredicateMatcher` cop. ([@ydah])

## 2.24.1 (2023-09-23)

Expand All @@ -17,6 +18,7 @@
- Fix an infinite loop error when `RSpec/ExcessiveDocstringSpacing` finds a description with non-ASCII leading/trailing whitespace. ([@bcgraham])
- Fix an incorrect autocorrect for `RSpec/ReceiveMessages` when return values declared between stubs. ([@marocchino])
- Fix a false positive `RSpec/Focus` when chained method call and inside define method. ([@ydah])
- Split `RSpec/FilePath` into `RSpec/SpecFilePathSuffix` and `RSpec/SpecFilePathFormat`. `RSpec/FilePath` cop is enabled by default, the two new cups are pending and need to be enabled explicitly. ([@ydah])

## 2.23.2 (2023-08-09)

Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,12 @@ RSpec/RedundantAround:
VersionAdded: '2.19'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RedundantAround

RSpec/RedundantPredicateMatcher:
Description: Checks for redundant predicate matcher.
Enabled: pending
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RedundantPredicateMatcher

RSpec/RepeatedDescription:
Description: Check for repeated description strings in 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 @@ -80,6 +80,7 @@
* xref:cops_rspec.adoc#rspecreceivemessages[RSpec/ReceiveMessages]
* xref:cops_rspec.adoc#rspecreceivenever[RSpec/ReceiveNever]
* xref:cops_rspec.adoc#rspecredundantaround[RSpec/RedundantAround]
* xref:cops_rspec.adoc#rspecredundantpredicatematcher[RSpec/RedundantPredicateMatcher]
* xref:cops_rspec.adoc#rspecrepeateddescription[RSpec/RepeatedDescription]
* xref:cops_rspec.adoc#rspecrepeatedexample[RSpec/RepeatedExample]
* xref:cops_rspec.adoc#rspecrepeatedexamplegroupbody[RSpec/RepeatedExampleGroupBody]
Expand Down
31 changes: 31 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4491,6 +4491,37 @@ end

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

== RSpec/RedundantPredicateMatcher

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

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

Checks for redundant predicate matcher.

=== Examples

[source,ruby]
----
# bad
expect(foo).to be_exist(bar)
expect(foo).not_to be_include(bar)
# good
expect(foo).to exist(bar)
expect(foo).not_to include(bar)
----

=== References

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

== RSpec/RepeatedDescription

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

module RuboCop
module Cop
module RSpec
# Checks for redundant predicate matcher.
#
# @example
# # bad
# expect(foo).to be_exist(bar)
# expect(foo).not_to be_include(bar)
#
# # good
# expect(foo).to exist(bar)
# expect(foo).not_to include(bar)
#
class RedundantPredicateMatcher < Base
extend AutoCorrector

MSG = 'Use `%<good>s` instead of `%<bad>s`.'
RESTRICT_ON_SEND =
%i[be_all be_cover be_end_with be_eql be_equal
be_exist be_exists be_include be_match
be_respond_to be_start_with].freeze

def on_send(node)
return if node.parent.block_type? || !replacable_arguments?(node)

method_name = node.method_name.to_s
replaced = replaced_method_name(method_name)
add_offense(node, message: message(method_name,
replaced)) do |corrector|
corrector.replace(node.loc.selector, replaced) unless node.method?(:be_all)
end
end

private

def message(bad_method, good_method)
format(MSG, bad: bad_method, good: good_method)
end

def replacable_arguments?(node)
if node.method?(:be_all)
node.first_argument.send_type?
else
true
end
end

def replaced_method_name(method_name)
name = method_name.to_s.delete_prefix('be_')
if name == 'exists'
'exist'
else
name
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 @@ -106,6 +106,7 @@
require_relative 'rspec/receive_messages'
require_relative 'rspec/receive_never'
require_relative 'rspec/redundant_around'
require_relative 'rspec/redundant_predicate_matcher'
require_relative 'rspec/repeated_description'
require_relative 'rspec/repeated_example'
require_relative 'rspec/repeated_example_group_body'
Expand Down
145 changes: 145 additions & 0 deletions spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::RedundantPredicateMatcher do
it 'registers an offense when using `be_all`' do
expect_offense(<<~RUBY)
expect(foo).to be_all be_odd
^^^^^^^^^^^^^ Use `all` instead of `be_all`.
expect(foo).to be_all(expected_foo_element_value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `all` instead of `be_all`.
RUBY

expect_no_corrections
end

it 'does not register an offense when using `be_all` with block' do
expect_no_offenses(<<~RUBY)
expect(foo).to be_all { |bar| bar == baz }
RUBY
end

it 'does not register an offense when using `be_all` without send' do
expect_no_offenses(<<~RUBY)
expect(foo).to be_all(false)
expect(foo).to be_all(1)
RUBY
end

it 'registers an offense when using `be_cover`' do
expect_offense(<<~RUBY)
expect(foo).to be_cover(bar, baz)
^^^^^^^^^^^^^^^^^^ Use `cover` instead of `be_cover`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to cover(bar, baz)
RUBY
end

it 'registers an offense when using `be_end_with`' do
expect_offense(<<~RUBY)
expect(foo).to be_end_with('?')
^^^^^^^^^^^^^^^^ Use `end_with` instead of `be_end_with`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to end_with('?')
RUBY
end

it 'registers an offense when using `be_eql`' do
expect_offense(<<~RUBY)
expect(foo).to be_eql(bar)
^^^^^^^^^^^ Use `eql` instead of `be_eql`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to eql(bar)
RUBY
end

it 'registers an offense when using `be_equal`' do
expect_offense(<<~RUBY)
expect(foo).to be_equal(bar)
^^^^^^^^^^^^^ Use `equal` instead of `be_equal`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to equal(bar)
RUBY
end

it 'registers an offense when using `be_exist`' do
expect_offense(<<~RUBY)
expect(foo).to be_exist("bar.txt")
^^^^^^^^^^^^^^^^^^^ Use `exist` instead of `be_exist`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to exist("bar.txt")
RUBY
end

it 'registers an offense when using `be_exists`' do
expect_offense(<<~RUBY)
expect(foo).to be_exists("bar.txt")
^^^^^^^^^^^^^^^^^^^^ Use `exist` instead of `be_exists`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to exist("bar.txt")
RUBY
end

it 'registers an offense when using `be_include`' do
expect_offense(<<~RUBY)
expect(foo).to be_include(bar, baz)
^^^^^^^^^^^^^^^^^^^^ Use `include` instead of `be_include`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to include(bar, baz)
RUBY
end

it 'registers an offense when using `be_match`' do
expect_offense(<<~RUBY)
expect(foo).to be_match(/bar/)
^^^^^^^^^^^^^^^ Use `match` instead of `be_match`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to match(/bar/)
RUBY
end

it 'registers an offense when using `be_respond_to`' do
expect_offense(<<~RUBY)
expect("string").to be_respond_to(:length)
^^^^^^^^^^^^^^^^^^^^^^ Use `respond_to` instead of `be_respond_to`.
RUBY

expect_correction(<<~RUBY)
expect("string").to respond_to(:length)
RUBY
end

it 'registers an offense when using `be_start_with`' do
expect_offense(<<~RUBY)
expect(foo).to be_start_with("A")
^^^^^^^^^^^^^^^^^^ Use `start_with` instead of `be_start_with`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to start_with("A")
RUBY
end

it 'does not register an offense when using built-in matcher' do
expect_no_offenses(<<~RUBY)
expect(foo).to all(bar)
expect(foo).to cover(bar)
expect(foo).to end_with(bar)
RUBY
end
end

0 comments on commit ea4c936

Please sign in to comment.