module SimpleCov::SourceFile::RubyDataParser
def call(structure)
Tests use the real data structures (except for integration tests)
def call(structure) return structure if structure.is_a?(Array) parse_array_string(structure.to_s) end
def parse_array_string(str)
Parse a string like '[:if, 0, 3, 4, 3, 21]' or
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
def parse_element(node)
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
def parse_integer_node(node)
def parse_integer_node(node) node[0] == :@int ? node[1].to_i : -node[2][1].to_i end
def parse_symbol_node(node)
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
def quote_inspected_class_segments(str)
syntax. Wrap them in quotes so Ripper can parse the surrounding
like `#
Method coverage keys can contain inspect-format class references
def quote_inspected_class_segments(str) str.gsub(/#<[^>]*>/) { |segment| %("#{segment.gsub('"', '\\"')}") } end
def string_literal_text(string_content)
may emit zero, one, or many `:@tstring_content` children depending
Concatenate the text fragments of a `:string_content` node. Ripper
def string_literal_text(string_content) Array(string_content[1..]).map { |child| child[1] }.join end
def unescape_ruby(raw)
Undo the same backslash-prefix escapes the previous hand-rolled
def unescape_ruby(raw) raw.gsub(/\\(.)/) { ::Regexp.last_match(1) } end