class Prism::Translation::Ripper

This class provides a compatibility layer between prism and Ripper. It functions by parsing the entire tree first and then walking it and executing each of the Ripper callbacks as it goes. To use this class, you treat Prism::Translation::Ripper effectively as you would treat the Ripper class.

Note that this class will serve the most common use cases, but Ripper’s API is extensive and undocumented. It relies on reporting the state of the parser at any given time. We do our best to replicate that here, but because it is a different architecture it is not possible to perfectly replicate the behavior of Ripper.

The main known difference is that we may omit dispatching some events in some cases. This impacts the following events:

  • on_assign_error

  • on_comma

  • on_ignored_nl

  • on_ignored_sp

  • on_kw

  • on_label_end

  • on_lbrace

  • on_lbracket

  • on_lparen

  • on_nl

  • on_op

  • on_operator_ambiguous

  • on_rbrace

  • on_rbracket

  • on_rparen

  • on_semicolon

  • on_sp

  • on_symbeg

  • on_tstring_beg

  • on_tstring_end

Constants

EVENTS

This array contains name of all ripper events.

PARSER_EVENTS

This array contains name of parser events.

PARSER_EVENT_TABLE

This contains a table of all of the parser events and their corresponding arity.

SCANNER_EVENTS

This array contains name of scanner events.

SCANNER_EVENT_TABLE

This contains a table of all of the scanner events and their corresponding arity.

Attributes

The current column number of the parser.

The filename of the source being parsed.

The current line number of the parser.

The source that is being parsed.

Public Class Methods

Tokenizes the Ruby program and returns an array of an array, which is formatted like [[lineno, column], type, token, state]. The filename argument is mostly ignored. By default, this method does not handle syntax errors in src, use the raise_errors keyword to raise a SyntaxError for an error in src.

require "ripper"
require "pp"

pp Ripper.lex("def m(a) nil end")
#=> [[[1,  0], :on_kw,     "def", FNAME    ],
     [[1,  3], :on_sp,     " ",   FNAME    ],
     [[1,  4], :on_ident,  "m",   ENDFN    ],
     [[1,  5], :on_lparen, "(",   BEG|LABEL],
     [[1,  6], :on_ident,  "a",   ARG      ],
     [[1,  7], :on_rparen, ")",   ENDFN    ],
     [[1,  8], :on_sp,     " ",   BEG      ],
     [[1,  9], :on_kw,     "nil", END      ],
     [[1, 12], :on_sp,     " ",   END      ],
     [[1, 13], :on_kw,     "end", END      ]]
# File lib/prism/translation/ripper.rb, line 71
def self.lex(src, filename = "-", lineno = 1, raise_errors: false)
  result = Prism.lex_compat(src, filepath: filename, line: lineno, version: "current")

  if result.failure? && raise_errors
    raise SyntaxError, result.errors.first.message
  else
    result.value
  end
end

Create a new Translation::Ripper object with the given source.

# File lib/prism/translation/ripper.rb, line 468
def initialize(source, filename = "(ripper)", lineno = 1)
  @source = source
  @filename = filename
  @lineno = lineno
  @column = 0
  @result = nil
end

Parses the given Ruby program read from src. src must be a String or an IO or a object with a gets method.

# File lib/prism/translation/ripper.rb, line 45
def self.parse(src, filename = "(ripper)", lineno = 1)
  new(src, filename, lineno).parse
end

Parses src and create S-exp tree. Returns more readable tree rather than Ripper.sexp_raw. This method is mainly for developer use. The filename argument is mostly ignored. By default, this method does not handle syntax errors in src, returning nil in such cases. Use the raise_errors keyword to raise a SyntaxError for an error in src.

require "ripper"
require "pp"

pp Ripper.sexp("def m(a) nil end")
  #=> [:program,
       [[:def,
        [:@ident, "m", [1, 4]],
        [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil, nil, nil, nil]],
        [:bodystmt, [[:var_ref, [:@kw, "nil", [1, 9]]]], nil, nil, nil]]]]
# File lib/prism/translation/ripper.rb, line 380
def self.sexp(src, filename = "-", lineno = 1, raise_errors: false)
  builder = SexpBuilderPP.new(src, filename, lineno)
  sexp = builder.parse
  if builder.error?
    if raise_errors
      raise SyntaxError, builder.error
    end
  else
    sexp
  end
end

Parses src and create S-exp tree. This method is mainly for developer use. The filename argument is mostly ignored. By default, this method does not handle syntax errors in src, returning nil in such cases. Use the raise_errors keyword to raise a SyntaxError for an error in src.

require "ripper"
require "pp"

pp Ripper.sexp_raw("def m(a) nil end")
  #=> [:program,
       [:stmts_add,
        [:stmts_new],
        [:def,
         [:@ident, "m", [1, 4]],
         [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil]],
         [:bodystmt,
          [:stmts_add, [:stmts_new], [:var_ref, [:@kw, "nil", [1, 9]]]],
          nil,
          nil,
          nil]]]]
# File lib/prism/translation/ripper.rb, line 415
def self.sexp_raw(src, filename = "-", lineno = 1, raise_errors: false)
  builder = SexpBuilder.new(src, filename, lineno)
  sexp = builder.parse
  if builder.error?
    if raise_errors
      raise SyntaxError, builder.error
    end
  else
    sexp
  end
end

Public Instance Methods

True if the parser encountered an error during parsing.

# File lib/prism/translation/ripper.rb, line 481
def error?
  result.failure?
end

Parse the source and return the result.

# File lib/prism/translation/ripper.rb, line 486
def parse
  result.comments.each do |comment|
    location = comment.location
    bounds(location)

    if comment.is_a?(InlineComment)
      on_comment(comment.slice)
    else
      offset = location.start_offset
      lines = comment.slice.lines

      lines.each_with_index do |line, index|
        bounds(location.copy(start_offset: offset))

        if index == 0
          on_embdoc_beg(line)
        elsif index == lines.size - 1
          on_embdoc_end(line)
        else
          on_embdoc(line)
        end

        offset += line.bytesize
      end
    end
  end

  result.magic_comments.each do |magic_comment|
    on_magic_comment(magic_comment.key, magic_comment.value)
  end

  unless result.data_loc.nil?
    on___end__(result.data_loc.slice.each_line.first)
  end

  result.warnings.each do |warning|
    bounds(warning.location)

    if warning.level == :default
      warning(warning.message)
    else
      case warning.type
      when :ambiguous_first_argument_plus
        on_arg_ambiguous("+")
      when :ambiguous_first_argument_minus
        on_arg_ambiguous("-")
      when :ambiguous_slash
        on_arg_ambiguous("/")
      else
        warn(warning.message)
      end
    end
  end

  if error?
    result.errors.each do |error|
      location = error.location
      bounds(location)

      case error.type
      when :alias_argument
        on_alias_error("can't make alias for the number variables", location.slice)
      when :argument_formal_class
        on_param_error("formal argument cannot be a class variable", location.slice)
      when :argument_format_constant
        on_param_error("formal argument cannot be a constant", location.slice)
      when :argument_formal_global
        on_param_error("formal argument cannot be a global variable", location.slice)
      when :argument_formal_ivar
        on_param_error("formal argument cannot be an instance variable", location.slice)
      when :class_name, :module_name
        on_class_name_error("class/module name must be CONSTANT", location.slice)
      else
        on_parse_error(error.message)
      end
    end

    nil
  else
    result.value.accept(self)
  end
end

alias $foo $bar ^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 585
def visit_alias_global_variable_node(node)
  new_name = visit_alias_global_variable_node_value(node.new_name)
  old_name = visit_alias_global_variable_node_value(node.old_name)

  bounds(node.location)
  on_var_alias(new_name, old_name)
end

