Sfoglia il codice sorgente

LibJS: Allow anonymous functions as default exports

This requires a special case with names as the default function is
supposed to have a unique name ("*default*" in our case) but when
checked should have name "default".
davidot 2 anni fa
parent
commit
9f661d20f7

+ 39 - 15
Userland/Libraries/LibJS/Parser.cpp

@@ -2534,7 +2534,7 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement()
 }
 
 template<typename FunctionNodeType>
-NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options, Optional<Position> const& function_start)
+NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u16 parse_options, Optional<Position> const& function_start)
 {
     auto rule_start = function_start.has_value()
         ? RulePosition { *this, *function_start }
@@ -2572,7 +2572,9 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options, Op
             parse_options |= FunctionNodeParseOptions::IsGeneratorFunction;
         }
 
-        if (FunctionNodeType::must_have_name() || match_identifier())
+        if (parse_options & FunctionNodeParseOptions::HasDefaultExportName)
+            name = ExportStatement::local_name_for_default;
+        else if (FunctionNodeType::must_have_name() || match_identifier())
             name = consume_identifier().flystring_value();
         else if (is_function_expression && (match(TokenType::Yield) || match(TokenType::Await)))
             name = consume().flystring_value();
@@ -2624,7 +2626,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options, Op
         contains_direct_call_to_eval);
 }
 
