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/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/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/next.txt4
-rw-r--r--test/prism/fixtures/non_void_value.txt31
-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.txt32
-rw-r--r--test/prism/fixtures/unary_method_calls.txt8
-rw-r--r--test/prism/fixtures/write_command_operator.txt3
-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.rb6
-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.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.rb17
-rw-r--r--test/prism/ruby/parser_test.rb80
-rw-r--r--test/prism/ruby/ripper_test.rb277
-rw-r--r--test/prism/ruby/ruby_parser_test.rb45
-rw-r--r--test/prism/ruby/source_test.rb51
-rw-r--r--test/prism/snippets_test.rb12
-rw-r--r--test/prism/test_helper.rb58
162 files changed, 2454 insertions, 273 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/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/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/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/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 0787152786..1419f975b7 100644
--- a/test/prism/fixtures/strings.txt
+++ b/test/prism/fixtures/strings.txt
@@ -99,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]
@@ -146,6 +174,10 @@ baz
%Q{abc}
+%Q(\«)
+
+%q(\«)
+
%^#$^#
%@#@#
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/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_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
index 55ff723395..0e008ffb08 100644
--- a/test/prism/ractor_test.rb
+++ b/test/prism/ractor_test.rb
@@ -62,7 +62,11 @@ module Prism
if reader
reader.gets.chomp
else
- puts(ignore_warnings { Ractor.new(*arguments, &block) }.take)
+ 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
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 4643fb134f..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")
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 af5b54ed91..1ca2b144a9 100644
--- a/test/prism/ruby/parameters_signature_test.rb
+++ b/test/prism/ruby/parameters_signature_test.rb
@@ -50,11 +50,16 @@ 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
- if RUBY_ENGINE != "truffleruby"
+ 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
@@ -72,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 cd52758f2e..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.
@@ -62,8 +60,14 @@ module Prism
"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
@@ -97,18 +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 | [
+ # 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 = [
@@ -143,11 +140,11 @@ module Prism
"whitequark/space_args_block.txt"
]
- Fixture.each(except: skip_syntax_error) 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"
)
@@ -166,27 +163,52 @@ module Prism
if RUBY_VERSION >= "3.3"
def test_current_parser_for_current_ruby
- major, minor, _patch = Gem::Version.new(RUBY_VERSION).segments
+ major, minor = CURRENT_MAJOR_MINOR.split(".")
# Let's just hope there never is a Ruby 3.10 or similar
- expected = major * 10 + minor
+ expected = major.to_i * 10 + minor.to_i
assert_equal(expected, Translation::ParserCurrent.new.version)
end
end
- def test_it_block_parameter_syntax
- it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/it.txt")
+ def test_invalid_syntax
+ code = <<~RUBY
+ foo do
+ case bar
+ when
+ end
+ end
+ RUBY
+ buffer = Parser::Source::Buffer.new("(string)")
+ buffer.source = code
- buffer = Parser::Source::Buffer.new(it_fixture_path)
- buffer.source = it_fixture_path.read
- actual_ast = Prism::Translation::Parser34.new.tokenize(buffer)[0]
+ 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
@@ -284,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 d4b278c28e..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" || RUBY_ENGINE == "truffleruby"
+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 03bcfafc42..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",
@@ -73,6 +59,7 @@ module Prism
"spanning_heredoc.txt",
"symbols.txt",
"tilde_heredocs.txt",
+ "unary_method_calls.txt",
"unparser/corpus/literal/literal.txt",
"while.txt",
"whitequark/cond_eflipflop.txt",
@@ -90,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
@@ -105,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/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 0be9d1e7da..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"
@@ -58,8 +57,12 @@ module Prism
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
@@ -217,6 +239,36 @@ module Prism
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