From 4a62703d883c4b4c97bde83b23453d37bdea4095 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Sat, 21 Sep 2024 16:39:58 +0000 Subject: [PATCH] feat(isolated-declarations): handle `export` in the `namespace` correctly (#5950) Previous I didn't follow the behavior of `TypeScript` to handle `export` in `namespace` as I thought no one used this --- crates/oxc_ast/src/ast/ts.rs | 11 +++++- crates/oxc_isolated_declarations/src/lib.rs | 25 ++++++++++--- .../oxc_isolated_declarations/src/module.rs | 22 ++++++++++++ crates/oxc_isolated_declarations/src/scope.rs | 2 +- .../module-declaration-with-export.ts | 36 +++++++++++++++++++ .../tests/snapshots/expando-function.snap | 6 ++-- .../module-declaration-with-export.snap | 34 ++++++++++++++++++ tasks/coverage/snapshots/transpile.snap | 5 ++- 8 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 crates/oxc_isolated_declarations/tests/fixtures/module-declaration-with-export.ts create mode 100644 crates/oxc_isolated_declarations/tests/snapshots/module-declaration-with-export.snap diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index 5ccb8f55f9c8c..4824915304df5 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -1073,13 +1073,22 @@ pub enum TSModuleDeclarationBody<'a> { TSModuleBlock(Box<'a, TSModuleBlock<'a>>) = 1, } -impl TSModuleDeclarationBody<'_> { +impl<'a> TSModuleDeclarationBody<'a> { pub fn is_empty(&self) -> bool { match self { TSModuleDeclarationBody::TSModuleDeclaration(declaration) => declaration.body.is_none(), TSModuleDeclarationBody::TSModuleBlock(block) => block.body.len() == 0, } } + + pub fn as_module_block_mut(&mut self) -> Option<&mut TSModuleBlock<'a>> { + match self { + TSModuleDeclarationBody::TSModuleBlock(block) => Some(block.as_mut()), + TSModuleDeclarationBody::TSModuleDeclaration(decl) => { + decl.body.as_mut().and_then(|body| body.as_module_block_mut()) + } + } + } } // See serializer in serialize.rs diff --git a/crates/oxc_isolated_declarations/src/lib.rs b/crates/oxc_isolated_declarations/src/lib.rs index fd9ca24b210e9..9c80f7b562b8f 100644 --- a/crates/oxc_isolated_declarations/src/lib.rs +++ b/crates/oxc_isolated_declarations/src/lib.rs @@ -204,16 +204,27 @@ impl<'a> IsolatedDeclarations<'a> { for mut stmt in filtered_stmts { match stmt { match_declaration!(Statement) => { - if let Declaration::TSModuleDeclaration(decl) = stmt.to_declaration() { + if let Statement::TSModuleDeclaration(ref mut decl) = stmt { if self.has_internal_annotation(decl.span) { continue; } - // declare global { ... } or declare module "foo" { ... } + // `declare global { ... }` or `declare module "foo" { ... }` // We need to emit it anyway - if decl.kind.is_global() || decl.id.is_string_literal() { + let is_global = decl.kind.is_global(); + if is_global || decl.id.is_string_literal() { + transformed_spans.insert(decl.span); + + // Remove export keyword from all statements in `declare module "xxx" { ... }` + if !is_global { + if let Some(body) = + decl.body.as_mut().and_then(|body| body.as_module_block_mut()) + { + self.strip_export_keyword(&mut body.body); + } + } + // We need to visit the module declaration to collect all references self.scope.visit_ts_module_declaration(decl); - transformed_spans.insert(decl.span); } } if !self.has_internal_annotation(stmt.span()) { @@ -367,6 +378,10 @@ impl<'a> IsolatedDeclarations<'a> { self.ast.alloc_export_named_declaration(SPAN, None, specifiers, None, kind, NONE); new_stmts .push(Statement::from(ModuleDeclaration::ExportNamedDeclaration(empty_export))); + } else if self.scope.is_ts_module_block() { + // If we are in a module block and we don't need to add `export {}`, in that case we need to remove `export` keyword from all ExportNamedDeclaration + // + self.strip_export_keyword(&mut new_stmts); } new_stmts @@ -600,6 +615,6 @@ impl<'a> IsolatedDeclarations<'a> { pub fn is_declare(&self) -> bool { // If we are in a module block, we don't need to add declare - !self.scope.is_ts_module_block_flag() + !self.scope.is_ts_module_block() } } diff --git a/crates/oxc_isolated_declarations/src/module.rs b/crates/oxc_isolated_declarations/src/module.rs index 03d0c04757c55..4448f430b6e60 100644 --- a/crates/oxc_isolated_declarations/src/module.rs +++ b/crates/oxc_isolated_declarations/src/module.rs @@ -1,4 +1,5 @@ use oxc_allocator::Box; +use oxc_allocator::Vec; #[allow(clippy::wildcard_imports)] use oxc_ast::ast::*; use oxc_span::{Atom, GetSpan, SPAN}; @@ -124,4 +125,25 @@ impl<'a> IsolatedDeclarations<'a> { )) } } + + /// Strip export keyword from ExportNamedDeclaration + /// + /// ```ts + /// export const a = 1; + /// export function b() {} + /// ``` + /// to + /// ```ts + /// const a = 1; + /// function b() {} + /// ``` + pub fn strip_export_keyword(&self, stmts: &mut Vec<'a, Statement<'a>>) { + stmts.iter_mut().for_each(|stmt| { + if let Statement::ExportNamedDeclaration(decl) = stmt { + if let Some(declaration) = &mut decl.declaration { + *stmt = Statement::from(self.ast.move_declaration(declaration)); + } + } + }); + } } diff --git a/crates/oxc_isolated_declarations/src/scope.rs b/crates/oxc_isolated_declarations/src/scope.rs index 69d0f1e96cb3b..29ad2c3ffb436 100644 --- a/crates/oxc_isolated_declarations/src/scope.rs +++ b/crates/oxc_isolated_declarations/src/scope.rs @@ -45,7 +45,7 @@ impl<'a> ScopeTree<'a> { Self { levels } } - pub fn is_ts_module_block_flag(&self) -> bool { + pub fn is_ts_module_block(&self) -> bool { let scope = self.levels.last().unwrap(); scope.flags.contains(ScopeFlags::TsModuleBlock) } diff --git a/crates/oxc_isolated_declarations/tests/fixtures/module-declaration-with-export.ts b/crates/oxc_isolated_declarations/tests/fixtures/module-declaration-with-export.ts new file mode 100644 index 0000000000000..245a67b251806 --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/fixtures/module-declaration-with-export.ts @@ -0,0 +1,36 @@ +export namespace OnlyOneExport { + export const a = 0; +} + + +export namespace TwoExports { + export const c = 0; + export const a: typeof c = 0; +} + + +export namespace OneExportReferencedANonExported { + const c = 0; + export const a: typeof c = c; +} + +declare module "OnlyOneExport" { + export const a = 0; +} + + +declare module "TwoExports" { + export const c = 0; + export const a: typeof c; +} + + +declare module "OneExportReferencedANonExported" { + const c = 0; + export const a: typeof c; +} + +declare global { + const c = 0; + export const a: typeof c; +} \ No newline at end of file diff --git a/crates/oxc_isolated_declarations/tests/snapshots/expando-function.snap b/crates/oxc_isolated_declarations/tests/snapshots/expando-function.snap index 8274cf2f951e9..c9cac1d593338 100644 --- a/crates/oxc_isolated_declarations/tests/snapshots/expando-function.snap +++ b/crates/oxc_isolated_declarations/tests/snapshots/expando-function.snap @@ -8,14 +8,14 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/expando-function.ts export declare function foo(): void; export declare const bar: () => void; export declare namespace NS { - export const goo: () => void; + const goo: () => void; } export declare namespace foo { - export let baz: number; + let baz: number; } declare function qux(): void; declare namespace qux { - export let woo: number; + let woo: number; } export default qux; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/module-declaration-with-export.snap b/crates/oxc_isolated_declarations/tests/snapshots/module-declaration-with-export.snap new file mode 100644 index 0000000000000..ef686b45bdd61 --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/snapshots/module-declaration-with-export.snap @@ -0,0 +1,34 @@ +--- +source: crates/oxc_isolated_declarations/tests/mod.rs +input_file: crates/oxc_isolated_declarations/tests/fixtures/module-declaration-with-export.ts +--- +``` +==================== .D.TS ==================== + +export declare namespace OnlyOneExport { + const a = 0; +} +export declare namespace TwoExports { + const c = 0; + const a: typeof c; +} +export declare namespace OneExportReferencedANonExported { + const c = 0; + export const a: typeof c; + export {}; +} +declare module "OnlyOneExport" { + const a = 0; +} +declare module "TwoExports" { + const c = 0; + const a: typeof c; +} +declare module "OneExportReferencedANonExported" { + const c = 0; + const a: typeof c; +} +declare global { + const c = 0; + export const a: typeof c; +} diff --git a/tasks/coverage/snapshots/transpile.snap b/tasks/coverage/snapshots/transpile.snap index cc98cde0d3054..42ca502a4e449 100644 --- a/tasks/coverage/snapshots/transpile.snap +++ b/tasks/coverage/snapshots/transpile.snap @@ -2,9 +2,8 @@ commit: a709f989 transpile Summary: AST Parsed : 20/20 (100.00%) -Positive Passed: 17/20 (85.00%) +Positive Passed: 18/20 (90.00%) Mismatch: tasks/coverage/typescript/tests/cases/transpile/declarationBasicSyntax.ts -Mismatch: tasks/coverage/typescript/tests/cases/transpile/declarationEmitPartialNodeReuse.ts Mismatch: tasks/coverage/typescript/tests/cases/transpile/declarationRestParameters.ts #### "typescript/tests/cases/transpile/declarationComputedPropertyNames.ts" #### @@ -68,7 +67,7 @@ export const D = { //// [declarationComputedPropertyNames.d.ts] //// export declare namespace presentNs { - export const a: unknown; + const a: unknown; } declare const aliasing: unknown; export type A = {