alias foo bar ^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 575
def visit_alias_method_node(node)
  new_name = visit(node.new_name)
  old_name = visit(node.old_name)

  bounds(node.location)
  on_alias(new_name, old_name)
end

foo => bar | baz ^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 609
def visit_alternation_pattern_node(node)
  left = visit_pattern_node(node.left)
  right = visit_pattern_node(node.right)

  bounds(node.location)
  on_binary(left, :|, right)
end

a and b ^^^^^^^

# File lib/prism/translation/ripper.rb, line 629
def visit_and_node(node)
  left = visit(node.left)
  right = visit(node.right)

  bounds(node.location)
  on_binary(left, node.operator.to_sym, right)
end

foo(bar) ^^^

# File lib/prism/translation/ripper.rb, line 820
def visit_arguments_node(node)
  arguments, _ = visit_call_node_arguments(node, nil, false)
  arguments
end

[] ^^

# File lib/prism/translation/ripper.rb, line 639
def visit_array_node(node)
  case (opening = node.opening)
  when /^%w/
    opening_loc = node.opening_loc
    bounds(opening_loc)
    on_qwords_beg(opening)

    elements = on_qwords_new
    previous = nil

    node.elements.each do |element|
      visit_words_sep(opening_loc, previous, element)

      bounds(element.location)
      elements = on_qwords_add(elements, on_tstring_content(element.content))

      previous = element
    end

    bounds(node.closing_loc)
    on_tstring_end(node.closing)
  when /^%i/
    opening_loc = node.opening_loc
    bounds(opening_loc)
    on_qsymbols_beg(opening)

    elements = on_qsymbols_new
    previous = nil

    node.elements.each do |element|
      visit_words_sep(opening_loc, previous, element)

      bounds(element.location)
      elements = on_qsymbols_add(elements, on_tstring_content(element.value))

      previous = element
    end

    bounds(node.closing_loc)
    on_tstring_end(node.closing)
  when /^%W/
    opening_loc = node.opening_loc
    bounds(opening_loc)
    on_words_beg(opening)

    elements = on_words_new
    previous = nil

    node.elements.each do |element|
      visit_words_sep(opening_loc, previous, element)

      bounds(element.location)
      elements =
        on_words_add(
          elements,
          if element.is_a?(StringNode)
            on_word_add(on_word_new, on_tstring_content(element.content))
          else
            element.parts.inject(on_word_new) do |word, part|
              word_part =
                if part.is_a?(StringNode)
                  bounds(part.location)
                  on_tstring_content(part.content)
                else
                  visit(part)
                end

              on_word_add(word, word_part)
            end
          end
        )

      previous = element
    end

    bounds(node.closing_loc)
    on_tstring_end(node.closing)
  when /^%I/
    opening_loc = node.opening_loc
    bounds(opening_loc)
    on_symbols_beg(opening)

    elements = on_symbols_new
    previous = nil

    node.elements.each do |element|
      visit_words_sep(opening_loc, previous, element)

      bounds(element.location)
      elements =
        on_symbols_add(
          elements,
          if element.is_a?(SymbolNode)
            on_word_add(on_word_new, on_tstring_content(element.value))
          else
            element.parts.inject(on_word_new) do |word, part|
              word_part =
                if part.is_a?(StringNode)
                  bounds(part.location)
                  on_tstring_content(part.content)
                else
                  visit(part)
                end

              on_word_add(word, word_part)
            end
          end
        )

      previous = element
    end

    bounds(node.closing_loc)
    on_tstring_end(node.closing)
  else
    bounds(node.opening_loc)
    on_lbracket(opening)

    elements = visit_arguments(node.elements) unless node.elements.empty?

    bounds(node.closing_loc)
    on_rbracket(node.closing)
  end

  bounds(node.location)
  on_array(elements)
end

foo => [bar] ^^^^^

# File lib/prism/translation/ripper.rb, line 799
def visit_array_pattern_node(node)
  constant = visit(node.constant)
  requireds = visit_all(node.requireds) if node.requireds.any?
  rest =
    if (rest_node = node.rest).is_a?(SplatNode)
      if rest_node.expression.nil?
        bounds(rest_node.location)
        on_var_field(nil)
      else
        visit(rest_node.expression)
      end
    end

  posts = visit_all(node.posts) if node.posts.any?

  bounds(node.location)
  on_aryptn(constant, requireds, rest, posts)
end

{ a: 1 } ^^^^

# File lib/prism/translation/ripper.rb, line 827
def visit_assoc_node(node)
  key = visit(node.key)
  value = visit(node.value)

  bounds(node.location)
  on_assoc_new(key, value)
end

def foo(); bar(); end ^^

{ **foo } ^^^^^

# File lib/prism/translation/ripper.rb, line 840
def visit_assoc_splat_node(node)
  value = visit(node.value)

  bounds(node.location)
  on_assoc_splat(value)
end

$+ ^^

# File lib/prism/translation/ripper.rb, line 849
def visit_back_reference_read_node(node)
  bounds(node.location)
  on_backref(node.slice)
end

begin end ^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 856
def visit_begin_node(node)
  clauses = visit_begin_node_clauses(node.begin_keyword_loc, node, false)

  bounds(node.location)
  on_begin(clauses)
end

foo(&bar) ^^^^

# File lib/prism/translation/ripper.rb, line 920
def visit_block_argument_node(node)
  visit(node.expression)
end

foo { |; bar| } ^^^

# File lib/prism/translation/ripper.rb, line 926
def visit_block_local_variable_node(node)
  bounds(node.location)
  on_ident(node.name.to_s)
end

Visit a BlockNode.

# File lib/prism/translation/ripper.rb, line 932
def visit_block_node(node)
  braces = node.opening == "{"
  parameters = visit(node.parameters)

  body =
    case node.body
    when nil
      bounds(node.location)
      stmts = on_stmts_add(on_stmts_new, on_void_stmt)

      bounds(node.location)
      braces ? stmts : on_bodystmt(stmts, nil, nil, nil)
    when StatementsNode
      stmts = node.body.body
      stmts.unshift(nil) if void_stmt?(node.parameters&.location || node.opening_loc, node.body.location, false)
      stmts = visit_statements_node_body(stmts)

      bounds(node.body.location)
      braces ? stmts : on_bodystmt(stmts, nil, nil, nil)
    when BeginNode
      visit_body_node(node.parameters&.location || node.opening_loc, node.body)
    else
      raise
    end

  if braces
    bounds(node.location)
    on_brace_block(parameters, body)
  else
    bounds(node.location)
    on_do_block(parameters, body)
  end
end

def foo(&bar); end ^^^^

# File lib/prism/translation/ripper.rb, line 968
def visit_block_parameter_node(node)
  if node.name_loc.nil?
    bounds(node.location)
    on_blockarg(nil)
  else
    bounds(node.name_loc)
    name = visit_token(node.name.to_s)

    bounds(node.location)
    on_blockarg(name)
  end
end

A block’s parameters.

# File lib/prism/translation/ripper.rb, line 982
def visit_block_parameters_node(node)
  parameters =
    if node.parameters.nil?
      on_params(nil, nil, nil, nil, nil, nil, nil)
    else
      visit(node.parameters)
    end

  locals =
    if node.locals.any?
      visit_all(node.locals)
    else
      false
    end

  bounds(node.location)
  on_block_var(parameters, locals)
end

break ^^^^^

break foo ^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1006
def visit_break_node(node)
  if node.arguments.nil?
    bounds(node.location)
    on_break(on_args_new)
  else
    arguments = visit(node.arguments)

    bounds(node.location)
    on_break(arguments)
  end
end

