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.

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 }

# 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, #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

#check_node(node) ⇒ Object



118
119
120
121
122
123
124
125
126
127
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 118

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)
  return if chain_length(method_chain, method) > max_chain_length
  return if unsafe_method_used?(method_chain, method)
  return if method_chain.method?(:empty?)

  add_offense(node) { |corrector| autocorrect(corrector, node) }
end

#modifier_if_safe_navigation_candidate(node) ⇒ Object

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



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 91

def_node_matcher :modifier_if_safe_navigation_candidate, "{\n  (if {\n        (send $_ {:nil? :!})\n        $_\n      } nil? $_)\n\n  (if {\n        (send (send $_ :nil?) :!)\n        $_\n      } $_ nil?)\n}\n"

#not_nil_check?(node) ⇒ Object



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

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

#on_and(node) ⇒ Object



114
115
116
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 114

def on_and(node)
  check_node(node)
end

#on_if(node) ⇒ Object



108
109
110
111
112
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 108

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

  check_node(node)
end

#use_var_only_in_unless_modifier?(node, variable) ⇒ Boolean



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

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