Class: RuboCop::Cop::Style::SafeNavigation

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector, TargetRubyVersion
Includes:
NilMethods, RangeHelp
Defined in:
lib/rubocop/cop/style/safe_navigation.rb

Overview

Transforms usages of a method call safeguarded by a non nil check for the variable whose method is being called to safe navigation (&.). If there is a method chain, all of the methods in the chain need to be checked for safety, and all of the methods will need to be changed to use safe navigation.

The default for ConvertCodeThatCanStartToReturnNil is false. When configured to true, this will check for code in the format !foo.nil? && foo.bar. As it is written, the return of this code is limited to false and whatever the return of the method is. If this is converted to safe navigation, foo&.bar can start returning nil as well as what the method returns.

The default for MaxChainLength is 2 We have limited the cop to not register an offense for method chains that exceed this option is set.

Examples:

# bad
foo.bar if foo
foo.bar.baz if foo
foo.bar(param1, param2) if foo
foo.bar { |e| e.something } if foo
foo.bar(param) { |e| e.something } if foo

foo.bar if !foo.nil?
foo.bar unless !foo
foo.bar unless foo.nil?

foo && foo.bar
foo && foo.bar.baz
foo && foo.bar(param1, param2)
foo && foo.bar { |e| e.something }
foo && foo.bar(param) { |e| e.something }

foo ? foo.bar : nil
foo.nil? ? nil : foo.bar
!foo.nil? ? foo.bar : nil
!foo ? nil : foo.bar

# good
foo&.bar
foo&.bar&.baz
foo&.bar(param1, param2)
foo&.bar { |e| e.something }
foo&.bar(param) { |e| e.something }
foo && foo.bar.baz.qux # method chain with more than 2 methods
foo && foo.nil? # method that `nil` responds to

# Method calls that do not use `.`
foo && foo < bar
foo < bar if foo

# When checking `foo&.empty?` in a conditional, `foo` being `nil` will actually
# do the opposite of what the author intends.
foo && foo.empty?

# This could start returning `nil` as well as the return of the method
foo.nil? || foo.bar
!foo || foo.bar

# Methods that are used on assignment, arithmetic operation or
# comparison should not be converted to use safe navigation
foo.baz = bar if foo
foo.baz + bar if foo
foo.bar > 2 if foo

Cop Safety Information:

  • Autocorrection is unsafe because if a value is false, the resulting code will have different behavior or raise an error.

    x = false
    x && x.foo  # return false
    x&.foo      # raises NoMethodError

Constant Summary collapse

MSG =
'Use safe navigation (`&.`) instead of checking if an object ' \
'exists before calling the method.'
LOGIC_JUMP_KEYWORDS =
%i[break fail next raise return throw yield].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 included from TargetRubyVersion

minimum_target_ruby_version, required_minimum_ruby_version, support_target_ruby_version?

Methods inherited from Base

#active_support_extensions_enabled?, #add_global_offense, #add_offense, autocorrect_incompatible_with, badge, #begin_investigation, callbacks_needed, #callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #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, #ready, #relevant_file?, 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

#modifier_if_safe_navigation_candidate(node) ⇒ Object

if format: (if checked_variable body nil) unless format: (if checked_variable nil body)



99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 99

def_node_matcher :modifier_if_safe_navigation_candidate, <<~PATTERN
  {
    (if {
          (send $_ {:nil? :!})
          $_
        } nil? $_)

    (if {
          (send (send $_ :nil?) :!)
          $_
        } $_ nil?)
  }
PATTERN

#not_nil_check?(node) ⇒ Object



125
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 125

def_node_matcher :not_nil_check?, '(send (send $_ :nil?) :!)'

#on_and(node) ⇒ Object



133
134
135
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 133

def on_and(node)
  check_node(node)
end

#on_if(node) ⇒ Object



127
128
129
130
131
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 127

def on_if(node)
  return if allowed_if_condition?(node)

  check_node(node)
end

#ternary_safe_navigation_candidate(node) ⇒ Object



114
115
116
117
118
119
120
121
122
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 114

def_node_matcher :ternary_safe_navigation_candidate, <<~PATTERN
  {
    (if (send $_ {:nil? :!}) nil $_)

    (if (send (send $_ :nil?) :!) $_ nil)

    (if $_ $_ nil)
  }
PATTERN