diff --git a/.github/actions/clone-submodules/action.yml b/.github/actions/clone-submodules/action.yml index 2b0cc1f37e292..1526a2953f912 100644 --- a/.github/actions/clone-submodules/action.yml +++ b/.github/actions/clone-submodules/action.yml @@ -38,4 +38,4 @@ runs: show-progress: false repository: oxc-project/acorn-test262 path: tasks/coverage/acorn-test262 - ref: 4e88e3a36b29e72a4d9b7b77b9bf721915a609aa # Latest main at 7/5/25 + ref: 2096e67d35f9f9988b186b6a126e489e89ec730b # Latest main at 19/5/25 diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 04e0d60dd9afb..fc475dc1828a0 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -2396,13 +2396,13 @@ pub struct ImportExpression<'a> { #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree)] +#[estree(field_order(span, specifiers, source, with_clause, phase, import_kind))] pub struct ImportDeclaration<'a> { pub span: Span, /// `None` for `import 'foo'`, `Some([])` for `import {} from 'foo'` #[estree(via = ImportDeclarationSpecifiers)] pub specifiers: Option>>, pub source: StringLiteral<'a>, - #[estree(skip)] pub phase: Option, /// Some(vec![]) for empty assertion #[estree(rename = "attributes", via = ImportDeclarationWithClause)] diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index c2a8b82062fa3..5b8fbcda9fdf6 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -1669,6 +1669,7 @@ impl ESTree for ImportDeclaration<'_> { "attributes", &crate::serialize::js::ImportDeclarationWithClause(self), ); + state.serialize_field("phase", &self.phase); state.serialize_ts_field("importKind", &self.import_kind); state.end(); } diff --git a/justfile b/justfile index ef9956d88fbfb..4302f66ab7ae5 100755 --- a/justfile +++ b/justfile @@ -40,7 +40,7 @@ submodules: just clone-submodule tasks/coverage/babel https://github.com/babel/babel.git 578ac4df1c8a05f01350553950dbfbbeaac013c2 just clone-submodule tasks/coverage/typescript https://github.com/microsoft/TypeScript.git 15392346d05045742e653eab5c87538ff2a3c863 just clone-submodule tasks/prettier_conformance/prettier https://github.com/prettier/prettier.git 7584432401a47a26943dd7a9ca9a8e032ead7285 - just clone-submodule tasks/coverage/acorn-test262 https://github.com/oxc-project/acorn-test262 4e88e3a36b29e72a4d9b7b77b9bf721915a609aa + just clone-submodule tasks/coverage/acorn-test262 https://github.com/oxc-project/acorn-test262 2096e67d35f9f9988b186b6a126e489e89ec730b just update-transformer-fixtures # Install git pre-commit to format files diff --git a/napi/parser/README.md b/napi/parser/README.md index 61165dd2ff5a8..dd4f38e2436ed 100644 --- a/napi/parser/README.md +++ b/napi/parser/README.md @@ -18,6 +18,9 @@ If you need all ASTs in the same with-TS-properties format, use the `astType: 't The only differences between Oxc's AST and ESTree / TS-ESTree are: +- Support for Stage 3 ECMA features [`import defer`](https://github.com/tc39/proposal-defer-import-eval) + and [`import source`](https://github.com/tc39/proposal-source-phase-imports) on `ImportDeclaration` + (not supported on `ImportExpression` yet). - Addition of a non-standard `hashbang` field to `Program`. That aside, the AST should completely align with Acorn's ESTree AST or TS-ESLint's TS-ESTree. diff --git a/napi/parser/generated/deserialize/js.js b/napi/parser/generated/deserialize/js.js index a48232d9153cf..b7f6a23a05b75 100644 --- a/napi/parser/generated/deserialize/js.js +++ b/napi/parser/generated/deserialize/js.js @@ -924,6 +924,7 @@ function deserializeImportDeclaration(pos) { specifiers, source: deserializeStringLiteral(pos + 32), attributes: withClause === null ? [] : withClause.attributes, + phase: deserializeOptionImportPhase(pos + 88), }; } diff --git a/napi/parser/generated/deserialize/ts.js b/napi/parser/generated/deserialize/ts.js index d88d9ba54cdce..366479b98bc3c 100644 --- a/napi/parser/generated/deserialize/ts.js +++ b/napi/parser/generated/deserialize/ts.js @@ -1071,6 +1071,7 @@ function deserializeImportDeclaration(pos) { specifiers, source: deserializeStringLiteral(pos + 32), attributes: withClause === null ? [] : withClause.attributes, + phase: deserializeOptionImportPhase(pos + 88), importKind: deserializeImportOrExportKind(pos + 89), }; } diff --git a/napi/parser/test/parse-raw.test.ts b/napi/parser/test/parse-raw.test.ts index 08fbf9ccc336c..30285d4c20c99 100644 --- a/napi/parser/test/parse-raw.test.ts +++ b/napi/parser/test/parse-raw.test.ts @@ -125,6 +125,9 @@ describe('JSX', () => { // Test raw transfer output matches standard (via JSON) output for edge cases not covered by Test262 describe('edge cases', () => { it.each([ + // ECMA stage 3 + 'import defer * as ns from "x";', + 'import source src from "x";', // `StringLiteral`s containing lone surrogates and/or lossy replacement characters ';"\\uD800\\uDBFF";', ';"�\\u{FFFD}";', diff --git a/napi/parser/test/parse.test.ts b/napi/parser/test/parse.test.ts index 8c714b79e7b2b..ef73f21279662 100644 --- a/napi/parser/test/parse.test.ts +++ b/napi/parser/test/parse.test.ts @@ -346,6 +346,118 @@ describe('parse', () => { }); }); + describe('`ImportDeclaration`', () => { + describe('import defer', () => { + it('ESTree', () => { + const ret = parseSync('test.js', 'import defer * as ns from "x";'); + expect(ret.errors.length).toBe(0); + expect(ret.program.body.length).toBe(1); + expect(ret.program.body[0]).toEqual({ + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportNamespaceSpecifier', + start: 13, + end: 20, + local: { type: 'Identifier', start: 18, end: 20, name: 'ns' }, + }, + ], + source: { type: 'Literal', start: 26, end: 29, value: 'x', raw: '"x"' }, + attributes: [], + phase: 'defer', + }); + }); + + it('TS-ESTree', () => { + const ret = parseSync('test.ts', 'import defer * as ns from "x";'); + expect(ret.errors.length).toBe(0); + expect(ret.program.body.length).toBe(1); + expect(ret.program.body[0]).toEqual({ + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportNamespaceSpecifier', + start: 13, + end: 20, + local: { + type: 'Identifier', + start: 18, + end: 20, + decorators: [], + name: 'ns', + optional: false, + typeAnnotation: null, + }, + }, + ], + source: { type: 'Literal', start: 26, end: 29, value: 'x', raw: '"x"' }, + attributes: [], + phase: 'defer', + importKind: 'value', + }); + }); + }); + + describe('import source', () => { + it('ESTree', () => { + const ret = parseSync('test.js', 'import source src from "x";'); + expect(ret.errors.length).toBe(0); + expect(ret.program.body.length).toBe(1); + expect(ret.program.body[0]).toEqual({ + type: 'ImportDeclaration', + start: 0, + end: 27, + specifiers: [ + { + type: 'ImportDefaultSpecifier', + start: 14, + end: 17, + local: { type: 'Identifier', start: 14, end: 17, name: 'src' }, + }, + ], + source: { type: 'Literal', start: 23, end: 26, value: 'x', raw: '"x"' }, + attributes: [], + phase: 'source', + }); + }); + + it('TS-ESTree', () => { + const ret = parseSync('test.ts', 'import source src from "x";'); + expect(ret.errors.length).toBe(0); + expect(ret.program.body.length).toBe(1); + expect(ret.program.body[0]).toEqual({ + type: 'ImportDeclaration', + start: 0, + end: 27, + specifiers: [ + { + type: 'ImportDefaultSpecifier', + start: 14, + end: 17, + local: { + type: 'Identifier', + start: 14, + end: 17, + decorators: [], + name: 'src', + optional: false, + typeAnnotation: null, + }, + }, + ], + source: { type: 'Literal', start: 23, end: 26, value: 'x', raw: '"x"' }, + attributes: [], + phase: 'source', + importKind: 'value', + }); + }); + }); + }); + it('lossy replacement character', () => { const ret = parseSync('test.js', '`�\\u{FFFD}${x}�\\u{FFFD}`;'); expect(ret.errors.length).toBe(0); diff --git a/npm/oxc-types/types.d.ts b/npm/oxc-types/types.d.ts index 5fcdd2578fbcd..1e52c1936274b 100644 --- a/npm/oxc-types/types.d.ts +++ b/npm/oxc-types/types.d.ts @@ -749,6 +749,7 @@ export interface ImportDeclaration extends Span { specifiers: Array; source: StringLiteral; attributes: Array; + phase: ImportPhase | null; importKind?: ImportOrExportKind; } diff --git a/tasks/coverage/snapshots/estree_acorn_jsx.snap b/tasks/coverage/snapshots/estree_acorn_jsx.snap index cfaab6cad4441..835f6c0c68d64 100644 --- a/tasks/coverage/snapshots/estree_acorn_jsx.snap +++ b/tasks/coverage/snapshots/estree_acorn_jsx.snap @@ -1,4 +1,4 @@ -commit: 4e88e3a3 +commit: 2096e67d estree_acorn_jsx Summary: AST Parsed : 39/39 (100.00%)