class PowerAssert::Parser

def extract_idents(sexp)


+--------+
extract_idents(Ripper.sexp('a&.b(c).d')) #=> a--+ +--d
+--c--b--+

Returns idents as graph structure.
def extract_idents(sexp)
  tag, * = sexp
  case tag
  when :arg_paren, :assoc_splat, :fcall, :hash, :method_add_block, :string_literal, :return
    extract_idents(sexp[1])
  when :assign, :massign
    extract_idents(sexp[2])
  when :opassign
    _, _, (_, op_name, (_, op_column)), s0 = sexp
    extract_idents(s0) + [Ident[:method, op_name.sub(/=\z/, ''), op_column]]
  when :dyna_symbol
    if sexp[1][0].kind_of?(Symbol)
      # sexp[1] can be [:string_content, [..]] while parsing { "a": 1 }
      extract_idents(sexp[1])
    else
      sexp[1].flat_map {|s| extract_idents(s) }
    end
  when :assoclist_from_args, :bare_assoc_hash, :paren, :string_embexpr,
    :regexp_literal, :xstring_literal
    sexp[1].flat_map {|s| extract_idents(s) }
  when :command
    [sexp[2], sexp[1]].flat_map {|s| extract_idents(s) }
  when :assoc_new, :dot2, :dot3, :string_content
    sexp[1..-1].flat_map {|s| extract_idents(s) }
  when :unary
    handle_columnless_ident([], sexp[1], extract_idents(sexp[2]))
  when :binary
    op = sexp[2]
    if AND_OR_OPS.include?(op)
      extract_idents(sexp[1]) + [Branch[extract_idents(sexp[3]), []]]
    else
      handle_columnless_ident(extract_idents(sexp[1]), op, extract_idents(sexp[3]))
    end
  when :call
    _, recv, (op_sym, op_name, _), method = sexp
    with_safe_op = ((op_sym == :@op and op_name == '&.') or op_sym == :"&.")
    if method == :call
      handle_columnless_ident(extract_idents(recv), :call, [], with_safe_op)
    else
      extract_idents(recv) + (with_safe_op ? [Branch[extract_idents(method), []]] : extract_idents(method))
    end
  when :array
    sexp[1] ? sexp[1].flat_map {|s| extract_idents(s) } : []
  when :command_call
    [sexp[1], sexp[4], sexp[3]].flat_map {|s| extract_idents(s) }
  when :aref
    handle_columnless_ident(extract_idents(sexp[1]), :[], extract_idents(sexp[2]))
  when :method_add_arg
    idents = extract_idents(sexp[1])
    if idents.empty?
      # idents may be empty(e.g. ->{}.())
      extract_idents(sexp[2])
    else
      if idents[-1].kind_of?(Branch) and idents[-1][1].empty?
        # Safe navigation operator is used. See :call clause also.
        idents[0..-2] + [Branch[extract_idents(sexp[2]) + idents[-1][0], []]]
      else
        idents[0..-2] + extract_idents(sexp[2]) + [idents[-1]]
      end
    end
  when :args_add_block
    _, (tag, ss0, *ss1), _ = sexp
    if tag == :args_add_star
      (ss0 + ss1).flat_map {|s| extract_idents(s) }
    else
      sexp[1].flat_map {|s| extract_idents(s) }
    end
  when :vcall
    _, (tag, name, (_, column)) = sexp
    if tag == :@ident
      [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]]
    else
      []
    end
  when :program
    _, ((tag0, (tag1, (tag2, (tag3, mname, _)), _), (tag4, _, ss))) = sexp
    if tag0 == :method_add_block and tag1 == :method_add_arg and tag2 == :fcall and
        (tag3 == :@ident or tag3 == :@const) and mname == @assertion_method_name and (tag4 == :brace_block or tag4 == :do_block)
      ss.flat_map {|s| extract_idents(s) }
    else
      _, (s0, *) = sexp
      extract_idents(s0)
    end
  when :ifop
    _, s0, s1, s2 = sexp
    [*extract_idents(s0), Branch[extract_idents(s1), extract_idents(s2)]]
  when :if, :unless
    _, s0, ss0, (_, ss1) = sexp
    [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, ss1 ? ss1.flat_map {|s| extract_idents(s) } : []]]
  when :if_mod, :unless_mod
    _, s0, s1 = sexp
    [*extract_idents(s0), Branch[extract_idents(s1), []]]
  when :var_ref, :var_field
    _, (tag, ref_name, (_, column)) = sexp
    case tag
    when :@kw
      if ref_name == 'self'
        [Ident[:ref, 'self', column]]
      else
        []
      end
    when :@ident, :@const, :@cvar, :@ivar, :@gvar
      [Ident[:ref, ref_name, column]]
    else
      []
    end
  when :@ident, :@const, :@op
    _, method_name, (_, column) = sexp
    [Ident[:method, method_name, column]]
  else
    []
  end
end