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.

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 275

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

#conservative?Boolean (readonly, private)

[ GitHub ]

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

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 154

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 184

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 146

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 269

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 242

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

#boolean_return?(value) ⇒ Boolean (private)

[ GitHub ]

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

def boolean_return?(value)
  return true if value.boolean_type?
  return false unless value.call_type?

  value.comparison_method? || value.predicate_method? || value.negation_method?
end

#extract_and_or_clauses(node) (private)

[ GitHub ]

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

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 253

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`.
    node.branches.filter_map { |branch| branch ? last_value(branch) : s(:nil) }
  end
end

#extract_return_value(node) (private)

[ GitHub ]

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

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 225

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

#on_def(node) Also known as: #on_defs

[ GitHub ]

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

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 142

alias on_defs on_def

#potential_non_predicate?(return_values) ⇒ Boolean (private)

[ GitHub ]

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

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 230

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 170

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 164

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

  !value.comparison_method? && !value.predicate_method? && !value.negation_method?
end