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
This commit is contained in:
Dunqing 2024-09-21 16:39:58 +00:00
parent 463c24e5ec
commit 4a62703d88
8 changed files with 128 additions and 13 deletions

View file

@ -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

View file

@ -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
// <https://github.com/microsoft/TypeScript/blob/a709f9899c2a544b6de65a0f2623ecbbe1394eab/src/compiler/transformers/declarations.ts#L1556-L1563>
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()
}
}

View file

@ -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));
}
}
});
}
}

View file

@ -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)
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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 = {