class RuboCop::Cop::Style::HashFetchChain


hash.fetch(‘foo’, {}).fetch(‘bar’, {})
# ok - not handled by the cop since the final ‘fetch` value is non-nil
hash.dig(’foo’, ‘bar’)
# good
hash.fetch(‘foo’, {}).fetch(‘bar’, nil)
# has ‘nil` as a default value
# earlier members of the chain can return `{}` as long as the final `fetch`
# bad
hash.fetch(’foo’, nil)&.fetch(‘bar’, nil)
# bad
@example
is a ‘Hash` or that `fetch` or `dig` have the expected standard implementation.
This cop is unsafe because it cannot be guaranteed that the receiver
@safety
a single method call.
NOTE: See `Style/DigChain` for replacing chains of `dig` calls with
given with `dig`.
the chain will not be registered as an offense, as the default value cannot be safely
as long as the final call in the chain is a nil value. If a non-nil value is given,
If the 2nd parameter is `{}` or `Hash.new`, an offense will also be registered,
multiple arguments.
and in both cases, these can be simplified with a single call to `dig` with
is that each step in the chain returns either `nil` or another hash,
When `fetch(identifier, nil)` calls are chained on a hash, the expectation
Use `Hash#dig` instead of chaining potentially null `fetch` calls.

def inspect_chain(node)

def inspect_chain(node)
  arguments = []
  while (arg = diggable?(node))
    arguments.unshift(arg)
    ignore_node(node)
    last_replaceable_node = node
    node = node.receiver
  end
  [last_replaceable_node, arguments]
end

def last_fetch_non_nil?(node)

def last_fetch_non_nil?(node)
  # When chaining `fetch` methods, `fetch(x, {})` is acceptable within
  # the chain, as long as the last method in the chain has a `nil`
  # default value.
  return false unless node.method?(:fetch)
  !node.last_argument&.nil_type?
end

def on_send(node)

def on_send(node)
  return if ignored_node?(node)
  return if last_fetch_non_nil?(node)
  last_replaceable_node, arguments = inspect_chain(node)
  return unless last_replaceable_node
  return unless arguments.size > 1
  range = last_replaceable_node.selector.join(node.loc.end)
  replacement = replacement(arguments)
  message = format(MSG, replacement: replacement)
  add_offense(range, message: message) do |corrector|
    corrector.replace(range, replacement)
  end
end

def replacement(arguments)

def replacement(arguments)
  values = arguments.map(&:source).join(', ')
  "dig(#{values})"
end