Skip to content

Commit

Permalink
Add new Minitest/AssertPredicate and Minitest/RefutePredicate cops
Browse files Browse the repository at this point in the history
This PR adds new `Minitest/AssertPredicate` and `Minitest/RefutePredicate` cops.

## `Minitest/AssertPredicate` cop

This cop enforces the test to use `assert_predicate`
instead of using `assert(obj.a_predicate_method?)`.

```ruby
# bad
assert(obj.one?)
assert(obj.one?, 'message')

# good
assert_predicate(obj, :one?)
assert_predicate(obj, :one?, 'message')
```

## `Minitest/RefutePredicate` cop

This cop enforces the test to use `refute_predicate`
instead of using `refute(obj.a_predicate_method?)`.

```ruby
# bad
refute(obj.one?)
refute(obj.one?, 'message')

# good
refute_predicate(obj, :one?)
refute_predicate(obj, :one?, 'message')
```
  • Loading branch information
koic committed Mar 2, 2022
1 parent f7694b5 commit 4c38d48
Show file tree
Hide file tree
Showing 8 changed files with 435 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#161](https://github.com/rubocop/rubocop-minitest/pull/161): Add new `Minitest/AssertPredicate` and `Minitest/RefutePredicate` cops. ([@koic][])
12 changes: 12 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ Minitest/AssertRespondTo:
Enabled: true
VersionAdded: '0.3'

Minitest/AssertPredicate:
Description: 'This cop enforces the test to use `assert_predicate` instead of using `assert(obj.a_predicate_method?)`.'
StyleGuide: 'https://minitest.rubystyle.guide/#assert-predicate'
Enabled: pending
VersionAdded: '<<next>>'

Minitest/AssertSilent:
Description: "This cop enforces the test to use `assert_silent { ... }` instead of using `assert_output('', '') { ... }`."
StyleGuide: 'https://github.com/rubocop/minitest-style-guide#assert-silent'
Expand Down Expand Up @@ -194,6 +200,12 @@ Minitest/RefutePathExists:
Enabled: 'pending'
VersionAdded: '0.10'

Minitest/RefutePredicate:
Description: 'This cop enforces the test to use `refute_predicate` instead of using `refute(obj.a_predicate_method?)`.'
StyleGuide: 'https://minitest.rubystyle.guide/#refute-predicate'
Enabled: pending
VersionAdded: '<<next>>'

Minitest/RefuteRespondTo:
Description: 'This cop enforces the test to use `refute_respond_to(object, :do_something)` over `refute(object.respond_to?(:do_something))`.'
StyleGuide: 'https://minitest.rubystyle.guide#refute-respond-to'
Expand Down
34 changes: 34 additions & 0 deletions lib/rubocop/cop/minitest/assert_predicate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Minitest
# This cop enforces the test to use `assert_predicate`
# instead of using `assert(obj.a_predicate_method?)`.
#
# @example
# # bad
# assert(obj.one?)
# assert(obj.one?, 'message')
#
# # good
# assert_predicate(obj, :one?)
# assert_predicate(obj, :one?, 'message')
#
class AssertPredicate < Base
include ArgumentRangeHelper
include PredicateAssertionHandleable
extend AutoCorrector

MSG = 'Prefer using `assert_predicate(%<new_arguments>s)`.'
RESTRICT_ON_SEND = %i[assert].freeze

private

def assertion_type
'assert'
end
end
end
end
end
34 changes: 34 additions & 0 deletions lib/rubocop/cop/minitest/refute_predicate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Minitest
# This cop enforces the test to use `refute_predicate`
# instead of using `refute(obj.a_predicate_method?)`.
#
# @example
# # bad
# refute(obj.one?)
# refute(obj.one?, 'message')
#
# # good
# refute_predicate(obj, :one?)
# refute_predicate(obj, :one?, 'message')
#
class RefutePredicate < Base
include ArgumentRangeHelper
include PredicateAssertionHandleable
extend AutoCorrector

MSG = 'Prefer using `refute_predicate(%<new_arguments>s)`.'
RESTRICT_ON_SEND = %i[refute].freeze

private