foo.bar &&= baz ^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1228
def visit_call_and_write_node(node)
  receiver = visit(node.receiver)

  bounds(node.call_operator_loc)
  call_operator = visit_token(node.call_operator)

  bounds(node.message_loc)
  message = visit_token(node.message)

  bounds(node.location)
  target = on_field(receiver, call_operator, message)

  bounds(node.operator_loc)
  operator = on_op("&&=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo ^^^

foo.bar ^^^^^^^

foo.bar() {} ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1026
def visit_call_node(node)
  if node.call_operator_loc.nil?
    case node.name
    when :[]
      receiver = visit(node.receiver)
      arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))

      bounds(node.location)
      call = on_aref(receiver, arguments)

      if block.nil?
        call
      else
        bounds(node.location)
        on_method_add_block(call, block)
      end
    when :[]=
      receiver = visit(node.receiver)

      *arguments, last_argument = node.arguments.arguments
      arguments << node.block if !node.block.nil?

      arguments =
        if arguments.any?
          args = visit_arguments(arguments)

          if !node.block.nil?
            args
          else
            bounds(arguments.first.location)
            on_args_add_block(args, false)
          end
        end

      bounds(node.location)
      call = on_aref_field(receiver, arguments)
      value = visit_write_value(last_argument)

      bounds(last_argument.location)
      on_assign(call, value)
    when :-@, :+@, :~
      receiver = visit(node.receiver)

      bounds(node.location)
      on_unary(node.name, receiver)
    when :!
      if node.message == "not"
        receiver =
          if !node.receiver.is_a?(ParenthesesNode) || !node.receiver.body.nil?
            visit(node.receiver)
          end

        bounds(node.location)
        on_unary(:not, receiver)
      else
        receiver = visit(node.receiver)

        bounds(node.location)
        on_unary(:!, receiver)
      end
    when *BINARY_OPERATORS
      receiver = visit(node.receiver)
      value = visit(node.arguments.arguments.first)

      bounds(node.location)
      on_binary(receiver, node.name, value)
    else
      bounds(node.message_loc)
      message = visit_token(node.message, false)

      if node.variable_call?
        on_vcall(message)
      else
        arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
        call =
          if node.opening_loc.nil? && arguments&.any?
            bounds(node.location)
            on_command(message, arguments)
          elsif !node.opening_loc.nil?
            bounds(node.location)
            on_method_add_arg(on_fcall(message), on_arg_paren(arguments))
          else
            bounds(node.location)
            on_method_add_arg(on_fcall(message), on_args_new)
          end

        if block.nil?
          call
        else
          bounds(node.block.location)
          on_method_add_block(call, block)
        end
      end
    end
  else
    receiver = visit(node.receiver)

    bounds(node.call_operator_loc)
    call_operator = visit_token(node.call_operator)

    message =
      if node.message_loc.nil?
        :call
      else
        bounds(node.message_loc)
        visit_token(node.message, false)
      end

    if node.name.end_with?("=") && !node.message.end_with?("=") && !node.arguments.nil? && node.block.nil?
      value = visit_write_value(node.arguments.arguments.first)

      bounds(node.location)
      on_assign(on_field(receiver, call_operator, message), value)
    else
      arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
      call =
        if node.opening_loc.nil?
          bounds(node.location)

          if node.arguments.nil? && !node.block.is_a?(BlockArgumentNode)
            on_call(receiver, call_operator, message)
          else
            on_command_call(receiver, call_operator, message, arguments)
          end
        else
          bounds(node.opening_loc)
          arguments = on_arg_paren(arguments)

          bounds(node.location)
          on_method_add_arg(on_call(receiver, call_operator, message), arguments)
        end

      if block.nil?
        call
      else
        bounds(node.block.location)
        on_method_add_block(call, block)
      end
    end
  end
end

