Module: RuboCop::Cop::MinitestCopRule

Overview

Provide a method to define offense rule for Minitest cops.

Instance Method Summary collapse

Instance Method Details

#define_rule(assertion_method, target_method:, preferred_method: nil, inverse: false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Define offense rule for Minitest cops.

Examples:

define_rule :assert, target_method: :match
define_rule :refute, target_method: :match
define_rule :assert, target_method: :include?, preferred_method: :assert_includes
define_rule :assert, target_method: :instance_of?, inverse: true

Multiple target methods

# `preferred_method` is required
define_rule :assert, target_method: %i[match match? =~],
            preferred_method: :assert_match, inverse: 'regexp_type?'

Parameters:

  • assertion_method (Symbol)

    Assertion method like assert or refute.

  • target_method (Symbol, Array<Symbol>)

    Method name(s) offensed by assertion method arguments.

  • preferred_method (Symbol) (defaults to: nil)

    Is required if passing multiple target methods. Custom method name replaced by autocorrection. The preferred method name that connects assertion_method and target_method with _ is the default name.

  • inverse (Boolean, String) (defaults to: false)

    An optional param. Order of arguments replaced by autocorrection. If string is passed, it becomes a predicate method for the first argument node.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/rubocop/cop/mixin/minitest_cop_rule.rb', line 31

def define_rule(assertion_method, target_method:, preferred_method: nil, inverse: false)
  target_methods = Array(target_method)
  if target_methods.size > 1 && preferred_method.nil?
    raise ArgumentError, '`:preferred_method` keyword argument must be used if using more than one target method.'
  end

  preferred_method = "#{assertion_method}_#{target_methods.first.to_s.delete('?')}" if preferred_method.nil?

  class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
    include ArgumentRangeHelper
    extend AutoCorrector

    MSG = 'Prefer using `#{preferred_method}(%<new_arguments>s)`.'
    RESTRICT_ON_SEND = %i[#{assertion_method}].freeze

    def on_send(node)
      return unless node.method?(:#{assertion_method})
      return unless node.arguments.first&.call_type?
      return if node.arguments.first.arguments.empty? ||
                #{target_methods}.none? { |target_method| node.arguments.first.method?(target_method) }

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

    def autocorrect(corrector, node, arguments)
      corrector.replace(node.loc.selector, '#{preferred_method}')

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

      corrector.replace(node.first_argument, new_arguments)
    end

    private

    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,
        new_arguments: new_arguments
      )
    end

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

      new_arguments = [receiver, method_argument&.source].compact
      inverse_condition = if %w[true false].include?('#{inverse}')
        #{inverse}
      else
        method_argument.#{inverse}
      end
      new_arguments.reverse! if inverse_condition
      new_arguments
    end

    def correct_receiver(receiver)
      receiver ? receiver.source : 'self'
    end
  RUBY
end