Kaynağa Gözat

LibJS: Parse assert clauses of in- and export statements

Based on proposal: https://tc39.es/proposal-import-assertions
Since imports are not supported yet this is not functional.
davidot 3 yıl önce
ebeveyn
işleme
be3b806487

+ 20 - 3
Userland/Libraries/LibJS/AST.cpp

@@ -3510,6 +3510,16 @@ Value ExportStatement::execute(Interpreter& interpreter, GlobalObject& global_ob
     return {};
 }
 
+static void dump_assert_clauses(ModuleRequest const& request)
+{
+    if (!request.assertions.is_empty()) {
+        out("[ ");
+        for (auto& assertion : request.assertions)
+            out("{}: {}, ", assertion.key, assertion.value);
+        out(" ]");
+    }
+}
+
 void ExportStatement::dump(int indent) const
 {
     ASTNode::dump(indent);
@@ -3525,7 +3535,12 @@ void ExportStatement::dump(int indent) const
 
     for (auto& entry : m_entries) {
         print_indent(indent + 2);
-        outln("ModuleRequest: {}, ImportName: {}, LocalName: {}, ExportName: {}", string_or_null(entry.module_request), entry.kind == ExportEntry::ModuleRequest ? string_or_null(entry.local_or_import_name) : "null", entry.kind != ExportEntry::ModuleRequest ? string_or_null(entry.local_or_import_name) : "null", string_or_null(entry.export_name));
+        out("ModuleRequest: {}", entry.module_request.module_specifier);
+        dump_assert_clauses(entry.module_request);
+        outln(", ImportName: {}, LocalName: {}, ExportName: {}",
+            entry.kind == ExportEntry::Kind::ModuleRequest ? string_or_null(entry.local_or_import_name) : "null",
+            entry.kind != ExportEntry::Kind::ModuleRequest ? string_or_null(entry.local_or_import_name) : "null",
+            string_or_null(entry.export_name));
     }
 }
 
