From 870b213ace304e43e5f51ec74e4337ea557dbdd4 Mon Sep 17 00:00:00 2001 From: Abhay Nikam Date: Fri, 27 Sep 2019 23:27:09 +0530 Subject: [PATCH] Add cop to enfore refute_empty over refute(actual.empty?) --- CHANGELOG.md | 4 + config/default.yml | 6 ++ lib/rubocop/cop/minitest/refute_empty.rb | 50 +++++++++ lib/rubocop/cop/minitest_cops.rb | 1 + manual/cops.md | 1 + manual/cops_minitest.md | 20 ++++ .../rubocop/cop/minitest/refute_empty_test.rb | 101 ++++++++++++++++++ 7 files changed, 183 insertions(+) create mode 100644 lib/rubocop/cop/minitest/refute_empty.rb create mode 100644 test/rubocop/cop/minitest/refute_empty_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index ef954ca7..c5d2ca2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## master (unreleased) +### New features + +* [#20](https://github.com/rubocop-hq/rubocop-minitest/pull/20): Add new `Minitest/RefuteEmpty` cop. ([@abhaynikam][]) + ## 0.2.1 (2019-09-24) ### Bug fixes diff --git a/config/default.yml b/config/default.yml index 5f345835..b378d7e4 100644 --- a/config/default.yml +++ b/config/default.yml @@ -31,3 +31,9 @@ Minitest/RefuteNil: StyleGuide: 'https://github.com/rubocop-hq/minitest-style-guide#refute-nil' Enabled: true VersionAdded: '0.2' + +Minitest/RefuteEmpty: + Description: 'Check if your test uses `refute_empty` instead of `refute(actual.empty?)`.' + StyleGuide: 'https://github.com/rubocop-hq/minitest-style-guide#refute-empty' + Enabled: true + VersionAdded: '0.3' diff --git a/lib/rubocop/cop/minitest/refute_empty.rb b/lib/rubocop/cop/minitest/refute_empty.rb new file mode 100644 index 00000000..d890df6c --- /dev/null +++ b/lib/rubocop/cop/minitest/refute_empty.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Minitest + # Check if your test uses `refute_empty` instead of `refute(actual.empty?)`. + # + # @example + # # bad + # refute(actual.empty?) + # refute(actual.empty?, 'the message') + # + # # good + # refute_empty(actual) + # refute_empty(actual, 'the message') + # + class RefuteEmpty < Cop + MSG = 'Prefer using `refute_empty(%s)` over ' \ + '`refute(%s)`.' + + def_node_matcher :refute_with_empty, <<~PATTERN + (send nil? :refute $(send $_ :empty?) $...) + PATTERN + + def on_send(node) + refute_with_empty(node) do |first_receiver_arg, actual, rest_receiver_arg| + message = rest_receiver_arg.first + + arguments = [actual.source, message&.source].compact.join(', ') + receiver = [first_receiver_arg.source, message&.source].compact.join(', ') + + offense_message = format(MSG, arguments: arguments, receiver: receiver) + add_offense(node, message: offense_message) + end + end + + def autocorrect(node) + lambda do |corrector| + refute_with_empty(node) do |_first_receiver_arg, actual, rest_receiver_arg| + message = rest_receiver_arg.first + + replacement = [actual.source, message&.source].compact.join(', ') + corrector.replace(node.loc.expression, "refute_empty(#{replacement})") + end + end + end + end + end + end +end diff --git a/lib/rubocop/cop/minitest_cops.rb b/lib/rubocop/cop/minitest_cops.rb index 75e82c2a..36020080 100644 --- a/lib/rubocop/cop/minitest_cops.rb +++ b/lib/rubocop/cop/minitest_cops.rb @@ -4,4 +4,5 @@ require_relative 'minitest/assert_nil' require_relative 'minitest/assert_includes' require_relative 'minitest/assert_truthy' +require_relative 'minitest/refute_empty' require_relative 'minitest/refute_nil' diff --git a/manual/cops.md b/manual/cops.md index dc617dbc..599cb08b 100644 --- a/manual/cops.md +++ b/manual/cops.md @@ -5,6 +5,7 @@ * [Minitest/AssertIncludes](cops_minitest.md#minitestassertincludes) * [Minitest/AssertNil](cops_minitest.md#minitestassertnil) * [Minitest/AssertTruthy](cops_minitest.md#minitestasserttruthy) +* [Minitest/RefuteEmpty](cops_minitest.md#minitestrefuteempty) * [Minitest/RefuteNil](cops_minitest.md#minitestrefutenil) diff --git a/manual/cops_minitest.md b/manual/cops_minitest.md index 2d3097cd..8da00f6f 100644 --- a/manual/cops_minitest.md +++ b/manual/cops_minitest.md @@ -98,6 +98,26 @@ assert(actual, 'the message') * [https://github.com/rubocop-hq/minitest-style-guide#assert-truthy](https://github.com/rubocop-hq/minitest-style-guide#assert-truthy) +## Minitest/RefuteEmpty + +Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged +--- | --- | --- | --- | --- +Enabled | Yes | Yes | - | - + +Check if your test uses `refute_empty` instead of `refute(actual.empty?)`. + +### Examples + +```ruby +# bad +assert(actual.empty?) +assert(actual.empty?, 'the message') + +# good +refute_empty(actual) +refute_empty(actual, 'the message') +``` + ## Minitest/RefuteNil Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged diff --git a/test/rubocop/cop/minitest/refute_empty_test.rb b/test/rubocop/cop/minitest/refute_empty_test.rb new file mode 100644 index 00000000..1dee2e6f --- /dev/null +++ b/test/rubocop/cop/minitest/refute_empty_test.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'test_helper' + +class RefuteEmptyTest < Minitest::Test + def setup + @cop = RuboCop::Cop::Minitest::RefuteEmpty.new + end + + def test_registers_offense_when_using_refute_with_empty + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute(somestuff.empty?) + ^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_empty(somestuff)` over `refute(somestuff.empty?)`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_empty(somestuff) + end + end + RUBY + end + + def test_registers_offense_when_using_refute_with_empty_and_string_message + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute(somestuff.empty?, 'the message') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_empty(somestuff, 'the message')` over `refute(somestuff.empty?, 'the message')`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_empty(somestuff, 'the message') + end + end + RUBY + end + + def test_registers_offense_when_using_refute_with_empty_and_variable_message + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + message = 'the message' + refute(somestuff.empty?, message) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_empty(somestuff, message)` over `refute(somestuff.empty?, message)`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + message = 'the message' + refute_empty(somestuff, message) + end + end + RUBY + end + + def test_registers_offense_when_using_refute_with_empty_and_constant_message + assert_offense(<<~RUBY, @cop) + class FooTest < Minitest::Test + MESSAGE = 'the message' + + def test_do_something + refute(somestuff.empty?, MESSAGE) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `refute_empty(somestuff, MESSAGE)` over `refute(somestuff.empty?, MESSAGE)`. + end + end + RUBY + + assert_correction(<<~RUBY, @cop) + class FooTest < Minitest::Test + MESSAGE = 'the message' + + def test_do_something + refute_empty(somestuff, MESSAGE) + end + end + RUBY + end + + def refute_empty_method + assert_no_offenses(<<~RUBY, @cop) + class FooTest < Minitest::Test + def test_do_something + refute_empty(somestuff) + end + end + RUBY + end +end