123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Naming::PredicateMethod

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/naming/predicate_method.rb

Overview

Checks that predicate methods end with ? and non-predicate methods do not.

The names of predicate methods (methods that return a boolean value) should end in a question mark. Methods that don’t return a boolean, shouldn’t end in a question mark.

The cop assesses a predicate method as one that returns boolean values. Likewise, a method that only returns literal values is assessed as non-predicate. Other predicate method calls are assumed to return boolean values. The cop does not make an assessment if the return type is unknown (non-predicate method calls, variables, etc.).

Note
Operator methods (def ==, etc.) are ignored.

By default, the cop runs in conservative mode, which allows a method to be named with a question mark as long as at least one return value is boolean. In aggressive mode, methods with a question mark will register an offense if any known non-boolean return values are detected.

The cop also has ::RuboCop::Cop::AllowedMethods configuration in order to prevent the cop from registering an offense from a method name that does not confirm to the naming guidelines. By default, call is allowed. The cop also has AllowedPatterns configuration to allow method names by regular expression.

Although returning a call to another predicate method is treated as a boolean value, certain method names can be known to not return a boolean, despite ending in a ? (for example, Numeric#nonzero? returns self or nil). These methods can be configured using NonBooleanPredicates.

The cop can furthermore be configured to allow all bang methods (method names ending with !), with AllowBangMethods: true (default false).

Examples:

Mode: conservative (default)

# bad
def foo
  bar == baz
end

# good
def foo?
  bar == baz
end

# bad
def foo?
  5
end

# good
def foo
  5
end

# bad
def foo
  x == y
end

# good
def foo?
  x == y
end

# bad
def foo
  !x
end

# good
def foo?
  !x
end

# bad - returns the value of another predicate method
def foo
  bar?
end

# good
def foo?
  bar?
end

# good - operator method
def ==(other)
  hash == other.hash
end

# good - at least one return value is boolean
def foo?
  return unless bar?
  true
end

# ok - return type is not known
def foo?
  bar
end

# ok - return type is not known
def foo
  bar?
end

Mode: aggressive

# bad - the method returns nil in some cases
def foo?
  return unless bar?
  true
end

AllowBangMethods: false (default)

# bad
def save!
  true
end

AllowBangMethods: true

# good
def save!
  true
end

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

Class Attribute Summary

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_source?

Override if your cop should be called repeatedly for multiple investigations Between calls to on_new_investigation and on_investigation_end, the result of processed_source will remain constant.

Class Method Summary

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

List of cops that should not try to autocorrect at the same time as this cop.

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Returns a url to view this cops documentation online.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

Override and return the Force class(es) you need to join.

.match?

Returns true if the cop name or the cop namespace matches any of the given names.

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::AllowedPattern - Included

::RuboCop::Cop::AllowedMethods - Included

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense.

#begin_investigation

Called before any investigation.

#callbacks_needed,
#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

#inspect,
#message

Gets called if no message is specified when calling add_offense or add_global_offense Cops are discouraged to override this; instead pass your message directly.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

There should be very limited reasons for a Cop to do it’s own parsing.

#parser_engine,
#ready

Called between investigations.

#relevant_file?,
#target_gem_version

Returns a gems locked versions (i.e.

#target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #range_for_original, #range_from_node_or_range,
#reset_investigation

Actually private methods.

#use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Instance Attribute Details

#allow_bang_methods?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 289

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

#conservative?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 279

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

Instance Method Details

#acceptable?(return_values) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 159

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

#all_return_values_boolean?(return_values) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 189

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

#allowed?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 151

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

#allowed_bang_method?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 283

def allowed_bang_method?(node)
  return false unless allow_bang_methods?

  node.bang_method?
end

#and_or?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 253

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

#boolean_return?(value) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 196

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

  method_returning_boolean?(value)
end

#extract_and_or_clauses(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 257

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

#extract_conditional_branches(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 264

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

#extract_return_value(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 223

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

#last_value(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 236

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

#method_returning_boolean?(value) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 202

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

#on_def(node) Also known as: #on_defs

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 135

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

#on_defs(node)

Alias for #on_def.

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 147

alias on_defs on_def

#potential_non_predicate?(return_values) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 209

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

#process_return_values(return_values) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 241

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

#return_values(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 175

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

#unknown_method_call?(value) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 169

def unknown_method_call?(value)
  return false unless value.call_type?

  !method_returning_boolean?(value)
end

#wayward_predicate?(name) ⇒ Boolean (private)

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

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 296

def wayward_predicate?(name)
  wayward_predicates.include?(name.to_s)
end

#wayward_predicates (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/naming/predicate_method.rb', line 300

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