diff options
Diffstat (limited to 'test/prism')
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 |
