diff --git a/lib/ast.js b/lib/ast.js index c0a50d437..07f2b5010 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -889,12 +889,13 @@ var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", { }, }); -var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", { +var AST_Import = DEFNODE("Import", "imported_name imported_names module_name assert_clause", { $documentation: "An `import` statement", $propdoc: { imported_name: "[AST_SymbolImport] The name of the variable holding the module's default export.", imported_names: "[AST_NameMapping*] The names of non-default imported variables", module_name: "[AST_String] String literal describing where this module came from", + assert_clause: "[AST_Object?] The import assertion" }, _walk: function(visitor) { return visitor._visit(this, function() { @@ -923,14 +924,15 @@ var AST_ImportMeta = DEFNODE("ImportMeta", null, { $documentation: "A reference to import.meta", }); -var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default exported_names module_name", { +var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default exported_names module_name assert_clause", { $documentation: "An `export` statement", $propdoc: { exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition", exported_value: "[AST_Node?] An exported value", exported_names: "[AST_NameMapping*?] List of exported names", module_name: "[AST_String?] Name of the file to load exports from", - is_default: "[Boolean] Whether this is the default exported value of this module" + is_default: "[Boolean] Whether this is the default exported value of this module", + assert_clause: "[AST_Object?] The import assertion" }, _walk: function (visitor) { return visitor._visit(this, function () { diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index fb75cca71..b17510e88 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -158,6 +158,7 @@ import { AST_With, AST_Yield, } from "./ast.js"; +import { is_basic_identifier_string } from "./parse.js"; (function() { @@ -179,6 +180,24 @@ import { return body; }; + const assert_clause_from_moz = (assertions) => { + if (assertions && assertions.length > 0) { + return new AST_Object({ + start: my_start_token(assertions), + end: my_end_token(assertions), + properties: assertions.map((assertion_kv) => + new AST_ObjectKeyVal({ + start: my_start_token(assertion_kv), + end: my_end_token(assertion_kv), + key: assertion_kv.key.name || assertion_kv.key.value, + value: from_moz(assertion_kv.value) + }) + ) + }) + } + return null; + } + var MOZ_TO_ME = { Program: function(M) { return new AST_Toplevel({ @@ -499,7 +518,8 @@ import { end : my_end_token(M), imported_name: imported_name, imported_names : imported_names, - module_name : from_moz(M.source) + module_name : from_moz(M.source), + assert_clause: assert_clause_from_moz(M.assertions) }); }, ExportAllDeclaration: function(M) { @@ -512,7 +532,8 @@ import { foreign_name: new AST_SymbolExportForeign({ name: "*" }) }) ], - module_name: from_moz(M.source) + module_name: from_moz(M.source), + assert_clause: assert_clause_from_moz(M.assertions) }); }, ExportNamedDeclaration: function(M) { @@ -526,7 +547,8 @@ import { name: from_moz(specifier.local) }); }) : null, - module_name: from_moz(M.source) + module_name: from_moz(M.source), + assert_clause: assert_clause_from_moz(M.assertions) }); }, ExportDefaultDeclaration: function(M) { @@ -818,12 +840,30 @@ import { }; }); + const assert_clause_to_moz = assert_clause => { + const assertions = []; + if (assert_clause) { + for (const { key, value } of assert_clause.properties) { + const key_moz = is_basic_identifier_string(key) + ? { type: 'Identifier', name: key } + : { type: 'Literal', value: key, raw: JSON.stringify(key) }; + assertions.push({ + type: 'ImportAttribute', + key: key_moz, + value: to_moz(value) + }); + } + } + return assertions; + } + def_to_moz(AST_Export, function To_Moz_ExportDeclaration(M) { if (M.exported_names) { if (M.exported_names[0].name.name === "*") { return { type: "ExportAllDeclaration", - source: to_moz(M.module_name) + source: to_moz(M.module_name), + assertions: assert_clause_to_moz(M.assert_clause) }; } return { @@ -836,7 +876,8 @@ import { }; }), declaration: to_moz(M.exported_definition), - source: to_moz(M.module_name) + source: to_moz(M.module_name), + assertions: assert_clause_to_moz(M.assert_clause) }; } return { @@ -870,7 +911,8 @@ import { return { type: "ImportDeclaration", specifiers: specifiers, - source: to_moz(M.module_name) + source: to_moz(M.module_name), + assertions: assert_clause_to_moz(M.assert_clause) }; }); diff --git a/lib/output.js b/lib/output.js index c85b61b46..1ee6b8e52 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1637,6 +1637,10 @@ function OutputStream(options) { output.space(); } self.module_name.print(output); + if (self.assert_clause) { + output.print("assert"); + self.assert_clause.print(output); + } output.semicolon(); }); DEFPRINT(AST_ImportMeta, function(self, output) { @@ -1702,6 +1706,10 @@ function OutputStream(options) { output.space(); self.module_name.print(output); } + if (self.assert_clause) { + output.print("assert"); + self.assert_clause.print(output); + } if (self.exported_value && !(self.exported_value instanceof AST_Defun || self.exported_value instanceof AST_Function || diff --git a/lib/parse.js b/lib/parse.js index 3e0cd8b13..1f4ef4e80 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1222,7 +1222,7 @@ function parse($TEXT, options) { } if (S.token.value == "import" && !is_token(peek(), "punc", "(") && !is_token(peek(), "punc", ".")) { next(); - var node = import_(); + var node = import_statement(); semicolon(); return node; } @@ -1374,7 +1374,7 @@ function parse($TEXT, options) { case "export": if (!is_token(peek(), "punc", "(")) { next(); - var node = export_(); + var node = export_statement(); if (is("punc", ";")) semicolon(); return node; } @@ -2666,7 +2666,15 @@ function parse($TEXT, options) { } } - function import_() { + function maybe_import_assertion() { + if (is("name", "assert") && !has_newline_before(S.token)) { + next(); + return object_or_destructuring_(); + } + return null + } + + function import_statement() { var start = prev(); var imported_name; @@ -2689,16 +2697,20 @@ function parse($TEXT, options) { unexpected(); } next(); + + const assert_clause = maybe_import_assertion(); + return new AST_Import({ - start: start, - imported_name: imported_name, - imported_names: imported_names, + start, + imported_name, + imported_names, module_name: new AST_String({ start: mod_str, value: mod_str.value, quote: mod_str.quote, end: mod_str, }), + assert_clause, end: S.token, }); } @@ -2806,7 +2818,7 @@ function parse($TEXT, options) { return names; } - function export_() { + function export_statement() { var start = S.token; var is_default; var exported_names; @@ -2824,6 +2836,8 @@ function parse($TEXT, options) { } next(); + const assert_clause = maybe_import_assertion(); + return new AST_Export({ start: start, is_default: is_default, @@ -2835,6 +2849,7 @@ function parse($TEXT, options) { end: mod_str, }), end: prev(), + assert_clause }); } else { return new AST_Export({ @@ -2880,6 +2895,7 @@ function parse($TEXT, options) { exported_value: exported_value, exported_definition: exported_definition, end: prev(), + assert_clause: null }); } diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 2276cdbc7..31c28a818 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -303,6 +303,34 @@ export_from_statement: { expect_exact: 'export*from"a.js";export{A}from"a.js";export{A,B}from"a.js";export{C};' } +import_assertions: { + input: { + import "hello" assert { key: 'value' }; + } + expect_exact: 'import"hello"assert{key:"value"};' +} + +import_assertions_with_spaces_in_obj: { + input: { + import "hello" assert { 'k e y': 'value' }; + } + expect_exact: 'import"hello"assert{"k e y":"value"};' +} + +export_from_assertions: { + input: { + export * from "hello" assert { key: 'value' }; + } + expect_exact: 'export*from"hello"assert{key:"value"};' +} + +export_named_from_assertions: { + input: { + export { x } from "hello" assert { key: 'value' }; + } + expect_exact: 'export{x}from"hello"assert{key:"value"};' +} + import_statement_mangling: { mangle = { toplevel: true }; input: {