rubocop / rubocop-minitest

Code style checking for Minitest files.
https://docs.rubocop.org/rubocop-minitest
MIT License
144 stars 44 forks source link

Add `{Assert,Refute}Match` `OnlyRegexpLiteral` config #323

Open sambostock opened 2 months ago

sambostock commented 2 months ago

AssertMatch & RefuteMatch enforce

-assert matcher.match?(object)
+assert_match(matcher, object)

and

-refute matcher.match?(object)
+refute_match(matcher, object)

respectively, which use =~ under the hood. This makes the cops highly susceptible to false positives, in cases where the matcher object responds to match?, but not to =~

class SuitColorMatcher
  BLACK = [:clubs,    :spades]
  RED   = [:diamonds, :hearts]
  SUITS = BLACK + RED

  def initialize(suit)
    raise ArgumentError, suit.inspect unless SUITS.include?(suit)
    @suit = suit
  end

  def match?(other)
    case @suit
    when *BLACK then BLACK.include?(other)
    when *RED   then RED  .include?(other)
    end
  end
end

SuitColorMatcher.new(:clubs).match?(:spades) # => true
SuitColorMatcher.new(:clubs) =~ :spades      # NoMethodError

or cases where the matcher object does also respond to =~, but in an incompatible way

"".match? "" # => true
"" =~ ""     # TypeError

Given RuboCop doesn't have runtime type information, the only case we can reliably identify is the case where a Regexp literal is used.

Therefore, this adds an OnlyRegexpLiteral config (false by default), which consumers can enable if their codebase has many false positives.

This provides a way for consumers to address #310, without changing the cop's behavior for everyone.

As part of making this change, a common module is extracted for the implementations of AssertMatch and RefuteMatch, which are largely identical.


Before submitting the PR make sure the following are checked: