diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index b88f8ac4ba..89dd33ed10 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -406,9 +406,7 @@ NonnullRefPtr Parser::parse_statement(AllowLabelledFunction allow_lab m_state.current_token = m_state.lexer.force_slash_as_regex(); [[fallthrough]]; default: - if (type == TokenType::EscapedKeyword - && (m_state.strict_mode - || (m_state.current_token.value() != "yield"sv && m_state.current_token.value() != "let"sv))) + if (match_invalid_escaped_keyword()) syntax_error("Keyword must not contain escaped characters"); if (match_identifier_name()) { @@ -432,6 +430,20 @@ NonnullRefPtr Parser::parse_statement(AllowLabelledFunction allow_lab } } +bool Parser::match_invalid_escaped_keyword() const +{ + if (m_state.current_token.type() != TokenType::EscapedKeyword) + return false; + auto token_value = m_state.current_token.value(); + if (token_value == "await"sv) { + return m_program_type == Program::Type::Module; + } + if (m_state.strict_mode) { + return true; + } + return token_value != "yield"sv && token_value != "let"sv; +} + static constexpr AK::Array strict_reserved_words = { "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield" }; static bool is_strict_reserved_word(StringView str) @@ -582,7 +594,10 @@ RefPtr Parser::try_parse_labelled_statement(AllowLabelledFunction all }; if (m_state.current_token.value() == "yield"sv && (m_state.strict_mode || m_state.in_generator_function_context)) { - syntax_error("'yield' label not allowed in this context"); + return {}; + } + + if (m_state.current_token.value() == "await"sv && m_program_type == Program::Type::Module) { return {}; } @@ -2854,6 +2869,7 @@ bool Parser::match_expression() const || type == TokenType::TemplateLiteralStart || type == TokenType::NullLiteral || match_identifier() + || type == TokenType::Await || type == TokenType::New || type == TokenType::Class || type == TokenType::CurlyOpen @@ -3026,12 +3042,14 @@ bool Parser::match_identifier() const return !m_state.strict_mode; if (m_state.current_token.value() == "yield"sv) return !m_state.strict_mode && !m_state.in_generator_function_context; + if (m_state.current_token.value() == "await"sv) + return m_program_type != Program::Type::Module; return true; } return m_state.current_token.type() == TokenType::Identifier || (m_state.current_token.type() == TokenType::Let && !m_state.strict_mode) - || (m_state.current_token.type() == TokenType::Await && !m_state.strict_mode) + || (m_state.current_token.type() == TokenType::Await && m_program_type != Program::Type::Module) || (m_state.current_token.type() == TokenType::Yield && !m_state.in_generator_function_context && !m_state.strict_mode); // See note in Parser::parse_identifier(). } @@ -3119,10 +3137,10 @@ Token Parser::consume_identifier_reference() if (match(TokenType::EscapedKeyword)) { auto name = m_state.current_token.value(); - if (name == "await"sv) - syntax_error("Identifier reference may not be 'await'"); - else if (m_state.strict_mode && (name == "let"sv || name == "yield"sv)) + if (m_state.strict_mode && (name == "let"sv || name == "yield"sv)) syntax_error(String::formatted("'{}' is not allowed as an identifier in strict mode", name)); + if (m_program_type == Program::Type::Module && name == "await"sv) + syntax_error("'await' is not allowed as an identifier in module"); return consume(); } @@ -3141,7 +3159,8 @@ Token Parser::consume_identifier_reference() } if (match(TokenType::Await)) { - syntax_error("Identifier reference may not be 'await'"); + if (m_program_type == Program::Type::Module) + syntax_error("'await' is not allowed as an identifier in module"); return consume(); } @@ -3343,7 +3362,7 @@ NonnullRefPtr Parser::parse_import_statement(Program& program) expected("import clauses"); } - auto from_statement = consume(TokenType::Identifier).value(); + auto from_statement = consume(TokenType::Identifier).original_value(); if (from_statement != "from"sv) syntax_error(String::formatted("Expected 'from' got {}", from_statement)); diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index db860257d7..654b036408 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -199,6 +199,8 @@ private: bool try_parse_arrow_function_expression_failed_at_position(const Position&) const; void set_try_parse_arrow_function_expression_failed_at_position(const Position&, bool); + bool match_invalid_escaped_keyword() const; + struct RulePosition { AK_MAKE_NONCOPYABLE(RulePosition); AK_MAKE_NONMOVABLE(RulePosition); diff --git a/Userland/Libraries/LibJS/Tests/labels.js b/Userland/Libraries/LibJS/Tests/labels.js index 6747a13eff..4224a290a0 100644 --- a/Userland/Libraries/LibJS/Tests/labels.js +++ b/Userland/Libraries/LibJS/Tests/labels.js @@ -110,6 +110,28 @@ test("can use certain 'keywords' as labels", () => { expect(`const: { break const; }`).not.toEval(); }); +test("can use certain 'keywords' even in strict mode", () => { + "use strict"; + + let i = 0; + await: { + i++; + break await; + expect().fail(); + } + + async: { + i++; + break async; + expect().fail(); + } + expect(i).toBe(2); + + expect(`'use strict'; let: { break let; }`).not.toEval(); + + expect(`'use strict'; l\u0065t: { break l\u0065t; }`).not.toEval(); +}); + test("invalid label usage", () => { expect(() => eval(`