lib/rubocop/cop/sorbet/select_by_is_a.rb



# frozen_string_literal: true

require "rubocop"

module RuboCop
  module Cop
    module Sorbet
      # Suggests using `grep` over `select` when using it only for type narrowing.
      #
      # @example
      #
      #   # bad
      #   strings_or_integers.select { |e| e.is_a?(String) }
      #   strings_or_integers.filter { |e| e.is_a?(String) }
      #   strings_or_integers.select { |e| e.kind_of?(String) }
      #
      #   # good
      #   strings_or_integers.grep(String)
      class SelectByIsA < RuboCop::Cop::Base
        extend AutoCorrector

        MSG = "Use `grep` instead of `select` when using it only for type narrowing."
        RESTRICT_ON_SEND = [:select, :filter].freeze

        # @!method type_narrowing_select?(node)
        def_node_matcher :type_narrowing_select?, <<~PATTERN
          {
            (block
              (call _ {:select :filter})
              (args (arg _))
              (send (lvar _) { :is_a? :kind_of? } (const nil? _)))
            (numblock
              (call _ {:select :filter})
              _
              (send (lvar _) { :is_a? :kind_of? } (const nil? _)))
            (itblock
              (call _ {:select :filter})
              _
              (send (lvar _) { :is_a? :kind_of? } (const nil? _)))
          }
        PATTERN

        def on_send(node)
          block_node = node.block_node

          return unless block_node
          return unless type_narrowing_select?(block_node)

          add_offense(block_node) do |corrector|
            receiver = node.receiver
            type_class = block_node.body.children[2]
            navigation = node.csend_type? ? "&." : "."
            replacement = "#{receiver.source}#{navigation}grep(#{type_class.source})"

            corrector.replace(block_node, replacement)
          end
        end
        alias_method :on_csend, :on_send
      end
    end
  end
end