foo.bar += baz ^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1206
def visit_call_operator_write_node(node)
  receiver = visit(node.receiver)

  bounds(node.call_operator_loc)
  call_operator = visit_token(node.call_operator)

  bounds(node.message_loc)
  message = visit_token(node.message)

  bounds(node.location)
  target = on_field(receiver, call_operator, message)

  bounds(node.binary_operator_loc)
  operator = on_op("#{node.binary_operator}=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo.bar ||= baz ^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1250
def visit_call_or_write_node(node)
  receiver = visit(node.receiver)

  bounds(node.call_operator_loc)
  call_operator = visit_token(node.call_operator)

  bounds(node.message_loc)
  message = visit_token(node.message)

  bounds(node.location)
  target = on_field(receiver, call_operator, message)

  bounds(node.operator_loc)
  operator = on_op("||=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo.bar, = 1 ^^^^^^^

# File lib/prism/translation/ripper.rb, line 1272
def visit_call_target_node(node)
  if node.call_operator == "::"
    receiver = visit(node.receiver)

    bounds(node.message_loc)
    message = visit_token(node.message)

    bounds(node.location)
    on_const_path_field(receiver, message)
  else
    receiver = visit(node.receiver)

    bounds(node.call_operator_loc)
    call_operator = visit_token(node.call_operator)

    bounds(node.message_loc)
    message = visit_token(node.message)

    bounds(node.location)
    on_field(receiver, call_operator, message)
  end
end

foo => bar => baz ^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1297
def visit_capture_pattern_node(node)
  value = visit(node.value)
  target = visit(node.target)

  bounds(node.location)
  on_binary(value, :"=>", target)
end

case foo; in bar; end ^^^^^^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1320
def visit_case_match_node(node)
  predicate = visit(node.predicate)
  clauses =
    node.conditions.reverse_each.inject(visit(node.else_clause)) do |current, condition|
      on_in(*visit(condition), current)
    end

  bounds(node.location)
  on_case(predicate, clauses)
end

case foo; when bar; end ^^^^^^^^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1307
def visit_case_node(node)
  predicate = visit(node.predicate)
  clauses =
    node.conditions.reverse_each.inject(visit(node.else_clause)) do |current, condition|
      on_when(*visit(condition), current)
    end

  bounds(node.location)
  on_case(predicate, clauses)
end

class Foo; end ^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1333
def visit_class_node(node)
  constant_path =
    if node.constant_path.is_a?(ConstantReadNode)
      bounds(node.constant_path.location)
      on_const_ref(on_const(node.constant_path.name.to_s))
    else
      visit(node.constant_path)
    end

  superclass = visit(node.superclass)
  bodystmt = visit_body_node(node.superclass&.location || node.constant_path.location, node.body, node.superclass.nil?)

  bounds(node.location)
  on_class(constant_path, superclass, bodystmt)
end

@@foo &&= bar ^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1386
def visit_class_variable_and_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_cvar(node.name.to_s))

  bounds(node.operator_loc)
  operator = on_op("&&=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

@@foo += bar ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1372
def visit_class_variable_operator_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_cvar(node.name.to_s))

  bounds(node.binary_operator_loc)
  operator = on_op("#{node.binary_operator}=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

@@foo ||= bar ^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1400
def visit_class_variable_or_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_cvar(node.name.to_s))

  bounds(node.operator_loc)
  operator = on_op("||=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

@@foo ^^^^^

# File lib/prism/translation/ripper.rb, line 1351
def visit_class_variable_read_node(node)
  bounds(node.location)
  on_var_ref(on_cvar(node.slice))
end

@@foo, = bar ^^^^^

# File lib/prism/translation/ripper.rb, line 1414
def visit_class_variable_target_node(node)
  bounds(node.location)
  on_var_field(on_cvar(node.name.to_s))
end

@@foo = 1 ^^^^^^^^^

@@foo, @@bar = 1 ^^^^^ ^^^^^

# File lib/prism/translation/ripper.rb, line 1361
def visit_class_variable_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_cvar(node.name.to_s))
  value = visit_write_value(node.value)

  bounds(node.location)
  on_assign(target, value)
end

Foo &&= bar ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1456
def visit_constant_and_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_const(node.name.to_s))

  bounds(node.operator_loc)
  operator = on_op("&&=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

Foo += bar ^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1442
def visit_constant_operator_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_const(node.name.to_s))

  bounds(node.binary_operator_loc)
  operator = on_op("#{node.binary_operator}=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

Foo ||= bar ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1470
def visit_constant_or_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_const(node.name.to_s))

  bounds(node.operator_loc)
  operator = on_op("||=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

Foo::Bar &&= baz ^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1557
def visit_constant_path_and_write_node(node)
  target = visit_constant_path_write_node_target(node.target)
  value = visit(node.value)

  bounds(node.operator_loc)
  operator = on_op("&&=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

Foo::Bar ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1491
def visit_constant_path_node(node)
  if node.parent.nil?
    bounds(node.name_loc)
    child = on_const(node.name.to_s)

    bounds(node.location)
    on_top_const_ref(child)
  else
    parent = visit(node.parent)

    bounds(node.name_loc)
    child = on_const(node.name.to_s)

    bounds(node.location)
    on_const_path_ref(parent, child)
  end
end

Foo::Bar += baz ^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1543
def visit_constant_path_operator_write_node(node)
  target = visit_constant_path_write_node_target(node.target)
  value = visit(node.value)

  bounds(node.binary_operator_loc)
  operator = on_op("#{node.binary_operator}=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

Foo::Bar ||= baz ^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1571
def visit_constant_path_or_write_node(node)
  target = visit_constant_path_write_node_target(node.target)
  value = visit(node.value)

  bounds(node.operator_loc)
  operator = on_op("||=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

Foo::Bar, = baz ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1585
def visit_constant_path_target_node(node)
  visit_constant_path_write_node_target(node)
end

Foo::Bar = 1 ^^^^^^^^^^^^

Foo::Foo, Bar::Bar = 1 ^^^^^^^^ ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1514
def visit_constant_path_write_node(node)
  target = visit_constant_path_write_node_target(node.target)
  value = visit_write_value(node.value)

  bounds(node.location)
  on_assign(target, value)
end

Foo ^^^

# File lib/prism/translation/ripper.rb, line 1421
def visit_constant_read_node(node)
  bounds(node.location)
  on_var_ref(on_const(node.name.to_s))
end

Foo, = bar ^^^

# File lib/prism/translation/ripper.rb, line 1484
def visit_constant_target_node(node)
  bounds(node.location)
  on_var_field(on_const(node.name.to_s))
end

Foo = 1 ^^^^^^^

Foo, Bar = 1 ^^^ ^^^

# File lib/prism/translation/ripper.rb, line 1431
def visit_constant_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_const(node.name.to_s))
  value = visit_write_value(node.value)

  bounds(node.location)
  on_assign(target, value)
end

def foo; end ^^^^^^^^^^^^

def self.foo; end ^^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1594
def visit_def_node(node)
  receiver = visit(node.receiver)
  operator =
    if !node.operator_loc.nil?
      bounds(node.operator_loc)
      visit_token(node.operator)
    end

  bounds(node.name_loc)
  name = visit_token(node.name_loc.slice)

  parameters =
    if node.parameters.nil?
      bounds(node.location)
      on_params(nil, nil, nil, nil, nil, nil, nil)
    else
      visit(node.parameters)
    end

  if !node.lparen_loc.nil?
    bounds(node.lparen_loc)
    parameters = on_paren(parameters)
  end

  bodystmt =
    if node.equal_loc.nil?
      visit_body_node(node.rparen_loc || node.end_keyword_loc, node.body)
    else
      body = visit(node.body.body.first)

      bounds(node.body.location)
      on_bodystmt(body, nil, nil, nil)
    end

  bounds(node.location)
  if receiver.nil?
    on_def(name, parameters, bodystmt)
  else
    on_defs(receiver, operator, name, parameters, bodystmt)
  end
end

defined? a ^^^^^^^^^^

defined?(a) ^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1641
def visit_defined_node(node)
  expression = visit(node.value)

  # Very weird circumstances here where something like:
  #
  #     defined?
  #     (1)
  #
  # gets parsed in Ruby as having only the `1` expression but in Ripper it
  # gets parsed as having a parentheses node. In this case we need to
  # synthesize that node to match Ripper's behavior.
  if node.lparen_loc && node.keyword_loc.join(node.lparen_loc).slice.include?("\n")
    bounds(node.lparen_loc.join(node.rparen_loc))
    expression = on_paren(on_stmts_add(on_stmts_new, expression))
  end

  bounds(node.location)
  on_defined(expression)
end

if foo then bar else baz end ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1663
def visit_else_node(node)
  statements =
    if node.statements.nil?
      [nil]
    else
      body = node.statements.body
      body.unshift(nil) if void_stmt?(node.else_keyword_loc, node.statements.body[0].location, false)
      body
    end

  bounds(node.location)
  on_else(visit_statements_node_body(statements))
end

“foo #{bar}” ^^^^^^

# File lib/prism/translation/ripper.rb, line 1679
def visit_embedded_statements_node(node)
  bounds(node.opening_loc)
  on_embexpr_beg(node.opening)

  statements =
    if node.statements.nil?
      bounds(node.location)
      on_stmts_add(on_stmts_new, on_void_stmt)
    else
      visit(node.statements)
    end

  bounds(node.closing_loc)
  on_embexpr_end(node.closing)

  bounds(node.location)
  on_string_embexpr(statements)
end

“foo #@bar” ^^^^^

# File lib/prism/translation/ripper.rb, line 1700
def visit_embedded_variable_node(node)
  bounds(node.operator_loc)
  on_embvar(node.operator)

  variable = visit(node.variable)

  bounds(node.location)
  on_string_dvar(variable)
end

Visit an EnsureNode node.

# File lib/prism/translation/ripper.rb, line 1711
def visit_ensure_node(node)
  statements =
    if node.statements.nil?
      [nil]
    else
      body = node.statements.body
      body.unshift(nil) if void_stmt?(node.ensure_keyword_loc, body[0].location, false)
      body
    end

  statements = visit_statements_node_body(statements)

  bounds(node.location)
  on_ensure(statements)
end

false ^^^^^

# File lib/prism/translation/ripper.rb, line 1729
def visit_false_node(node)
  bounds(node.location)
  on_var_ref(on_kw("false"))
end

foo => [, bar, ] ^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1736
def visit_find_pattern_node(node)
  constant = visit(node.constant)
  left =
    if node.left.expression.nil?
      bounds(node.left.location)
      on_var_field(nil)
    else
      visit(node.left.expression)
    end

  requireds = visit_all(node.requireds) if node.requireds.any?
  right =
    if node.right.expression.nil?
      bounds(node.right.location)
      on_var_field(nil)
    else
      visit(node.right.expression)
    end

  bounds(node.location)
  on_fndptn(constant, left, requireds, right)
end

if foo .. bar; end ^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1761
def visit_flip_flop_node(node)
  left = visit(node.left)
  right = visit(node.right)

  bounds(node.location)
  if node.exclude_end?
    on_dot3(left, right)
  else
    on_dot2(left, right)
  end
end

1.0 ^^^

# File lib/prism/translation/ripper.rb, line 1775
def visit_float_node(node)
  visit_number_node(node) { |text| on_float(text) }
end

for foo in bar do end ^^^^^^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1781
def visit_for_node(node)
  index = visit(node.index)
  collection = visit(node.collection)
  statements =
    if node.statements.nil?
      bounds(node.location)
      on_stmts_add(on_stmts_new, on_void_stmt)
    else
      visit(node.statements)
    end

  bounds(node.location)
  on_for(index, collection, statements)
end

def foo(…); bar(…); end ^^^

# File lib/prism/translation/ripper.rb, line 1798
def visit_forwarding_arguments_node(node)
  bounds(node.location)
  on_args_forward
end

def foo(…); end ^^^

# File lib/prism/translation/ripper.rb, line 1805
def visit_forwarding_parameter_node(node)
  bounds(node.location)
  on_args_forward
end

super ^^^^^

super {} ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1815
def visit_forwarding_super_node(node)
  if node.block.nil?
    bounds(node.location)
    on_zsuper
  else
    block = visit(node.block)

    bounds(node.location)
    on_method_add_block(on_zsuper, block)
  end
end

$foo &&= bar ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1864
def visit_global_variable_and_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_gvar(node.name.to_s))

  bounds(node.operator_loc)
  operator = on_op("&&=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

$foo += bar ^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1850
def visit_global_variable_operator_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_gvar(node.name.to_s))

  bounds(node.binary_operator_loc)
  operator = on_op("#{node.binary_operator}=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

$foo ||= bar ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1878
def visit_global_variable_or_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_gvar(node.name.to_s))

  bounds(node.operator_loc)
  operator = on_op("||=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

$foo ^^^^

# File lib/prism/translation/ripper.rb, line 1829
def visit_global_variable_read_node(node)
  bounds(node.location)
  on_var_ref(on_gvar(node.name.to_s))
end

$foo, = bar ^^^^

# File lib/prism/translation/ripper.rb, line 1892
def visit_global_variable_target_node(node)
  bounds(node.location)
  on_var_field(on_gvar(node.name.to_s))
end

$foo = 1 ^^^^^^^^

$foo, $bar = 1 ^^^^ ^^^^

# File lib/prism/translation/ripper.rb, line 1839
def visit_global_variable_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_gvar(node.name.to_s))
  value = visit_write_value(node.value)

  bounds(node.location)
  on_assign(target, value)
end

{} ^^

# File lib/prism/translation/ripper.rb, line 1899
def visit_hash_node(node)
  elements =
    if node.elements.any?
      args = visit_all(node.elements)

      bounds(node.elements.first.location)
      on_assoclist_from_args(args)
    end

  bounds(node.location)
  on_hash(elements)
end

foo => {} ^^

# File lib/prism/translation/ripper.rb, line 1914
def visit_hash_pattern_node(node)
  constant = visit(node.constant)
  elements =
    if node.elements.any? || !node.rest.nil?
      node.elements.map do |element|
        [
          if (key = element.key).opening_loc.nil?
            visit(key)
          else
            bounds(key.value_loc)
            if (value = key.value).empty?
              on_string_content
            else
              on_string_add(on_string_content, on_tstring_content(value))
            end
          end,
          visit(element.value)
        ]
      end
    end

  rest =
    case node.rest
    when AssocSplatNode
      visit(node.rest.value)
    when NoKeywordsParameterNode
      bounds(node.rest.location)
      on_var_field(visit(node.rest))
    end

  bounds(node.location)
  on_hshptn(constant, elements, rest)
end

if foo then bar end ^^^^^^^^^^^^^^^^^^^

bar if foo ^^^^^^^^^^

foo ? bar : baz ^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 1956
def visit_if_node(node)
  if node.then_keyword == "?"
    predicate = visit(node.predicate)
    truthy = visit(node.statements.body.first)
    falsy = visit(node.subsequent.statements.body.first)

    bounds(node.location)
    on_ifop(predicate, truthy, falsy)
  elsif node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset)
    predicate = visit(node.predicate)
    statements =
      if node.statements.nil?
        bounds(node.location)
        on_stmts_add(on_stmts_new, on_void_stmt)
      else
        visit(node.statements)
      end
    subsequent = visit(node.subsequent)

    bounds(node.location)
    if node.if_keyword == "if"
      on_if(predicate, statements, subsequent)
    else
      on_elsif(predicate, statements, subsequent)
    end
  else
    statements = visit(node.statements.body.first)
    predicate = visit(node.predicate)

    bounds(node.location)
    on_if_mod(predicate, statements)
  end
end

1i ^^

# File lib/prism/translation/ripper.rb, line 1992
def visit_imaginary_node(node)
  visit_number_node(node) { |text| on_imaginary(text) }
end

{ foo: } ^^^^

# File lib/prism/translation/ripper.rb, line 1998
def visit_implicit_node(node)
end

foo { |bar,| } ^

# File lib/prism/translation/ripper.rb, line 2003
def visit_implicit_rest_node(node)
  bounds(node.location)
  on_excessed_comma
end

case foo; in bar; end ^^^^^^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2010
def visit_in_node(node)
  # This is a special case where we're not going to call on_in directly
  # because we don't have access to the subsequent. Instead, we'll return
  # the component parts and let the parent node handle it.
  pattern = visit_pattern_node(node.pattern)
  statements =
    if node.statements.nil?
      bounds(node.location)
      on_stmts_add(on_stmts_new, on_void_stmt)
    else
      visit(node.statements)
    end

  [pattern, statements]
end

foo &&= baz ^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2045
def visit_index_and_write_node(node)
  receiver = visit(node.receiver)
  arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))

  bounds(node.location)
  target = on_aref_field(receiver, arguments)

  bounds(node.operator_loc)
  operator = on_op("&&=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo += baz ^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2028
def visit_index_operator_write_node(node)
  receiver = visit(node.receiver)
  arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))

  bounds(node.location)
  target = on_aref_field(receiver, arguments)

  bounds(node.binary_operator_loc)
  operator = on_op("#{node.binary_operator}=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo ||= baz ^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2062
def visit_index_or_write_node(node)
  receiver = visit(node.receiver)
  arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))

  bounds(node.location)
  target = on_aref_field(receiver, arguments)

  bounds(node.operator_loc)
  operator = on_op("||=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo, = 1 ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2079
def visit_index_target_node(node)
  receiver = visit(node.receiver)
  arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))

  bounds(node.location)
  on_aref_field(receiver, arguments)
end

@foo &&= bar ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2121
def visit_instance_variable_and_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_ivar(node.name.to_s))

  bounds(node.operator_loc)
  operator = on_op("&&=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

@foo += bar ^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2107
def visit_instance_variable_operator_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_ivar(node.name.to_s))

  bounds(node.binary_operator_loc)
  operator = on_op("#{node.binary_operator}=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

@foo ||= bar ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2135
def visit_instance_variable_or_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_ivar(node.name.to_s))

  bounds(node.operator_loc)
  operator = on_op("||=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

@foo ^^^^

# File lib/prism/translation/ripper.rb, line 2089
def visit_instance_variable_read_node(node)
  bounds(node.location)
  on_var_ref(on_ivar(node.name.to_s))
end

@foo, = bar ^^^^

# File lib/prism/translation/ripper.rb, line 2149
def visit_instance_variable_target_node(node)
  bounds(node.location)
  on_var_field(on_ivar(node.name.to_s))
end

@foo = 1 ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2096
def visit_instance_variable_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_ivar(node.name.to_s))
  value = visit_write_value(node.value)

  bounds(node.location)
  on_assign(target, value)
end

1 ^

# File lib/prism/translation/ripper.rb, line 2156
def visit_integer_node(node)
  visit_number_node(node) { |text| on_int(text) }
end

if /foo #{bar}/ then end ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2162
def visit_interpolated_match_last_line_node(node)
  bounds(node.opening_loc)
  on_regexp_beg(node.opening)

  bounds(node.parts.first.location)
  parts =
    node.parts.inject(on_regexp_new) do |content, part|
      on_regexp_add(content, visit_string_content(part))
    end

  bounds(node.closing_loc)
  closing = on_regexp_end(node.closing)

  bounds(node.location)
  on_regexp_literal(parts, closing)
end