-Vector<FunctionNode::Parameter> Parser::parse_formal_parameters(int& function_length, u8 parse_options)
+Vector<FunctionNode::Parameter> Parser::parse_formal_parameters(int& function_length, u16 parse_options)
 {
     auto rule_start = push_start();
     bool has_default_parameter = false;
@@ -4327,6 +4329,12 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
 
         auto lookahead_token = next_token();
 
+        enum class MatchesFunctionDeclaration {
+            Yes,
+            No,
+            WithoutName,
+        };
+
         // Note: For some reason the spec here has declaration which can have no name
         //       and the rest of the parser is just not setup for that. With these
         //       hacks below we get through most things but we should probably figure
@@ -4337,6 +4345,14 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
         //          `export default function() {}()`
         //       Since we parse this as an expression you are immediately allowed to call it
         //       which is incorrect and this should give a SyntaxError.
+
+        auto has_name = [&](Token const& token) {
+            if (token.type() != TokenType::ParenOpen)
+                return MatchesFunctionDeclaration::Yes;
+
+            return MatchesFunctionDeclaration::WithoutName;
+        };
+
         auto match_function_declaration = [&] {
             // Hack part 1.
             // Match a function declaration with a name, since we have async and generator
@@ -4347,32 +4363,40 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
 
             if (current_type == TokenType::Function) {
                 if (lookahead_token.type() == TokenType::Asterisk)
-                    return lookahead_lexer.next().type() != TokenType::ParenOpen; // function * <name>
+                    return has_name(lookahead_lexer.next()); // function * [name]
                 else
-                    return lookahead_token.type() != TokenType::ParenOpen; // function <name>
+                    return has_name(lookahead_token); // function [name]
             }
 
             if (current_type == TokenType::Async) {
                 if (lookahead_token.type() != TokenType::Function)
-                    return false;
+                    return MatchesFunctionDeclaration::No;
 
                 if (lookahead_token.trivia_contains_line_terminator())
-                    return false;
+                    return MatchesFunctionDeclaration::No;
 
                 auto lookahead_two_token = lookahead_lexer.next();
                 if (lookahead_two_token.type() == TokenType::Asterisk)
-                    return lookahead_lexer.next().type() != TokenType::ParenOpen; // async function * <name>
+                    return has_name(lookahead_lexer.next()); // async function * [name]
                 else
-                    return lookahead_two_token.type() != TokenType::ParenOpen; // async function <name>
+                    return has_name(lookahead_two_token); // async function [name]
             }
 
-            return false;
+            return MatchesFunctionDeclaration::No;
         };
 
-        if (match_function_declaration()) {
-            auto function_declaration = parse_function_node<FunctionDeclaration>();
+        if (auto matches_function = match_function_declaration(); matches_function != MatchesFunctionDeclaration::No) {
+
+            auto function_declaration = parse_function_node<FunctionDeclaration>(
+                (matches_function == MatchesFunctionDeclaration::WithoutName ? FunctionNodeParseOptions::HasDefaultExportName : 0)
+                | FunctionNodeParseOptions::CheckForFunctionAndName);
+
             m_state.current_scope_pusher->add_declaration(function_declaration);
-            local_name = function_declaration->name();
+            if (matches_function == MatchesFunctionDeclaration::WithoutName)
+                local_name = ExportStatement::local_name_for_default;
+            else
+                local_name = function_declaration->name();
+
             expression = move(function_declaration);
         } else if (match(TokenType::Class) && lookahead_token.type() != TokenType::CurlyOpen && lookahead_token.type() != TokenType::Extends) {
             // Hack part 2.
@@ -4633,7 +4657,7 @@ Parser::ForbiddenTokens Parser::ForbiddenTokens::forbid(std::initializer_list<To
     return result;
 }
 
-template NonnullRefPtr<FunctionExpression> Parser::parse_function_node(u8, Optional<Position> const&);
-template NonnullRefPtr<FunctionDeclaration> Parser::parse_function_node(u8, Optional<Position> const&);
+template NonnullRefPtr<FunctionExpression> Parser::parse_function_node(u16, Optional<Position> const&);
+template NonnullRefPtr<FunctionDeclaration> Parser::parse_function_node(u16, Optional<Position> const&);
 
 }

+ 4 - 3
Userland/Libraries/LibJS/Parser.h

@@ -27,7 +27,7 @@ enum class Associativity {
 };
 
 struct FunctionNodeParseOptions {
-    enum {
+    enum : u16 {
         CheckForFunctionAndName = 1 << 0,
         AllowSuperPropertyLookup = 1 << 1,
         AllowSuperConstructorCall = 1 << 2,
@@ -36,6 +36,7 @@ struct FunctionNodeParseOptions {
         IsArrowFunction = 1 << 5,
         IsGeneratorFunction = 1 << 6,
         IsAsyncFunction = 1 << 7,
+        HasDefaultExportName = 1 << 8,
     };
 };
 
@@ -55,8 +56,8 @@ public:
     NonnullRefPtr<Program> parse_program(bool starts_in_strict_mode = false);
 
     template<typename FunctionNodeType>
-    NonnullRefPtr<FunctionNodeType> parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName, Optional<Position> const& function_start = {});
-    Vector<FunctionNode::Parameter> parse_formal_parameters(int& function_length, u8 parse_options = 0);
+    NonnullRefPtr<FunctionNodeType> parse_function_node(u16 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName, Optional<Position> const& function_start = {});
+    Vector<FunctionNode::Parameter> parse_formal_parameters(int& function_length, u16 parse_options = 0);
 
     enum class AllowDuplicates {
         Yes,

+ 6 - 1
Userland/Libraries/LibJS/SourceTextModule.cpp

@@ -460,7 +460,12 @@ ThrowCompletionOr<void> SourceTextModule::initialize_environment(VM& vm)
                 auto const& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
 
                 // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
-                auto* function = ECMAScriptFunctionObject::create(realm(), function_declaration.name(), function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval());
+                // NOTE: Special case if the function is a default export of an anonymous function
+                //       it has name "*default*" but internally should have name "default".
+                FlyString function_name = function_declaration.name();
+                if (function_name == ExportStatement::local_name_for_default)
+                    function_name = "default"sv;
+                auto* function = ECMAScriptFunctionObject::create(realm(), function_name, function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval());
 
                 // 2. Perform ! env.InitializeBinding(dn, fo).
                 MUST(environment->initialize_binding(vm, name, function));

+ 26 - 0
Userland/Libraries/LibJS/Tests/modules/anon-func-decl-default-export.mjs

@@ -0,0 +1,26 @@
+try {
+    f();
+} catch (e) {
+    if (!(e instanceof ReferenceError)) throw e;
+    if (!e.message.includes("bindingUsedInFunction")) throw e;
+}
+
+let bindingUsedInFunction = 0;
+
+const immediateResult = f();
+const immediateName = f.name + "";
+
+import f from "./anon-func-decl-default-export.mjs";
+export default function () {
+    return bindingUsedInFunction++;
+}
+
+const postImportResult = f();
+const postImportName = f.name + "";
+
+export const passed =
+    immediateResult === 0 &&
+    postImportResult === 1 &&
+    bindingUsedInFunction === 2 &&
+    immediateName === "default" &&
+    postImportName === "default";

+ 4 - 0
Userland/Libraries/LibJS/Tests/modules/basic-modules.js

@@ -202,6 +202,10 @@ describe("in- and exports", () => {
     test("import lexical binding before import statement behaves as initialized but non mutable binding", () => {
         expectModulePassed("./accessing-lex-import-before-decl.mjs");
     });
+
+    test("exporting anonymous function", () => {
+        expectModulePassed("./anon-func-decl-default-export.mjs");
+    });
 });
 
 describe("loops", () => {