From 398ce54121a71c205cb5ad7bacc61d13806611bc Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Mon, 14 Aug 2023 16:11:08 +0900 Subject: [PATCH] Add new `RSpec/RedundantPredicateMatcher` cop Fix: https://github.com/rubocop/rubocop-rspec/issues/1689 --- .rubocop.yml | 2 + CHANGELOG.md | 3 + config/default.yml | 6 + docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_rspec.adoc | 31 ++++ .../cop/rspec/redundant_predicate_matcher.rb | 65 +++++++ lib/rubocop/cop/rspec_cops.rb | 1 + .../rspec/redundant_predicate_matcher_spec.rb | 159 ++++++++++++++++++ 8 files changed, 268 insertions(+) create mode 100644 lib/rubocop/cop/rspec/redundant_predicate_matcher.rb create mode 100644 spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index bf1be2c13..fec4631b4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -177,6 +177,8 @@ RSpec/ReceiveMessages: Enabled: true RSpec/RedundantAround: Enabled: true +RSpec/RedundantPredicateMatcher: + Enabled: true RSpec/SkipBlockInsideExample: Enabled: true RSpec/SortMetadata: diff --git a/CHANGELOG.md b/CHANGELOG.md index e7974d61b..de9c1330c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Master (Unreleased) +- Add new `RSpec/RedundantPredicateMatcher` cop. ([@ydah]) + ## 2.25.0 (2023-10-27) - Add support single quoted string and percent string and heredoc for `RSpec/Rails/HttpStatus`. ([@ydah]) @@ -21,6 +23,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) diff --git a/config/default.yml b/config/default.yml index f672f26f4..0429572b1 100644 --- a/config/default.yml +++ b/config/default.yml @@ -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: "<>" + 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 diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 9f0962c12..b2878c1a9 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -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] diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index f985c7e5f..dc309f3bf 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -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 +| <> +| - +|=== + +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 |=== diff --git a/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb b/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb new file mode 100644 index 000000000..2c3eec266 --- /dev/null +++ b/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb @@ -0,0 +1,65 @@ +# 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 `%s` instead of `%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? || node.arguments.empty? + return unless 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| + unless node.method?(:be_all) + corrector.replace(node.loc.selector, replaced) + end + 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 diff --git a/lib/rubocop/cop/rspec_cops.rb b/lib/rubocop/cop/rspec_cops.rb index b6d8d83d7..70add9ab0 100644 --- a/lib/rubocop/cop/rspec_cops.rb +++ b/lib/rubocop/cop/rspec_cops.rb @@ -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' diff --git a/spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb b/spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb new file mode 100644 index 000000000..22c25b919 --- /dev/null +++ b/spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb @@ -0,0 +1,159 @@ +# 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 `{}`' 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` with `do ... end`' do + expect_no_offenses(<<~RUBY) + expect(foo).to be_all do |bar| + bar == baz + end + 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 'does not register an offense when using `be_match` without argument' do + expect_no_offenses(<<~RUBY) + expect(foo).to be_match + 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