123456789_123456789_123456789_123456789_123456789_

Module: SimpleCov::SourceFile::RubyDataParser

Relationships & Source Files
Defined in: lib/simplecov/source_file/ruby_data_parser.rb

Overview

Coverage.result reports condition and method keys as Ruby arrays. When the resultset is round-tripped through JSON those array keys become their stringified inspect form, so this parser walks the literal back into a real Array without using eval (see #801). The grammar covers symbols, strings, integers, unary minus, and constant paths — every shape Coverage ever emits.

Class Method Summary

Class Method Details

.call(structure) (mod_func)

Tests use the real data structures (except for integration tests) so no need to put them through here.

[ GitHub ]

  
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 18

def call(structure)
  return structure if structure.is_a?(Array)

  parse_array_string(structure.to_s)
end

.parse_array_string(str) (mod_func)

Parse a string like '[:if, 0, 3, 4, 3, 21]' or '["ClassName", :method1, 2, 2, 5, 5]' back into a Ruby array.

Raises:

  • (ArgumentError)
[ GitHub ]

  
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 26

def parse_array_string(str)
  # Try plain Ripper first; only pre-quote `#<...>` inspect segments
  # if the input isn't already valid Ruby (otherwise we corrupt
  # `"#<Class:Foo>"` strings that *are* valid Ruby literals — exactly
  # the shape simplecov-on-simplecov method-coverage keys take).
  sexp = Ripper.sexp(str) || Ripper.sexp(quote_inspected_class_segments(str))
  # simplecov:disable — defensive: Ripper.sexp returning nil from both passes requires malformed input
  array_node = sexp&.dig(1, 0)
  # simplecov:enable
  raise ArgumentError, "expected array literal: #{str.inspect}" unless array_node && array_node[0] == :array

  Array(array_node[1]).map { |element| parse_element(element) }
end

.parse_element(node) (mod_func)

[ GitHub ]

  
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 40

def parse_element(node)
  case node[0]
  when :@int, :unary                 then parse_integer_node(node)
  when :symbol_literal, :dyna_symbol then parse_symbol_node(node)
  when :string_literal               then unescape_ruby(string_literal_text(node[1]))
  when :var_ref                      then node.dig(1, 1) # `Foo`
  when :const_path_ref               then "#{parse_element(node[1])}::#{node[2][1]}" # `Foo::Bar`
  else
    # simplecov:disable — defensive fallback for unexpected Ripper node shapes
    raise ArgumentError, "unexpected element: #{node.inspect}"
    # simplecov:enable
  end
end

.parse_integer_node(node) (mod_func)

[ GitHub ]

  
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 54

def parse_integer_node(node)
  node[0] == :@int ? node[1].to_i : -node[2][1].to_i
end

.parse_symbol_node(node) (mod_func)

[ GitHub ]

  
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 58

def parse_symbol_node(node)
  if node[0] == :symbol_literal
    node.dig(1, 1, 1).to_sym
  else
    unescape_ruby(string_literal_text(node[1])).to_sym
  end
end

.quote_inspected_class_segments(str) (mod_func)

Method coverage keys can contain inspect-format class references like # or #, which aren't valid Ruby syntax. Wrap them in quotes so Ripper can parse the surrounding array literal; downstream we treat them as opaque strings.

[ GitHub ]

  
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 83

def quote_inspected_class_segments(str)
  str.gsub(/#<[^>]*>/) { |segment| %("#{segment.gsub('"', '\\"')}") }
end

.string_literal_text(string_content) (mod_func)

Concatenate the text fragments of a :string_content node. Ripper may emit zero, one, or many :@tstring_content children depending on the literal.

[ GitHub ]

  
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 69

def string_literal_text(string_content)
  Array(string_content[1..]).map { |child| child[1] }.join
end

.unescape_ruby(raw) (mod_func)

Undo the same backslash-prefix escapes the previous hand-rolled parser undid: \XX for any X.

[ GitHub ]

  
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 75

def unescape_ruby(raw)
  raw.gsub(/\\(.)/) { ::Regexp.last_match(1) }
end