# frozen_string_literal: truemoduleRuboCopmoduleCopmodulePerformance# This cop is used to identify usages of `count` on an `Enumerable` that# follow calls to `select`, `find_all`, `filter` or `reject`. Querying logic can instead be# passed to the `count` call.## @example# # bad# [1, 2, 3].select { |e| e > 2 }.size# [1, 2, 3].reject { |e| e > 2 }.size# [1, 2, 3].select { |e| e > 2 }.length# [1, 2, 3].reject { |e| e > 2 }.length# [1, 2, 3].select { |e| e > 2 }.count { |e| e.odd? }# [1, 2, 3].reject { |e| e > 2 }.count { |e| e.even? }# array.select(&:value).count## # good# [1, 2, 3].count { |e| e > 2 }# [1, 2, 3].count { |e| e < 2 }# [1, 2, 3].count { |e| e > 2 && e.odd? }# [1, 2, 3].count { |e| e < 2 && e.even? }# Model.select('field AS field_one').count# Model.select(:value).count## `ActiveRecord` compatibility:# `ActiveRecord` will ignore the block that is passed to `count`.# Other methods, such as `select`, will convert the association to an# array and then run the block on the array. A simple work around to# make `count` work with a block is to call `to_a.count {...}`.## Example:# `Model.where(id: [1, 2, 3]).select { |m| m.method == true }.size`## becomes:## `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`classCount<BaseincludeRangeHelpextendAutoCorrectorMSG='Use `count` instead of `%<selector>s...%<counter>s`.'RESTRICT_ON_SEND=%i[count length size].freezedef_node_matcher:count_candidate?,<<~PATTERN
{
(send (block $(send _ ${:select :filter :find_all :reject}) ...) ${:count :length :size})
(send $(send _ ${:select :filter :find_all :reject} (:block_pass _)) ${:count :length :size})
}
PATTERNdefon_send(node)count_candidate?(node)do|selector_node,selector,counter|returnunlesseligible_node?(node)range=source_starting_at(node)doselector_node.loc.selector.begin_posendadd_offense(range,message: format(MSG,selector: selector,counter: counter))do|corrector|autocorrect(corrector,node,selector_node,selector)endendendprivatedefautocorrect(corrector,node,selector_node,selector)selector_loc=selector_node.loc.selectorreturnifselector==:rejectrange=source_starting_at(node){|n|n.loc.dot.begin_pos}corrector.remove(range)corrector.replace(selector_loc,'count')enddefeligible_node?(node)!(node.parent&&node.parent.block_type?)enddefsource_starting_at(node)begin_pos=ifblock_given?yieldnodeelsenode.source_range.begin_posendrange_between(begin_pos,node.source_range.end_pos)endendendendend