class RuboCop::Cop::Style::CollectionQuerying
Prefer ‘Enumerable` predicate methods over expressions with `count`.
The cop checks calls to ‘count` without arguments, or with a block. It doesn’t register offenses for ‘count` with a positional argument because its behavior differs from predicate methods (`count` matches the argument using `==`, while `any?`, `none?` and `one?` use `===`).
NOTE: This cop doesn’t check ‘length` and `size` methods because they would yield false positives. For
example, `String` implements `length` and `size`, but it doesn’t include ‘Enumerable`.
@safety
The cop is unsafe because receiver might not include `Enumerable`, or it has nonstandard implementation of `count` or any replacement methods. It's also unsafe because for collections with falsey values, expressions with `count` without a block return a different result than methods `any?`, `none?` and `one?`: [source,ruby] ---- [nil, false].count.positive? [nil].count == 1 # => true [nil, false].any? [nil].one? # => false [nil].count == 0 # => false [nil].none? # => true ---- Autocorrection is unsafe when replacement methods don't iterate over every element in collection and the given block runs side effects: [source,ruby] ---- x.count(&:method_with_side_effects).positive? # calls `method_with_side_effects` on every element x.any?(&:method_with_side_effects) # calls `method_with_side_effects` until first element returns a truthy value ----
@example
# bad x.count.positive? x.count > 0 x.count != 0 x.count(&:foo?).positive? x.count { |item| item.foo? }.positive? # good x.any? x.any?(&:foo?) x.any? { |item| item.foo? } # bad x.count.zero? x.count == 0 # good x.none? # bad x.count == 1 x.one?
@example AllCops:ActiveSupportExtensionsEnabled: false (default)
# good x.count > 1
@example AllCops:ActiveSupportExtensionsEnabled: true
# bad x.count > 1 # good x.many?
Constants
- MSG
- REPLACEMENTS
- RESTRICT_ON_SEND
Public Instance Methods
Source
# File lib/rubocop/cop/style/collection_querying.rb, line 132 def on_send(node) return unless (count_node = count_predicate(node)) replacement_method = replacement_method(node) return unless replacement_supported?(replacement_method) offense_range = count_node.loc.selector.join(node.source_range.end) add_offense(offense_range, message: format(MSG, prefer: replacement_method)) do |corrector| corrector.replace(count_node.loc.selector, replacement_method) corrector.remove(removal_range(node)) end end
Private Instance Methods
Source
# File lib/rubocop/cop/style/collection_querying.rb, line 159 def removal_range(node) range = (node.loc.dot || node.loc.selector).join(node.source_range.end) range_with_surrounding_space(range, side: :left) end
Source
# File lib/rubocop/cop/style/collection_querying.rb, line 149 def replacement_method(node) REPLACEMENTS.fetch([node.method_name, node.first_argument&.value]) end
Source
# File lib/rubocop/cop/style/collection_querying.rb, line 153 def replacement_supported?(method_name) return true if active_support_extensions_enabled? method_name != :many? end