class RuboCop::Cop::Style::NumericPredicate

Checks for usage of comparison operators (‘==`, `>`, `<`) to test numbers as zero, positive, or negative. These can be replaced by their respective predicate methods. This cop can also be configured to do the reverse.

This cop can be customized allowed methods with ‘AllowedMethods`. By default, there are no methods to allowed.

This cop disregards ‘#nonzero?` as its value is truthy or falsey, but not `true` and `false`, and thus not always interchangeable with `!= 0`.

This cop allows comparisons to global variables, since they are often populated with objects which can be compared with integers, but are not themselves ‘Integer` polymorphic.

@safety

This cop is unsafe because it cannot be guaranteed that the receiver
defines the predicates or can be compared to a number, which may lead
to a false positive for non-standard classes.

@example EnforcedStyle: predicate (default)

# bad
foo == 0
0 > foo
bar.baz > 0

# good
foo.zero?
foo.negative?
bar.baz.positive?

@example EnforcedStyle: comparison

# bad
foo.zero?
foo.negative?
bar.baz.positive?

# good
foo == 0
0 > foo
bar.baz > 0

@example AllowedMethods: [] (default) with EnforcedStyle: predicate

# bad
foo == 0
0 > foo
bar.baz > 0

@example AllowedMethods: [==] with EnforcedStyle: predicate

# good
foo == 0

# bad
0 > foo
bar.baz > 0

@example AllowedPatterns: [] (default) with EnforcedStyle: comparison

# bad
foo.zero?
foo.negative?
bar.baz.positive?

@example AllowedPatterns: [‘zero’] with EnforcedStyle: predicate

# good
# bad
foo.zero?

# bad
foo.negative?
bar.baz.positive?

Constants

MSG
REPLACEMENTS
RESTRICT_ON_SEND

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubocop/cop/style/numeric_predicate.rb, line 90
def on_send(node)
  numeric, replacement = check(node)
  return unless numeric

  return if allowed_method_name?(node.method_name) ||
            node.each_ancestor(:send, :block).any? do |ancestor|
              allowed_method_name?(ancestor.method_name)
            end

  message = format(MSG, prefer: replacement, current: node.source)
  add_offense(node, message: message) do |corrector|
    corrector.replace(node, replacement)
  end
end

Private Instance Methods

allowed_method_name?(name) click to toggle source
# File lib/rubocop/cop/style/numeric_predicate.rb, line 107
def allowed_method_name?(name)
  allowed_method?(name) || matches_allowed_pattern?(name)
end
check(node) click to toggle source
# File lib/rubocop/cop/style/numeric_predicate.rb, line 111
def check(node)
  numeric, operator =
    if style == :predicate
      comparison(node) || inverted_comparison(node, &invert)
    else
      predicate(node)
    end

  return unless numeric && operator && replacement_supported?(operator)

  [numeric, replacement(numeric, operator)]
end
invert() click to toggle source
# File lib/rubocop/cop/style/numeric_predicate.rb, line 152
def invert
  lambda do |comparison, numeric|
    comparison = { :> => :<, :< => :> }[comparison] || comparison

    [numeric, comparison]
  end
end
parenthesized_source(node) click to toggle source
# File lib/rubocop/cop/style/numeric_predicate.rb, line 132
def parenthesized_source(node)
  if require_parentheses?(node)
    "(#{node.source})"
  else
    node.source
  end
end
replacement(numeric, operation) click to toggle source
# File lib/rubocop/cop/style/numeric_predicate.rb, line 124
def replacement(numeric, operation)
  if style == :predicate
    [parenthesized_source(numeric), REPLACEMENTS.invert[operation.to_s]].join('.')
  else
    [numeric.source, REPLACEMENTS[operation.to_s], 0].join(' ')
  end
end
replacement_supported?(operator) click to toggle source
# File lib/rubocop/cop/style/numeric_predicate.rb, line 144
def replacement_supported?(operator)
  if %i[> <].include?(operator)
    target_ruby_version >= 2.3
  else
    true
  end
end
require_parentheses?(node) click to toggle source
# File lib/rubocop/cop/style/numeric_predicate.rb, line 140
def require_parentheses?(node)
  node.send_type? && node.binary_operation? && !node.parenthesized?
end