lib/rubocop/cop/rspec/rails/minitest_assertions.rb in rubocop-rspec-2.26.1 vs lib/rubocop/cop/rspec/rails/minitest_assertions.rb in rubocop-rspec-2.27.0
- old
+ new
@@ -2,57 +2,349 @@
module RuboCop
module Cop
module RSpec
module Rails
- # Check if using Minitest matchers.
+ # Check if using Minitest-like matchers.
#
+ # Check the use of minitest-like matchers
+ # starting with `assert_` or `refute_`.
+ #
# @example
# # bad
# assert_equal(a, b)
# assert_equal a, b, "must be equal"
+ # assert_not_includes a, b
# refute_equal(a, b)
+ # assert_nil a
+ # refute_empty(b)
+ # assert_true(a)
+ # assert_false(a)
#
# # good
# expect(b).to eq(a)
# expect(b).to(eq(a), "must be equal")
+ # expect(a).not_to include(b)
# expect(b).not_to eq(a)
+ # expect(a).to eq(nil)
+ # expect(a).not_to be_empty
+ # expect(a).to be(true)
+ # expect(a).to be(false)
#
class MinitestAssertions < Base
extend AutoCorrector
+ # :nodoc:
+ class BasicAssertion
+ extend NodePattern::Macros
+
+ attr_reader :expected, :actual, :failure_message
+
+ def self.minitest_assertion
+ raise NotImplementedError
+ end
+
+ def initialize(expected, actual, failure_message)
+ @expected = expected&.source
+ @actual = actual.source
+ @failure_message = failure_message&.source
+ end
+
+ def replaced(node)
+ runner = negated?(node) ? 'not_to' : 'to'
+ if failure_message.nil?
+ "expect(#{actual}).#{runner} #{assertion}"
+ else
+ "expect(#{actual}).#{runner}(#{assertion}, #{failure_message})"
+ end
+ end
+
+ def negated?(node)
+ node.method_name.start_with?('assert_not_', 'refute_')
+ end
+
+ def assertion
+ raise NotImplementedError
+ end
+ end
+
+ # :nodoc:
+ class EqualAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_equal
+ assert_not_equal
+ refute_equal
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_equal :assert_not_equal :refute_equal} $_ $_ $_?)
+ PATTERN
+
+ def self.match(expected, actual, failure_message)
+ new(expected, actual, failure_message.first)
+ end
+
+ def assertion
+ "eq(#{expected})"
+ end
+ end
+
+ # :nodoc:
+ class KindOfAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_kind_of
+ assert_not_kind_of
+ refute_kind_of
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_kind_of :assert_not_kind_of :refute_kind_of} $_ $_ $_?)
+ PATTERN
+
+ def self.match(expected, actual, failure_message)
+ new(expected, actual, failure_message.first)
+ end
+
+ def assertion
+ "be_a_kind_of(#{expected})"
+ end
+ end
+
+ # :nodoc:
+ class InstanceOfAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_instance_of
+ assert_not_instance_of
+ refute_instance_of
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_instance_of :assert_not_instance_of :refute_instance_of} $_ $_ $_?)
+ PATTERN
+
+ def self.match(expected, actual, failure_message)
+ new(expected, actual, failure_message.first)
+ end
+
+ def assertion
+ "be_an_instance_of(#{expected})"
+ end
+ end
+
+ # :nodoc:
+ class IncludesAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_includes
+ assert_not_includes
+ refute_includes
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_includes :assert_not_includes :refute_includes} $_ $_ $_?)
+ PATTERN
+
+ def self.match(collection, expected, failure_message)
+ new(expected, collection, failure_message.first)
+ end
+
+ def assertion
+ "include(#{expected})"
+ end
+ end
+
+ # :nodoc:
+ class InDeltaAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_in_delta
+ assert_not_in_delta
+ refute_in_delta
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_in_delta :assert_not_in_delta :refute_in_delta} $_ $_ $_? $_?)
+ PATTERN
+
+ def self.match(expected, actual, delta, failure_message)
+ new(expected, actual, delta.first, failure_message.first)
+ end
+
+ def initialize(expected, actual, delta, fail_message)
+ super(expected, actual, fail_message)
+
+ @delta = delta&.source || '0.001'
+ end
+
+ def assertion
+ "be_within(#{@delta}).of(#{expected})"
+ end
+ end
+
+ # :nodoc:
+ class PredicateAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_predicate
+ assert_not_predicate
+ refute_predicate
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_predicate :assert_not_predicate :refute_predicate} $_ ${sym} $_?)
+ PATTERN
+
+ def self.match(subject, predicate, failure_message)
+ return nil unless predicate.value.end_with?('?')
+
+ new(predicate, subject, failure_message.first)
+ end
+
+ def assertion
+ "be_#{expected.delete_prefix(':').delete_suffix('?')}"
+ end
+ end
+
+ # :nodoc:
+ class MatchAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_match
+ refute_match
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_match :refute_match} $_ $_ $_?)
+ PATTERN
+
+ def self.match(matcher, actual, failure_message)
+ new(matcher, actual, failure_message.first)
+ end
+
+ def assertion
+ "match(#{expected})"
+ end
+ end
+
+ # :nodoc:
+ class NilAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_nil
+ assert_not_nil
+ refute_nil
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_nil :assert_not_nil :refute_nil} $_ $_?)
+ PATTERN
+
+ def self.match(actual, failure_message)
+ new(nil, actual, failure_message.first)
+ end
+
+ def assertion
+ 'eq(nil)'
+ end
+ end
+
+ # :nodoc:
+ class EmptyAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_empty
+ assert_not_empty
+ refute_empty
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_empty :assert_not_empty :refute_empty} $_ $_?)
+ PATTERN
+
+ def self.match(actual, failure_message)
+ new(nil, actual, failure_message.first)
+ end
+
+ def assertion
+ 'be_empty'
+ end
+ end
+
+ # :nodoc:
+ class TrueAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_true
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_true} $_ $_?)
+ PATTERN
+
+ def self.match(actual, failure_message)
+ new(nil, actual, failure_message.first)
+ end
+
+ def assertion
+ 'be(true)'
+ end
+ end
+
+ # :nodoc:
+ class FalseAssertion < BasicAssertion
+ MATCHERS = %i[
+ assert_false
+ ].freeze
+
+ # @!method self.minitest_assertion(node)
+ def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
+ (send nil? {:assert_false} $_ $_?)
+ PATTERN
+
+ def self.match(actual, failure_message)
+ new(nil, actual, failure_message.first)
+ end
+
+ def assertion
+ 'be(false)'
+ end
+ end
+
MSG = 'Use `%<prefer>s`.'
- RESTRICT_ON_SEND = %i[assert_equal refute_equal].freeze
- # @!method minitest_assertion(node)
- def_node_matcher :minitest_assertion, <<~PATTERN
- (send nil? {:assert_equal :refute_equal} $_ $_ $_?)
- PATTERN
+ # TODO: replace with `BasicAssertion.subclasses` in Ruby 3.1+
+ ASSERTION_MATCHERS = constants(false).filter_map do |c|
+ const = const_get(c)
+ const if const.is_a?(Class) && const.superclass == BasicAssertion
+ end
+
+ RESTRICT_ON_SEND = ASSERTION_MATCHERS.flat_map { |m| m::MATCHERS }
+
def on_send(node)
- minitest_assertion(node) do |expected, actual, failure_message|
- prefer = replacement(node, expected, actual,
- failure_message.first)
- add_offense(node, message: message(prefer)) do |corrector|
- corrector.replace(node, prefer)
+ ASSERTION_MATCHERS.each do |m|
+ m.minitest_assertion(node) do |*args|
+ assertion = m.match(*args)
+
+ next if assertion.nil?
+
+ on_assertion(node, assertion)
end
end
end
- private
-
- def replacement(node, expected, actual, failure_message)
- runner = node.method?(:assert_equal) ? 'to' : 'not_to'
- if failure_message.nil?
- "expect(#{actual.source}).#{runner} eq(#{expected.source})"
- else
- "expect(#{actual.source}).#{runner}(eq(#{expected.source}), " \
- "#{failure_message.source})"
+ def on_assertion(node, assertion)
+ preferred = assertion.replaced(node)
+ add_offense(node, message: message(preferred)) do |corrector|
+ corrector.replace(node, preferred)
end
end
- def message(prefer)
- format(MSG, prefer: prefer)
+ def message(preferred)
+ format(MSG, prefer: preferred)
end
end
end
end
end