/foo #{bar}/ ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2181
def visit_interpolated_regular_expression_node(node)
  bounds(node.opening_loc)
  on_regexp_beg(node.opening)

  bounds(node.parts.first.location)
  parts =
    node.parts.inject(on_regexp_new) do |content, part|
      on_regexp_add(content, visit_string_content(part))
    end

  bounds(node.closing_loc)
  closing = on_regexp_end(node.closing)

  bounds(node.location)
  on_regexp_literal(parts, closing)
end

“foo #{bar}” ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2200
def visit_interpolated_string_node(node)
  if node.opening&.start_with?("<<~")
    heredoc = visit_heredoc_string_node(node)

    bounds(node.location)
    on_string_literal(heredoc)
  elsif !node.heredoc? && node.parts.length > 1 && node.parts.any? { |part| (part.is_a?(StringNode) || part.is_a?(InterpolatedStringNode)) && !part.opening_loc.nil? }
    first, *rest = node.parts
    rest.inject(visit(first)) do |content, part|
      concat = visit(part)

      bounds(part.location)
      on_string_concat(content, concat)
    end
  else
    bounds(node.parts.first.location)
    parts =
      node.parts.inject(on_string_content) do |content, part|
        on_string_add(content, visit_string_content(part))
      end

    bounds(node.location)
    on_string_literal(parts)
  end
end

:“foo #{bar}” ^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2228
def visit_interpolated_symbol_node(node)
  bounds(node.parts.first.location)
  parts =
    node.parts.inject(on_string_content) do |content, part|
      on_string_add(content, visit_string_content(part))
    end

  bounds(node.location)
  on_dyna_symbol(parts)
end

foo #{bar} ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2241
def visit_interpolated_x_string_node(node)
  if node.opening.start_with?("<<~")
    heredoc = visit_heredoc_x_string_node(node)

    bounds(node.location)
    on_xstring_literal(heredoc)
  else
    bounds(node.parts.first.location)
    parts =
      node.parts.inject(on_xstring_new) do |content, part|
        on_xstring_add(content, visit_string_content(part))
      end

    bounds(node.location)
    on_xstring_literal(parts)
  end
end

-> { it } ^^

# File lib/prism/translation/ripper.rb, line 2271
def visit_it_local_variable_read_node(node)
  bounds(node.location)
  on_vcall(on_ident(node.slice))
end

-> { it } ^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2278
def visit_it_parameters_node(node)
end

foo(bar: baz) ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2283
def visit_keyword_hash_node(node)
  elements = visit_all(node.elements)

  bounds(node.location)
  on_bare_assoc_hash(elements)
end

def foo(**bar); end ^^^^^

def foo(**); end ^^

# File lib/prism/translation/ripper.rb, line 2295
def visit_keyword_rest_parameter_node(node)
  if node.name_loc.nil?
    bounds(node.location)
    on_kwrest_param(nil)
  else
    bounds(node.name_loc)
    name = on_ident(node.name.to_s)

    bounds(node.location)
    on_kwrest_param(name)
  end
end

-> {}

# File lib/prism/translation/ripper.rb, line 2309
def visit_lambda_node(node)
  bounds(node.operator_loc)
  on_tlambda(node.operator)

  parameters =
    if node.parameters.is_a?(BlockParametersNode)
      # Ripper does not track block-locals within lambdas, so we skip
      # directly to the parameters here.
      params =
        if node.parameters.parameters.nil?
          bounds(node.location)
          on_params(nil, nil, nil, nil, nil, nil, nil)
        else
          visit(node.parameters.parameters)
        end

      if node.parameters.opening_loc.nil?
        params
      else
        bounds(node.parameters.opening_loc)
        on_paren(params)
      end
    else
      bounds(node.location)
      on_params(nil, nil, nil, nil, nil, nil, nil)
    end

  braces = node.opening == "{"
  if braces
    bounds(node.opening_loc)
    on_tlambeg(node.opening)
  end

  body =
    case node.body
    when nil
      bounds(node.location)
      stmts = on_stmts_add(on_stmts_new, on_void_stmt)

      bounds(node.location)
      braces ? stmts : on_bodystmt(stmts, nil, nil, nil)
    when StatementsNode
      stmts = node.body.body
      stmts.unshift(nil) if void_stmt?(node.parameters&.location || node.opening_loc, node.body.location, false)
      stmts = visit_statements_node_body(stmts)

      bounds(node.body.location)
      braces ? stmts : on_bodystmt(stmts, nil, nil, nil)
    when BeginNode
      visit_body_node(node.opening_loc, node.body)
    else
      raise
    end

  bounds(node.location)
  on_lambda(parameters, body)
end

foo &&= bar ^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2401
def visit_local_variable_and_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_ident(node.name_loc.slice))

  bounds(node.operator_loc)
  operator = on_op("&&=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo += bar ^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2387
def visit_local_variable_operator_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_ident(node.name_loc.slice))

  bounds(node.binary_operator_loc)
  operator = on_op("#{node.binary_operator}=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo ||= bar ^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2415
def visit_local_variable_or_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_ident(node.name_loc.slice))

  bounds(node.operator_loc)
  operator = on_op("||=")
  value = visit_write_value(node.value)

  bounds(node.location)
  on_opassign(target, operator, value)
end

foo ^^^

# File lib/prism/translation/ripper.rb, line 2369
def visit_local_variable_read_node(node)
  bounds(node.location)
  on_var_ref(on_ident(node.slice))
end

foo, = bar ^^^

# File lib/prism/translation/ripper.rb, line 2429
def visit_local_variable_target_node(node)
  bounds(node.location)
  on_var_field(on_ident(node.name.to_s))
end

foo = 1 ^^^^^^^

# File lib/prism/translation/ripper.rb, line 2376
def visit_local_variable_write_node(node)
  bounds(node.name_loc)
  target = on_var_field(on_ident(node.name_loc.slice))
  value = visit_write_value(node.value)

  bounds(node.location)
  on_assign(target, value)
end

if /foo/ then end ^^^^^

# File lib/prism/translation/ripper.rb, line 2436
def visit_match_last_line_node(node)
  bounds(node.opening_loc)
  on_regexp_beg(node.opening)

  bounds(node.content_loc)
  tstring_content = on_tstring_content(node.content)

  bounds(node.closing_loc)
  closing = on_regexp_end(node.closing)

  on_regexp_literal(on_regexp_add(on_regexp_new, tstring_content), closing)
end

foo in bar ^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2451
def visit_match_predicate_node(node)
  value = visit(node.value)
  pattern = on_in(visit_pattern_node(node.pattern), nil, nil)

  on_case(value, pattern)
end

foo => bar ^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2460
def visit_match_required_node(node)
  value = visit(node.value)
  pattern = on_in(visit_pattern_node(node.pattern), nil, nil)

  on_case(value, pattern)
end

/(?<foo>foo)/ =~ bar ^^^^^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2469
def visit_match_write_node(node)
  visit(node.call)
end

A node that is missing from the syntax tree. This is only used in the case of a syntax error.

# File lib/prism/translation/ripper.rb, line 2475
def visit_missing_node(node)
  raise "Cannot visit missing nodes directly."
end

module Foo; end ^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2481
def visit_module_node(node)
  constant_path =
    if node.constant_path.is_a?(ConstantReadNode)
      bounds(node.constant_path.location)
      on_const_ref(on_const(node.constant_path.name.to_s))
    else
      visit(node.constant_path)
    end

  bodystmt = visit_body_node(node.constant_path.location, node.body, true)

  bounds(node.location)
  on_module(constant_path, bodystmt)
end

(foo, bar), bar = qux ^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2498
def visit_multi_target_node(node)
  bounds(node.location)
  targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, true)

  if node.lparen_loc.nil?
    targets
  else
    bounds(node.lparen_loc)
    on_mlhs_paren(targets)
  end
