diff --git a/.rubocop.yml b/.rubocop.yml index e49435723..fcad09946 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -128,3 +128,5 @@ RSpec/SubjectDeclaration: Enabled: true RSpec/VerifiedDoubleReference: Enabled: true +RSpec/MinitestAssertions: + Enabled: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 777e08817..761c1e3a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Master (Unreleased) +- Add new `RSpec/MinitestAssertions` cop. ([@ydah]) - Add new `RSpec/FactoryBot/FactoryNameStyle` cop. ([@ydah]) - Fix wrong autocorrection in `n_times` style on `RSpec/FactoryBot/CreateList`. ([@r7kamura]) - Fix a false positive for `RSpec/FactoryBot/ConsistentParenthesesStyle` when using `generate` with multiple arguments. ([@ydah]) diff --git a/config/default.yml b/config/default.yml index 63696a7ac..d56410021 100644 --- a/config/default.yml +++ b/config/default.yml @@ -573,6 +573,12 @@ RSpec/MessageSpies: VersionAdded: '1.9' Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageSpies +RSpec/MinitestAssertions: + Description: Check if using Minitest matchers. + Enabled: pending + VersionAdded: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MinitestAssertions + RSpec/MissingExampleGroupArgument: Description: Checks that the first argument to an example group is not empty. Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 541b45f04..fc7ebef55 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -55,6 +55,7 @@ * xref:cops_rspec.adoc#rspecmessagechain[RSpec/MessageChain] * xref:cops_rspec.adoc#rspecmessageexpectation[RSpec/MessageExpectation] * xref:cops_rspec.adoc#rspecmessagespies[RSpec/MessageSpies] +* xref:cops_rspec.adoc#rspecminitestassertions[RSpec/MinitestAssertions] * xref:cops_rspec.adoc#rspecmissingexamplegroupargument[RSpec/MissingExampleGroupArgument] * xref:cops_rspec.adoc#rspecmultipledescribes[RSpec/MultipleDescribes] * xref:cops_rspec.adoc#rspecmultipleexpectations[RSpec/MultipleExpectations] diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index 0d8f02cd7..144c30943 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -2946,6 +2946,39 @@ do_something * https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageSpies +== RSpec/MinitestAssertions + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes +| <> +| - +|=== + +Check if using Minitest matchers. + +=== Examples + +[source,ruby] +---- +# bad +assert_equal(a, b) +assert_equal a, b, "must be equal" +refute_equal(a, b) + +# good +expect(a).to eq(b) +expect(a).to(eq(b), "must be equal") +expect(a).not_to eq(b) +---- + +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MinitestAssertions + == RSpec/MissingExampleGroupArgument |=== diff --git a/lib/rubocop/cop/rspec/minitest_assertions.rb b/lib/rubocop/cop/rspec/minitest_assertions.rb new file mode 100644 index 000000000..8a6779416 --- /dev/null +++ b/lib/rubocop/cop/rspec/minitest_assertions.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Check if using Minitest matchers. + # + # @example + # # bad + # assert_equal(a, b) + # assert_equal a, b, "must be equal" + # refute_equal(a, b) + # + # # good + # expect(a).to eq(b) + # expect(a).to(eq(b), "must be equal") + # expect(a).not_to eq(b) + # + class MinitestAssertions < Base + extend AutoCorrector + + MSG = 'Use `%s`.' + RESTRICT_ON_SEND = %i[assert_equal refute_equal].freeze + + # @!method minitest_assertion(node) + def_node_matcher :minitest_assertion, <<-PATTERN + (send nil? {:assert_equal :refute_equal} $_ $_ $_?) + PATTERN + + def on_send(node) + minitest_assertion(node) do |expected, actual, failure_message| + prefer = replacement(node, expected, actual, failure_message.first) + add_offense(node, message: message(prefer)) do |corrector| + corrector.replace(node, prefer) + end + end + end + + private + + def replacement(node, expected, actual, failure_message) + runner = node.method?(:assert_equal) ? 'to' : 'not_to' + if failure_message.nil? + "expect(#{expected.source}).#{runner} eq(#{actual.source})" + else + "expect(#{expected.source}).#{runner}(eq(#{actual.source}), " \ + "#{failure_message.source})" + end + end + + def message(prefer) + format(MSG, prefer: prefer) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec_cops.rb b/lib/rubocop/cop/rspec_cops.rb index d68fa240e..69b83bf6e 100644 --- a/lib/rubocop/cop/rspec_cops.rb +++ b/lib/rubocop/cop/rspec_cops.rb @@ -77,6 +77,7 @@ require_relative 'rspec/message_chain' require_relative 'rspec/message_expectation' require_relative 'rspec/message_spies' +require_relative 'rspec/minitest_assertions' require_relative 'rspec/missing_example_group_argument' require_relative 'rspec/multiple_describes' require_relative 'rspec/multiple_expectations' diff --git a/spec/rubocop/cop/rspec/minitest_assertions_spec.rb b/spec/rubocop/cop/rspec/minitest_assertions_spec.rb new file mode 100644 index 000000000..1c4c91f77 --- /dev/null +++ b/spec/rubocop/cop/rspec/minitest_assertions_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::RSpec::MinitestAssertions, :config do + it 'registers an offense when using `assert_equal`' do + expect_offense(<<~RUBY) + assert_equal(a, b) + ^^^^^^^^^^^^^^^^^^ Use `expect(a).to eq(b)`. + RUBY + + expect_correction(<<~RUBY) + expect(a).to eq(b) + RUBY + end + + it 'registers an offense when using `assert_equal` with no parentheses' do + expect_offense(<<~RUBY) + assert_equal a, b + ^^^^^^^^^^^^^^^^^ Use `expect(a).to eq(b)`. + RUBY + + expect_correction(<<~RUBY) + expect(a).to eq(b) + RUBY + end + + it 'registers an offense when using `assert_equal` with failure message' do + expect_offense(<<~RUBY) + assert_equal a, b, "must be equal" + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to(eq(b), "must be equal")`. + RUBY + + expect_correction(<<~RUBY) + expect(a).to(eq(b), "must be equal") + RUBY + end + + it 'registers an offense when using `assert_equal` with ' \ + 'multi-line arguments' do + expect_offense(<<~RUBY) + assert_equal(a, + ^^^^^^^^^^^^^^^ Use `expect(a).to(eq(b), "must be equal")`. + b, + "must be equal") + RUBY + + expect_correction(<<~RUBY) + expect(a).to(eq(b), "must be equal") + RUBY + end + + it 'registers an offense when using `refute_equal`' do + expect_offense(<<~RUBY) + refute_equal a, b + ^^^^^^^^^^^^^^^^^ Use `expect(a).not_to eq(b)`. + RUBY + + expect_correction(<<~RUBY) + expect(a).not_to eq(b) + RUBY + end + + it 'does not register an offense when using `expect(a).to eq(b)`' do + expect_no_offenses(<<~RUBY) + expect(a).to eq(b) + RUBY + end + + it 'does not register an offense when using `expect(a).not_to eq(b)`' do + expect_no_offenses(<<~RUBY) + expect(a).not_to eq(b) + RUBY + end +end