@@ -3535,9 +3550,11 @@ void ImportStatement::dump(int indent) const
     print_indent(indent + 1);
     if (m_entries.is_empty()) {
         // direct from "module" import
-        outln("Entire module '{}'", m_module_request);
+        outln("Entire module '{}'", m_module_request.module_specifier);
+        dump_assert_clauses(m_module_request);
     } else {
-        outln("(ExportEntries) from {}", m_module_request);
+        outln("(ExportEntries) from {}", m_module_request.module_specifier);
+        dump_assert_clauses(m_module_request);
 
         for (auto& entry : m_entries) {
             print_indent(indent + 2);

+ 29 - 6
Userland/Libraries/LibJS/AST.h

@@ -217,6 +217,29 @@ private:
     NonnullRefPtrVector<FunctionDeclaration> m_functions_hoistable_with_annexB_extension;
 };
 
+// 2.9 ModuleRequest Records, https://tc39.es/proposal-import-assertions/#sec-modulerequest-record
+struct ModuleRequest {
+    struct Assertion {
+        String key;
+        String value;
+    };
+
+    ModuleRequest() = default;
+
+    explicit ModuleRequest(String specifier)
+        : module_specifier(move(specifier))
+    {
+    }
+
+    void add_assertion(String key, String value)
+    {
+        assertions.empend(move(key), move(value));
+    }
+
+    String module_specifier;      // [[Specifier]]
+    Vector<Assertion> assertions; // [[Assertions]]
+};
+
 class ImportStatement final : public Statement {
 public:
     struct ImportEntry {
@@ -224,9 +247,9 @@ public:
         String local_name;
     };
 
-    explicit ImportStatement(SourceRange source_range, StringView from_module, Vector<ImportEntry> entries = {})
+    explicit ImportStatement(SourceRange source_range, ModuleRequest from_module, Vector<ImportEntry> entries = {})
         : Statement(source_range)
-        , m_module_request(from_module)
+        , m_module_request(move(from_module))
         , m_entries(move(entries))
     {
     }
@@ -238,14 +261,14 @@ public:
     bool has_bound_name(StringView name) const;
 
 private:
-    String m_module_request;
+    ModuleRequest m_module_request;
     Vector<ImportEntry> m_entries;
 };
 
 class ExportStatement final : public Statement {
 public:
     struct ExportEntry {
-        enum Kind {
+        enum class Kind {
             ModuleRequest,
             LocalExport
         } kind;
@@ -253,13 +276,13 @@ public:
         String export_name;
 
         // Only if module request
-        String module_request;
+        ModuleRequest module_request;
 
         // Has just one of ones below
         String local_or_import_name;
 
         ExportEntry(String export_name, String local_name)
-            : kind(LocalExport)
+            : kind(Kind::LocalExport)
             , export_name(move(export_name))
             , local_or_import_name(move(local_name))
         {

+ 73 - 8
Userland/Libraries/LibJS/Parser.cpp

@@ -529,7 +529,7 @@ void Parser::parse_module(Program& program)
         if (export_statement.has_statement())
             continue;
         for (auto& entry : export_statement.entries()) {
-            if (entry.kind == ExportStatement::ExportEntry::ModuleRequest)
+            if (entry.kind == ExportStatement::ExportEntry::Kind::ModuleRequest)
                 return;
 
             auto const& exported_name = entry.local_or_import_name;
@@ -3912,8 +3912,58 @@ void Parser::check_identifier_name_for_assignment_validity(StringView name, bool
     }
 }
 
+bool Parser::match_assert_clause() const
+{
+    return !m_state.current_token.trivia_contains_line_terminator() && m_state.current_token.original_value() == "assert"sv;
+}
+
+// AssertClause, https://tc39.es/proposal-import-assertions/#prod-AssertClause
+void Parser::parse_assert_clause(ModuleRequest& request)
+{
+    VERIFY(m_state.current_token.original_value() == "assert"sv);
+    consume(TokenType::Identifier);
+    consume(TokenType::CurlyOpen);
+
+    while (!done() && !match(TokenType::CurlyClose)) {
+        String key;
+        if (match(TokenType::StringLiteral)) {
+            key = parse_string_literal(m_state.current_token)->value().to_string();
+            consume();
+        } else if (match_identifier_name()) {
+            key = consume().value();
+        } else {
+            expected("IdentifierName or StringValue as AssertionKey");
+            consume();
+        }
+
+        consume(TokenType::Colon);
+
+        if (match(TokenType::StringLiteral)) {
+            for (auto& entries : request.assertions) {
+                if (entries.key == key)
+                    syntax_error(String::formatted("Duplicate assertion clauses with name: {}", key));
+            }
+            request.add_assertion(move(key), parse_string_literal(m_state.current_token)->value().to_string());
+        }
+        consume(TokenType::StringLiteral);
+
+        if (match(TokenType::Comma))
+            consume(TokenType::Comma);
+        else
+            break;
+    }
+
+    consume(TokenType::CurlyClose);
+}
+
 NonnullRefPtr<ImportStatement> Parser::parse_import_statement(Program& program)
 {
+    // We use the extended syntax which adds:
+    //  ImportDeclaration:
+    //      import ImportClause FromClause [no LineTerminator here] AssertClause;
+    //      import ModuleSpecifier [no LineTerminator here] AssertClause;
+    // From:  https://tc39.es/proposal-import-assertions/#prod-ImportDeclaration
+
     auto rule_start = push_start();
     if (program.type() != Program::Type::Module)
         syntax_error("Cannot use import statement outside a module");
@@ -3921,8 +3971,11 @@ NonnullRefPtr<ImportStatement> Parser::parse_import_statement(Program& program)
     consume(TokenType::Import);
 
     if (match(TokenType::StringLiteral)) {
-        auto module_name = consume(TokenType::StringLiteral).value();
-        return create_ast_node<ImportStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, module_name);
+        auto module_request = ModuleRequest(consume(TokenType::StringLiteral).value());
+        if (match_assert_clause())
+            parse_assert_clause(module_request);
+
+        return create_ast_node<ImportStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, move(module_request));
     }
 
     auto match_imported_binding = [&] {
@@ -4016,7 +4069,10 @@ NonnullRefPtr<ImportStatement> Parser::parse_import_statement(Program& program)
     if (from_statement != "from"sv)
         syntax_error(String::formatted("Expected 'from' got {}", from_statement));
 
-    auto module_name = consume(TokenType::StringLiteral).value();
+    auto module_request = ModuleRequest(consume(TokenType::StringLiteral).value());
+
+    if (match_assert_clause())
+        parse_assert_clause(module_request);
 
     Vector<ImportStatement::ImportEntry> entries;
     entries.ensure_capacity(entries_with_location.size());
@@ -4035,11 +4091,16 @@ NonnullRefPtr<ImportStatement> Parser::parse_import_statement(Program& program)
         entries.append(move(entry.entry));
     }
 
-    return create_ast_node<ImportStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, module_name, move(entries));
+    return create_ast_node<ImportStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, move(module_request), move(entries));
 }
 
 NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
 {
+    // We use the extended syntax which adds:
+    //  ExportDeclaration:
+    //      export ExportFromClause FromClause [no LineTerminator here] AssertClause ;
+    // From:  https://tc39.es/proposal-import-assertions/#prod-ExportDeclaration
+
     auto rule_start = push_start();
     if (program.type() != Program::Type::Module)
         syntax_error("Cannot use export statement outside a module");
@@ -4062,10 +4123,10 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
         ExportStatement::ExportEntry entry;
         Position position;
 
-        void to_module_request(String from_module)
+        void to_module_request(ModuleRequest from_module)
         {
             entry.kind = ExportStatement::ExportEntry::Kind::ModuleRequest;
-            entry.module_request = from_module;
+            entry.module_request = move(from_module);
         }
     };
 
@@ -4200,7 +4261,11 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
         if (check_for_from != NotAllowed && match_from()) {
             consume(TokenType::Identifier);
             if (match(TokenType::StringLiteral)) {
-                auto from_specifier = consume().value();
+                auto from_specifier = ModuleRequest(consume().value());
+
+                if (match_assert_clause())
+                    parse_assert_clause(from_specifier);
+
                 for (auto& entry : entries_with_location)
                     entry.to_module_request(from_specifier);
             } else {

+ 2 - 0
Userland/Libraries/LibJS/Parser.h

@@ -185,6 +185,7 @@ private:
     bool match_secondary_expression(const Vector<TokenType>& forbidden = {}) const;
     bool match_statement() const;
     bool match_export_or_import() const;
+    bool match_assert_clause() const;
     bool match_declaration() const;
     bool try_match_let_declaration() const;
     bool match_variable_declaration() const;
@@ -220,6 +221,7 @@ private:
 
     bool parse_directive(ScopeNode& body);
     void parse_statement_list(ScopeNode& output_node, AllowLabelledFunction allow_labelled_functions = AllowLabelledFunction::No);
+    void parse_assert_clause(ModuleRequest& request);
 
     struct RulePosition {
         AK_MAKE_NONCOPYABLE(RulePosition);