end

foo, bar = baz ^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2552
def visit_multi_write_node(node)
  bounds(node.location)
  targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, true)

  unless node.lparen_loc.nil?
    bounds(node.lparen_loc)
    targets = on_mlhs_paren(targets)
  end

  value = visit_write_value(node.value)

  bounds(node.location)
  on_massign(targets, value)
end

next ^^^^

next foo ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2572
def visit_next_node(node)
  if node.arguments.nil?
    bounds(node.location)
    on_next(on_args_new)
  else
    arguments = visit(node.arguments)

    bounds(node.location)
    on_next(arguments)
  end
end

nil ^^^

# File lib/prism/translation/ripper.rb, line 2586
def visit_nil_node(node)
  bounds(node.location)
  on_var_ref(on_kw("nil"))
end

def foo(**nil); end ^^^^^

# File lib/prism/translation/ripper.rb, line 2593
def visit_no_keywords_parameter_node(node)
  bounds(node.location)
  on_nokw_param(nil)

  :nil
end

-> { 1 + 2 } ^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2602
def visit_numbered_parameters_node(node)
end

$1 ^^

# File lib/prism/translation/ripper.rb, line 2607
def visit_numbered_reference_read_node(node)
  bounds(node.location)
  on_backref(node.slice)
end

def foo(bar: baz); end ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2614
def visit_optional_keyword_parameter_node(node)
  bounds(node.name_loc)
  name = on_label("#{node.name}:")
  value = visit(node.value)

  [name, value]
end

def foo(bar = 1); end ^^^^^^^

# File lib/prism/translation/ripper.rb, line 2624
def visit_optional_parameter_node(node)
  bounds(node.name_loc)
  name = visit_token(node.name.to_s)
  value = visit(node.value)

  [name, value]
end

a or b ^^^^^^

# File lib/prism/translation/ripper.rb, line 2634
def visit_or_node(node)
  left = visit(node.left)
  right = visit(node.right)

  bounds(node.location)
  on_binary(left, node.operator.to_sym, right)
end

def foo(bar, *baz); end ^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2644
def visit_parameters_node(node)
  requireds = node.requireds.map { |required| required.is_a?(MultiTargetNode) ? visit_destructured_parameter_node(required) : visit(required) } if node.requireds.any?
  optionals = visit_all(node.optionals) if node.optionals.any?
  rest = visit(node.rest)
  posts = node.posts.map { |post| post.is_a?(MultiTargetNode) ? visit_destructured_parameter_node(post) : visit(post) } if node.posts.any?
  keywords = visit_all(node.keywords) if node.keywords.any?
  keyword_rest = visit(node.keyword_rest)
  block = visit(node.block)

  bounds(node.location)
  on_params(requireds, optionals, rest, posts, keywords, keyword_rest, block)
end

() ^^

(1) ^^^

# File lib/prism/translation/ripper.rb, line 2671
def visit_parentheses_node(node)
  body =
    if node.body.nil?
      on_stmts_add(on_stmts_new, on_void_stmt)
    else
      visit(node.body)
    end

  bounds(node.location)
  on_paren(body)
end

foo => ^(bar) ^^^^^^

# File lib/prism/translation/ripper.rb, line 2685
def visit_pinned_expression_node(node)
  expression = visit(node.expression)

  bounds(node.location)
  on_begin(expression)
end

foo = 1 and bar => ^foo ^^^^

# File lib/prism/translation/ripper.rb, line 2694
def visit_pinned_variable_node(node)
  visit(node.variable)
end

END {} ^^^^^^

# File lib/prism/translation/ripper.rb, line 2700
def visit_post_execution_node(node)
  statements =
    if node.statements.nil?
      bounds(node.location)
      on_stmts_add(on_stmts_new, on_void_stmt)
    else
      visit(node.statements)
    end

  bounds(node.location)
  on_END(statements)
end

BEGIN {} ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2715
def visit_pre_execution_node(node)
  statements =
    if node.statements.nil?
      bounds(node.location)
      on_stmts_add(on_stmts_new, on_void_stmt)
    else
      visit(node.statements)
    end

  bounds(node.location)
  on_BEGIN(statements)
end

The top-level program node.

# File lib/prism/translation/ripper.rb, line 2729
def visit_program_node(node)
  body = node.statements.body
  body << nil if body.empty?
  statements = visit_statements_node_body(body)

  bounds(node.location)
  on_program(statements)
end

0..5 ^^^^

# File lib/prism/translation/ripper.rb, line 2740
def visit_range_node(node)
  left = visit(node.left)
  right = visit(node.right)

  bounds(node.location)
  if node.exclude_end?
    on_dot3(left, right)
  else
    on_dot2(left, right)
  end
end

1r ^^

# File lib/prism/translation/ripper.rb, line 2754
def visit_rational_node(node)
  visit_number_node(node) { |text| on_rational(text) }
end

redo ^^^^

# File lib/prism/translation/ripper.rb, line 2760
def visit_redo_node(node)
  bounds(node.location)
  on_redo
end

/foo/ ^^^^^

# File lib/prism/translation/ripper.rb, line 2767
def visit_regular_expression_node(node)
  bounds(node.opening_loc)
  on_regexp_beg(node.opening)

  if node.content.empty?
    bounds(node.closing_loc)
    closing = on_regexp_end(node.closing)

    on_regexp_literal(on_regexp_new, closing)
  else
    bounds(node.content_loc)
    tstring_content = on_tstring_content(node.content)

    bounds(node.closing_loc)
    closing = on_regexp_end(node.closing)

    on_regexp_literal(on_regexp_add(on_regexp_new, tstring_content), closing)
  end
end

def foo(bar:); end ^^^^

# File lib/prism/translation/ripper.rb, line 2789
def visit_required_keyword_parameter_node(node)
  bounds(node.name_loc)
  [on_label("#{node.name}:"), false]
end

def foo(bar); end ^^^

# File lib/prism/translation/ripper.rb, line 2796
def visit_required_parameter_node(node)
  bounds(node.location)
  on_ident(node.name.to_s)
end

foo rescue bar ^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2803
def visit_rescue_modifier_node(node)
  expression = visit_write_value(node.expression)
  rescue_expression = visit(node.rescue_expression)

  bounds(node.location)
  on_rescue_mod(expression, rescue_expression)
end

begin; rescue; end ^^^^^^^

# File lib/prism/translation/ripper.rb, line 2813
def visit_rescue_node(node)
  exceptions =
    case node.exceptions.length
    when 0
      nil
    when 1
      if (exception = node.exceptions.first).is_a?(SplatNode)
        bounds(exception.location)
        on_mrhs_add_star(on_mrhs_new, visit(exception))
      else
        [visit(node.exceptions.first)]
      end
    else
      bounds(node.location)
      length = node.exceptions.length

      node.exceptions.each_with_index.inject(on_args_new) do |mrhs, (exception, index)|
        arg = visit(exception)

        bounds(exception.location)
        mrhs = on_mrhs_new_from_args(mrhs) if index == length - 1

        if exception.is_a?(SplatNode)
          if index == length - 1
            on_mrhs_add_star(mrhs, arg)
          else
            on_args_add_star(mrhs, arg)
          end
        else
          if index == length - 1
            on_mrhs_add(mrhs, arg)
          else
            on_args_add(mrhs, arg)
          end
        end
      end
    end

  reference = visit(node.reference)
  statements =
    if node.statements.nil?
      bounds(node.location)
      on_stmts_add(on_stmts_new, on_void_stmt)
    else
      visit(node.statements)
    end

  subsequent = visit(node.subsequent)

  bounds(node.location)
  on_rescue(exceptions, reference, statements, subsequent)
end

def foo(*bar); end ^^^^

def foo(*); end ^

