diff --git a/CHANGELOG.md b/CHANGELOG.md index e68659bd..03b31e4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## master (unreleased) +### New features + +* [#60](https://github.com/rubocop-hq/rubocop-minitest/pull/60): Add new cop `Minitest/GlobalExpectations` to check for deprecated global expectations. ([@tejasbubane][]) + ### Bug fixes * [#58](https://github.com/rubocop-hq/rubocop-minitest/pull/58): Fix a false negative for `Minitest/AssertMatch` and `Minitest/RefuteMatch` when an argument is enclosed in redundant parentheses. ([@koic][]) diff --git a/config/default.yml b/config/default.yml index 6fd6a0c8..ff2de422 100644 --- a/config/default.yml +++ b/config/default.yml @@ -56,6 +56,11 @@ Minitest/AssertTruthy: Enabled: true VersionAdded: '0.2' +Minitest/GlobalExpectations: + Description: 'This cop checks for deprecated global expectations.' + Enabled: true + VersionAdded: '0.7' + Minitest/RefuteEmpty: Description: 'This cop enforces to use `refute_empty` instead of using `refute(object.empty?)`.' StyleGuide: 'https://github.com/rubocop-hq/minitest-style-guide#refute-empty' diff --git a/lib/rubocop/cop/minitest/global_expectations.rb b/lib/rubocop/cop/minitest/global_expectations.rb new file mode 100644 index 00000000..393b4c4c --- /dev/null +++ b/lib/rubocop/cop/minitest/global_expectations.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Minitest + # This Cop checks for deprecated global expectations + # and autocorrects them to use expect format. + # + # @example + # # bad + # n.must_equal 42 + # n.wont_match b + # + # # good + # _(n).must_equal 42 + # _(n).wont_match b + class GlobalExpectations < Cop + MSG = 'Prefer using `%s`.' + + VALUE_EXPECTATIONS = %i[ + be be_close_to be_empty be_instance_of be_kind_of + be_nil be_same_as be_silent be_within_epsilon equal + include match respond_to must_exist + ].map do |expectation| + [:"must_#{expectation}", :"wont_#{expectation}"] + end.flatten.freeze + + BLOCK_EXPECTATIONS = %i[must_output must_raise must_throw].freeze + + EXPECTATIONS_STR = (VALUE_EXPECTATIONS + BLOCK_EXPECTATIONS).map do |expectation| + ":#{expectation}" + end.join(' ').freeze + + def_node_matcher :global_expectation?, <<~PATTERN + (send (send _ _) {#{EXPECTATIONS_STR}} ...) + PATTERN + + def on_send(node) + return unless global_expectation?(node) + + message = format(MSG, corrected: correct_suggestion(node)) + add_offense(node, message: message) + end + + def autocorrect(node) + return unless global_expectation?(node) + + lambda do |corrector| + receiver = node.receiver.loc.selector + + if BLOCK_EXPECTATIONS.include?(node.method_name) + corrector.insert_before(receiver, '_ { ') + corrector.insert_after(receiver, ' }') + else + corrector.insert_before(receiver, '_(') + corrector.insert_after(receiver, ')') + end + end + end + + private + + def correct_suggestion(node) + source = node.receiver.source + if BLOCK_EXPECTATIONS.include?(node.method_name) + node.source.sub(source, "_ { #{source} }") + else + node.source.sub(source, "_(#{source})") + end + end + end + end + end +end diff --git a/lib/rubocop/cop/minitest_cops.rb b/lib/rubocop/cop/minitest_cops.rb index 1069e8c3..5c9f9629 100644 --- a/lib/rubocop/cop/minitest_cops.rb +++ b/lib/rubocop/cop/minitest_cops.rb @@ -11,6 +11,7 @@ require_relative 'minitest/assert_match' require_relative 'minitest/assert_respond_to' require_relative 'minitest/assert_truthy' +require_relative 'minitest/global_expectations' require_relative 'minitest/refute_empty' require_relative 'minitest/refute_false' require_relative 'minitest/refute_equal' diff --git a/manual/cops.md b/manual/cops.md index 2eb5aa0d..a3b2771a 100644 --- a/manual/cops.md +++ b/manual/cops.md @@ -10,6 +10,7 @@ * [Minitest/AssertNil](cops_minitest.md#minitestassertnil) * [Minitest/AssertRespondTo](cops_minitest.md#minitestassertrespondto) * [Minitest/AssertTruthy](cops_minitest.md#minitestasserttruthy) +* [Minitest/GlobalExpectations](cops_minitest.md#minitestglobalexpectations) * [Minitest/RefuteEmpty](cops_minitest.md#minitestrefuteempty) * [Minitest/RefuteEqual](cops_minitest.md#minitestrefuteequal) * [Minitest/RefuteFalse](cops_minitest.md#minitestrefutefalse) diff --git a/manual/cops_minitest.md b/manual/cops_minitest.md index 599883f9..43d8a820 100644 --- a/manual/cops_minitest.md +++ b/manual/cops_minitest.md @@ -220,6 +220,27 @@ assert(actual, 'message') * [https://github.com/rubocop-hq/minitest-style-guide#assert-truthy](https://github.com/rubocop-hq/minitest-style-guide#assert-truthy) +## Minitest/GlobalExpectations + +Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged +--- | --- | --- | --- | --- +Enabled | Yes | Yes | 0.7 | - + +This Cop checks for deprecated global expectations +and autocorrects them to use expect format. + +### Examples + +```ruby +# bad +n.must_equal 42 +n.wont_match b + +# good +_(n).must_equal 42 +_(n).wont_match b +``` + ## Minitest/RefuteEmpty Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged diff --git a/test/rubocop/cop/minitest/global_expectations_test.rb b/test/rubocop/cop/minitest/global_expectations_test.rb new file mode 100644 index 00000000..3d5d2b8a --- /dev/null +++ b/test/rubocop/cop/minitest/global_expectations_test.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'test_helper' + +class GlobalExpectationsTest < Minitest::Test + RuboCop::Cop::Minitest::GlobalExpectations::VALUE_EXPECTATIONS.each do |expectation| + define_method(:"test_registers_offense_when_using_global_#{expectation}") do + assert_offense(<<~RUBY) + it 'does something' do + n.#{expectation} 42 + #{'^' * (expectation.length + 5)} Prefer using `_(n).#{expectation} 42`. + end + RUBY + + assert_correction(<<~RUBY) + it 'does something' do + _(n).#{expectation} 42 + end + RUBY + end + + define_method(:"test_no_offense_when_using_expect_form_of_#{expectation}") do + assert_no_offenses(<<~RUBY) + it 'does something' do + _(n).#{expectation} 42 + end + RUBY + end + end + + def test_works_with_chained_method_calls + assert_offense(<<~RUBY) + it 'does something' do + A.foo.bar.must_equal 42 + ^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `_(A.foo.bar).must_equal 42`. + end + RUBY + end + + RuboCop::Cop::Minitest::GlobalExpectations::BLOCK_EXPECTATIONS.each do |expectation| + define_method(:"test_registers_offense_when_using_global_#{expectation}") do + assert_offense(<<~RUBY) + it 'does something' do + n.#{expectation} 42 + #{'^' * (expectation.length + 5)} Prefer using `_ { n }.#{expectation} 42`. + end + RUBY + + assert_correction(<<~RUBY) + it 'does something' do + _ { n }.#{expectation} 42 + end + RUBY + end + + define_method(:"test_no_offense_when_using_expect_form_of_#{expectation}") do + assert_no_offenses(<<~RUBY) + it 'does something' do + _ { n }.#{expectation} 42 + end + RUBY + end + end +end