class RuboCop::Cop::Style::SafeNavigation

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 (`&.`).

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.

@example

# bad
foo.bar 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(param1, param2)
foo && foo.bar { |e| e.something }
foo && foo.bar(param) { |e| e.something }

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

foo.nil? || foo.bar
!foo || foo.bar

# Methods that `nil` will `respond_to?` should not be converted to
# use safe navigation
foo.to_i if foo

Constants

MSG
NIL_METHODS

Public Instance Methods

autocorrect(node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 90
def autocorrect(node)
  _check, body, = node.node_parts
  _checked_variable, matching_receiver, = extract_parts(node)
  method_call, = matching_receiver.parent

  lambda do |corrector|
    corrector.remove(begin_range(node, body))
    corrector.remove(end_range(node, body))
    corrector.insert_before((method_call || body).loc.dot, '&')
  end
end
check_node(node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 81
def check_node(node)
  return if target_ruby_version < 2.3
  checked_variable, receiver, method = extract_parts(node)
  return unless receiver == checked_variable
  return if unsafe_method?(method)

  add_offense(node)
end
on_and(node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 77
def on_and(node)
  check_node(node)
end
on_if(node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 72
def on_if(node)
  return if allowed_if_condition?(node)
  check_node(node)
end

Private Instance Methods

allowed_if_condition?(node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 104
def allowed_if_condition?(node)
  node.else? || node.elsif? || node.ternary?
end
begin_range(node, method_call) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 166
def begin_range(node, method_call)
  range_between(node.loc.expression.begin_pos,
                method_call.loc.expression.begin_pos)
end
end_range(node, method_call) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 171
def end_range(node, method_call)
  range_between(method_call.loc.expression.end_pos,
                node.loc.expression.end_pos)
end
extract_common_parts(continuation, checked_variable) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 134
def extract_common_parts(continuation, checked_variable)
  matching_receiver =
    find_matching_receiver_invocation(continuation, checked_variable)

  method = matching_receiver.parent if matching_receiver

  [checked_variable, matching_receiver, method]
end
extract_parts(node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 108
def extract_parts(node)
  case node.type
  when :if
    extract_parts_from_if(node)
  when :and
    extract_parts_from_and(node)
  end
end
extract_parts_from_and(node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 124
def extract_parts_from_and(node)
  checked_variable, rhs = *node
  if cop_config['ConvertCodeThatCanStartToReturnNil']
    checked_variable =
      not_nil_check?(checked_variable) || checked_variable
  end

  extract_common_parts(rhs, checked_variable)
end
extract_parts_from_if(node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 117
def extract_parts_from_if(node)
  checked_variable, receiver =
    modifier_if_safe_navigation_candidate?(node)

  extract_common_parts(receiver, checked_variable)
end
find_matching_receiver_invocation(node, checked_variable) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 143
def find_matching_receiver_invocation(node, checked_variable)
  return nil unless node

  receiver = if node.block_type?
               node.send_node.receiver
             else
               node.receiver
             end

  return receiver if receiver == checked_variable

  find_matching_receiver_invocation(receiver, checked_variable)
end
negated?(send_node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 162
def negated?(send_node)
  send_node.parent.send_type? && send_node.parent.method?(:!)
end
unsafe_method?(send_node) click to toggle source
# File lib/rubocop/cop/style/safe_navigation.rb, line 157
def unsafe_method?(send_node)
  NIL_METHODS.include?(send_node.method_name) ||
    negated?(send_node) || !send_node.dot?
end