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