diff --git a/changelog/new_add_new_minitest_assert_operator_and_refute_operator_cops.md b/changelog/new_add_new_minitest_assert_operator_and_refute_operator_cops.md new file mode 100644 index 00000000..b8c50376 --- /dev/null +++ b/changelog/new_add_new_minitest_assert_operator_and_refute_operator_cops.md @@ -0,0 +1 @@ +* [#255](https://github.com/rubocop/rubocop-minitest/issues/255): Add new `Minitest/AssertOperator` and `Minitest/RefuteOperator` cops. ([@koic][]) diff --git a/config/default.yml b/config/default.yml index e5ebf1e8..9184b2f4 100644 --- a/config/default.yml +++ b/config/default.yml @@ -59,6 +59,12 @@ Minitest/AssertNil: Enabled: true VersionAdded: '0.1' +Minitest/AssertOperator: + Description: 'This cop enforces the use of `assert_operator(expected, :<, actual)` over `assert(expected < actual)`.' + StyleGuide: 'https://minitest.rubystyle.guide#assert-operator' + Enabled: pending + VersionAdded: '<>' + Minitest/AssertOutput: Description: 'This cop checks for opportunities to use `assert_output`.' StyleGuide: 'https://minitest.rubystyle.guide/#assert-output' @@ -248,6 +254,12 @@ Minitest/RefuteNil: Enabled: true VersionAdded: '0.2' +Minitest/RefuteOperator: + Description: 'This cop enforces the use of `refute_operator(expected, :<, actual)` over `refute(expected < actual)`.' + StyleGuide: 'https://minitest.rubystyle.guide#refute-operator' + Enabled: pending + VersionAdded: '<>' + Minitest/RefutePathExists: Description: 'This cop enforces the test to use `refute_path_exists` instead of using `refute(File.exist?(path))`.' StyleGuide: 'https://minitest.rubystyle.guide/#refute-path-exists' diff --git a/lib/rubocop/cop/minitest/assert_operator.rb b/lib/rubocop/cop/minitest/assert_operator.rb new file mode 100644 index 00000000..d7c5bfd4 --- /dev/null +++ b/lib/rubocop/cop/minitest/assert_operator.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Minitest + # Enforces the use of `assert_operator(expected, :<, actual)` over `assert(expected < actual)`. + # + # @example + # + # # bad + # assert(expected < actual) + # + # # good + # assert_operator(expected, :<, actual) + # + class AssertOperator < Base + extend AutoCorrector + + MSG = 'Prefer using `assert_operator(%s)`.' + RESTRICT_ON_SEND = %i[assert].freeze + + def on_send(node) + return unless node.first_argument.operator_method? + + new_arguments = build_new_arguments(node) + + add_offense(node, message: format(MSG, new_arguments: new_arguments)) do |corrector| + corrector.replace(node.loc.selector, 'assert_operator') + + corrector.replace(range_of_arguments(node), new_arguments) + end + end + + private + + def build_new_arguments(node) + lhs, op, rhs = *node.first_argument + new_arguments = "#{lhs.source}, :#{op}, #{rhs.source}" + + if node.arguments.count == 2 + new_arguments << ", #{node.last_argument.source}" + else + new_arguments + end + end + + def range_of_arguments(node) + node.first_argument.source_range.begin.join(node.last_argument.source_range.end) + end + end + end + end +end diff --git a/lib/rubocop/cop/minitest/refute_operator.rb b/lib/rubocop/cop/minitest/refute_operator.rb new file mode 100644 index 00000000..647ef195 --- /dev/null +++ b/lib/rubocop/cop/minitest/refute_operator.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Minitest + # Enforces the use of `refute_operator(expected, :<, actual)` over `refute(expected < actual)`. + # + # @example + # + # # bad + # refute(expected < actual) + # + # # good + # refute_operator(expected, :<, actual) + # + class RefuteOperator < Base + extend AutoCorrector + + MSG = 'Prefer using `refute_operator(%s)`.' + RESTRICT_ON_SEND = %i[refute].freeze + + def on_send(node) + return unless node.first_argument.operator_method? + + new_arguments = build_new_arguments(node) + + add_offense(node, message: format(MSG, new_arguments: new_arguments)) do |corrector| + corrector.replace(node.loc.selector, 'refute_operator') + + corrector.replace(range_of_arguments(node), new_arguments) + end + end + + private + + def build_new_arguments(node) + lhs, op, rhs = *node.first_argument + new_arguments = "#{lhs.source}, :#{op}, #{rhs.source}" + + if node.arguments.count == 2 + new_arguments << ", #{node.last_argument.source}" + else + new_arguments + end + end + + def range_of_arguments(node) + node.first_argument.source_range.begin.join(node.last_argument.source_range.end) + end + end + end + end +end diff --git a/lib/rubocop/cop/minitest_cops.rb b/lib/rubocop/cop/minitest_cops.rb index 14d28dde..58675e7a 100644 --- a/lib/rubocop/cop/minitest_cops.rb +++ b/lib/rubocop/cop/minitest_cops.rb @@ -11,6 +11,7 @@ require_relative 'minitest/assert_empty_literal' require_relative 'minitest/assert_equal' require_relative 'minitest/assert_in_delta' +require_relative 'minitest/assert_operator' require_relative 'minitest/assert_predicate' require_relative 'minitest/assert_raises_compound_body' require_relative 'minitest/assert_raises_with_regexp_argument' @@ -42,11 +43,12 @@ require_relative 'minitest/refute_false' require_relative 'minitest/refute_equal' require_relative 'minitest/refute_in_delta' -require_relative 'minitest/refute_kind_of' -require_relative 'minitest/refute_nil' require_relative 'minitest/refute_includes' -require_relative 'minitest/refute_match' require_relative 'minitest/refute_instance_of' +require_relative 'minitest/refute_kind_of' +require_relative 'minitest/refute_match' +require_relative 'minitest/refute_nil' +require_relative 'minitest/refute_operator' require_relative 'minitest/refute_path_exists' require_relative 'minitest/refute_predicate' require_relative 'minitest/refute_respond_to' diff --git a/test/project_test.rb b/test/project_test.rb index 77e648e2..82648ff1 100644 --- a/test/project_test.rb +++ b/test/project_test.rb @@ -136,7 +136,7 @@ def test_default_rules_are_sorted_alphabetically config_default = YAML.load_file('config/default.yml') config_default.each_key do |key| - assert(previous_key <= key, "Cops should be sorted alphabetically. Please sort #{key}.") + assert_operator(previous_key, :<=, key, "Cops should be sorted alphabetically. Please sort #{key}.") previous_key = key end end diff --git a/test/rubocop/cop/minitest/assert_operator_test.rb b/test/rubocop/cop/minitest/assert_operator_test.rb new file mode 100644 index 00000000..0ae1095f --- /dev/null +++ b/test/rubocop/cop/minitest/assert_operator_test.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require_relative '../../../test_helper' + +class AssertOperatorTest < Minitest::Test + def test_registers_offense_when_using_assert_with_operator_method + assert_offense(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + assert(expected < actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `assert_operator(expected, :<, actual)`. + end + end + RUBY + + assert_correction(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + assert_operator(expected, :<, actual) + end + end + RUBY + end + + def test_registers_offense_when_using_assert_with_operator_method_and_message + assert_offense(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + assert(expected < actual, 'message') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `assert_operator(expected, :<, actual, 'message')`. + end + end + RUBY + + assert_correction(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + assert_operator(expected, :<, actual, 'message') + end + end + RUBY + end + + def test_registers_offense_when_using_assert_with_operator_method_without_parentheses + assert_offense(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + assert expected < actual + ^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `assert_operator(expected, :<, actual)`. + end + end + RUBY + + assert_correction(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + assert_operator expected, :<, actual + end + end + RUBY + end + + def test_does_not_register_offense_when_using_not_assert_with_operator_method + assert_no_offenses(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + assert_operator(expected, :<, actual) + end + end + RUBY + end +end diff --git a/test/rubocop/cop/minitest/refute_operator_test.rb b/test/rubocop/cop/minitest/refute_operator_test.rb new file mode 100644 index 00000000..3208e6c2 --- /dev/null +++ b/test/rubocop/cop/minitest/refute_operator_test.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require_relative '../../../test_helper' + +class RefuteOperatorTest < Minitest::Test + def test_registers_offense_when_using_refute_with_operator_method + assert_offense(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + refute(expected < actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_operator(expected, :<, actual)`. + end + end + RUBY + + assert_correction(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + refute_operator(expected, :<, actual) + end + end + RUBY + end + + def test_registers_offense_when_using_refute_with_operator_method_and_message + assert_offense(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + refute(expected < actual, 'message') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_operator(expected, :<, actual, 'message')`. + end + end + RUBY + + assert_correction(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + refute_operator(expected, :<, actual, 'message') + end + end + RUBY + end + + def test_registers_offense_when_using_refute_with_operator_method_without_parentheses + assert_offense(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + refute expected < actual + ^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_operator(expected, :<, actual)`. + end + end + RUBY + + assert_correction(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + refute_operator expected, :<, actual + end + end + RUBY + end + + def test_does_not_register_offense_when_using_not_refute_with_operator_method + assert_no_offenses(<<~RUBY) + class FooTest < Minitest::Test + def test_do_something + refute_operator(expected, :<, actual) + end + end + RUBY + end +end