[ruby/prism] Error messages closer to CRuby
https://github.com/ruby/prism/commit/19ffa0b980
This commit is contained in:
parent
e4d3e652ff
commit
e08c128417
@ -159,7 +159,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
|
||||
[PM_ERR_ESCAPE_INVALID_UNICODE_LONG] = { "invalid Unicode escape sequence; maximum length is 6 digits", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_ESCAPE_INVALID_UNICODE_TERM] = { "invalid Unicode escape sequence; needs closing `}`", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_EXPECT_ARGUMENT] = { "expected an argument", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "expected a newline or semicolon after the statement", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "unexpected %s, expecting end-of-input", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ] = { "expected an expression after `&&=`", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = { "expected an expression after `||=`", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA] = { "expected an expression after `,`", PM_ERROR_LEVEL_FATAL },
|
||||
@ -190,8 +190,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
|
||||
[PM_ERR_HASH_VALUE] = { "expected a value in the hash literal", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_HEREDOC_TERM] = { "could not find a terminator for the heredoc", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INCOMPLETE_QUESTION_MARK] = { "incomplete expression at `?`", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "incomplete class variable", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "incomplete instance variable", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "`%.*s' is not allowed as an class variable name", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "`%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INVALID_FLOAT_EXPONENT] = { "invalid exponent", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INVALID_NUMBER_BINARY] = { "invalid binary number", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INVALID_NUMBER_DECIMAL] = { "invalid decimal number", PM_ERROR_LEVEL_FATAL },
|
||||
@ -202,7 +202,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
|
||||
[PM_ERR_INVALID_MULTIBYTE_CHARACTER] = { "invalid multibyte character 0x%X", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INVALID_PRINTABLE_CHARACTER] = { "invalid character `%c`", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INVALID_PERCENT] = { "invalid `%` token", PM_ERROR_LEVEL_FATAL }, // TODO WHAT?
|
||||
[PM_ERR_INVALID_VARIABLE_GLOBAL] = { "invalid global variable", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_INVALID_VARIABLE_GLOBAL] = { "`%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_IT_NOT_ALLOWED] = { "`it` is not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_LAMBDA_OPEN] = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_LAMBDA_TERM_BRACE] = { "expected a lambda block beginning with `{` to end with `}`", PM_ERROR_LEVEL_FATAL },
|
||||
@ -221,6 +221,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
|
||||
[PM_ERR_MODULE_NAME] = { "expected a constant name after `module`", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_MODULE_TERM] = { "expected an `end` to close the `module` statement", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_MULTI_ASSIGN_MULTI_SPLATS] = { "multiple splats in multiple assignment", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST] = { "unexpected '%.*s' resulting in multiple splats in multiple assignment", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_NOT_EXPRESSION] = { "expected an expression after `not`", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_NO_LOCAL_VARIABLE] = { "%.*s: no such local variable", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_NUMBER_LITERAL_UNDERSCORE] = { "number literal ending with a `_`", PM_ERROR_LEVEL_FATAL },
|
||||
@ -274,7 +275,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
|
||||
[PM_ERR_STATEMENT_UNDEF] = { "unexpected an `undef` at a non-statement position", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_STRING_CONCATENATION] = { "expected a string for concatenation", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_STRING_INTERPOLATED_TERM] = { "expected a closing delimiter for the interpolated string", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_STRING_LITERAL_TERM] = { "expected a closing delimiter for the string literal", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_STRING_LITERAL_EOF] = { "unterminated string meets end of file", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_STRING_LITERAL_TERM] = { "unexpected %s, expected a string literal terminator", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_FATAL }, // TODO expected symbol? prism.c ~9719
|
||||
[PM_ERR_SYMBOL_TERM_DYNAMIC] = { "expected a closing delimiter for the dynamic symbol", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_SYMBOL_TERM_INTERPOLATED] = { "expected a closing delimiter for the interpolated symbol", PM_ERROR_LEVEL_FATAL },
|
||||
@ -292,7 +294,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
|
||||
[PM_ERR_VOID_EXPRESSION] = { "unexpected void value expression", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_WHILE_TERM] = { "expected an `end` to close the `while` statement", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_WRITE_TARGET_IN_METHOD] = { "dynamic constant assignment", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_WRITE_TARGET_READONLY] = { "immutable variable as a write target", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_WRITE_TARGET_READONLY] = { "Can't set variable %.*s", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_WRITE_TARGET_UNEXPECTED] = { "unexpected write target", PM_ERROR_LEVEL_FATAL },
|
||||
[PM_ERR_XSTRING_TERM] = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_LEVEL_FATAL },
|
||||
|
||||
|
@ -219,6 +219,7 @@ typedef enum {
|
||||
PM_ERR_MODULE_NAME,
|
||||
PM_ERR_MODULE_TERM,
|
||||
PM_ERR_MULTI_ASSIGN_MULTI_SPLATS,
|
||||
PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST,
|
||||
PM_ERR_NOT_EXPRESSION,
|
||||
PM_ERR_NO_LOCAL_VARIABLE,
|
||||
PM_ERR_NUMBER_LITERAL_UNDERSCORE,
|
||||
@ -272,6 +273,7 @@ typedef enum {
|
||||
PM_ERR_STATEMENT_UNDEF,
|
||||
PM_ERR_STRING_CONCATENATION,
|
||||
PM_ERR_STRING_INTERPOLATED_TERM,
|
||||
PM_ERR_STRING_LITERAL_EOF,
|
||||
PM_ERR_STRING_LITERAL_TERM,
|
||||
PM_ERR_SYMBOL_INVALID,
|
||||
PM_ERR_SYMBOL_TERM_DYNAMIC,
|
||||
|
@ -492,7 +492,8 @@ pm_parser_err(pm_parser_t *parser, const uint8_t *start, const uint8_t *end, pm_
|
||||
/**
|
||||
* Append an error to the list of errors on the parser using a format string.
|
||||
*/
|
||||
#define PM_PARSER_ERR_FORMAT(parser, start, end, diag_id, ...) pm_diagnostic_list_append_format(&parser->error_list, start, end, diag_id, __VA_ARGS__)
|
||||
#define PM_PARSER_ERR_FORMAT(parser, start, end, diag_id, ...) \
|
||||
pm_diagnostic_list_append_format(&parser->error_list, start, end, diag_id, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Append an error to the list of errors on the parser using the location of the
|
||||
@ -507,7 +508,8 @@ pm_parser_err_current(pm_parser_t *parser, pm_diagnostic_id_t diag_id) {
|
||||
* Append an error to the list of errors on the parser using the given location
|
||||
* using a format string.
|
||||
*/
|
||||
#define PM_PARSER_ERR_LOCATION_FORMAT(parser, location, diag_id, ...) pm_diagnostic_list_append_format(&parser->error_list, (location)->start, (location)->end, diag_id, __VA_ARGS__)
|
||||
#define PM_PARSER_ERR_LOCATION_FORMAT(parser, location, diag_id, ...) \
|
||||
PM_PARSER_ERR_FORMAT(parser, (location)->start, (location)->end, diag_id, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Append an error to the list of errors on the parser using the location of the
|
||||
@ -522,7 +524,15 @@ pm_parser_err_node(pm_parser_t *parser, const pm_node_t *node, pm_diagnostic_id_
|
||||
* Append an error to the list of errors on the parser using the location of the
|
||||
* given node and a format string.
|
||||
*/
|
||||
#define PM_PARSER_ERR_NODE_FORMAT(parser, node, diag_id, ...) pm_diagnostic_list_append_format(&parser->error_list, node->location.start, node->location.end, diag_id, __VA_ARGS__)
|
||||
#define PM_PARSER_ERR_NODE_FORMAT(parser, node, diag_id, ...) \
|
||||
PM_PARSER_ERR_FORMAT(parser, (node)->location.start, (node)->location.end, diag_id, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Append an error to the list of errors on the parser using the location of the
|
||||
* given node and a format string, and add on the content of the node.
|
||||
*/
|
||||
#define PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, node, diag_id) \
|
||||
PM_PARSER_ERR_NODE_FORMAT(parser, node, diag_id, (int) ((node)->location.end - (node)->location.start), (const char *) (node)->location.start)
|
||||
|
||||
/**
|
||||
* Append an error to the list of errors on the parser using the location of the
|
||||
@ -546,7 +556,15 @@ pm_parser_err_token(pm_parser_t *parser, const pm_token_t *token, pm_diagnostic_
|
||||
* Append an error to the list of errors on the parser using the location of the
|
||||
* given token and a format string.
|
||||
*/
|
||||
#define PM_PARSER_ERR_TOKEN_FORMAT(parser, token, diag_id, ...) pm_diagnostic_list_append_format(&parser->error_list, (token).start, (token).end, diag_id, __VA_ARGS__)
|
||||
#define PM_PARSER_ERR_TOKEN_FORMAT(parser, token, diag_id, ...) \
|
||||
PM_PARSER_ERR_FORMAT(parser, (token).start, (token).end, diag_id, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Append an error to the list of errors on the parser using the location of the
|
||||
* given token and a format string, and add on the content of the token.
|
||||
*/
|
||||
#define PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, token, diag_id) \
|
||||
PM_PARSER_ERR_TOKEN_FORMAT(parser, token, diag_id, (int) ((token).end - (token).start), (const char *) (token).start)
|
||||
|
||||
/**
|
||||
* Append a warning to the list of warnings on the parser.
|
||||
@ -4643,13 +4661,20 @@ pm_multi_target_node_create(pm_parser_t *parser) {
|
||||
*/
|
||||
static void
|
||||
pm_multi_target_node_targets_append(pm_parser_t *parser, pm_multi_target_node_t *node, pm_node_t *target) {
|
||||
if (PM_NODE_TYPE_P(target, PM_SPLAT_NODE) || PM_NODE_TYPE_P(target, PM_IMPLICIT_REST_NODE)) {
|
||||
if (PM_NODE_TYPE_P(target, PM_SPLAT_NODE)) {
|
||||
if (node->rest == NULL) {
|
||||
node->rest = target;
|
||||
} else {
|
||||
pm_parser_err_node(parser, target, PM_ERR_MULTI_ASSIGN_MULTI_SPLATS);
|
||||
pm_node_list_append(&node->rights, target);
|
||||
}
|
||||
} else if (PM_NODE_TYPE_P(target, PM_IMPLICIT_REST_NODE)) {
|
||||
if (node->rest == NULL) {
|
||||
node->rest = target;
|
||||
} else {
|
||||
PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST);
|
||||
pm_node_list_append(&node->rights, target);
|
||||
}
|
||||
} else if (node->rest == NULL) {
|
||||
pm_node_list_append(&node->lefts, target);
|
||||
} else {
|
||||
@ -7173,7 +7198,7 @@ lex_numeric(pm_parser_t *parser) {
|
||||
static pm_token_type_t
|
||||
lex_global_variable(pm_parser_t *parser) {
|
||||
if (parser->current.end >= parser->end) {
|
||||
pm_parser_err_current(parser, PM_ERR_INVALID_VARIABLE_GLOBAL);
|
||||
PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_ERR_INVALID_VARIABLE_GLOBAL);
|
||||
return PM_TOKEN_GLOBAL_VARIABLE;
|
||||
}
|
||||
|
||||
@ -7214,7 +7239,7 @@ lex_global_variable(pm_parser_t *parser) {
|
||||
} while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0);
|
||||
|
||||
// $0 isn't allowed to be followed by anything.
|
||||
pm_parser_err_current(parser, PM_ERR_INVALID_VARIABLE_GLOBAL);
|
||||
PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_ERR_INVALID_VARIABLE_GLOBAL);
|
||||
}
|
||||
|
||||
return PM_TOKEN_GLOBAL_VARIABLE;
|
||||
@ -7245,7 +7270,7 @@ lex_global_variable(pm_parser_t *parser) {
|
||||
} else {
|
||||
// If we get here, then we have a $ followed by something that isn't
|
||||
// recognized as a global variable.
|
||||
pm_parser_err_current(parser, PM_ERR_INVALID_VARIABLE_GLOBAL);
|
||||
PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_ERR_INVALID_VARIABLE_GLOBAL);
|
||||
}
|
||||
|
||||
return PM_TOKEN_GLOBAL_VARIABLE;
|
||||
@ -8149,10 +8174,10 @@ lex_at_variable(pm_parser_t *parser) {
|
||||
while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0) {
|
||||
parser->current.end += width;
|
||||
}
|
||||
} else if (type == PM_TOKEN_CLASS_VARIABLE) {
|
||||
pm_parser_err_current(parser, PM_ERR_INCOMPLETE_VARIABLE_CLASS);
|
||||
} else {
|
||||
pm_parser_err_current(parser, PM_ERR_INCOMPLETE_VARIABLE_INSTANCE);
|
||||
pm_diagnostic_id_t diag_id = (type == PM_TOKEN_CLASS_VARIABLE) ? PM_ERR_INCOMPLETE_VARIABLE_CLASS : PM_ERR_INCOMPLETE_VARIABLE_INSTANCE;
|
||||
size_t width = parser->encoding->char_width(parser->current.end, parser->end - parser->current.end);
|
||||
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, diag_id, (int) ((parser->current.end + width) - parser->current.start), (const char *) parser->current.start);
|
||||
}
|
||||
|
||||
// If we're lexing an embedded variable, then we need to pop back into the
|
||||
@ -11055,7 +11080,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target) {
|
||||
return target;
|
||||
case PM_BACK_REFERENCE_READ_NODE:
|
||||
case PM_NUMBERED_REFERENCE_READ_NODE:
|
||||
pm_parser_err_node(parser, target, PM_ERR_WRITE_TARGET_READONLY);
|
||||
PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, target, PM_ERR_WRITE_TARGET_READONLY);
|
||||
return target;
|
||||
case PM_GLOBAL_VARIABLE_READ_NODE:
|
||||
assert(sizeof(pm_global_variable_target_node_t) == sizeof(pm_global_variable_read_node_t));
|
||||
@ -11193,7 +11218,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod
|
||||
}
|
||||
case PM_BACK_REFERENCE_READ_NODE:
|
||||
case PM_NUMBERED_REFERENCE_READ_NODE:
|
||||
pm_parser_err_node(parser, target, PM_ERR_WRITE_TARGET_READONLY);
|
||||
PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, target, PM_ERR_WRITE_TARGET_READONLY);
|
||||
/* fallthrough */
|
||||
case PM_GLOBAL_VARIABLE_READ_NODE: {
|
||||
pm_global_variable_write_node_t *node = pm_global_variable_write_node_create(parser, target, operator, value);
|
||||
@ -11368,7 +11393,7 @@ parse_targets(pm_parser_t *parser, pm_node_t *first_target, pm_binding_power_t b
|
||||
pm_multi_target_node_targets_append(parser, result, target);
|
||||
} else if (!match1(parser, PM_TOKEN_EOF)) {
|
||||
// If we get here, then we have a trailing , in a multi target node.
|
||||
// We'll set the implicit rest flag to indicate this.
|
||||
// We'll add an implicit rest node to represent this.
|
||||
pm_node_t *rest = (pm_node_t *) pm_implicit_rest_node_create(parser, &parser->previous);
|
||||
pm_multi_target_node_targets_append(parser, result, rest);
|
||||
break;
|
||||
@ -11458,8 +11483,13 @@ parse_statements(pm_parser_t *parser, pm_context_t context) {
|
||||
|
||||
while (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON));
|
||||
if (context_terminator(context, &parser->current)) break;
|
||||
} else {
|
||||
expect1(parser, PM_TOKEN_NEWLINE, PM_ERR_EXPECT_EOL_AFTER_STATEMENT);
|
||||
} else if (!accept1(parser, PM_TOKEN_NEWLINE)) {
|
||||
// This is an inlined version of accept1 because the error that we
|
||||
// want to add has varargs. If this happens again, we should
|
||||
// probably extract a helper function.
|
||||
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_type_human(parser->current.type));
|
||||
parser->previous.start = parser->previous.end;
|
||||
parser->previous.type = PM_TOKEN_MISSING;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13853,7 +13883,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) {
|
||||
pm_constant_id_t name_id = pm_parser_constant_id_constant(parser, "0it", 3);
|
||||
variable = (pm_node_t *) pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0);
|
||||
} else {
|
||||
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE, (int) (parser->previous.end - parser->previous.start), parser->previous.start);
|
||||
PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE);
|
||||
variable = (pm_node_t *) pm_local_variable_read_node_create(parser, &parser->previous, 0);
|
||||
}
|
||||
}
|
||||
@ -14162,7 +14192,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) {
|
||||
parser_lex(parser);
|
||||
|
||||
if (match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) {
|
||||
expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_TERM);
|
||||
expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF);
|
||||
// If we get here, then we have an end immediately after a
|
||||
// start. In that case we'll create an empty content token and
|
||||
// return an uninterpolated string.
|
||||
@ -14219,15 +14249,19 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) {
|
||||
parser_lex(parser);
|
||||
} while (match1(parser, PM_TOKEN_STRING_CONTENT));
|
||||
|
||||
expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_TERM);
|
||||
expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF);
|
||||
node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous);
|
||||
} else if (accept1(parser, PM_TOKEN_LABEL_END) && !state_is_arg_labeled) {
|
||||
node = (pm_node_t *) pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &unescaped));
|
||||
} else if (match1(parser, PM_TOKEN_EOF)) {
|
||||
pm_parser_err_token(parser, &opening, PM_ERR_STRING_LITERAL_TERM);
|
||||
pm_parser_err_token(parser, &opening, PM_ERR_STRING_LITERAL_EOF);
|
||||
node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped);
|
||||
} else if (accept1(parser, PM_TOKEN_STRING_END)) {
|
||||
node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped);
|
||||
} else {
|
||||
expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_TERM);
|
||||
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, PM_ERR_STRING_LITERAL_TERM, pm_token_type_human(parser->previous.type));
|
||||
parser->previous.start = parser->previous.end;
|
||||
parser->previous.type = PM_TOKEN_MISSING;
|
||||
node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped);
|
||||
}
|
||||
} else if (match1(parser, PM_TOKEN_STRING_CONTENT)) {
|
||||
@ -14242,7 +14276,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) {
|
||||
if (match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) {
|
||||
node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped);
|
||||
pm_node_flag_set(node, parse_unescaped_encoding(parser));
|
||||
expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_TERM);
|
||||
expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF);
|
||||
} else if (accept1(parser, PM_TOKEN_LABEL_END)) {
|
||||
node = (pm_node_t *) pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &unescaped));
|
||||
} else {
|
||||
@ -14517,7 +14551,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
|
||||
// If we didn't find a terminator and we didn't find a right
|
||||
// parenthesis, then this is a syntax error.
|
||||
if (!terminator_found) {
|
||||
pm_parser_err(parser, parser->current.start, parser->current.start, PM_ERR_EXPECT_EOL_AFTER_STATEMENT);
|
||||
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_type_human(parser->current.type));
|
||||
}
|
||||
|
||||
// Parse each statement within the parentheses.
|
||||
@ -14546,7 +14580,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
|
||||
} else if (match1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) {
|
||||
break;
|
||||
} else {
|
||||
pm_parser_err(parser, parser->current.start, parser->current.start, PM_ERR_EXPECT_EOL_AFTER_STATEMENT);
|
||||
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_type_human(parser->current.type));
|
||||
}
|
||||
}
|
||||
|
||||
@ -16897,7 +16931,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
|
||||
switch (PM_NODE_TYPE(node)) {
|
||||
case PM_BACK_REFERENCE_READ_NODE:
|
||||
case PM_NUMBERED_REFERENCE_READ_NODE:
|
||||
pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_READONLY);
|
||||
PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, node, PM_ERR_WRITE_TARGET_READONLY);
|
||||
/* fallthrough */
|
||||
case PM_GLOBAL_VARIABLE_READ_NODE: {
|
||||
parser_lex(parser);
|
||||
@ -17008,7 +17042,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
|
||||
switch (PM_NODE_TYPE(node)) {
|
||||
case PM_BACK_REFERENCE_READ_NODE:
|
||||
case PM_NUMBERED_REFERENCE_READ_NODE:
|
||||
pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_READONLY);
|
||||
PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, node, PM_ERR_WRITE_TARGET_READONLY);
|
||||
/* fallthrough */
|
||||
case PM_GLOBAL_VARIABLE_READ_NODE: {
|
||||
parser_lex(parser);
|
||||
@ -17129,7 +17163,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
|
||||
switch (PM_NODE_TYPE(node)) {
|
||||
case PM_BACK_REFERENCE_READ_NODE:
|
||||
case PM_NUMBERED_REFERENCE_READ_NODE:
|
||||
pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_READONLY);
|
||||
PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, node, PM_ERR_WRITE_TARGET_READONLY);
|
||||
/* fallthrough */
|
||||
case PM_GLOBAL_VARIABLE_READ_NODE: {
|
||||
parser_lex(parser);
|
||||
|
@ -138,7 +138,7 @@ pm_token_type_human(pm_token_type_t token_type) {
|
||||
case PM_TOKEN_HEREDOC_START:
|
||||
return "heredoc beginning";
|
||||
case PM_TOKEN_IDENTIFIER:
|
||||
return "local variable or method identifier";
|
||||
return "local variable or method";
|
||||
case PM_TOKEN_IGNORED_NEWLINE:
|
||||
return "ignored newline";
|
||||
case PM_TOKEN_INSTANCE_VARIABLE:
|
||||
@ -248,7 +248,7 @@ pm_token_type_human(pm_token_type_t token_type) {
|
||||
case PM_TOKEN_LABEL:
|
||||
return "label";
|
||||
case PM_TOKEN_LABEL_END:
|
||||
return "':'";
|
||||
return "label terminator";
|
||||
case PM_TOKEN_LAMBDA_BEGIN:
|
||||
return "'{'";
|
||||
case PM_TOKEN_LESS:
|
||||
|
@ -9,7 +9,7 @@ module Prism
|
||||
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"
|
||||
"unexpected global variable, expecting end-of-input"
|
||||
]
|
||||
end
|
||||
|
||||
@ -151,7 +151,7 @@ module Prism
|
||||
def test_unterminated_interpolated_string
|
||||
expr = expression('"hello')
|
||||
assert_errors expr, '"hello', [
|
||||
["expected a closing delimiter for the string literal", 6..6]
|
||||
["unterminated string meets end of file", 6..6]
|
||||
]
|
||||
assert_equal expr.unescaped, "hello"
|
||||
assert_equal expr.closing, ""
|
||||
@ -160,7 +160,7 @@ module Prism
|
||||
def test_unterminated_string
|
||||
expr = expression("'hello")
|
||||
assert_errors expr, "'hello", [
|
||||
["expected a closing delimiter for the string literal", 0..1]
|
||||
["unterminated string meets end of file", 0..1]
|
||||
]
|
||||
assert_equal expr.unescaped, "hello"
|
||||
assert_equal expr.closing, ""
|
||||
@ -169,7 +169,7 @@ module Prism
|
||||
def test_unterminated_empty_string
|
||||
expr = expression('"')
|
||||
assert_errors expr, '"', [
|
||||
["expected a closing delimiter for the string literal", 1..1]
|
||||
["unterminated string meets end of file", 1..1]
|
||||
]
|
||||
assert_equal expr.unescaped, ""
|
||||
assert_equal expr.closing, ""
|
||||
@ -177,8 +177,8 @@ module Prism
|
||||
|
||||
def test_incomplete_instance_var_string
|
||||
assert_errors expression('%@#@@#'), '%@#@@#', [
|
||||
["incomplete instance variable", 4..5],
|
||||
["expected a newline or semicolon after the statement", 4..4]
|
||||
["`@#' is not allowed as an instance variable name", 4..5],
|
||||
["unexpected instance variable, expecting end-of-input", 4..5]
|
||||
]
|
||||
end
|
||||
|
||||
@ -190,7 +190,7 @@ module Prism
|
||||
|
||||
def test_unterminated_parenthesized_expression
|
||||
assert_errors expression('(1 + 2'), '(1 + 2', [
|
||||
["expected a newline or semicolon after the statement", 6..6],
|
||||
["unexpected end of file, expecting end-of-input", 6..6],
|
||||
["unexpected end of file, assuming it is closing the parent top level context", 6..6],
|
||||
["expected a matching `)`", 6..6]
|
||||
]
|
||||
@ -198,7 +198,7 @@ module Prism
|
||||
|
||||
def test_missing_terminator_in_parentheses
|
||||
assert_error_messages "(0 0)", [
|
||||
"expected a newline or semicolon after the statement"
|
||||
"unexpected integer, expecting end-of-input"
|
||||
]
|
||||
end
|
||||
|
||||
@ -224,24 +224,24 @@ module Prism
|
||||
|
||||
def test_1_2_3
|
||||
assert_errors expression("(1, 2, 3)"), "(1, 2, 3)", [
|
||||
["expected a newline or semicolon after the statement", 2..2],
|
||||
["unexpected ',', expecting end-of-input", 2..3],
|
||||
["unexpected ',', ignoring it", 2..3],
|
||||
["expected a matching `)`", 2..2],
|
||||
["expected a newline or semicolon after the statement", 2..2],
|
||||
["unexpected ',', expecting end-of-input", 2..3],
|
||||
["unexpected ',', ignoring it", 2..3],
|
||||
["expected a newline or semicolon after the statement", 5..5],
|
||||
["unexpected ',', expecting end-of-input", 5..6],
|
||||
["unexpected ',', ignoring it", 5..6],
|
||||
["expected a newline or semicolon after the statement", 8..8],
|
||||
["unexpected ')', expecting end-of-input", 8..9],
|
||||
["unexpected ')', ignoring it", 8..9]
|
||||
]
|
||||
end
|
||||
|
||||
def test_return_1_2_3
|
||||
assert_error_messages "return(1, 2, 3)", [
|
||||
"expected a newline or semicolon after the statement",
|
||||
"unexpected ',', expecting end-of-input",
|
||||
"unexpected ',', ignoring it",
|
||||
"expected a matching `)`",
|
||||
"expected a newline or semicolon after the statement",
|
||||
"unexpected ')', expecting end-of-input",
|
||||
"unexpected ')', ignoring it"
|
||||
]
|
||||
end
|
||||
@ -254,10 +254,10 @@ module Prism
|
||||
|
||||
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],
|
||||
["unexpected ',', expecting end-of-input", 6..7],
|
||||
["unexpected ',', ignoring it", 6..7],
|
||||
["expected a matching `)`", 6..6],
|
||||
["expected a newline or semicolon after the statement", 12..12],
|
||||
["unexpected ')', expecting end-of-input", 12..13],
|
||||
["unexpected ')', ignoring it", 12..13]
|
||||
]
|
||||
end
|
||||
@ -270,10 +270,10 @@ module Prism
|
||||
|
||||
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],
|
||||
["unexpected ',', expecting end-of-input", 7..8],
|
||||
["unexpected ',', ignoring it", 7..8],
|
||||
["expected a matching `)`", 7..7],
|
||||
["expected a newline or semicolon after the statement", 13..13],
|
||||
["unexpected ')', expecting end-of-input", 13..14],
|
||||
["unexpected ')', ignoring it", 13..14]
|
||||
]
|
||||
end
|
||||
@ -300,14 +300,14 @@ module Prism
|
||||
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"
|
||||
"unexpected local variable or method, expecting end-of-input"
|
||||
]
|
||||
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"
|
||||
"unexpected local variable or method, expecting end-of-input"
|
||||
]
|
||||
end
|
||||
|
||||
@ -354,7 +354,7 @@ module Prism
|
||||
|
||||
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",
|
||||
"unexpected 'end', expecting end-of-input",
|
||||
"unexpected 'end', ignoring it",
|
||||
"unexpected end of file, assuming it is closing the parent top level context",
|
||||
"expected a block beginning with `{` to end with `}`"
|
||||
@ -403,7 +403,7 @@ module Prism
|
||||
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",
|
||||
"unexpected ')', expecting end-of-input",
|
||||
"unexpected ')', ignoring it"
|
||||
]
|
||||
end
|
||||
@ -1124,8 +1124,8 @@ module Prism
|
||||
)
|
||||
|
||||
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]
|
||||
["Can't set variable $+", 6..8],
|
||||
["Can't set variable $1466", 15..20]
|
||||
]
|
||||
end
|
||||
|
||||
@ -1258,20 +1258,19 @@ module Prism
|
||||
|
||||
def test_unterminated_global_variable
|
||||
assert_errors expression("$"), "$", [
|
||||
["invalid global variable", 0..1]
|
||||
["`$' is not allowed as a global variable name", 0..1]
|
||||
]
|
||||
end
|
||||
|
||||
def test_invalid_global_variable_write
|
||||
assert_errors expression("$',"), "$',", [
|
||||
["immutable variable as a write target", 0..2],
|
||||
["Can't set variable $'", 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
|
||||
@ -1280,8 +1279,8 @@ module Prism
|
||||
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 "$1,", ["Can't set variable $1", *error_messages]
|
||||
assert_error_messages "$+,", ["Can't set variable $+", *error_messages]
|
||||
assert_error_messages "Foo,", error_messages
|
||||
assert_error_messages "::Foo,", error_messages
|
||||
assert_error_messages "Foo::Foo,", error_messages
|
||||
@ -1471,9 +1470,10 @@ module Prism
|
||||
|
||||
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],
|
||||
["unexpected end of file, expecting end-of-input", 7..7],
|
||||
["unexpected end of file, assuming it is closing the parent top level context", 7..7],
|
||||
["expected a lambda block beginning with `do` to end with `end`", 7..7]
|
||||
]
|
||||
@ -1517,14 +1517,14 @@ module Prism
|
||||
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],
|
||||
["unexpected label terminator, expected a string literal terminator", 12..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],
|
||||
["unexpected label terminator, expected a string literal terminator", 5..7]
|
||||
]
|
||||
end
|
||||
|
||||
@ -1545,19 +1545,19 @@ module Prism
|
||||
RUBY
|
||||
|
||||
assert_errors expression(source), source, [
|
||||
["expected a newline or semicolon after the statement", 6..6],
|
||||
["unexpected '+', expecting end-of-input", 7..8],
|
||||
["unexpected '+', ignoring it", 7..8],
|
||||
["expected a newline or semicolon after the statement", 17..17],
|
||||
["unexpected '+', expecting end-of-input", 18..19],
|
||||
["unexpected '+', ignoring it", 18..19]
|
||||
]
|
||||
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]
|
||||
["unexpected local variable or method, expecting end-of-input", 3..4],
|
||||
["unexpected local variable or method, expecting end-of-input", 9..11]
|
||||
]
|
||||
end
|
||||
|
||||
@ -1845,7 +1845,7 @@ module Prism
|
||||
source = '1....2'
|
||||
|
||||
assert_errors expression(source), source, [
|
||||
["expected a newline or semicolon after the statement", 4..4],
|
||||
["unexpected '.', expecting end-of-input", 4..5],
|
||||
["unexpected '.', ignoring it", 4..5]
|
||||
]
|
||||
end
|
||||
@ -1879,21 +1879,21 @@ module Prism
|
||||
RUBY
|
||||
|
||||
assert_errors expression(source), source, [
|
||||
["expected a newline or semicolon after the statement", 9..9],
|
||||
["unexpected '+', expecting end-of-input", 10..11],
|
||||
["unexpected '+', ignoring it", 10..11],
|
||||
["expected a newline or semicolon after the statement", 23..23],
|
||||
["unexpected '.', expecting end-of-input", 23..24],
|
||||
["unexpected '.', ignoring it", 23..24],
|
||||
["expected a newline or semicolon after the statement", 39..39],
|
||||
["unexpected '+', expecting end-of-input", 40..41],
|
||||
["unexpected '+', ignoring it", 40..41],
|
||||
["expected a newline or semicolon after the statement", 57..57],
|
||||
["unexpected '.', expecting end-of-input", 57..58],
|
||||
["unexpected '.', ignoring it", 57..58],
|
||||
["expected a newline or semicolon after the statement", 71..71],
|
||||
["unexpected '+', expecting end-of-input", 72..73],
|
||||
["unexpected '+', ignoring it", 72..73],
|
||||
["expected a newline or semicolon after the statement", 87..87],
|
||||
["unexpected '.', expecting end-of-input", 87..88],
|
||||
["unexpected '.', ignoring it", 87..88],
|
||||
["expected a newline or semicolon after the statement", 97..97],
|
||||
["unexpected '+', expecting end-of-input", 98..99],
|
||||
["unexpected '+', ignoring it", 98..99],
|
||||
["expected a newline or semicolon after the statement", 109..109],
|
||||
["unexpected '.', expecting end-of-input", 109..110],
|
||||
["unexpected '.', ignoring it", 109..110]
|
||||
]
|
||||
end
|
||||
@ -1920,9 +1920,9 @@ module Prism
|
||||
RUBY
|
||||
|
||||
assert_errors expression(source), source, [
|
||||
["expected a newline or semicolon after the statement", 3..3],
|
||||
["unexpected '..', expecting end-of-input", 3..5],
|
||||
["unexpected '..', ignoring it", 3..5],
|
||||
["expected a newline or semicolon after the statement", 10..10],
|
||||
["unexpected '..', expecting end-of-input", 10..12],
|
||||
["unexpected '..', ignoring it", 10..12]
|
||||
]
|
||||
end
|
||||
@ -2004,13 +2004,12 @@ module Prism
|
||||
foo 1 in a
|
||||
a = foo 2 in b
|
||||
RUBY
|
||||
message1 = 'unexpected `in` keyword in arguments'
|
||||
message2 = 'expected a newline or semicolon after the statement'
|
||||
|
||||
assert_errors expression(source), source, [
|
||||
[message1, 9..10],
|
||||
[message2, 8..8],
|
||||
[message1, 24..25],
|
||||
[message2, 23..23],
|
||||
["unexpected `in` keyword in arguments", 9..10],
|
||||
["unexpected local variable or method, expecting end-of-input", 9..10],
|
||||
["unexpected `in` keyword in arguments", 24..25],
|
||||
["unexpected local variable or method, expecting end-of-input", 24..25]
|
||||
]
|
||||
end
|
||||
|
||||
@ -2032,17 +2031,17 @@ module Prism
|
||||
RUBY
|
||||
|
||||
assert_errors expression(source), source, [
|
||||
["expected a newline or semicolon after the statement", 6..6],
|
||||
["unexpected '==', expecting end-of-input", 7..9],
|
||||
["unexpected '==', ignoring it", 7..9],
|
||||
["expected a newline or semicolon after the statement", 18..18],
|
||||
["unexpected '!=', expecting end-of-input", 19..21],
|
||||
["unexpected '!=', ignoring it", 19..21],
|
||||
["expected a newline or semicolon after the statement", 31..31],
|
||||
["unexpected '===', expecting end-of-input", 32..35],
|
||||
["unexpected '===', ignoring it", 32..35],
|
||||
["expected a newline or semicolon after the statement", 44..44],
|
||||
["unexpected '=~', expecting end-of-input", 45..47],
|
||||
["unexpected '=~', ignoring it", 45..47],
|
||||
["expected a newline or semicolon after the statement", 56..56],
|
||||
["unexpected '!~', expecting end-of-input", 57..59],
|
||||
["unexpected '!~', ignoring it", 57..59],
|
||||
["expected a newline or semicolon after the statement", 69..69],
|
||||
["unexpected '<=>', expecting end-of-input", 70..73],
|
||||
["unexpected '<=>', ignoring it", 70..73]
|
||||
]
|
||||
end
|
||||
|
@ -15,9 +15,9 @@ module Prism
|
||||
|
||||
assert_equal <<~'ERROR', Debug.format_errors('"%W"\u"', false)
|
||||
> 1 | "%W"\u"
|
||||
| ^ expected a newline or semicolon after the statement
|
||||
| ^ invalid character `\`
|
||||
| ^ expected a closing delimiter for the string literal
|
||||
| ^ unexpected local variable or method, expecting end-of-input
|
||||
| ^ unterminated string meets end of file
|
||||
ERROR
|
||||
end
|
||||
end
|
||||
|
@ -3,11 +3,14 @@
|
||||
require_relative "test_helper"
|
||||
|
||||
begin
|
||||
verbose, $VERBOSE = $VERBOSE, nil
|
||||
require "parser/current"
|
||||
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.
|
||||
return
|
||||
ensure
|
||||
$VERBOSE = verbose
|
||||
end
|
||||
|
||||
# First, opt in to every AST feature.
|
||||
|
Loading…
x
Reference in New Issue
Block a user