ruby/test/yarp/parse_test.rb
Aaron Patterson abce8583e2 [ruby/yarp] Fix heredocs inside %W and %w lists
The problem was that we were treating heredoc bodies as part of the %W
list because we didn't push the scanning cursor past the heredoc after
lexing out the here doc.  To fix this, we changed the whitespace
scanning function to quit scanning when it reaches a newline but only in
the case that a heredoc is present.

Additionally, we need to prevent double counting newlines in the case of
a heredoc.  For example:

```ruby
%W(<<foo 123)
foo
```

The newline after the `)` is counted as part of scanning the heredoc, so
we added logic to prevent double counting the newline when scanning the
rest of the %W list.

eb090d8126

Co-authored-by: Jemma Issroff <jemmaissroff@gmail.com>
2023-07-20 14:58:11 +00:00

132 lines
4.4 KiB
Ruby

# frozen_string_literal: true
require "yarp_test_helper"
class ParseTest < Test::Unit::TestCase
# When we pretty-print the trees to compare against the snapshots, we want to
# be certain that we print with the same external encoding. This is because
# methods like Symbol#inspect take into account external encoding and it could
# change how the snapshot is generated. On machines with certain settings
# (like LANG=C or -Eascii-8bit) this could have been changed. So here we're
# going to force it to be UTF-8 to keep the snapshots consistent.
def setup
@previous_default_external = Encoding.default_external
ignore_warnings { Encoding.default_external = Encoding::UTF_8 }
end
def teardown
ignore_warnings { Encoding.default_external = @previous_default_external }
end
def test_Ruby_3_2_plus
assert_operator RUBY_VERSION, :>=, "3.2.0", "ParseTest requires Ruby 3.2+"
end
def test_empty_string
YARP.parse("") => YARP::ParseResult[value: YARP::ProgramNode[statements: YARP::StatementsNode[body: []]]]
end
known_failures = %w[
seattlerb/heredoc_nested.txt
]
def find_source_file_node(node)
if node.is_a?(YARP::SourceFileNode)
node
else
node && node.child_nodes.each do |child_node|
source_file_node = find_source_file_node(child_node)
return source_file_node if source_file_node
end
end
end
def test_parse_dollar0
parsed_result = YARP.parse("$0", "-e")
assert_equal 2, parsed_result.value.location.length
end
def test_parse_takes_file_path
filepath = "filepath.rb"
parsed_result = YARP.parse("def foo; __FILE__; end", filepath)
assert_equal filepath, find_source_file_node(parsed_result.value).filepath
end
base = File.join(__dir__, "fixtures")
Dir["**/*.txt", base: base].each do |relative|
next if known_failures.include?(relative)
filepath = File.join(base, relative)
snapshot = File.expand_path(File.join("snapshots", relative), __dir__)
directory = File.dirname(snapshot)
FileUtils.mkdir_p(directory) unless File.directory?(directory)
define_method "test_filepath_#{relative}" do
# First, read the source from the filepath. Use binmode to avoid converting CRLF on Windows,
# and explicitly set the external encoding to UTF-8 to override the binmode default.
source = File.read(filepath, binmode: true, external_encoding: Encoding::UTF_8)
# Make sure that it can be correctly parsed by Ripper. If it can't, then we have a fixture
# that is invalid Ruby.
refute_nil Ripper.sexp_raw(source)
# Next, assert that there were no errors during parsing.
result = YARP.parse(source, relative)
assert_empty result.errors
# Next, pretty print the source.
printed = PP.pp(result.value, +"", 79)
if File.exist?(snapshot)
saved = File.read(snapshot)
# If the snapshot file exists, but the printed value does not match the
# snapshot, then update the snapshot file.
if printed != saved
File.write(snapshot, printed)
warn("Updated snapshot at #{snapshot}.")
end
# If the snapshot file exists, then assert that the printed value
# matches the snapshot.
assert_equal(saved, printed)
else
# If the snapshot file does not yet exist, then write it out now.
File.write(snapshot, printed)
warn("Created snapshot at #{snapshot}.")
end
# Next, assert that the value can be serialized and deserialized without
# changing the shape of the tree.
assert_equal_nodes(result.value, YARP.load(source, YARP.dump(source, relative)))
# Next, assert that the newlines are in the expected places.
expected_newlines = [0]
source.b.scan("\n") { expected_newlines << $~.offset(0)[0] + 1 }
assert_equal expected_newlines, YARP.newlines(source)
# Finally, assert that we can lex the source and get the same tokens as
# Ripper.
YARP.lex_compat(source) => { errors: [], value: tokens }
begin
YARP.lex_ripper(source).zip(tokens).each do |(ripper, yarp)|
assert_equal ripper, yarp
end
rescue SyntaxError
raise ArgumentError, "Test file has invalid syntax #{filepath}"
end
end
end
private
def ignore_warnings
previous_verbosity = $VERBOSE
$VERBOSE = nil
yield
ensure
$VERBOSE = previous_verbosity
end
end