class RuboCop::Cop::Style::SafeNavigation

foo.bar > 2 if foo
foo.baz + bar if foo
foo.baz = bar if foo
# comparison should not be converted to use safe navigation
# Methods that are used on assignment, arithmetic operation or
!foo || foo.bar
foo.nil? || foo.bar
# This could start returning ‘nil` as well as the return of the method
foo < bar if foo
foo && foo < bar
# Method calls that do not use `.`
foo && foo.nil? # method that `nil` responds to
foo && foo.bar.baz.qux # method chain with more than 2 methods
foo&.bar(param) { |e| e.something }
foo&.bar { |e| e.something }
foo&.bar(param1, param2)
foo&.bar&.baz
foo&.bar
# good
foo && foo.bar(param) { |e| e.something }
foo && foo.bar { |e| e.something }
foo && foo.bar(param1, param2)
foo && foo.bar.baz
foo && foo.bar
foo.bar unless foo.nil?
foo.bar unless !foo
foo.bar if !foo.nil?
foo.bar(param) { |e| e.something } if foo
foo.bar { |e| e.something } if foo
foo.bar(param1, param2) if foo
foo.bar.baz if foo
foo.bar if foo
# bad
@example
returns.
`foo&.bar` can start returning `nil` as well as what the method
of the method is. If this is converted to safe navigation,
the return of this code is limited to `false` and whatever the return
check for code in the format `!foo.nil? && foo.bar`. As it is written,
The default for this is `false`. When configured to `true`, this will
Configuration option: ConvertCodeThatCanStartToReturnNil
not register an offense for method chains that exceed 2 methods.
need to be changed to use safe navigation. We have limited the cop to
in the chain need to be checked for safety, and all of the methods will
safe navigation (`&.`). If there is a method chain, all of the methods
check for the variable whose method is being called to
This cop transforms usages of a method call safeguarded by a non `nil`

def add_safe_nav_to_all_methods_in_chain(corrector,

def add_safe_nav_to_all_methods_in_chain(corrector,
                                         start_method,
                                         method_chain)
  start_method.each_ancestor do |ancestor|
    break unless %i[send block].include?(ancestor.type)
    next unless ancestor.send_type?
    corrector.insert_before(ancestor.loc.dot, '&')
    break if ancestor == method_chain
  end
end

def allowed_if_condition?(node)

def allowed_if_condition?(node)
  node.else? || node.elsif? || node.ternary?
end

def autocorrect(node)

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.loc.dot, '&')
    add_safe_nav_to_all_methods_in_chain(corrector, method_call, body)
  end
end

def begin_range(node, method_call)

def begin_range(node, method_call)
  range_between(node.loc.expression.begin_pos,
                method_call.loc.expression.begin_pos)
end

def chain_size(method_chain, method)

def chain_size(method_chain, method)
  method.each_ancestor(:send).inject(0) do |total, ancestor|
    break total + 1 if ancestor == method_chain
    total + 1
  end
end

def check_node(node)

def check_node(node)
  return if target_ruby_version < 2.3
  checked_variable, receiver, method_chain, method = extract_parts(node)
  return unless receiver == 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)
  add_offense(node)
end

def end_range(node, method_call)

def end_range(node, method_call)
  range_between(method_call.loc.expression.end_pos,
                node.loc.expression.end_pos)
end

def extract_common_parts(method_chain, checked_variable)

def extract_common_parts(method_chain, checked_variable)
  matching_receiver =
    find_matching_receiver_invocation(method_chain, checked_variable)
  method = matching_receiver.parent if matching_receiver
  [checked_variable, matching_receiver, method]
end

def extract_parts(node)

def extract_parts(node)
  case node.type
  when :if
    extract_parts_from_if(node)
  when :and
    extract_parts_from_and(node)
  end
end

def extract_parts_from_and(node)

def extract_parts_from_and(node)
  checked_variable, rhs = *node
  if cop_config['ConvertCodeThatCanStartToReturnNil']
    checked_variable =
      not_nil_check?(checked_variable) || checked_variable
  end
  checked_variable, matching_receiver, method =
    extract_common_parts(rhs, checked_variable)
  [checked_variable, matching_receiver, rhs, method]
end

def extract_parts_from_if(node)

def extract_parts_from_if(node)
  variable, receiver =
    modifier_if_safe_navigation_candidate?(node)
  checked_variable, matching_receiver, method =
    extract_common_parts(receiver, variable)
  [checked_variable, matching_receiver, receiver, method]
end

def find_matching_receiver_invocation(method_chain, checked_variable)

def find_matching_receiver_invocation(method_chain, checked_variable)
  return nil unless method_chain
  receiver = if method_chain.block_type?
               method_chain.send_node.receiver
             else
               method_chain.receiver
             end
  return receiver if receiver == checked_variable
  find_matching_receiver_invocation(receiver, checked_variable)
end

def negated?(send_node)

def negated?(send_node)
  if send_node.parent && send_node.parent.send_type?
    negated?(send_node.parent)
  else
    send_node.send_type? && send_node.method?(:!)
  end
end

def on_and(node)

def on_and(node)
  check_node(node)
end

def on_if(node)

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

def unsafe_method?(send_node)

def unsafe_method?(send_node)
  negated?(send_node) || send_node.assignment? || !send_node.dot?
end

def unsafe_method_used?(method_chain, method)

def unsafe_method_used?(method_chain, method)
  return true if unsafe_method?(method)
  method.each_ancestor(:send).any? do |ancestor|
    unless config.for_cop('Lint/SafeNavigationChain')['Enabled']
      break true
    end
    break true if unsafe_method?(ancestor)
    break true if nil_methods.include?(ancestor.method_name)
    break false if ancestor == method_chain
  end
end