diff --git a/crates/oxc_parser/src/module_record.rs b/crates/oxc_parser/src/module_record.rs index 5a7ffe157ef2c..746cea8d824a8 100644 --- a/crates/oxc_parser/src/module_record.rs +++ b/crates/oxc_parser/src/module_record.rs @@ -317,11 +317,8 @@ impl<'a> ModuleRecordBuilder<'a> { } fn visit_export_named_declaration(&mut self, decl: &ExportNamedDeclaration<'a>) { - if decl.export_kind.is_type() { - return; - } // ignore all TypeScript syntax as they overload - if decl.is_typescript_syntax() { + if decl.declaration.as_ref().is_some_and(Declaration::is_typescript_syntax) { return; } diff --git a/crates/oxc_syntax/src/generated/derive_estree.rs b/crates/oxc_syntax/src/generated/derive_estree.rs index 343e57278eb54..4f3514187b2a8 100644 --- a/crates/oxc_syntax/src/generated/derive_estree.rs +++ b/crates/oxc_syntax/src/generated/derive_estree.rs @@ -52,6 +52,7 @@ impl ESTree for ExportEntry<'_> { state.serialize_field("importName", &self.import_name); state.serialize_field("exportName", &self.export_name); state.serialize_field("localName", &self.local_name); + state.serialize_field("isType", &self.is_type); state.end(); } } diff --git a/crates/oxc_syntax/src/module_record.rs b/crates/oxc_syntax/src/module_record.rs index 1346e3124b850..d1ea3ca066e6e 100644 --- a/crates/oxc_syntax/src/module_record.rs +++ b/crates/oxc_syntax/src/module_record.rs @@ -270,7 +270,6 @@ pub struct ExportEntry<'a> { /// export { type foo } /// export type { foo } from 'mod' /// ``` - #[estree(skip)] pub is_type: bool, } diff --git a/napi/parser/deserialize-js.js b/napi/parser/deserialize-js.js index ba3ad123aa90e..745b9b3ffb550 100644 --- a/napi/parser/deserialize-js.js +++ b/napi/parser/deserialize-js.js @@ -2039,6 +2039,7 @@ function deserializeExportEntry(pos) { importName: deserializeExportImportName(pos + 40), exportName: deserializeExportExportName(pos + 72), localName: deserializeExportLocalName(pos + 104), + isType: deserializeBool(pos + 136), }; } diff --git a/napi/parser/deserialize-ts.js b/napi/parser/deserialize-ts.js index 040c26675a471..ff7aefabf3327 100644 --- a/napi/parser/deserialize-ts.js +++ b/napi/parser/deserialize-ts.js @@ -2166,6 +2166,7 @@ function deserializeExportEntry(pos) { importName: deserializeExportImportName(pos + 40), exportName: deserializeExportExportName(pos + 72), localName: deserializeExportLocalName(pos + 104), + isType: deserializeBool(pos + 136), }; } diff --git a/napi/parser/index.d.ts b/napi/parser/index.d.ts index d3902af761dd2..9b7aeb3f65e19 100644 --- a/napi/parser/index.d.ts +++ b/napi/parser/index.d.ts @@ -232,6 +232,20 @@ export interface StaticExportEntry { exportName: ExportExportName /** The name that is used to locally access the exported value from within the importing module. */ localName: ExportLocalName + /** + * Whether the export is a TypeScript `export type`. + * + * Examples: + * + * ```ts + * export type * from 'mod'; + * export type * as ns from 'mod'; + * export type { foo }; + * export { type foo }: + * export type { foo } from 'mod'; + * ``` + */ + isType: boolean } export interface StaticImport { diff --git a/napi/parser/src/convert.rs b/napi/parser/src/convert.rs index 6f37e85623e64..3838646b59ab0 100644 --- a/napi/parser/src/convert.rs +++ b/napi/parser/src/convert.rs @@ -92,6 +92,7 @@ impl From<&module_record::ExportEntry<'_>> for StaticExportEntry { import_name: ExportImportName::from(&e.import_name), export_name: ExportExportName::from(&e.export_name), local_name: ExportLocalName::from(&e.local_name), + is_type: e.is_type, } } } diff --git a/napi/parser/src/types.rs b/napi/parser/src/types.rs index 1392a9c51b3ae..3b3c1e5caa035 100644 --- a/napi/parser/src/types.rs +++ b/napi/parser/src/types.rs @@ -180,6 +180,18 @@ pub struct StaticExportEntry { pub export_name: ExportExportName, /// The name that is used to locally access the exported value from within the importing module. pub local_name: ExportLocalName, + /// Whether the export is a TypeScript `export type`. + /// + /// Examples: + /// + /// ```ts + /// export type * from 'mod'; + /// export type * as ns from 'mod'; + /// export type { foo }; + /// export { type foo }: + /// export type { foo } from 'mod'; + /// ``` + pub is_type: bool, } #[napi(object)] diff --git a/napi/parser/test/__snapshots__/esm.test.ts.snap b/napi/parser/test/__snapshots__/esm.test.ts.snap index ec1f3cc9c8b46..95390302af83a 100644 --- a/napi/parser/test/__snapshots__/esm.test.ts.snap +++ b/napi/parser/test/__snapshots__/esm.test.ts.snap @@ -28,7 +28,8 @@ exports[`esm > export * as name1 from "module-name"; 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -63,7 +64,8 @@ exports[`esm > export * from "module-name"; 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -104,7 +106,8 @@ exports[`esm > export { default as name1 } from "module-name"; 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -145,7 +148,8 @@ exports[`esm > export { default, /* …, */ } from "module-name"; 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -186,7 +190,8 @@ exports[`esm > export { import1 as name1, import2 as name2, /* …, */ nameN } f }, "localName": { "kind": "None" - } + }, + "isType": false }, { "start": 27, @@ -210,7 +215,8 @@ exports[`esm > export { import1 as name1, import2 as name2, /* …, */ nameN } f }, "localName": { "kind": "None" - } + }, + "isType": false }, { "start": 54, @@ -234,7 +240,8 @@ exports[`esm > export { import1 as name1, import2 as name2, /* …, */ nameN } f }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -270,7 +277,8 @@ exports[`esm > export { name1 as default /*, … */ }; 1`] = ` "name": "name1", "start": 9, "end": 14 - } + }, + "isType": false } ] } @@ -311,7 +319,8 @@ exports[`esm > export { name1, /* …, */ nameN } from "module-name"; 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false }, { "start": 25, @@ -335,7 +344,8 @@ exports[`esm > export { name1, /* …, */ nameN } from "module-name"; 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -371,7 +381,8 @@ exports[`esm > export { name1, /* …, */ nameN }; 1`] = ` "name": "name1", "start": 9, "end": 14 - } + }, + "isType": false }, { "start": 25, @@ -390,7 +401,8 @@ exports[`esm > export { name1, /* …, */ nameN }; 1`] = ` "name": "nameN", "start": 25, "end": 30 - } + }, + "isType": false } ] } @@ -426,7 +438,8 @@ exports[`esm > export { variable1 as "string name" }; 1`] = ` "name": "variable1", "start": 9, "end": 18 - } + }, + "isType": false } ] } @@ -462,7 +475,8 @@ exports[`esm > export { variable1 as name1, variable2 as name2, /* …, */ nameN "name": "variable1", "start": 9, "end": 18 - } + }, + "isType": false }, { "start": 29, @@ -481,7 +495,8 @@ exports[`esm > export { variable1 as name1, variable2 as name2, /* …, */ nameN "name": "variable2", "start": 29, "end": 38 - } + }, + "isType": false }, { "start": 58, @@ -500,7 +515,8 @@ exports[`esm > export { variable1 as name1, variable2 as name2, /* …, */ nameN "name": "nameN", "start": 58, "end": 63 - } + }, + "isType": false } ] } @@ -536,7 +552,8 @@ exports[`esm > export class ClassName { /* … */ } 1`] = ` "name": "ClassName", "start": 13, "end": 22 - } + }, + "isType": false } ] } @@ -572,7 +589,8 @@ exports[`esm > export const [ name1, name2 ] = array; 1`] = ` "name": "name1", "start": 15, "end": 20 - } + }, + "isType": false }, { "start": 7, @@ -591,7 +609,8 @@ exports[`esm > export const [ name1, name2 ] = array; 1`] = ` "name": "name2", "start": 22, "end": 27 - } + }, + "isType": false } ] } @@ -627,7 +646,8 @@ exports[`esm > export const { name1, name2: bar } = o; 1`] = ` "name": "name1", "start": 15, "end": 20 - } + }, + "isType": false }, { "start": 7, @@ -646,7 +666,8 @@ exports[`esm > export const { name1, name2: bar } = o; 1`] = ` "name": "bar", "start": 29, "end": 32 - } + }, + "isType": false } ] } @@ -682,7 +703,8 @@ exports[`esm > export const name1 = 1, name2 = 2/*, … */; // also var, let 1`] "name": "name1", "start": 13, "end": 18 - } + }, + "isType": false }, { "start": 7, @@ -701,7 +723,8 @@ exports[`esm > export const name1 = 1, name2 = 2/*, … */; // also var, let 1`] "name": "name2", "start": 24, "end": 29 - } + }, + "isType": false } ] } @@ -733,7 +756,8 @@ exports[`esm > export default class { /* … */ } 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -768,7 +792,8 @@ exports[`esm > export default class ClassName { /* … */ } 1`] = ` "name": "ClassName", "start": 21, "end": 30 - } + }, + "isType": false } ] } @@ -803,7 +828,8 @@ exports[`esm > export default expression; 1`] = ` "name": "expression", "start": 15, "end": 25 - } + }, + "isType": false } ] } @@ -835,7 +861,8 @@ exports[`esm > export default function () { /* … */ } 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -870,7 +897,8 @@ exports[`esm > export default function functionName() { /* … */ } 1`] = ` "name": "functionName", "start": 24, "end": 36 - } + }, + "isType": false } ] } @@ -902,7 +930,8 @@ exports[`esm > export default function* () { /* … */ } 1`] = ` }, "localName": { "kind": "None" - } + }, + "isType": false } ] } @@ -937,7 +966,8 @@ exports[`esm > export default function* generatorFunctionName() { /* … */ } 1` "name": "generatorFunctionName", "start": 25, "end": 46 - } + }, + "isType": false } ] } @@ -973,7 +1003,8 @@ exports[`esm > export function functionName() { /* … */ } 1`] = ` "name": "functionName", "start": 16, "end": 28 - } + }, + "isType": false } ] } @@ -1009,7 +1040,8 @@ exports[`esm > export function* generatorFunctionName() { /* … */ } 1`] = ` "name": "generatorFunctionName", "start": 17, "end": 38 - } + }, + "isType": false } ] } @@ -1045,7 +1077,8 @@ exports[`esm > export let name1, name2/*, … */; // also var 1`] = ` "name": "name1", "start": 11, "end": 16 - } + }, + "isType": false }, { "start": 7, @@ -1064,7 +1097,8 @@ exports[`esm > export let name1, name2/*, … */; // also var 1`] = ` "name": "name2", "start": 18, "end": 23 - } + }, + "isType": false } ] } diff --git a/napi/parser/test/esm.test.ts b/napi/parser/test/esm.test.ts index c1a5794ab9a2b..4b8c50656b51c 100644 --- a/napi/parser/test/esm.test.ts +++ b/napi/parser/test/esm.test.ts @@ -82,3 +82,19 @@ describe('hasModuleSyntax', () => { expect(ret.module.hasModuleSyntax).toBe(false); }); }); + +describe('export type', () => { + const code = [ + "export type * from 'mod'", + "export type * as ns from 'mod'", + 'export type { foo }', + 'export { type foo }', + "export type { foo } from 'mod'", + ]; + test.each(code)('%s', (s) => { + const ret = parseSync('test.ts', s); + expect(ret.module.staticExports.length).toBe(1); + expect(ret.module.staticExports[0].entries.length).toBe(1); + expect(ret.module.staticExports[0].entries[0].isType).toBe(true); + }); +}); diff --git a/napi/parser/test/parse.test.ts b/napi/parser/test/parse.test.ts index e4c6cd8767500..c2e5cd81bb351 100644 --- a/napi/parser/test/parse.test.ts +++ b/napi/parser/test/parse.test.ts @@ -523,6 +523,7 @@ describe('UTF-16 span', () => { "name": "x", "start": 12, }, + "isType": false, "localName": { "kind": "None", },