mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 09:34:59 +00:00
LibJS: Allow CallExpressions as lhs of assignments in most cases
Although not quite like the spec says the web reality is that a lhs target of CallExpression should not give a SyntaxError but only a ReferenceError once executed.
This commit is contained in:
parent
8319d7ac06
commit
d218a68296
2 changed files with 41 additions and 16 deletions
|
@ -1559,6 +1559,11 @@ NonnullRefPtr<RegExpLiteral> Parser::parse_regexp_literal()
|
|||
return create_ast_node<RegExpLiteral>(move(range), move(parsed_regex), move(parsed_pattern), move(parsed_flags), pattern.to_string(), move(flags));
|
||||
}
|
||||
|
||||
static bool is_simple_assignment_target(Expression const& expression, bool allow_web_reality_call_expression = true)
|
||||
{
|
||||
return is<Identifier>(expression) || is<MemberExpression>(expression) || (allow_web_reality_call_expression && is<CallExpression>(expression));
|
||||
}
|
||||
|
||||
NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
|
||||
{
|
||||
auto rule_start = push_start();
|
||||
|
@ -1569,9 +1574,7 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
|
|||
consume();
|
||||
auto rhs_start = position();
|
||||
auto rhs = parse_expression(precedence, associativity);
|
||||
// FIXME: Apparently for functions this should also not be enforced on a parser level,
|
||||
// other engines throw ReferenceError for ++foo()
|
||||
if (!is<Identifier>(*rhs) && !is<MemberExpression>(*rhs))
|
||||
if (!is_simple_assignment_target(*rhs))
|
||||
syntax_error(String::formatted("Right-hand side of prefix increment operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start);
|
||||
|
||||
if (m_state.strict_mode && is<Identifier>(*rhs)) {
|
||||
|
@ -1586,9 +1589,7 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
|
|||
consume();
|
||||
auto rhs_start = position();
|
||||
auto rhs = parse_expression(precedence, associativity);
|
||||
// FIXME: Apparently for functions this should also not be enforced on a parser level,
|
||||
// other engines throw ReferenceError for --foo()
|
||||
if (!is<Identifier>(*rhs) && !is<MemberExpression>(*rhs))
|
||||
if (!is_simple_assignment_target(*rhs))
|
||||
syntax_error(String::formatted("Right-hand side of prefix decrement operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start);
|
||||
|
||||
if (m_state.strict_mode && is<Identifier>(*rhs)) {
|
||||
|
@ -2145,9 +2146,7 @@ Parser::ExpressionResult Parser::parse_secondary_expression(NonnullRefPtr<Expres
|
|||
return expression;
|
||||
}
|
||||
case TokenType::PlusPlus:
|
||||
// FIXME: Apparently for functions this should also not be enforced on a parser level,
|
||||
// other engines throw ReferenceError for foo()++
|
||||
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs))
|
||||
if (!is_simple_assignment_target(*lhs))
|
||||
syntax_error(String::formatted("Left-hand side of postfix increment operator must be identifier or member expression, got {}", lhs->class_name()));
|
||||
|
||||
if (m_state.strict_mode && is<Identifier>(*lhs)) {
|
||||
|
@ -2159,9 +2158,7 @@ Parser::ExpressionResult Parser::parse_secondary_expression(NonnullRefPtr<Expres
|
|||
consume();
|
||||
return create_ast_node<UpdateExpression>({ m_source_code, rule_start.position(), position() }, UpdateOp::Increment, move(lhs));
|
||||
case TokenType::MinusMinus:
|
||||
// FIXME: Apparently for functions this should also not be enforced on a parser level,
|
||||
// other engines throw ReferenceError for foo()--
|
||||
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs))
|
||||
if (!is_simple_assignment_target(*lhs))
|
||||
syntax_error(String::formatted("Left-hand side of postfix increment operator must be identifier or member expression, got {}", lhs->class_name()));
|
||||
|
||||
if (m_state.strict_mode && is<Identifier>(*lhs)) {
|
||||
|
@ -2293,13 +2290,18 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs) && !is<CallExpression>(*lhs)) {
|
||||
|
||||
// Note: The web reality is that all but &&=, ||= and ??= do allow left hand side CallExpresions.
|
||||
// These are the exception as they are newer.
|
||||
auto has_web_reality_assignment_target_exceptions = assignment_op != AssignmentOp::AndAssignment
|
||||
&& assignment_op != AssignmentOp::OrAssignment
|
||||
&& assignment_op != AssignmentOp::NullishAssignment;
|
||||
|
||||
if (!is_simple_assignment_target(*lhs, has_web_reality_assignment_target_exceptions)) {
|
||||
syntax_error("Invalid left-hand side in assignment");
|
||||
} else if (m_state.strict_mode && is<Identifier>(*lhs)) {
|
||||
auto const& name = static_cast<Identifier const&>(*lhs).string();
|
||||
check_identifier_name_for_assignment_validity(name);
|
||||
} else if (m_state.strict_mode && is<CallExpression>(*lhs)) {
|
||||
syntax_error("Cannot assign to function call");
|
||||
}
|
||||
auto rhs = parse_expression(min_precedence, associativity, forbidden);
|
||||
return create_ast_node<AssignmentExpression>({ m_source_code, rule_start.position(), position() }, assignment_op, move(lhs), move(rhs));
|
||||
|
|
|
@ -6,7 +6,7 @@ test("assignment to function call", () => {
|
|||
});
|
||||
|
||||
test("assignment to function call in strict mode", () => {
|
||||
expect("'use strict'; foo() = 'foo'").not.toEval();
|
||||
expect("'use strict'; foo() = 'foo'").toEval();
|
||||
});
|
||||
|
||||
test("assignment to inline function call", () => {
|
||||
|
@ -29,4 +29,27 @@ test("assignment to invalid LHS is syntax error", () => {
|
|||
expect("1 >>= 1").not.toEval();
|
||||
expect("1 >>>= 1").not.toEval();
|
||||
expect("1 = 1").not.toEval();
|
||||
expect("1 &&= 1").not.toEval();
|
||||
expect("1 ||= 1").not.toEval();
|
||||
expect("1 ??= 1").not.toEval();
|
||||
});
|
||||
|
||||
test("assignment to call LHS is only syntax error for new operators", () => {
|
||||
expect("f() += 1").toEval();
|
||||
expect("f() -= 1").toEval();
|
||||
expect("f() *= 1").toEval();
|
||||
expect("f() /= 1").toEval();
|
||||
expect("f() %= 1").toEval();
|
||||
expect("f() **= 1").toEval();
|
||||
expect("f() &= 1").toEval();
|
||||
expect("f() |= 1").toEval();
|
||||
expect("f() ^= 1").toEval();
|
||||
expect("f() <<= 1").toEval();
|
||||
expect("f() >>= 1").toEval();
|
||||
expect("f() >>>= 1").toEval();
|
||||
expect("f() = 1").toEval();
|
||||
|
||||
expect("f() &&= 1").not.toEval();
|
||||
expect("f() ||= 1").not.toEval();
|
||||
expect("f() ??= 1").not.toEval();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue