From a598a2c19df576301058a787e1a6e96cfc843f19 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sat, 31 Oct 2020 13:37:09 +0000 Subject: [PATCH] LibJS: Function declarations in if statement clauses https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses B.3.4 FunctionDeclarations in IfStatement Statement Clauses The following augments the IfStatement production in 13.6: IfStatement[Yield, Await, Return] : if ( Expression[+In, ?Yield, ?Await] ) FunctionDeclaration[?Yield, ?Await, ~Default] else Statement[?Yield, ?Await, ?Return] if ( Expression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] else FunctionDeclaration[?Yield, ?Await, ~Default] if ( Expression[+In, ?Yield, ?Await] ) FunctionDeclaration[?Yield, ?Await, ~Default] else FunctionDeclaration[?Yield, ?Await, ~Default] if ( Expression[+In, ?Yield, ?Await] ) FunctionDeclaration[?Yield, ?Await, ~Default] This production only applies when parsing non-strict code. Code matching this production is processed as if each matching occurrence of FunctionDeclaration[?Yield, ?Await, ~Default] was the sole StatementListItem of a BlockStatement occupying that position in the source code. The semantics of such a synthetic BlockStatement includes the web legacy compatibility semantics specified in B.3.3. --- Libraries/LibJS/Parser.cpp | 29 +++++++++++-- .../if-statement-function-declaration.js | 41 +++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 Libraries/LibJS/Tests/if-statement-function-declaration.js diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index a99c057a35..b1b97fded9 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -1616,17 +1616,38 @@ NonnullRefPtr Parser::parse_catch_clause() NonnullRefPtr Parser::parse_if_statement() { + auto parse_function_declaration_as_block_statement = [&] { + // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses + // Code matching this production is processed as if each matching occurrence of + // FunctionDeclaration[?Yield, ?Await, ~Default] was the sole StatementListItem + // of a BlockStatement occupying that position in the source code. + ScopePusher scope(*this, ScopePusher::Let); + auto block = create_ast_node(); + block->append(parse_declaration()); + block->add_functions(m_parser_state.m_function_scopes.last()); + return block; + }; + consume(TokenType::If); consume(TokenType::ParenOpen); auto predicate = parse_expression(0); consume(TokenType::ParenClose); - auto consequent = parse_statement(); + + RefPtr consequent; + if (!m_parser_state.m_strict_mode && match(TokenType::Function)) + consequent = parse_function_declaration_as_block_statement(); + else + consequent = parse_statement(); + RefPtr alternate; if (match(TokenType::Else)) { - consume(TokenType::Else); - alternate = parse_statement(); + consume(); + if (!m_parser_state.m_strict_mode && match(TokenType::Function)) + alternate = parse_function_declaration_as_block_statement(); + else + alternate = parse_statement(); } - return create_ast_node(move(predicate), move(consequent), move(alternate)); + return create_ast_node(move(predicate), move(*consequent), move(alternate)); } NonnullRefPtr Parser::parse_for_statement() diff --git a/Libraries/LibJS/Tests/if-statement-function-declaration.js b/Libraries/LibJS/Tests/if-statement-function-declaration.js new file mode 100644 index 0000000000..583585ebe5 --- /dev/null +++ b/Libraries/LibJS/Tests/if-statement-function-declaration.js @@ -0,0 +1,41 @@ +describe("function declarations in if statement clauses", () => { + test("if clause", () => { + if (true) function foo() {} + if (false) function bar() {} + expect(typeof globalThis.foo).toBe("function"); + expect(typeof globalThis.bar).toBe("undefined"); + }); + + test("else clause", () => { + if (false); + else function foo() {} + if (true); + else function bar() {} + expect(typeof globalThis.foo).toBe("function"); + expect(typeof globalThis.bar).toBe("undefined"); + }); + + test("if and else clause", () => { + if (true) function foo() {} + else function bar() {} + expect(typeof globalThis.foo).toBe("function"); + expect(typeof globalThis.bar).toBe("undefined"); + }); + + test("syntax error in strict mode", () => { + expect(` + "use strict"; + if (true) function foo() {} + `).not.toEval(); + expect(` + "use strict"; + if (false); + else function foo() {} + `).not.toEval(); + expect(` + "use strict"; + if (false) function foo() {} + else function bar() {} + `).not.toEval(); + }); +});