mirror of
https://github.com/ruby/ruby.git
synced 2025-09-15 08:33:58 +02:00

Fix https://github.com/ruby/prism/pull/1946
This fixes to set an error position for unterminated strings to the
opening delimiters. Previously, the error position was set to the end
of the delimiter.
The same fix applies to other string-like literals.
Additionally, this fixes https://github.com/ruby/prism/pull/1946; that is, it adds the last part of the
string even though the string literal does not terminate.
c1240baafd
1908 lines
54 KiB
Ruby
1908 lines
54 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "test_helper"
|
|
|
|
module Prism
|
|
class ErrorsTest < TestCase
|
|
include DSL
|
|
|
|
def test_constant_path_with_invalid_token_after
|
|
assert_error_messages "A::$b", [
|
|
"expected a constant after the `::` operator",
|
|
"expected a newline or semicolon after the statement"
|
|
]
|
|
end
|
|
|
|
def test_module_name_recoverable
|
|
expected = ModuleNode(
|
|
[],
|
|
Location(),
|
|
ConstantReadNode(:Parent),
|
|
StatementsNode(
|
|
[ModuleNode([], Location(), MissingNode(), nil, Location(), :"")]
|
|
),
|
|
Location(),
|
|
:Parent
|
|
)
|
|
|
|
assert_errors expected, "module Parent module end", [
|
|
["expected a constant name after `module`", 20..20]
|
|
]
|
|
end
|
|
|
|
def test_for_loops_index_missing
|
|
expected = ForNode(
|
|
MissingNode(),
|
|
expression("1..10"),
|
|
StatementsNode([expression("i")]),
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "for in 1..10\ni\nend", [
|
|
["expected an index after `for`", 0..3]
|
|
]
|
|
end
|
|
|
|
def test_for_loops_only_end
|
|
expected = ForNode(
|
|
MissingNode(),
|
|
MissingNode(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "for end", [
|
|
["expected an index after `for`", 0..3],
|
|
["expected an `in` after the index in a `for` statement", 3..3],
|
|
["expected a collection after the `in` in a `for` statement", 3..3]
|
|
]
|
|
end
|
|
|
|
def test_pre_execution_missing_brace
|
|
expected = PreExecutionNode(
|
|
StatementsNode([expression("1")]),
|
|
Location(),
|
|
Location(),
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "BEGIN 1 }", [
|
|
["expected a `{` after `BEGIN`", 5..5]
|
|
]
|
|
end
|
|
|
|
def test_pre_execution_context
|
|
expected = PreExecutionNode(
|
|
StatementsNode([
|
|
CallNode(
|
|
expression("1"),
|
|
nil,
|
|
:+,
|
|
Location(),
|
|
nil,
|
|
ArgumentsNode([MissingNode()], 0),
|
|
nil,
|
|
nil,
|
|
0
|
|
)
|
|
]),
|
|
Location(),
|
|
Location(),
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "BEGIN { 1 + }", [
|
|
["expected an expression after the operator", 11..11]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_embdoc
|
|
assert_errors expression("1"), "1\n=begin\n", [
|
|
["could not find a terminator for the embedded document", 2..9]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_i_list
|
|
assert_errors expression("%i["), "%i[", [
|
|
["expected a closing delimiter for the `%i` list", 0..3]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_w_list
|
|
assert_errors expression("%w["), "%w[", [
|
|
["expected a closing delimiter for the `%w` list", 0..3]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_W_list
|
|
assert_errors expression("%W["), "%W[", [
|
|
["expected a closing delimiter for the `%W` list", 0..3]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_regular_expression
|
|
assert_errors expression("/hello"), "/hello", [
|
|
["expected a closing delimiter for the regular expression", 0..1]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_regular_expression_with_heredoc
|
|
source = "<<-END + /b\nEND\n"
|
|
|
|
assert_errors expression(source), source, [
|
|
["expected a closing delimiter for the regular expression", 9..10]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_xstring
|
|
assert_errors expression("`hello"), "`hello", [
|
|
["expected a closing delimiter for the `%x` or backtick string", 0..1]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_string
|
|
assert_errors expression('"hello'), '"hello', [
|
|
["expected a closing delimiter for the interpolated string", 0..1]
|
|
]
|
|
end
|
|
|
|
def test_incomplete_instance_var_string
|
|
assert_errors expression('%@#@@#'), '%@#@@#', [
|
|
["incomplete instance variable", 4..5],
|
|
["expected a newline or semicolon after the statement", 4..4]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_s_symbol
|
|
assert_errors expression("%s[abc"), "%s[abc", [
|
|
["expected a closing delimiter for the dynamic symbol", 0..3]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_parenthesized_expression
|
|
assert_errors expression('(1 + 2'), '(1 + 2', [
|
|
["expected a newline or semicolon after the statement", 6..6],
|
|
["cannot parse the expression", 6..6],
|
|
["expected a matching `)`", 6..6]
|
|
]
|
|
end
|
|
|
|
def test_missing_terminator_in_parentheses
|
|
assert_error_messages "(0 0)", [
|
|
"expected a newline or semicolon after the statement"
|
|
]
|
|
end
|
|
|
|
def test_unterminated_argument_expression
|
|
assert_errors expression('a %'), 'a %', [
|
|
["invalid `%` token", 2..3],
|
|
["expected an expression after the operator", 3..3],
|
|
]
|
|
end
|
|
|
|
def test_unterminated_interpolated_symbol
|
|
assert_error_messages ":\"#", [
|
|
"expected a closing delimiter for the interpolated symbol"
|
|
]
|
|
end
|
|
|
|
def test_cr_without_lf_in_percent_expression
|
|
assert_errors expression("%\r"), "%\r", [
|
|
["invalid `%` token", 0..2],
|
|
]
|
|
end
|
|
|
|
def test_1_2_3
|
|
assert_errors expression("(1, 2, 3)"), "(1, 2, 3)", [
|
|
["expected a newline or semicolon after the statement", 2..2],
|
|
["cannot parse the expression", 2..2],
|
|
["expected a matching `)`", 2..2],
|
|
["expected a newline or semicolon after the statement", 2..2],
|
|
["cannot parse the expression", 2..2],
|
|
["expected a newline or semicolon after the statement", 5..5],
|
|
["cannot parse the expression", 5..5],
|
|
["expected a newline or semicolon after the statement", 8..8],
|
|
["cannot parse the expression", 8..8]
|
|
]
|
|
end
|
|
|
|
def test_return_1_2_3
|
|
assert_error_messages "return(1, 2, 3)", [
|
|
"expected a newline or semicolon after the statement",
|
|
"cannot parse the expression",
|
|
"expected a matching `)`",
|
|
"expected a newline or semicolon after the statement",
|
|
"cannot parse the expression"
|
|
]
|
|
end
|
|
|
|
def test_return_1
|
|
assert_errors expression("return 1,;"), "return 1,;", [
|
|
["expected an argument", 9..9]
|
|
]
|
|
end
|
|
|
|
def test_next_1_2_3
|
|
assert_errors expression("next(1, 2, 3)"), "next(1, 2, 3)", [
|
|
["expected a newline or semicolon after the statement", 6..6],
|
|
["cannot parse the expression", 6..6],
|
|
["expected a matching `)`", 6..6],
|
|
["expected a newline or semicolon after the statement", 12..12],
|
|
["cannot parse the expression", 12..12]
|
|
]
|
|
end
|
|
|
|
def test_next_1
|
|
assert_errors expression("next 1,;"), "next 1,;", [
|
|
["expected an argument", 7..7]
|
|
]
|
|
end
|
|
|
|
def test_break_1_2_3
|
|
assert_errors expression("break(1, 2, 3)"), "break(1, 2, 3)", [
|
|
["expected a newline or semicolon after the statement", 7..7],
|
|
["cannot parse the expression", 7..7],
|
|
["expected a matching `)`", 7..7],
|
|
["expected a newline or semicolon after the statement", 13..13],
|
|
["cannot parse the expression", 13..13]
|
|
]
|
|
end
|
|
|
|
def test_break_1
|
|
assert_errors expression("break 1,;"), "break 1,;", [
|
|
["expected an argument", 8..8]
|
|
]
|
|
end
|
|
|
|
def test_argument_forwarding_when_parent_is_not_forwarding
|
|
assert_errors expression('def a(x, y, z); b(...); end'), 'def a(x, y, z); b(...); end', [
|
|
["unexpected `...` when the parent method is not forwarding", 18..21]
|
|
]
|
|
end
|
|
|
|
def test_argument_forwarding_only_effects_its_own_internals
|
|
assert_errors expression('def a(...); b(...); end; def c(x, y, z); b(...); end'),
|
|
'def a(...); b(...); end; def c(x, y, z); b(...); end', [
|
|
["unexpected `...` when the parent method is not forwarding", 43..46]
|
|
]
|
|
end
|
|
|
|
def test_top_level_constant_with_downcased_identifier
|
|
assert_error_messages "::foo", [
|
|
"expected a constant after the `::` operator",
|
|
"expected a newline or semicolon after the statement"
|
|
]
|
|
end
|
|
|
|
def test_top_level_constant_starting_with_downcased_identifier
|
|
assert_error_messages "::foo::A", [
|
|
"expected a constant after the `::` operator",
|
|
"expected a newline or semicolon after the statement"
|
|
]
|
|
end
|
|
|
|
def test_aliasing_global_variable_with_non_global_variable
|
|
assert_errors expression("alias $a b"), "alias $a b", [
|
|
["invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", 9..10]
|
|
]
|
|
end
|
|
|
|
def test_aliasing_non_global_variable_with_global_variable
|
|
assert_errors expression("alias a $b"), "alias a $b", [
|
|
["invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", 8..10]
|
|
]
|
|
end
|
|
|
|
def test_aliasing_global_variable_with_global_number_variable
|
|
assert_errors expression("alias $a $1"), "alias $a $1", [
|
|
["invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", 9..11]
|
|
]
|
|
end
|
|
|
|
def test_def_with_expression_receiver_and_no_identifier
|
|
assert_errors expression("def (a); end"), "def (a); end", [
|
|
["expected a `.` or `::` after the receiver in a method definition", 7..7],
|
|
["expected a method name", 7..7]
|
|
]
|
|
end
|
|
|
|
def test_def_with_multiple_statements_receiver
|
|
assert_errors expression("def (\na\nb\n).c; end"), "def (\na\nb\n).c; end", [
|
|
["expected a matching `)`", 7..7],
|
|
["expected a `.` or `::` after the receiver in a method definition", 7..7],
|
|
["expected a method name", 7..7],
|
|
["cannot parse the expression", 10..10],
|
|
["cannot parse the expression", 11..11]
|
|
]
|
|
end
|
|
|
|
def test_def_with_empty_expression_receiver
|
|
assert_errors expression("def ().a; end"), "def ().a; end", [
|
|
["expected a receiver for the method definition", 5..5]
|
|
]
|
|
end
|
|
|
|
def test_block_beginning_with_brace_and_ending_with_end
|
|
assert_error_messages "x.each { x end", [
|
|
"expected a newline or semicolon after the statement",
|
|
"cannot parse the expression",
|
|
"cannot parse the expression",
|
|
"expected a block beginning with `{` to end with `}`"
|
|
]
|
|
end
|
|
|
|
def test_double_splat_followed_by_splat_argument
|
|
expected = CallNode(
|
|
nil,
|
|
nil,
|
|
:a,
|
|
Location(),
|
|
Location(),
|
|
ArgumentsNode([
|
|
KeywordHashNode([AssocSplatNode(expression("kwargs"), Location())]),
|
|
SplatNode(Location(), expression("args"))
|
|
], 1),
|
|
Location(),
|
|
nil,
|
|
0
|
|
)
|
|
|
|
assert_errors expected, "a(**kwargs, *args)", [
|
|
["unexpected `*` splat argument after a `**` keyword splat argument", 12..17]
|
|
]
|
|
end
|
|
|
|
def test_arguments_after_block
|
|
expected = CallNode(
|
|
nil,
|
|
nil,
|
|
:a,
|
|
Location(),
|
|
Location(),
|
|
ArgumentsNode([expression("foo")], 0),
|
|
Location(),
|
|
BlockArgumentNode(expression("block"), Location()),
|
|
0
|
|
)
|
|
|
|
assert_errors expected, "a(&block, foo)", [
|
|
["unexpected argument after a block argument", 10..13]
|
|
]
|
|
end
|
|
|
|
def test_arguments_binding_power_for_and
|
|
assert_error_messages "foo(*bar and baz)", [
|
|
"expected a `)` to close the arguments",
|
|
"expected a newline or semicolon after the statement",
|
|
"cannot parse the expression"
|
|
]
|
|
end
|
|
|
|
def test_splat_argument_after_keyword_argument
|
|
expected = CallNode(
|
|
nil,
|
|
nil,
|
|
:a,
|
|
Location(),
|
|
Location(),
|
|
ArgumentsNode([
|
|
KeywordHashNode(
|
|
[AssocNode(
|
|
SymbolNode(nil, Location(), Location(), "foo"),
|
|
expression("bar"),
|
|
nil
|
|
)]
|
|
),
|
|
SplatNode(Location(), expression("args"))
|
|
], 0),
|
|
Location(),
|
|
nil,
|
|
0
|
|
)
|
|
|
|
assert_errors expected, "a(foo: bar, *args)", [
|
|
["unexpected `*` splat argument after a `**` keyword splat argument", 12..17]
|
|
]
|
|
end
|
|
|
|
def test_module_definition_in_method_body
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
StatementsNode([ModuleNode([], Location(), ConstantReadNode(:A), nil, Location(), :A)]),
|
|
[],
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo;module A;end;end", [
|
|
["unexpected module definition in a method definition", 8..14]
|
|
]
|
|
end
|
|
|
|
def test_module_definition_in_method_body_within_block
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
StatementsNode(
|
|
[CallNode(
|
|
nil,
|
|
nil,
|
|
:bar,
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
BlockNode(
|
|
[],
|
|
nil,
|
|
StatementsNode([ModuleNode([], Location(), ConstantReadNode(:Foo), nil, Location(), :Foo)]),
|
|
Location(),
|
|
Location(),
|
|
0
|
|
),
|
|
0
|
|
)]
|
|
),
|
|
[],
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, <<~RUBY, [["unexpected module definition in a method definition", 21..27]]
|
|
def foo
|
|
bar do
|
|
module Foo;end
|
|
end
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
def test_module_definition_in_method_defs
|
|
source = <<~RUBY
|
|
def foo(bar = module A;end);end
|
|
def foo;rescue;module A;end;end
|
|
def foo;ensure;module A;end;end
|
|
RUBY
|
|
message = "unexpected module definition in a method definition"
|
|
assert_errors expression(source), source, [
|
|
[message, 14..20],
|
|
[message, 47..53],
|
|
[message, 79..85],
|
|
]
|
|
end
|
|
|
|
def test_class_definition_in_method_body
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
StatementsNode(
|
|
[ClassNode(
|
|
[],
|
|
Location(),
|
|
ConstantReadNode(:A),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
Location(),
|
|
:A
|
|
)]
|
|
),
|
|
[],
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo;class A;end;end", [
|
|
["unexpected class definition in a method definition", 8..13]
|
|
]
|
|
end
|
|
|
|
def test_class_definition_in_method_defs
|
|
source = <<~RUBY
|
|
def foo(bar = class A;end);end
|
|
def foo;rescue;class A;end;end
|
|
def foo;ensure;class A;end;end
|
|
RUBY
|
|
message = "unexpected class definition in a method definition"
|
|
assert_errors expression(source), source, [
|
|
[message, 14..19],
|
|
[message, 46..51],
|
|
[message, 77..82],
|
|
]
|
|
end
|
|
|
|
def test_bad_arguments
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode([
|
|
RequiredParameterNode(:A),
|
|
RequiredParameterNode(:@a),
|
|
RequiredParameterNode(:$A),
|
|
RequiredParameterNode(:@@a),
|
|
], [], nil, [], [], nil, nil),
|
|
nil,
|
|
[:A, :@a, :$A, :@@a],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(A, @a, $A, @@a);end", [
|
|
["invalid formal argument; formal argument cannot be a constant", 8..9],
|
|
["invalid formal argument; formal argument cannot be an instance variable", 11..13],
|
|
["invalid formal argument; formal argument cannot be a global variable", 15..17],
|
|
["invalid formal argument; formal argument cannot be a class variable", 19..22],
|
|
]
|
|
end
|
|
|
|
def test_cannot_assign_to_a_reserved_numbered_parameter
|
|
expected = BeginNode(
|
|
Location(),
|
|
StatementsNode([
|
|
LocalVariableWriteNode(:_1, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_2, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_3, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_4, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_5, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_6, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_7, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_8, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_9, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location()),
|
|
LocalVariableWriteNode(:_10, 0, Location(), SymbolNode(Location(), Location(), nil, "a"), Location())
|
|
]),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
Location()
|
|
)
|
|
source = <<~RUBY
|
|
begin
|
|
_1=:a;_2=:a;_3=:a;_4=:a;_5=:a
|
|
_6=:a;_7=:a;_8=:a;_9=:a;_10=:a
|
|
end
|
|
RUBY
|
|
assert_errors expected, source, [
|
|
["_1 is reserved for a numbered parameter", 8..10],
|
|
["_2 is reserved for a numbered parameter", 14..16],
|
|
["_3 is reserved for a numbered parameter", 20..22],
|
|
["_4 is reserved for a numbered parameter", 26..28],
|
|
["_5 is reserved for a numbered parameter", 32..34],
|
|
["_6 is reserved for a numbered parameter", 40..42],
|
|
["_7 is reserved for a numbered parameter", 46..48],
|
|
["_8 is reserved for a numbered parameter", 52..54],
|
|
["_9 is reserved for a numbered parameter", 58..60],
|
|
]
|
|
end
|
|
|
|
def test_do_not_allow_trailing_commas_in_method_parameters
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[RequiredParameterNode(:a), RequiredParameterNode(:b), RequiredParameterNode(:c)],
|
|
[],
|
|
nil,
|
|
[],
|
|
[],
|
|
nil,
|
|
nil
|
|
),
|
|
nil,
|
|
[:a, :b, :c],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(a,b,c,);end", [
|
|
["unexpected `,` in parameters", 13..14]
|
|
]
|
|
end
|
|
|
|
def test_do_not_allow_trailing_commas_in_lambda_parameters
|
|
expected = LambdaNode(
|
|
[:a, :b],
|
|
Location(),
|
|
Location(),
|
|
Location(),
|
|
BlockParametersNode(
|
|
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b)], [], nil, [], [], nil, nil),
|
|
[],
|
|
Location(),
|
|
Location()
|
|
),
|
|
nil,
|
|
0
|
|
)
|
|
assert_errors expected, "-> (a, b, ) {}", [
|
|
["unexpected `,` in parameters", 8..9]
|
|
]
|
|
end
|
|
|
|
def test_do_not_allow_multiple_codepoints_in_a_single_character_literal
|
|
expected = StringNode(0, Location(), Location(), nil, "\u0001\u0002")
|
|
|
|
assert_errors expected, '?\u{0001 0002}', [
|
|
["invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal", 9..12]
|
|
]
|
|
end
|
|
|
|
def test_invalid_hex_escape
|
|
assert_errors expression('"\\xx"'), '"\\xx"', [
|
|
["invalid hexadecimal escape sequence", 1..3],
|
|
]
|
|
end
|
|
|
|
def test_do_not_allow_more_than_6_hexadecimal_digits_in_u_Unicode_character_notation
|
|
expected = StringNode(0, Location(), Location(), Location(), "\u0001")
|
|
|
|
assert_errors expected, '"\u{0000001}"', [
|
|
["invalid Unicode escape sequence; maximum length is 6 digits", 4..11],
|
|
]
|
|
end
|
|
|
|
def test_do_not_allow_characters_other_than_0_9_a_f_and_A_F_in_u_Unicode_character_notation
|
|
expected = StringNode(0, Location(), Location(), Location(), "\u0000z}")
|
|
|
|
assert_errors expected, '"\u{000z}"', [
|
|
["invalid Unicode escape sequence", 7..7],
|
|
]
|
|
end
|
|
|
|
def test_unterminated_unicode_brackets_should_be_a_syntax_error
|
|
assert_errors expression('?\\u{3'), '?\\u{3', [
|
|
["invalid Unicode escape sequence; needs closing `}`", 1..5],
|
|
]
|
|
end
|
|
|
|
def test_method_parameters_after_block
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[],
|
|
[],
|
|
nil,
|
|
[RequiredParameterNode(:a)],
|
|
[],
|
|
nil,
|
|
BlockParameterNode(:block, Location(), Location())
|
|
),
|
|
nil,
|
|
[:block, :a],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
assert_errors expected, "def foo(&block, a)\nend", [
|
|
["unexpected parameter order", 16..17]
|
|
]
|
|
end
|
|
|
|
def test_method_with_arguments_after_anonymous_block
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode([], [], nil, [RequiredParameterNode(:a)], [], nil, BlockParameterNode(nil, nil, Location())),
|
|
nil,
|
|
[:&, :a],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(&, a)\nend", [
|
|
["unexpected parameter order", 11..12]
|
|
]
|
|
end
|
|
|
|
def test_method_parameters_after_arguments_forwarding
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[],
|
|
[],
|
|
nil,
|
|
[RequiredParameterNode(:a)],
|
|
[],
|
|
ForwardingParameterNode(),
|
|
nil
|
|
),
|
|
nil,
|
|
[:"...", :a],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
assert_errors expected, "def foo(..., a)\nend", [
|
|
["unexpected parameter order", 13..14]
|
|
]
|
|
end
|
|
|
|
def test_keywords_parameters_before_required_parameters
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[],
|
|
[],
|
|
nil,
|
|
[RequiredParameterNode(:a)],
|
|
[RequiredKeywordParameterNode(:b, Location())],
|
|
nil,
|
|
nil
|
|
),
|
|
nil,
|
|
[:b, :a],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
assert_errors expected, "def foo(b:, a)\nend", [
|
|
["unexpected parameter order", 12..13]
|
|
]
|
|
end
|
|
|
|
def test_rest_keywords_parameters_before_required_parameters
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[],
|
|
[],
|
|
nil,
|
|
[],
|
|
[RequiredKeywordParameterNode(:b, Location())],
|
|
KeywordRestParameterNode(:rest, Location(), Location()),
|
|
nil
|
|
),
|
|
nil,
|
|
[:rest, :b],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(**rest, b:)\nend", [
|
|
["unexpected parameter order", 16..18]
|
|
]
|
|
end
|
|
|
|
def test_double_arguments_forwarding
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode([], [], nil, [], [], ForwardingParameterNode(), nil),
|
|
nil,
|
|
[:"..."],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(..., ...)\nend", [
|
|
["unexpected parameter order", 13..16]
|
|
]
|
|
end
|
|
|
|
def test_multiple_error_in_parameters_order
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[],
|
|
[],
|
|
nil,
|
|
[RequiredParameterNode(:a)],
|
|
[RequiredKeywordParameterNode(:b, Location())],
|
|
KeywordRestParameterNode(:args, Location(), Location()),
|
|
nil
|
|
),
|
|
nil,
|
|
[:args, :a, :b],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(**args, a, b:)\nend", [
|
|
["unexpected parameter order", 16..17],
|
|
["unexpected parameter order", 19..21]
|
|
]
|
|
end
|
|
|
|
def test_switching_to_optional_arguments_twice
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[],
|
|
[],
|
|
nil,
|
|
[RequiredParameterNode(:a)],
|
|
[RequiredKeywordParameterNode(:b, Location())],
|
|
KeywordRestParameterNode(:args, Location(), Location()),
|
|
nil
|
|
),
|
|
nil,
|
|
[:args, :a, :b],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
)
|
|
|
|
assert_errors expected, "def foo(**args, a, b:)\nend", [
|
|
["unexpected parameter order", 16..17],
|
|
["unexpected parameter order", 19..21]
|
|
]
|
|
end
|
|
|
|
def test_switching_to_named_arguments_twice
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[],
|
|
[],
|
|
nil,
|
|
[RequiredParameterNode(:a)],
|
|
[RequiredKeywordParameterNode(:b, Location())],
|
|
KeywordRestParameterNode(:args, Location(), Location()),
|
|
nil
|
|
),
|
|
nil,
|
|
[:args, :a, :b],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
)
|
|
|
|
assert_errors expected, "def foo(**args, a, b:)\nend", [
|
|
["unexpected parameter order", 16..17],
|
|
["unexpected parameter order", 19..21]
|
|
]
|
|
end
|
|
|
|
def test_returning_to_optional_parameters_multiple_times
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode(
|
|
[RequiredParameterNode(:a)],
|
|
[
|
|
OptionalParameterNode(:b, Location(), Location(), IntegerNode(IntegerBaseFlags::DECIMAL)),
|
|
OptionalParameterNode(:d, Location(), Location(), IntegerNode(IntegerBaseFlags::DECIMAL))
|
|
],
|
|
nil,
|
|
[RequiredParameterNode(:c), RequiredParameterNode(:e)],
|
|
[],
|
|
nil,
|
|
nil
|
|
),
|
|
nil,
|
|
[:a, :b, :c, :d, :e],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
)
|
|
|
|
assert_errors expected, "def foo(a, b = 1, c, d = 2, e)\nend", [
|
|
["unexpected parameter order", 23..24]
|
|
]
|
|
end
|
|
|
|
def test_case_without_when_clauses_errors_on_else_clause
|
|
expected = CaseMatchNode(
|
|
SymbolNode(Location(), Location(), nil, "a"),
|
|
[],
|
|
ElseNode(Location(), nil, Location()),
|
|
Location(),
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "case :a\nelse\nend", [
|
|
["expected a `when` or `in` clause after `case`", 0..4]
|
|
]
|
|
end
|
|
|
|
def test_case_without_clauses
|
|
expected = CaseNode(
|
|
SymbolNode(Location(), Location(), nil, "a"),
|
|
[],
|
|
nil,
|
|
Location(),
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "case :a\nend", [
|
|
["expected a `when` or `in` clause after `case`", 0..4]
|
|
]
|
|
end
|
|
|
|
def test_setter_method_cannot_be_defined_in_an_endless_method_definition
|
|
expected = DefNode(
|
|
:a=,
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
StatementsNode([IntegerNode(IntegerBaseFlags::DECIMAL)]),
|
|
[],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
Location(),
|
|
nil
|
|
)
|
|
|
|
assert_errors expected, "def a=() = 42", [
|
|
["invalid method name; a setter method cannot be defined in an endless method definition", 4..6]
|
|
]
|
|
end
|
|
|
|
def test_do_not_allow_forward_arguments_in_lambda_literals
|
|
expected = LambdaNode(
|
|
[],
|
|
Location(),
|
|
Location(),
|
|
Location(),
|
|
BlockParametersNode(ParametersNode([], [], nil, [], [], ForwardingParameterNode(), nil), [], Location(), Location()),
|
|
nil,
|
|
0
|
|
)
|
|
|
|
assert_errors expected, "->(...) {}", [
|
|
["unexpected `...` when the parent method is not forwarding", 3..6]
|
|
]
|
|
end
|
|
|
|
def test_do_not_allow_forward_arguments_in_blocks
|
|
expected = CallNode(
|
|
nil,
|
|
nil,
|
|
:a,
|
|
Location(),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
BlockNode(
|
|
[],
|
|
BlockParametersNode(ParametersNode([], [], nil, [], [], ForwardingParameterNode(), nil), [], Location(), Location()),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
0
|
|
),
|
|
0
|
|
)
|
|
|
|
assert_errors expected, "a {|...|}", [
|
|
["unexpected `...` when the parent method is not forwarding", 4..7]
|
|
]
|
|
end
|
|
|
|
def test_dont_allow_return_inside_class_body
|
|
expected = ClassNode(
|
|
[],
|
|
Location(),
|
|
ConstantReadNode(:A),
|
|
nil,
|
|
nil,
|
|
StatementsNode([ReturnNode(Location(), nil)]),
|
|
Location(),
|
|
:A
|
|
)
|
|
|
|
assert_errors expected, "class A; return; end", [
|
|
["invalid `return` in a class or module body", 15..16]
|
|
]
|
|
end
|
|
|
|
def test_dont_allow_return_inside_module_body
|
|
expected = ModuleNode(
|
|
[],
|
|
Location(),
|
|
ConstantReadNode(:A),
|
|
StatementsNode([ReturnNode(Location(), nil)]),
|
|
Location(),
|
|
:A
|
|
)
|
|
|
|
assert_errors expected, "module A; return; end", [
|
|
["invalid `return` in a class or module body", 16..17]
|
|
]
|
|
end
|
|
|
|
def test_dont_allow_setting_to_back_and_nth_reference
|
|
expected = BeginNode(
|
|
Location(),
|
|
StatementsNode([
|
|
GlobalVariableWriteNode(:$+, Location(), NilNode(), Location()),
|
|
GlobalVariableWriteNode(:$1466, Location(), NilNode(), Location())
|
|
]),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "begin\n$+ = nil\n$1466 = nil\nend", [
|
|
["immutable variable as a write target", 6..8],
|
|
["immutable variable as a write target", 15..20]
|
|
]
|
|
end
|
|
|
|
def test_duplicated_parameter_names
|
|
# For some reason, Ripper reports no error for Ruby 3.0 when you have
|
|
# duplicated parameter names for positional parameters.
|
|
unless RUBY_VERSION < "3.1.0"
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b), RequiredParameterNode(:a)], [], nil, [], [], nil, nil),
|
|
nil,
|
|
[:a, :b],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(a,b,a);end", [
|
|
["repeated parameter name", 12..13]
|
|
]
|
|
end
|
|
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b)], [], RestParameterNode(:a, Location(), Location()), [], [], nil, nil),
|
|
nil,
|
|
[:a, :b],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(a,b,*a);end", [
|
|
["repeated parameter name", 13..14]
|
|
]
|
|
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b)], [], nil, [], [], KeywordRestParameterNode(:a, Location(), Location()), nil),
|
|
nil,
|
|
[:a, :b],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(a,b,**a);end", [
|
|
["repeated parameter name", 14..15]
|
|
]
|
|
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode([RequiredParameterNode(:a), RequiredParameterNode(:b)], [], nil, [], [], nil, BlockParameterNode(:a, Location(), Location())),
|
|
nil,
|
|
[:a, :b],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(a,b,&a);end", [
|
|
["repeated parameter name", 13..14]
|
|
]
|
|
|
|
expected = DefNode(
|
|
:foo,
|
|
Location(),
|
|
nil,
|
|
ParametersNode([], [OptionalParameterNode(:a, Location(), Location(), IntegerNode(IntegerBaseFlags::DECIMAL))], RestParameterNode(:c, Location(), Location()), [RequiredParameterNode(:b)], [], nil, nil),
|
|
nil,
|
|
[:a, :b, :c],
|
|
Location(),
|
|
nil,
|
|
Location(),
|
|
Location(),
|
|
nil,
|
|
Location()
|
|
)
|
|
|
|
assert_errors expected, "def foo(a = 1,b,*c);end", [["unexpected parameter `*`", 16..17]]
|
|
end
|
|
|
|
def test_invalid_message_name
|
|
result = Prism.parse("+.@foo,+=foo")
|
|
assert_equal :"", result.value.statements.body.first.write_name
|
|
end
|
|
|
|
def test_invalid_operator_write_fcall
|
|
source = "foo! += 1"
|
|
assert_errors expression(source), source, [
|
|
["unexpected write target", 0..4]
|
|
]
|
|
end
|
|
|
|
def test_invalid_operator_write_dot
|
|
source = "foo.+= 1"
|
|
assert_errors expression(source), source, [
|
|
["unexpected write target", 5..6]
|
|
]
|
|
end
|
|
|
|
def test_unterminated_global_variable
|
|
assert_errors expression("$"), "$", [
|
|
["invalid global variable", 0..1]
|
|
]
|
|
end
|
|
|
|
def test_invalid_global_variable_write
|
|
assert_errors expression("$',"), "$',", [
|
|
["immutable variable as a write target", 0..2],
|
|
["unexpected write target", 0..2]
|
|
]
|
|
end
|
|
|
|
def test_invalid_multi_target
|
|
error_messages = ["unexpected write target"]
|
|
immutable = "immutable variable as a write target"
|
|
|
|
assert_error_messages "foo,", error_messages
|
|
assert_error_messages "foo = 1; foo,", error_messages
|
|
assert_error_messages "foo.bar,", error_messages
|
|
assert_error_messages "*foo,", error_messages
|
|
assert_error_messages "@foo,", error_messages
|
|
assert_error_messages "@@foo,", error_messages
|
|
assert_error_messages "$foo,", error_messages
|
|
assert_error_messages "$1,", [immutable, *error_messages]
|
|
assert_error_messages "$+,", [immutable, *error_messages]
|
|
assert_error_messages "Foo,", error_messages
|
|
assert_error_messages "::Foo,", error_messages
|
|
assert_error_messages "Foo::Foo,", error_messages
|
|
assert_error_messages "Foo::foo,", error_messages
|
|
assert_error_messages "foo[foo],", error_messages
|
|
assert_error_messages "(foo, bar)", error_messages
|
|
assert_error_messages "foo((foo, bar))", error_messages
|
|
assert_error_messages "foo((*))", error_messages
|
|
assert_error_messages "foo(((foo, bar), *))", error_messages
|
|
assert_error_messages "(foo, bar) + 1", error_messages
|
|
assert_error_messages "(foo, bar) in baz", error_messages
|
|
end
|
|
|
|
def test_call_with_block_and_write
|
|
source = "foo {} &&= 1"
|
|
assert_errors expression(source), source, [
|
|
["unexpected write target", 0..6],
|
|
["unexpected operator after a call with a block", 7..10]
|
|
]
|
|
end
|
|
|
|
def test_call_with_block_or_write
|
|
source = "foo {} ||= 1"
|
|
assert_errors expression(source), source, [
|
|
["unexpected write target", 0..6],
|
|
["unexpected operator after a call with a block", 7..10]
|
|
]
|
|
end
|
|
|
|
def test_call_with_block_operator_write
|
|
source = "foo {} += 1"
|
|
assert_errors expression(source), source, [
|
|
["unexpected write target", 0..6],
|
|
["unexpected operator after a call with a block", 7..9]
|
|
]
|
|
end
|
|
|
|
def test_index_call_with_block_and_write
|
|
source = "foo[1] {} &&= 1"
|
|
assert_errors expression(source), source, [
|
|
["unexpected write target", 0..9],
|
|
["unexpected operator after a call with arguments", 10..13],
|
|
["unexpected operator after a call with a block", 10..13]
|
|
]
|
|
end
|
|
|
|
def test_index_call_with_block_or_write
|
|
source = "foo[1] {} ||= 1"
|
|
assert_errors expression(source), source, [
|
|
["unexpected write target", 0..9],
|
|
["unexpected operator after a call with arguments", 10..13],
|
|
["unexpected operator after a call with a block", 10..13]
|
|
]
|
|
end
|
|
|
|
def test_index_call_with_block_operator_write
|
|
source = "foo[1] {} += 1"
|
|
assert_errors expression(source), source, [
|
|
["unexpected write target", 0..9],
|
|
["unexpected operator after a call with arguments", 10..12],
|
|
["unexpected operator after a call with a block", 10..12]
|
|
]
|
|
end
|
|
|
|
def test_writing_numbered_parameter
|
|
assert_errors expression("-> { _1 = 0 }"), "-> { _1 = 0 }", [
|
|
["_1 is reserved for a numbered parameter", 5..7]
|
|
]
|
|
end
|
|
|
|
def test_targeting_numbered_parameter
|
|
assert_errors expression("-> { _1, = 0 }"), "-> { _1, = 0 }", [
|
|
["_1 is reserved for a numbered parameter", 5..7]
|
|
]
|
|
end
|
|
|
|
def test_defining_numbered_parameter
|
|
error_messages = ["_1 is reserved for a numbered parameter"]
|
|
|
|
assert_error_messages "def _1; end", error_messages
|
|
assert_error_messages "def self._1; end", error_messages
|
|
end
|
|
|
|
def test_double_scope_numbered_parameters
|
|
source = "-> { _1 + -> { _2 } }"
|
|
errors = [["numbered parameter is already used in outer scope", 15..17]]
|
|
|
|
assert_errors expression(source), source, errors, compare_ripper: false
|
|
end
|
|
|
|
def test_invalid_number_underscores
|
|
error_messages = ["invalid underscore placement in number"]
|
|
|
|
assert_error_messages "1__1", error_messages
|
|
assert_error_messages "0b1__1", error_messages
|
|
assert_error_messages "0o1__1", error_messages
|
|
assert_error_messages "01__1", error_messages
|
|
assert_error_messages "0d1__1", error_messages
|
|
assert_error_messages "0x1__1", error_messages
|
|
|
|
assert_error_messages "1_1_", error_messages
|
|
assert_error_messages "0b1_1_", error_messages
|
|
assert_error_messages "0o1_1_", error_messages
|
|
assert_error_messages "01_1_", error_messages
|
|
assert_error_messages "0d1_1_", error_messages
|
|
assert_error_messages "0x1_1_", error_messages
|
|
end
|
|
|
|
def test_alnum_delimiters
|
|
error_messages = ["invalid `%` token"]
|
|
|
|
assert_error_messages "%qXfooX", error_messages
|
|
assert_error_messages "%QXfooX", error_messages
|
|
assert_error_messages "%wXfooX", error_messages
|
|
assert_error_messages "%WxfooX", error_messages
|
|
assert_error_messages "%iXfooX", error_messages
|
|
assert_error_messages "%IXfooX", error_messages
|
|
assert_error_messages "%xXfooX", error_messages
|
|
assert_error_messages "%rXfooX", error_messages
|
|
assert_error_messages "%sXfooX", error_messages
|
|
end
|
|
|
|
def test_begin_at_toplevel
|
|
source = "def foo; BEGIN {}; end"
|
|
assert_errors expression(source), source, [
|
|
["BEGIN is permitted only at toplevel", 9..14],
|
|
]
|
|
end
|
|
|
|
def test_numbered_parameters_in_block_arguments
|
|
source = "foo { |_1| }"
|
|
assert_errors expression(source), source, [
|
|
["_1 is reserved for a numbered parameter", 7..9],
|
|
]
|
|
end
|
|
|
|
def test_conditional_predicate_closed
|
|
source = "if 0 0; elsif 0 0; end\nunless 0 0; end"
|
|
assert_errors expression(source), source, [
|
|
["expected `then` or `;` or '\n" + "'", 5..6],
|
|
["expected `then` or `;` or '\n" + "'", 16..17],
|
|
["expected `then` or `;` or '\n" + "'", 32..33],
|
|
]
|
|
end
|
|
|
|
def test_parameter_name_ending_with_bang_or_question_mark
|
|
source = "def foo(x!,y?); end"
|
|
errors = [
|
|
["unexpected name for a parameter", 8..10],
|
|
["unexpected name for a parameter", 11..13]
|
|
]
|
|
assert_errors expression(source), source, errors, compare_ripper: false
|
|
end
|
|
|
|
def test_class_name
|
|
source = "class 0.X end"
|
|
assert_errors expression(source), source, [
|
|
["expected a constant name after `class`", 6..9],
|
|
]
|
|
end
|
|
|
|
def test_loop_conditional_is_closed
|
|
source = "while 0 0; foo; end; until 0 0; foo; end"
|
|
assert_errors expression(source), source, [
|
|
["expected a predicate expression for the `while` statement", 7..7],
|
|
["expected a predicate expression for the `until` statement", 28..28],
|
|
]
|
|
end
|
|
|
|
def test_forwarding_arg_after_keyword_rest
|
|
source = "def f(**,...);end"
|
|
assert_errors expression(source), source, [
|
|
["unexpected `...` in parameters", 9..12],
|
|
]
|
|
end
|
|
|
|
def test_semicolon_after_inheritance_operator
|
|
source = "class Foo < Bar end"
|
|
assert_errors expression(source), source, [
|
|
["unexpected `end`, expecting ';' or '\n'", 15..15],
|
|
]
|
|
end
|
|
|
|
def test_shadow_args_in_lambda
|
|
source = "->a;b{}"
|
|
assert_errors expression(source), source, [
|
|
["expected a `do` keyword or a `{` to open the lambda block", 3..3],
|
|
["expected a newline or semicolon after the statement", 7..7],
|
|
["cannot parse the expression", 7..7],
|
|
["expected a lambda block beginning with `do` to end with `end`", 7..7],
|
|
]
|
|
end
|
|
|
|
def test_shadow_args_in_block
|
|
source = "tap{|a;a|}"
|
|
assert_errors expression(source), source, [
|
|
["repeated parameter name", 7..8],
|
|
]
|
|
end
|
|
|
|
def test_repeated_parameter_name_in_destructured_params
|
|
source = "def f(a, (b, (a))); end"
|
|
# In Ruby 3.0.x, `Ripper.sexp_raw` does not return `nil` for this case.
|
|
compare_ripper = RUBY_ENGINE == "ruby" && (RUBY_VERSION.split('.').map { |x| x.to_i } <=> [3, 1]) >= 1
|
|
assert_errors expression(source), source, [
|
|
["repeated parameter name", 14..15],
|
|
], compare_ripper: compare_ripper
|
|
end
|
|
|
|
def test_assign_to_numbered_parameter
|
|
source = <<~RUBY
|
|
a in _1
|
|
a => _1
|
|
1 => a, _1
|
|
1 in a, _1
|
|
/(?<_1>)/ =~ a
|
|
RUBY
|
|
|
|
message = "_1 is reserved for a numbered parameter"
|
|
assert_errors expression(source), source, [
|
|
[message, 5..7],
|
|
[message, 13..15],
|
|
[message, 24..26],
|
|
[message, 35..37],
|
|
[message, 42..44]
|
|
]
|
|
end
|
|
|
|
def test_symbol_in_keyword_parameter
|
|
source = "def foo(x:'y':); end"
|
|
assert_errors expression(source), source, [
|
|
["expected a closing delimiter for the string literal", 14..14],
|
|
]
|
|
end
|
|
|
|
def test_symbol_in_hash
|
|
source = "{x:'y':}"
|
|
assert_errors expression(source), source, [
|
|
["expected a closing delimiter for the string literal", 7..7],
|
|
]
|
|
end
|
|
|
|
def test_while_endless_method
|
|
source = "while def f = g do end"
|
|
assert_errors expression(source), source, [
|
|
['expected a predicate expression for the `while` statement', 22..22],
|
|
['cannot parse the expression', 22..22],
|
|
['expected an `end` to close the `while` statement', 22..22]
|
|
]
|
|
end
|
|
|
|
def test_match_plus
|
|
source = <<~RUBY
|
|
a in b + c
|
|
a => b + c
|
|
RUBY
|
|
message1 = 'expected a newline or semicolon after the statement'
|
|
message2 = 'cannot parse the expression'
|
|
assert_errors expression(source), source, [
|
|
[message1, 6..6],
|
|
[message2, 6..6],
|
|
[message1, 17..17],
|
|
[message2, 17..17],
|
|
]
|
|
end
|
|
|
|
def test_rational_number_with_exponential_portion
|
|
source = '1e1r; 1e1ri'
|
|
message = 'expected a newline or semicolon after the statement'
|
|
assert_errors expression(source), source, [
|
|
[message, 3..3],
|
|
[message, 9..9]
|
|
]
|
|
end
|
|
|
|
def test_check_value_expression
|
|
source = <<~RUBY
|
|
1 => ^(return)
|
|
while true
|
|
1 => ^(break)
|
|
1 => ^(next)
|
|
1 => ^(redo)
|
|
1 => ^(retry)
|
|
1 => ^(2 => a)
|
|
end
|
|
1 => ^(if 1; (return) else (return) end)
|
|
1 => ^(unless 1; (return) else (return) end)
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 7..13],
|
|
[message, 35..40],
|
|
[message, 51..55],
|
|
[message, 66..70],
|
|
[message, 81..86],
|
|
[message, 97..103],
|
|
[message, 123..129],
|
|
[message, 168..174],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_statement
|
|
source = <<~RUBY
|
|
if (return)
|
|
end
|
|
unless (return)
|
|
end
|
|
while (return)
|
|
end
|
|
until (return)
|
|
end
|
|
case (return)
|
|
when 1
|
|
end
|
|
class A < (return)
|
|
end
|
|
class << (return)
|
|
end
|
|
for x in (return)
|
|
end
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 4..10],
|
|
[message, 24..30],
|
|
[message, 43..49],
|
|
[message, 62..68],
|
|
[message, 80..86],
|
|
[message, 110..116],
|
|
[message, 132..138],
|
|
[message, 154..160],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_def
|
|
source = <<~RUBY
|
|
def (return).x
|
|
end
|
|
def x(a = return)
|
|
end
|
|
def x(a: return)
|
|
end
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 5..11],
|
|
[message, 29..35],
|
|
[message, 50..56],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_assignment
|
|
source = <<~RUBY
|
|
a = return
|
|
a = 1, return
|
|
a, b = return, 1
|
|
a, b = 1, *return
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 4..10],
|
|
[message, 18..24],
|
|
[message, 32..38],
|
|
[message, 53..59],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_modifier
|
|
source = <<~RUBY
|
|
1 if (return)
|
|
1 unless (return)
|
|
1 while (return)
|
|
1 until (return)
|
|
(return) => a
|
|
(return) in a
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 6..12],
|
|
[message, 24..30],
|
|
[message, 41..47],
|
|
[message, 58..64],
|
|
[message, 67..73],
|
|
[message, 81..87]
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_expression
|
|
source = <<~RUBY
|
|
(return) ? 1 : 1
|
|
(return)..1
|
|
1..(return)
|
|
(return)...1
|
|
1...(return)
|
|
(..(return))
|
|
(...(return))
|
|
((return)..)
|
|
((return)...)
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 1..7],
|
|
[message, 18..24],
|
|
[message, 33..39],
|
|
[message, 42..48],
|
|
[message, 59..65],
|
|
[message, 71..77],
|
|
[message, 85..91],
|
|
[message, 96..102],
|
|
[message, 109..115]
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_hash
|
|
source = <<~RUBY
|
|
{ return => 1 }
|
|
{ 1 => return }
|
|
{ a: return }
|
|
{ **return }
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 2..8],
|
|
[message, 23..29],
|
|
[message, 37..43],
|
|
[message, 50..56],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_call
|
|
source = <<~RUBY
|
|
(return).foo
|
|
(return).(1)
|
|
(return)[1]
|
|
(return)[1] = 2
|
|
(return)::foo
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 1..7],
|
|
[message, 14..20],
|
|
[message, 27..33],
|
|
[message, 39..45],
|
|
[message, 55..61],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_constant_path
|
|
source = <<~RUBY
|
|
(return)::A
|
|
class (return)::A; end
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 1..7],
|
|
[message, 19..25],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_arguments
|
|
source = <<~RUBY
|
|
foo(return)
|
|
foo(1, return)
|
|
foo(*return)
|
|
foo(**return)
|
|
foo(&return)
|
|
foo(return => 1)
|
|
foo(:a => return)
|
|
foo(a: return)
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 4..10],
|
|
[message, 19..25],
|
|
[message, 32..38],
|
|
[message, 46..52],
|
|
[message, 59..65],
|
|
[message, 71..77],
|
|
[message, 94..100],
|
|
[message, 109..115],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_unary_call
|
|
source = <<~RUBY
|
|
+(return)
|
|
not return
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 2..8],
|
|
[message, 14..20],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_void_value_expression_in_binary_call
|
|
source = <<~RUBY
|
|
1 + (return)
|
|
(return) + 1
|
|
1 and (return)
|
|
(return) and 1
|
|
1 or (return)
|
|
(return) or 1
|
|
RUBY
|
|
message = 'unexpected void value expression'
|
|
assert_errors expression(source), source, [
|
|
[message, 5..11],
|
|
[message, 14..20],
|
|
[message, 42..48],
|
|
[message, 71..77],
|
|
], compare_ripper: false # Ripper does not check 'void value expression'.
|
|
end
|
|
|
|
def test_trailing_comma_in_calls
|
|
assert_errors expression("foo 1,"), "foo 1,", [
|
|
["expected an argument", 5..6]
|
|
]
|
|
end
|
|
|
|
def test_argument_after_ellipsis
|
|
source = 'def foo(...); foo(..., 1); end'
|
|
assert_errors expression(source), source, [
|
|
['unexpected argument after `...`', 23..24]
|
|
]
|
|
end
|
|
|
|
def test_ellipsis_in_no_paren_call
|
|
source = 'def foo(...); foo 1, ...; end'
|
|
assert_errors expression(source), source, [
|
|
['unexpected `...` in an non-parenthesized call', 21..24]
|
|
]
|
|
end
|
|
|
|
def test_non_assoc_range
|
|
source = '1....2'
|
|
assert_errors expression(source), source, [
|
|
['expected a newline or semicolon after the statement', 4..4],
|
|
['cannot parse the expression', 4..4],
|
|
]
|
|
end
|
|
|
|
def test_upcase_end_in_def
|
|
assert_warning_messages "def foo; END { }; end", [
|
|
"END in method; use at_exit"
|
|
]
|
|
end
|
|
|
|
def test_statement_operators
|
|
source = <<~RUBY
|
|
alias x y + 1
|
|
alias x y.z
|
|
BEGIN { bar } + 1
|
|
BEGIN { bar }.z
|
|
END { bar } + 1
|
|
END { bar }.z
|
|
undef x + 1
|
|
undef x.z
|
|
RUBY
|
|
message1 = 'expected a newline or semicolon after the statement'
|
|
message2 = 'cannot parse the expression'
|
|
assert_errors expression(source), source, [
|
|
[message1, 9..9],
|
|
[message2, 9..9],
|
|
[message1, 23..23],
|
|
[message2, 23..23],
|
|
[message1, 39..39],
|
|
[message2, 39..39],
|
|
[message1, 57..57],
|
|
[message2, 57..57],
|
|
[message1, 71..71],
|
|
[message2, 71..71],
|
|
[message1, 87..87],
|
|
[message2, 87..87],
|
|
[message1, 97..97],
|
|
[message2, 97..97],
|
|
[message1, 109..109],
|
|
[message2, 109..109],
|
|
]
|
|
end
|
|
|
|
def test_statement_at_non_statement
|
|
source = <<~RUBY
|
|
foo(alias x y)
|
|
foo(BEGIN { bar })
|
|
foo(END { bar })
|
|
foo(undef x)
|
|
RUBY
|
|
assert_errors expression(source), source, [
|
|
['unexpected an `alias` at a non-statement position', 4..9],
|
|
['unexpected a `BEGIN` at a non-statement position', 19..24],
|
|
['unexpected an `END` at a non-statement position', 38..41],
|
|
['unexpected an `undef` at a non-statement position', 55..60],
|
|
]
|
|
end
|
|
|
|
def test_binary_range_with_left_unary_range
|
|
source = <<~RUBY
|
|
..1..
|
|
...1..
|
|
RUBY
|
|
message1 = 'expected a newline or semicolon after the statement'
|
|
message2 = 'cannot parse the expression'
|
|
assert_errors expression(source), source, [
|
|
[message1, 3..3],
|
|
[message2, 3..3],
|
|
[message1, 10..10],
|
|
[message2, 10..10],
|
|
]
|
|
end
|
|
|
|
def test_circular_param
|
|
source = <<~RUBY
|
|
def foo(bar = bar) = 42
|
|
def foo(bar: bar) = 42
|
|
proc { |foo = foo| }
|
|
proc { |foo: foo| }
|
|
RUBY
|
|
message = 'parameter default value references itself'
|
|
assert_errors expression(source), source, [
|
|
[message, 14..17],
|
|
[message, 37..40],
|
|
[message, 61..64],
|
|
[message, 81..84],
|
|
], compare_ripper: false # Ripper does not check 'circular reference'.
|
|
end
|
|
|
|
private
|
|
|
|
def assert_errors(expected, source, errors, compare_ripper: RUBY_ENGINE == "ruby")
|
|
# Ripper behaves differently on JRuby/TruffleRuby, so only check this on CRuby
|
|
assert_nil Ripper.sexp_raw(source) if compare_ripper
|
|
|
|
result = Prism.parse(source)
|
|
node = result.value.statements.body.last
|
|
|
|
assert_equal_nodes(expected, node, compare_location: false)
|
|
assert_equal(errors, result.errors.map { |e| [e.message, e.location.start_offset..e.location.end_offset] })
|
|
end
|
|
|
|
def assert_error_messages(source, errors, compare_ripper: RUBY_ENGINE == "ruby")
|
|
assert_nil Ripper.sexp_raw(source) if compare_ripper
|
|
result = Prism.parse(source)
|
|
assert_equal(errors, result.errors.map(&:message))
|
|
end
|
|
|
|
def assert_warning_messages(source, warnings)
|
|
result = Prism.parse(source)
|
|
assert_equal(warnings, result.warnings.map(&:message))
|
|
end
|
|
|
|
def expression(source)
|
|
Prism.parse(source).value.statements.body.last
|
|
end
|
|
end
|
|
end
|