class RuboCop::Cop::Naming::PredicateMethod


end
true
def save!
# good
@example AllowBangMethods: true
end
true
def save!
# bad
@example AllowBangMethods: false (default)
end
true
return unless bar?
def foo?
# bad - the method returns nil in some cases
@example Mode: aggressive
end
bar?
def foo
# ok - return type is not known
end
bar
def foo?
# ok - return type is not known
end
true
return unless bar?
def foo?
# good - at least one return value is boolean
end
hash == other.hash
def ==(other)
# good - operator method
end
bar?
def foo?
# good
end
bar?
def foo
# bad - returns the value of another predicate method
end
!x
def foo?
# good
end
!x
def foo
# bad
end
x == y
def foo?
# good
end
x == y
def foo
# bad
end
5
def foo
# good
end
5
def foo?
# bad
end
bar == baz
def foo?
# good
end
bar == baz
def foo
# bad
@example Mode: conservative (default)
ending with ‘!`), with `AllowBangMethods: true` (default false).
The cop can furthermore be configured to allow all bang methods (method names
configured using `NonBooleanPredicates`.
(for example, `Numeric#nonzero?` returns `self` or `nil`). These methods can be
certain method names can be known to not return a boolean, despite ending in a `?`
Although returning a call to another predicate method is treated as a boolean value,
configuration to allow method names by regular expression.
guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns`
registering an offense from a method name that does not confirm to the naming
The cop also has `AllowedMethods` configuration in order to prevent the cop from
return values are detected.
mode, methods with a question mark will register an offense if any known non-boolean
with a question mark as long as at least one return value is boolean. In `aggressive`
By default, the cop runs in `conservative` mode, which allows a method to be named
NOTE: Operator methods (`def ==`, etc.) are ignored.
if the return type is unknown (non-predicate method calls, variables, etc.).
method calls are assumed to return boolean values. The cop does not make an assessment
a method that only returns literal values is assessed as non-predicate. Other predicate
The cop assesses a predicate method as one that returns boolean values. Likewise,
end in a question mark.
in a question mark. Methods that don’t return a boolean, shouldn’t
The names of predicate methods (methods that return a boolean value) should end
Checks that predicate methods end with ‘?` and non-predicate methods do not.

def acceptable?(return_values)

def acceptable?(return_values)
  # In `conservative` mode, if the method returns `super`, `zsuper`, or a
  # non-comparison method call, the method name is acceptable.
  return false unless conservative?
  return_values.any? do |value|
    value.type?(:super, :zsuper) || unknown_method_call?(value)
  end
end

def all_return_values_boolean?(return_values)

def all_return_values_boolean?(return_values)
  values = return_values.reject { |value| value.type?(:super, :zsuper) }
  return false if values.empty?
  values.all? { |value| boolean_return?(value) }
end

def allow_bang_methods?

def allow_bang_methods?
  cop_config.fetch('AllowBangMethods', false)
end

def allowed?(node)

def allowed?(node)
  allowed_method?(node.method_name) ||
    matches_allowed_pattern?(node.method_name) ||
    allowed_bang_method?(node) ||
    node.operator_method? ||
    node.body.nil?
end

def allowed_bang_method?(node)

def allowed_bang_method?(node)
  return false unless allow_bang_methods?
  node.bang_method?
end

def and_or?(node)

def and_or?(node)
  node.type?(:and, :or)
end

def boolean_return?(value)

def boolean_return?(value)
  return true if value.boolean_type?
  method_returning_boolean?(value)
end

def conservative?

def conservative?
  cop_config.fetch('Mode', :conservative).to_sym == :conservative
end

def extract_and_or_clauses(node)

def extract_and_or_clauses(node)
  # Recursively traverse an `and` or `or` node to collect all clauses within
  return node unless and_or?(node)
  [extract_and_or_clauses(node.lhs), extract_and_or_clauses(node.rhs)].flatten
end

def extract_conditional_branches(node)

def extract_conditional_branches(node)
  return node unless node.conditional?
  if node.type?(:while, :until)
    # If there is no body, act as implicit `nil`.
    node.body ? [last_value(node.body)] : [s(:nil)]
  else
    # Branches with no value act as an implicit `nil`.
    branches = node.branches.map { |branch| branch ? last_value(branch) : s(:nil) }
    # Missing else branches also act as an implicit `nil`.
    branches.push(s(:nil)) unless node.else_branch
    branches
  end
end

def extract_return_value(node)

def extract_return_value(node)
  return node unless node.return_type?
  # `return` without a value is a `nil` return.
  return s(:nil) if node.arguments.empty?
  # When there's a multiple return, it cannot be a predicate
  # so just return an `array` sexp for simplicity.
  return s(:array) unless node.arguments.one?
  node.first_argument
end

def last_value(node)

def last_value(node)
  value = node.begin_type? ? node.children.last : node
  value&.return_type? ? extract_return_value(value) : value
end

def method_returning_boolean?(value)

def method_returning_boolean?(value)
  return false unless value.call_type?
  return false if wayward_predicate?(value.method_name)
  value.comparison_method? || value.predicate_method? || value.negation_method?
end

def on_def(node)

def on_def(node)
  return if allowed?(node)
  return_values = return_values(node.body)
  return if acceptable?(return_values)
  if node.predicate_method? && potential_non_predicate?(return_values)
    add_offense(node.loc.name, message: MSG_NON_PREDICATE)
  elsif !node.predicate_method? && all_return_values_boolean?(return_values)
    add_offense(node.loc.name, message: MSG_PREDICATE)
  end
end

def potential_non_predicate?(return_values)

def potential_non_predicate?(return_values)
  # Assumes a method to be non-predicate if all return values are non-boolean literals.
  #
  # In `Mode: conservative`, if any of the return values is a boolean,
  # the method name is acceptable.
  # In `Mode: aggressive`, all return values must be booleans for a predicate
  # method, or else an offense will be registered.
  return false if conservative? && return_values.any? { |value| boolean_return?(value) }
  return_values.any? do |value|
    value.literal? && !value.boolean_type?
  end
end

def process_return_values(return_values)

def process_return_values(return_values)
  return_values.flat_map do |value|
    if value.conditional?
      process_return_values(extract_conditional_branches(value))
    elsif and_or?(value)
      process_return_values(extract_and_or_clauses(value))
    else
      value
    end
  end
end

def return_values(node)

def return_values(node)
  # Collect all the (implicit and explicit) return values of a node
  return_values = Set.new(node.begin_type? ? [] : [extract_return_value(node)])
  node.each_descendant(:return) do |return_node|
    return_values << extract_return_value(return_node)
  end
  last_value = last_value(node)
  return_values << last_value if last_value
  process_return_values(return_values)
end

def unknown_method_call?(value)

def unknown_method_call?(value)
  return false unless value.call_type?
  !method_returning_boolean?(value)
end

def wayward_predicate?(name)

value, despite the method naming.
(for example, `Numeric#nonzero?`) it should be treated as a non-boolean
If a method ending in `?` is known to not return a boolean value,
def wayward_predicate?(name)
  wayward_predicates.include?(name.to_s)
end

def wayward_predicates

def wayward_predicates
  Array(cop_config.fetch('WaywardPredicates', []))
end