lib/rubocop/cop/performance/detect.rb in rubocop-performance-1.8.0 vs lib/rubocop/cop/performance/detect.rb in rubocop-performance-1.8.1
- old
+ new
@@ -1,22 +1,24 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
- # This cop is used to identify usages of
- # `select.first`, `select.last`, `find_all.first`, `find_all.last`, `filter.first`, and `filter.last`
+ # This cop is used to identify usages of `first`, `last`, `[0]` or `[-1]`
+ # chained to `select`, `find_all`, or `find_all`
# 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
# [].filter { |item| true }.first
# [].filter { |item| true }.last
+ # [].filter { |item| true }[0]
+ # [].filter { |item| true }[-1]
#
# # good
# [].detect { |item| true }
# [].reverse.detect { |item| true }
#
@@ -25,31 +27,44 @@
# own meaning. Correcting ActiveRecord methods with this cop should be
# considered unsafe.
class Detect < Base
extend AutoCorrector
+ CANDIDATE_METHODS = Set[:select, :find_all, :filter].freeze
+
MSG = 'Use `%<prefer>s` instead of ' \
'`%<first_method>s.%<second_method>s`.'
REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
'`%<first_method>s.%<second_method>s`.'
+ INDEX_MSG = 'Use `%<prefer>s` instead of ' \
+ '`%<first_method>s[%<index>i]`.'
+ INDEX_REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
+ '`%<first_method>s[%<index>i]`.'
def_node_matcher :detect_candidate?, <<~PATTERN
{
- (send $(block (send _ {:select :find_all :filter}) ...) ${:first :last} $...)
- (send $(send _ {:select :find_all :filter} ...) ${:first :last} $...)
+ (send $(block (send _ %CANDIDATE_METHODS) ...) ${:first :last} $...)
+ (send $(block (send _ %CANDIDATE_METHODS) ...) $:[] (int ${0 -1}))
+ (send $(send _ %CANDIDATE_METHODS ...) ${:first :last} $...)
+ (send $(send _ %CANDIDATE_METHODS ...) $:[] (int ${0 -1}))
}
PATTERN
def on_send(node)
detect_candidate?(node) do |receiver, second_method, args|
+ if second_method == :[]
+ index = args
+ args = {}
+ end
+
return unless args.empty?
return unless receiver
receiver, _args, body = *receiver if receiver.block_type?
return if accept_first_call?(receiver, body)
- register_offense(node, receiver, second_method)
+ register_offense(node, receiver, second_method, index)
end
end
private
@@ -60,38 +75,52 @@
return true if body.nil? && (args.nil? || !args.block_pass_type?)
lazy?(caller)
end
- def register_offense(node, receiver, second_method)
+ def register_offense(node, receiver, second_method, index)
_caller, first_method, _args = *receiver
range = receiver.loc.selector.join(node.loc.selector)
- message = second_method == :last ? REVERSE_MSG : MSG
+ message = message_for_method(second_method, index)
formatted_message = format(message, prefer: preferred_method,
first_method: first_method,
- second_method: second_method)
+ second_method: second_method,
+ index: index)
add_offense(range, message: formatted_message) do |corrector|
- autocorrect(corrector, node)
+ autocorrect(corrector, node, replacement(second_method, index))
end
end
- def autocorrect(corrector, node)
- receiver, first_method = *node
+ def replacement(method, index)
+ if method == :last || method == :[] && index == -1
+ "reverse.#{preferred_method}"
+ else
+ preferred_method
+ end
+ end
- replacement = if first_method == :last
- "reverse.#{preferred_method}"
- else
- preferred_method
- end
+ def autocorrect(corrector, node, replacement)
+ receiver, _first_method = *node
first_range = receiver.source_range.end.join(node.loc.selector)
receiver, _args, _body = *receiver if receiver.block_type?
corrector.remove(first_range)
corrector.replace(receiver.loc.selector, replacement)
+ end
+
+ def message_for_method(method, index)
+ case method
+ when :[]
+ index == -1 ? INDEX_REVERSE_MSG : INDEX_MSG
+ when :last
+ REVERSE_MSG
+ else
+ MSG
+ end
end
def preferred_method
config.for_cop('Style/CollectionMethods')['PreferredMethods']['detect'] || 'detect'
end