From bf5b2516841228231a080fa050002b2daff90327 Mon Sep 17 00:00:00 2001 From: Stephan Unverwerth Date: Sat, 18 Apr 2020 20:31:27 +0200 Subject: [PATCH] LibJS: Allow reserved words as keys in object expressions. --- Libraries/LibJS/Parser.cpp | 13 +++++++-- Libraries/LibJS/Parser.h | 1 + Libraries/LibJS/Tests/object-basic.js | 8 ++++++ Libraries/LibJS/Token.cpp | 40 +++++++++++++++++++++++++++ Libraries/LibJS/Token.h | 2 ++ 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index aafd977e63..11b5ee984a 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -437,8 +437,8 @@ NonnullRefPtr Parser::parse_object_expression() while (!done() && !match(TokenType::CurlyClose)) { FlyString property_name; - if (match(TokenType::Identifier)) { - property_name = consume(TokenType::Identifier).value(); + if (match_identifier_name()) { + property_name = consume().value(); } else if (match(TokenType::StringLiteral)) { property_name = consume(TokenType::StringLiteral).string_value(); } else if (match(TokenType::NumericLiteral)) { @@ -582,7 +582,9 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr(AssignmentOp::Assignment, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::Period: consume(); - return create_ast_node(move(lhs), parse_expression(min_precedence, associativity)); + if (!match_identifier_name()) + expected("IdentifierName"); + return create_ast_node(move(lhs), create_ast_node(consume().value())); case TokenType::BracketOpen: { consume(TokenType::BracketOpen); auto expression = create_ast_node(move(lhs), parse_expression(0), true); @@ -1072,6 +1074,11 @@ bool Parser::match_statement() const || type == TokenType::Var; } +bool Parser::match_identifier_name() const +{ + return m_parser_state.m_current_token.is_identifier_name(); +} + bool Parser::done() const { return match(TokenType::Eof); diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index 65fddcc306..bceda86121 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -84,6 +84,7 @@ private: bool match_secondary_expression() const; bool match_statement() const; bool match_variable_declaration() const; + bool match_identifier_name() const; bool match(TokenType type) const; bool done() const; void expected(const char* what); diff --git a/Libraries/LibJS/Tests/object-basic.js b/Libraries/LibJS/Tests/object-basic.js index 2320304fc0..d93fe8ff07 100644 --- a/Libraries/LibJS/Tests/object-basic.js +++ b/Libraries/LibJS/Tests/object-basic.js @@ -23,6 +23,14 @@ try { // Note : this test doesn't pass yet due to floating-point literals being coerced to i32 on access // assert(math[3.14] === "pi"); + // This is also allowed! Watch out for syntax errors. + var o2 = { return: 1, yield: 1, for: 1, catch: 1, break: 1 }; + assert(o2.return === 1); + assert(o2.yield === 1); + assert(o2.for === 1); + assert(o2.catch === 1); + assert(o2.break === 1); + console.log("PASS"); } catch (e) { console.log("FAIL: " + e); diff --git a/Libraries/LibJS/Token.cpp b/Libraries/LibJS/Token.cpp index 573e32c8f1..fca1e4597d 100644 --- a/Libraries/LibJS/Token.cpp +++ b/Libraries/LibJS/Token.cpp @@ -129,4 +129,44 @@ bool Token::bool_value() const return m_value == "true"; } +bool Token::is_identifier_name() const +{ + // IdentifierNames are Identifiers + ReservedWords + // The standard defines this reversed: Identifiers are IdentifierNames except reserved words + // https://www.ecma-international.org/ecma-262/5.1/#sec-7.6 + return m_type == TokenType::Identifier + || m_type == TokenType::Await + || m_type == TokenType::BoolLiteral + || m_type == TokenType::Break + || m_type == TokenType::Case + || m_type == TokenType::Catch + || m_type == TokenType::Class + || m_type == TokenType::Const + || m_type == TokenType::Continue + || m_type == TokenType::Default + || m_type == TokenType::Delete + || m_type == TokenType::Do + || m_type == TokenType::Else + || m_type == TokenType::Finally + || m_type == TokenType::For + || m_type == TokenType::Function + || m_type == TokenType::If + || m_type == TokenType::In + || m_type == TokenType::Instanceof + || m_type == TokenType::Interface + || m_type == TokenType::Let + || m_type == TokenType::New + || m_type == TokenType::NullLiteral + || m_type == TokenType::Return + || m_type == TokenType::Switch + || m_type == TokenType::This + || m_type == TokenType::Throw + || m_type == TokenType::Try + || m_type == TokenType::Typeof + || m_type == TokenType::Var + || m_type == TokenType::Void + || m_type == TokenType::While + || m_type == TokenType::Yield; +} + } diff --git a/Libraries/LibJS/Token.h b/Libraries/LibJS/Token.h index 36c6d210c3..596772bad5 100644 --- a/Libraries/LibJS/Token.h +++ b/Libraries/LibJS/Token.h @@ -153,6 +153,8 @@ public: String string_value() const; bool bool_value() const; + bool is_identifier_name() const; + private: TokenType m_type; StringView m_trivia;