class RuboCop::Cop::Performance::Detect

This cop is used to identify usages of `select.first`, `select.last`, `find_all.first`, and `find_all.last` and change them to use `detect` instead.

@example

# bad
[].select { |item| true }.first
[].select { |item| true }.last
[].find_all { |item| true }.first
[].find_all { |item| true }.last

# good
[].detect { |item| true }
[].reverse.detect { |item| true }

`ActiveRecord` compatibility: `ActiveRecord` does not implement a `detect` method and `find` has its own meaning. Correcting ActiveRecord methods with this cop should be considered unsafe.

Constants

DANGEROUS_METHODS
MSG
REVERSE_MSG
SELECT_METHODS

Public Instance Methods

autocorrect(node) click to toggle source
# File lib/rubocop/cop/performance/detect.rb, line 43
def autocorrect(node)
  receiver, first_method = *node

  replacement = if first_method == :last
                  "reverse.#{preferred_method}"
                else
                  preferred_method
                end

  first_range = receiver.source_range.end.join(node.loc.selector)

  receiver, _args, _body = *receiver if receiver.block_type?

  lambda do |corrector|
    corrector.remove(first_range)
    corrector.replace(receiver.loc.selector, replacement)
  end
end
on_send(node) click to toggle source
# File lib/rubocop/cop/performance/detect.rb, line 32
def on_send(node)
  return unless should_run?
  receiver, second_method, *args = *node
  return if accept_second_call?(receiver, second_method, args)

  receiver, _args, body = *receiver if receiver.block_type?
  return if accept_first_call?(receiver, body)

  offense(node, receiver, second_method)
end

Private Instance Methods

accept_first_call?(receiver, body) click to toggle source
# File lib/rubocop/cop/performance/detect.rb, line 76
def accept_first_call?(receiver, body)
  caller, first_method, args = *receiver

  # check that we have usual block or block pass
  return true if body.nil? && (args.nil? || !args.block_pass_type?)
  return true unless SELECT_METHODS.include?(first_method)

  lazy?(caller)
end
accept_second_call?(receiver, method, args) click to toggle source
# File lib/rubocop/cop/performance/detect.rb, line 70
def accept_second_call?(receiver, method, args)
  !receiver ||
    !DANGEROUS_METHODS.include?(method) ||
    !args.empty?
end
lazy?(node) click to toggle source
# File lib/rubocop/cop/performance/detect.rb, line 102
def lazy?(node)
  return false if node.nil?
  receiver, method, _args = *node
  method == :lazy && !receiver.nil?
end
offense(node, receiver, second_method) click to toggle source
# File lib/rubocop/cop/performance/detect.rb, line 86
def offense(node, receiver, second_method)
  _caller, first_method, _args = *receiver
  range = receiver.loc.selector.join(node.loc.selector)

  message = second_method == :last ? REVERSE_MSG : MSG
  add_offense(node, range, format(message,
                                  preferred_method,
                                  first_method,
                                  second_method))
end
preferred_method() click to toggle source
# File lib/rubocop/cop/performance/detect.rb, line 97
def preferred_method
  config.for_cop('Style/CollectionMethods')              ['PreferredMethods']['detect'] || 'detect'
end
should_run?() click to toggle source
# File lib/rubocop/cop/performance/detect.rb, line 64
def should_run?
  !(cop_config['SafeMode'.freeze] ||
    config['Rails'.freeze] &&
    config['Rails'.freeze]['Enabled'.freeze])
end