Module: SimpleCov::SourceFile::RubyDataParser
- 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 collapse
-
.call(structure) ⇒ Object
Tests use the real data structures (except for integration tests) so no need to put them through here.
-
.parse_array_string(str) ⇒ Object
Parse a string like ‘[:if, 0, 3, 4, 3, 21]’ or ‘[“ClassName”, :method1, 2, 2, 5, 5]’ back into a Ruby array.
- .parse_element(node) ⇒ Object
- .parse_integer_node(node) ⇒ Object
- .parse_symbol_node(node) ⇒ Object
-
.quote_inspected_class_segments(str) ⇒ Object
Method coverage keys can contain inspect-format class references like ‘#<Class:Foo>` or `#<Class:0x…>`, which aren’t valid Ruby syntax.
-
.string_literal_text(string_content) ⇒ Object
Concatenate the text fragments of a ‘:string_content` node.
-
.unescape_ruby(raw) ⇒ Object
Undo the same backslash-prefix escapes the previous hand-rolled parser undid: ‘X` → `X` for any X.
Class Method Details
.call(structure) ⇒ Object
Tests use the real data structures (except for integration tests) so no need to put them through here.
18 19 20 21 22 |
# 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) ⇒ Object
Parse a string like ‘[:if, 0, 3, 4, 3, 21]’ or ‘[“ClassName”, :method1, 2, 2, 5, 5]’ back into a Ruby array.
26 27 28 29 30 31 32 33 34 35 36 37 38 |
# 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) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 |
# 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) ⇒ Object
54 55 56 |
# 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) ⇒ Object
58 59 60 61 62 63 64 |
# 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) ⇒ Object
Method coverage keys can contain inspect-format class references like ‘#<Class:Foo>` or `#<Class:0x…>`, 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.
83 84 85 |
# 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) ⇒ Object
Concatenate the text fragments of a ‘:string_content` node. Ripper may emit zero, one, or many `:@tstring_content` children depending on the literal.
69 70 71 |
# 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) ⇒ Object
Undo the same backslash-prefix escapes the previous hand-rolled parser undid: ‘X` → `X` for any X.
75 76 77 |
# File 'lib/simplecov/source_file/ruby_data_parser.rb', line 75 def unescape_ruby(raw) raw.gsub(/\\(.)/) { ::Regexp.last_match(1) } end |