From 4fb0c6aef82cde1c1ad0b3a150c34852a4eb9c97 Mon Sep 17 00:00:00 2001 From: ydah Date: Fri, 3 Oct 2025 01:30:28 +0900 Subject: [PATCH] Add SupportedMethods configuration to `RSpec/RedundantPredicateMatcher` cop This PR add SupportedMethods configuration to `RSpec/RedundantPredicateMatcher` cop. Fixes: https://github.com/rubocop/rubocop-rspec/issues/2012 This change involves trade-offs. Previously, we could specify replaceable method names in a hard-coded manner, which allowed us to use RESTRICT_ON_SEND. However, now that the decision is based on values set in the config, RESTRICT_ON_SEND can no longer be used. Additionally, since `be_all` requires special internal processing, it's difficult to create this cop generically with Config, leaving special handling for `be_all` inside the implementation. However, this change enables the `ResultPredictedMatcher` cop to determine which cops trigger violations through Config. For example, when using Cucumber, this allows you to exclude only `match` from violations. As mentioned earlier though, this change involves trade-offs, so if these trade-offs are unacceptable, we need to discard this change. I think it's fine to discard it if necessary. --- config/default.yml | 13 ++++ docs/modules/ROOT/pages/cops_rspec.adoc | 18 +++++- .../cop/rspec/redundant_predicate_matcher.rb | 61 +++++++++++-------- .../rspec/redundant_predicate_matcher_spec.rb | 23 +++++++ 4 files changed, 88 insertions(+), 27 deletions(-) diff --git a/config/default.yml b/config/default.yml index f8678689c..ab26b15a8 100644 --- a/config/default.yml +++ b/config/default.yml @@ -818,7 +818,20 @@ RSpec/RedundantAround: RSpec/RedundantPredicateMatcher: Description: Checks for redundant predicate matcher. Enabled: true + SupportedMethods: + be_all: all + be_cover: cover + be_end_with: end_with + be_eql: eql + be_equal: equal + be_exist: exist + be_exists: exist + be_include: include + be_match: match + be_respond_to: respond_to + be_start_with: start_with VersionAdded: '2.26' + VersionChanged: "<>" Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RedundantPredicateMatcher RSpec/RemoveConst: diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index 3de155ea6..d090c809d 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -5155,11 +5155,14 @@ end | Yes | Always | 2.26 -| - +| <> |=== Checks for redundant predicate matcher. +This cop configures the mapping of redundant predicate matchers +and their preferable alternatives via `SupportedMethods` option. + [#examples-rspecredundantpredicatematcher] === Examples @@ -5176,6 +5179,19 @@ expect(foo).not_to include(bar) expect(foo).to all be(bar) ---- +[#_supportedmethods_-_be_exist_-exist_-be_include_-include__-rspecredundantpredicatematcher] +==== `SupportedMethods: {be_exist: exist, be_include: include}` + +[source,ruby] +---- +# good +expect(foo).to exist(bar) +expect(foo).not_to include(bar) + +# bad +expect(foo).to all be(bar) +---- + [#references-rspecredundantpredicatematcher] === References diff --git a/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb b/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb index f04992e5d..fabc09274 100644 --- a/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +++ b/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb @@ -5,6 +5,9 @@ module Cop module RSpec # Checks for redundant predicate matcher. # + # This cop configures the mapping of redundant predicate matchers + # and their preferable alternatives via `SupportedMethods` option. + # # @example # # bad # expect(foo).to be_exist(bar) @@ -16,50 +19,56 @@ module RSpec # expect(foo).not_to include(bar) # expect(foo).to all be(bar) # + # @example `SupportedMethods: {be_exist: exist, be_include: include}` + # # good + # expect(foo).to exist(bar) + # expect(foo).not_to include(bar) + # + # # bad + # expect(foo).to all be(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 + UNSUPPORTED_AUTOCORRECT_METHODS = %w[be_all].freeze def on_send(node) + return if node.parent.nil? return if node.parent.block_type? || node.arguments.empty? - return unless replaceable_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 + return unless supported_methods.key?(method_name) + return unless valid_arguments?(node) + + register_offense(node, method_name) end private - def message(bad_method, good_method) - format(MSG, bad: bad_method, good: good_method) + def valid_arguments?(node) + return true unless node.method?(:be_all) + + node.first_argument.send_type? end - def replaceable_arguments?(node) - if node.method?(:be_all) - node.first_argument.send_type? - else - true + def register_offense(node, method_name) + replacement = supported_methods[method_name] + + add_offense(node, message: message(method_name, + replacement)) do |corrector| + unless UNSUPPORTED_AUTOCORRECT_METHODS.include?(method_name) + corrector.replace(node.loc.selector, replacement) + end end end - def replaced_method_name(method_name) - name = method_name.to_s.delete_prefix('be_') - if name == 'exists' - 'exist' - else - name - end + def message(bad_method, good_method) + format(MSG, bad: bad_method, good: good_method) + end + + def supported_methods + @supported_methods ||= cop_config.fetch('SupportedMethods', {}) end end end diff --git a/spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb b/spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb index 22c25b919..6945024db 100644 --- a/spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb +++ b/spec/rubocop/cop/rspec/redundant_predicate_matcher_spec.rb @@ -156,4 +156,27 @@ expect(foo).to end_with(bar) RUBY end + + context 'when `SupportedMethods` is customized' do + let(:cop_config) do + { 'SupportedMethods' => { 'be_include' => 'include' } } + 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 'does not register an offense when using `be_exist`' do + expect_no_offenses(<<~RUBY) + expect(foo).to be_exist("bar.txt") + RUBY + end + end end