From adb33ca45a3bd6f4bf202b141ca50048b172de9a Mon Sep 17 00:00:00 2001 From: Phil Pirozhkov Date: Wed, 22 May 2024 08:25:50 +0300 Subject: [PATCH] Add new `RSpec/ExpectInLet` cop Backport the addition of new cop RSpec/ExpectInLet added in #1885 from the main branch to the 2-x-stable branch. We want to get as much feedback as possible on new cops before (eventually) enabling them when v3 is released. --- .rubocop.yml | 2 + CHANGELOG.md | 3 ++ config/default.yml | 6 +++ docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_rspec.adoc | 33 ++++++++++++ lib/rubocop/cop/rspec/expect_in_let.rb | 44 +++++++++++++++ lib/rubocop/cop/rspec_cops.rb | 1 + spec/rubocop/cop/rspec/expect_in_let_spec.rb | 57 ++++++++++++++++++++ 8 files changed, 147 insertions(+) create mode 100644 lib/rubocop/cop/rspec/expect_in_let.rb create mode 100644 spec/rubocop/cop/rspec/expect_in_let_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 4a19f5f8e..0e332b929 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -170,6 +170,8 @@ RSpec/Eq: Enabled: true RSpec/ExcessiveDocstringSpacing: Enabled: true +RSpec/ExpectInLet: + Enabled: true RSpec/IdenticalEqualityAssertion: Enabled: true RSpec/IndexedLet: diff --git a/CHANGELOG.md b/CHANGELOG.md index 21aba0806..9d6f20786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Master (Unreleased) +- Add new `RSpec/ExpectInLet` cop. ([@yasu551]) + ## 2.29.2 (2024-05-02) - Fix beginless and endless range bug for RepeatedIncludeExample cop. ([@hasghari]) @@ -976,6 +978,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features. [@twalpole]: https://github.com/twalpole [@vzvu3k6k]: https://github.com/vzvu3k6k [@walf443]: https://github.com/walf443 +[@yasu551]: https://github.com/yasu551 [@ybiquitous]: https://github.com/ybiquitous [@ydah]: https://github.com/ydah [@yevhene]: https://github.com/yevhene diff --git a/config/default.yml b/config/default.yml index 7ed394d9e..2ae7f4f33 100644 --- a/config/default.yml +++ b/config/default.yml @@ -447,6 +447,12 @@ RSpec/ExpectInHook: VersionAdded: '1.16' Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInHook +RSpec/ExpectInLet: + Description: Do not use `expect` in let. + Enabled: pending + VersionAdded: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInLet + RSpec/ExpectOutput: Description: Checks for opportunities to use `expect { ... }.to output`. Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index dd6fcfaa9..9bcbd9d0a 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -41,6 +41,7 @@ * xref:cops_rspec.adoc#rspecexpectactual[RSpec/ExpectActual] * xref:cops_rspec.adoc#rspecexpectchange[RSpec/ExpectChange] * xref:cops_rspec.adoc#rspecexpectinhook[RSpec/ExpectInHook] +* xref:cops_rspec.adoc#rspecexpectinlet[RSpec/ExpectInLet] * xref:cops_rspec.adoc#rspecexpectoutput[RSpec/ExpectOutput] * xref:cops_rspec.adoc#rspecfilepath[RSpec/FilePath] * xref:cops_rspec.adoc#rspecfocus[RSpec/Focus] diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index 618d3cb6c..55d731e46 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -2042,6 +2042,39 @@ end * https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInHook +== RSpec/ExpectInLet + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| No +| <> +| - +|=== + +Do not use `expect` in let. + +=== Examples + +[source,ruby] +---- +# bad +let(:foo) do + expect(something).to eq 'foo' +end + +# good +it do + expect(something).to eq 'foo' +end +---- + +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInLet + == RSpec/ExpectOutput |=== diff --git a/lib/rubocop/cop/rspec/expect_in_let.rb b/lib/rubocop/cop/rspec/expect_in_let.rb new file mode 100644 index 000000000..3111e72aa --- /dev/null +++ b/lib/rubocop/cop/rspec/expect_in_let.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Do not use `expect` in let. + # + # @example + # # bad + # let(:foo) do + # expect(something).to eq 'foo' + # end + # + # # good + # it do + # expect(something).to eq 'foo' + # end + # + class ExpectInLet < Base + MSG = 'Do not use `%s` in let' + + # @!method expectation(node) + def_node_search :expectation, '(send nil? #Expectations.all ...)' + + def on_block(node) + return unless let?(node) + return if node.body.nil? + + expectation(node.body) do |expect| + add_offense(expect.loc.selector, message: message(expect)) + end + end + + alias on_numblock on_block + + private + + def message(expect) + format(MSG, expect: expect.method_name) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec_cops.rb b/lib/rubocop/cop/rspec_cops.rb index 7af420e4e..1bf497f3d 100644 --- a/lib/rubocop/cop/rspec_cops.rb +++ b/lib/rubocop/cop/rspec_cops.rb @@ -63,6 +63,7 @@ require_relative 'rspec/expect_actual' require_relative 'rspec/expect_change' require_relative 'rspec/expect_in_hook' +require_relative 'rspec/expect_in_let' require_relative 'rspec/expect_output' require_relative 'rspec/file_path' require_relative 'rspec/focus' diff --git a/spec/rubocop/cop/rspec/expect_in_let_spec.rb b/spec/rubocop/cop/rspec/expect_in_let_spec.rb new file mode 100644 index 000000000..6bc6d53d8 --- /dev/null +++ b/spec/rubocop/cop/rspec/expect_in_let_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::RSpec::ExpectInLet do + it 'adds an offense for `expect` in let' do + expect_offense(<<~RUBY) + let(:foo) do + expect(something).to eq 'foo' + ^^^^^^ Do not use `expect` in let + end + RUBY + end + + it 'adds an offense for `is_expected` in let' do + expect_offense(<<~RUBY) + let(:foo) do + is_expected.to eq 'foo' + ^^^^^^^^^^^ Do not use `is_expected` in let + end + RUBY + end + + it 'adds an offense for `expect_any_instance_of` in let' do + expect_offense(<<~RUBY) + let(:foo) do + expect_any_instance_of(Something).to receive :foo + ^^^^^^^^^^^^^^^^^^^^^^ Do not use `expect_any_instance_of` in let + end + RUBY + end + + it 'adds offenses for multiple expectations in let' do + expect_offense(<<~RUBY) + let(:foo) do + expect(something).to eq 'foo' + ^^^^^^ Do not use `expect` in let + is_expected.to eq 'foo' + ^^^^^^^^^^^ Do not use `is_expected` in let + end + RUBY + end + + it 'accepts an empty let' do + expect_no_offenses(<<~RUBY) + let(:foo) {} + RUBY + end + + it 'accepts `expect` in `it`' do + expect_no_offenses(<<~RUBY) + it do + expect(something).to eq 'foo' + is_expected.to eq 'foo' + expect_any_instance_of(Something).to receive :foo + end + RUBY + end +end