Skip to content

Commit

Permalink
feat(isolated-declarations): handle export in the namespace corre…
Browse files Browse the repository at this point in the history
…ctly (#5950)

Previous I didn't follow the behavior of `TypeScript` to handle `export` in `namespace` as I thought no one used this
  • Loading branch information
Dunqing committed Sep 21, 2024
1 parent 463c24e commit 4a62703
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 13 deletions.
11 changes: 10 additions & 1 deletion crates/oxc_ast/src/ast/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 20 additions & 5 deletions crates/oxc_isolated_declarations/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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
// <https://github.com/microsoft/TypeScript/blob/a709f9899c2a544b6de65a0f2623ecbbe1394eab/src/compiler/transformers/declarations.ts#L1556-L1563>
self.strip_export_keyword(&mut new_stmts);
}

new_stmts
Expand Down Expand Up @@ -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()
}
}
22 changes: 22 additions & 0 deletions crates/oxc_isolated_declarations/src/module.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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));
}
}
});
}
}
2 changes: 1 addition & 1 deletion crates/oxc_isolated_declarations/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 2 additions & 3 deletions tasks/coverage/snapshots/transpile.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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" ####
Expand Down Expand Up @@ -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 = {
Expand Down

0 comments on commit 4a62703

Please sign in to comment.