summaryrefslogtreecommitdiff
path: root/test/prism
diff options
context:
space:
mode:
Diffstat (limited to 'test/prism')
-rw-r--r--test/prism/api/freeze_test.rb5
-rw-r--r--test/prism/api/parse_stream_test.rb51
-rw-r--r--test/prism/api/parse_test.rb18
-rw-r--r--test/prism/bom_test.rb3
-rw-r--r--test/prism/encoding/encodings_test.rb18
-rw-r--r--test/prism/encoding/regular_expression_encoding_test.rb34
-rw-r--r--test/prism/errors/3.3-3.3/circular_parameters.txt12
-rw-r--r--test/prism/errors/3.3-3.4/leading_logical.txt34
-rw-r--r--test/prism/errors/3.3-3.4/private_endless_method.txt3
-rw-r--r--test/prism/errors/3.3-4.0/do_not_allow_trailing_commas_in_method_parameters.txt (renamed from test/prism/errors/do_not_allow_trailing_commas_in_method_parameters.txt)0
-rw-r--r--test/prism/errors/3.3-4.0/noblock.txt6
-rw-r--r--test/prism/errors/3.3-4.0/singleton_method_with_void_value.txt3
-rw-r--r--test/prism/errors/3.4-4.0/void_value.txt18
-rw-r--r--test/prism/errors/3.4/block_args_in_array_assignment.txt (renamed from test/prism/errors/block_args_in_array_assignment.txt)0
-rw-r--r--test/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt (renamed from test/prism/errors/dont_allow_return_inside_sclass_body.txt)0
-rw-r--r--test/prism/errors/3.4/it_with_ordinary_parameter.txt (renamed from test/prism/errors/it_with_ordinary_parameter.txt)0
-rw-r--r--test/prism/errors/3.4/keyword_args_in_array_assignment.txt (renamed from test/prism/errors/keyword_args_in_array_assignment.txt)0
-rw-r--r--test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt6
-rw-r--r--test/prism/errors/4.1/end_block_exit.txt10
-rw-r--r--test/prism/errors/4.1/multiple_blocks.txt12
-rw-r--r--test/prism/errors/4.1/singleton_method_with_void_value.txt4
-rw-r--r--test/prism/errors/4.1/void_value.txt44
-rw-r--r--test/prism/errors/block_args_with_endless_def.txt5
-rw-r--r--test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt2
-rw-r--r--test/prism/errors/block_pass_return_value.txt33
-rw-r--r--test/prism/errors/command_call_in.txt1
-rw-r--r--test/prism/errors/command_call_in_2.txt4
-rw-r--r--test/prism/errors/command_call_in_3.txt4
-rw-r--r--test/prism/errors/command_call_in_4.txt4
-rw-r--r--test/prism/errors/command_call_in_5.txt4
-rw-r--r--test/prism/errors/command_call_in_6.txt4
-rw-r--r--test/prism/errors/command_call_in_7.txt4
-rw-r--r--test/prism/errors/command_call_value_and.txt3
-rw-r--r--test/prism/errors/command_call_value_or.txt3
-rw-r--r--test/prism/errors/command_calls.txt7
-rw-r--r--test/prism/errors/command_calls_2.txt2
-rw-r--r--test/prism/errors/command_calls_24.txt2
-rw-r--r--test/prism/errors/command_calls_25.txt2
-rw-r--r--test/prism/errors/command_calls_31.txt17
-rw-r--r--test/prism/errors/command_calls_32.txt19
-rw-r--r--test/prism/errors/command_calls_33.txt6
-rw-r--r--test/prism/errors/command_calls_34.txt31
-rw-r--r--test/prism/errors/command_calls_35.txt50
-rw-r--r--test/prism/errors/def_endless_do.txt6
-rw-r--r--test/prism/errors/def_with_optional_splat.txt6
-rw-r--r--test/prism/errors/defined_empty.txt3
-rw-r--r--test/prism/errors/destroy_call_operator_write_arguments.txt11
-rw-r--r--test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt12
-rw-r--r--test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt12
-rw-r--r--test/prism/errors/endless_method_command_call.txt3
-rw-r--r--test/prism/errors/endless_method_command_call_parameters.txt27
-rw-r--r--test/prism/errors/heredoc_percent_q_newline_delimiter.txt11
-rw-r--r--test/prism/errors/heredoc_unterminated.txt2
-rw-r--r--test/prism/errors/infix_after_label.txt2
-rw-r--r--test/prism/errors/interpolated_symbol_pattern_hash_key.txt3
-rw-r--r--test/prism/errors/label_in_interpolated_string.txt14
-rw-r--r--test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt1
-rw-r--r--test/prism/errors/match_predicate_after_rescue_with_opreator.txt1
-rw-r--r--test/prism/errors/match_required_after_rescue_with_dot_method_call.txt1
-rw-r--r--test/prism/errors/match_required_after_rescue_with_opreator.txt1
-rw-r--r--test/prism/errors/modifier_conditional_in_predicate.txt12
-rw-r--r--test/prism/errors/not_without_parens_assignment.txt4
-rw-r--r--test/prism/errors/not_without_parens_call.txt7
-rw-r--r--test/prism/errors/not_without_parens_command.txt4
-rw-r--r--test/prism/errors/not_without_parens_command_call.txt4
-rw-r--r--test/prism/errors/not_without_parens_return.txt4
-rw-r--r--test/prism/errors/pattern-capture-in-alt-array.txt4
-rw-r--r--test/prism/errors/pattern-capture-in-alt-hash.txt3
-rw-r--r--test/prism/errors/pattern-capture-in-alt-name.txt3
-rw-r--r--test/prism/errors/pattern-capture-in-alt-top.txt4
-rw-r--r--test/prism/errors/pattern_arithmetic_expressions.txt3
-rw-r--r--test/prism/errors/pattern_match_implicit_rest.txt3
-rw-r--r--test/prism/errors/pattern_string_key.txt8
-rw-r--r--test/prism/errors/rescue_pattern.txt4
-rw-r--r--test/prism/errors/shadow_args_in_lambda.txt2
-rw-r--r--test/prism/errors/singleton_method_for_literals.txt2
-rw-r--r--test/prism/errors/unterminated_begin.txt4
-rw-r--r--test/prism/errors/unterminated_begin_upcase.txt4
-rw-r--r--test/prism/errors/unterminated_block.txt2
-rw-r--r--test/prism/errors/unterminated_block_do_end.txt4
-rw-r--r--test/prism/errors/unterminated_class.txt4
-rw-r--r--test/prism/errors/unterminated_def.txt5
-rw-r--r--test/prism/errors/unterminated_end_upcase.txt4
-rw-r--r--test/prism/errors/unterminated_for.txt5
-rw-r--r--test/prism/errors/unterminated_heredoc_and_embexpr.txt11
-rw-r--r--test/prism/errors/unterminated_heredoc_and_embexpr_2.txt9
-rw-r--r--test/prism/errors/unterminated_if.txt5
-rw-r--r--test/prism/errors/unterminated_if_else.txt5
-rw-r--r--test/prism/errors/unterminated_lambda_brace.txt4
-rw-r--r--test/prism/errors/unterminated_module.txt4
-rw-r--r--test/prism/errors/unterminated_pattern_bracket.txt7
-rw-r--r--test/prism/errors/unterminated_pattern_paren.txt7
-rw-r--r--test/prism/errors/unterminated_until.txt5
-rw-r--r--test/prism/errors/void_value_expression_in_begin_statement.txt2
-rw-r--r--test/prism/errors/while_endless_method.txt2
-rw-r--r--test/prism/errors_test.rb110
-rw-r--r--test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt1
-rw-r--r--test/prism/fixtures/3.3-3.3/it.txt (renamed from test/prism/fixtures/it.txt)2
-rw-r--r--test/prism/fixtures/3.3-3.3/it_indirect_writes.txt (renamed from test/prism/fixtures/it_indirect_writes.txt)0
-rw-r--r--test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt1
-rw-r--r--test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt1
-rw-r--r--test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt1
-rw-r--r--test/prism/fixtures/3.3-3.3/return_in_sclass.txt1
-rw-r--r--test/prism/fixtures/3.3-4.0/end_block_exit.txt11
-rw-r--r--test/prism/fixtures/3.3-4.0/void_value.txt29
-rw-r--r--test/prism/fixtures/3.4/circular_parameters.txt4
-rw-r--r--test/prism/fixtures/3.4/it.txt5
-rw-r--r--test/prism/fixtures/3.4/it_indirect_writes.txt23
-rw-r--r--test/prism/fixtures/3.4/it_read_and_assignment.txt1
-rw-r--r--test/prism/fixtures/4.0/endless_methods_command_call.txt11
-rw-r--r--test/prism/fixtures/4.0/leading_logical.txt16
-rw-r--r--test/prism/fixtures/4.1/noblock.txt4
-rw-r--r--test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt15
-rw-r--r--test/prism/fixtures/4.1/void_value.txt7
-rw-r--r--test/prism/fixtures/__END__.txt3
-rw-r--r--test/prism/fixtures/and_or_with_suffix.txt17
-rw-r--r--test/prism/fixtures/blocks.txt8
-rw-r--r--test/prism/fixtures/bom_leading_space.txt1
-rw-r--r--test/prism/fixtures/bom_spaces.txt1
-rw-r--r--test/prism/fixtures/break.txt4
-rw-r--r--test/prism/fixtures/case_in_hash_key.txt6
-rw-r--r--test/prism/fixtures/case_in_in.txt4
-rw-r--r--test/prism/fixtures/character_literal.txt2
-rw-r--r--test/prism/fixtures/command_method_call_2.txt1
-rw-r--r--test/prism/fixtures/command_method_call_3.txt19
-rw-r--r--test/prism/fixtures/defined.txt9
-rw-r--r--test/prism/fixtures/dstring.txt9
-rw-r--r--test/prism/fixtures/endless_method_as_default_arg.txt11
-rw-r--r--test/prism/fixtures/endless_methods.txt6
-rw-r--r--test/prism/fixtures/escaped_newline_with_trailing_content.txt2
-rw-r--r--test/prism/fixtures/heredoc_dedent_line_continuation.txt5
-rw-r--r--test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt22
-rw-r--r--test/prism/fixtures/it_assignment.txt1
-rw-r--r--test/prism/fixtures/keyword_method_names.txt9
-rw-r--r--test/prism/fixtures/next.txt4
-rw-r--r--test/prism/fixtures/non_void_value.txt31
-rw-r--r--test/prism/fixtures/patterns.txt5
-rw-r--r--test/prism/fixtures/rescue.txt4
-rw-r--r--test/prism/fixtures/return.txt3
-rw-r--r--test/prism/fixtures/string_concatination_frozen_false.txt5
-rw-r--r--test/prism/fixtures/string_concatination_frozen_true.txt5
-rw-r--r--test/prism/fixtures/strings.txt56
-rw-r--r--test/prism/fixtures/symbols.txt4
-rw-r--r--test/prism/fixtures/unary_method_calls.txt8
-rw-r--r--test/prism/fixtures/variables.txt2
-rw-r--r--test/prism/fixtures/write_command_operator.txt3
-rw-r--r--test/prism/fixtures/xstring.txt9
-rw-r--r--test/prism/fixtures_test.rb10
-rw-r--r--test/prism/lex_test.rb117
-rw-r--r--test/prism/locals_test.rb20
-rw-r--r--test/prism/magic_comment_test.rb5
-rw-r--r--test/prism/newline_offsets_test.rb27
-rw-r--r--test/prism/newline_test.rb3
-rw-r--r--test/prism/ractor_test.rb74
-rw-r--r--test/prism/result/breadth_first_search_test.rb11
-rw-r--r--test/prism/result/continuable_test.rb124
-rw-r--r--test/prism/result/error_recovery_test.rb237
-rw-r--r--test/prism/result/numeric_value_test.rb11
-rw-r--r--test/prism/result/overlap_test.rb9
-rw-r--r--test/prism/result/source_location_test.rb16
-rw-r--r--test/prism/result/warnings_test.rb21
-rw-r--r--test/prism/ruby/dispatcher_test.rb19
-rw-r--r--test/prism/ruby/find_fixtures.rb69
-rw-r--r--test/prism/ruby/find_test.rb242
-rw-r--r--test/prism/ruby/location_test.rb49
-rw-r--r--test/prism/ruby/parameters_signature_test.rb22
-rw-r--r--test/prism/ruby/parser_test.rb117
-rw-r--r--test/prism/ruby/ripper_test.rb277
-rw-r--r--test/prism/ruby/ruby_parser_test.rb46
-rw-r--r--test/prism/ruby/source_test.rb51
-rw-r--r--test/prism/snapshots/heredocs_with_fake_newlines.txt223
-rw-r--r--test/prism/snapshots/strings.txt800
-rw-r--r--test/prism/snapshots_test.rb77
-rw-r--r--test/prism/snippets_test.rb12
-rw-r--r--test/prism/test_helper.rb82
175 files changed, 2610 insertions, 1451 deletions
diff --git a/test/prism/api/freeze_test.rb b/test/prism/api/freeze_test.rb
index 5533a00331..bf91792e69 100644
--- a/test/prism/api/freeze_test.rb
+++ b/test/prism/api/freeze_test.rb
@@ -8,6 +8,11 @@ module Prism
assert_frozen(Prism.parse("1 + 2; %i{foo} + %i{bar}", freeze: true))
end
+ def test_offsets_usable
+ node = Prism.parse_statement("1 + 2", freeze: true)
+ assert_equal(1, node.start_line)
+ end
+
def test_lex
assert_frozen(Prism.lex("1 + 2; %i{foo} + %i{bar}", freeze: true))
end
diff --git a/test/prism/api/parse_stream_test.rb b/test/prism/api/parse_stream_test.rb
index 1c068c617c..3bc86fbd61 100644
--- a/test/prism/api/parse_stream_test.rb
+++ b/test/prism/api/parse_stream_test.rb
@@ -30,16 +30,28 @@ module Prism
end
def test___END__
- io = StringIO.new("1 + 2\n3 + 4\n__END__\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ __END__
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
assert_equal 2, result.value.statements.body.length
- assert_equal "5 + 6", io.read
+ assert_equal "5 + 6\n", io.read
end
def test_false___END___in_string
- io = StringIO.new("1 + 2\n3 + 4\n\"\n__END__\n\"\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ "
+ __END__
+ "
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
@@ -47,7 +59,14 @@ module Prism
end
def test_false___END___in_regexp
- io = StringIO.new("1 + 2\n3 + 4\n/\n__END__\n/\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ /
+ __END__
+ /
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
@@ -55,7 +74,14 @@ module Prism
end
def test_false___END___in_list
- io = StringIO.new("1 + 2\n3 + 4\n%w[\n__END__\n]\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ %w[
+ __END__
+ ]
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
@@ -63,7 +89,14 @@ module Prism
end
def test_false___END___in_heredoc
- io = StringIO.new("1 + 2\n3 + 4\n<<-EOF\n__END__\nEOF\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ <<-EOF
+ __END__
+ EOF
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
@@ -71,7 +104,11 @@ module Prism
end
def test_nul_bytes
- io = StringIO.new("1 # \0\0\0 \n2 # \0\0\0\n3")
+ io = StringIO.new(<<~RUBY)
+ 1 # \0\0\0\t
+ 2 # \0\0\0
+ 3
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb
index bbce8a8fad..c9a47c1a61 100644
--- a/test/prism/api/parse_test.rb
+++ b/test/prism/api/parse_test.rb
@@ -119,6 +119,12 @@ module Prism
assert Prism.parse_success?("1 + 1", version: "3.5")
assert Prism.parse_success?("1 + 1", version: "3.5.0")
+ assert Prism.parse_success?("1 + 1", version: "4.0")
+ assert Prism.parse_success?("1 + 1", version: "4.0.0")
+
+ assert Prism.parse_success?("1 + 1", version: "4.1")
+ assert Prism.parse_success?("1 + 1", version: "4.1.0")
+
assert Prism.parse_success?("1 + 1", version: "latest")
# Test edge case
@@ -140,6 +146,18 @@ module Prism
end
end
+ def test_version_current
+ if RUBY_VERSION >= "3.3"
+ assert Prism.parse_success?("1 + 1", version: "current")
+ else
+ assert_raise(CurrentVersionError) { Prism.parse_success?("1 + 1", version: "current") }
+ end
+ end
+
+ def test_nearest
+ assert Prism.parse_success?("1 + 1", version: "nearest")
+ end
+
def test_scopes
assert_kind_of Prism::CallNode, Prism.parse_statement("foo")
assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]])
diff --git a/test/prism/bom_test.rb b/test/prism/bom_test.rb
index 890bc4b36c..0fa00ae4e8 100644
--- a/test/prism/bom_test.rb
+++ b/test/prism/bom_test.rb
@@ -5,6 +5,7 @@
return if RUBY_ENGINE != "ruby"
require_relative "test_helper"
+require "ripper"
module Prism
class BOMTest < TestCase
@@ -53,7 +54,7 @@ module Prism
def assert_bom(source)
bommed = "\xEF\xBB\xBF#{source}"
- assert_equal Prism.lex_ripper(bommed), Prism.lex_compat(bommed).value
+ assert_equal Ripper.lex(bommed), Prism.lex_compat(bommed).value
end
end
end
diff --git a/test/prism/encoding/encodings_test.rb b/test/prism/encoding/encodings_test.rb
index 4ad2b465cc..b008fc3fa1 100644
--- a/test/prism/encoding/encodings_test.rb
+++ b/test/prism/encoding/encodings_test.rb
@@ -56,21 +56,11 @@ module Prism
# Check that we can properly parse every codepoint in the given encoding.
def assert_encoding(encoding, name, range)
- # I'm not entirely sure, but I believe these codepoints are incorrect in
- # their parsing in CRuby. They all report as matching `[[:lower:]]` but
- # then they are parsed as constants. This is because CRuby determines if
- # an identifier is a constant or not by case folding it down to lowercase
- # and checking if there is a difference. And even though they report
- # themselves as lowercase, their case fold is different. I have reported
- # this bug upstream.
+ unicode = false
+
case encoding
when Encoding::UTF_8, Encoding::UTF_8_MAC, Encoding::UTF8_DoCoMo, Encoding::UTF8_KDDI, Encoding::UTF8_SoftBank, Encoding::CESU_8
- range = range.to_a - [
- 0x01c5, 0x01c8, 0x01cb, 0x01f2, 0x1f88, 0x1f89, 0x1f8a, 0x1f8b,
- 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b,
- 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1fa8, 0x1fa9, 0x1faa, 0x1fab,
- 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fbc, 0x1fcc, 0x1ffc,
- ]
+ unicode = true
when Encoding::Windows_1253
range = range.to_a - [0xb5]
end
@@ -79,7 +69,7 @@ module Prism
character = codepoint.chr(encoding)
if character.match?(/[[:alpha:]]/)
- if character.match?(/[[:upper:]]/)
+ if character.match?(/[[:upper:]]/) || (unicode && character.match?(Regexp.new("\\p{Lt}".encode(encoding))))
assert_encoding_constant(name, character)
else
assert_encoding_identifier(name, character)
diff --git a/test/prism/encoding/regular_expression_encoding_test.rb b/test/prism/encoding/regular_expression_encoding_test.rb
index e2daae1d7f..fdff1e3281 100644
--- a/test/prism/encoding/regular_expression_encoding_test.rb
+++ b/test/prism/encoding/regular_expression_encoding_test.rb
@@ -2,6 +2,7 @@
return unless defined?(RubyVM::InstructionSequence)
return if RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism
+return if RUBY_VERSION < "3.2"
require_relative "../test_helper"
@@ -21,7 +22,7 @@ module Prism
["n", "u", "e", "s"].each do |modifier|
define_method(:"test_regular_expression_encoding_modifiers_/#{modifier}_#{encoding.name}") do
- regexp_sources = ["abc", "garçon", "\\x80", "gar\\xC3\\xA7on", "gar\\u{E7}on", "abc\\u{FFFFFF}", "\\x80\\u{80}" ]
+ regexp_sources = ["abc", "garçon", "\\x80", "gar\\xC3\\xA7on", "gar\\u{E7}on", "abc\\u{FFFFFF}", "\\x80\\u{80}", "\\p{L}" ]
assert_regular_expression_encoding_flags(
encoding,
@@ -35,17 +36,15 @@ module Prism
def assert_regular_expression_encoding_flags(encoding, regexps)
regexps.each do |regexp|
- regexp_modifier_used = regexp.end_with?("/u") || regexp.end_with?("/e") || regexp.end_with?("/s") || regexp.end_with?("/n")
source = "# encoding: #{encoding.name}\n#{regexp}"
- encoding_errors = ["invalid multibyte char", "escaped non ASCII character in UTF-8 regexp", "differs from source encoding"]
- skipped_errors = ["invalid multibyte escape", "incompatible character encoding", "UTF-8 character in non UTF-8 regexp", "invalid Unicode range", "invalid Unicode list"]
-
- # TODO (nirvdrum 21-Feb-2024): Prism currently does not handle Regexp validation unless modifiers are used. So, skip processing those errors for now: https://github.com/ruby/prism/issues/2104
- unless regexp_modifier_used
- skipped_errors += encoding_errors
- encoding_errors.clear
- end
+ encoding_errors = [
+ "invalid multibyte char", "escaped non ASCII character in UTF-8 regexp",
+ "differs from source encoding", "incompatible character encoding",
+ "invalid multibyte escape", "UTF-8 character in non UTF-8 regexp",
+ "invalid Unicode range", "non escaped non ASCII character",
+ "invalid character property name", "invalid Unicode list",
+ ]
expected =
begin
@@ -53,8 +52,6 @@ module Prism
rescue SyntaxError => error
if encoding_errors.find { |e| error.message.include?(e) }
error.message.split("\n").map { |m| m[/: (.+?)$/, 1] }
- elsif skipped_errors.find { |e| error.message.include?(e) }
- next
else
raise
end
@@ -111,19 +108,6 @@ module Prism
end
end
- # TODO (nirvdrum 22-Feb-2024): Remove this workaround once Prism better maps CRuby's error messages.
- # This class of error message is tricky. The part not being compared is a representation of the regexp.
- # Depending on the source encoding and any encoding modifiers being used, CRuby alters how the regexp is represented.
- # Sometimes it's an MBC string. Other times it uses hexadecimal character escapes. And in other cases it uses
- # the long-form Unicode escape sequences. This short-circuit checks that the error message is mostly correct.
- if expected.is_a?(Array) && actual.is_a?(Array)
- if expected.last.start_with?("/.../n has a non escaped non ASCII character in non ASCII-8BIT script:") &&
- actual.last.start_with?("/.../n has a non escaped non ASCII character in non ASCII-8BIT script:")
- expected.pop
- actual.pop
- end
- end
-
assert_equal expected, actual
end
end
diff --git a/test/prism/errors/3.3-3.3/circular_parameters.txt b/test/prism/errors/3.3-3.3/circular_parameters.txt
new file mode 100644
index 0000000000..ef9642b075
--- /dev/null
+++ b/test/prism/errors/3.3-3.3/circular_parameters.txt
@@ -0,0 +1,12 @@
+def foo(bar = bar) = 42
+ ^~~ circular argument reference - bar
+
+def foo(bar: bar) = 42
+ ^~~ circular argument reference - bar
+
+proc { |foo = foo| }
+ ^~~ circular argument reference - foo
+
+proc { |foo: foo| }
+ ^~~ circular argument reference - foo
+
diff --git a/test/prism/errors/3.3-3.4/leading_logical.txt b/test/prism/errors/3.3-3.4/leading_logical.txt
new file mode 100644
index 0000000000..2a702e281d
--- /dev/null
+++ b/test/prism/errors/3.3-3.4/leading_logical.txt
@@ -0,0 +1,34 @@
+1
+&& 2
+^~ unexpected '&&', ignoring it
+&& 3
+^~ unexpected '&&', ignoring it
+
+1
+|| 2
+^ unexpected '|', ignoring it
+ ^ unexpected '|', ignoring it
+|| 3
+^ unexpected '|', ignoring it
+ ^ unexpected '|', ignoring it
+
+1
+and 2
+^~~ unexpected 'and', ignoring it
+and 3
+^~~ unexpected 'and', ignoring it
+
+1
+or 2
+^~ unexpected 'or', ignoring it
+or 3
+^~ unexpected 'or', ignoring it
+
+1
+and foo
+^~~ unexpected 'and', ignoring it
+
+2
+or foo
+^~ unexpected 'or', ignoring it
+
diff --git a/test/prism/errors/3.3-3.4/private_endless_method.txt b/test/prism/errors/3.3-3.4/private_endless_method.txt
new file mode 100644
index 0000000000..8aae5e0cd3
--- /dev/null
+++ b/test/prism/errors/3.3-3.4/private_endless_method.txt
@@ -0,0 +1,3 @@
+private def foo = puts "Hello"
+ ^ unexpected string literal, expecting end-of-input
+
diff --git a/test/prism/errors/do_not_allow_trailing_commas_in_method_parameters.txt b/test/prism/errors/3.3-4.0/do_not_allow_trailing_commas_in_method_parameters.txt
index c0fec0c704..c0fec0c704 100644
--- a/test/prism/errors/do_not_allow_trailing_commas_in_method_parameters.txt
+++ b/test/prism/errors/3.3-4.0/do_not_allow_trailing_commas_in_method_parameters.txt
diff --git a/test/prism/errors/3.3-4.0/noblock.txt b/test/prism/errors/3.3-4.0/noblock.txt
new file mode 100644
index 0000000000..07939041bb
--- /dev/null
+++ b/test/prism/errors/3.3-4.0/noblock.txt
@@ -0,0 +1,6 @@
+def foo(&nil)
+ ^~~ unexpected 'nil'; expected a `)` to close the parameters
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+end
+
diff --git a/test/prism/errors/3.3-4.0/singleton_method_with_void_value.txt b/test/prism/errors/3.3-4.0/singleton_method_with_void_value.txt
new file mode 100644
index 0000000000..2954f7ea48
--- /dev/null
+++ b/test/prism/errors/3.3-4.0/singleton_method_with_void_value.txt
@@ -0,0 +1,3 @@
+def ((return; 1)).bar; end
+ ^ cannot define singleton method for literals
+
diff --git a/test/prism/errors/3.4-4.0/void_value.txt b/test/prism/errors/3.4-4.0/void_value.txt
new file mode 100644
index 0000000000..c03139bb05
--- /dev/null
+++ b/test/prism/errors/3.4-4.0/void_value.txt
@@ -0,0 +1,18 @@
+x = begin
+ return
+ ^~~~~~ unexpected void value expression
+rescue
+ return
+else
+ return
+end
+
+x = begin
+ return
+rescue
+ "OK"
+else
+ return
+ ^~~~~~ unexpected void value expression
+end
+
diff --git a/test/prism/errors/block_args_in_array_assignment.txt b/test/prism/errors/3.4/block_args_in_array_assignment.txt
index 71dca8452b..71dca8452b 100644
--- a/test/prism/errors/block_args_in_array_assignment.txt
+++ b/test/prism/errors/3.4/block_args_in_array_assignment.txt
diff --git a/test/prism/errors/dont_allow_return_inside_sclass_body.txt b/test/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt
index c29fe01728..c29fe01728 100644
--- a/test/prism/errors/dont_allow_return_inside_sclass_body.txt
+++ b/test/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt
diff --git a/test/prism/errors/it_with_ordinary_parameter.txt b/test/prism/errors/3.4/it_with_ordinary_parameter.txt
index ff9c4276ca..ff9c4276ca 100644
--- a/test/prism/errors/it_with_ordinary_parameter.txt
+++ b/test/prism/errors/3.4/it_with_ordinary_parameter.txt
diff --git a/test/prism/errors/keyword_args_in_array_assignment.txt b/test/prism/errors/3.4/keyword_args_in_array_assignment.txt
index e379ec0ef4..e379ec0ef4 100644
--- a/test/prism/errors/keyword_args_in_array_assignment.txt
+++ b/test/prism/errors/3.4/keyword_args_in_array_assignment.txt
diff --git a/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt b/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt
new file mode 100644
index 0000000000..b3e06f4154
--- /dev/null
+++ b/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt
@@ -0,0 +1,6 @@
+def foo(a,b,...,);end
+ ^ unexpected `,` in parameters
+
+def foo(a,b,&block,);end
+ ^ unexpected `,` in parameters
+
diff --git a/test/prism/errors/4.1/end_block_exit.txt b/test/prism/errors/4.1/end_block_exit.txt
new file mode 100644
index 0000000000..a4a1e9bc2c
--- /dev/null
+++ b/test/prism/errors/4.1/end_block_exit.txt
@@ -0,0 +1,10 @@
+END {
+ break
+ ^~~~~ Invalid break
+}
+
+END {
+ next
+ ^~~~ Invalid next
+}
+
diff --git a/test/prism/errors/4.1/multiple_blocks.txt b/test/prism/errors/4.1/multiple_blocks.txt
new file mode 100644
index 0000000000..7e8433cf82
--- /dev/null
+++ b/test/prism/errors/4.1/multiple_blocks.txt
@@ -0,0 +1,12 @@
+def foo(&nil, &nil); end
+ ^ unexpected parameter order
+ ^~~~ multiple block parameters; only one block is allowed
+
+def foo(&foo, &nil); end
+ ^ unexpected parameter order
+ ^~~~ multiple block parameters; only one block is allowed
+
+def foo(&nil, &foo); end
+ ^ unexpected parameter order
+ ^~~~ multiple block parameters; only one block is allowed
+
diff --git a/test/prism/errors/4.1/singleton_method_with_void_value.txt b/test/prism/errors/4.1/singleton_method_with_void_value.txt
new file mode 100644
index 0000000000..bc6cf9c602
--- /dev/null
+++ b/test/prism/errors/4.1/singleton_method_with_void_value.txt
@@ -0,0 +1,4 @@
+def ((return; 1)).bar; end
+ ^~~~~~ unexpected void value expression
+ ^ cannot define singleton method for literals
+
diff --git a/test/prism/errors/4.1/void_value.txt b/test/prism/errors/4.1/void_value.txt
new file mode 100644
index 0000000000..a27ffd763a
--- /dev/null
+++ b/test/prism/errors/4.1/void_value.txt
@@ -0,0 +1,44 @@
+x = begin
+ return
+rescue
+ return
+else
+ return
+ ^~~~~~ unexpected void value expression
+end
+
+x = begin
+ ignored_because_else_branch
+rescue
+ return
+else
+ return
+ ^~~~~~ unexpected void value expression
+end
+
+x = case
+ when 1 then return
+ ^~~~~~ unexpected void value expression
+ else return
+end
+
+x = case 1
+ in 2 then return
+ ^~~~~~ unexpected void value expression
+ else return
+end
+
+x = begin
+ return
+ ^~~~~~ unexpected void value expression
+ "NG"
+end
+
+x = if rand < 0.5
+ return
+ ^~~~~~ unexpected void value expression
+ "NG"
+else
+ return
+end
+
diff --git a/test/prism/errors/block_args_with_endless_def.txt b/test/prism/errors/block_args_with_endless_def.txt
new file mode 100644
index 0000000000..a7242160d2
--- /dev/null
+++ b/test/prism/errors/block_args_with_endless_def.txt
@@ -0,0 +1,5 @@
+p do |a = def f = 1; b| end
+ ^~~~~~~ unexpected endless method definition; expected a default value for a parameter
+p do |a = def f = 1| 2; b|c end
+ ^~~~~~~ unexpected endless method definition; expected a default value for a parameter
+
diff --git a/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt b/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt
index 16af8200ec..1184b38ce8 100644
--- a/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt
+++ b/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt
@@ -1,5 +1,5 @@
x.each { x end
^~~ unexpected 'end', expecting end-of-input
^~~ unexpected 'end', ignoring it
- ^ expected a block beginning with `{` to end with `}`
+ ^ expected a block beginning with `{` to end with `}`
diff --git a/test/prism/errors/block_pass_return_value.txt b/test/prism/errors/block_pass_return_value.txt
new file mode 100644
index 0000000000..c9d12281d9
--- /dev/null
+++ b/test/prism/errors/block_pass_return_value.txt
@@ -0,0 +1,33 @@
+return &b
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+
+return(&b)
+ ^ unexpected '&', ignoring it
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+ ^ expected a matching `)`
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+return a, &b
+ ^~ block argument should not be given
+
+return(a, &b)
+ ^~ unexpected write target
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+ ^ expected a matching `)`
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+tap { break a, &b }
+ ^~ block argument should not be given
+
+tap { next a, &b }
+ ^~ block argument should not be given
+
diff --git a/test/prism/errors/command_call_in.txt b/test/prism/errors/command_call_in.txt
index 2fdcf09738..2b7286abc3 100644
--- a/test/prism/errors/command_call_in.txt
+++ b/test/prism/errors/command_call_in.txt
@@ -2,4 +2,5 @@ foo 1 in a
^~ unexpected 'in', expecting end-of-input
^~ unexpected 'in', ignoring it
a = foo 2 in b
+ ^~ unexpected 'in', expecting end-of-input
diff --git a/test/prism/errors/command_call_in_2.txt b/test/prism/errors/command_call_in_2.txt
new file mode 100644
index 0000000000..6676b1acba
--- /dev/null
+++ b/test/prism/errors/command_call_in_2.txt
@@ -0,0 +1,4 @@
+a.b x in pattern
+ ^~ unexpected 'in', expecting end-of-input
+ ^~ unexpected 'in', ignoring it
+
diff --git a/test/prism/errors/command_call_in_3.txt b/test/prism/errors/command_call_in_3.txt
new file mode 100644
index 0000000000..6fe026d7d3
--- /dev/null
+++ b/test/prism/errors/command_call_in_3.txt
@@ -0,0 +1,4 @@
+a.b x: in pattern
+ ^~ unexpected 'in', expecting end-of-input
+ ^~ unexpected 'in', ignoring it
+
diff --git a/test/prism/errors/command_call_in_4.txt b/test/prism/errors/command_call_in_4.txt
new file mode 100644
index 0000000000..045afe6498
--- /dev/null
+++ b/test/prism/errors/command_call_in_4.txt
@@ -0,0 +1,4 @@
+a.b &x in pattern
+ ^~ unexpected 'in', expecting end-of-input
+ ^~ unexpected 'in', ignoring it
+
diff --git a/test/prism/errors/command_call_in_5.txt b/test/prism/errors/command_call_in_5.txt
new file mode 100644
index 0000000000..be07287f81
--- /dev/null
+++ b/test/prism/errors/command_call_in_5.txt
@@ -0,0 +1,4 @@
+a.b *x => pattern
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+
diff --git a/test/prism/errors/command_call_in_6.txt b/test/prism/errors/command_call_in_6.txt
new file mode 100644
index 0000000000..470f323872
--- /dev/null
+++ b/test/prism/errors/command_call_in_6.txt
@@ -0,0 +1,4 @@
+a.b x: => pattern
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+
diff --git a/test/prism/errors/command_call_in_7.txt b/test/prism/errors/command_call_in_7.txt
new file mode 100644
index 0000000000..a8bea912b5
--- /dev/null
+++ b/test/prism/errors/command_call_in_7.txt
@@ -0,0 +1,4 @@
+a.b &x => pattern
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+
diff --git a/test/prism/errors/command_call_value_and.txt b/test/prism/errors/command_call_value_and.txt
new file mode 100644
index 0000000000..a131aa5530
--- /dev/null
+++ b/test/prism/errors/command_call_value_and.txt
@@ -0,0 +1,3 @@
+a = b c and 1
+ ^~~ unexpected 'and', expecting end-of-input
+
diff --git a/test/prism/errors/command_call_value_or.txt b/test/prism/errors/command_call_value_or.txt
new file mode 100644
index 0000000000..cc75714166
--- /dev/null
+++ b/test/prism/errors/command_call_value_or.txt
@@ -0,0 +1,3 @@
+a = b c or 1
+ ^~ unexpected 'or', expecting end-of-input
+
diff --git a/test/prism/errors/command_calls.txt b/test/prism/errors/command_calls.txt
index 19812a1d0a..6601e5fbbc 100644
--- a/test/prism/errors/command_calls.txt
+++ b/test/prism/errors/command_calls.txt
@@ -1,3 +1,10 @@
[a b]
^ unexpected local variable or method; expected a `,` separator for the array elements
+
+[
+ a b do
+ ^ unexpected local variable or method; expected a `,` separator for the array elements
+ end,
+]
+
diff --git a/test/prism/errors/command_calls_2.txt b/test/prism/errors/command_calls_2.txt
index b0983c015b..13e10f7ebf 100644
--- a/test/prism/errors/command_calls_2.txt
+++ b/test/prism/errors/command_calls_2.txt
@@ -1,5 +1,5 @@
{a: b c}
- ^ expected a `}` to close the hash literal
+^ expected a `}` to close the hash literal
^ unexpected local variable or method, expecting end-of-input
^ unexpected '}', expecting end-of-input
^ unexpected '}', ignoring it
diff --git a/test/prism/errors/command_calls_24.txt b/test/prism/errors/command_calls_24.txt
index 3046b36dc1..27a32ea3bf 100644
--- a/test/prism/errors/command_calls_24.txt
+++ b/test/prism/errors/command_calls_24.txt
@@ -1,5 +1,5 @@
->a=b c{}
^ expected a `do` keyword or a `{` to open the lambda block
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a lambda block beginning with `do` to end with `end`
+^~ expected a lambda block beginning with `do` to end with `end`
diff --git a/test/prism/errors/command_calls_25.txt b/test/prism/errors/command_calls_25.txt
index 5fddd90fdd..cf04508f87 100644
--- a/test/prism/errors/command_calls_25.txt
+++ b/test/prism/errors/command_calls_25.txt
@@ -4,5 +4,5 @@
^ unexpected ')', expecting end-of-input
^ unexpected ')', ignoring it
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a lambda block beginning with `do` to end with `end`
+^~ expected a lambda block beginning with `do` to end with `end`
diff --git a/test/prism/errors/command_calls_31.txt b/test/prism/errors/command_calls_31.txt
new file mode 100644
index 0000000000..e662b25444
--- /dev/null
+++ b/test/prism/errors/command_calls_31.txt
@@ -0,0 +1,17 @@
+true && not true
+ ^~~~ expected a `(` after `not`
+ ^~~~ unexpected 'true', expecting end-of-input
+
+true || not true
+ ^~~~ expected a `(` after `not`
+ ^~~~ unexpected 'true', expecting end-of-input
+
+true && not (true)
+ ^ expected a `(` immediately after `not`
+ ^ unexpected '(', expecting end-of-input
+
+true && not
+true
+^~~~ expected a `(` after `not`
+^~~~ unexpected 'true', expecting end-of-input
+
diff --git a/test/prism/errors/command_calls_32.txt b/test/prism/errors/command_calls_32.txt
new file mode 100644
index 0000000000..14488ca335
--- /dev/null
+++ b/test/prism/errors/command_calls_32.txt
@@ -0,0 +1,19 @@
+foo && return bar
+ ^~~ unexpected local variable or method, expecting end-of-input
+
+tap { foo && break bar }
+ ^~~ unexpected local variable or method, expecting end-of-input
+
+tap { foo && next bar }
+ ^~~ unexpected local variable or method, expecting end-of-input
+
+foo && return()
+ ^ unexpected '(', expecting end-of-input
+
+foo && return(bar)
+ ^ unexpected '(', expecting end-of-input
+
+foo && return(bar, baz)
+ ^~~~~~~~~~ unexpected write target
+ ^ unexpected '(', expecting end-of-input
+
diff --git a/test/prism/errors/command_calls_33.txt b/test/prism/errors/command_calls_33.txt
new file mode 100644
index 0000000000..13e3b35c9e
--- /dev/null
+++ b/test/prism/errors/command_calls_33.txt
@@ -0,0 +1,6 @@
+1 if foo = bar baz
+ ^~~ unexpected local variable or method, expecting end-of-input
+
+1 and foo = bar baz
+ ^~~ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/command_calls_34.txt b/test/prism/errors/command_calls_34.txt
new file mode 100644
index 0000000000..bc0ea5e81c
--- /dev/null
+++ b/test/prism/errors/command_calls_34.txt
@@ -0,0 +1,31 @@
+foo(bar 1 do end, 2)
+ ^~ unexpected 'do'; expected a `)` to close the arguments
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', ignoring it
+ ^ unexpected ',', ignoring it
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+foo(bar 1 do end,)
+ ^~ unexpected 'do'; expected a `)` to close the arguments
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', ignoring it
+ ^ unexpected ',', ignoring it
+ ^ unexpected ')', ignoring it
+
+foo(1, bar 2 do end)
+ ^ unexpected integer; expected a `)` to close the arguments
+ ^ unexpected integer, expecting end-of-input
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', ignoring it
+ ^ unexpected ')', ignoring it
+
+foo(1, bar 2)
+ ^ unexpected integer; expected a `)` to close the arguments
+ ^ unexpected integer, expecting end-of-input
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
diff --git a/test/prism/errors/command_calls_35.txt b/test/prism/errors/command_calls_35.txt
new file mode 100644
index 0000000000..bd72d1be56
--- /dev/null
+++ b/test/prism/errors/command_calls_35.txt
@@ -0,0 +1,50 @@
+p(p a, x: b => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a, x: => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a, &block => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a do end => value)
+ ^~ unexpected 'do'; expected a `)` to close the arguments
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', ignoring it
+ ^~ unexpected '=>', ignoring it
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a, *args => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a, **kwargs => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p p 1, &block => 2, &block
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+ ^ unexpected ',', expecting end-of-input
+ ^ unexpected ',', ignoring it
+ ^ unexpected '&', ignoring it
+
+p p p 1 => 2 => 3 => 4
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+
+p[p a, x: b => value]
+ ^ expected a matching `]`
+ ^ unexpected ']', expecting end-of-input
+ ^ unexpected ']', ignoring it
+
diff --git a/test/prism/errors/def_endless_do.txt b/test/prism/errors/def_endless_do.txt
new file mode 100644
index 0000000000..d66b7086da
--- /dev/null
+++ b/test/prism/errors/def_endless_do.txt
@@ -0,0 +1,6 @@
+def a = a b do 1 end
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', expecting end-of-input
+ ^~~ unexpected 'end', ignoring it
+
diff --git a/test/prism/errors/def_with_optional_splat.txt b/test/prism/errors/def_with_optional_splat.txt
new file mode 100644
index 0000000000..74a833ceec
--- /dev/null
+++ b/test/prism/errors/def_with_optional_splat.txt
@@ -0,0 +1,6 @@
+def foo(*bar = nil); end
+ ^ unexpected '='; expected a `)` to close the parameters
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+ ^~~ unexpected 'end', ignoring it
+
diff --git a/test/prism/errors/defined_empty.txt b/test/prism/errors/defined_empty.txt
new file mode 100644
index 0000000000..4d7ea76413
--- /dev/null
+++ b/test/prism/errors/defined_empty.txt
@@ -0,0 +1,3 @@
+defined?()
+ ^ expected an expression after `defined?`
+
diff --git a/test/prism/errors/destroy_call_operator_write_arguments.txt b/test/prism/errors/destroy_call_operator_write_arguments.txt
new file mode 100644
index 0000000000..b6933d61d1
--- /dev/null
+++ b/test/prism/errors/destroy_call_operator_write_arguments.txt
@@ -0,0 +1,11 @@
+t next&&do end&=
+ ^~ unexpected 'do'; expected an expression after the operator
+ ^~~~ unexpected void value expression
+ ^~~~ unexpected void value expression
+ ^~ unexpected '&=', expecting end-of-input
+ ^~ unexpected '&=', ignoring it
+ ^~~~ Invalid next
+''while=
+ ^~~~~ expected a predicate expression for the `while` statement
+ ^ unexpected '='; target cannot be written
+
diff --git a/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt b/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt
index df49557617..639dec3af2 100644
--- a/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt
+++ b/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt
@@ -1,3 +1,13 @@
a {|...|}
- ^~~ unexpected ... when the parent method is not forwarding
+ ^~~ unexpected ... in block argument
+
+def foo(...)
+ a {|...|}
+ ^~~ unexpected ... in block argument
+end
+
+def foo
+ a {|...|}
+ ^~~ unexpected ... in block argument
+end
diff --git a/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt b/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt
index c2405a5c66..03e17683e4 100644
--- a/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt
+++ b/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt
@@ -1,3 +1,13 @@
->(...) {}
- ^~~ unexpected ... when the parent method is not forwarding
+ ^~~ unexpected ... in lambda argument
+
+def foo(...)
+ ->(...) {}
+ ^~~ unexpected ... in lambda argument
+end
+
+def foo
+ ->(...) {}
+ ^~~ unexpected ... in lambda argument
+end
diff --git a/test/prism/errors/endless_method_command_call.txt b/test/prism/errors/endless_method_command_call.txt
new file mode 100644
index 0000000000..e6a328c294
--- /dev/null
+++ b/test/prism/errors/endless_method_command_call.txt
@@ -0,0 +1,3 @@
+private :m, def hello = puts "Hello"
+ ^ unexpected string literal, expecting end-of-input
+
diff --git a/test/prism/errors/endless_method_command_call_parameters.txt b/test/prism/errors/endless_method_command_call_parameters.txt
new file mode 100644
index 0000000000..5dc92ce7f9
--- /dev/null
+++ b/test/prism/errors/endless_method_command_call_parameters.txt
@@ -0,0 +1,27 @@
+def f x: = 1
+ ^ could not parse the endless method parameters
+
+def f ... = 1
+ ^ could not parse the endless method parameters
+
+def f * = 1
+ ^ could not parse the endless method parameters
+
+def f ** = 1
+ ^ could not parse the endless method parameters
+
+def f & = 1
+ ^ could not parse the endless method parameters
+
+def f *a = 1
+ ^ could not parse the endless method parameters
+
+def f **a = 1
+ ^ could not parse the endless method parameters
+
+def f &a = 1
+ ^ could not parse the endless method parameters
+
+def f a, (b) = 1
+ ^ could not parse the endless method parameters
+
diff --git a/test/prism/errors/heredoc_percent_q_newline_delimiter.txt b/test/prism/errors/heredoc_percent_q_newline_delimiter.txt
new file mode 100644
index 0000000000..73664c071f
--- /dev/null
+++ b/test/prism/errors/heredoc_percent_q_newline_delimiter.txt
@@ -0,0 +1,11 @@
+%q
+#{<<B}
+B
+^ unexpected constant, expecting end-of-input
+
+<<A; %q
+A
+#{<<B}
+B
+^ unexpected constant, expecting end-of-input
+
diff --git a/test/prism/errors/heredoc_unterminated.txt b/test/prism/errors/heredoc_unterminated.txt
index 3c6aeaeb81..56bd162998 100644
--- a/test/prism/errors/heredoc_unterminated.txt
+++ b/test/prism/errors/heredoc_unterminated.txt
@@ -3,7 +3,7 @@ a=>{<<b
^~~ unexpected heredoc beginning; expected a key in the hash pattern
^ unterminated heredoc; can't find string "b" anywhere before EOF
^~~ expected a label as the key in the hash pattern
- ^ expected a `}` to close the pattern expression
+ ^ expected a `}` to close the pattern expression
^ unexpected heredoc ending, expecting end-of-input
^ unexpected heredoc ending, ignoring it
diff --git a/test/prism/errors/infix_after_label.txt b/test/prism/errors/infix_after_label.txt
index c3bcfaeceb..f02a29470f 100644
--- a/test/prism/errors/infix_after_label.txt
+++ b/test/prism/errors/infix_after_label.txt
@@ -1,6 +1,6 @@
{ 'a':.upcase => 1 }
^ unexpected '.'; expected a value in the hash literal
- ^ expected a `}` to close the hash literal
+^ expected a `}` to close the hash literal
^ unexpected '}', expecting end-of-input
^ unexpected '}', ignoring it
diff --git a/test/prism/errors/interpolated_symbol_pattern_hash_key.txt b/test/prism/errors/interpolated_symbol_pattern_hash_key.txt
new file mode 100644
index 0000000000..b4532439ff
--- /dev/null
+++ b/test/prism/errors/interpolated_symbol_pattern_hash_key.txt
@@ -0,0 +1,3 @@
+case foo; in { "bar#{1}": 1 }; end
+ ^~~~~~~~~~ symbol literal with interpolation is not allowed
+
diff --git a/test/prism/errors/label_in_interpolated_string.txt b/test/prism/errors/label_in_interpolated_string.txt
new file mode 100644
index 0000000000..29af5310a1
--- /dev/null
+++ b/test/prism/errors/label_in_interpolated_string.txt
@@ -0,0 +1,14 @@
+case in el""Q
+^~~~ expected a predicate for a case matching statement
+ ^ expected a delimiter after the patterns of an `in` clause
+ ^ unexpected constant, expecting end-of-input
+^~~~ expected an `end` to close the `case` statement
+ !"""#{in el"":Q
+ ^~ unexpected 'in', assuming it is closing the parent 'in' clause
+ ^ expected a `}` to close the embedded expression
+ ^~ cannot parse the string part
+ ^~ cannot parse the string part
+ ^ cannot parse the string part
+ ^~~~~~~~~~~ unexpected label
+ ^~~~~~~~~~~ expected a string for concatenation
+
diff --git a/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt b/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt
index fead8aaf23..f599dc476b 100644
--- a/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt
+++ b/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt
@@ -1,3 +1,4 @@
'a' rescue 2 in 3.upcase
^ unexpected '.', expecting end-of-input
+ ^ unexpected '.', ignoring it
diff --git a/test/prism/errors/match_predicate_after_rescue_with_opreator.txt b/test/prism/errors/match_predicate_after_rescue_with_opreator.txt
index b2363a544d..44a4ba8488 100644
--- a/test/prism/errors/match_predicate_after_rescue_with_opreator.txt
+++ b/test/prism/errors/match_predicate_after_rescue_with_opreator.txt
@@ -1,3 +1,4 @@
1 rescue 2 in 3 << 4
^~ unexpected <<, expecting end-of-input
+ ^~ unexpected <<, ignoring it
diff --git a/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt b/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt
index d72d72ce60..abcfaf094d 100644
--- a/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt
+++ b/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt
@@ -1,3 +1,4 @@
1 rescue 2 => 3.inspect
^ unexpected '.', expecting end-of-input
+ ^ unexpected '.', ignoring it
diff --git a/test/prism/errors/match_required_after_rescue_with_opreator.txt b/test/prism/errors/match_required_after_rescue_with_opreator.txt
index 903e2ccc8e..5e6387ca4d 100644
--- a/test/prism/errors/match_required_after_rescue_with_opreator.txt
+++ b/test/prism/errors/match_required_after_rescue_with_opreator.txt
@@ -1,3 +1,4 @@
1 rescue 2 => 3 ** 4
^~ unexpected '**', expecting end-of-input
+ ^~ unexpected '**', ignoring it
diff --git a/test/prism/errors/modifier_conditional_in_predicate.txt b/test/prism/errors/modifier_conditional_in_predicate.txt
new file mode 100644
index 0000000000..5b89ee4a26
--- /dev/null
+++ b/test/prism/errors/modifier_conditional_in_predicate.txt
@@ -0,0 +1,12 @@
+if a if b then end
+ ^~ expected `then` or `;` or '\n'
+ ^~ unexpected 'if', ignoring it
+ ^~~~ unexpected 'then', expecting end-of-input
+ ^~~~ unexpected 'then', ignoring it
+
+unless a unless b then end
+ ^~~~~~ expected `then` or `;` or '\n'
+ ^~~~~~ unexpected 'unless', ignoring it
+ ^~~~ unexpected 'then', expecting end-of-input
+ ^~~~ unexpected 'then', ignoring it
+
diff --git a/test/prism/errors/not_without_parens_assignment.txt b/test/prism/errors/not_without_parens_assignment.txt
new file mode 100644
index 0000000000..32d58efedf
--- /dev/null
+++ b/test/prism/errors/not_without_parens_assignment.txt
@@ -0,0 +1,4 @@
+x = not y
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/not_without_parens_call.txt b/test/prism/errors/not_without_parens_call.txt
new file mode 100644
index 0000000000..a778193400
--- /dev/null
+++ b/test/prism/errors/not_without_parens_call.txt
@@ -0,0 +1,7 @@
+foo(not y)
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method; expected a `)` to close the arguments
+ ^ unexpected local variable or method, expecting end-of-input
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
diff --git a/test/prism/errors/not_without_parens_command.txt b/test/prism/errors/not_without_parens_command.txt
new file mode 100644
index 0000000000..957a06f8f1
--- /dev/null
+++ b/test/prism/errors/not_without_parens_command.txt
@@ -0,0 +1,4 @@
+foo not y
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/not_without_parens_command_call.txt b/test/prism/errors/not_without_parens_command_call.txt
new file mode 100644
index 0000000000..564833c7de
--- /dev/null
+++ b/test/prism/errors/not_without_parens_command_call.txt
@@ -0,0 +1,4 @@
+a.b not y
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/not_without_parens_return.txt b/test/prism/errors/not_without_parens_return.txt
new file mode 100644
index 0000000000..1c7edb6ff1
--- /dev/null
+++ b/test/prism/errors/not_without_parens_return.txt
@@ -0,0 +1,4 @@
+return not y
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/pattern-capture-in-alt-array.txt b/test/prism/errors/pattern-capture-in-alt-array.txt
new file mode 100644
index 0000000000..5cb59fa328
--- /dev/null
+++ b/test/prism/errors/pattern-capture-in-alt-array.txt
@@ -0,0 +1,4 @@
+1 => [a, b] | 2
+ ^ variable capture in alternative pattern
+ ^ variable capture in alternative pattern
+
diff --git a/test/prism/errors/pattern-capture-in-alt-hash.txt b/test/prism/errors/pattern-capture-in-alt-hash.txt
new file mode 100644
index 0000000000..150b3baecc
--- /dev/null
+++ b/test/prism/errors/pattern-capture-in-alt-hash.txt
@@ -0,0 +1,3 @@
+1 => { a: b } | 2
+ ^ variable capture in alternative pattern
+
diff --git a/test/prism/errors/pattern-capture-in-alt-name.txt b/test/prism/errors/pattern-capture-in-alt-name.txt
new file mode 100644
index 0000000000..cbf2bae85f
--- /dev/null
+++ b/test/prism/errors/pattern-capture-in-alt-name.txt
@@ -0,0 +1,3 @@
+1 => (2 => b) | 2
+ ^ variable capture in alternative pattern
+
diff --git a/test/prism/errors/pattern-capture-in-alt-top.txt b/test/prism/errors/pattern-capture-in-alt-top.txt
new file mode 100644
index 0000000000..bdf3a7f637
--- /dev/null
+++ b/test/prism/errors/pattern-capture-in-alt-top.txt
@@ -0,0 +1,4 @@
+1 => a | b
+ ^ variable capture in alternative pattern
+ ^ variable capture in alternative pattern
+
diff --git a/test/prism/errors/pattern_arithmetic_expressions.txt b/test/prism/errors/pattern_arithmetic_expressions.txt
new file mode 100644
index 0000000000..cfb3650531
--- /dev/null
+++ b/test/prism/errors/pattern_arithmetic_expressions.txt
@@ -0,0 +1,3 @@
+case 1; in -1**2; end
+ ^~~~~ expected a pattern expression after the `in` keyword
+
diff --git a/test/prism/errors/pattern_match_implicit_rest.txt b/test/prism/errors/pattern_match_implicit_rest.txt
new file mode 100644
index 0000000000..8602c0add0
--- /dev/null
+++ b/test/prism/errors/pattern_match_implicit_rest.txt
@@ -0,0 +1,3 @@
+a=>b, *,
+ ^ expected a pattern expression after `,`
+
diff --git a/test/prism/errors/pattern_string_key.txt b/test/prism/errors/pattern_string_key.txt
new file mode 100644
index 0000000000..41bc1fa57b
--- /dev/null
+++ b/test/prism/errors/pattern_string_key.txt
@@ -0,0 +1,8 @@
+case:a
+^~~~ expected an `end` to close the `case` statement
+in b:"","#{}"
+ ^~~~~ expected a label after the `,` in the hash pattern
+ ^ expected a pattern expression after the key
+ ^ expected a delimiter after the patterns of an `in` clause
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+
diff --git a/test/prism/errors/rescue_pattern.txt b/test/prism/errors/rescue_pattern.txt
new file mode 100644
index 0000000000..c85feb27bd
--- /dev/null
+++ b/test/prism/errors/rescue_pattern.txt
@@ -0,0 +1,4 @@
+a rescue b => c in d
+ ^~ unexpected 'in', expecting end-of-input
+ ^~ unexpected 'in', ignoring it
+
diff --git a/test/prism/errors/shadow_args_in_lambda.txt b/test/prism/errors/shadow_args_in_lambda.txt
index 2399a0ebd5..7fc78d7d8f 100644
--- a/test/prism/errors/shadow_args_in_lambda.txt
+++ b/test/prism/errors/shadow_args_in_lambda.txt
@@ -1,5 +1,5 @@
->a;b{}
^ expected a `do` keyword or a `{` to open the lambda block
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a lambda block beginning with `do` to end with `end`
+^~ expected a lambda block beginning with `do` to end with `end`
diff --git a/test/prism/errors/singleton_method_for_literals.txt b/test/prism/errors/singleton_method_for_literals.txt
index 6247b4f025..ae850fca29 100644
--- a/test/prism/errors/singleton_method_for_literals.txt
+++ b/test/prism/errors/singleton_method_for_literals.txt
@@ -2,8 +2,6 @@ def (1).g; end
^ cannot define singleton method for literals
def ((a; 1)).foo; end
^ cannot define singleton method for literals
-def ((return; 1)).bar; end
- ^ cannot define singleton method for literals
def (((1))).foo; end
^ cannot define singleton method for literals
def (__FILE__).foo; end
diff --git a/test/prism/errors/unterminated_begin.txt b/test/prism/errors/unterminated_begin.txt
new file mode 100644
index 0000000000..2733f830c9
--- /dev/null
+++ b/test/prism/errors/unterminated_begin.txt
@@ -0,0 +1,4 @@
+begin
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~~~ expected an `end` to close the `begin` statement
+
diff --git a/test/prism/errors/unterminated_begin_upcase.txt b/test/prism/errors/unterminated_begin_upcase.txt
new file mode 100644
index 0000000000..5512f2089e
--- /dev/null
+++ b/test/prism/errors/unterminated_begin_upcase.txt
@@ -0,0 +1,4 @@
+BEGIN {
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^ expected a `}` to close the `BEGIN` statement
+
diff --git a/test/prism/errors/unterminated_block.txt b/test/prism/errors/unterminated_block.txt
index 8cc772db16..db6a4aa56c 100644
--- a/test/prism/errors/unterminated_block.txt
+++ b/test/prism/errors/unterminated_block.txt
@@ -1,4 +1,4 @@
foo {
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a block beginning with `{` to end with `}`
+ ^ expected a block beginning with `{` to end with `}`
diff --git a/test/prism/errors/unterminated_block_do_end.txt b/test/prism/errors/unterminated_block_do_end.txt
new file mode 100644
index 0000000000..0b7c64965f
--- /dev/null
+++ b/test/prism/errors/unterminated_block_do_end.txt
@@ -0,0 +1,4 @@
+foo do
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^~ expected a block beginning with `do` to end with `end`
+
diff --git a/test/prism/errors/unterminated_class.txt b/test/prism/errors/unterminated_class.txt
new file mode 100644
index 0000000000..f47a3aa7df
--- /dev/null
+++ b/test/prism/errors/unterminated_class.txt
@@ -0,0 +1,4 @@
+class Foo
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~~~ expected an `end` to close the `class` statement
+
diff --git a/test/prism/errors/unterminated_def.txt b/test/prism/errors/unterminated_def.txt
new file mode 100644
index 0000000000..a6212e3a21
--- /dev/null
+++ b/test/prism/errors/unterminated_def.txt
@@ -0,0 +1,5 @@
+def foo
+ ^ expected a delimiter to close the parameters
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~ expected an `end` to close the `def` statement
+
diff --git a/test/prism/errors/unterminated_end_upcase.txt b/test/prism/errors/unterminated_end_upcase.txt
new file mode 100644
index 0000000000..ef01caa0ca
--- /dev/null
+++ b/test/prism/errors/unterminated_end_upcase.txt
@@ -0,0 +1,4 @@
+END {
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^ expected a `}` to close the `END` statement
+
diff --git a/test/prism/errors/unterminated_for.txt b/test/prism/errors/unterminated_for.txt
new file mode 100644
index 0000000000..75978a7cae
--- /dev/null
+++ b/test/prism/errors/unterminated_for.txt
@@ -0,0 +1,5 @@
+for x in y
+ ^ unexpected end-of-input; expected a 'do', newline, or ';' after the 'for' loop collection
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~ expected an `end` to close the `for` loop
+
diff --git a/test/prism/errors/unterminated_heredoc_and_embexpr.txt b/test/prism/errors/unterminated_heredoc_and_embexpr.txt
new file mode 100644
index 0000000000..bed7fcd24e
--- /dev/null
+++ b/test/prism/errors/unterminated_heredoc_and_embexpr.txt
@@ -0,0 +1,11 @@
+<<A+B
+ ^ unterminated heredoc; can't find string "A" anywhere before EOF
+ ^ unexpected '+', ignoring it
+ ^ unterminated heredoc; can't find string "A" anywhere before EOF
+#{C
+ ^ unexpected heredoc ending; expected an argument
+ ^ unexpected heredoc ending, expecting end-of-input
+ ^ unexpected heredoc ending, ignoring it
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^ expected a `}` to close the embedded expression
+
diff --git a/test/prism/errors/unterminated_heredoc_and_embexpr_2.txt b/test/prism/errors/unterminated_heredoc_and_embexpr_2.txt
new file mode 100644
index 0000000000..a03ff1d212
--- /dev/null
+++ b/test/prism/errors/unterminated_heredoc_and_embexpr_2.txt
@@ -0,0 +1,9 @@
+<<A+B
+ ^ unterminated heredoc; can't find string "A" anywhere before EOF
+#{C + "#{"}
+ ^ unterminated string meets end of file
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^ expected a `}` to close the embedded expression
+ ^ unterminated string; expected a closing delimiter for the interpolated string
+ ^ expected a `}` to close the embedded expression
+
diff --git a/test/prism/errors/unterminated_if.txt b/test/prism/errors/unterminated_if.txt
new file mode 100644
index 0000000000..1697931773
--- /dev/null
+++ b/test/prism/errors/unterminated_if.txt
@@ -0,0 +1,5 @@
+if true
+ ^ expected `then` or `;` or '\n'
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~ expected an `end` to close the conditional clause
+
diff --git a/test/prism/errors/unterminated_if_else.txt b/test/prism/errors/unterminated_if_else.txt
new file mode 100644
index 0000000000..db7828cce8
--- /dev/null
+++ b/test/prism/errors/unterminated_if_else.txt
@@ -0,0 +1,5 @@
+if true
+^~ expected an `end` to close the `else` clause
+else
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+
diff --git a/test/prism/errors/unterminated_lambda_brace.txt b/test/prism/errors/unterminated_lambda_brace.txt
new file mode 100644
index 0000000000..75474c7534
--- /dev/null
+++ b/test/prism/errors/unterminated_lambda_brace.txt
@@ -0,0 +1,4 @@
+-> {
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^ expected a lambda block beginning with `{` to end with `}`
+
diff --git a/test/prism/errors/unterminated_module.txt b/test/prism/errors/unterminated_module.txt
new file mode 100644
index 0000000000..4c50ba5f63
--- /dev/null
+++ b/test/prism/errors/unterminated_module.txt
@@ -0,0 +1,4 @@
+module Foo
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~~~~ expected an `end` to close the `module` statement
+
diff --git a/test/prism/errors/unterminated_pattern_bracket.txt b/test/prism/errors/unterminated_pattern_bracket.txt
new file mode 100644
index 0000000000..4f35cd84af
--- /dev/null
+++ b/test/prism/errors/unterminated_pattern_bracket.txt
@@ -0,0 +1,7 @@
+case x
+^~~~ expected an `end` to close the `case` statement
+in [1
+ ^ expected a `]` to close the pattern expression
+ ^ expected a delimiter after the patterns of an `in` clause
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+
diff --git a/test/prism/errors/unterminated_pattern_paren.txt b/test/prism/errors/unterminated_pattern_paren.txt
new file mode 100644
index 0000000000..426d614e61
--- /dev/null
+++ b/test/prism/errors/unterminated_pattern_paren.txt
@@ -0,0 +1,7 @@
+case x
+^~~~ expected an `end` to close the `case` statement
+in (1
+ ^ expected a `)` to close the pattern expression
+ ^ expected a delimiter after the patterns of an `in` clause
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+
diff --git a/test/prism/errors/unterminated_until.txt b/test/prism/errors/unterminated_until.txt
new file mode 100644
index 0000000000..42a0545200
--- /dev/null
+++ b/test/prism/errors/unterminated_until.txt
@@ -0,0 +1,5 @@
+until true
+ ^ expected a predicate expression for the `until` statement
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~~~ expected an `end` to close the `until` statement
+
diff --git a/test/prism/errors/void_value_expression_in_begin_statement.txt b/test/prism/errors/void_value_expression_in_begin_statement.txt
index aa8f1ded96..fb968a12e1 100644
--- a/test/prism/errors/void_value_expression_in_begin_statement.txt
+++ b/test/prism/errors/void_value_expression_in_begin_statement.txt
@@ -14,8 +14,6 @@ x = begin return ensure return end
^~~~~~ unexpected void value expression
x = begin return; rescue; return end
^~~~~~ unexpected void value expression
-x = begin return; rescue; return; else return end
- ^~~~~~ unexpected void value expression
x = begin; return; rescue; retry; end
^~~~~~ unexpected void value expression
diff --git a/test/prism/errors/while_endless_method.txt b/test/prism/errors/while_endless_method.txt
index 6f062d89d0..cdd7ba9aba 100644
--- a/test/prism/errors/while_endless_method.txt
+++ b/test/prism/errors/while_endless_method.txt
@@ -1,5 +1,5 @@
while def f = g do end
^ expected a predicate expression for the `while` statement
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected an `end` to close the `while` statement
+^~~~~ expected an `end` to close the `while` statement
diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb
index 62bbd8458b..9dd7fbe3fe 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -1,41 +1,24 @@
# frozen_string_literal: true
+return if RUBY_VERSION < "3.3.0"
+
require_relative "test_helper"
module Prism
class ErrorsTest < TestCase
base = File.expand_path("errors", __dir__)
- filepaths = Dir["*.txt", base: base]
-
- if RUBY_VERSION < "3.0"
- filepaths -= [
- "cannot_assign_to_a_reserved_numbered_parameter.txt",
- "writing_numbered_parameter.txt",
- "targeting_numbered_parameter.txt",
- "defining_numbered_parameter.txt",
- "defining_numbered_parameter_2.txt",
- "numbered_parameters_in_block_arguments.txt",
- "numbered_and_write.txt",
- "numbered_or_write.txt",
- "numbered_operator_write.txt"
- ]
- end
+ filepaths = Dir[ENV.fetch("FOCUS", "**/*.txt"), base: base]
- if RUBY_VERSION < "3.4"
- filepaths -= [
- "it_with_ordinary_parameter.txt",
- "block_args_in_array_assignment.txt",
- "keyword_args_in_array_assignment.txt"
- ]
- end
-
- if RUBY_VERSION < "3.4" || RUBY_RELEASE_DATE < "2024-07-24"
- filepaths -= ["dont_allow_return_inside_sclass_body.txt"]
- end
+ PARSE_Y_EXCLUDES = [
+ # https://bugs.ruby-lang.org/issues/20409
+ "#{base}/4.1/end_block_exit.txt"
+ ]
filepaths.each do |filepath|
- define_method(:"test_#{File.basename(filepath, ".txt")}") do
- assert_errors(File.join(base, filepath))
+ ruby_versions_for(filepath).each do |version|
+ define_method(:"test_#{version}_#{File.basename(filepath, ".txt")}") do
+ assert_errors(File.join(base, filepath), version)
+ end
end
end
@@ -67,52 +50,85 @@ module Prism
def test_unterminated_string_closing
statement = Prism.parse_statement("'hello")
assert_equal statement.unescaped, "hello"
- assert_empty statement.closing
+ assert_nil statement.closing
end
def test_unterminated_interpolated_string_closing
statement = Prism.parse_statement('"hello')
assert_equal statement.unescaped, "hello"
- assert_empty statement.closing
+ assert_nil statement.closing
end
def test_unterminated_empty_string_closing
statement = Prism.parse_statement('"')
assert_empty statement.unescaped
- assert_empty statement.closing
+ assert_nil statement.closing
end
- def test_invalid_message_name
- assert_equal :"", Prism.parse_statement("+.@foo,+=foo").write_name
+ def test_regexp_encoding_option_mismatch_error
+ # UTF-8 char with ASCII-8BIT modifier
+ result = Prism.parse('/Ȃ/n')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with EUC-JP modifier
+ result = Prism.parse('/Ȃ/e')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with Windows-31J modifier
+ result = Prism.parse('/Ȃ/s')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with UTF-8 modifier
+ result = Prism.parse('/Ȃ/u')
+ assert_empty result.errors
end
- def test_circular_parameters
- source = <<~RUBY
- def foo(bar = bar) = 42
- def foo(bar: bar) = 42
- proc { |foo = foo| }
- proc { |foo: foo| }
- RUBY
+ def test_incomplete_def_closing_loc
+ statement = Prism.parse_statement("def f; 123")
+ assert_nil(statement.end_keyword)
+ end
- source.each_line do |line|
- assert_predicate Prism.parse(line, version: "3.3.0"), :failure?
- assert_predicate Prism.parse(line), :success?
- end
+ def test_unclosed_interpolation
+ statement = Prism.parse_statement("\"\#{")
+ assert_equal('"', statement.opening)
+ assert_nil(statement.closing)
+
+ assert_equal(1, statement.parts.count)
+ assert_equal('#{', statement.parts[0].opening)
+ assert_equal("", statement.parts[0].closing)
+ assert_nil(statement.parts[0].statements)
+ end
+
+ def test_unclosed_heredoc_and_interpolation
+ statement = Prism.parse_statement("<<D\n\#{")
+ assert_equal("<<D", statement.opening)
+ assert_nil(statement.closing)
+
+ assert_equal(1, statement.parts.count)
+ assert_equal('#{', statement.parts[0].opening)
+ assert_equal("", statement.parts[0].closing)
+ assert_nil(statement.parts[0].statements)
end
private
- def assert_errors(filepath)
+ def assert_errors(filepath, version)
expected = File.read(filepath, binmode: true, external_encoding: Encoding::UTF_8)
source = expected.lines.grep_v(/^\s*\^/).join.gsub(/\n*\z/, "")
- refute_valid_syntax(source)
+ if CURRENT_MAJOR_MINOR == version && !PARSE_Y_EXCLUDES.include?(filepath)
+ refute_valid_syntax(source)
+ end
- result = Prism.parse(source)
+ result = Prism.parse(source, version: version)
errors = result.errors
refute_empty errors, "Expected errors in #{filepath}"
actual = result.errors_format
+ if expected != actual && ENV["UPDATE_SNAPSHOTS"]
+ File.write(filepath, actual)
+ end
+
assert_equal expected, actual, "Expected errors to match for #{filepath}"
end
end
diff --git a/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt b/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt
new file mode 100644
index 0000000000..6d6b052681
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt
@@ -0,0 +1 @@
+matrix[5, &block] = 8
diff --git a/test/prism/fixtures/it.txt b/test/prism/fixtures/3.3-3.3/it.txt
index 76deb68028..5410b01e71 100644
--- a/test/prism/fixtures/it.txt
+++ b/test/prism/fixtures/3.3-3.3/it.txt
@@ -1,3 +1,5 @@
x do
it
end
+
+-> { it }
diff --git a/test/prism/fixtures/it_indirect_writes.txt b/test/prism/fixtures/3.3-3.3/it_indirect_writes.txt
index bb87e9483e..bb87e9483e 100644
--- a/test/prism/fixtures/it_indirect_writes.txt
+++ b/test/prism/fixtures/3.3-3.3/it_indirect_writes.txt
diff --git a/test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt b/test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt
new file mode 100644
index 0000000000..2cceeb2a54
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt
@@ -0,0 +1 @@
+42.tap { p it; it = it; p it }
diff --git a/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt b/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt
new file mode 100644
index 0000000000..178b641e6b
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt
@@ -0,0 +1 @@
+proc { || it }
diff --git a/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt b/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt
new file mode 100644
index 0000000000..88016c2afe
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt
@@ -0,0 +1 @@
+matrix[5, axis: :y] = 8
diff --git a/test/prism/fixtures/3.3-3.3/return_in_sclass.txt b/test/prism/fixtures/3.3-3.3/return_in_sclass.txt
new file mode 100644
index 0000000000..f1fde5771a
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/return_in_sclass.txt
@@ -0,0 +1 @@
+class << A; return; end
diff --git a/test/prism/fixtures/3.3-4.0/end_block_exit.txt b/test/prism/fixtures/3.3-4.0/end_block_exit.txt
new file mode 100644
index 0000000000..8ebf0d6369
--- /dev/null
+++ b/test/prism/fixtures/3.3-4.0/end_block_exit.txt
@@ -0,0 +1,11 @@
+END {
+ return
+}
+
+END {
+ break
+}
+
+END {
+ next
+}
diff --git a/test/prism/fixtures/3.3-4.0/void_value.txt b/test/prism/fixtures/3.3-4.0/void_value.txt
new file mode 100644
index 0000000000..bfb8eff09c
--- /dev/null
+++ b/test/prism/fixtures/3.3-4.0/void_value.txt
@@ -0,0 +1,29 @@
+x = begin
+ foo
+rescue
+ return
+else
+ return
+end
+
+x = case
+ when 1 then return
+ else return
+end
+
+x = case 1
+ in 2 then return
+ else return
+end
+
+x = begin
+ return
+ "NG"
+end
+
+x = if rand < 0.5
+ return
+ "NG"
+else
+ return
+end
diff --git a/test/prism/fixtures/3.4/circular_parameters.txt b/test/prism/fixtures/3.4/circular_parameters.txt
new file mode 100644
index 0000000000..11537023ad
--- /dev/null
+++ b/test/prism/fixtures/3.4/circular_parameters.txt
@@ -0,0 +1,4 @@
+def foo(bar = bar) = 42
+def foo(bar: bar) = 42
+proc { |foo = foo| }
+proc { |foo: foo| }
diff --git a/test/prism/fixtures/3.4/it.txt b/test/prism/fixtures/3.4/it.txt
new file mode 100644
index 0000000000..5410b01e71
--- /dev/null
+++ b/test/prism/fixtures/3.4/it.txt
@@ -0,0 +1,5 @@
+x do
+ it
+end
+
+-> { it }
diff --git a/test/prism/fixtures/3.4/it_indirect_writes.txt b/test/prism/fixtures/3.4/it_indirect_writes.txt
new file mode 100644
index 0000000000..bb87e9483e
--- /dev/null
+++ b/test/prism/fixtures/3.4/it_indirect_writes.txt
@@ -0,0 +1,23 @@
+tap { it += 1 }
+
+tap { it ||= 1 }
+
+tap { it &&= 1 }
+
+tap { it; it += 1 }
+
+tap { it; it ||= 1 }
+
+tap { it; it &&= 1 }
+
+tap { it += 1; it }
+
+tap { it ||= 1; it }
+
+tap { it &&= 1; it }
+
+tap { it; it += 1; it }
+
+tap { it; it ||= 1; it }
+
+tap { it; it &&= 1; it }
diff --git a/test/prism/fixtures/3.4/it_read_and_assignment.txt b/test/prism/fixtures/3.4/it_read_and_assignment.txt
new file mode 100644
index 0000000000..2cceeb2a54
--- /dev/null
+++ b/test/prism/fixtures/3.4/it_read_and_assignment.txt
@@ -0,0 +1 @@
+42.tap { p it; it = it; p it }
diff --git a/test/prism/fixtures/4.0/endless_methods_command_call.txt b/test/prism/fixtures/4.0/endless_methods_command_call.txt
new file mode 100644
index 0000000000..146a6ee579
--- /dev/null
+++ b/test/prism/fixtures/4.0/endless_methods_command_call.txt
@@ -0,0 +1,11 @@
+private def foo = puts "Hello"
+private def foo = puts "Hello", "World"
+private def foo = puts "Hello" do expr end
+private def foo() = puts "Hello"
+private def foo(x) = puts x
+private def obj.foo = puts "Hello"
+private def obj.foo() = puts "Hello"
+private def obj.foo(x) = puts x
+
+private def foo = bar baz
+private def foo = bar baz do expr end
diff --git a/test/prism/fixtures/4.0/leading_logical.txt b/test/prism/fixtures/4.0/leading_logical.txt
new file mode 100644
index 0000000000..ee87e00d4f
--- /dev/null
+++ b/test/prism/fixtures/4.0/leading_logical.txt
@@ -0,0 +1,16 @@
+1
+&& 2
+&& 3
+
+1
+|| 2
+|| 3
+
+1
+and 2
+and 3
+
+1
+or 2
+or 3
+
diff --git a/test/prism/fixtures/4.1/noblock.txt b/test/prism/fixtures/4.1/noblock.txt
new file mode 100644
index 0000000000..2395393e22
--- /dev/null
+++ b/test/prism/fixtures/4.1/noblock.txt
@@ -0,0 +1,4 @@
+def foo(&nil)
+end
+
+-> (&nil) {}
diff --git a/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt b/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt
new file mode 100644
index 0000000000..ef1385d973
--- /dev/null
+++ b/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt
@@ -0,0 +1,15 @@
+def foo(a,b,c,);end
+
+def foo(a,b,*c,);end
+
+def foo(a,b,*,);end
+
+def foo(a,b,**c,);end
+
+def foo(a,b,**,);end
+
+def foo(
+ a,
+ b,
+ c,
+);end
diff --git a/test/prism/fixtures/4.1/void_value.txt b/test/prism/fixtures/4.1/void_value.txt
new file mode 100644
index 0000000000..915112d623
--- /dev/null
+++ b/test/prism/fixtures/4.1/void_value.txt
@@ -0,0 +1,7 @@
+x = begin
+ return
+rescue
+ "OK"
+else
+ return
+end
diff --git a/test/prism/fixtures/__END__.txt b/test/prism/fixtures/__END__.txt
new file mode 100644
index 0000000000..c0f4f28004
--- /dev/null
+++ b/test/prism/fixtures/__END__.txt
@@ -0,0 +1,3 @@
+foo
+__END__
+Available in DATA constant
diff --git a/test/prism/fixtures/and_or_with_suffix.txt b/test/prism/fixtures/and_or_with_suffix.txt
new file mode 100644
index 0000000000..59ee4d0b88
--- /dev/null
+++ b/test/prism/fixtures/and_or_with_suffix.txt
@@ -0,0 +1,17 @@
+foo
+and?
+
+foo
+or?
+
+foo
+and!
+
+foo
+or!
+
+foo
+andbar
+
+foo
+orbar
diff --git a/test/prism/fixtures/blocks.txt b/test/prism/fixtures/blocks.txt
index e33d95c150..51ec84950c 100644
--- a/test/prism/fixtures/blocks.txt
+++ b/test/prism/fixtures/blocks.txt
@@ -52,3 +52,11 @@ foo lambda { |
}
foo do |bar,| end
+
+foo bar baz, qux do end
+
+foo.bar baz do end
+
+foo.bar baz do end.qux quux do end
+
+foo bar, baz do |x| x end
diff --git a/test/prism/fixtures/bom_leading_space.txt b/test/prism/fixtures/bom_leading_space.txt
new file mode 100644
index 0000000000..48d3ee50ea
--- /dev/null
+++ b/test/prism/fixtures/bom_leading_space.txt
@@ -0,0 +1 @@
+ p (42)
diff --git a/test/prism/fixtures/bom_spaces.txt b/test/prism/fixtures/bom_spaces.txt
new file mode 100644
index 0000000000..c18ad4c21a
--- /dev/null
+++ b/test/prism/fixtures/bom_spaces.txt
@@ -0,0 +1 @@
+p ( 42 )
diff --git a/test/prism/fixtures/break.txt b/test/prism/fixtures/break.txt
index 5532322c5c..d823f866df 100644
--- a/test/prism/fixtures/break.txt
+++ b/test/prism/fixtures/break.txt
@@ -20,6 +20,10 @@ tap { break() }
tap { break(1) }
+tap { (break 1) }
+
+tap { foo && (break 1) }
+
foo { break 42 } == 42
foo { |a| break } == 42
diff --git a/test/prism/fixtures/case_in_hash_key.txt b/test/prism/fixtures/case_in_hash_key.txt
new file mode 100644
index 0000000000..75ac8a846f
--- /dev/null
+++ b/test/prism/fixtures/case_in_hash_key.txt
@@ -0,0 +1,6 @@
+case 1
+in 2
+ A.print message:
+in 3
+ A.print message:
+end
diff --git a/test/prism/fixtures/case_in_in.txt b/test/prism/fixtures/case_in_in.txt
new file mode 100644
index 0000000000..a5f9e4ec41
--- /dev/null
+++ b/test/prism/fixtures/case_in_in.txt
@@ -0,0 +1,4 @@
+case args
+in [event]
+ context.event in ^event
+end
diff --git a/test/prism/fixtures/character_literal.txt b/test/prism/fixtures/character_literal.txt
new file mode 100644
index 0000000000..920332123f
--- /dev/null
+++ b/test/prism/fixtures/character_literal.txt
@@ -0,0 +1,2 @@
+# encoding: Windows-31J
+p ?\u3042""
diff --git a/test/prism/fixtures/command_method_call_2.txt b/test/prism/fixtures/command_method_call_2.txt
new file mode 100644
index 0000000000..8bd40cff9e
--- /dev/null
+++ b/test/prism/fixtures/command_method_call_2.txt
@@ -0,0 +1 @@
+foo(bar baz, bat)
diff --git a/test/prism/fixtures/command_method_call_3.txt b/test/prism/fixtures/command_method_call_3.txt
new file mode 100644
index 0000000000..6de0446aa9
--- /dev/null
+++ b/test/prism/fixtures/command_method_call_3.txt
@@ -0,0 +1,19 @@
+foo(bar 1, key => '2')
+
+foo(bar 1, KEY => '2')
+
+foo(bar 1, :key => '2')
+
+foo(bar 1, { baz: :bat } => '2')
+
+foo bar - %i[baz] => '2'
+
+foo(bar {} => '2')
+
+foo(bar baz {} => '2')
+
+foo(bar do end => '2')
+
+foo(1, bar {} => '2')
+
+foo(1, bar do end => '2')
diff --git a/test/prism/fixtures/defined.txt b/test/prism/fixtures/defined.txt
index 247fa94e3a..09fc0a29e7 100644
--- a/test/prism/fixtures/defined.txt
+++ b/test/prism/fixtures/defined.txt
@@ -8,3 +8,12 @@ defined? 1
defined?("foo"
)
+
+defined?
+1
+
+defined?
+(1)
+
+defined?
+()
diff --git a/test/prism/fixtures/dstring.txt b/test/prism/fixtures/dstring.txt
index 0cc964f13f..ef698d8fe9 100644
--- a/test/prism/fixtures/dstring.txt
+++ b/test/prism/fixtures/dstring.txt
@@ -29,10 +29,6 @@ foo\\\\\
"
"
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
->>>>>>> 2637007929 (Better handle all kinds of multiline strings in the parser translator)
foo\
b\nar
#{}
@@ -43,9 +39,4 @@ b\nar
a\nb\n#{}\nc\n"
"
-<<<<<<< HEAD
-=======
->>>>>>> a651126458 (Fix an incompatibility with the parser translator)
-=======
->>>>>>> 2637007929 (Better handle all kinds of multiline strings in the parser translator)
’"
diff --git a/test/prism/fixtures/endless_method_as_default_arg.txt b/test/prism/fixtures/endless_method_as_default_arg.txt
new file mode 100644
index 0000000000..0063d9a8fa
--- /dev/null
+++ b/test/prism/fixtures/endless_method_as_default_arg.txt
@@ -0,0 +1,11 @@
+def foo(a = def f = 1); end
+
+def foo(a = def f = 1, b); end
+
+def foo(b, a = def f = 1); end
+
+def foo(a: def f = 1); end
+
+def foo(a = def f = 1+2); end
+
+->(a = def f = 1) {}
diff --git a/test/prism/fixtures/endless_methods.txt b/test/prism/fixtures/endless_methods.txt
index 8c2f2a30cc..6e0488a5ee 100644
--- a/test/prism/fixtures/endless_methods.txt
+++ b/test/prism/fixtures/endless_methods.txt
@@ -3,3 +3,9 @@ def foo = 1
def bar = A ""
def method = 1 + 2 + 3
+
+x = def f = p 1
+
+def foo = bar baz
+
+def foo = bar(baz)
diff --git a/test/prism/fixtures/escaped_newline_with_trailing_content.txt b/test/prism/fixtures/escaped_newline_with_trailing_content.txt
new file mode 100644
index 0000000000..fe947a3f10
--- /dev/null
+++ b/test/prism/fixtures/escaped_newline_with_trailing_content.txt
@@ -0,0 +1,2 @@
+"A
+B\nCC"
diff --git a/test/prism/fixtures/heredoc_dedent_line_continuation.txt b/test/prism/fixtures/heredoc_dedent_line_continuation.txt
new file mode 100644
index 0000000000..661db490c7
--- /dev/null
+++ b/test/prism/fixtures/heredoc_dedent_line_continuation.txt
@@ -0,0 +1,5 @@
+<<~FOO
+ foo\
+ \
+ bar
+FOO
diff --git a/test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt b/test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt
new file mode 100644
index 0000000000..dbfa0bf4b4
--- /dev/null
+++ b/test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt
@@ -0,0 +1,22 @@
+%Q
+#{<<B}
+B
+
+%
+#{<<B}
+B
+
+<<A; %Q
+A
+#{<<B}
+B
+
+<<A; %
+A
+#{<<B}
+B
+
+# \r\n
+%Q
+#{<<B}
+B
diff --git a/test/prism/fixtures/it_assignment.txt b/test/prism/fixtures/it_assignment.txt
new file mode 100644
index 0000000000..523b0ffe1e
--- /dev/null
+++ b/test/prism/fixtures/it_assignment.txt
@@ -0,0 +1 @@
+42.tap { it = it; p it }
diff --git a/test/prism/fixtures/keyword_method_names.txt b/test/prism/fixtures/keyword_method_names.txt
index 9154469441..d3b6eac537 100644
--- a/test/prism/fixtures/keyword_method_names.txt
+++ b/test/prism/fixtures/keyword_method_names.txt
@@ -12,18 +12,9 @@ end
def m(a, **nil)
end
-def __ENCODING__.a
-end
-
%{abc}
%"abc"
-def __FILE__.a
-end
-
-def __LINE__.a
-end
-
def nil::a
end
diff --git a/test/prism/fixtures/next.txt b/test/prism/fixtures/next.txt
index 2ef14c6304..0d2d6a11f5 100644
--- a/test/prism/fixtures/next.txt
+++ b/test/prism/fixtures/next.txt
@@ -22,3 +22,7 @@ tap { next
tap { next() }
tap { next(1) }
+
+tap { (next 1) }
+
+tap { foo && (next 1) }
diff --git a/test/prism/fixtures/non_void_value.txt b/test/prism/fixtures/non_void_value.txt
new file mode 100644
index 0000000000..388e9f2574
--- /dev/null
+++ b/test/prism/fixtures/non_void_value.txt
@@ -0,0 +1,31 @@
+x = begin
+ return if true
+ "conditional"
+end
+
+x = if rand < 0.5
+ return
+ "else is nil"
+end
+
+x = if true
+ return if true
+ "conditional"
+else
+ return
+end
+
+x = if true
+ return if true
+else
+ return if true
+ "conditional"
+end
+
+x = case
+ when 1
+ return if true
+ "conditional"
+ else
+ return
+end
diff --git a/test/prism/fixtures/patterns.txt b/test/prism/fixtures/patterns.txt
index f4f3489e4d..449dac619b 100644
--- a/test/prism/fixtures/patterns.txt
+++ b/test/prism/fixtures/patterns.txt
@@ -212,8 +212,13 @@ foo => Object[{x:}]
case (); in [_a, _a]; end
case (); in [{a:1}, {a:2}]; end
+a => ^({'a' => 'b'})
a in b, and c
a in b, or c
(a in b,) and c
(a in b,) or c
+
+x => ^([*a.x])
+x => ^([**a.x])
+x => ^({ a: })
diff --git a/test/prism/fixtures/rescue.txt b/test/prism/fixtures/rescue.txt
index 99170fbe0f..f436463029 100644
--- a/test/prism/fixtures/rescue.txt
+++ b/test/prism/fixtures/rescue.txt
@@ -33,3 +33,7 @@ end
foo if bar rescue baz
z = x y rescue c d
+
+begin
+rescue => A[]
+end
diff --git a/test/prism/fixtures/return.txt b/test/prism/fixtures/return.txt
index a8b5b95fab..952fb80da8 100644
--- a/test/prism/fixtures/return.txt
+++ b/test/prism/fixtures/return.txt
@@ -22,3 +22,6 @@ return()
return(1)
+(return 1)
+
+foo && (return 1)
diff --git a/test/prism/fixtures/string_concatination_frozen_false.txt b/test/prism/fixtures/string_concatination_frozen_false.txt
new file mode 100644
index 0000000000..abe9301408
--- /dev/null
+++ b/test/prism/fixtures/string_concatination_frozen_false.txt
@@ -0,0 +1,5 @@
+# frozen_string_literal: false
+
+'foo' 'bar'
+
+'foo' 'bar' "baz#{bat}"
diff --git a/test/prism/fixtures/string_concatination_frozen_true.txt b/test/prism/fixtures/string_concatination_frozen_true.txt
new file mode 100644
index 0000000000..829777f0a7
--- /dev/null
+++ b/test/prism/fixtures/string_concatination_frozen_true.txt
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+'foo' 'bar'
+
+'foo' 'bar' "baz#{bat}"
diff --git a/test/prism/fixtures/strings.txt b/test/prism/fixtures/strings.txt
index 1e8d10231d..1419f975b7 100644
--- a/test/prism/fixtures/strings.txt
+++ b/test/prism/fixtures/strings.txt
@@ -45,19 +45,10 @@ foo\
b\nar
"
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
->>>>>>> 4ef093b600 (Fix parser translator edge-case when multiline string ends with \n)
"foo
\nbar\n\n
a\nb\n\nc\n"
-<<<<<<< HEAD
-=======
->>>>>>> 2637007929 (Better handle all kinds of multiline strings in the parser translator)
-=======
->>>>>>> 4ef093b600 (Fix parser translator edge-case when multiline string ends with \n)
%q{abc}
%s[abc]
@@ -85,13 +76,6 @@ a\nb\n\nc\n"
%w[foo\ bar\\ baz\\\
bat]
-<<<<<<< HEAD
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
->>>>>>> 4edfe9d981 (Further refine string handling in the parser translator)
-=======
->>>>>>> 4edfe9d981 (Further refine string handling in the parser translator)
%W[#{foo}\
bar
baz #{bat}
@@ -107,14 +91,6 @@ baz #{bat}
%W(foo\
bar)
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
->>>>>>> bd3dd2b62a (Fix parser translator tokens for %-arrays with whitespace escapes)
-=======
->>>>>>> 4edfe9d981 (Further refine string handling in the parser translator)
-=======
->>>>>>> 4edfe9d981 (Further refine string handling in the parser translator)
%w[foo bar]
%w[
@@ -123,6 +99,34 @@ bar)
d
]
+%w[
+ foo\nbar baz\n\n\
+ bat\n\\\n\foo
+]
+
+%W[
+ foo\nbar baz\n\n\
+ bat\n\\\n\foo
+]
+
+%w[foo\
+ bar
+ baz\\
+ bat
+ 1\n
+ 2
+ 3\\n
+]
+
+%W[foo\
+ bar
+ baz\\
+ bat
+ 1\n
+ 2
+ 3\\n
+]
+
%W[f\u{006f 006f}]
%W[a b#{c}d e]
@@ -170,6 +174,10 @@ baz
%Q{abc}
+%Q(\«)
+
+%q(\«)
+
%^#$^#
%@#@#
diff --git a/test/prism/fixtures/symbols.txt b/test/prism/fixtures/symbols.txt
index edee418bca..34895b9e9f 100644
--- a/test/prism/fixtures/symbols.txt
+++ b/test/prism/fixtures/symbols.txt
@@ -4,12 +4,12 @@
:"abc#{1}"
-"
+:"
foo\
b\nar
"
-"
+:"
foo\
b\nar
#{}
diff --git a/test/prism/fixtures/unary_method_calls.txt b/test/prism/fixtures/unary_method_calls.txt
new file mode 100644
index 0000000000..a8327d23cc
--- /dev/null
+++ b/test/prism/fixtures/unary_method_calls.txt
@@ -0,0 +1,8 @@
+42.~@
+42.!@
+
+-
+42
+
++
+42
diff --git a/test/prism/fixtures/variables.txt b/test/prism/fixtures/variables.txt
index 1545c30c80..4f4dc6f9c8 100644
--- a/test/prism/fixtures/variables.txt
+++ b/test/prism/fixtures/variables.txt
@@ -45,3 +45,5 @@ Foo = 1, 2
(a; b; c)
a, (b, c), d = []
+
+(a,), = []
diff --git a/test/prism/fixtures/write_command_operator.txt b/test/prism/fixtures/write_command_operator.txt
new file mode 100644
index 0000000000..d719d24f87
--- /dev/null
+++ b/test/prism/fixtures/write_command_operator.txt
@@ -0,0 +1,3 @@
+foo = 123 | '456' or return
+
+foo = 123 | '456' in BAR
diff --git a/test/prism/fixtures/xstring.txt b/test/prism/fixtures/xstring.txt
index cdac033865..465a14e84b 100644
--- a/test/prism/fixtures/xstring.txt
+++ b/test/prism/fixtures/xstring.txt
@@ -13,18 +13,9 @@
%x{}
`
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
->>>>>>> 2637007929 (Better handle all kinds of multiline strings in the parser translator)
foo\
b\nar
`
`
-<<<<<<< HEAD
-=======
->>>>>>> a651126458 (Fix an incompatibility with the parser translator)
-=======
->>>>>>> 2637007929 (Better handle all kinds of multiline strings in the parser translator)
’`
diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb
index 3b4a502b90..dcbcb7c117 100644
--- a/test/prism/fixtures_test.rb
+++ b/test/prism/fixtures_test.rb
@@ -8,7 +8,6 @@ module Prism
class FixturesTest < TestCase
except = []
-
if RUBY_VERSION < "3.3.0"
# Ruby < 3.3.0 cannot parse heredocs where there are leading whitespace
# characters in the heredoc start.
@@ -25,7 +24,14 @@ module Prism
except << "whitequark/ruby_bug_19281.txt"
end
- Fixture.each(except: except) do |fixture|
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ except << "command_method_call_2.txt"
+ # https://bugs.ruby-lang.org/issues/21669
+ except << "4.1/void_value.txt"
+ # https://bugs.ruby-lang.org/issues/19107
+ except << "4.1/trailing_comma_after_method_arguments.txt"
+
+ Fixture.each_for_current_ruby(except: except) do |fixture|
define_method(fixture.test_name) { assert_valid_syntax(fixture.read) }
end
end
diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb
index 2786c45a22..1e06d52184 100644
--- a/test/prism/lex_test.rb
+++ b/test/prism/lex_test.rb
@@ -3,49 +3,10 @@
return if !(RUBY_ENGINE == "ruby" && RUBY_VERSION >= "3.2.0")
require_relative "test_helper"
+require "ripper"
module Prism
class LexTest < TestCase
- except = [
- # It seems like there are some oddities with nested heredocs and ripper.
- # Waiting for feedback on https://bugs.ruby-lang.org/issues/19838.
- "seattlerb/heredoc_nested.txt",
- "whitequark/dedenting_heredoc.txt",
- # Ripper seems to have a bug that the regex portions before and after
- # the heredoc are combined into a single token. See
- # https://bugs.ruby-lang.org/issues/19838.
- "spanning_heredoc.txt",
- "spanning_heredoc_newlines.txt",
- # Prism emits a single :on_tstring_content in <<- style heredocs when there
- # is a line continuation preceeded by escaped backslashes. It should emit two, same
- # as if the backslashes are not present.
- "heredocs_with_fake_newlines.txt",
- ]
-
- if RUBY_VERSION < "3.3.0"
- # This file has changed behavior in Ripper in Ruby 3.3, so we skip it if
- # we're on an earlier version.
- except << "seattlerb/pct_w_heredoc_interp_nested.txt"
-
- # Ruby < 3.3.0 cannot parse heredocs where there are leading whitespace
- # characters in the heredoc start.
- # Example: <<~' EOF' or <<-' EOF'
- # https://bugs.ruby-lang.org/issues/19539
- except << "heredocs_leading_whitespace.txt"
- except << "whitequark/ruby_bug_19539.txt"
-
- # https://bugs.ruby-lang.org/issues/19025
- except << "whitequark/numparam_ruby_bug_19025.txt"
- # https://bugs.ruby-lang.org/issues/18878
- except << "whitequark/ruby_bug_18878.txt"
- # https://bugs.ruby-lang.org/issues/19281
- except << "whitequark/ruby_bug_19281.txt"
- end
-
- Fixture.each(except: except) do |fixture|
- define_method(fixture.test_name) { assert_lex(fixture) }
- end
-
def test_lex_file
assert_nothing_raised do
Prism.lex_file(__FILE__)
@@ -86,17 +47,77 @@ module Prism
end
end
- private
-
- def assert_lex(fixture)
- source = fixture.read
+ def test_lex_encoding
+ tokens = Prism.lex('"わたし"', encoding: Encoding::Windows_31J).value
+ tokens.each do |t|
+ assert_equal(Encoding::Windows_31J, t[0].value.encoding)
+ end
- result = Prism.lex_compat(source)
- assert_equal [], result.errors
+ # Shebangs must appear on the first line. For these cases, the encoding
+ # comment may appear second, but it should still change encoding.
+ tokens = Prism.lex(<<~RUBY, encoding: Encoding::Windows_31J).value
+ #! /usr/bin/env ruby
+ # encoding: utf-8
+ "わたし"
+ RUBY
+ tokens.each do |t|
+ assert_equal(Encoding::UTF_8, t[0].value.encoding)
+ end
+ end
- Prism.lex_ripper(source).zip(result.value).each do |(ripper, prism)|
- assert_equal ripper, prism
+ if RUBY_VERSION >= "3.3"
+ def test_lex_compat
+ source = "foo bar"
+ prism = Prism.lex_compat(source, version: "current").value
+ ripper = Ripper.lex(source)
+ assert_equal(ripper, prism)
end
end
+
+ def test_lex_interpolation_unterminated
+ assert_equal(
+ %i[STRING_BEGIN EMBEXPR_BEGIN EOF],
+ token_types('"#{')
+ )
+
+ assert_equal(
+ %i[STRING_BEGIN EMBEXPR_BEGIN IGNORED_NEWLINE EOF],
+ token_types('"#{' + "\n")
+ )
+ end
+
+ def test_lex_interpolation_unterminated_with_content
+ # FIXME: Emits EOL twice.
+ assert_equal(
+ %i[STRING_BEGIN EMBEXPR_BEGIN CONSTANT EOF EOF],
+ token_types('"#{C')
+ )
+
+ assert_equal(
+ %i[STRING_BEGIN EMBEXPR_BEGIN CONSTANT NEWLINE EOF],
+ token_types('"#{C' + "\n")
+ )
+ end
+
+ def test_lex_heredoc_unterminated
+ code = <<~'RUBY'.strip
+ <<A+B
+ #{C
+ RUBY
+
+ assert_equal(
+ %i[HEREDOC_START EMBEXPR_BEGIN CONSTANT HEREDOC_END PLUS CONSTANT NEWLINE EOF],
+ token_types(code)
+ )
+
+ assert_equal(
+ %i[HEREDOC_START EMBEXPR_BEGIN CONSTANT NEWLINE HEREDOC_END PLUS CONSTANT NEWLINE EOF],
+ token_types(code + "\n")
+ )
+ end
+
+ def token_types(code)
+ Prism.lex(code).value.map { |token, _state| token.type }
+ end
end
end
diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb
index e0e9a45855..417730a8a7 100644
--- a/test/prism/locals_test.rb
+++ b/test/prism/locals_test.rb
@@ -13,11 +13,6 @@ return if !defined?(RubyVM::InstructionSequence) || RUBY_VERSION < "3.4.0"
# in comparing the locals because they will be the same.
return if RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism
-# In Ruby 3.4.0, the local table for method forwarding changed. But 3.4.0 can
-# refer to the dev version, so while 3.4.0 still isn't released, we need to
-# check if we have a high enough revision.
-return if RubyVM::InstructionSequence.compile("def foo(...); end").to_a[13][2][2][10].length != 1
-
# Omit tests if running on a 32-bit machine because there is a bug with how
# Ruby is handling large ISeqs on 32-bit machines
return if RUBY_PLATFORM =~ /i686/
@@ -29,10 +24,19 @@ module Prism
except = [
# Skip this fixture because it has a different number of locals because
# CRuby is eliminating dead code.
- "whitequark/ruby_bug_10653.txt"
+ "whitequark/ruby_bug_10653.txt",
+
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ "command_method_call_2.txt",
+
+ # https://bugs.ruby-lang.org/issues/21669
+ "4.1/void_value.txt",
+
+ # https://bugs.ruby-lang.org/issues/19107
+ "4.1/trailing_comma_after_method_arguments.txt",
]
- Fixture.each(except: except) do |fixture|
+ Fixture.each_for_current_ruby(except: except) do |fixture|
define_method(fixture.test_name) { assert_locals(fixture) }
end
@@ -206,7 +210,7 @@ module Prism
end
end
- if params.block
+ if params.block.is_a?(BlockParameterNode)
sorted << (params.block.name || :&)
end
diff --git a/test/prism/magic_comment_test.rb b/test/prism/magic_comment_test.rb
index ab4b5f56e5..7985bae568 100644
--- a/test/prism/magic_comment_test.rb
+++ b/test/prism/magic_comment_test.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative "test_helper"
+require "ripper"
module Prism
class MagicCommentTest < TestCase
@@ -68,6 +69,10 @@ module Prism
assert_magic_encoding(Encoding::US_ASCII, "# -*- foo: bar; encoding: ascii -*-")
end
+ def test_emacs_missing_delimiter
+ assert_magic_encoding(Encoding::US_ASCII, '# -*- \1; encoding: ascii -*-')
+ end
+
def test_coding_whitespace
assert_magic_encoding(Encoding::ASCII_8BIT, "# coding \t \r \v : \t \v \r ascii-8bit")
end
diff --git a/test/prism/newline_offsets_test.rb b/test/prism/newline_offsets_test.rb
index 99b808b1df..bb06876a96 100644
--- a/test/prism/newline_offsets_test.rb
+++ b/test/prism/newline_offsets_test.rb
@@ -8,15 +8,38 @@ module Prism
define_method(fixture.test_name) { assert_newline_offsets(fixture) }
end
+ def test_escape_control_newline
+ # Newlines consumed inside escape sequences like \C-, \c, and \M-
+ # must be tracked in line offsets across all literal types.
+ %w[\\C- \\c \\M-].each do |escape|
+ assert_newline_offsets_for("\"#{escape}\n\"", "#{escape} in string")
+ assert_newline_offsets_for("`#{escape}\n`", "#{escape} in xstring")
+ assert_newline_offsets_for("/#{escape}\n/", "#{escape} in regexp")
+ assert_newline_offsets_for("%Q{#{escape}\n}", "#{escape} in %Q")
+ assert_newline_offsets_for("%W[#{escape}\n]", "#{escape} in %W")
+ assert_newline_offsets_for("<<~H\n#{escape}\n\nH\n", "#{escape} in heredoc")
+ assert_newline_offsets_for("?#{escape}\n", "#{escape} in char literal")
+ end
+
+ # Combined meta + control escapes
+ assert_newline_offsets_for("\"\\M-\\C-\n\"", "\\M-\\C- in string")
+ assert_newline_offsets_for("\"\\M-\\c\n\"", "\\M-\\c in string")
+
+ # \r\n consumed inside escape context
+ assert_newline_offsets_for("\"\\C-\r\n\"", "\\C- with \\r\\n")
+ end
+
private
def assert_newline_offsets(fixture)
- source = fixture.read
+ assert_newline_offsets_for(fixture.read)
+ end
+ def assert_newline_offsets_for(source, message = nil)
expected = [0]
source.b.scan("\n") { expected << $~.offset(0)[0] + 1 }
- assert_equal expected, Prism.parse(source).source.offsets
+ assert_equal expected, Prism.parse(source).source.offsets, message
end
end
end
diff --git a/test/prism/newline_test.rb b/test/prism/newline_test.rb
index fefe9def91..97e698202d 100644
--- a/test/prism/newline_test.rb
+++ b/test/prism/newline_test.rb
@@ -17,7 +17,10 @@ module Prism
result/breadth_first_search_test.rb
result/static_literals_test.rb
result/warnings_test.rb
+ ruby/find_fixtures.rb
+ ruby/find_test.rb
ruby/parser_test.rb
+ ruby/ripper_test.rb
ruby/ruby_parser_test.rb
]
diff --git a/test/prism/ractor_test.rb b/test/prism/ractor_test.rb
new file mode 100644
index 0000000000..0e008ffb08
--- /dev/null
+++ b/test/prism/ractor_test.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+return unless defined?(Ractor) && Process.respond_to?(:fork)
+
+require_relative "test_helper"
+
+module Prism
+ class RactorTest < TestCase
+ def test_version
+ assert_match(/\A\d+\.\d+\.\d+\z/, with_ractor { Prism::VERSION })
+ end
+
+ def test_parse_file
+ assert_equal("Prism::ParseResult", with_ractor(__FILE__) { |filepath| Prism.parse_file(filepath).class })
+ end
+
+ def test_lex_file
+ assert_equal("Prism::LexResult", with_ractor(__FILE__) { |filepath| Prism.lex_file(filepath).class })
+ end
+
+ def test_parse_file_comments
+ assert_equal("Array", with_ractor(__FILE__) { |filepath| Prism.parse_file_comments(filepath).class })
+ end
+
+ def test_parse_lex_file
+ assert_equal("Prism::ParseLexResult", with_ractor(__FILE__) { |filepath| Prism.parse_lex_file(filepath).class })
+ end
+
+ def test_parse_success
+ assert_equal("true", with_ractor("1 + 1") { |source| Prism.parse_success?(source) })
+ end
+
+ def test_parse_failure
+ assert_equal("true", with_ractor("1 +") { |source| Prism.parse_failure?(source) })
+ end
+
+ def test_string_query_local
+ assert_equal("true", with_ractor("foo") { |source| StringQuery.local?(source) })
+ end
+
+ def test_string_query_constant
+ assert_equal("true", with_ractor("FOO") { |source| StringQuery.constant?(source) })
+ end
+
+ def test_string_query_method_name
+ assert_equal("true", with_ractor("foo?") { |source| StringQuery.method_name?(source) })
+ end
+
+ if !ENV["PRISM_BUILD_MINIMAL"]
+ def test_dump_file
+ result = with_ractor(__FILE__) { |filepath| Prism.dump_file(filepath) }
+ assert_operator(result, :start_with?, "PRISM")
+ end
+ end
+
+ private
+
+ # Note that this must be done in a subprocess, otherwise it can mess up
+ # CRuby's test suite.
+ def with_ractor(*arguments, &block)
+ IO.popen("-") do |reader|
+ if reader
+ reader.gets.chomp
+ else
+ ractor = ignore_warnings { Ractor.new(*arguments, &block) }
+
+ # Somewhere in the Ruby 4.0.* series, Ractor#take was removed and
+ # Ractor#value was added.
+ puts(ractor.respond_to?(:value) ? ractor.value : ractor.take)
+ end
+ end
+ end
+ end
+end
diff --git a/test/prism/result/breadth_first_search_test.rb b/test/prism/result/breadth_first_search_test.rb
index e2e043a902..7e7962f172 100644
--- a/test/prism/result/breadth_first_search_test.rb
+++ b/test/prism/result/breadth_first_search_test.rb
@@ -14,5 +14,16 @@ module Prism
refute_nil found
assert_equal 8, found.start_offset
end
+
+ def test_breadth_first_search_all
+ result = Prism.parse("[1 + 2, 2]")
+ found_nodes =
+ result.value.breadth_first_search_all do |node|
+ node.is_a?(IntegerNode)
+ end
+
+ assert_equal 3, found_nodes.size
+ assert_equal 8, found_nodes[0].start_offset
+ end
end
end
diff --git a/test/prism/result/continuable_test.rb b/test/prism/result/continuable_test.rb
new file mode 100644
index 0000000000..3533552167
--- /dev/null
+++ b/test/prism/result/continuable_test.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class ContinuableTest < TestCase
+ def test_valid_input
+ # Valid input is not continuable (nothing to continue).
+ refute_predicate Prism.parse("1 + 1"), :continuable?
+ refute_predicate Prism.parse(""), :continuable?
+ end
+
+ def test_stray_closing_tokens
+ # Stray closing tokens make input non-continuable regardless of what
+ # follows (matches the feature-request examples exactly).
+ refute_predicate Prism.parse("1 + ]"), :continuable?
+ refute_predicate Prism.parse("end.tap do"), :continuable?
+
+ # A mix: stray end plus an unclosed block is not continuable because the
+ # stray end cannot be fixed by appending more input.
+ refute_predicate Prism.parse("end\ntap do"), :continuable?
+ end
+
+ def test_unclosed_constructs
+ # Unclosed constructs are continuable.
+ assert_predicate Prism.parse("1 + ["), :continuable?
+ assert_predicate Prism.parse("tap do"), :continuable?
+ end
+
+ def test_unclosed_keywords
+ assert_predicate Prism.parse("def foo"), :continuable?
+ assert_predicate Prism.parse("class Foo"), :continuable?
+ assert_predicate Prism.parse("module Foo"), :continuable?
+ assert_predicate Prism.parse("if true"), :continuable?
+ assert_predicate Prism.parse("while true"), :continuable?
+ assert_predicate Prism.parse("begin"), :continuable?
+ assert_predicate Prism.parse("for x in [1]"), :continuable?
+ end
+
+ def test_unclosed_delimiters
+ assert_predicate Prism.parse("{"), :continuable?
+ assert_predicate Prism.parse("foo("), :continuable?
+ assert_predicate Prism.parse('"hello'), :continuable?
+ assert_predicate Prism.parse("'hello"), :continuable?
+ assert_predicate Prism.parse("<<~HEREDOC\nhello"), :continuable?
+ end
+
+ def test_trailing_whitespace
+ # Trailing whitespace or newlines should not affect continuability.
+ assert_predicate Prism.parse("class A\n"), :continuable?
+ assert_predicate Prism.parse("def f "), :continuable?
+ assert_predicate Prism.parse("def f\n"), :continuable?
+ assert_predicate Prism.parse("def f\n "), :continuable?
+ assert_predicate Prism.parse("( "), :continuable?
+ assert_predicate Prism.parse("(\n"), :continuable?
+ assert_predicate Prism.parse("1 +\n"), :continuable?
+ end
+
+ def test_incomplete_expressions
+ assert_predicate Prism.parse("-"), :continuable?
+ assert_predicate Prism.parse("[1,"), :continuable?
+ assert_predicate Prism.parse("f arg1,"), :continuable?
+ assert_predicate Prism.parse("def f ="), :continuable?
+ assert_predicate Prism.parse("def $a"), :continuable?
+ assert_predicate Prism.parse("a ="), :continuable?
+ assert_predicate Prism.parse("a,b"), :continuable?
+ end
+
+ def test_modifier_keywords
+ assert_predicate Prism.parse("return if"), :continuable?
+ assert_predicate Prism.parse("return unless"), :continuable?
+ assert_predicate Prism.parse("while"), :continuable?
+ assert_predicate Prism.parse("until"), :continuable?
+ end
+
+ def test_ternary_operator
+ assert_predicate Prism.parse("x ?"), :continuable?
+ assert_predicate Prism.parse("x ? y :"), :continuable?
+ end
+
+ def test_class_with_superclass
+ assert_predicate Prism.parse("class Foo <"), :continuable?
+ end
+
+ def test_keyword_expressions
+ assert_predicate Prism.parse("not"), :continuable?
+ assert_predicate Prism.parse("defined?"), :continuable?
+ assert_predicate Prism.parse("module"), :continuable?
+ end
+
+ def test_for_loops
+ assert_predicate Prism.parse("for"), :continuable?
+ assert_predicate Prism.parse("for x in"), :continuable?
+ end
+
+ def test_pattern_matching
+ assert_predicate Prism.parse("foo => ["), :continuable?
+ assert_predicate Prism.parse("case foo; when"), :continuable?
+ end
+
+ def test_splat_and_block_pass
+ assert_predicate Prism.parse("[*"), :continuable?
+ assert_predicate Prism.parse("f(**"), :continuable?
+ assert_predicate Prism.parse("f(&"), :continuable?
+ end
+
+ def test_default_parameter_value
+ assert_predicate Prism.parse("def f(x ="), :continuable?
+ end
+
+ def test_line_continuation
+ assert_predicate Prism.parse("1 +\\"), :continuable?
+ assert_predicate Prism.parse("\"foo\" \\"), :continuable?
+ end
+
+ def test_embedded_document
+ # Embedded document (=begin) truncated at various points.
+ assert_predicate Prism.parse("=b"), :continuable?
+ assert_predicate Prism.parse("=beg"), :continuable?
+ assert_predicate Prism.parse("=begin"), :continuable?
+ assert_predicate Prism.parse("foo\n=b"), :continuable?
+ end
+ end
+end
diff --git a/test/prism/result/error_recovery_test.rb b/test/prism/result/error_recovery_test.rb
new file mode 100644
index 0000000000..d07c858d1b
--- /dev/null
+++ b/test/prism/result/error_recovery_test.rb
@@ -0,0 +1,237 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class ErrorRecoveryTest < TestCase
+ def test_alias_global_variable_node_old_name_symbol
+ result = Prism.parse("alias $a b")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_kind_of SymbolNode, node.old_name.unexpected
+ end
+
+ def test_alias_global_variable_node_old_name_missing
+ result = Prism.parse("alias $a 42")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_nil node.old_name.unexpected
+ end
+
+ def test_alias_method_node_old_name_global_variable
+ result = Prism.parse("alias a $b")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_kind_of GlobalVariableReadNode, node.old_name.unexpected
+ end
+
+ def test_alias_method_node_old_name_missing
+ result = Prism.parse("alias a 42")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_nil node.old_name.unexpected
+ end
+
+ def test_class_node_constant_path_call
+ result = Prism.parse("class 0.X; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_kind_of CallNode, node.constant_path.unexpected
+ end
+
+ def test_for_node_index_back_reference
+ result = Prism.parse("for $& in a; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_kind_of BackReferenceReadNode, node.index.unexpected
+ end
+
+ def test_for_node_index_numbered_reference
+ result = Prism.parse("for $1 in a; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_kind_of NumberedReferenceReadNode, node.index.unexpected
+ end
+
+ def test_for_node_index_missing
+ result = Prism.parse("for in 1..10; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_nil node.index.unexpected
+ end
+
+ def test_interpolated_string_node_parts_xstring
+ result = Prism.parse("<<~`FOO` \"bar\"\nls\nFOO\n")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.parts.any? { |part| part.is_a?(ErrorRecoveryNode) && part.unexpected.is_a?(XStringNode) }
+ end
+
+ def test_interpolated_string_node_parts_interpolated_xstring
+ result = Prism.parse("<<~`FOO` \"bar\"\n\#{ls}\nFOO\n")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.parts.any? { |part| part.is_a?(ErrorRecoveryNode) && part.unexpected.is_a?(InterpolatedXStringNode) }
+ end
+
+ def test_module_node_constant_path_def
+ result = Prism.parse("module def foo; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_kind_of DefNode, node.constant_path.unexpected
+ end
+
+ def test_module_node_constant_path_missing
+ result = Prism.parse("module Parent module end")
+ refute result.success?
+
+ node = result.value.statements.body.first.body.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_nil node.constant_path.unexpected
+ end
+
+ def test_multi_target_node_lefts_back_reference
+ result = Prism.parse("a, (b, $&) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_target_node_lefts_numbered_reference
+ result = Prism.parse("a, (b, $1) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_target_node_rights_back_reference
+ result = Prism.parse("a, (*, $&) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_target_node_rights_numbered_reference
+ result = Prism.parse("a, (*, $1) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_write_node_lefts_back_reference
+ result = Prism.parse("$&, = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_write_node_lefts_numbered_reference
+ result = Prism.parse("$1, = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_write_node_rights_back_reference
+ result = Prism.parse("*, $& = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_write_node_rights_numbered_reference
+ result = Prism.parse("*, $1 = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_parameters_node_posts_keyword_rest
+ result = Prism.parse("def f(**kwargs, ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(KeywordRestParameterNode) }
+ end
+
+ def test_parameters_node_posts_no_keywords
+ result = Prism.parse("def f(**nil, ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(NoKeywordsParameterNode) }
+ end
+
+ def test_parameters_node_posts_forwarding
+ result = Prism.parse("def f(..., ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(ForwardingParameterNode) }
+ end
+
+ def test_pinned_variable_node_variable_missing
+ result = Prism.parse("foo in ^Bar")
+ refute result.success?
+
+ node = result.value.statements.body.first.pattern
+ assert_kind_of ErrorRecoveryNode, node.variable
+ assert_nil node.variable.unexpected
+ end
+
+ def test_rescue_node_reference_back_reference
+ result = Prism.parse("begin; rescue => $&; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_kind_of BackReferenceReadNode, node.reference.unexpected
+ end
+
+ def test_rescue_node_reference_numbered_reference
+ result = Prism.parse("begin; rescue => $1; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_kind_of NumberedReferenceReadNode, node.reference.unexpected
+ end
+
+ def test_rescue_node_reference_missing
+ result = Prism.parse("begin; rescue =>; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_nil node.reference.unexpected
+ end
+ end
+end
diff --git a/test/prism/result/numeric_value_test.rb b/test/prism/result/numeric_value_test.rb
index 5c89230a1f..0207fa6a86 100644
--- a/test/prism/result/numeric_value_test.rb
+++ b/test/prism/result/numeric_value_test.rb
@@ -6,16 +6,27 @@ module Prism
class NumericValueTest < TestCase
def test_numeric_value
assert_equal 123, Prism.parse_statement("123").value
+ assert_equal 123, Prism.parse_statement("1_23").value
assert_equal 3.14, Prism.parse_statement("3.14").value
+ assert_equal 3.14, Prism.parse_statement("3.1_4").value
assert_equal 42i, Prism.parse_statement("42i").value
+ assert_equal 42i, Prism.parse_statement("4_2i").value
assert_equal 42.1ri, Prism.parse_statement("42.1ri").value
+ assert_equal 42.1ri, Prism.parse_statement("42.1_0ri").value
assert_equal 3.14i, Prism.parse_statement("3.14i").value
+ assert_equal 3.14i, Prism.parse_statement("3.1_4i").value
assert_equal 42r, Prism.parse_statement("42r").value
+ assert_equal 42r, Prism.parse_statement("4_2r").value
assert_equal 0.5r, Prism.parse_statement("0.5r").value
+ assert_equal 0.5r, Prism.parse_statement("0.5_0r").value
assert_equal 42ri, Prism.parse_statement("42ri").value
+ assert_equal 42ri, Prism.parse_statement("4_2ri").value
assert_equal 0.5ri, Prism.parse_statement("0.5ri").value
+ assert_equal 0.5ri, Prism.parse_statement("0.5_0ri").value
assert_equal 0xFFr, Prism.parse_statement("0xFFr").value
+ assert_equal 0xFFr, Prism.parse_statement("0xF_Fr").value
assert_equal 0xFFri, Prism.parse_statement("0xFFri").value
+ assert_equal 0xFFri, Prism.parse_statement("0xF_Fri").value
end
end
end
diff --git a/test/prism/result/overlap_test.rb b/test/prism/result/overlap_test.rb
index 155bc870d3..d605eeca44 100644
--- a/test/prism/result/overlap_test.rb
+++ b/test/prism/result/overlap_test.rb
@@ -33,8 +33,13 @@ module Prism
queue << child
if compare
- assert_operator current.location.start_offset, :<=, child.location.start_offset
- assert_operator current.location.end_offset, :>=, child.location.end_offset
+ assert_operator current.location.start_offset, :<=, child.location.start_offset, -> {
+ "[#{fixture.full_path}] Parent node #{current.class} at #{current.location} does not start before child node #{child.class} at #{child.location}"
+ }
+
+ assert_operator current.location.end_offset, :>=, child.location.end_offset, -> {
+ "[#{fixture.full_path}] Parent node #{current.class} at #{current.location} does not end after child node #{child.class} at #{child.location}"
+ }
end
end
end
diff --git a/test/prism/result/source_location_test.rb b/test/prism/result/source_location_test.rb
index 7bdc707658..a8d27b95a8 100644
--- a/test/prism/result/source_location_test.rb
+++ b/test/prism/result/source_location_test.rb
@@ -13,7 +13,7 @@ module Prism
end
def test_AlternationPatternNode
- assert_location(AlternationPatternNode, "foo => bar | baz", 7...16, &:pattern)
+ assert_location(AlternationPatternNode, "foo => 0 | 1", 7...12, &:pattern)
end
def test_AndNode
@@ -650,6 +650,10 @@ module Prism
assert_location(NilNode, "nil")
end
+ def test_NoBlockParameterNode
+ assert_location(NoBlockParameterNode, "def foo(&nil); end", 8...12) { |node| node.parameters.block }
+ end
+
def test_NoKeywordsParameterNode
assert_location(NoKeywordsParameterNode, "def foo(**nil); end", 8...13) { |node| node.parameters.keyword_rest }
end
@@ -920,7 +924,7 @@ module Prism
end
def test_all_tested
- expected = Prism.constants.grep(/.Node$/).sort - %i[MissingNode ProgramNode]
+ expected = Prism.constants.grep(/.Node$/).sort - %i[ErrorRecoveryNode ProgramNode]
actual = SourceLocationTest.instance_methods(false).grep(/.Node$/).map { |name| name[5..].to_sym }.sort
assert_equal expected, actual
end
@@ -935,16 +939,16 @@ module Prism
node = yield node if block_given?
if expected.begin == 0
- assert_equal 0, node.location.start_column
+ assert_equal 0, node.location.start_column, "#{kind} start_column"
end
if expected.end == source.length
- assert_equal source.split("\n").last.length, node.location.end_column
+ assert_equal source.split("\n").last.length, node.location.end_column, "#{kind} end_column"
end
assert_kind_of kind, node
- assert_equal expected.begin, node.location.start_offset
- assert_equal expected.end, node.location.end_offset
+ assert_equal expected.begin, node.location.start_offset, "#{kind} start_offset"
+ assert_equal expected.end, node.location.end_offset, "#{kind} end_offset"
end
end
end
diff --git a/test/prism/result/warnings_test.rb b/test/prism/result/warnings_test.rb
index 04542dbada..27f1119b98 100644
--- a/test/prism/result/warnings_test.rb
+++ b/test/prism/result/warnings_test.rb
@@ -230,6 +230,8 @@ module Prism
refute_warning("foo = 1", compare: false, command_line: "e")
refute_warning("foo = 1", compare: false, scopes: [[]])
+ refute_warning("foo(bar = 1)")
+
assert_warning("def foo; bar = 1; end", "unused")
assert_warning("def foo; bar, = 1; end", "unused")
@@ -263,6 +265,23 @@ module Prism
refute_warning("def foo; bar = 1; end", line: -2, compare: false)
end
+ def test_unused_local_variable_or_assign_with_begin_node
+ assert_warning(<<~RUBY, "assigned but unused variable - foo", compare: false)
+ var ||= begin
+ foo = bar
+ baz
+ end
+ RUBY
+
+ assert_warning(<<~RUBY, "assigned but unused variable - foo", compare: false)
+ foo = false
+ var ||= begin
+ foo = true
+ bar
+ end
+ RUBY
+ end
+
def test_void_statements
assert_warning("foo = 1; foo", "a variable in void")
assert_warning("@foo", "a variable in void")
@@ -339,7 +358,7 @@ module Prism
assert_warning("tap { redo; foo }", "statement not reached")
end
- if RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince/i)
+ if windows?
def test_shebang_ending_with_carriage_return
refute_warning("#!ruby\r\np(123)\n", compare: false)
end
diff --git a/test/prism/ruby/dispatcher_test.rb b/test/prism/ruby/dispatcher_test.rb
index 1b6d7f4117..83eb29e1f3 100644
--- a/test/prism/ruby/dispatcher_test.rb
+++ b/test/prism/ruby/dispatcher_test.rb
@@ -25,9 +25,12 @@ module Prism
end
def test_dispatching_events
- listener = TestListener.new
+ listener_manual = TestListener.new
+ listener_public = TestListener.new
+
dispatcher = Dispatcher.new
- dispatcher.register(listener, :on_call_node_enter, :on_call_node_leave, :on_integer_node_enter)
+ dispatcher.register(listener_manual, :on_call_node_enter, :on_call_node_leave, :on_integer_node_enter)
+ dispatcher.register_public_methods(listener_public)
root = Prism.parse(<<~RUBY).value
def foo
@@ -36,11 +39,17 @@ module Prism
RUBY
dispatcher.dispatch(root)
- assert_equal([:on_call_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_call_node_leave], listener.events_received)
- listener.events_received.clear
+ [listener_manual, listener_public].each do |listener|
+ assert_equal([:on_call_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_call_node_leave], listener.events_received)
+ listener.events_received.clear
+ end
+
dispatcher.dispatch_once(root.statements.body.first.body.body.first)
- assert_equal([:on_call_node_enter, :on_call_node_leave], listener.events_received)
+
+ [listener_manual, listener_public].each do |listener|
+ assert_equal([:on_call_node_enter, :on_call_node_leave], listener.events_received)
+ end
end
end
end
diff --git a/test/prism/ruby/find_fixtures.rb b/test/prism/ruby/find_fixtures.rb
new file mode 100644
index 0000000000..c1bef0d0e6
--- /dev/null
+++ b/test/prism/ruby/find_fixtures.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+# Test fixtures for Prism.find. These must be in a separate file because
+# source_location returns the file path and Prism.find re-parses the file.
+
+module Prism
+ module FindFixtures
+ module Methods
+ def simple_method
+ 42
+ end
+
+ def method_with_params(a, b, c)
+ a + b + c
+ end
+
+ def method_with_block(&block)
+ block.call
+ end
+
+ def self.singleton_method_fixture
+ :singleton
+ end
+
+ def été
+ :utf8
+ end
+
+ def inline_method; :inline; end
+ end
+
+ module Procs
+ SIMPLE_PROC = proc { 42 }
+ SIMPLE_LAMBDA = ->(x) { x * 2 }
+ MULTI_LINE_LAMBDA = lambda do |x|
+ x + 1
+ end
+ DO_BLOCK_PROC = proc do |x|
+ x - 1
+ end
+ end
+
+ module DefineMethod
+ define_method(:dynamic) { |x| x + 1 }
+ end
+
+ module ForLoop
+ for_proc = nil
+ o = Object.new
+ def o.each(&block) = block.call(block)
+ for for_proc in o; end
+ FOR_PROC = for_proc
+ end
+
+ module MultipleOnLine
+ def self.first; end; def self.second; end
+ end
+
+ module Errors
+ def self.divide(a, b)
+ a / b
+ end
+
+ def self.call_undefined
+ undefined_method_call
+ end
+ end
+ end
+end
diff --git a/test/prism/ruby/find_test.rb b/test/prism/ruby/find_test.rb
new file mode 100644
index 0000000000..5b59113d30
--- /dev/null
+++ b/test/prism/ruby/find_test.rb
@@ -0,0 +1,242 @@
+# frozen_string_literal: true
+
+return if RUBY_ENGINE == "ruby" && RUBY_VERSION < "3.4"
+return if defined?(RubyVM::InstructionSequence) && RubyVM::InstructionSequence.compile("").to_a[4][:parser] != :prism
+
+require_relative "../test_helper"
+require_relative "find_fixtures"
+
+module Prism
+ class FindTest < TestCase
+ Fixtures = FindFixtures
+ FIXTURES_PATH = File.expand_path("find_fixtures.rb", __dir__)
+
+ # === Method / UnboundMethod tests ===
+
+ def test_simple_method
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:simple_method)), :simple_method
+ end
+
+ def test_method_with_params
+ node = Prism.find(Fixtures::Methods.instance_method(:method_with_params))
+ assert_def_node node, :method_with_params
+ assert_equal 3, node.parameters.requireds.length
+ end
+
+ def test_method_with_block_param
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:method_with_block)), :method_with_block
+ end
+
+ def test_singleton_method
+ assert_def_node Prism.find(Fixtures::Methods.method(:singleton_method_fixture)), :singleton_method_fixture
+ end
+
+ def test_utf8_method_name
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:été)), :été
+ end
+
+ def test_inline_method
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:inline_method)), :inline_method
+ end
+
+ def test_bound_method
+ obj = Object.new
+ obj.extend(Fixtures::Methods)
+ assert_def_node Prism.find(obj.method(:simple_method)), :simple_method
+ end
+
+ # === Proc / Lambda tests ===
+
+ def test_simple_proc
+ assert_not_nil Prism.find(Fixtures::Procs::SIMPLE_PROC)
+ end
+
+ def test_simple_lambda
+ assert_not_nil Prism.find(Fixtures::Procs::SIMPLE_LAMBDA)
+ end
+
+ def test_multi_line_lambda
+ assert_not_nil Prism.find(Fixtures::Procs::MULTI_LINE_LAMBDA)
+ end
+
+ def test_do_block_proc
+ assert_not_nil Prism.find(Fixtures::Procs::DO_BLOCK_PROC)
+ end
+
+ # === define_method tests ===
+
+ def test_define_method
+ assert_not_nil Prism.find(Fixtures::DefineMethod.instance_method(:dynamic))
+ end
+
+ def test_define_method_bound
+ obj = Object.new
+ obj.extend(Fixtures::DefineMethod)
+ assert_not_nil Prism.find(obj.method(:dynamic))
+ end
+
+ # === for loop test ===
+
+ def test_for_loop_proc
+ node = Prism.find(Fixtures::ForLoop::FOR_PROC)
+ assert_instance_of ForNode, node
+ end
+
+ # === Thread::Backtrace::Location tests ===
+
+ def test_backtrace_location_zero_division
+ location = zero_division_location
+ assert_not_nil location, "could not find backtrace location in fixtures file"
+ assert_not_nil Prism.find(location)
+ end
+
+ def test_backtrace_location_name_error
+ location = begin
+ Fixtures::Errors.call_undefined
+ rescue NameError => e
+ fixture_backtrace_location(e)
+ end
+
+ assert_not_nil location, "could not find backtrace location in fixtures file"
+ assert_not_nil Prism.find(location)
+ end
+
+ def test_backtrace_location_from_caller
+ # caller_locations returns locations for the current call stack
+ location = caller_locations(0, 1).first
+ node = Prism.find(location)
+ assert_not_nil node
+ end
+
+ def test_backtrace_location_eval_returns_nil
+ location = begin
+ eval("raise 'eval error'")
+ rescue RuntimeError => e
+ e.backtrace_locations.find { |loc| loc.path == "(eval)" || loc.label&.include?("eval") }
+ end
+
+ # eval locations have no file on disk
+ assert_nil Prism.find(location) if location
+ end
+
+ # === Edge cases ===
+
+ def test_nil_source_location
+ # Built-in methods have nil source_location
+ assert_nil Prism.find(method(:puts))
+ end
+
+ def test_argument_error_on_wrong_type
+ assert_raise(ArgumentError) { Prism.find("not a callable") }
+ assert_raise(ArgumentError) { Prism.find(42) }
+ assert_raise(ArgumentError) { Prism.find(nil) }
+ end
+
+ def test_eval_returns_nil
+ # eval'd code has no file on disk
+ m = eval("proc { 1 }")
+ assert_nil Prism.find(m)
+ end
+
+ def test_multiple_methods_on_same_line
+ assert_def_node Prism.find(Fixtures::MultipleOnLine.method(:first)), :first
+ assert_def_node Prism.find(Fixtures::MultipleOnLine.method(:second)), :second
+ end
+
+ # === Fallback (line-based) tests via rubyvm: false ===
+
+ def test_fallback_simple_method
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:simple_method), rubyvm: false), :simple_method
+ end
+
+ def test_fallback_singleton_method
+ assert_def_node Prism.find(Fixtures::Methods.method(:singleton_method_fixture), rubyvm: false), :singleton_method_fixture
+ end
+
+ def test_fallback_lambda
+ node = Prism.find(Fixtures::Procs::SIMPLE_LAMBDA, rubyvm: false)
+ assert_instance_of LambdaNode, node
+ end
+
+ def test_fallback_proc
+ node = Prism.find(Fixtures::Procs::SIMPLE_PROC, rubyvm: false)
+ assert_instance_of CallNode, node
+ assert node.block.is_a?(BlockNode)
+ end
+
+ def test_fallback_define_method
+ node = Prism.find(Fixtures::DefineMethod.instance_method(:dynamic), rubyvm: false)
+ assert_instance_of CallNode, node
+ assert node.block.is_a?(BlockNode)
+ end
+
+ def test_fallback_for_loop
+ node = Prism.find(Fixtures::ForLoop::FOR_PROC, rubyvm: false)
+ assert_instance_of ForNode, node
+ end
+
+ def test_fallback_backtrace_location
+ location = zero_division_location
+ assert_not_nil location
+ node = Prism.find(location, rubyvm: false)
+ assert_not_nil node
+ assert_equal location.lineno, node.location.start_line
+ end
+
+ # === Node identity with node_id (CRuby only) ===
+
+ if defined?(RubyVM::InstructionSequence)
+ def test_node_id_matches_iseq
+ m = Fixtures::Methods.instance_method(:simple_method)
+ node = Prism.find(m)
+ assert_equal node_id_of(m), node.node_id
+ end
+
+ def test_node_id_for_lambda
+ node = Prism.find(Fixtures::Procs::SIMPLE_LAMBDA)
+ assert_equal node_id_of(Fixtures::Procs::SIMPLE_LAMBDA), node.node_id
+ end
+
+ def test_node_id_for_proc
+ node = Prism.find(Fixtures::Procs::SIMPLE_PROC)
+ assert_equal node_id_of(Fixtures::Procs::SIMPLE_PROC), node.node_id
+ end
+
+ def test_node_id_for_define_method
+ m = Fixtures::DefineMethod.instance_method(:dynamic)
+ node = Prism.find(m)
+ assert_equal node_id_of(m), node.node_id
+ end
+
+ def test_node_id_for_backtrace_location
+ location = zero_division_location
+ assert_not_nil location
+ expected_node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location)
+
+ node = Prism.find(location)
+ assert_equal expected_node_id, node.node_id
+ end
+ end
+
+ private
+
+ def assert_def_node(node, expected_name)
+ assert_instance_of DefNode, node
+ assert_equal expected_name, node.name
+ end
+
+ def fixture_backtrace_location(exception)
+ exception.backtrace_locations.find { |loc| loc.path == FIXTURES_PATH }
+ end
+
+ def zero_division_location
+ Fixtures::Errors.divide(1, 0)
+ rescue ZeroDivisionError => e
+ fixture_backtrace_location(e)
+ end
+
+ def node_id_of(callable)
+ RubyVM::InstructionSequence.of(callable).to_a[4][:node_id]
+ end
+ end
+end
diff --git a/test/prism/ruby/location_test.rb b/test/prism/ruby/location_test.rb
index 33f844243c..12c4258cde 100644
--- a/test/prism/ruby/location_test.rb
+++ b/test/prism/ruby/location_test.rb
@@ -13,19 +13,22 @@ module Prism
assert_equal 0, joined.start_offset
assert_equal 10, joined.length
- assert_raise(RuntimeError, "Incompatible locations") do
+ e = assert_raise(RuntimeError) do
argument.location.join(receiver.location)
end
+ assert_equal "Incompatible locations", e.message
other_argument = Prism.parse_statement("1234 + 567").arguments.arguments.first
- assert_raise(RuntimeError, "Incompatible sources") do
+ e = assert_raise(RuntimeError) do
other_argument.location.join(receiver.location)
end
+ assert_equal "Incompatible sources", e.message
- assert_raise(RuntimeError, "Incompatible sources") do
+ e = assert_raise(RuntimeError) do
receiver.location.join(other_argument.location)
end
+ assert_equal "Incompatible sources", e.message
end
def test_character_offsets
@@ -70,7 +73,7 @@ module Prism
assert_equal 0, location.start_code_units_offset(Encoding::UTF_16LE)
assert_equal 0, location.start_code_units_offset(Encoding::UTF_32LE)
- assert_equal 1, location.end_code_units_offset(Encoding::UTF_8)
+ assert_equal 4, location.end_code_units_offset(Encoding::UTF_8)
assert_equal 2, location.end_code_units_offset(Encoding::UTF_16LE)
assert_equal 1, location.end_code_units_offset(Encoding::UTF_32LE)
@@ -78,37 +81,37 @@ module Prism
assert_equal 0, location.start_code_units_column(Encoding::UTF_16LE)
assert_equal 0, location.start_code_units_column(Encoding::UTF_32LE)
- assert_equal 1, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 4, location.end_code_units_column(Encoding::UTF_8)
assert_equal 2, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 1, location.end_code_units_column(Encoding::UTF_32LE)
# second 😀
location = program.statements.body.first.arguments.arguments.first.location
- assert_equal 4, location.start_code_units_offset(Encoding::UTF_8)
+ assert_equal 7, location.start_code_units_offset(Encoding::UTF_8)
assert_equal 5, location.start_code_units_offset(Encoding::UTF_16LE)
assert_equal 4, location.start_code_units_offset(Encoding::UTF_32LE)
- assert_equal 5, location.end_code_units_offset(Encoding::UTF_8)
+ assert_equal 11, location.end_code_units_offset(Encoding::UTF_8)
assert_equal 7, location.end_code_units_offset(Encoding::UTF_16LE)
assert_equal 5, location.end_code_units_offset(Encoding::UTF_32LE)
- assert_equal 4, location.start_code_units_column(Encoding::UTF_8)
+ assert_equal 7, location.start_code_units_column(Encoding::UTF_8)
assert_equal 5, location.start_code_units_column(Encoding::UTF_16LE)
assert_equal 4, location.start_code_units_column(Encoding::UTF_32LE)
- assert_equal 5, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 11, location.end_code_units_column(Encoding::UTF_8)
assert_equal 7, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 5, location.end_code_units_column(Encoding::UTF_32LE)
# first 😍
location = program.statements.body.last.name_loc
- assert_equal 6, location.start_code_units_offset(Encoding::UTF_8)
+ assert_equal 12, location.start_code_units_offset(Encoding::UTF_8)
assert_equal 8, location.start_code_units_offset(Encoding::UTF_16LE)
assert_equal 6, location.start_code_units_offset(Encoding::UTF_32LE)
- assert_equal 7, location.end_code_units_offset(Encoding::UTF_8)
+ assert_equal 16, location.end_code_units_offset(Encoding::UTF_8)
assert_equal 10, location.end_code_units_offset(Encoding::UTF_16LE)
assert_equal 7, location.end_code_units_offset(Encoding::UTF_32LE)
@@ -116,26 +119,26 @@ module Prism
assert_equal 0, location.start_code_units_column(Encoding::UTF_16LE)
assert_equal 0, location.start_code_units_column(Encoding::UTF_32LE)
- assert_equal 1, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 4, location.end_code_units_column(Encoding::UTF_8)
assert_equal 2, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 1, location.end_code_units_column(Encoding::UTF_32LE)
# second 😍
location = program.statements.body.last.value.location
- assert_equal 12, location.start_code_units_offset(Encoding::UTF_8)
+ assert_equal 21, location.start_code_units_offset(Encoding::UTF_8)
assert_equal 15, location.start_code_units_offset(Encoding::UTF_16LE)
assert_equal 12, location.start_code_units_offset(Encoding::UTF_32LE)
- assert_equal 13, location.end_code_units_offset(Encoding::UTF_8)
+ assert_equal 25, location.end_code_units_offset(Encoding::UTF_8)
assert_equal 17, location.end_code_units_offset(Encoding::UTF_16LE)
assert_equal 13, location.end_code_units_offset(Encoding::UTF_32LE)
- assert_equal 6, location.start_code_units_column(Encoding::UTF_8)
+ assert_equal 9, location.start_code_units_column(Encoding::UTF_8)
assert_equal 7, location.start_code_units_column(Encoding::UTF_16LE)
assert_equal 6, location.start_code_units_column(Encoding::UTF_32LE)
- assert_equal 7, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 13, location.end_code_units_column(Encoding::UTF_8)
assert_equal 9, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 7, location.end_code_units_column(Encoding::UTF_32LE)
end
@@ -154,7 +157,7 @@ module Prism
assert_equal 0, location.cached_start_code_units_offset(utf16_cache)
assert_equal 0, location.cached_start_code_units_offset(utf32_cache)
- assert_equal 1, location.cached_end_code_units_offset(utf8_cache)
+ assert_equal 4, location.cached_end_code_units_offset(utf8_cache)
assert_equal 2, location.cached_end_code_units_offset(utf16_cache)
assert_equal 1, location.cached_end_code_units_offset(utf32_cache)
@@ -162,26 +165,26 @@ module Prism
assert_equal 0, location.cached_start_code_units_column(utf16_cache)
assert_equal 0, location.cached_start_code_units_column(utf32_cache)
- assert_equal 1, location.cached_end_code_units_column(utf8_cache)
+ assert_equal 4, location.cached_end_code_units_column(utf8_cache)
assert_equal 2, location.cached_end_code_units_column(utf16_cache)
assert_equal 1, location.cached_end_code_units_column(utf32_cache)
# second 😀
location = result.value.statements.body.first.arguments.arguments.first.location
- assert_equal 4, location.cached_start_code_units_offset(utf8_cache)
+ assert_equal 7, location.cached_start_code_units_offset(utf8_cache)
assert_equal 5, location.cached_start_code_units_offset(utf16_cache)
assert_equal 4, location.cached_start_code_units_offset(utf32_cache)
- assert_equal 5, location.cached_end_code_units_offset(utf8_cache)
+ assert_equal 11, location.cached_end_code_units_offset(utf8_cache)
assert_equal 7, location.cached_end_code_units_offset(utf16_cache)
assert_equal 5, location.cached_end_code_units_offset(utf32_cache)
- assert_equal 4, location.cached_start_code_units_column(utf8_cache)
+ assert_equal 7, location.cached_start_code_units_column(utf8_cache)
assert_equal 5, location.cached_start_code_units_column(utf16_cache)
assert_equal 4, location.cached_start_code_units_column(utf32_cache)
- assert_equal 5, location.cached_end_code_units_column(utf8_cache)
+ assert_equal 11, location.cached_end_code_units_column(utf8_cache)
assert_equal 7, location.cached_end_code_units_column(utf16_cache)
assert_equal 5, location.cached_end_code_units_column(utf32_cache)
end
@@ -197,7 +200,7 @@ module Prism
assert_equal "😀".b.to_sym, receiver.name
location = receiver.location
- assert_equal 1, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 4, location.end_code_units_column(Encoding::UTF_8)
assert_equal 2, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 1, location.end_code_units_column(Encoding::UTF_32LE)
end
diff --git a/test/prism/ruby/parameters_signature_test.rb b/test/prism/ruby/parameters_signature_test.rb
index 9256bcc070..1ca2b144a9 100644
--- a/test/prism/ruby/parameters_signature_test.rb
+++ b/test/prism/ruby/parameters_signature_test.rb
@@ -50,13 +50,19 @@ module Prism
assert_parameters([[:nokey]], "**nil")
end
+ def test_noblock
+ # FIXME: `compare: RUBY_VERSION >= "4.1"` once builds are available
+ assert_parameters([[:noblock]], "&nil", compare: false)
+ end
+
def test_keyrest_anonymous
assert_parameters([[:keyrest, :**]], "**")
end
- def test_key_ordering
- omit("TruffleRuby returns keys in order they were declared") if RUBY_ENGINE == "truffleruby"
- assert_parameters([[:keyreq, :a], [:keyreq, :b], [:key, :c], [:key, :d]], "a:, c: 1, b:, d: 2")
+ if RUBY_ENGINE == "ruby"
+ def test_key_ordering
+ assert_parameters([[:keyreq, :a], [:keyreq, :b], [:key, :c], [:key, :d]], "a:, c: 1, b:, d: 2")
+ end
end
def test_block
@@ -71,12 +77,20 @@ module Prism
assert_parameters([[:rest, :*], [:keyrest, :**], [:block, :&]], "...")
end
+ def test_invalid_syntax
+ e = assert_raise(RuntimeError) do
+ Prism.parse_statement("def f(**nil, ...); end").parameters.signature
+ end
+ assert_equal("Invalid syntax", e.message)
+ end
+
private
- def assert_parameters(expected, source)
+ def assert_parameters(expected, source, compare: true)
# Compare against our expectation.
assert_equal(expected, signature(source))
+ return unless compare
# Compare against Ruby's expectation.
object = Object.new
eval("def object.m(#{source}); end")
diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb
index 6b72beddc4..ad9fa0c92c 100644
--- a/test/prism/ruby/parser_test.rb
+++ b/test/prism/ruby/parser_test.rb
@@ -5,8 +5,6 @@ require_relative "../test_helper"
begin
verbose, $VERBOSE = $VERBOSE, nil
require "parser/ruby33"
- require "prism/translation/parser33"
- require "prism/translation/parser34"
rescue LoadError
# In CRuby's CI, we're not going to test against the parser gem because we
# don't want to have to install it. So in this case we'll just skip this test.
@@ -56,6 +54,22 @@ Parser::AST::Node.prepend(
module Prism
class ParserTest < TestCase
+ # These files contain code with valid syntax that can't be parsed.
+ skip_syntax_error = [
+ # alias/undef with %s(abc) symbol literal
+ "alias.txt",
+ "seattlerb/bug_215.txt",
+
+ # %Q with newline delimiter and heredoc interpolation
+ "heredoc_percent_q_newline_delimiter.txt",
+
+ # 1.. && 2
+ "ranges.txt",
+
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ "command_method_call_2.txt",
+ ]
+
# These files contain code that is being parsed incorrectly by the parser
# gem, and therefore we don't want to compare against our translation.
skip_incorrect = [
@@ -87,23 +101,11 @@ module Prism
# Regex with \c escape
"unescaping.txt",
"seattlerb/regexp_esc_C_slash.txt",
- ]
- # These files are either failing to parse or failing to translate, so we'll
- # skip them for now.
- skip_all = skip_incorrect | [
-<<<<<<< HEAD
-=======
- "unescaping.txt",
- "seattlerb/regexp_esc_C_slash.txt",
->>>>>>> 4edfe9d981 (Further refine string handling in the parser translator)
+ # https://github.com/whitequark/parser/issues/1084
+ "unary_method_calls.txt",
]
- # Not sure why these files are failing on JRuby, but skipping them for now.
- if RUBY_ENGINE == "jruby"
- skip_all.push("emoji_method_calls.txt", "symbols.txt")
- end
-
# These files are failing to translate their lexer output into the lexer
# output expected by the parser gem, so we'll skip them for now.
skip_tokens = [
@@ -132,38 +134,81 @@ module Prism
"whitequark/newline_in_hash_argument.txt",
"whitequark/pattern_matching_expr_in_paren.txt",
"whitequark/pattern_matching_hash.txt",
- "whitequark/pin_expr.txt",
"whitequark/ruby_bug_14690.txt",
"whitequark/ruby_bug_9669.txt",
"whitequark/space_args_arg_block.txt",
"whitequark/space_args_block.txt"
]
- Fixture.each do |fixture|
+ Fixture.each_for_version(except: skip_syntax_error, version: "3.3") do |fixture|
define_method(fixture.test_name) do
assert_equal_parses(
fixture,
- compare_asts: !skip_all.include?(fixture.path),
+ compare_asts: !skip_incorrect.include?(fixture.path),
compare_tokens: !skip_tokens.include?(fixture.path),
compare_comments: fixture.path != "embdoc_no_newline_at_end.txt"
)
end
end
- def test_it_block_parameter_syntax
- it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/it.txt")
+ def test_non_prism_builder_class_deprecated
+ warnings = capture_warnings { Prism::Translation::Parser33.new(Parser::Builders::Default.new) }
+
+ assert_include(warnings, "#{__FILE__}:#{__LINE__ - 2}")
+ assert_include(warnings, "is not a `Prism::Translation::Parser::Builder` subclass")
+
+ warnings = capture_warnings { Prism::Translation::Parser33.new }
+ assert_empty(warnings)
+ end
+
+ if RUBY_VERSION >= "3.3"
+ def test_current_parser_for_current_ruby
+ major, minor = CURRENT_MAJOR_MINOR.split(".")
+ # Let's just hope there never is a Ruby 3.10 or similar
+ expected = major.to_i * 10 + minor.to_i
+ assert_equal(expected, Translation::ParserCurrent.new.version)
+ end
+ end
- buffer = Parser::Source::Buffer.new(it_fixture_path)
- buffer.source = it_fixture_path.read
- actual_ast = Prism::Translation::Parser34.new.tokenize(buffer)[0]
+ def test_invalid_syntax
+ code = <<~RUBY
+ foo do
+ case bar
+ when
+ end
+ end
+ RUBY
+ buffer = Parser::Source::Buffer.new("(string)")
+ buffer.source = code
+
+ parser = Prism::Translation::Parser33.new
+ parser.diagnostics.all_errors_are_fatal = true
+ assert_raise(Parser::SyntaxError) { parser.tokenize(buffer) }
+ end
- it_block_parameter_sexp = parse_sexp {
+ def test_it_block_parameter_syntax
+ assert_new_syntax("3.4/it.txt", Prism::Translation::Parser34) do
+ s(:begin,
s(:itblock,
s(:send, nil, :x), :it,
- s(:lvar, :it))
- }
+ s(:lvar, :it)),
+ s(:itblock,
+ s(:lambda), :it,
+ s(:lvar, :it)))
+ end
+ end
- assert_equal(it_block_parameter_sexp, actual_ast.to_sexp)
+ def test_nil_block_parameter_syntax
+ assert_new_syntax("4.1/noblock.txt", Prism::Translation::Parser41) do
+ s(:begin,
+ s(:def, :foo,
+ s(:args,
+ s(:blocknilarg)), nil),
+ s(:block,
+ s(:lambda),
+ s(:args,
+ s(:blocknilarg)), nil))
+ end
end
private
@@ -177,11 +222,7 @@ module Prism
parser.diagnostics.all_errors_are_fatal = true
expected_ast, expected_comments, expected_tokens =
- begin
- ignore_warnings { parser.tokenize(buffer) }
- rescue ArgumentError, Parser::SyntaxError
- return
- end
+ ignore_warnings { parser.tokenize(buffer) }
actual_ast, actual_comments, actual_tokens =
ignore_warnings { Prism::Translation::Parser33.new.tokenize(buffer) }
@@ -265,6 +306,16 @@ module Prism
}
end
+ def assert_new_syntax(path, parser, &sexp)
+ fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures", path)
+
+ buffer = Parser::Source::Buffer.new(fixture_path)
+ buffer.source = fixture_path.read
+ actual_ast = parser.new.tokenize(buffer)[0]
+
+ assert_equal(parse_sexp(&sexp), actual_ast.to_sexp)
+ end
+
def parse_sexp(&block)
Class.new { extend AST::Sexp }.instance_eval(&block).to_sexp
end
diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb
index 4afe377038..4fff630561 100644
--- a/test/prism/ruby/ripper_test.rb
+++ b/test/prism/ruby/ripper_test.rb
@@ -1,36 +1,58 @@
# frozen_string_literal: true
-return if RUBY_VERSION < "3.3"
+return if RUBY_VERSION < "3.3" || RUBY_ENGINE != "ruby"
require_relative "../test_helper"
+require "ripper"
module Prism
class RipperTest < TestCase
# Skip these tests that Ripper is reporting the wrong results for.
incorrect = [
# Ripper incorrectly attributes the block to the keyword.
- "seattlerb/block_break.txt",
- "seattlerb/block_next.txt",
"seattlerb/block_return.txt",
- "whitequark/break_block.txt",
- "whitequark/next_block.txt",
"whitequark/return_block.txt",
- # Ripper is not accounting for locals created by patterns using the **
- # operator within an `in` clause.
- "seattlerb/parse_pattern_058.txt",
-
# Ripper cannot handle named capture groups in regular expressions.
"regex.txt",
- "regex_char_width.txt",
- "whitequark/lvar_injecting_match.txt",
# Ripper fails to understand some structures that span across heredocs.
- "spanning_heredoc.txt"
+ "spanning_heredoc.txt",
+
+ # Ripper interprets circular keyword arguments as method calls.
+ "3.4/circular_parameters.txt",
+
+ # Ripper doesn't emit `args_add_block` when endless method is prefixed by modifier.
+ "4.0/endless_methods_command_call.txt",
+
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ "command_method_call_2.txt",
]
+ if RUBY_VERSION.start_with?("3.3.")
+ incorrect += [
+ "whitequark/lvar_injecting_match.txt",
+ "seattlerb/parse_pattern_058.txt",
+ "regex_char_width.txt",
+ ]
+ end
+
+ if RUBY_VERSION.start_with?("4.")
+ incorrect += [
+ # https://bugs.ruby-lang.org/issues/21945
+ "and_or_with_suffix.txt",
+ ]
+ end
+
+ # https://bugs.ruby-lang.org/issues/21669
+ incorrect << "4.1/void_value.txt"
+ # https://bugs.ruby-lang.org/issues/19107
+ incorrect << "4.1/trailing_comma_after_method_arguments.txt"
+
# Skip these tests that we haven't implemented yet.
- omitted = [
+ omitted_sexp_raw = [
+ "bom_leading_space.txt",
+ "bom_spaces.txt",
"dos_endings.txt",
"heredocs_with_fake_newlines.txt",
"heredocs_with_ignored_newlines.txt",
@@ -51,14 +73,237 @@ module Prism
"whitequark/slash_newline_in_heredocs.txt"
]
- Fixture.each(except: incorrect | omitted) do |fixture|
- define_method(fixture.test_name) { assert_ripper(fixture.read) }
+ omitted_lex = [
+ "heredoc_with_escaped_newline_at_start.txt",
+ "heredocs_with_fake_newlines.txt",
+ "indented_file_end.txt",
+ "spanning_heredoc_newlines.txt",
+ "whitequark/dedenting_heredoc.txt",
+ "whitequark/procarg0.txt",
+ ]
+
+ omitted_scan = [
+ "bom_leading_space.txt",
+ "bom_spaces.txt",
+ "dos_endings.txt",
+ "heredocs_with_fake_newlines.txt",
+ "rescue_modifier.txt",
+ "seattlerb/block_call_dot_op2_brace_block.txt",
+ "seattlerb/block_command_operation_colon.txt",
+ "seattlerb/block_command_operation_dot.txt",
+ "seattlerb/case_in.txt",
+ "seattlerb/heredoc__backslash_dos_format.txt",
+ "seattlerb/heredoc_backslash_nl.txt",
+ "seattlerb/heredoc_nested.txt",
+ "seattlerb/heredoc_squiggly_blank_line_plus_interpolation.txt",
+ "seattlerb/heredoc_squiggly_empty.txt",
+ "seattlerb/masgn_command_call.txt",
+ "seattlerb/messy_op_asgn_lineno.txt",
+ "seattlerb/op_asgn_primary_colon_const_command_call.txt",
+ "seattlerb/parse_pattern_076.txt",
+ "seattlerb/pct_w_heredoc_interp_nested.txt",
+ "tilde_heredocs.txt",
+ "unparser/corpus/literal/assignment.txt",
+ "unparser/corpus/literal/pattern.txt",
+ "unparser/corpus/semantic/dstr.txt",
+ "variables.txt",
+ "whitequark/dedenting_heredoc.txt",
+ "whitequark/masgn_nested.txt",
+ "whitequark/newline_in_hash_argument.txt",
+ "whitequark/numparam_ruby_bug_19025.txt",
+ "whitequark/op_asgn_cmd.txt",
+ "whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt",
+ "whitequark/parser_slash_slash_n_escaping_in_literals.txt",
+ "whitequark/pattern_matching_nil_pattern.txt",
+ "whitequark/ruby_bug_12402.txt",
+ "whitequark/ruby_bug_18878.txt",
+ "whitequark/send_block_chain_cmd.txt",
+ "whitequark/slash_newline_in_heredocs.txt",
+ ]
+
+ Fixture.each_for_current_ruby(except: incorrect | omitted_sexp_raw) do |fixture|
+ define_method("#{fixture.test_name}_sexp_raw") { assert_ripper_sexp_raw(fixture.read) }
+ end
+
+ Fixture.each_for_current_ruby(except: incorrect | omitted_lex) do |fixture|
+ define_method("#{fixture.test_name}_lex") { assert_ripper_lex(fixture.read) }
+ end
+
+ def test_lex_ignored_missing_heredoc_end
+ ["", "-", "~"].each do |type|
+ source = "<<#{type}FOO\n"
+ assert_ripper_lex(source)
+
+ source = "<<#{type}'FOO'\n"
+ assert_ripper_lex(source)
+ end
+ end
+
+ UNSUPPORTED_EVENTS = %i[comma ignored_nl nl semicolon sp ignored_sp]
+ # Events that are currently not emitted
+ SUPPORTED_EVENTS = Translation::Ripper::EVENTS - UNSUPPORTED_EVENTS
+ # Events that assert against their line/column
+ CHECK_LOCATION_EVENTS = %i[kw op lbrace rbrace lbracket rbracket lparen rparen words_sep label_end]
+
+ module Events
+ attr_reader :events
+
+ def initialize(...)
+ super
+ @events = []
+ end
+
+ SUPPORTED_EVENTS.each do |event|
+ define_method(:"on_#{event}") do |*args|
+ if CHECK_LOCATION_EVENTS.include?(event)
+ @events << [event, lineno, column, *args]
+ else
+ @events << [event, *args]
+ end
+ super(*args)
+ end
+ end
+ end
+
+ class RipperEvents < Ripper
+ include Events
+ end
+
+ class PrismEvents < Translation::Ripper
+ include Events
+ end
+
+ class ObjectEvents < Translation::Ripper
+ OBJECT = BasicObject.new
+ SUPPORTED_EVENTS.each do |event|
+ define_method(:"on_#{event}") { |*args| OBJECT }
+ end
+ end
+
+ Fixture.each_for_current_ruby(except: incorrect | omitted_scan) do |fixture|
+ define_method("#{fixture.test_name}_events") do
+ source = fixture.read
+ # Similar to test/ripper/assert_parse_files.rb in CRuby
+ object_events = ObjectEvents.new(source)
+ assert_nothing_raised { object_events.parse }
+
+ ripper = RipperEvents.new(source, fixture.path)
+ prism = PrismEvents.new(source, fixture.path)
+ ripper.parse
+ prism.parse
+ # Check that the same events are emitted, regardless of order
+ assert_equal(ripper.events.sort_by(&:inspect), prism.events.sort_by(&:inspect))
+ end
+ end
+
+ def test_lexer
+ lexer = Translation::Ripper::Lexer.new("foo")
+ expected = [[1, 0], :on_ident, "foo", Translation::Ripper::EXPR_CMDARG]
+
+ assert_equal([expected], lexer.lex)
+ assert_equal(expected, lexer.parse[0].to_a)
+ assert_equal(lexer.parse[0].to_a, lexer.scan[0].to_a)
+
+ assert_equal(%i[on_int on_sp on_op], Translation::Ripper::Lexer.new("1 +").lex.map { |token| token[1] })
+ assert_raise(SyntaxError) { Translation::Ripper::Lexer.new("1 +").lex(raise_errors: true) }
+ end
+
+
+ # On syntax invalid code the output doesn't always match up
+ # In these cases we just want to make sure that it doesn't raise.
+ def test_lex_invalid_syntax
+ assert_nothing_raised do
+ Translation::Ripper.lex('scan/\p{alpha}/')
+ end
+
+ assert_equal(Ripper.lex('if;)'), Translation::Ripper.lex('if;)'))
+ end
+
+ def test_tokenize
+ source = "foo;1;BAZ"
+ assert_equal(Ripper.tokenize(source), Translation::Ripper.tokenize(source))
+ end
+
+ def test_encoding
+ source = '"わたし"'.encode(Encoding::Windows_31J)
+ assert_equal(Ripper.tokenize(source), Translation::Ripper.tokenize(source))
+ assert_equal(Ripper.sexp(source), Translation::Ripper.sexp(source))
+ end
+
+ def test_sexp_coercion
+ string_like = Object.new
+ def string_like.to_str
+ "a"
+ end
+ assert_equal Ripper.sexp(string_like), Translation::Ripper.sexp(string_like)
+
+ File.open(__FILE__) do |file1|
+ File.open(__FILE__) do |file2|
+ assert_equal Ripper.sexp(file1), Translation::Ripper.sexp(file2)
+ end
+ end
+
+ File.open(__FILE__) do |file1|
+ File.open(__FILE__) do |file2|
+ object1_with_gets = Object.new
+ object1_with_gets.define_singleton_method(:gets) do
+ file1.gets
+ end
+
+ object2_with_gets = Object.new
+ object2_with_gets.define_singleton_method(:gets) do
+ file2.gets
+ end
+
+ assert_equal Ripper.sexp(object1_with_gets), Translation::Ripper.sexp(object2_with_gets)
+ end
+ end
+ end
+
+ def test_lex_coersion
+ string_like = Object.new
+ def string_like.to_str
+ "a"
+ end
+ assert_equal Ripper.lex(string_like), Translation::Ripper.lex(string_like)
+ end
+
+ # Check that the hardcoded values don't change without us noticing.
+ def test_internals
+ actual = Translation::Ripper.constants.select { |name| name.start_with?("EXPR_") }.sort
+ expected = Ripper.constants.select { |name| name.start_with?("EXPR_") }.sort
+
+ assert_equal(expected, actual)
+ expected.zip(actual).each do |ripper, prism|
+ assert_equal(Ripper.const_get(ripper), Translation::Ripper.const_get(prism))
+ end
end
private
- def assert_ripper(source)
+ def assert_ripper_sexp_raw(source)
assert_equal Ripper.sexp_raw(source), Prism::Translation::Ripper.sexp_raw(source)
end
+
+ def assert_ripper_lex(source)
+ prism = Translation::Ripper.lex(source)
+ ripper = Ripper.lex(source)
+
+ # Prism emits tokens by their order in the code, not in parse order
+ ripper.sort_by! { |elem| elem[0] }
+
+ [prism.size, ripper.size].max.times do |index|
+ expected = ripper[index]
+ actual = prism[index]
+
+ # There are some tokens that have slightly different state that do not
+ # effect the parse tree, so they may not match.
+ if expected && actual && expected[1] == actual[1] && %i[on_comment on_heredoc_end on_embexpr_end on_sp].include?(expected[1])
+ expected[3] = actual[3] = nil
+ end
+
+ assert_equal(expected, actual)
+ end
+ end
end
end
diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb
index a92e8080de..bc89bdae72 100644
--- a/test/prism/ruby/ruby_parser_test.rb
+++ b/test/prism/ruby/ruby_parser_test.rb
@@ -13,23 +13,12 @@ rescue LoadError
return
end
-# We want to also compare lines and files to make sure we're setting them
-# correctly.
-Sexp.prepend(
- Module.new do
- def ==(other)
- super && line == other.line && file == other.file # && line_max == other.line_max
- end
- end
-)
-
module Prism
class RubyParserTest < TestCase
todos = [
+ "character_literal.txt",
"encoding_euc_jp.txt",
- "newline_terminated.txt",
"regex_char_width.txt",
- "seattlerb/bug169.txt",
"seattlerb/masgn_colon3.txt",
"seattlerb/messy_op_asgn_lineno.txt",
"seattlerb/op_asgn_primary_colon_const_command_call.txt",
@@ -37,15 +26,10 @@ module Prism
"seattlerb/str_lit_concat_bad_encodings.txt",
"strings.txt",
"unescaping.txt",
- "unparser/corpus/literal/kwbegin.txt",
- "unparser/corpus/literal/send.txt",
"whitequark/masgn_const.txt",
"whitequark/pattern_matching_constants.txt",
- "whitequark/pattern_matching_implicit_array_match.txt",
"whitequark/pattern_matching_single_match.txt",
"whitequark/ruby_bug_12402.txt",
- "whitequark/ruby_bug_14690.txt",
- "whitequark/space_args_block.txt"
]
# https://github.com/seattlerb/ruby_parser/issues/344
@@ -53,6 +37,8 @@ module Prism
"alias.txt",
"dsym_str.txt",
"dos_endings.txt",
+ "heredoc_dedent_line_continuation.txt",
+ "heredoc_percent_q_newline_delimiter.txt",
"heredocs_with_fake_newlines.txt",
"heredocs_with_ignored_newlines.txt",
"method_calls.txt",
@@ -71,7 +57,9 @@ module Prism
"seattlerb/heredoc_with_only_carriage_returns.txt",
"spanning_heredoc_newlines.txt",
"spanning_heredoc.txt",
+ "symbols.txt",
"tilde_heredocs.txt",
+ "unary_method_calls.txt",
"unparser/corpus/literal/literal.txt",
"while.txt",
"whitequark/cond_eflipflop.txt",
@@ -89,10 +77,20 @@ module Prism
"whitequark/ruby_bug_11989.txt",
"whitequark/ruby_bug_18878.txt",
"whitequark/ruby_bug_19281.txt",
- "whitequark/slash_newline_in_heredocs.txt"
+ "whitequark/slash_newline_in_heredocs.txt",
+
+ "3.3-3.3/block_args_in_array_assignment.txt",
+ "3.3-3.3/it_with_ordinary_parameter.txt",
+ "3.3-3.3/keyword_args_in_array_assignment.txt",
+ "3.3-3.3/return_in_sclass.txt",
+
+ "3.3-4.0/void_value.txt",
+
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ "command_method_call_2.txt",
]
- Fixture.each(except: failures) do |fixture|
+ Fixture.each_for_version(version: "3.3", except: failures) do |fixture|
define_method(fixture.test_name) do
assert_ruby_parser(fixture, todos.include?(fixture.path))
end
@@ -104,10 +102,16 @@ module Prism
source = fixture.read
expected = ignore_warnings { ::RubyParser.new.parse(source, fixture.path) }
actual = Prism::Translation::RubyParser.new.parse(source, fixture.path)
+ on_failure = -> { message(expected, actual) }
if !allowed_failure
- assert_equal(expected, actual, -> { message(expected, actual) })
- elsif expected == actual
+ assert_equal(expected, actual, on_failure)
+
+ unless actual.nil?
+ assert_equal(expected.line, actual.line, on_failure)
+ assert_equal(expected.file, actual.file, on_failure)
+ end
+ elsif expected == actual && expected.line && actual.line && expected.file == actual.file
puts "#{name} now passes"
end
end
diff --git a/test/prism/ruby/source_test.rb b/test/prism/ruby/source_test.rb
new file mode 100644
index 0000000000..f7cf4fe83a
--- /dev/null
+++ b/test/prism/ruby/source_test.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class SourceTest < TestCase
+ def test_byte_offset
+ source = Prism.parse(<<~SRC).source
+ abcd
+ efgh
+ ijkl
+ SRC
+
+ assert_equal 0, source.byte_offset(1, 0)
+ assert_equal 5, source.byte_offset(2, 0)
+ assert_equal 10, source.byte_offset(3, 0)
+ assert_equal 15, source.byte_offset(4, 0)
+
+ error = assert_raise(ArgumentError) { source.byte_offset(5, 0) }
+ assert_equal "line 5 is out of range", error.message
+
+ error = assert_raise(ArgumentError) { source.byte_offset(0, 0) }
+ assert_equal "line 0 is out of range", error.message
+
+ error = assert_raise(ArgumentError) { source.byte_offset(-1, 0) }
+ assert_equal "line -1 is out of range", error.message
+ end
+
+ def test_byte_offset_with_start_line
+ source = Prism.parse(<<~SRC, line: 11).source
+ abcd
+ efgh
+ ijkl
+ SRC
+
+ assert_equal 0, source.byte_offset(11, 0)
+ assert_equal 5, source.byte_offset(12, 0)
+ assert_equal 10, source.byte_offset(13, 0)
+ assert_equal 15, source.byte_offset(14, 0)
+
+ error = assert_raise(ArgumentError) { source.byte_offset(15, 0) }
+ assert_equal "line 15 is out of range", error.message
+
+ error = assert_raise(ArgumentError) { source.byte_offset(10, 0) }
+ assert_equal "line 10 is out of range", error.message
+
+ error = assert_raise(ArgumentError) { source.byte_offset(9, 0) }
+ assert_equal "line 9 is out of range", error.message
+ end
+ end
+end
diff --git a/test/prism/snapshots/heredocs_with_fake_newlines.txt b/test/prism/snapshots/heredocs_with_fake_newlines.txt
deleted file mode 100644
index df59b29b94..0000000000
--- a/test/prism/snapshots/heredocs_with_fake_newlines.txt
+++ /dev/null
@@ -1,223 +0,0 @@
-@ ProgramNode (location: (1,0)-(43,8))
-├── flags: ∅
-├── locals: []
-└── statements:
- @ StatementsNode (location: (1,0)-(43,8))
- ├── flags: ∅
- └── body: (length: 4)
- ├── @ StringNode (location: (1,0)-(1,7))
- │ ├── flags: newline
- │ ├── opening_loc: (1,0)-(1,7) = "<<-RUBY"
- │ ├── content_loc: (2,0)-(13,0) = " \\n\n \\n\n exit\n \\\\n\n \\n\\n\\n\\n\n argh\n \\\\\n \\\\\\\n foo\\nbar\n \\f\n ok\n"
- │ ├── closing_loc: (13,0)-(14,0) = "RUBY\n"
- │ └── unescaped: " \n\n \n\n exit\n \\n\n \n\n\n\n\n argh\n \\\n \\ foo\nbar\n \f\n ok\n"
- ├── @ InterpolatedStringNode (location: (15,0)-(15,7))
- │ ├── flags: newline, static_literal
- │ ├── opening_loc: (15,0)-(15,7) = "<<~RUBY"
- │ ├── parts: (length: 11)
- │ │ ├── @ StringNode (location: (16,0)-(17,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (16,0)-(17,0) = " \\n\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\n\n"
- │ │ ├── @ StringNode (location: (17,0)-(18,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (17,0)-(18,0) = " \\n\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\n\n"
- │ │ ├── @ StringNode (location: (18,0)-(19,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (18,0)-(19,0) = " exit\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "exit\n"
- │ │ ├── @ StringNode (location: (19,0)-(20,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (19,0)-(20,0) = " \\\\n\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\\n\n"
- │ │ ├── @ StringNode (location: (20,0)-(21,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (20,0)-(21,0) = " \\n\\n\\n\\n\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\n\n\n\n\n"
- │ │ ├── @ StringNode (location: (21,0)-(22,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (21,0)-(22,0) = " argh\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "argh\n"
- │ │ ├── @ StringNode (location: (22,0)-(23,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (22,0)-(23,0) = " \\\\\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\\\n"
- │ │ ├── @ StringNode (location: (23,0)-(24,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (23,0)-(24,0) = " \\\\\\\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\\"
- │ │ ├── @ StringNode (location: (24,0)-(25,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (24,0)-(25,0) = " foo\\nbar\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "foo\nbar\n"
- │ │ ├── @ StringNode (location: (25,0)-(26,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (25,0)-(26,0) = " \\f\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\f\n"
- │ │ └── @ StringNode (location: (26,0)-(27,0))
- │ │ ├── flags: static_literal, frozen
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (26,0)-(27,0) = " ok\n"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "ok\n"
- │ └── closing_loc: (27,0)-(28,0) = "RUBY\n"
- ├── @ InterpolatedStringNode (location: (29,0)-(29,7))
- │ ├── flags: newline
- │ ├── opening_loc: (29,0)-(29,7) = "<<~RUBY"
- │ ├── parts: (length: 18)
- │ │ ├── @ EmbeddedStatementsNode (location: (30,2)-(30,8))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: (30,2)-(30,4) = "\#{"
- │ │ │ ├── statements:
- │ │ │ │ @ StatementsNode (location: (30,4)-(30,7))
- │ │ │ │ ├── flags: ∅
- │ │ │ │ └── body: (length: 1)
- │ │ │ │ └── @ IntegerNode (location: (30,4)-(30,7))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 123
- │ │ │ └── closing_loc: (30,7)-(30,8) = "}"
- │ │ ├── @ StringNode (location: (30,8)-(31,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (30,8)-(31,0) = "\\n\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\n\n"
- │ │ ├── @ StringNode (location: (31,0)-(32,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (31,0)-(32,0) = " \\n\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\n\n"
- │ │ ├── @ StringNode (location: (32,0)-(33,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (32,0)-(33,0) = " exit\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "exit\n"
- │ │ ├── @ StringNode (location: (33,0)-(33,4))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (33,0)-(33,4) = " \\\\"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\\"
- │ │ ├── @ EmbeddedStatementsNode (location: (33,4)-(33,10))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: (33,4)-(33,6) = "\#{"
- │ │ │ ├── statements:
- │ │ │ │ @ StatementsNode (location: (33,6)-(33,9))
- │ │ │ │ ├── flags: ∅
- │ │ │ │ └── body: (length: 1)
- │ │ │ │ └── @ IntegerNode (location: (33,6)-(33,9))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 123
- │ │ │ └── closing_loc: (33,9)-(33,10) = "}"
- │ │ ├── @ StringNode (location: (33,10)-(34,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (33,10)-(34,0) = "n\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "n\n"
- │ │ ├── @ StringNode (location: (34,0)-(34,4))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (34,0)-(34,4) = " \\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\n"
- │ │ ├── @ EmbeddedStatementsNode (location: (34,4)-(34,10))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: (34,4)-(34,6) = "\#{"
- │ │ │ ├── statements:
- │ │ │ │ @ StatementsNode (location: (34,6)-(34,9))
- │ │ │ │ ├── flags: ∅
- │ │ │ │ └── body: (length: 1)
- │ │ │ │ └── @ IntegerNode (location: (34,6)-(34,9))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 123
- │ │ │ └── closing_loc: (34,9)-(34,10) = "}"
- │ │ ├── @ StringNode (location: (34,10)-(35,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (34,10)-(35,0) = "\\n\\n\\n\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\n\n\n\n"
- │ │ ├── @ StringNode (location: (35,0)-(36,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (35,0)-(36,0) = " argh\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "argh\n"
- │ │ ├── @ StringNode (location: (36,0)-(36,4))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (36,0)-(36,4) = " \\\\"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\\"
- │ │ ├── @ EmbeddedStatementsNode (location: (36,4)-(36,10))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: (36,4)-(36,6) = "\#{"
- │ │ │ ├── statements:
- │ │ │ │ @ StatementsNode (location: (36,6)-(36,9))
- │ │ │ │ ├── flags: ∅
- │ │ │ │ └── body: (length: 1)
- │ │ │ │ └── @ IntegerNode (location: (36,6)-(36,9))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 123
- │ │ │ └── closing_loc: (36,9)-(36,10) = "}"
- │ │ ├── @ StringNode (location: (36,10)-(37,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (36,10)-(37,0) = "baz\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "baz\n"
- │ │ ├── @ StringNode (location: (37,0)-(38,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (37,0)-(38,0) = " \\\\\\\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\\"
- │ │ ├── @ StringNode (location: (38,0)-(39,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (38,0)-(39,0) = " foo\\nbar\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "foo\nbar\n"
- │ │ ├── @ StringNode (location: (39,0)-(40,0))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (39,0)-(40,0) = " \\f\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "\f\n"
- │ │ └── @ StringNode (location: (40,0)-(41,0))
- │ │ ├── flags: static_literal, frozen
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (40,0)-(41,0) = " ok\n"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "ok\n"
- │ └── closing_loc: (41,0)-(42,0) = "RUBY\n"
- └── @ StringNode (location: (43,0)-(43,8))
- ├── flags: newline
- ├── opening_loc: (43,0)-(43,8) = "<<'RUBY'"
- ├── content_loc: (44,0)-(55,0) = " \\n\n \\n\n exit\n \\n\n \\n\\n\\n\\n\n argh\n \\\n \\\n foo\\nbar\n \\f\n ok\n"
- ├── closing_loc: (55,0)-(56,0) = "RUBY\n"
- └── unescaped: " \\n\n \\n\n exit\n \\n\n \\n\\n\\n\\n\n argh\n \\\n \\\n foo\\nbar\n \\f\n ok\n"
diff --git a/test/prism/snapshots/strings.txt b/test/prism/snapshots/strings.txt
deleted file mode 100644
index c5e7883b4a..0000000000
--- a/test/prism/snapshots/strings.txt
+++ /dev/null
@@ -1,800 +0,0 @@
-@ ProgramNode (location: (1,0)-(147,15))
-├── flags: ∅
-├── locals: []
-└── statements:
- @ StatementsNode (location: (1,0)-(147,15))
- ├── flags: ∅
- └── body: (length: 63)
- ├── @ StringNode (location: (1,0)-(1,6))
- │ ├── flags: newline
- │ ├── opening_loc: (1,0)-(1,2) = "%%"
- │ ├── content_loc: (1,2)-(1,5) = "abc"
- │ ├── closing_loc: (1,5)-(1,6) = "%"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (3,0)-(3,6))
- │ ├── flags: newline
- │ ├── opening_loc: (3,0)-(3,2) = "%^"
- │ ├── content_loc: (3,2)-(3,5) = "abc"
- │ ├── closing_loc: (3,5)-(3,6) = "^"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (5,0)-(5,6))
- │ ├── flags: newline
- │ ├── opening_loc: (5,0)-(5,2) = "%&"
- │ ├── content_loc: (5,2)-(5,5) = "abc"
- │ ├── closing_loc: (5,5)-(5,6) = "&"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (7,0)-(7,6))
- │ ├── flags: newline
- │ ├── opening_loc: (7,0)-(7,2) = "%*"
- │ ├── content_loc: (7,2)-(7,5) = "abc"
- │ ├── closing_loc: (7,5)-(7,6) = "*"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (9,0)-(9,6))
- │ ├── flags: newline
- │ ├── opening_loc: (9,0)-(9,2) = "%_"
- │ ├── content_loc: (9,2)-(9,5) = "abc"
- │ ├── closing_loc: (9,5)-(9,6) = "_"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (11,0)-(11,6))
- │ ├── flags: newline
- │ ├── opening_loc: (11,0)-(11,2) = "%+"
- │ ├── content_loc: (11,2)-(11,5) = "abc"
- │ ├── closing_loc: (11,5)-(11,6) = "+"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (13,0)-(13,6))
- │ ├── flags: newline
- │ ├── opening_loc: (13,0)-(13,2) = "%-"
- │ ├── content_loc: (13,2)-(13,5) = "abc"
- │ ├── closing_loc: (13,5)-(13,6) = "-"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (15,0)-(15,6))
- │ ├── flags: newline
- │ ├── opening_loc: (15,0)-(15,2) = "%:"
- │ ├── content_loc: (15,2)-(15,5) = "abc"
- │ ├── closing_loc: (15,5)-(15,6) = ":"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (17,0)-(17,6))
- │ ├── flags: newline
- │ ├── opening_loc: (17,0)-(17,2) = "%;"
- │ ├── content_loc: (17,2)-(17,5) = "abc"
- │ ├── closing_loc: (17,5)-(17,6) = ";"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (19,0)-(19,6))
- │ ├── flags: newline
- │ ├── opening_loc: (19,0)-(19,2) = "%'"
- │ ├── content_loc: (19,2)-(19,5) = "abc"
- │ ├── closing_loc: (19,5)-(19,6) = "'"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (21,0)-(21,6))
- │ ├── flags: newline
- │ ├── opening_loc: (21,0)-(21,2) = "%~"
- │ ├── content_loc: (21,2)-(21,5) = "abc"
- │ ├── closing_loc: (21,5)-(21,6) = "~"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (23,0)-(23,6))
- │ ├── flags: newline
- │ ├── opening_loc: (23,0)-(23,2) = "%?"
- │ ├── content_loc: (23,2)-(23,5) = "abc"
- │ ├── closing_loc: (23,5)-(23,6) = "?"
- │ └── unescaped: "abc"
- ├── @ ArrayNode (location: (25,0)-(25,8))
- │ ├── flags: newline, static_literal
- │ ├── elements: (length: 0)
- │ ├── opening_loc: (25,0)-(25,3) = "%w{"
- │ └── closing_loc: (25,7)-(25,8) = "}"
- ├── @ StringNode (location: (27,0)-(27,6))
- │ ├── flags: newline
- │ ├── opening_loc: (27,0)-(27,2) = "%/"
- │ ├── content_loc: (27,2)-(27,5) = "abc"
- │ ├── closing_loc: (27,5)-(27,6) = "/"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (29,0)-(29,6))
- │ ├── flags: newline
- │ ├── opening_loc: (29,0)-(29,2) = "%`"
- │ ├── content_loc: (29,2)-(29,5) = "abc"
- │ ├── closing_loc: (29,5)-(29,6) = "`"
- │ └── unescaped: "abc"
- ├── @ InterpolatedStringNode (location: (31,0)-(31,8))
- │ ├── flags: newline
- │ ├── opening_loc: (31,0)-(31,1) = "\""
- │ ├── parts: (length: 1)
- │ │ └── @ EmbeddedVariableNode (location: (31,1)-(31,7))
- │ │ ├── flags: ∅
- │ │ ├── operator_loc: (31,1)-(31,2) = "#"
- │ │ └── variable:
- │ │ @ ClassVariableReadNode (location: (31,2)-(31,7))
- │ │ ├── flags: ∅
- │ │ └── name: :@@foo
- │ └── closing_loc: (31,7)-(31,8) = "\""
- ├── @ StringNode (location: (33,0)-(33,6))
- │ ├── flags: newline
- │ ├── opening_loc: (33,0)-(33,2) = "%\\"
- │ ├── content_loc: (33,2)-(33,5) = "abc"
- │ ├── closing_loc: (33,5)-(33,6) = "\\"
- │ └── unescaped: "abc"
- ├── @ InterpolatedStringNode (location: (35,0)-(35,17))
- │ ├── flags: newline
- │ ├── opening_loc: (35,0)-(35,2) = "%{"
- │ ├── parts: (length: 3)
- │ │ ├── @ StringNode (location: (35,2)-(35,6))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (35,2)-(35,6) = "aaa "
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "aaa "
- │ │ ├── @ EmbeddedStatementsNode (location: (35,6)-(35,12))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: (35,6)-(35,8) = "\#{"
- │ │ │ ├── statements:
- │ │ │ │ @ StatementsNode (location: (35,8)-(35,11))
- │ │ │ │ ├── flags: ∅
- │ │ │ │ └── body: (length: 1)
- │ │ │ │ └── @ CallNode (location: (35,8)-(35,11))
- │ │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ │ ├── receiver: ∅
- │ │ │ │ ├── call_operator_loc: ∅
- │ │ │ │ ├── name: :bbb
- │ │ │ │ ├── message_loc: (35,8)-(35,11) = "bbb"
- │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ ├── arguments: ∅
- │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ └── block: ∅
- │ │ │ └── closing_loc: (35,11)-(35,12) = "}"
- │ │ └── @ StringNode (location: (35,12)-(35,16))
- │ │ ├── flags: static_literal, frozen
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (35,12)-(35,16) = " ccc"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: " ccc"
- │ └── closing_loc: (35,16)-(35,17) = "}"
- ├── @ StringNode (location: (37,0)-(37,8))
- │ ├── flags: newline
- │ ├── opening_loc: (37,0)-(37,2) = "%["
- │ ├── content_loc: (37,2)-(37,7) = "foo[]"
- │ ├── closing_loc: (37,7)-(37,8) = "]"
- │ └── unescaped: "foo[]"
- ├── @ CallNode (location: (39,0)-(41,5))
- │ ├── flags: newline
- │ ├── receiver:
- │ │ @ StringNode (location: (39,0)-(39,5))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: (39,0)-(39,1) = "\""
- │ │ ├── content_loc: (39,1)-(39,4) = "foo"
- │ │ ├── closing_loc: (39,4)-(39,5) = "\""
- │ │ └── unescaped: "foo"
- │ ├── call_operator_loc: ∅
- │ ├── name: :+
- │ ├── message_loc: (39,6)-(39,7) = "+"
- │ ├── opening_loc: ∅
- │ ├── arguments:
- │ │ @ ArgumentsNode (location: (41,0)-(41,5))
- │ │ ├── flags: ∅
- │ │ └── arguments: (length: 1)
- │ │ └── @ StringNode (location: (41,0)-(41,5))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: (41,0)-(41,1) = "\""
- │ │ ├── content_loc: (41,1)-(41,4) = "bar"
- │ │ ├── closing_loc: (41,4)-(41,5) = "\""
- │ │ └── unescaped: "bar"
- │ ├── closing_loc: ∅
- │ └── block: ∅
- ├── @ StringNode (location: (43,0)-(46,1))
- │ ├── flags: newline
- │ ├── opening_loc: (43,0)-(43,1) = "\""
- │ ├── content_loc: (43,1)-(46,0) = "\nfoo\\\nb\\nar\n"
- │ ├── closing_loc: (46,0)-(46,1) = "\""
- │ └── unescaped: "\nfoob\nar\n"
- ├── @ StringNode (location: (48,0)-(48,7))
- │ ├── flags: newline
- │ ├── opening_loc: (48,0)-(48,3) = "%q{"
- │ ├── content_loc: (48,3)-(48,6) = "abc"
- │ ├── closing_loc: (48,6)-(48,7) = "}"
- │ └── unescaped: "abc"
- ├── @ SymbolNode (location: (50,0)-(50,7))
- │ ├── flags: newline, static_literal, forced_us_ascii_encoding
- │ ├── opening_loc: (50,0)-(50,3) = "%s["
- │ ├── value_loc: (50,3)-(50,6) = "abc"
- │ ├── closing_loc: (50,6)-(50,7) = "]"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (52,0)-(52,6))
- │ ├── flags: newline
- │ ├── opening_loc: (52,0)-(52,2) = "%{"
- │ ├── content_loc: (52,2)-(52,5) = "abc"
- │ ├── closing_loc: (52,5)-(52,6) = "}"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (54,0)-(54,2))
- │ ├── flags: newline
- │ ├── opening_loc: (54,0)-(54,1) = "'"
- │ ├── content_loc: (54,1)-(54,1) = ""
- │ ├── closing_loc: (54,1)-(54,2) = "'"
- │ └── unescaped: ""
- ├── @ StringNode (location: (56,0)-(56,5))
- │ ├── flags: newline
- │ ├── opening_loc: (56,0)-(56,1) = "\""
- │ ├── content_loc: (56,1)-(56,4) = "abc"
- │ ├── closing_loc: (56,4)-(56,5) = "\""
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (58,0)-(58,7))
- │ ├── flags: newline
- │ ├── opening_loc: (58,0)-(58,1) = "\""
- │ ├── content_loc: (58,1)-(58,6) = "\#@---"
- │ ├── closing_loc: (58,6)-(58,7) = "\""
- │ └── unescaped: "\#@---"
- ├── @ InterpolatedStringNode (location: (60,0)-(60,16))
- │ ├── flags: newline
- │ ├── opening_loc: (60,0)-(60,1) = "\""
- │ ├── parts: (length: 3)
- │ │ ├── @ StringNode (location: (60,1)-(60,5))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (60,1)-(60,5) = "aaa "
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "aaa "
- │ │ ├── @ EmbeddedStatementsNode (location: (60,5)-(60,11))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: (60,5)-(60,7) = "\#{"
- │ │ │ ├── statements:
- │ │ │ │ @ StatementsNode (location: (60,7)-(60,10))
- │ │ │ │ ├── flags: ∅
- │ │ │ │ └── body: (length: 1)
- │ │ │ │ └── @ CallNode (location: (60,7)-(60,10))
- │ │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ │ ├── receiver: ∅
- │ │ │ │ ├── call_operator_loc: ∅
- │ │ │ │ ├── name: :bbb
- │ │ │ │ ├── message_loc: (60,7)-(60,10) = "bbb"
- │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ ├── arguments: ∅
- │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ └── block: ∅
- │ │ │ └── closing_loc: (60,10)-(60,11) = "}"
- │ │ └── @ StringNode (location: (60,11)-(60,15))
- │ │ ├── flags: static_literal, frozen
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (60,11)-(60,15) = " ccc"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: " ccc"
- │ └── closing_loc: (60,15)-(60,16) = "\""
- ├── @ StringNode (location: (62,0)-(62,5))
- │ ├── flags: newline
- │ ├── opening_loc: (62,0)-(62,1) = "'"
- │ ├── content_loc: (62,1)-(62,4) = "abc"
- │ ├── closing_loc: (62,4)-(62,5) = "'"
- │ └── unescaped: "abc"
- ├── @ ArrayNode (location: (64,0)-(64,9))
- │ ├── flags: newline
- │ ├── elements: (length: 3)
- │ │ ├── @ StringNode (location: (64,3)-(64,4))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (64,3)-(64,4) = "a"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "a"
- │ │ ├── @ StringNode (location: (64,5)-(64,6))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (64,5)-(64,6) = "b"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "b"
- │ │ └── @ StringNode (location: (64,7)-(64,8))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (64,7)-(64,8) = "c"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "c"
- │ ├── opening_loc: (64,0)-(64,3) = "%w["
- │ └── closing_loc: (64,8)-(64,9) = "]"
- ├── @ ArrayNode (location: (66,0)-(66,17))
- │ ├── flags: newline
- │ ├── elements: (length: 3)
- │ │ ├── @ StringNode (location: (66,3)-(66,6))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (66,3)-(66,6) = "a[]"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "a[]"
- │ │ ├── @ StringNode (location: (66,7)-(66,12))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (66,7)-(66,12) = "b[[]]"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "b[[]]"
- │ │ └── @ StringNode (location: (66,13)-(66,16))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (66,13)-(66,16) = "c[]"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "c[]"
- │ ├── opening_loc: (66,0)-(66,3) = "%w["
- │ └── closing_loc: (66,16)-(66,17) = "]"
- ├── @ ArrayNode (location: (68,0)-(68,18))
- │ ├── flags: newline
- │ ├── elements: (length: 2)
- │ │ ├── @ StringNode (location: (68,3)-(68,11))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (68,3)-(68,11) = "foo\\ bar"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "foo bar"
- │ │ └── @ StringNode (location: (68,12)-(68,17))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (68,12)-(68,17) = "\\\#{1}"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "\\\#{1}"
- │ ├── opening_loc: (68,0)-(68,3) = "%w["
- │ └── closing_loc: (68,17)-(68,18) = "]"
- ├── @ ArrayNode (location: (70,0)-(70,16))
- │ ├── flags: newline
- │ ├── elements: (length: 2)
- │ │ ├── @ StringNode (location: (70,3)-(70,11))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (70,3)-(70,11) = "foo\\ bar"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "foo bar"
- │ │ └── @ StringNode (location: (70,12)-(70,15))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (70,12)-(70,15) = "baz"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "baz"
- │ ├── opening_loc: (70,0)-(70,3) = "%w["
- │ └── closing_loc: (70,15)-(70,16) = "]"
- ├── @ ArrayNode (location: (72,0)-(73,5))
- │ ├── flags: newline
- │ ├── elements: (length: 3)
- │ │ ├── @ StringNode (location: (72,3)-(72,13))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (72,3)-(72,13) = "foo\\ bar\\\\"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "foo bar\\"
- │ │ ├── @ StringNode (location: (72,14)-(73,0))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (72,14)-(73,0) = "baz\\\\\\\n"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "baz\\\n"
- │ │ └── @ StringNode (location: (73,1)-(73,4))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (73,1)-(73,4) = "bat"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "bat"
- │ ├── opening_loc: (72,0)-(72,3) = "%w["
- │ └── closing_loc: (73,4)-(73,5) = "]"
- ├── @ ArrayNode (location: (75,0)-(78,1))
- │ ├── flags: newline
- │ ├── elements: (length: 3)
- │ │ ├── @ InterpolatedStringNode (location: (75,3)-(76,3))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── parts: (length: 2)
- │ │ │ │ ├── @ EmbeddedStatementsNode (location: (75,3)-(75,9))
- │ │ │ │ │ ├── flags: ∅
- │ │ │ │ │ ├── opening_loc: (75,3)-(75,5) = "\#{"
- │ │ │ │ │ ├── statements:
- │ │ │ │ │ │ @ StatementsNode (location: (75,5)-(75,8))
- │ │ │ │ │ │ ├── flags: ∅
- │ │ │ │ │ │ └── body: (length: 1)
- │ │ │ │ │ │ └── @ CallNode (location: (75,5)-(75,8))
- │ │ │ │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ │ │ │ ├── receiver: ∅
- │ │ │ │ │ │ ├── call_operator_loc: ∅
- │ │ │ │ │ │ ├── name: :foo
- │ │ │ │ │ │ ├── message_loc: (75,5)-(75,8) = "foo"
- │ │ │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ │ │ ├── arguments: ∅
- │ │ │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ │ │ └── block: ∅
- │ │ │ │ │ └── closing_loc: (75,8)-(75,9) = "}"
- │ │ │ │ └── @ StringNode (location: (75,9)-(76,3))
- │ │ │ │ ├── flags: static_literal, frozen
- │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ ├── content_loc: (75,9)-(76,3) = "\\\nbar"
- │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ └── unescaped: "\nbar"
- │ │ │ └── closing_loc: ∅
- │ │ ├── @ StringNode (location: (77,0)-(77,3))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (77,0)-(77,3) = "baz"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "baz"
- │ │ └── @ InterpolatedStringNode (location: (77,4)-(77,10))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── parts: (length: 1)
- │ │ │ └── @ EmbeddedStatementsNode (location: (77,4)-(77,10))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: (77,4)-(77,6) = "\#{"
- │ │ │ ├── statements:
- │ │ │ │ @ StatementsNode (location: (77,6)-(77,9))
- │ │ │ │ ├── flags: ∅
- │ │ │ │ └── body: (length: 1)
- │ │ │ │ └── @ CallNode (location: (77,6)-(77,9))
- │ │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ │ ├── receiver: ∅
- │ │ │ │ ├── call_operator_loc: ∅
- │ │ │ │ ├── name: :bat
- │ │ │ │ ├── message_loc: (77,6)-(77,9) = "bat"
- │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ ├── arguments: ∅
- │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ └── block: ∅
- │ │ │ └── closing_loc: (77,9)-(77,10) = "}"
- │ │ └── closing_loc: ∅
- │ ├── opening_loc: (75,0)-(75,3) = "%W["
- │ └── closing_loc: (78,0)-(78,1) = "]"
- ├── @ ArrayNode (location: (80,0)-(80,9))
- │ ├── flags: newline
- │ ├── elements: (length: 1)
- │ │ └── @ StringNode (location: (80,3)-(80,8))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (80,3)-(80,8) = "foo\\n"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "foo\\n"
- │ ├── opening_loc: (80,0)-(80,3) = "%w("
- │ └── closing_loc: (80,8)-(80,9) = ")"
- ├── @ ArrayNode (location: (82,0)-(83,1))
- │ ├── flags: newline
- │ ├── elements: (length: 1)
- │ │ └── @ StringNode (location: (82,3)-(83,0))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (82,3)-(83,0) = "foo\\\n"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "foo\n"
- │ ├── opening_loc: (82,0)-(82,3) = "%w("
- │ └── closing_loc: (83,0)-(83,1) = ")"
- ├── @ ArrayNode (location: (85,0)-(85,10))
- │ ├── flags: newline
- │ ├── elements: (length: 2)
- │ │ ├── @ StringNode (location: (85,3)-(85,6))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (85,3)-(85,6) = "foo"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "foo"
- │ │ └── @ StringNode (location: (85,7)-(85,9))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (85,7)-(85,9) = "\\n"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "\\n"
- │ ├── opening_loc: (85,0)-(85,3) = "%w("
- │ └── closing_loc: (85,9)-(85,10) = ")"
- ├── @ ArrayNode (location: (87,0)-(88,4))
- │ ├── flags: newline
- │ ├── elements: (length: 1)
- │ │ └── @ StringNode (location: (87,3)-(88,3))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (87,3)-(88,3) = "foo\\\nbar"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "foo\nbar"
- │ ├── opening_loc: (87,0)-(87,3) = "%W("
- │ └── closing_loc: (88,3)-(88,4) = ")"
- ├── @ ArrayNode (location: (90,0)-(90,15))
- │ ├── flags: newline
- │ ├── elements: (length: 2)
- │ │ ├── @ StringNode (location: (90,3)-(90,6))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (90,3)-(90,6) = "foo"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "foo"
- │ │ └── @ StringNode (location: (90,11)-(90,14))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (90,11)-(90,14) = "bar"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "bar"
- │ ├── opening_loc: (90,0)-(90,3) = "%w["
- │ └── closing_loc: (90,14)-(90,15) = "]"
- ├── @ ArrayNode (location: (92,0)-(96,1))
- │ ├── flags: newline
- │ ├── elements: (length: 4)
- │ │ ├── @ StringNode (location: (93,2)-(93,3))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (93,2)-(93,3) = "a"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "a"
- │ │ ├── @ StringNode (location: (94,2)-(94,3))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (94,2)-(94,3) = "b"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "b"
- │ │ ├── @ StringNode (location: (94,6)-(94,7))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (94,6)-(94,7) = "c"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "c"
- │ │ └── @ StringNode (location: (95,1)-(95,2))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (95,1)-(95,2) = "d"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "d"
- │ ├── opening_loc: (92,0)-(92,3) = "%w["
- │ └── closing_loc: (96,0)-(96,1) = "]"
- ├── @ ArrayNode (location: (98,0)-(98,18))
- │ ├── flags: newline
- │ ├── elements: (length: 1)
- │ │ └── @ StringNode (location: (98,3)-(98,17))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (98,3)-(98,17) = "f\\u{006f 006f}"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "foo"
- │ ├── opening_loc: (98,0)-(98,3) = "%W["
- │ └── closing_loc: (98,17)-(98,18) = "]"
- ├── @ ArrayNode (location: (100,0)-(100,14))
- │ ├── flags: newline
- │ ├── elements: (length: 3)
- │ │ ├── @ StringNode (location: (100,3)-(100,4))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (100,3)-(100,4) = "a"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "a"
- │ │ ├── @ InterpolatedStringNode (location: (100,5)-(100,11))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── parts: (length: 3)
- │ │ │ │ ├── @ StringNode (location: (100,5)-(100,6))
- │ │ │ │ │ ├── flags: static_literal, frozen
- │ │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ │ ├── content_loc: (100,5)-(100,6) = "b"
- │ │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ │ └── unescaped: "b"
- │ │ │ │ ├── @ EmbeddedStatementsNode (location: (100,6)-(100,10))
- │ │ │ │ │ ├── flags: ∅
- │ │ │ │ │ ├── opening_loc: (100,6)-(100,8) = "\#{"
- │ │ │ │ │ ├── statements:
- │ │ │ │ │ │ @ StatementsNode (location: (100,8)-(100,9))
- │ │ │ │ │ │ ├── flags: ∅
- │ │ │ │ │ │ └── body: (length: 1)
- │ │ │ │ │ │ └── @ CallNode (location: (100,8)-(100,9))
- │ │ │ │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ │ │ │ ├── receiver: ∅
- │ │ │ │ │ │ ├── call_operator_loc: ∅
- │ │ │ │ │ │ ├── name: :c
- │ │ │ │ │ │ ├── message_loc: (100,8)-(100,9) = "c"
- │ │ │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ │ │ ├── arguments: ∅
- │ │ │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ │ │ └── block: ∅
- │ │ │ │ │ └── closing_loc: (100,9)-(100,10) = "}"
- │ │ │ │ └── @ StringNode (location: (100,10)-(100,11))
- │ │ │ │ ├── flags: static_literal, frozen
- │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ ├── content_loc: (100,10)-(100,11) = "d"
- │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ └── unescaped: "d"
- │ │ │ └── closing_loc: ∅
- │ │ └── @ StringNode (location: (100,12)-(100,13))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (100,12)-(100,13) = "e"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "e"
- │ ├── opening_loc: (100,0)-(100,3) = "%W["
- │ └── closing_loc: (100,13)-(100,14) = "]"
- ├── @ ArrayNode (location: (102,0)-(102,9))
- │ ├── flags: newline
- │ ├── elements: (length: 3)
- │ │ ├── @ StringNode (location: (102,3)-(102,4))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (102,3)-(102,4) = "a"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "a"
- │ │ ├── @ StringNode (location: (102,5)-(102,6))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (102,5)-(102,6) = "b"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "b"
- │ │ └── @ StringNode (location: (102,7)-(102,8))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (102,7)-(102,8) = "c"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "c"
- │ ├── opening_loc: (102,0)-(102,3) = "%W["
- │ └── closing_loc: (102,8)-(102,9) = "]"
- ├── @ ArrayNode (location: (104,0)-(108,1))
- │ ├── flags: newline
- │ ├── elements: (length: 3)
- │ │ ├── @ StringNode (location: (105,2)-(105,3))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (105,2)-(105,3) = "a"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "a"
- │ │ ├── @ StringNode (location: (106,2)-(106,3))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── content_loc: (106,2)-(106,3) = "b"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "b"
- │ │ └── @ StringNode (location: (107,2)-(107,3))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: ∅
- │ │ ├── content_loc: (107,2)-(107,3) = "c"
- │ │ ├── closing_loc: ∅
- │ │ └── unescaped: "c"
- │ ├── opening_loc: (104,0)-(104,3) = "%w["
- │ └── closing_loc: (108,0)-(108,1) = "]"
- ├── @ StringNode (location: (110,0)-(110,15))
- │ ├── flags: newline
- │ ├── opening_loc: (110,0)-(110,1) = "'"
- │ ├── content_loc: (110,1)-(110,14) = "\\' foo \\' bar"
- │ ├── closing_loc: (110,14)-(110,15) = "'"
- │ └── unescaped: "' foo ' bar"
- ├── @ StringNode (location: (112,0)-(112,15))
- │ ├── flags: newline
- │ ├── opening_loc: (112,0)-(112,1) = "'"
- │ ├── content_loc: (112,1)-(112,14) = "\\\\ foo \\\\ bar"
- │ ├── closing_loc: (112,14)-(112,15) = "'"
- │ └── unescaped: "\\ foo \\ bar"
- ├── @ StringNode (location: (114,0)-(117,1))
- │ ├── flags: newline
- │ ├── opening_loc: (114,0)-(114,1) = "'"
- │ ├── content_loc: (114,1)-(117,0) = "foo\\\nbar\\\\\nbaz\n"
- │ ├── closing_loc: (117,0)-(117,1) = "'"
- │ └── unescaped: "foo\\\nbar\\\nbaz\n"
- ├── @ InterpolatedStringNode (location: (119,0)-(119,7))
- │ ├── flags: newline
- │ ├── opening_loc: (119,0)-(119,1) = "\""
- │ ├── parts: (length: 1)
- │ │ └── @ EmbeddedVariableNode (location: (119,1)-(119,6))
- │ │ ├── flags: ∅
- │ │ ├── operator_loc: (119,1)-(119,2) = "#"
- │ │ └── variable:
- │ │ @ GlobalVariableReadNode (location: (119,2)-(119,6))
- │ │ ├── flags: ∅
- │ │ └── name: :$foo
- │ └── closing_loc: (119,6)-(119,7) = "\""
- ├── @ InterpolatedStringNode (location: (121,0)-(121,7))
- │ ├── flags: newline
- │ ├── opening_loc: (121,0)-(121,1) = "\""
- │ ├── parts: (length: 1)
- │ │ └── @ EmbeddedVariableNode (location: (121,1)-(121,6))
- │ │ ├── flags: ∅
- │ │ ├── operator_loc: (121,1)-(121,2) = "#"
- │ │ └── variable:
- │ │ @ InstanceVariableReadNode (location: (121,2)-(121,6))
- │ │ ├── flags: ∅
- │ │ └── name: :@foo
- │ └── closing_loc: (121,6)-(121,7) = "\""
- ├── @ StringNode (location: (123,0)-(123,15))
- │ ├── flags: newline
- │ ├── opening_loc: (123,0)-(123,1) = "\""
- │ ├── content_loc: (123,1)-(123,14) = "\\x7 \\x23 \\x61"
- │ ├── closing_loc: (123,14)-(123,15) = "\""
- │ └── unescaped: "\a # a"
- ├── @ StringNode (location: (125,0)-(125,13))
- │ ├── flags: newline
- │ ├── opening_loc: (125,0)-(125,1) = "\""
- │ ├── content_loc: (125,1)-(125,12) = "\\7 \\43 \\141"
- │ ├── closing_loc: (125,12)-(125,13) = "\""
- │ └── unescaped: "\a # a"
- ├── @ StringNode (location: (127,0)-(127,17))
- │ ├── flags: newline, forced_utf8_encoding
- │ ├── opening_loc: (127,0)-(127,1) = "\""
- │ ├── content_loc: (127,1)-(127,16) = "ち\\xE3\\x81\\xFF"
- │ ├── closing_loc: (127,16)-(127,17) = "\""
- │ └── unescaped: "ち\xE3\x81\xFF"
- ├── @ StringNode (location: (129,0)-(129,6))
- │ ├── flags: newline
- │ ├── opening_loc: (129,0)-(129,2) = "%["
- │ ├── content_loc: (129,2)-(129,5) = "abc"
- │ ├── closing_loc: (129,5)-(129,6) = "]"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (131,0)-(131,6))
- │ ├── flags: newline
- │ ├── opening_loc: (131,0)-(131,2) = "%("
- │ ├── content_loc: (131,2)-(131,5) = "abc"
- │ ├── closing_loc: (131,5)-(131,6) = ")"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (133,0)-(133,6))
- │ ├── flags: newline
- │ ├── opening_loc: (133,0)-(133,2) = "%@"
- │ ├── content_loc: (133,2)-(133,5) = "abc"
- │ ├── closing_loc: (133,5)-(133,6) = "@"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (135,0)-(135,6))
- │ ├── flags: newline
- │ ├── opening_loc: (135,0)-(135,2) = "%$"
- │ ├── content_loc: (135,2)-(135,5) = "abc"
- │ ├── closing_loc: (135,5)-(135,6) = "$"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (137,0)-(137,2))
- │ ├── flags: newline
- │ ├── opening_loc: (137,0)-(137,1) = "?"
- │ ├── content_loc: (137,1)-(137,2) = "a"
- │ ├── closing_loc: ∅
- │ └── unescaped: "a"
- ├── @ InterpolatedStringNode (location: (139,0)-(139,6))
- │ ├── flags: newline, static_literal
- │ ├── opening_loc: ∅
- │ ├── parts: (length: 2)
- │ │ ├── @ StringNode (location: (139,0)-(139,2))
- │ │ │ ├── flags: static_literal, frozen
- │ │ │ ├── opening_loc: (139,0)-(139,1) = "?"
- │ │ │ ├── content_loc: (139,1)-(139,2) = "a"
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── unescaped: "a"
- │ │ └── @ StringNode (location: (139,3)-(139,6))
- │ │ ├── flags: static_literal, frozen
- │ │ ├── opening_loc: (139,3)-(139,4) = "\""
- │ │ ├── content_loc: (139,4)-(139,5) = "a"
- │ │ ├── closing_loc: (139,5)-(139,6) = "\""
- │ │ └── unescaped: "a"
- │ └── closing_loc: ∅
- ├── @ StringNode (location: (141,0)-(141,7))
- │ ├── flags: newline
- │ ├── opening_loc: (141,0)-(141,3) = "%Q{"
- │ ├── content_loc: (141,3)-(141,6) = "abc"
- │ ├── closing_loc: (141,6)-(141,7) = "}"
- │ └── unescaped: "abc"
- ├── @ StringNode (location: (143,0)-(143,5))
- │ ├── flags: newline
- │ ├── opening_loc: (143,0)-(143,2) = "%^"
- │ ├── content_loc: (143,2)-(143,4) = "\#$"
- │ ├── closing_loc: (143,4)-(143,5) = "^"
- │ └── unescaped: "\#$"
- ├── @ StringNode (location: (145,0)-(145,4))
- │ ├── flags: newline
- │ ├── opening_loc: (145,0)-(145,2) = "%@"
- │ ├── content_loc: (145,2)-(145,3) = "#"
- │ ├── closing_loc: (145,3)-(145,4) = "@"
- │ └── unescaped: "#"
- └── @ InterpolatedStringNode (location: (147,0)-(147,15))
- ├── flags: newline
- ├── opening_loc: (147,0)-(147,1) = "\""
- ├── parts: (length: 2)
- │ ├── @ EmbeddedStatementsNode (location: (147,1)-(147,12))
- │ │ ├── flags: ∅
- │ │ ├── opening_loc: (147,1)-(147,3) = "\#{"
- │ │ ├── statements:
- │ │ │ @ StatementsNode (location: (147,3)-(147,11))
- │ │ │ ├── flags: ∅
- │ │ │ └── body: (length: 1)
- │ │ │ └── @ InterpolatedStringNode (location: (147,3)-(147,11))
- │ │ │ ├── flags: ∅
- │ │ │ ├── opening_loc: (147,3)-(147,4) = "\""
- │ │ │ ├── parts: (length: 2)
- │ │ │ │ ├── @ EmbeddedStatementsNode (location: (147,4)-(147,8))
- │ │ │ │ │ ├── flags: ∅
- │ │ │ │ │ ├── opening_loc: (147,4)-(147,6) = "\#{"
- │ │ │ │ │ ├── statements:
- │ │ │ │ │ │ @ StatementsNode (location: (147,6)-(147,7))
- │ │ │ │ │ │ ├── flags: ∅
- │ │ │ │ │ │ └── body: (length: 1)
- │ │ │ │ │ │ └── @ ConstantReadNode (location: (147,6)-(147,7))
- │ │ │ │ │ │ ├── flags: ∅
- │ │ │ │ │ │ └── name: :B
- │ │ │ │ │ └── closing_loc: (147,7)-(147,8) = "}"
- │ │ │ │ └── @ StringNode (location: (147,8)-(147,10))
- │ │ │ │ ├── flags: static_literal, frozen
- │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ ├── content_loc: (147,8)-(147,10) = " C"
- │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ └── unescaped: " C"
- │ │ │ └── closing_loc: (147,10)-(147,11) = "\""
- │ │ └── closing_loc: (147,11)-(147,12) = "}"
- │ └── @ StringNode (location: (147,12)-(147,14))
- │ ├── flags: static_literal, frozen
- │ ├── opening_loc: ∅
- │ ├── content_loc: (147,12)-(147,14) = " D"
- │ ├── closing_loc: ∅
- │ └── unescaped: " D"
- └── closing_loc: (147,14)-(147,15) = "\""
diff --git a/test/prism/snapshots_test.rb b/test/prism/snapshots_test.rb
deleted file mode 100644
index 3fbda1f86e..0000000000
--- a/test/prism/snapshots_test.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "test_helper"
-
-# We don't want to generate snapshots when running
-# against files outside of the project folder.
-return if Prism::TestCase::Fixture.custom_base_path?
-
-module Prism
- class SnapshotsTest < 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
-
- except = []
-
- # These fail on TruffleRuby due to a difference in Symbol#inspect:
- # :测试 vs :"测试"
- if RUBY_ENGINE == "truffleruby"
- except.push(
- "emoji_method_calls.txt",
- "seattlerb/bug202.txt",
- "seattlerb/magic_encoding_comment.txt"
- )
- end
-
- Fixture.each(except: except) do |fixture|
- define_method(fixture.test_name) { assert_snapshot(fixture) }
- end
-
- private
-
- def assert_snapshot(fixture)
- source = fixture.read
-
- result = Prism.parse(source, filepath: fixture.path)
- assert result.success?
-
- printed = PP.pp(result.value, +"", 79)
- snapshot = fixture.snapshot_path
-
- 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.
- directory = File.dirname(snapshot)
- FileUtils.mkdir_p(directory) unless File.directory?(directory)
-
- File.write(snapshot, printed)
- warn("Created snapshot at #{snapshot}.")
- end
- end
- end
-end
diff --git a/test/prism/snippets_test.rb b/test/prism/snippets_test.rb
index 66802c5dc3..3c28d27a25 100644
--- a/test/prism/snippets_test.rb
+++ b/test/prism/snippets_test.rb
@@ -18,24 +18,24 @@ module Prism
"whitequark/multiple_pattern_matches.txt"
]
- Fixture.each(except: except) do |fixture|
- define_method(fixture.test_name) { assert_snippets(fixture) }
+ Fixture.each_with_all_versions(except: except) do |fixture, version|
+ define_method(fixture.test_name(version)) { assert_snippets(fixture, version) }
end
private
# We test every snippet (separated by \n\n) in isolation to ensure the
# parser does not try to read bytes further than the end of each snippet.
- def assert_snippets(fixture)
+ def assert_snippets(fixture, version)
fixture.read.split(/(?<=\S)\n\n(?=\S)/).each do |snippet|
snippet = snippet.rstrip
- result = Prism.parse(snippet, filepath: fixture.path)
+ result = Prism.parse(snippet, filepath: fixture.path, version: version)
assert result.success?
if !ENV["PRISM_BUILD_MINIMAL"]
- dumped = Prism.dump(snippet, filepath: fixture.path)
- assert_equal_nodes(result.value, Prism.load(snippet, dumped).value)
+ dumped = Prism.dump(snippet, filepath: fixture.path, version: version)
+ assert_equal_nodes(result.value, Prism.load(snippet, dumped, version: version).value)
end
end
end
diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb
index c5afdc0ea5..406582c0a5 100644
--- a/test/prism/test_helper.rb
+++ b/test/prism/test_helper.rb
@@ -2,7 +2,6 @@
require "prism"
require "pp"
-require "ripper"
require "stringio"
require "test/unit"
require "tempfile"
@@ -55,11 +54,15 @@ module Prism
end
def snapshot_path
- File.join(__dir__, "snapshots", path)
+ File.join(File.expand_path("../..", __dir__), "snapshots", path)
end
- def test_name
- :"test_#{path}"
+ def test_name(version = nil)
+ if version
+ :"test_#{version}_#{path}"
+ else
+ :"test_#{path}"
+ end
end
def self.each(except: [], &block)
@@ -68,6 +71,25 @@ module Prism
paths.each { |path| yield Fixture.new(path) }
end
+ def self.each_for_version(except: [], version:, &block)
+ each(except: except) do |fixture|
+ next unless TestCase.ruby_versions_for(fixture.path).include?(version)
+ yield fixture
+ end
+ end
+
+ def self.each_for_current_ruby(except: [], &block)
+ each_for_version(except: except, version: CURRENT_MAJOR_MINOR, &block)
+ end
+
+ def self.each_with_all_versions(except: [], &block)
+ each(except: except) do |fixture|
+ TestCase.ruby_versions_for(fixture.path).each do |version|
+ yield fixture, version
+ end
+ end
+ end
+
def self.custom_base_path?
ENV.key?("FIXTURE_BASE")
end
@@ -212,6 +234,41 @@ module Prism
yield Encoding::EUC_TW, codepoints_euc_tw
end
+ # True if the current platform is Windows.
+ def self.windows?
+ RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince/i)
+ end
+
+ # All versions that prism can parse
+ SYNTAX_VERSIONS = %w[3.3 3.4 4.0 4.1]
+
+ # `RUBY_VERSION` with the patch version excluded
+ CURRENT_MAJOR_MINOR = RUBY_VERSION.split(".")[0, 2].join(".")
+
+ # Returns an array of ruby versions that a given filepath should test against:
+ # test.txt # => all available versions
+ # 3.4/test.txt # => versions since 3.4 (inclusive)
+ # 3.4-4.2/test.txt # => verisions since 3.4 (inclusive) up to 4.2 (inclusive)
+ def self.ruby_versions_for(filepath)
+ return [ENV['SYNTAX_VERSION']] if ENV['SYNTAX_VERSION']
+
+ parts = filepath.split("/")
+ return SYNTAX_VERSIONS if parts.size == 1
+
+ version_start, version_stop = parts[0].split("-")
+ if version_stop
+ SYNTAX_VERSIONS[SYNTAX_VERSIONS.index(version_start)..SYNTAX_VERSIONS.index(version_stop)]
+ else
+ SYNTAX_VERSIONS[SYNTAX_VERSIONS.index(version_start)..]
+ end
+ end
+
+ if RUBY_VERSION >= "3.3.0"
+ def test_all_syntax_versions_present
+ assert_include(SYNTAX_VERSIONS, CURRENT_MAJOR_MINOR)
+ end
+ end
+
private
if RUBY_ENGINE == "ruby" && RubyVM::InstructionSequence.compile("").to_a[4][:parser] != :prism
@@ -314,15 +371,16 @@ module Prism
end
end
- def ignore_warnings
- previous = $VERBOSE
- $VERBOSE = nil
+ def capture_warnings
+ $stderr = StringIO.new
+ yield
+ $stderr.string
+ ensure
+ $stderr = STDERR
+ end
- begin
- yield
- ensure
- $VERBOSE = previous
- end
+ def ignore_warnings
+ capture_warnings { return yield }
end
end
end