# File lib/prism/translation/ripper.rb, line 2871
def visit_rest_parameter_node(node)
  if node.name_loc.nil?
    bounds(node.location)
    on_rest_param(nil)
  else
    bounds(node.name_loc)
    on_rest_param(visit_token(node.name.to_s))
  end
end

retry ^^^^^

# File lib/prism/translation/ripper.rb, line 2883
def visit_retry_node(node)
  bounds(node.location)
  on_retry
end

return ^^^^^^

return 1 ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2893
def visit_return_node(node)
  if node.arguments.nil?
    bounds(node.location)
    on_return0
  else
    arguments = visit(node.arguments)

    bounds(node.location)
    on_return(arguments)
  end
end

self ^^^^

# File lib/prism/translation/ripper.rb, line 2907
def visit_self_node(node)
  bounds(node.location)
  on_var_ref(on_kw("self"))
end

A shareable constant.

# File lib/prism/translation/ripper.rb, line 2913
def visit_shareable_constant_node(node)
  visit(node.write)
end

class << self; end ^^^^^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2919
def visit_singleton_class_node(node)
  expression = visit(node.expression)
  bodystmt = visit_body_node(node.body&.location || node.end_keyword_loc, node.body)

  bounds(node.location)
  on_sclass(expression, bodystmt)
end

ENCODING ^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2929
def visit_source_encoding_node(node)
  bounds(node.location)
  on_var_ref(on_kw("__ENCODING__"))
end

FILE ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2936
def visit_source_file_node(node)
  bounds(node.location)
  on_var_ref(on_kw("__FILE__"))
end

LINE ^^^^^^^^

# File lib/prism/translation/ripper.rb, line 2943
def visit_source_line_node(node)
  bounds(node.location)
  on_var_ref(on_kw("__LINE__"))
end

foo(*bar) ^^^^

def foo((bar, *baz)); end ^^^^

def foo(); bar(); end ^

# File lib/prism/translation/ripper.rb, line 2956
def visit_splat_node(node)
  visit(node.expression)
end

A list of statements.

# File lib/prism/translation/ripper.rb, line 2961
def visit_statements_node(node)
  bounds(node.location)
  visit_statements_node_body(node.body)
end

“foo” ^^^^^

# File lib/prism/translation/ripper.rb, line 2978
def visit_string_node(node)
  if (content = node.content).empty?
    bounds(node.location)
    on_string_literal(on_string_content)
  elsif (opening = node.opening) == "?"
    bounds(node.location)
    on_CHAR("?#{node.content}")
  elsif opening.start_with?("<<~")
    heredoc = visit_heredoc_string_node(node.to_interpolated)

    bounds(node.location)
    on_string_literal(heredoc)
  else
    bounds(node.content_loc)
    tstring_content = on_tstring_content(content)

    bounds(node.location)
    on_string_literal(on_string_add(on_string_content, tstring_content))
  end
end

super(foo) ^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 3110
def visit_super_node(node)
  arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location))

  if !node.lparen_loc.nil?
    bounds(node.lparen_loc)
    arguments = on_arg_paren(arguments)
  end

  bounds(node.location)
  call = on_super(arguments)

  if block.nil?
    call
  else
    bounds(node.block.location)
    on_method_add_block(call, block)
  end
end

:foo ^^^^

# File lib/prism/translation/ripper.rb, line 3131
def visit_symbol_node(node)
  if (opening = node.opening)&.match?(/^%s|['"]:?$/)
    bounds(node.value_loc)
    content = on_string_content

    if !(value = node.value).empty?
      content = on_string_add(content, on_tstring_content(value))
    end

    on_dyna_symbol(content)
  elsif (closing = node.closing) == ":"
    bounds(node.location)
    on_label("#{node.value}:")
  elsif opening.nil? && node.closing_loc.nil?
    bounds(node.value_loc)
    on_symbol_literal(visit_token(node.value))
  else
    bounds(node.value_loc)
    on_symbol_literal(on_symbol(visit_token(node.value)))
  end
end

true ^^^^

# File lib/prism/translation/ripper.rb, line 3155
def visit_true_node(node)
  bounds(node.location)
  on_var_ref(on_kw("true"))
end

undef foo ^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 3162
def visit_undef_node(node)
  names = visit_all(node.names)

  bounds(node.location)
  on_undef(names)
end

unless foo; bar end ^^^^^^^^^^^^^^^^^^^

bar unless foo ^^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 3174
def visit_unless_node(node)
  if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset)
    predicate = visit(node.predicate)
    statements =
      if node.statements.nil?
        bounds(node.location)
        on_stmts_add(on_stmts_new, on_void_stmt)
      else
        visit(node.statements)
      end
    else_clause = visit(node.else_clause)

    bounds(node.location)
    on_unless(predicate, statements, else_clause)
  else
    statements = visit(node.statements.body.first)
    predicate = visit(node.predicate)

    bounds(node.location)
    on_unless_mod(predicate, statements)
  end
end

until foo; bar end ^^^^^^^^^^^^^^^^^

bar until foo ^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 3202
def visit_until_node(node)
  if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset)
    predicate = visit(node.predicate)
    statements =
      if node.statements.nil?
        bounds(node.location)
        on_stmts_add(on_stmts_new, on_void_stmt)
      else
        visit(node.statements)
      end

    bounds(node.location)
    on_until(predicate, statements)
  else
    statements = visit(node.statements.body.first)
    predicate = visit(node.predicate)

    bounds(node.location)
    on_until_mod(predicate, statements)
  end
end

case foo; when bar; end ^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 3226
def visit_when_node(node)
  # This is a special case where we're not going to call on_when directly
  # because we don't have access to the subsequent. Instead, we'll return
  # the component parts and let the parent node handle it.
  conditions = visit_arguments(node.conditions)
  statements =
    if node.statements.nil?
      bounds(node.location)
      on_stmts_add(on_stmts_new, on_void_stmt)
    else
      visit(node.statements)
    end

  [conditions, statements]
end

while foo; bar end ^^^^^^^^^^^^^^^^^^

bar while foo ^^^^^^^^^^^^^

# File lib/prism/translation/ripper.rb, line 3247
def visit_while_node(node)
  if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset)
    predicate = visit(node.predicate)
    statements =
      if node.statements.nil?
        bounds(node.location)
        on_stmts_add(on_stmts_new, on_void_stmt)
      else
        visit(node.statements)
      end

    bounds(node.location)
    on_while(predicate, statements)
  else
    statements = visit(node.statements.body.first)
    predicate = visit(node.predicate)

    bounds(node.location)
    on_while_mod(predicate, statements)
  end
end

foo ^^^^^

# File lib/prism/translation/ripper.rb, line 3271
def visit_x_string_node(node)
  if node.unescaped.empty?
    bounds(node.location)
    on_xstring_literal(on_xstring_new)
  elsif node.opening.start_with?("<<~")
    heredoc = visit_heredoc_x_string_node(node.to_interpolated)

    bounds(node.location)
    on_xstring_literal(heredoc)
  else
    bounds(node.content_loc)
    content = on_tstring_content(node.content)

    bounds(node.location)
    on_xstring_literal(on_xstring_add(on_xstring_new, content))
  end
end

yield ^^^^^

yield 1 ^^^^^^^

# File lib/prism/translation/ripper.rb, line 3294
def visit_yield_node(node)
  if node.arguments.nil? && node.lparen_loc.nil?
    bounds(node.location)
    on_yield0
  else
    arguments =
      if node.arguments.nil?
        bounds(node.location)
        on_args_new
      else
        visit(node.arguments)
      end

    unless node.lparen_loc.nil?
      bounds(node.lparen_loc)
      arguments = on_paren(arguments)
    end

    bounds(node.location)
    on_yield(arguments)
  end
end