# frozen_string_literal: truemoduleRuboCopmoduleCopmodulePerformance# Identifies 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.## @safety# This cop is unsafe because it has known compatibility issues with `ActiveRecord` and other# frameworks. Before Rails 5.1, `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 {...}`.## For example:## [source,ruby]# ----# `Model.where(id: [1, 2, 3]).select { |m| m.method == true }.size`# ----## becomes:## [source,ruby]# ----# `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`# ----## @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).countclassCount<BaseincludeRangeHelpextendAutoCorrectorMSG='Use `count` instead of `%<selector>s...%<counter>s`.'RESTRICT_ON_SEND=%i[count length size].freezedef_node_matcher:count_candidate?,<<~PATTERN
{
(call (block $(call _ ${:select :filter :find_all :reject}) ...) ${:count :length :size})
(call $(call _ ${: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)endendendaliason_csendon_sendprivatedefautocorrect(corrector,node,selector_node,selector)selector_loc=selector_node.loc.selectorrange=source_starting_at(node){|n|n.loc.dot.begin_pos}corrector.remove(range)corrector.replace(selector_loc,'count')negate_reject(corrector,node)ifselector==:rejectenddefeligible_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)enddefnegate_reject(corrector,node)ifnode.receiver.call_type?negate_block_pass_reject(corrector,node)elsenegate_block_reject(corrector,node)endenddefnegate_block_pass_reject(corrector,node)corrector.replace(node.receiver.source_range.with(begin_pos: node.receiver.loc.begin.begin_pos),negate_block_pass_as_inline_block(node.receiver))enddefnegate_block_reject(corrector,node)target=ifnode.receiver.body.begin_type?node.receiver.body.children.lastelsenode.receiver.bodyendcorrector.replace(target,negate_expression(target))enddefnegate_expression(node)"!(#{node.source})"enddefnegate_block_pass_as_inline_block(node)ifnode.last_argument.children.first.sym_type?" { |element| !element.#{node.last_argument.children.first.value} }"else" { !#{node.last_argument.children.first.source}.call }"endendendendendend