def assertion_type
'refute'
end
end
end
end
end
3 changes: 3 additions & 0 deletions lib/rubocop/cop/minitest_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
require_relative 'mixin/minitest_cop_rule'
require_relative 'mixin/minitest_exploration_helpers'
require_relative 'mixin/nil_assertion_handleable'
require_relative 'mixin/predicate_assertion_handleable'
require_relative 'minitest/assert_empty'
require_relative 'minitest/assert_empty_literal'
require_relative 'minitest/assert_equal'
require_relative 'minitest/assert_in_delta'
require_relative 'minitest/assert_predicate'
require_relative 'minitest/assert_with_expected_argument'
require_relative 'minitest/assertion_in_lifecycle_hook'
require_relative 'minitest/assert_kind_of'
Expand All @@ -35,6 +37,7 @@
require_relative 'minitest/refute_match'
require_relative 'minitest/refute_instance_of'
require_relative 'minitest/refute_path_exists'
require_relative 'minitest/refute_predicate'
require_relative 'minitest/refute_respond_to'
require_relative 'minitest/test_method_name'
require_relative 'minitest/unreachable_assertion'
Expand Down
61 changes: 61 additions & 0 deletions lib/rubocop/cop/mixin/predicate_assertion_handleable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Minitest
# Common functionality for `Minitest/AssertPredicate` and `Minitest/RefutePredicate` cops.
module PredicateAssertionHandleable
MSG = 'Prefer using `%<assertion_type>s_predicate(%<new_arguments>s)`.'
RESTRICT_ON_SEND = %i[assert].freeze

def on_send(node)
return unless (arguments = peel_redundant_parentheses_from(node.arguments))
return unless arguments.first.respond_to?(:predicate_method?) && arguments.first.predicate_method?
return unless arguments.first.arguments.count.zero?

add_offense(node, message: offense_message(arguments)) do |corrector|
autocorrect(corrector, node, arguments)
end
end

def autocorrect(corrector, node, arguments)
corrector.replace(node.loc.selector, "#{assertion_type}_predicate")

new_arguments = new_arguments(arguments).join(', ')

corrector.replace(first_argument_range(node), new_arguments)
end

private

def peel_redundant_parentheses_from(arguments)
return arguments unless arguments.first&.begin_type?

peel_redundant_parentheses_from(arguments.first.children)
end

def offense_message(arguments)
message_argument = arguments.last if arguments.first != arguments.last

new_arguments = [
new_arguments(arguments),
message_argument&.source
].flatten.compact.join(', ')

format(MSG, assertion_type: assertion_type, new_arguments: new_arguments)
end

def new_arguments(arguments)
receiver = correct_receiver(arguments.first.receiver)
method_name = arguments.first.method_name

[receiver, ":#{method_name}"]
end

def correct_receiver(receiver)
receiver ? receiver.source : 'self'
end
end
end
end
end
145 changes: 145 additions & 0 deletions test/rubocop/cop/minitest/assert_predicate_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# frozen_string_literal: true

require 'test_helper'

class AssertPredicateTest < Minitest::Test
def test_registers_offense_when_using_assert_with_predicate_method
assert_offense(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert(obj.one?)
^^^^^^^^^^^^^^^^ Prefer using `assert_predicate(obj, :one?)`.
end
end
RUBY

assert_correction(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert_predicate(obj, :one?)
end
end
RUBY
end

def test_registers_offense_when_using_assert_with_predicate_method_and_message
assert_offense(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert(obj.one?, 'message')
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `assert_predicate(obj, :one?, 'message')`.
end
end
RUBY

assert_correction(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert_predicate(obj, :one?, 'message')
end
end
RUBY
end

def test_registers_offense_when_using_assert_with_predicate_method_and_heredoc_message
assert_offense(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert(obj.one?, <<~MESSAGE)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `assert_predicate(obj, :one?, <<~MESSAGE)`.
message
MESSAGE
end
end
RUBY

assert_correction(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert_predicate(obj, :one?, <<~MESSAGE)
message
MESSAGE
end
end
RUBY
end

def test_registers_offense_when_using_assert_with_predicate_method_in_redundant_parentheses
assert_offense(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert((obj.one?))
^^^^^^^^^^^^^^^^^^ Prefer using `assert_predicate(obj, :one?)`.
end
end
RUBY

assert_correction(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert_predicate(obj, :one?)
end
end
RUBY
end

def test_registers_offense_when_using_assert_with_receiver_omitted_predicate_method
assert_offense(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert(one?)
^^^^^^^^^^^^ Prefer using `assert_predicate(self, :one?)`.
end
end
RUBY

assert_correction(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert_predicate(self, :one?)
end
end
RUBY
end

def test_does_not_register_offense_when_using_assert_predicate_method
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert_predicate(obj, :one?)
end
end
RUBY
end

def test_does_not_registers_offense_when_using_assert_with_non_predicate_method
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert(obj.do_something)
end
end
RUBY
end

def test_does_not_registers_offense_when_using_assert_with_local_variable
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
obj = create_obj
assert(obj)
end
end
RUBY
end

def test_does_not_register_offense_when_using_assert_with_predicate_method_and_arguments
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
def test_do_something
assert(obj.foo?(arg))
end
end
RUBY
end
end

0 comments on commit 4c38d48

Please sign in to comment.