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

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

Overview

This cop 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. We have limited the cop to not register an offense for method chains that exceed 2 methods.

Configuration option: ConvertCodeThatCanStartToReturnNil The default for this 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.

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 }

# 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

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 inherited from Base

#add_global_offense, #add_offense, autocorrect_incompatible_with, badge, #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, joining_forces, lint?, match?, #message, #offenses, #on_investigation_end, #on_new_investigation, #on_other_file, #ready, #relevant_file?, support_autocorrect?, support_multiple_source?, #target_rails_version, #target_ruby_version

Methods included from AutocorrectLogic

#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #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

#check_node(node) ⇒ Object


103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 103

def check_node(node)
  checked_variable, receiver, method_chain, method = extract_parts(node)
  return unless receiver == checked_variable
  return if use_var_only_in_unless_modifier?(node, checked_variable)
  # method is already a method call so this is actually checking for a
  # chain greater than 2
  return if chain_size(method_chain, method) > 1
  return if unsafe_method_used?(method_chain, method)
  return if method_chain.method?(:empty?)

  add_offense(node) do |corrector|
    autocorrect(corrector, node)
  end
end

#on_and(node) ⇒ Object


99
100
101
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 99

def on_and(node)
  check_node(node)
end

#on_if(node) ⇒ Object


93
94
95
96
97
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 93

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

  check_node(node)
end

#use_var_only_in_unless_modifier?(node, variable) ⇒ Boolean

Returns:

  • (Boolean)

118
119
120
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 118

def use_var_only_in_unless_modifier?(node, variable)
  node.if_type? && node.unless? && !method_called?(variable)
end