Explorar o código

LibJS: Share parameter parsing between regular and arrow functions

This simplifies try_parse_arrow_function_expression() and fixes a few
cases that should not produce an arrow function AST but did:

    (a,,) => {}
    (a b) => {}
    (a ...b) => {}
    (...b a) => {}

The new parsing logic checks whether parens are expected and uses
parse_function_parameters() if so, rolling back if a new syntax error
occurs during that. Otherwise it's just an identifier in which case we
parse the single parameter ourselves.
Linus Groh %!s(int64=4) %!d(string=hai) anos
pai
achega
965d952ff3

+ 42 - 70
Libraries/LibJS/Parser.cpp

@@ -334,62 +334,28 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
     };
 
     Vector<FunctionNode::Parameter> parameters;
-    bool parse_failed = false;
-    bool has_rest_parameter = false;
     i32 function_length = -1;
-    while (true) {
-        if (match(TokenType::Comma)) {
-            if (has_rest_parameter || !expect_parens) {
-                parse_failed = true;
-                break;
-            }
-            consume(TokenType::Comma);
-        } else if (match(TokenType::Identifier)) {
-            auto parameter_name = consume(TokenType::Identifier).value();
-            RefPtr<Expression> default_value;
-            if (expect_parens && match(TokenType::Equals)) {
-                consume(TokenType::Equals);
-                function_length = parameters.size();
-                default_value = parse_expression(2);
-            }
-            parameters.append({ parameter_name, default_value });
-        } else if (match(TokenType::TripleDot)) {
-            consume();
-            if (has_rest_parameter) {
-                parse_failed = true;
-                break;
-            }
-            has_rest_parameter = true;
-            function_length = parameters.size();
-            auto parameter_name = consume(TokenType::Identifier).value();
-            parameters.append({ parameter_name, nullptr, true });
-        } else if (match(TokenType::ParenClose)) {
-            if (expect_parens) {
-                consume(TokenType::ParenClose);
-                if (match(TokenType::Arrow)) {
-                    consume(TokenType::Arrow);
-                } else {
-                    parse_failed = true;
-                }
-                break;
-            }
-            parse_failed = true;
-            break;
-        } else if (match(TokenType::Arrow)) {
-            if (!expect_parens) {
-                consume(TokenType::Arrow);
-                break;
-            }
-            parse_failed = true;
-            break;
-        } else {
-            parse_failed = true;
-            break;
-        }
+    if (expect_parens) {
+        // We have parens around the function parameters and can re-use the same parsing
+        // logic used for regular functions: multiple parameters, default values, rest
+        // parameter, maybe a trailing comma. If we have a new syntax error afterwards we
+        // know parsing failed and rollback the parser state.
+        auto previous_syntax_errors = m_parser_state.m_errors.size();
+        parameters = parse_function_parameters(function_length);
+        if (m_parser_state.m_errors.size() > previous_syntax_errors)
+            return nullptr;
+        if (!match(TokenType::ParenClose))
+            return nullptr;
+        consume();
+    } else {
+        // No parens - this must be an identifier followed by arrow. That's it.
+        if (!match(TokenType::Identifier))
+            return nullptr;
+        parameters.append({ consume().value(), {} });
     }
-
-    if (parse_failed)
+    if (!match(TokenType::Arrow))
         return nullptr;
+    consume();
 
     if (function_length == -1)
         function_length = parameters.size();
@@ -1299,8 +1265,29 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool check_for_funct
         }
     }
     consume(TokenType::ParenOpen);
-    Vector<FunctionNode::Parameter> parameters;
     i32 function_length = -1;
+    auto parameters = parse_function_parameters(function_length);
+    consume(TokenType::ParenClose);
+
+    if (function_length == -1)
+        function_length = parameters.size();
+
+    TemporaryChange change(m_parser_state.m_in_function_context, true);
+    auto old_labels_in_scope = move(m_parser_state.m_labels_in_scope);
+    ScopeGuard guard([&]() {
+        m_parser_state.m_labels_in_scope = move(old_labels_in_scope);
+    });
+
+    bool is_strict = false;
+    auto body = parse_block_statement(is_strict);
+    body->add_variables(m_parser_state.m_var_scopes.last());
+    body->add_functions(m_parser_state.m_function_scopes.last());
+    return create_ast_node<FunctionNodeType>(name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>(), is_strict);
+}
+
+Vector<FunctionNode::Parameter> Parser::parse_function_parameters(int& function_length)
+{
+    Vector<FunctionNode::Parameter> parameters;
     while (match(TokenType::Identifier) || match(TokenType::TripleDot)) {
         if (match(TokenType::TripleDot)) {
             consume();
@@ -1321,22 +1308,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool check_for_funct
             break;
         consume(TokenType::Comma);
     }
-    consume(TokenType::ParenClose);
-
-    if (function_length == -1)
-        function_length = parameters.size();
-
-    TemporaryChange change(m_parser_state.m_in_function_context, true);
-    auto old_labels_in_scope = move(m_parser_state.m_labels_in_scope);
-    ScopeGuard guard([&]() {
-        m_parser_state.m_labels_in_scope = move(old_labels_in_scope);
-    });
-
-    bool is_strict = false;
-    auto body = parse_block_statement(is_strict);
-    body->add_variables(m_parser_state.m_var_scopes.last());
-    body->add_functions(m_parser_state.m_function_scopes.last());
-    return create_ast_node<FunctionNodeType>(name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>(), is_strict);
+    return parameters;
 }
 
 NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration(bool with_semicolon)

+ 1 - 0
Libraries/LibJS/Parser.h

@@ -48,6 +48,7 @@ public:
 
     template<typename FunctionNodeType>
     NonnullRefPtr<FunctionNodeType> parse_function_node(bool check_for_function_and_name = true, bool allow_super_property_lookup = false, bool allow_super_constructor_call = false);
+    Vector<FunctionNode::Parameter> parse_function_parameters(int& function_length);
 
     NonnullRefPtr<Statement> parse_statement();
     NonnullRefPtr<BlockStatement> parse_block_statement();

+ 13 - 0
Libraries/LibJS/Tests/functions/arrow-functions.js

@@ -148,3 +148,16 @@ test("cannot be constructed", () => {
         new foo();
     }).toThrowWithMessage(TypeError, "foo is not a constructor");
 });
+
+test("syntax errors", () => {
+    expect("a, => {}").not.toEval();
+    expect("(a, => {}").not.toEval();
+    expect("(,) => {}").not.toEval();
+    expect("(,,) => {}").not.toEval();
+    expect("(a,,) => {}").not.toEval();
+    expect("(a,,b) => {}").not.toEval();
+    expect("(a, ...b, ...c) => {}").not.toEval();
+    expect("(a b) => {}").not.toEval();
+    expect("(a ...b) => {}").not.toEval();
+    expect("(a = 1 = 2) => {}").not.toEval();
+});