diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 9b2dfdc1e..e09132947 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1912,8 +1912,8 @@ pub struct ImportDeclaration<'a> { /// `None` for `import 'foo'`, `Some([])` for `import {} from 'foo'` pub specifiers: Option>, pub source: StringLiteral, - pub assertions: Option>, // Some(vec![]) for empty assertion - pub import_kind: ImportOrExportKind, // `import type { foo } from 'bar'` + pub with_clause: Option>, // Some(vec![]) for empty assertion + pub import_kind: ImportOrExportKind, // `import type { foo } from 'bar'` } #[derive(Debug, Hash)] @@ -1958,6 +1958,15 @@ pub struct ImportNamespaceSpecifier { pub local: BindingIdentifier, } +#[derive(Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] +pub struct WithClause<'a> { + #[cfg_attr(feature = "serde", serde(flatten))] + pub span: Span, + pub attributes_keyword: IdentifierName, // `with` or `assert` + pub with_entries: Vec<'a, ImportAttribute>, +} + #[derive(Debug, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] pub struct ImportAttribute { @@ -2027,8 +2036,8 @@ pub struct ExportAllDeclaration<'a> { pub span: Span, pub exported: Option, pub source: StringLiteral, - pub assertions: Option>, // Some(vec![]) for empty assertion - pub export_kind: ImportOrExportKind, // `export type *` + pub with_clause: Option>, // Some(vec![]) for empty assertion + pub export_kind: ImportOrExportKind, // `export type *` } impl<'a> ExportAllDeclaration<'a> { diff --git a/crates/oxc_ast/src/ast_builder.rs b/crates/oxc_ast/src/ast_builder.rs index 884784f2f..f2464d8a5 100644 --- a/crates/oxc_ast/src/ast_builder.rs +++ b/crates/oxc_ast/src/ast_builder.rs @@ -985,10 +985,10 @@ impl<'a> AstBuilder<'a> { span: Span, specifiers: Option>, source: StringLiteral, - assertions: Option>, + with_clause: Option>, import_kind: ImportOrExportKind, ) -> Box<'a, ImportDeclaration<'a>> { - self.alloc(ImportDeclaration { span, specifiers, source, assertions, import_kind }) + self.alloc(ImportDeclaration { span, specifiers, source, with_clause, import_kind }) } pub fn export_all_declaration( @@ -996,10 +996,10 @@ impl<'a> AstBuilder<'a> { span: Span, exported: Option, source: StringLiteral, - assertions: Option>, + with_clause: Option>, export_kind: ImportOrExportKind, ) -> Box<'a, ExportAllDeclaration<'a>> { - self.alloc(ExportAllDeclaration { span, exported, source, assertions, export_kind }) + self.alloc(ExportAllDeclaration { span, exported, source, with_clause, export_kind }) } pub fn export_default_declaration( diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 7bf4c666b..bb6163804 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -649,7 +649,7 @@ impl<'a, const MINIFY: bool> Gen for ImportDeclaration<'a> { p.print(b'\''); p.print_str(self.source.value.as_bytes()); p.print(b'\''); - self.assertions.gen(p, ctx); + self.with_clause.gen(p, ctx); p.print_semicolon_after_statement(); return; } @@ -713,17 +713,23 @@ impl<'a, const MINIFY: bool> Gen for ImportDeclaration<'a> { p.print_str(b" from "); } self.source.gen(p, ctx); - self.assertions.gen(p, ctx); + self.with_clause.gen(p, ctx); p.print_semicolon_after_statement(); } } -impl<'a, const MINIFY: bool> Gen for Option> { +impl<'a, const MINIFY: bool> Gen for Option> { fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) { - if let Some(assertions) = &self { - p.print_str(b"assert"); - p.print_block(assertions, Separator::Comma, ctx); - }; + if let Some(with_clause) = self { + with_clause.gen(p, ctx); + } + } +} + +impl<'a, const MINIFY: bool> Gen for WithClause<'a> { + fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) { + self.attributes_keyword.gen(p, ctx); + p.print_block(&self.with_entries, Separator::Comma, ctx); } } @@ -804,7 +810,7 @@ impl<'a, const MINIFY: bool> Gen for ExportAllDeclaration<'a> { p.print_str(b" from"); self.source.gen(p, ctx); - self.assertions.gen(p, ctx); + self.with_clause.gen(p, ctx); p.print_semicolon_after_statement(); } diff --git a/crates/oxc_formatter/src/gen.rs b/crates/oxc_formatter/src/gen.rs index ecd542cf6..b3567284d 100644 --- a/crates/oxc_formatter/src/gen.rs +++ b/crates/oxc_formatter/src/gen.rs @@ -571,7 +571,7 @@ impl<'a> Gen for ImportDeclaration<'a> { p.print(b'\''); p.print_str(self.source.value.as_bytes()); p.print(b'\''); - self.assertions.gen(p); + self.with_clause.gen(p); p.print_semicolon_after_statement(); return; } @@ -644,19 +644,25 @@ impl<'a> Gen for ImportDeclaration<'a> { p.print_str(b" from "); } self.source.gen(p); - self.assertions.gen(p); + self.with_clause.gen(p); p.print_semicolon_after_statement(); } } -impl<'a> Gen for Option> { +impl<'a> Gen for Option> { fn gen(&self, p: &mut Formatter) { - if let Some(assertions) = &self { + if let Some(with_clause) = self { p.print_space(); - p.print_str(b"assert"); + with_clause.attributes_keyword.gen(p); p.print_space(); - p.print_block(assertions, Separator::Comma); - }; + with_clause.with_entries.gen(p); + } + } +} + +impl<'a> Gen for Vec<'a, ImportAttribute> { + fn gen(&self, p: &mut Formatter) { + p.print_block(self, Separator::Comma); } } @@ -735,7 +741,7 @@ impl<'a> Gen for ExportAllDeclaration<'a> { p.print_str(b" from"); p.print_space(); self.source.gen(p); - self.assertions.gen(p); + self.with_clause.gen(p); p.print_semicolon_after_statement(); } diff --git a/crates/oxc_parser/src/js/module.rs b/crates/oxc_parser/src/js/module.rs index ea907c7c7..61cbe7b94 100644 --- a/crates/oxc_parser/src/js/module.rs +++ b/crates/oxc_parser/src/js/module.rs @@ -58,14 +58,14 @@ impl<'a> Parser<'a> { }; let source = self.parse_literal_string()?; - let assertions = self.parse_import_attributes()?; + let with_clause = self.parse_import_attributes()?; self.asi()?; let span = self.end_span(span); let decl = ModuleDeclaration::ImportDeclaration(self.ast.import_declaration( span, specifiers, source, - assertions, + with_clause, import_kind, )); Ok(self.ast.module_declaration(decl)) @@ -135,19 +135,23 @@ impl<'a> Parser<'a> { Ok(specifiers) } - /// [Import assertion](https://tc39.es/proposal-import-assertions) - fn parse_import_attributes(&mut self) -> Result>> { - if !self.at(Kind::Assert) || self.cur_token().is_on_new_line { - return Ok(None); - } - self.bump_any(); + /// [Import Attributes](https://tc39.es/proposal-import-attributes) + fn parse_import_attributes(&mut self) -> Result>> { + let span = self.start_span(); + let attributes_keyword = match self.cur_kind() { + Kind::Assert if !self.cur_token().is_on_new_line => self.parse_identifier_name()?, + Kind::With => self.parse_identifier_name()?, + _ => { + return Ok(None); + } + }; let ctx = self.ctx; self.ctx = Context::default(); - let entries = AssertEntries::parse(self)?.elements; + let with_entries = AssertEntries::parse(self)?.elements; self.ctx = ctx; - Ok(Some(entries)) + Ok(Some(WithClause { span: self.end_span(span), attributes_keyword, with_entries })) } pub(crate) fn parse_ts_export_assignment_declaration( @@ -365,10 +369,10 @@ impl<'a> Parser<'a> { let exported = self.eat(Kind::As).then(|| self.parse_module_export_name()).transpose()?; self.expect(Kind::From)?; let source = self.parse_literal_string()?; - let assertions = self.parse_import_attributes()?; + let with_clause = self.parse_import_attributes()?; self.asi()?; let span = self.end_span(span); - Ok(self.ast.export_all_declaration(span, exported, source, assertions, export_kind)) + Ok(self.ast.export_all_declaration(span, exported, source, with_clause, export_kind)) } // ImportSpecifier : diff --git a/tasks/coverage/codegen_test262.snap b/tasks/coverage/codegen_test262.snap index 65cff5d92..91dd7a9e0 100644 --- a/tasks/coverage/codegen_test262.snap +++ b/tasks/coverage/codegen_test262.snap @@ -1,3 +1,3 @@ codegen_test262 Summary: -AST Parsed : 44738/44738 (100.00%) -Positive Passed: 44738/44738 (100.00%) +AST Parsed : 45668/45668 (100.00%) +Positive Passed: 45668/45668 (100.00%) diff --git a/tasks/coverage/minifier_test262.snap b/tasks/coverage/minifier_test262.snap index cb57fc58a..cd433d9fb 100644 --- a/tasks/coverage/minifier_test262.snap +++ b/tasks/coverage/minifier_test262.snap @@ -1,3 +1,3 @@ minifier_test262 Summary: -AST Parsed : 44738/44738 (100.00%) -Positive Passed: 44738/44738 (100.00%) +AST Parsed : 45668/45668 (100.00%) +Positive Passed: 45668/45668 (100.00%) diff --git a/tasks/coverage/parser_test262.snap b/tasks/coverage/parser_test262.snap index 50c084581..d3f06cfa0 100644 --- a/tasks/coverage/parser_test262.snap +++ b/tasks/coverage/parser_test262.snap @@ -1,7 +1,11 @@ parser_test262 Summary: -AST Parsed : 44161/44161 (100.00%) -Positive Passed: 44161/44161 (100.00%) -Negative Passed: 3918/3918 (100.00%) +AST Parsed : 45097/45097 (100.00%) +Positive Passed: 45097/45097 (100.00%) +Negative Passed: 3925/3929 (99.90%) +Expect Syntax Error: "language/import/import-assertions/json-invalid.js" +Expect Syntax Error: "language/import/import-assertions/json-named-bindings.js" +Expect Syntax Error: "language/import/import-attributes/json-invalid.js" +Expect Syntax Error: "language/import/import-attributes/json-named-bindings.js" × '0'-prefixed octal literals and octal escape sequences are deprecated ╭─[annexB/language/expressions/template-literal/legacy-octal-escape-sequence-strict.js:17:1] 17 │ @@ -12971,13 +12975,6 @@ Negative Passed: 3918/3918 (100.00%) · ───────────────── ╰──── - × The keyword 'yield' is reserved - ╭─[language/expressions/dynamic-import/2nd-param-yield-ident-invalid.js:18:1] - 18 │ - 19 │ import('./empty_FIXTURE.js', yield); - · ───── - ╰──── - × Keywords cannot contain escape characters ╭─[language/expressions/dynamic-import/escape-sequence-import.js:34:1] 34 │ @@ -12985,6 +12982,20 @@ Negative Passed: 3918/3918 (100.00%) · ─────────── ╰──── + × The keyword 'yield' is reserved + ╭─[language/expressions/dynamic-import/import-assertions/2nd-param-yield-ident-invalid.js:18:1] + 18 │ + 19 │ import('./empty_FIXTURE.js', yield); + · ───── + ╰──── + + × The keyword 'yield' is reserved + ╭─[language/expressions/dynamic-import/import-attributes/2nd-param-yield-ident-invalid.js:18:1] + 18 │ + 19 │ import('./empty_FIXTURE.js', yield); + · ───── + ╰──── + × Cannot assign to this expression ╭─[language/expressions/dynamic-import/syntax/invalid/invalid-assignmenttargettype-syntax-error-1-update-expression.js:45:1] 45 │ @@ -18593,6 +18604,27 @@ Negative Passed: 3918/3918 (100.00%) · ▲ ╰──── + × Keywords cannot contain escape characters + ╭─[language/literals/boolean/false-with-unicode.js:19:1] + 19 │ + 20 │ f\u{61}lse; + · ────────── + ╰──── + + × Keywords cannot contain escape characters + ╭─[language/literals/boolean/true-with-unicode.js:19:1] + 19 │ + 20 │ tru\u{65}; + · ───────── + ╰──── + + × Keywords cannot contain escape characters + ╭─[language/literals/null/null-with-unicode.js:19:1] + 19 │ + 20 │ n\u{75}ll; + · ───────── + ╰──── + × '0'-prefixed octal literals and octal escape sequences are deprecated ╭─[language/literals/numeric/7.8.3-1gs.js:14:1] 14 │ @@ -19320,45 +19352,6 @@ Negative Passed: 3918/3918 (100.00%) · ─ ╰──── - × Identifier `test262_a` has already been declared - ╭─[language/module-code/early-dup-assert-key-export.js:20:1] - 20 │ export * from './import-assertion-3_FIXTURE.js' assert { - 21 │ test262_a: '', - · ────┬──── - · ╰── `test262_a` has already been declared here - 22 │ test262_b: '', - 23 │ 'test262_\u0061': '' - · ────────┬─────── - · ╰── It can not be redeclared here - 24 │ }; - ╰──── - - × Identifier `test262_a` has already been declared - ╭─[language/module-code/early-dup-assert-key-import-nobinding.js:21:1] - 21 │ import './import-assertion-2_FIXTURE.js' assert { - 22 │ test262_a: '', - · ────┬──── - · ╰── `test262_a` has already been declared here - 23 │ test262_b: '', - 24 │ 'test262_\u0061': '' - · ────────┬─────── - · ╰── It can not be redeclared here - 25 │ }; - ╰──── - - × Identifier `test262_a` has already been declared - ╭─[language/module-code/early-dup-assert-key-import-withbinding.js:21:1] - 21 │ import x from './import-assertion-1_FIXTURE.js' assert { - 22 │ test262_a: '', - · ────┬──── - · ╰── `test262_a` has already been declared here - 23 │ test262_b: '', - 24 │ 'test262_\u0061': '' - · ────────┬─────── - · ╰── It can not be redeclared here - 25 │ }; - ╰──── - × Duplicated export 'z' ╭─[language/module-code/early-dup-export-as-star-as.js:17:1] 17 │ var x; @@ -19739,6 +19732,78 @@ Negative Passed: 3918/3918 (100.00%) 22 │ ╰──── + × Identifier `type` has already been declared + ╭─[language/module-code/import-assertions/early-dup-assert-key-export.js:20:1] + 20 │ export * from './import-assertion-3_FIXTURE.js' assert { + 21 │ type: 'json', + · ──┬─ + · ╰── `type` has already been declared here + 22 │ 'typ\u0065': '' + · ─────┬───── + · ╰── It can not be redeclared here + 23 │ }; + ╰──── + + × Identifier `type` has already been declared + ╭─[language/module-code/import-assertions/early-dup-assert-key-import-nobinding.js:21:1] + 21 │ import './import-assertion-2_FIXTURE.js' assert { + 22 │ type: 'json', + · ──┬─ + · ╰── `type` has already been declared here + 23 │ 'typ\u0065': '' + · ─────┬───── + · ╰── It can not be redeclared here + 24 │ }; + ╰──── + + × Identifier `type` has already been declared + ╭─[language/module-code/import-assertions/early-dup-assert-key-import-withbinding.js:21:1] + 21 │ import x from './import-assertion-1_FIXTURE.js' assert { + 22 │ type: 'json', + · ──┬─ + · ╰── `type` has already been declared here + 23 │ 'typ\u0065': '' + · ─────┬───── + · ╰── It can not be redeclared here + 24 │ }; + ╰──── + + × Identifier `type` has already been declared + ╭─[language/module-code/import-attributes/early-dup-attribute-key-export.js:20:1] + 20 │ export * from './import-attribute-3_FIXTURE.js' with { + 21 │ type: 'json', + · ──┬─ + · ╰── `type` has already been declared here + 22 │ 'typ\u0065': '' + · ─────┬───── + · ╰── It can not be redeclared here + 23 │ }; + ╰──── + + × Identifier `type` has already been declared + ╭─[language/module-code/import-attributes/early-dup-attribute-key-import-nobinding.js:21:1] + 21 │ import './import-attribute-2_FIXTURE.js' with { + 22 │ type: 'json', + · ──┬─ + · ╰── `type` has already been declared here + 23 │ 'typ\u0065': '' + · ─────┬───── + · ╰── It can not be redeclared here + 24 │ }; + ╰──── + + × Identifier `type` has already been declared + ╭─[language/module-code/import-attributes/early-dup-attribute-key-import-withbinding.js:21:1] + 21 │ import x from './import-attribute-1_FIXTURE.js' with { + 22 │ type: 'json', + · ──┬─ + · ╰── `type` has already been declared here + 23 │ 'typ\u0065': '' + · ─────┬───── + · ╰── It can not be redeclared here + 24 │ }; + ╰──── + × Private identifier '#x' is not allowed outside class bodies ╭─[language/module-code/invalid-private-names-call-expression-bad-reference.js:39:1] 39 │ diff --git a/tasks/coverage/parser_typescript.snap b/tasks/coverage/parser_typescript.snap index 05fe5853e..2d59f4cc3 100644 --- a/tasks/coverage/parser_typescript.snap +++ b/tasks/coverage/parser_typescript.snap @@ -15803,28 +15803,23 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" 1 │ import * as f from "./first" assert { ╰──── - × Expected a semicolon or an implicit semicolon after a statement, but found none + × Expected `{` but found `EOF` ╭─[conformance/importAttributes/importAttributes4.ts:1:1] 1 │ import * as f from "./first" with - · ─ ╰──── - help: Try insert a semicolon here - × Expected a semicolon or an implicit semicolon after a statement, but found none + × Expected `}` but found `EOF` ╭─[conformance/importAttributes/importAttributes5.ts:1:1] 1 │ import * as f from "./first" with { - · ─ ╰──── - help: Try insert a semicolon here - × Expected a semicolon or an implicit semicolon after a statement, but found none + × Unexpected token ╭─[conformance/importAttributes/importAttributes6.ts:2:1] 2 │ // @filename: mod.mts 3 │ import * as thing1 from "./mod.mjs" with { field: 0 }; - · ─ + · ─ 4 │ import * as thing2 from "./mod.mjs" with { field: `a` }; ╰──── - help: Try insert a semicolon here × The keyword 'interface' is reserved ╭─[conformance/interfaces/interfaceDeclarations/asiPreventsParsingAsInterface05.ts:2:1] diff --git a/tasks/coverage/src/test262.rs b/tasks/coverage/src/test262.rs index 6a9568afa..aa4bae452 100644 --- a/tasks/coverage/src/test262.rs +++ b/tasks/coverage/src/test262.rs @@ -86,6 +86,8 @@ impl Suite for Test262Suite { fn skip_test_path(&self, path: &Path) -> bool { let path = path.to_string_lossy(); + // ignore markdown files + path.ends_with(".md") || // ignore fixtures path.contains("_FIXTURE") || // ignore regexp as we don't have a regexp parser for now @@ -159,8 +161,6 @@ impl Case for Test262Case { // Regex parser is required. See https://github.com/oxc-project/oxc/issues/385#issuecomment-1755566240 "regexp-v-flag", "regexp-unicode-property-escapes", - // Stage 3 `https://github.com/tc39/proposal-json-modules` - "json-modules", // Stage 3 `https://github.com/tc39/proposal-decorators` "decorators", ] diff --git a/tasks/coverage/test262 b/tasks/coverage/test262 index c4642dd71..2060494f2 160000 --- a/tasks/coverage/test262 +++ b/tasks/coverage/test262 @@ -1 +1 @@ -Subproject commit c4642dd714175b5d27939c920abc6059c9fddb06 +Subproject commit 2060494f280ba89d71a0f51d4ff171bafe476a05