diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index 339f8948e..f53b4c56f 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -861,6 +861,10 @@ pub enum TSModuleDeclarationName<'a> { } impl<'a> TSModuleDeclarationName<'a> { + pub fn is_string_literal(&self) -> bool { + matches!(self, Self::StringLiteral(_)) + } + pub fn name(&self) -> &Atom<'a> { match self { Self::Identifier(ident) => &ident.name, diff --git a/crates/oxc_transformer/src/typescript/diagnostics.rs b/crates/oxc_transformer/src/typescript/diagnostics.rs index 9989d051e..381bf515f 100644 --- a/crates/oxc_transformer/src/typescript/diagnostics.rs +++ b/crates/oxc_transformer/src/typescript/diagnostics.rs @@ -10,3 +10,8 @@ pub fn export_assignment_unsupported(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warning("`export = ;` is only supported when compiling modules to CommonJS.\nPlease consider using `export default ;`, or add @babel/plugin-transform-modules-commonjs to your Babel config.") .with_labels([span0.into()]) } + +pub fn ambient_module_nested(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warning("Ambient modules cannot be nested in other modules or namespaces.") + .with_label(span0) +} diff --git a/crates/oxc_transformer/src/typescript/namespace.rs b/crates/oxc_transformer/src/typescript/namespace.rs index 7cddfae5e..14cc23eb7 100644 --- a/crates/oxc_transformer/src/typescript/namespace.rs +++ b/crates/oxc_transformer/src/typescript/namespace.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashSet; -use super::TypeScript; +use super::{diagnostics::ambient_module_nested, TypeScript}; use oxc_allocator::{Box, Vec}; use oxc_ast::{ast::*, syntax_directed_operations::BoundNames}; @@ -121,6 +121,7 @@ impl<'a> TypeScript<'a> { parent_export: Option>, ) -> Option> { let mut names: FxHashSet> = FxHashSet::default(); + let real_name = decl.id.name(); let name = self.ctx.ast.new_atom(&format!("_{}", real_name.clone())); // path.scope.generateUid(realName.name); @@ -155,6 +156,10 @@ impl<'a> TypeScript<'a> { for stmt in namespace_top_level { match stmt { Statement::TSModuleDeclaration(decl) => { + if decl.id.is_string_literal() { + self.ctx.error(ambient_module_nested(decl.span)); + } + let module_name = decl.id.name().clone(); if let Some(transformed) = self.handle_nested(decl.unbox(), None) { is_empty = false; @@ -218,6 +223,10 @@ impl<'a> TypeScript<'a> { new_stmts.extend(stmts); } Declaration::TSModuleDeclaration(module_decl) => { + if module_decl.id.is_string_literal() { + self.ctx.error(ambient_module_nested(module_decl.span)); + } + let module_name = module_decl.id.name().clone(); if let Some(transformed) = self.handle_nested( module_decl.unbox(), diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 1d368d2a8..1b2f6cb72 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 304/362 +Passed: 306/362 # All Passed: * babel-preset-react @@ -24,7 +24,7 @@ Passed: 304/362 * opts/optimizeConstEnums/input.ts * opts/rewriteImportExtensions/input.ts -# babel-plugin-transform-typescript (119/156) +# babel-plugin-transform-typescript (121/156) * class/accessor-allowDeclareFields-false/input.ts * class/accessor-allowDeclareFields-true/input.ts * enum/mix-references/input.ts @@ -34,8 +34,6 @@ Passed: 304/362 * exports/export-type-star-from/input.ts * imports/enum-value/input.ts * imports/type-only-export-specifier-2/input.ts -* namespace/ambient-module-nested/input.ts -* namespace/ambient-module-nested-exported/input.ts * namespace/canonical/input.ts * namespace/contentious-names/input.ts * namespace/empty-removed/input.ts