diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 6614094f4b..453d767b83 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -1559,6 +1559,11 @@ NonnullRefPtr Parser::parse_regexp_literal() return create_ast_node(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(expression) || is(expression) || (allow_web_reality_call_expression && is(expression)); +} + NonnullRefPtr Parser::parse_unary_prefixed_expression() { auto rule_start = push_start(); @@ -1569,9 +1574,7 @@ NonnullRefPtr 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(*rhs) && !is(*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(*rhs)) { @@ -1586,9 +1589,7 @@ NonnullRefPtr 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(*rhs) && !is(*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(*rhs)) { @@ -2145,9 +2146,7 @@ Parser::ExpressionResult Parser::parse_secondary_expression(NonnullRefPtr(*lhs) && !is(*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(*lhs)) { @@ -2159,9 +2158,7 @@ Parser::ExpressionResult Parser::parse_secondary_expression(NonnullRefPtr({ 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(*lhs) && !is(*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(*lhs)) { @@ -2293,13 +2290,18 @@ NonnullRefPtr Parser::parse_assignment_expression(Assignme } } } - if (!is(*lhs) && !is(*lhs) && !is(*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(*lhs)) { auto const& name = static_cast(*lhs).string(); check_identifier_name_for_assignment_validity(name); - } else if (m_state.strict_mode && is(*lhs)) { - syntax_error("Cannot assign to function call"); } auto rhs = parse_expression(min_precedence, associativity, forbidden); return create_ast_node({ m_source_code, rule_start.position(), position() }, assignment_op, move(lhs), move(rhs)); diff --git a/Userland/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js b/Userland/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js index ec5024f8ec..382078ad8b 100644 --- a/Userland/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js +++ b/Userland/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js @@ -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(); });