From 18cbdfbf1fa0331695b8f7da01cc0a2c43f87e31 Mon Sep 17 00:00:00 2001 From: ydah Date: Tue, 27 Feb 2024 17:06:35 +0900 Subject: [PATCH] Add new `RSpec/IsExpectedSpecify` cop Fix: https://github.com/rubocop/rubocop-rspec/issues/1658 --- .rubocop.yml | 2 + CHANGELOG.md | 1 + config/default.yml | 7 +++ docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_rspec.adoc | 36 +++++++++++++++ lib/rubocop/cop/rspec/is_expected_specify.rb | 45 +++++++++++++++++++ lib/rubocop/cop/rspec_cops.rb | 1 + .../cop/rspec/is_expected_specify_spec.rb | 44 ++++++++++++++++++ 8 files changed, 137 insertions(+) create mode 100644 lib/rubocop/cop/rspec/is_expected_specify.rb create mode 100644 spec/rubocop/cop/rspec/is_expected_specify_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index e0fcb1184..9acc4e2af 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -165,6 +165,8 @@ RSpec/IdenticalEqualityAssertion: Enabled: true RSpec/IndexedLet: Enabled: true +RSpec/IsExpectedSpecify: + Enabled: true RSpec/MatchArray: Enabled: true RSpec/MetadataStyle: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce64d2c1..e00f5a89a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Master (Unreleased) +- Add new `RSpec/IsExpectedSpecify` cop. ([@ydah]) - Add support for `assert_true` and `assert_false` to `RSpec/Rails/MinitestAssertions`. ([@ydah]) - Support asserts with messages in `Rspec/BeEmpty`. ([@G-Rath]) - Add support for `assert_empty`, `assert_not_empty` and `refute_empty` to `RSpec/Rails/MinitestAssertions`. ([@ydah]) diff --git a/config/default.yml b/config/default.yml index cf19621df..5333595db 100644 --- a/config/default.yml +++ b/config/default.yml @@ -548,6 +548,13 @@ RSpec/InstanceVariable: StyleGuide: https://rspec.rubystyle.guide/#instance-variables Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceVariable +RSpec/IsExpectedSpecify: + Description: Check for `specify` with `is_expected` and one-liner expectations. + Enabled: pending + VersionAdded: "<>" + StyleGuide: https://rspec.rubystyle.guide/#it-and-specify + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IsExpectedSpecify + RSpec/ItBehavesLike: Description: Checks that only one `it_behaves_like` style is used. Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index ab201b97d..4dfed17f0 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -52,6 +52,7 @@ * xref:cops_rspec.adoc#rspecindexedlet[RSpec/IndexedLet] * xref:cops_rspec.adoc#rspecinstancespy[RSpec/InstanceSpy] * xref:cops_rspec.adoc#rspecinstancevariable[RSpec/InstanceVariable] +* xref:cops_rspec.adoc#rspecisexpectedspecify[RSpec/IsExpectedSpecify] * xref:cops_rspec.adoc#rspecitbehaveslike[RSpec/ItBehavesLike] * xref:cops_rspec.adoc#rspeciteratedexpectation[RSpec/IteratedExpectation] * xref:cops_rspec.adoc#rspecleadingsubject[RSpec/LeadingSubject] diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index e5b0f7182..346d9940a 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -2747,6 +2747,42 @@ end * https://rspec.rubystyle.guide/#instance-variables * https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceVariable +== RSpec/IsExpectedSpecify + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes +| <> +| - +|=== + +Check for `specify` with `is_expected` and one-liner expectations. + +=== Examples + +[source,ruby] +---- +# bad +specify { is_expected.to be_truthy } + +# good +it { is_expected.to be_truthy } + +# good +specify do + # ... +end +specify { expect(sqrt(4)).to eq(2) } +---- + +=== References + +* https://rspec.rubystyle.guide/#it-and-specify +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IsExpectedSpecify + == RSpec/ItBehavesLike |=== diff --git a/lib/rubocop/cop/rspec/is_expected_specify.rb b/lib/rubocop/cop/rspec/is_expected_specify.rb new file mode 100644 index 000000000..dd03383f9 --- /dev/null +++ b/lib/rubocop/cop/rspec/is_expected_specify.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Check for `specify` with `is_expected` and one-liner expectations. + # + # @example + # # bad + # specify { is_expected.to be_truthy } + # + # # good + # it { is_expected.to be_truthy } + # + # # good + # specify do + # # ... + # end + # specify { expect(sqrt(4)).to eq(2) } + # + class IsExpectedSpecify < Base + extend AutoCorrector + + RESTRICT_ON_SEND = %i[specify].freeze + IS_EXPECTED_METHODS = ::Set[:is_expected, :are_expected].freeze + MSG = 'Use `it` instead of `specify`.' + + # @!method offense?(node) + def_node_matcher :offense?, <<~PATTERN + (block (send _ :specify) _ (send (send _ IS_EXPECTED_METHODS) ...)) + PATTERN + + def on_send(node) + block_node = node.parent + return unless block_node&.single_line? && offense?(block_node) + + selector = node.loc.selector + add_offense(selector) do |corrector| + corrector.replace(selector, 'it') + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec_cops.rb b/lib/rubocop/cop/rspec_cops.rb index 89d873731..a1a4b45f9 100644 --- a/lib/rubocop/cop/rspec_cops.rb +++ b/lib/rubocop/cop/rspec_cops.rb @@ -78,6 +78,7 @@ require_relative 'rspec/indexed_let' require_relative 'rspec/instance_spy' require_relative 'rspec/instance_variable' +require_relative 'rspec/is_expected_specify' require_relative 'rspec/it_behaves_like' require_relative 'rspec/iterated_expectation' require_relative 'rspec/leading_subject' diff --git a/spec/rubocop/cop/rspec/is_expected_specify_spec.rb b/spec/rubocop/cop/rspec/is_expected_specify_spec.rb new file mode 100644 index 000000000..b6e2dbfd3 --- /dev/null +++ b/spec/rubocop/cop/rspec/is_expected_specify_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::RSpec::IsExpectedSpecify, :config do + it 'registers an offense when using `specify` and one-liner style' do + expect_offense(<<~RUBY) + specify { is_expected.to be_truthy } + ^^^^^^^ Use `it` instead of `specify`. + specify { are_expected.to be_falsy } + ^^^^^^^ Use `it` instead of `specify`. + RUBY + + expect_correction(<<~RUBY) + it { is_expected.to be_truthy } + it { are_expected.to be_falsy } + RUBY + end + + it 'does not register an offense when using `specify` ' \ + 'and not one-liner style' do + expect_no_offenses(<<~RUBY) + specify { expect(sqrt(4)).to eq(2) } + RUBY + end + + it 'does not register an offense when using `specify` and multi line' do + expect_no_offenses(<<~RUBY) + specify do + is_expected.to be_truthy + end + RUBY + end + + it 'does not register an offense when using `it` and one-liner style' do + expect_no_offenses(<<~RUBY) + it { is_expected.to be_truthy } + RUBY + end + + it 'does not register an offense when using `specify` with metadata' do + expect_no_offenses(<<~RUBY) + specify "pending", :pending + RUBY + end +end