rubocop-hq/rubocop

View on GitHub
lib/rubocop/cop/style/case_equality.rb

Summary

Maintainability
A
25 mins
Test Coverage
A
100%
# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # Checks for uses of the case equality operator(===).
      #
      # If `AllowOnConstant` option is enabled, the cop will ignore violations when the receiver of
      # the case equality operator is a constant.

      # If `AllowOnSelfClass` option is enabled, the cop will ignore violations when the receiver of
      # the case equality operator is `self.class`. Note intermediate variables are not accepted.
      #
      # @example
      #   # bad
      #   (1..100) === 7
      #   /something/ === some_string
      #
      #   # good
      #   something.is_a?(Array)
      #   (1..100).include?(7)
      #   /something/.match?(some_string)
      #
      # @example AllowOnConstant: false (default)
      #   # bad
      #   Array === something
      #
      # @example AllowOnConstant: true
      #   # good
      #   Array === something
      #
      # @example AllowOnSelfClass: false (default)
      #   # bad
      #   self.class === something
      #
      # @example AllowOnSelfClass: true
      #   # good
      #   self.class === something
      #
      class CaseEquality < Base
        extend AutoCorrector

        MSG = 'Avoid the use of the case equality operator `===`.'
        RESTRICT_ON_SEND = %i[===].freeze

        # @!method case_equality?(node)
        def_node_matcher :case_equality?, '(send $#offending_receiver? :=== $_)'

        # @!method self_class?(node)
        def_node_matcher :self_class?, '(send (self) :class)'

        def on_send(node)
          case_equality?(node) do |lhs, rhs|
            return if lhs.const_type? && !lhs.module_name?

            add_offense(node.loc.selector) do |corrector|
              replacement = replacement(lhs, rhs)
              corrector.replace(node, replacement) if replacement
            end
          end
        end

        private

        def offending_receiver?(node)
          return false if node&.const_type? && cop_config.fetch('AllowOnConstant', false)
          return false if self_class?(node) && cop_config.fetch('AllowOnSelfClass', false)

          true
        end

        def replacement(lhs, rhs)
          case lhs.type
          when :regexp
            # The automatic correction from `a === b` to `a.match?(b)` needs to
            # consider `Regexp.last_match?`, `$~`, `$1`, and etc.
            # This correction is expected to be supported by `Performance/Regexp` cop.
            # See: https://github.com/rubocop/rubocop-performance/issues/152
            #
            # So here is noop.
          when :begin
            begin_replacement(lhs, rhs)
          when :const
            const_replacement(lhs, rhs)
          when :send
            send_replacement(lhs, rhs)
          end
        end

        def begin_replacement(lhs, rhs)
          return unless lhs.children.first&.range_type?

          "#{lhs.source}.include?(#{rhs.source})"
        end

        def const_replacement(lhs, rhs)
          "#{rhs.source}.is_a?(#{lhs.source})"
        end

        def send_replacement(lhs, rhs)
          return unless self_class?(lhs)

          "#{rhs.source}.is_a?(#{lhs.source})"
        end
      end
    end
  end
end