diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 355377880f..dba379fb1e 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -172,6 +172,12 @@ public: class Declaration : public Statement { }; +class ErrorDeclaration final : public Declaration { +public: + Value execute(Interpreter&, GlobalObject&) const override { return js_undefined(); } + const char* class_name() const override { return "ErrorDeclaration"; } +}; + class FunctionNode { public: struct Parameter { diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 893db52aad..51ea22e96b 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -241,7 +241,13 @@ NonnullRefPtr Parser::parse_program() bool first = true; m_parser_state.m_use_strict_directive = UseStrictDirectiveState::Looking; while (!done()) { - if (match_statement()) { + if (match_declaration()) { + program->append(parse_declaration()); + if (first) { + first = false; + m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None; + } + } else if (match_statement()) { program->append(parse_statement()); if (first) { if (m_parser_state.m_use_strict_directive == UseStrictDirectiveState::Found) { @@ -252,7 +258,7 @@ NonnullRefPtr Parser::parse_program() m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None; } } else { - expected("statement"); + expected("statement or declaration"); consume(); } } @@ -266,7 +272,7 @@ NonnullRefPtr Parser::parse_program() return program; } -NonnullRefPtr Parser::parse_statement() +NonnullRefPtr Parser::parse_declaration() { switch (m_parser_state.m_current_token.type()) { case TokenType::Class: @@ -276,13 +282,24 @@ NonnullRefPtr Parser::parse_statement() m_parser_state.m_function_scopes.last().append(declaration); return declaration; } + case TokenType::Let: + case TokenType::Const: + return parse_variable_declaration(); + default: + expected("declaration"); + consume(); + return create_ast_node(); + } +} + +NonnullRefPtr Parser::parse_statement() +{ + switch (m_parser_state.m_current_token.type()) { case TokenType::CurlyOpen: return parse_block_statement(); case TokenType::Return: return parse_return_statement(); case TokenType::Var: - case TokenType::Let: - case TokenType::Const: return parse_variable_declaration(); case TokenType::For: return parse_for_statement(); @@ -314,11 +331,13 @@ NonnullRefPtr Parser::parse_statement() return result.release_nonnull(); } if (match_expression()) { + if (match(TokenType::Function)) + syntax_error("Function declaration not allowed in single-statement context"); auto expr = parse_expression(0); consume_or_insert_semicolon(); return create_ast_node(move(expr)); } - expected("statement (missing switch case)"); + expected("statement"); consume(); return create_ast_node(); } @@ -614,7 +633,7 @@ NonnullRefPtr Parser::parse_primary_expression() case TokenType::New: return parse_new_expression(); default: - expected("primary expression (missing switch case)"); + expected("primary expression"); consume(); return create_ast_node(); } @@ -676,7 +695,7 @@ NonnullRefPtr Parser::parse_unary_prefixed_expression() consume(); return create_ast_node(UnaryOp::Delete, parse_expression(precedence, associativity)); default: - expected("primary expression (missing switch case)"); + expected("primary expression"); consume(); return create_ast_node(); } @@ -1076,7 +1095,7 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr(); } @@ -1209,8 +1228,11 @@ NonnullRefPtr Parser::parse_block_statement(bool& is_strict) } while (!done() && !match(TokenType::CurlyClose)) { - if (match(TokenType::Semicolon)) { - consume(); + if (match_declaration()) { + block->append(parse_declaration()); + + if (first && !initial_strict_mode_state) + m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None; } else if (match_statement()) { block->append(parse_statement()); @@ -1222,7 +1244,7 @@ NonnullRefPtr Parser::parse_block_statement(bool& is_strict) m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None; } } else { - expected("statement"); + expected("statement or declaration"); consume(); } @@ -1508,8 +1530,14 @@ NonnullRefPtr Parser::parse_switch_case() NonnullRefPtrVector consequent; TemporaryChange break_change(m_parser_state.m_in_break_context, true); - while (match_statement()) - consequent.append(parse_statement()); + for (;;) { + if (match_declaration()) + consequent.append(parse_declaration()); + else if (match_statement()) + consequent.append(parse_statement()); + else + break; + } return create_ast_node(move(test), move(consequent)); } @@ -1635,18 +1663,6 @@ bool Parser::match(TokenType type) const return m_parser_state.m_current_token.type() == type; } -bool Parser::match_variable_declaration() const -{ - switch (m_parser_state.m_current_token.type()) { - case TokenType::Var: - case TokenType::Let: - case TokenType::Const: - return true; - default: - return false; - } -} - bool Parser::match_expression() const { auto type = m_parser_state.m_current_token.type(); @@ -1740,17 +1756,13 @@ bool Parser::match_statement() const { auto type = m_parser_state.m_current_token.type(); return match_expression() - || type == TokenType::Function || type == TokenType::Return - || type == TokenType::Let - || type == TokenType::Class || type == TokenType::Do || type == TokenType::If || type == TokenType::Throw || type == TokenType::Try || type == TokenType::While || type == TokenType::For - || type == TokenType::Const || type == TokenType::CurlyOpen || type == TokenType::Switch || type == TokenType::Break @@ -1760,6 +1772,23 @@ bool Parser::match_statement() const || type == TokenType::Semicolon; } +bool Parser::match_declaration() const +{ + auto type = m_parser_state.m_current_token.type(); + return type == TokenType::Function + || type == TokenType::Class + || type == TokenType::Const + || type == TokenType::Let; +} + +bool Parser::match_variable_declaration() const +{ + auto type = m_parser_state.m_current_token.type(); + return type == TokenType::Var + || type == TokenType::Let + || type == TokenType::Const; +} + bool Parser::match_identifier_name() const { return m_parser_state.m_current_token.is_identifier_name(); diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index 2fd3c99b22..007e8b6879 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -60,6 +60,7 @@ public: NonnullRefPtr parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName); Vector parse_function_parameters(int& function_length, u8 parse_options = 0); + NonnullRefPtr parse_declaration(); NonnullRefPtr parse_statement(); NonnullRefPtr parse_block_statement(); NonnullRefPtr parse_block_statement(bool& is_strict); @@ -147,6 +148,7 @@ private: bool match_unary_prefixed_expression() const; bool match_secondary_expression(Vector forbidden = {}) const; bool match_statement() const; + bool match_declaration() const; bool match_variable_declaration() const; bool match_identifier_name() const; bool match_property_key() const; diff --git a/Libraries/LibJS/Tests/parser-declaration-in-single-statement-context.js b/Libraries/LibJS/Tests/parser-declaration-in-single-statement-context.js new file mode 100644 index 0000000000..5e263427bc --- /dev/null +++ b/Libraries/LibJS/Tests/parser-declaration-in-single-statement-context.js @@ -0,0 +1,6 @@ +test("Declaration in single-statement context is a syntax error", () => { + expect("if (0) const foo = 1").not.toEval(); + expect("while (0) function foo() {}").not.toEval(); + expect("for (var 0;;) class foo() {}").not.toEval(); + expect("do let foo = 1 while (0)").not.toEval(); +});