Class: RuboCop::Cop::Lint::RedundantSafeNavigation

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


Checks for redundant safe navigation calls. Use cases where a constant, named in camel case for classes and modules is nil are rare, and an offense is not detected when the receiver is a constant. The detection also applies to literal receivers, except for nil.

For all receivers, the instance_of?, kind_of?, is_a?, eql?, respond_to?, and equal? methods are checked by default. These are customizable with AllowedMethods option.

The AllowedMethods option specifies nil-safe methods, in other words, it is a method that is allowed to skip safe navigation. Note that the AllowedMethod option is not an option that specifies methods for which to suppress (allow) this cop’s check.

In the example below, the safe navigation operator (&.) is unnecessary because NilClass has methods like respond_to? and is_a?.


# bad

# bad
do_something if attrs&.respond_to?(:[])

# good
do_something if attrs.respond_to?(:[])

# bad
while node&.is_a?(BeginNode)
  node = node.parent

# good

# good
while node.is_a?(BeginNode)
  node = node.parent

# good - without `&.` this will always return `true`

# bad - for `nil`s conversion methods return default values for the type
foo&.to_h || {}
foo&.to_h { |k, v| [k, v] } || {}
foo&.to_a || []
foo&.to_i || 0
foo&.to_f || 0.0
foo&.to_s || ''

# good
foo.to_h { |k, v| [k, v] }

AllowedMethods: [nil_safe_method]

# bad
do_something if attrs&.nil_safe_method(:[])

# good
do_something if attrs.nil_safe_method(:[])
do_something if attrs&.not_nil_safe_method(:[])

Cop Safety Information:

  • This cop is unsafe, because autocorrection can change the return type of the expression. An offending expression that previously could return nil will be autocorrected to never return nil.

Constant Summary

::RuboCop::Cop::Base - Inherited


Class Attribute Summary

::RuboCop::Cop::AutoCorrector - Extended

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,

Returns if class supports autocorrect.


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


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



.callbacks_needed, .cop_name, .department,

Returns an url to view this cops documentation online.


Call for abstract Cop classes.


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


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


Register a version requirement for the given gem name.


::RuboCop::ExcludeLimit - Extended


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


Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::AllowedMethods - Included

::RuboCop::Cop::Base - Inherited


Adds an offense that has no particular location.


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.


Called before any investigation.


Configuration Helpers.

#cop_name, #excluded_file?,

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


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.


Alias for Base#cop_name.


Called after all on_…​


Called before all on_…​


Called instead of all on_…​


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


Called between investigations.

#relevant_file?, #target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,

Reserved for Cop::Cop.


Called to complete an investigation.

#correct, #current_corrector,

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,

Actually private methods.


::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

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

Instance Method Details

#assume_receiver_instance_exists?(receiver) ⇒ Boolean (private)

[ GitHub ]

# File 'lib/rubocop/cop/lint/redundant_safe_navigation.rb', line 133

def assume_receiver_instance_exists?(receiver)
  return true if receiver.const_type? && !receiver.source.match?(SNAKE_CASE)

  receiver.literal? && !receiver.nil_type?

#check?(node) ⇒ Boolean (private)

[ GitHub ]

# File 'lib/rubocop/cop/lint/redundant_safe_navigation.rb', line 139

def check?(node)
  parent = node.parent
  return false unless parent

  condition?(parent, node) ||
    parent.and_type? ||
    parent.or_type? ||
    (parent.send_type? && parent.negation_method?)

#condition?(parent, node) ⇒ Boolean (private)

[ GitHub ]

# File 'lib/rubocop/cop/lint/redundant_safe_navigation.rb', line 149

def condition?(parent, node)
  (parent.conditional? || parent.post_condition_loop?) && parent.condition == node


[ GitHub ]

# File 'lib/rubocop/cop/lint/redundant_safe_navigation.rb', line 95

def_node_matcher :conversion_with_default?, <<~PATTERN
    (or $(csend _ :to_h) (hash))
    (or (block $(csend _ :to_h) ...) (hash))
    (or $(csend _ :to_a) (array))
    (or $(csend _ :to_i) (int 0))
    (or $(csend _ :to_f) (float 0.0))
    (or $(csend _ :to_s) (str empty?))


[ GitHub ]

# File 'lib/rubocop/cop/lint/redundant_safe_navigation.rb', line 107

def on_csend(node)
  unless assume_receiver_instance_exists?(node.receiver)
    return unless check?(node) && allowed_method?(node.method_name)
    return if respond_to_nil_specific_method?(node)

  range = node.loc.dot
  add_offense(range) { |corrector| corrector.replace(range, '.') }


[ GitHub ]

# File 'lib/rubocop/cop/lint/redundant_safe_navigation.rb', line 117

def on_or(node)
  conversion_with_default?(node) do |send_node|
    range = send_node.loc.dot.begin.join(node.source_range.end)

    add_offense(range, message: MSG_LITERAL) do |corrector|
      corrector.replace(send_node.loc.dot, '.')

      range_with_default = node.lhs.source_range.end.begin.join(node.source_range.end)


[ GitHub ]

# File 'lib/rubocop/cop/lint/redundant_safe_navigation.rb', line 90

def_node_matcher :respond_to_nil_specific_method?, <<~PATTERN
  (csend _ :respond_to? (sym %NIL_SPECIFIC_METHODS))