diff --git a/CHANGELOG.md b/CHANGELOG.md index f03f93bb..8d2d8b79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * [#15](https://github.com/rubocop-hq/rubocop-minitest/pull/15): Add new `Minitest/RefuteIncludes` cop. ([@abhaynikam][]) * [#18](https://github.com/rubocop-hq/rubocop-minitest/pull/18): Add new `Minitest/RefuteFalse` cop. ([@duduribeiro][]) * [#20](https://github.com/rubocop-hq/rubocop-minitest/pull/20): Add new `Minitest/RefuteEmpty` cop. ([@abhaynikam][]) +* [#21](https://github.com/rubocop-hq/rubocop-minitest/pull/21): Add new `Minitest/RefuteEqual` cop. ([@duduribeiro][]) ### Bug fixes diff --git a/config/default.yml b/config/default.yml index 70361aa0..b74c65cd 100644 --- a/config/default.yml +++ b/config/default.yml @@ -26,6 +26,12 @@ Minitest/AssertTruthy: Enabled: true VersionAdded: '0.2' +Minitest/RefuteEqual: + Description: 'Check if your test uses `refute_equal` instead of `assert(expected != object)` or `assert(! expected == object))`.' + StyleGuide: 'https://github.com/rubocop-hq/minitest-style-guide#refute-equal' + Enabled: true + VersionAdded: '0.3' + Minitest/RefuteNil: Description: 'This cop enforces the test to use `refute_nil` instead of using `refute_equal(nil, something)`.' StyleGuide: 'https://github.com/rubocop-hq/minitest-style-guide#refute-nil' diff --git a/lib/rubocop/cop/minitest/refute_equal.rb b/lib/rubocop/cop/minitest/refute_equal.rb new file mode 100644 index 00000000..963d1891 --- /dev/null +++ b/lib/rubocop/cop/minitest/refute_equal.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Minitest + # This cop enforces the usages of `refute_equal(expected, object)` + # over `assert_equal(expected != actual)` or `assert(! expected -= actual)`. + # + # @example + # # bad + # assert("rubocop-minitest" != actual) + # assert(! "rubocop-minitest" == actual) + # + # # good + # refute_equal("rubocop-minitest", actual) + # + class RefuteEqual < Cop + MSG = 'Prefer using `refute_equal(%s)` over ' \ + '`assert(%s)`.' + + def_node_matcher :assert_not_equal, <<~PATTERN + (send nil? :assert ${(send $_ :!= $_) (send (send $_ :! ) :== $_) } $... ) + PATTERN + + def on_send(node) + preferred, over = process_not_equal(node) + return unless preferred && over + + message = format(MSG, preferred: preferred, over: over) + add_offense(node, message: message) + end + + def autocorrect(node) + lambda do |corrector| + assert_not_equal(node) do |_, first_arg, second_arg, rest_args| + autocorrect_node(node, corrector, first_arg, second_arg, rest_args) + end + end + end + + private + + def autocorrect_node(node, corrector, first_arg, second_arg, rest_args) + custom_message = rest_args.first + replacement = preferred_usage(first_arg, second_arg, custom_message) + corrector.replace(node.loc.expression, "refute_equal(#{replacement})") + end + + def preferred_usage(first_arg, second_arg, custom_message = nil) + [first_arg, second_arg, custom_message] + .compact.map(&:source).join(', ') + end + + def original_usage(first_part, custom_message) + [first_part, custom_message].compact.join(', ') + end + + def process_not_equal(node) + assert_not_equal(node) do |over, first_arg, second_arg, rest_args| + custom_message = rest_args.first + preferred = preferred_usage(first_arg, second_arg, custom_message) + over = original_usage(over.source, custom_message&.source) + return [preferred, over] + end + end + end + end + end +end diff --git a/lib/rubocop/cop/minitest_cops.rb b/lib/rubocop/cop/minitest_cops.rb index 38e2df84..52421d0a 100644 --- a/lib/rubocop/cop/minitest_cops.rb +++ b/lib/rubocop/cop/minitest_cops.rb @@ -6,5 +6,6 @@ require_relative 'minitest/assert_truthy' require_relative 'minitest/refute_empty' require_relative 'minitest/refute_false' +require_relative 'minitest/refute_equal' require_relative 'minitest/refute_nil' require_relative 'minitest/refute_includes' diff --git a/manual/cops.md b/manual/cops.md index 90a9afb4..549976be 100644 --- a/manual/cops.md +++ b/manual/cops.md @@ -6,6 +6,7 @@ * [Minitest/AssertNil](cops_minitest.md#minitestassertnil) * [Minitest/AssertTruthy](cops_minitest.md#minitestasserttruthy) * [Minitest/RefuteEmpty](cops_minitest.md#minitestrefuteempty) +* [Minitest/RefuteEqual](cops_minitest.md#minitestrefuteequal) * [Minitest/RefuteFalse](cops_minitest.md#minitestrefutefalse) * [Minitest/RefuteIncludes](cops_minitest.md#minitestrefuteincludes) * [Minitest/RefuteNil](cops_minitest.md#minitestrefutenil) diff --git a/manual/cops_minitest.md b/manual/cops_minitest.md index b4a2aaaa..c26e4016 100644 --- a/manual/cops_minitest.md +++ b/manual/cops_minitest.md @@ -125,6 +125,30 @@ refute_empty(object, 'the message') * [https://github.com/rubocop-hq/minitest-style-guide#refute-empty](https://github.com/rubocop-hq/minitest-style-guide#refute-empty) +## Minitest/RefuteEqual + +Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged +--- | --- | --- | --- | --- +Enabled | Yes | Yes | 0.3 | - + +This cop enforces the usages of `refute_equal(expected, object)` +over `assert_equal(expected != actual)` or `assert(! expected -= actual)`. + +### Examples + +```ruby +# bad +assert("rubocop-minitest" != actual) +assert(! "rubocop-minitest" == actual) + +# good +refute_equal("rubocop-minitest", actual) +``` + +### References + +* [https://github.com/rubocop-hq/minitest-style-guide#refute-equal](https://github.com/rubocop-hq/minitest-style-guide#refute-equal) + ## Minitest/RefuteFalse Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged diff --git a/test/rubocop/cop/minitest/refute_equal_test.rb b/test/rubocop/cop/minitest/refute_equal_test.rb new file mode 100644 index 00000000..b51bd812 --- /dev/null +++ b/test/rubocop/cop/minitest/refute_equal_test.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'test_helper' + +class RefuteEqualTest < Minitest::Test + def setup + @cop = RuboCop::Cop::Minitest::RefuteEqual.new + end + + def test_registers_offense_when_using_assert_not_equal_operator_with_string + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + assert('rubocop-minitest' != actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_equal('rubocop-minitest', actual)` over `assert('rubocop-minitest' != actual)`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_equal('rubocop-minitest', actual) + end + end + RUBY + end + + def test_registers_offense_when_using_assert_not_equal_operator_with_object + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + assert(expected != actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_equal(expected, actual)` over `assert(expected != actual)`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_equal(expected, actual) + end + end + RUBY + end + + def test_registers_offense_when_using_assert_not_equal_operator_with_method_call + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + assert(obj.expected != other_obj.actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_equal(obj.expected, other_obj.actual)` over `assert(obj.expected != other_obj.actual)`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_equal(obj.expected, other_obj.actual) + end + end + RUBY + end + + def test_registers_offense_when_using_assert_not_equal_operator_with_the_message + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + assert('rubocop-minitest' != actual, 'the message') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_equal('rubocop-minitest', actual, 'the message')` over `assert('rubocop-minitest' != actual, 'the message')`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_equal('rubocop-minitest', actual, 'the message') + end + end + RUBY + end + + def test_registers_offense_when_using_negate_equals + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + assert(! 'rubocop-minitest' == object) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_equal('rubocop-minitest', object)` over `assert(! 'rubocop-minitest' == object)`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_equal('rubocop-minitest', object) + end + end + RUBY + end + + def test_registers_offense_when_using_negate_equals_with_message + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + assert(! 'rubocop-minitest' == object, 'the message') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_equal('rubocop-minitest', object, 'the message')` over `assert(! 'rubocop-minitest' == object, 'the message')`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_equal('rubocop-minitest', object, 'the message') + end + end + RUBY + end + + def test_does_not_register_offense_when_using_refute_equal + assert_no_offenses(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_equal('rubocop-minitest', actual) + end + end + RUBY + end +end