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

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
AllowedMethods
Defined in:
lib/rubocop/cop/lint/redundant_safe_navigation.rb

Overview

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?`.

Examples:

# bad
CamelCaseConst&.do_something

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

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

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

# good
CamelCaseConst.do_something

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

# good - without `&.` this will always return `true`
foo&.respond_to?(:to_a)

# 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
foo.to_h { |k, v| [k, v] }
foo.to_a
foo.to_i
foo.to_f
foo.to_s

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(:[])

Constant Summary collapse

MSG =
'Redundant safe navigation detected, use `.` instead.'
MSG_LITERAL =
'Redundant safe navigation with default literal detected.'
NIL_SPECIFIC_METHODS =
(nil.methods - Object.new.methods).to_set.freeze
SNAKE_CASE =
/\A[[:digit:][:upper:]_]+\z/.freeze

Constants inherited from Base

Base::RESTRICT_ON_SEND

Instance Attribute Summary

Attributes inherited from Base

#config, #processed_source

Instance Method Summary collapse

Methods included from AutoCorrector

support_autocorrect?

Methods inherited from Base

#active_support_extensions_enabled?, #add_global_offense, #add_offense, #always_autocorrect?, autocorrect_incompatible_with, badge, #begin_investigation, callbacks_needed, #callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #contextual_autocorrect?, #cop_config, cop_name, #cop_name, department, documentation_url, exclude_from_registry, #excluded_file?, #external_dependency_checksum, inherited, #initialize, #inspect, joining_forces, lint?, match?, #message, #offenses, #on_investigation_end, #on_new_investigation, #on_other_file, #parse, #parser_engine, #ready, #relevant_file?, requires_gem, support_autocorrect?, support_multiple_source?, #target_rails_version, #target_ruby_version

Methods included from ExcludeLimit

#exclude_limit

Methods included from AutocorrectLogic

#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #autocorrect_with_disable_uncorrectable?, #correctable?, #disable_uncorrectable?, #safe_autocorrect?

Methods included from IgnoredNode

#ignore_node, #ignored_node?, #part_of_ignored_node?

Methods included from Util

silence_warnings

Constructor Details

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

Instance Method Details

#conversion_with_default?(node) ⇒ Object



95
96
97
98
99
100
101
102
103
104
# 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?))
  }
PATTERN

#on_csend(node) ⇒ Object

rubocop:disable Metrics/AbcSize



107
108
109
110
111
112
113
114
115
# 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)
  end

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

#on_or(node) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
# 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)
      corrector.remove(range_with_default)
    end
  end
end

#respond_to_nil_specific_method?(node) ⇒ Object



90
91
92
# 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))
PATTERN