diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 4c9b80da2..2abf85797 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1318,6 +1318,18 @@ impl<'a> Declaration<'a> { } } + pub fn id(&self) -> Option<&BindingIdentifier<'a>> { + match self { + Declaration::FunctionDeclaration(decl) => decl.id.as_ref(), + Declaration::ClassDeclaration(decl) => decl.id.as_ref(), + Declaration::TSTypeAliasDeclaration(decl) => Some(&decl.id), + Declaration::TSInterfaceDeclaration(decl) => Some(&decl.id), + Declaration::TSEnumDeclaration(decl) => Some(&decl.id), + Declaration::TSImportEqualsDeclaration(decl) => Some(&decl.id), + _ => None, + } + } + pub fn modifiers(&self) -> Option<&Modifiers<'a>> { match self { Declaration::VariableDeclaration(decl) => Some(&decl.modifiers), diff --git a/crates/oxc_ast/src/ast_builder.rs b/crates/oxc_ast/src/ast_builder.rs index 5bf5eb5d3..050867bd3 100644 --- a/crates/oxc_ast/src/ast_builder.rs +++ b/crates/oxc_ast/src/ast_builder.rs @@ -1216,6 +1216,21 @@ impl<'a> AstBuilder<'a> { }) } + pub fn plain_export_named_declaration_declaration( + &self, + span: Span, + declaration: Declaration<'a>, + ) -> Box<'a, ExportNamedDeclaration<'a>> { + self.export_named_declaration( + span, + Some(declaration), + self.new_vec(), + None, + ImportOrExportKind::Value, + None, + ) + } + pub fn plain_export_named_declaration( &self, span: Span, diff --git a/crates/oxc_ast/src/syntax_directed_operations/bound_names.rs b/crates/oxc_ast/src/syntax_directed_operations/bound_names.rs index 180a889f8..30b71941b 100644 --- a/crates/oxc_ast/src/syntax_directed_operations/bound_names.rs +++ b/crates/oxc_ast/src/syntax_directed_operations/bound_names.rs @@ -134,8 +134,10 @@ impl<'a> BoundNames<'a> for FormalParameter<'a> { impl<'a> BoundNames<'a> for ModuleDeclaration<'a> { fn bound_names)>(&self, f: &mut F) { - if let ModuleDeclaration::ImportDeclaration(decl) = &self { - decl.bound_names(f); + match self { + ModuleDeclaration::ImportDeclaration(decl) => decl.bound_names(f), + ModuleDeclaration::ExportNamedDeclaration(decl) => decl.bound_names(f), + _ => {} } } } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 28c450fdc..4db518171 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -80,7 +80,10 @@ impl<'a> Transformer<'a> { impl<'a> VisitMut<'a> for Transformer<'a> { fn visit_program(&mut self, program: &mut Program<'a>) { + self.x0_typescript.transform_program(program); + walk_mut::walk_program_mut(self, program); + self.x1_react.transform_program_on_exit(program); self.x0_typescript.transform_program_on_exit(program); } @@ -186,8 +189,6 @@ impl<'a> VisitMut<'a> for Transformer<'a> { } fn visit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { - self.x0_typescript.transform_statements(stmts); - walk_mut::walk_statements_mut(self, stmts); self.x0_typescript.transform_statements_on_exit(stmts); diff --git a/crates/oxc_transformer/src/typescript/annotations.rs b/crates/oxc_transformer/src/typescript/annotations.rs index 705dabb59..595486796 100644 --- a/crates/oxc_transformer/src/typescript/annotations.rs +++ b/crates/oxc_transformer/src/typescript/annotations.rs @@ -58,11 +58,18 @@ impl<'a> TypeScriptAnnotations<'a> { program: &mut Program<'a>, references: &TypeScriptReferenceCollector, ) { - let mut import_type_names = FxHashSet::default(); + let mut type_names = FxHashSet::default(); let mut module_count = 0; let mut removed_count = 0; program.body.retain_mut(|stmt| { + // fix namespace/export-type-only/input.ts + // The namespace is type only. So if its name appear in the ExportNamedDeclaration, we should remove it. + if let Statement::Declaration(Declaration::TSModuleDeclaration(decl)) = stmt { + type_names.insert(decl.id.name().clone()); + return false; + } + let Statement::ModuleDeclaration(module_decl) = stmt else { return true; }; @@ -71,7 +78,7 @@ impl<'a> TypeScriptAnnotations<'a> { ModuleDeclaration::ExportNamedDeclaration(decl) => { decl.specifiers.retain(|specifier| { !(specifier.export_kind.is_type() - || import_type_names.contains(specifier.exported.name())) + || type_names.contains(specifier.exported.name())) }); decl.export_kind.is_type() @@ -92,7 +99,7 @@ impl<'a> TypeScriptAnnotations<'a> { specifiers.retain(|specifier| match specifier { ImportDeclarationSpecifier::ImportSpecifier(s) => { if is_type || s.import_kind.is_type() { - import_type_names.insert(s.local.name.clone()); + type_names.insert(s.local.name.clone()); return false; } @@ -104,7 +111,7 @@ impl<'a> TypeScriptAnnotations<'a> { } ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => { if is_type { - import_type_names.insert(s.local.name.clone()); + type_names.insert(s.local.name.clone()); return false; } @@ -115,7 +122,7 @@ impl<'a> TypeScriptAnnotations<'a> { } ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => { if is_type { - import_type_names.insert(s.local.name.clone()); + type_names.insert(s.local.name.clone()); } if self.options.only_remove_type_imports { @@ -295,6 +302,14 @@ impl<'a> TypeScriptAnnotations<'a> { // Remove TS specific statements stmts.retain(|stmt| match stmt { Statement::ExpressionStatement(s) => !s.expression.is_typescript_syntax(), + Statement::Declaration(s) => { + // Removed in transform_program_on_exit + if matches!(s, Declaration::TSModuleDeclaration(_)) { + true + } else { + !s.is_typescript_syntax() + } + } // Ignore ModuleDeclaration as it's handled in the program _ => true, }); diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index f2d33fcb0..8a6fb405f 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -69,6 +69,10 @@ impl<'a> TypeScript<'a> { // Transforms impl<'a> TypeScript<'a> { + pub fn transform_program(&self, program: &mut Program<'a>) { + self.transform_program_for_namespace(program); + } + pub fn transform_program_on_exit(&self, program: &mut Program<'a>) { self.annotations.transform_program_on_exit(program, &self.reference_collector); } @@ -133,10 +137,6 @@ impl<'a> TypeScript<'a> { self.annotations.transform_property_definition(def); } - pub fn transform_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { - self.transform_statements_for_namespace(stmts); - } - pub fn transform_statements_on_exit(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { self.annotations.transform_statements_on_exit(stmts); } diff --git a/crates/oxc_transformer/src/typescript/namespace.rs b/crates/oxc_transformer/src/typescript/namespace.rs index 7fab8e24e..4833e2a18 100644 --- a/crates/oxc_transformer/src/typescript/namespace.rs +++ b/crates/oxc_transformer/src/typescript/namespace.rs @@ -1,169 +1,308 @@ -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use super::TypeScript; use oxc_allocator::{Box, Vec}; -use oxc_ast::ast::*; +use oxc_ast::{ast::*, syntax_directed_operations::BoundNames}; use oxc_span::{Atom, SPAN}; use oxc_syntax::operator::{AssignmentOperator, LogicalOperator}; -#[derive(Default)] -struct State<'a> { - /// Deduplicate the `let` declarations` for namespace concatenation. - /// `namespace foo {}; namespace {}` creates a single `let foo;`. - names: FxHashSet>, - - /// Increment the argument name to avoid name clashes. - arg_names: FxHashMap, usize>, -} - -fn is_namespace(decl: &Declaration<'_>) -> bool { - matches!(decl, Declaration::TSModuleDeclaration(decl) if !decl.modifiers.is_contains_declare()) -} - // TODO: // 1. register scope for the newly created function: impl<'a> TypeScript<'a> { // `namespace Foo { }` -> `let Foo; (function (_Foo) { })(Foo || (Foo = {}));` - pub(super) fn transform_statements_for_namespace(&self, stmts: &mut Vec<'a, Statement<'a>>) { - // Only do the transform if a namespace declaration is found. - if !stmts.iter().any(|stmt| match stmt { - Statement::Declaration(decl) => is_namespace(decl), - Statement::ModuleDeclaration(decl) => match &**decl { - ModuleDeclaration::ExportNamedDeclaration(decl) => { - decl.declaration.as_ref().is_some_and(is_namespace) - } - _ => false, - }, - _ => false, - }) { + pub(super) fn transform_program_for_namespace(&self, program: &mut Program<'a>) { + // namespace declaration is only allowed at the top level + + if !has_namespace(program.body.as_slice()) { return; } + // Collect all binding names. Such as function name and class name. + let mut names: FxHashSet> = FxHashSet::default(); + // Recreate the statements vec for memory efficiency. // Inserting the `let` declaration multiple times will reallocate the whole statements vec // every time a namespace declaration is encountered. let mut new_stmts = self.ctx.ast.new_vec(); - let mut state = State::default(); + for stmt in self.ctx.ast.move_statement_vec(&mut program.body) { + match stmt { + Statement::Declaration(Declaration::TSModuleDeclaration(decl)) => { + if !decl.modifiers.is_contains_declare() { + if let Some(transformed_stmt) = + self.handle_nested(self.ctx.ast.copy(&decl).unbox(), None) + { + let name = decl.id.name(); + if names.insert(name.clone()) { + new_stmts.push(Statement::Declaration( + self.create_variable_declaration(name), + )); + } + new_stmts.push(transformed_stmt); + continue; + } + } + new_stmts.push(Statement::Declaration(Declaration::TSModuleDeclaration(decl))); + } + Statement::ModuleDeclaration(module_decl) => { + if let ModuleDeclaration::ExportNamedDeclaration(export_decl) = + self.ctx.ast.copy(&module_decl).unbox() + { + if let Some(Declaration::TSModuleDeclaration(decl)) = + &export_decl.declaration + { + if !decl.modifiers.is_contains_declare() { + if let Some(transformed_stmt) = + self.handle_nested(self.ctx.ast.copy(decl), None) + { + let name = decl.id.name(); + if names.insert(name.clone()) { + let declaration = self.create_variable_declaration(name); + let export_named_decl = self + .ctx + .ast + .plain_export_named_declaration_declaration( + SPAN, + declaration, + ); + let export_named_decl = + ModuleDeclaration::ExportNamedDeclaration( + export_named_decl, + ); + let stmt = + self.ctx.ast.module_declaration(export_named_decl); + new_stmts.push(stmt); + } + new_stmts.push(transformed_stmt); + continue; + } + } + } + } - for mut stmt in self.ctx.ast.move_statement_vec(stmts) { - if !self.transform_statement_for_namespace(&mut state, &mut new_stmts, &mut stmt) { - new_stmts.push(stmt); + module_decl.bound_names(&mut |id| { + names.insert(id.name.clone()); + }); + new_stmts.push(Statement::ModuleDeclaration(module_decl)); + } + // Collect bindings from class, function, variable and enum declarations + Statement::Declaration(Declaration::FunctionDeclaration(ref decl)) => { + if let Some(ident) = &decl.id { + names.insert(ident.name.clone()); + } + new_stmts.push(stmt); + } + Statement::Declaration(Declaration::ClassDeclaration(ref decl)) => { + if let Some(ident) = &decl.id { + names.insert(ident.name.clone()); + } + new_stmts.push(stmt); + } + Statement::Declaration(Declaration::TSEnumDeclaration(ref decl)) => { + names.insert(decl.id.name.clone()); + new_stmts.push(stmt); + } + Statement::Declaration(Declaration::VariableDeclaration(ref decl)) => { + decl.bound_names(&mut |id| { + names.insert(id.name.clone()); + }); + new_stmts.push(stmt); + } + _ => { + new_stmts.push(stmt); + } } } - *stmts = new_stmts; + program.body = new_stmts; } - fn transform_statement_for_namespace( + fn handle_nested( &self, - state: &mut State<'a>, - new_stmts: &mut Vec<'a, Statement<'a>>, - stmt: &mut Statement<'a>, - ) -> bool { - let mut is_export = false; - let ts_module_decl = match stmt { - Statement::Declaration(Declaration::TSModuleDeclaration(ts_module_decl)) => { - ts_module_decl - } - Statement::ModuleDeclaration(decl) => match &mut **decl { - ModuleDeclaration::ExportNamedDeclaration(decl) => { - if let Some(Declaration::TSModuleDeclaration(ts_module_decl)) = - decl.declaration.as_mut() - { - is_export = true; - ts_module_decl - } else { - return false; - } + decl: TSModuleDeclaration<'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); + + let namespace_top_level = if let Some(body) = decl.body { + match body { + TSModuleDeclarationBody::TSModuleBlock(mut block) => { + self.ctx.ast.move_statement_vec(&mut block.body) } - _ => return false, - }, - _ => return false, + // We handle `namespace X.Y {}` as if it was + // namespace X { + // export namespace Y {} + // } + TSModuleDeclarationBody::TSModuleDeclaration(declaration) => { + let declaration = + Declaration::TSModuleDeclaration(self.ctx.ast.copy(&declaration)); + let export_named_decl = + self.ctx.ast.plain_export_named_declaration_declaration(SPAN, declaration); + let stmt = self.ctx.ast.module_declaration( + ModuleDeclaration::ExportNamedDeclaration(export_named_decl), + ); + self.ctx.ast.new_vec_single(stmt) + } + } + } else { + self.ctx.ast.new_vec() }; - if ts_module_decl.modifiers.is_contains_declare() { - return false; + let mut is_empty = true; + let mut new_stmts = self.ctx.ast.new_vec(); + + for stmt in namespace_top_level { + match stmt { + Statement::Declaration(Declaration::TSModuleDeclaration(decl)) => { + let module_name = decl.id.name().clone(); + if let Some(transformed) = self.handle_nested(decl.unbox(), None) { + is_empty = false; + if names.insert(module_name.clone()) { + new_stmts.push(Statement::Declaration( + self.create_variable_declaration(&module_name), + )); + } + new_stmts.push(transformed); + } + } + Statement::Declaration(Declaration::ClassDeclaration(decl)) => { + is_empty = false; + decl.bound_names(&mut |id| { + names.insert(id.name.clone()); + }); + new_stmts.push(Statement::Declaration(Declaration::ClassDeclaration(decl))); + } + Statement::Declaration(Declaration::TSEnumDeclaration(enum_decl)) => { + is_empty = false; + names.insert(enum_decl.id.name.clone()); + new_stmts + .push(Statement::Declaration(Declaration::TSEnumDeclaration(enum_decl))); + } + Statement::ModuleDeclaration(decl) => { + if let ModuleDeclaration::ExportNamedDeclaration(export_decl) = decl.unbox() { + let export_decl = export_decl.unbox(); + if let Some(decl) = export_decl.declaration { + if decl.modifiers().is_some_and(Modifiers::is_contains_declare) { + continue; + } + match decl { + Declaration::TSEnumDeclaration(enum_decl) => { + is_empty = false; + self.add_declaration( + Declaration::TSEnumDeclaration(enum_decl), + &name, + &mut names, + &mut new_stmts, + ); + } + Declaration::FunctionDeclaration(func_decl) => { + is_empty = false; + self.add_declaration( + Declaration::FunctionDeclaration(func_decl), + &name, + &mut names, + &mut new_stmts, + ); + } + Declaration::ClassDeclaration(class_decl) => { + is_empty = false; + self.add_declaration( + Declaration::ClassDeclaration(class_decl), + &name, + &mut names, + &mut new_stmts, + ); + } + Declaration::VariableDeclaration(var_decl) => { + is_empty = false; + let stmts = self.handle_variable_declaration(var_decl, &name); + new_stmts.extend(stmts); + } + Declaration::TSModuleDeclaration(module_decl) => { + let module_name = module_decl.id.name().clone(); + if let Some(transformed) = self.handle_nested( + module_decl.unbox(), + Some(self.ctx.ast.identifier_reference_expression( + IdentifierReference::new(SPAN, name.clone()), + )), + ) { + is_empty = false; + if names.insert(module_name.clone()) { + new_stmts.push(Statement::Declaration( + self.create_variable_declaration(&module_name), + )); + } + new_stmts.push(transformed); + } + } + _ => {} + } + } else { + let stmt = self.ctx.ast.module_declaration( + ModuleDeclaration::ExportNamedDeclaration( + self.ctx.ast.alloc(export_decl), + ), + ); + new_stmts.push(stmt); + } + } + } + Statement::Declaration(decl) if decl.is_typescript_syntax() => continue, + stmt => { + is_empty = false; + new_stmts.push(stmt); + } + } } - let name = ts_module_decl.id.name().clone(); - - if state.names.insert(name.clone()) { - let stmt = self.create_variable_declaration_statement(&name, is_export); - new_stmts.push(stmt); + if is_empty { + return None; } - let namespace = self.transform_namespace(state, ts_module_decl); - new_stmts.push(namespace); - true + Some(self.transform_namespace(&name, real_name, new_stmts, parent_export)) } // `namespace Foo { }` -> `let Foo; (function (_Foo) { })(Foo || (Foo = {}));` // ^^^^^^^ - fn create_variable_declaration_statement( - &self, - name: &Atom<'a>, - is_export: bool, - ) -> Statement<'a> { + fn create_variable_declaration(&self, name: &Atom<'a>) -> Declaration<'a> { let kind = VariableDeclarationKind::Let; - let declarators = { + let declarator = { let ident = BindingIdentifier::new(SPAN, name.clone()); let pattern_kind = self.ctx.ast.binding_pattern_identifier(ident); let binding = self.ctx.ast.binding_pattern(pattern_kind, None, false); let decl = self.ctx.ast.variable_declarator(SPAN, kind, binding, None, false); self.ctx.ast.new_vec_single(decl) }; - let decl = Declaration::VariableDeclaration(self.ctx.ast.variable_declaration( + Declaration::VariableDeclaration(self.ctx.ast.variable_declaration( SPAN, kind, - declarators, + declarator, Modifiers::empty(), - )); - if is_export { - self.ctx.ast.module_declaration(ModuleDeclaration::ExportNamedDeclaration( - self.ctx.ast.export_named_declaration( - SPAN, - Some(decl), - self.ctx.ast.new_vec(), - None, - ImportOrExportKind::Value, - None, - ), - )) - } else { - Statement::Declaration(decl) - } + )) } // `namespace Foo { }` -> `let Foo; (function (_Foo) { })(Foo || (Foo = {}));` // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fn transform_namespace( &self, - state: &mut State<'a>, - block: &mut Box<'a, TSModuleDeclaration<'a>>, + arg_name: &Atom<'a>, + real_name: &Atom<'a>, + stmts: Vec<'a, Statement<'a>>, + parent_export: Option>, ) -> Statement<'a> { - let body_statements = match &mut block.body { - Some(TSModuleDeclarationBody::TSModuleDeclaration(decl)) => { - let transformed_module_block = self.transform_namespace(state, decl); - self.ctx.ast.new_vec_single(transformed_module_block) - } - Some(TSModuleDeclarationBody::TSModuleBlock(ts_module_block)) => { - self.ctx.ast.move_statement_vec(&mut ts_module_block.body) - } - None => self.ctx.ast.new_vec(), - }; - - let name = block.id.name(); - // `(function (_N) { var x; })(N || (N = {}))`; // ^^^^^^^^^^^^^^^^^^^^^^^^^^ let callee = { - let body = self.ctx.ast.function_body(SPAN, self.ctx.ast.new_vec(), body_statements); - let arg_name = self.get_namespace_arg_name(state, name); + let body = self.ctx.ast.function_body(SPAN, self.ctx.ast.new_vec(), stmts); let params = { - let ident = - self.ctx.ast.binding_pattern_identifier(BindingIdentifier::new(SPAN, arg_name)); + let ident = self.ctx.ast.binding_pattern_identifier(BindingIdentifier::new( + SPAN, + self.ctx.ast.new_atom(arg_name), + )); let pattern = self.ctx.ast.binding_pattern(ident, None, false); let items = self.ctx.ast.new_vec_single(self.ctx.ast.plain_formal_parameter(SPAN, pattern)); @@ -185,17 +324,36 @@ impl<'a> TypeScript<'a> { self.ctx.ast.parenthesized_expression(SPAN, function_expr) }; - // `(function (_N) { var x; })(N || (N = {}))`; - // ^^^^^^^^^^^^^ + // (function (_N) { var M; (function (_M) { var x; })(M || (M = _N.M || (_N.M = {})));})(N || (N = {})); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ + // Nested namespace arguments Normal namespace arguments let arguments = { + // M let logical_left = { - let ident = IdentifierReference::new(SPAN, name.clone()); + let ident = IdentifierReference::new(SPAN, real_name.clone()); self.ctx.ast.identifier_reference_expression(ident) }; - let logical_right = { - let assign_left = self.ctx.ast.simple_assignment_target_identifier( - IdentifierReference::new(SPAN, name.clone()), - ); + + // (_N.M = {}) or (N = {}) + let mut logical_right = { + // _N.M + let assign_left = if let Some(parent_export) = self.ctx.ast.copy(&parent_export) { + self.ctx.ast.simple_assignment_target_member_expression( + self.ctx.ast.static_member( + SPAN, + parent_export, + self.ctx.ast.identifier_name(SPAN, real_name), + false, + ), + ) + } else { + // _N + self.ctx.ast.simple_assignment_target_identifier(IdentifierReference::new( + SPAN, + real_name.clone(), + )) + }; + let assign_right = self.ctx.ast.object_expression(SPAN, self.ctx.ast.new_vec(), None); let op = AssignmentOperator::Assign; @@ -203,21 +361,138 @@ impl<'a> TypeScript<'a> { self.ctx.ast.assignment_expression(SPAN, op, assign_left, assign_right); self.ctx.ast.parenthesized_expression(SPAN, assign_expr) }; - self.ctx.ast.new_vec_single(Argument::Expression(self.ctx.ast.logical_expression( - SPAN, - logical_left, - LogicalOperator::Or, - logical_right, - ))) + + // (M = _N.M || (_N.M = {})) + if let Some(parent_export) = parent_export { + let assign_left = self.ctx.ast.simple_assignment_target_identifier( + IdentifierReference::new(SPAN, real_name.clone()), + ); + let assign_right = { + let property = self.ctx.ast.identifier_name(SPAN, real_name); + let logical_left = + self.ctx.ast.static_member_expression(SPAN, parent_export, property, false); + let op = LogicalOperator::Or; + self.ctx.ast.logical_expression(SPAN, logical_left, op, logical_right) + }; + let op = AssignmentOperator::Assign; + logical_right = + self.ctx.ast.assignment_expression(SPAN, op, assign_left, assign_right); + logical_right = self.ctx.ast.parenthesized_expression(SPAN, logical_right); + } + + let op = LogicalOperator::Or; + let expr = self.ctx.ast.logical_expression(SPAN, logical_left, op, logical_right); + self.ctx.ast.new_vec_single(Argument::Expression(expr)) }; + let expr = self.ctx.ast.call_expression(SPAN, callee, arguments, false, None); self.ctx.ast.expression_statement(SPAN, expr) } - fn get_namespace_arg_name(&self, state: &mut State<'a>, name: &Atom<'a>) -> Atom<'a> { - let count = state.arg_names.entry(name.clone()).or_insert(0); - *count += 1; - let name = if *count > 1 { format!("_{name}{count}") } else { format!("_{name}") }; - self.ctx.ast.new_atom(&name) + /// Add assignment statement for decl id + /// function id() {} -> function id() {}; Name.id = id; + fn add_declaration( + &self, + decl: Declaration<'a>, + name: &Atom<'a>, + names: &mut FxHashSet>, + new_stmts: &mut Vec<'a, Statement<'a>>, + ) { + if let Some(ident) = decl.id() { + let item_name = ident.name.clone(); + let assignment_statement = self.create_assignment_statement(name, &item_name); + new_stmts.push(Statement::Declaration(decl)); + let assignment_statement = + self.ctx.ast.expression_statement(SPAN, assignment_statement); + new_stmts.push(assignment_statement); + names.insert(item_name); + } + } + + // name.item_name = item_name + fn create_assignment_statement(&self, name: &Atom<'a>, item_name: &Atom<'a>) -> Expression<'a> { + let ident = self.ctx.ast.identifier_reference(SPAN, name.as_str()); + let object = self.ctx.ast.identifier_reference_expression(ident); + let property = IdentifierName::new(SPAN, item_name.clone()); + let left = self.ctx.ast.static_member(SPAN, object, property, false); + let left = SimpleAssignmentTarget::MemberAssignmentTarget(self.ctx.ast.alloc(left)); + let left = AssignmentTarget::SimpleAssignmentTarget(left); + let ident = self.ctx.ast.identifier_reference(SPAN, item_name.as_str()); + let right = self.ctx.ast.identifier_reference_expression(ident); + let op = AssignmentOperator::Assign; + self.ctx.ast.assignment_expression(SPAN, op, left, right) + } + + /// Convert `export const foo = 1` to `Namespace.foo = 1`; + fn handle_variable_declaration( + &self, + mut var_decl: Box<'a, VariableDeclaration<'a>>, + name: &Atom<'a>, + ) -> Vec<'a, Statement<'a>> { + let is_all_binding_identifier = var_decl + .declarations + .iter() + .all(|declaration| declaration.id.kind.is_binding_identifier()); + + // `export const a = 1` transforms to `const a = N.a = 1`, the output + // is smaller than `const a = 1; N.a = a`; + if is_all_binding_identifier { + var_decl.declarations.iter_mut().for_each(|declarator| { + let Some(property_name) = declarator.id.get_identifier() else { + return; + }; + if let Some(init) = &declarator.init { + declarator.init = Some(self.ctx.ast.assignment_expression( + SPAN, + AssignmentOperator::Assign, + self.ctx.ast.simple_assignment_target_member_expression( + self.ctx.ast.static_member( + SPAN, + self.ctx.ast.identifier_reference_expression( + IdentifierReference::new(SPAN, name.clone()), + ), + IdentifierName::new(SPAN, property_name.clone()), + false, + ), + ), + self.ctx.ast.copy(init), + )); + } + }); + return self.ctx.ast.new_vec_single(Statement::Declaration( + Declaration::VariableDeclaration(var_decl), + )); + } + + // Now we have pattern in declarators + // `export const [a] = 1` transforms to `const [a] = 1; N.a = a` + let mut assignments = self.ctx.ast.new_vec(); + var_decl.bound_names(&mut |id| { + assignments.push(self.create_assignment_statement(name, &id.name)); + }); + + let mut stmts = self.ctx.ast.new_vec_with_capacity(2); + stmts.push(Statement::Declaration(Declaration::VariableDeclaration(var_decl))); + stmts.push( + self.ctx + .ast + .expression_statement(SPAN, self.ctx.ast.sequence_expression(SPAN, assignments)), + ); + stmts } } + +/// Check if the statements contain a namespace declaration +fn has_namespace(stmts: &[Statement]) -> bool { + stmts.iter().any(|stmt| match stmt { + Statement::Declaration(Declaration::TSModuleDeclaration(_)) => true, + Statement::ModuleDeclaration(module_decl) => { + if let ModuleDeclaration::ExportNamedDeclaration(decl) = &**module_decl { + matches!(decl.declaration, Some(Declaration::TSModuleDeclaration(_))) + } else { + false + } + } + _ => false, + }) +} diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 443f57d3a..9fb57db79 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 153/209 +Passed: 157/209 # All Passed: * babel-plugin-transform-react-jsx-source @@ -17,7 +17,7 @@ Passed: 153/209 * opts/optimizeConstEnums/input.ts * opts/rewriteImportExtensions/input.ts -# babel-plugin-transform-typescript (97/139) +# babel-plugin-transform-typescript (101/139) * class/accessor-allowDeclareFields-false/input.ts * class/accessor-allowDeclareFields-true/input.ts * exports/declared-types/input.ts @@ -29,22 +29,18 @@ Passed: 153/209 * namespace/ambient-module-nested/input.ts * namespace/ambient-module-nested-exported/input.ts * namespace/canonical/input.ts -* namespace/clobber-class/input.ts * namespace/clobber-enum/input.ts -* namespace/clobber-export/input.ts -* namespace/clobber-import/input.ts * namespace/contentious-names/input.ts * namespace/empty-removed/input.ts -* namespace/export-type-only/input.ts * namespace/module-nested/input.ts * namespace/module-nested-export/input.ts +* namespace/multiple/input.ts * namespace/mutable-fail/input.ts * namespace/namespace-flag/input.ts * namespace/namespace-nested-module/input.ts * namespace/nested/input.ts * namespace/nested-destructuring/input.ts * namespace/nested-namespace/input.ts -* namespace/nested-shorthand/input.ts * namespace/nested-shorthand-export/input.ts * namespace/same-name/input.ts * optimize-const-enums/custom-values/input.ts diff --git a/tasks/transform_conformance/typescript.snap.md b/tasks/transform_conformance/typescript.snap.md index ebf98b5cb..e06414256 100644 --- a/tasks/transform_conformance/typescript.snap.md +++ b/tasks/transform_conformance/typescript.snap.md @@ -533,7 +533,7 @@ let M1; EImpl1[EImpl1['F'] = F] = 'F'; return EImpl1; })(EImpl1 || {}); -export var EConst1 = (EConst1 => { + var EConst1 = (EConst1 => { const A = 3; EConst1[EConst1['A'] = A] = 'A'; const B = 2; @@ -542,7 +542,8 @@ export var EConst1 = (EConst1 => { EConst1[EConst1['C'] = C] = 'C'; return EConst1; })(EConst1 || {}); -export var EConst1 = (EConst1 => { + _M1.EConst1 = EConst1; + var EConst1 = (EConst1 => { const D = 7; EConst1[EConst1['D'] = D] = 'D'; const E = 9; @@ -551,11 +552,12 @@ export var EConst1 = (EConst1 => { EConst1[EConst1['F'] = F] = 'F'; return EConst1; })(EConst1 || {}); + _M1.EConst1 = EConst1; var x = [EConst1.A, EConst1.B, EConst1.C, EConst1.D, EConst1.E, EConst1.F]; })(M1 || (M1 = {})); let M2; (function(_M2) { -export var EComp2 = (EComp2 => { + var EComp2 = (EComp2 => { const A = 'foo'.length; EComp2[EComp2['A'] = A] = 'A'; const B = 'foo'.length; @@ -564,7 +566,8 @@ export var EComp2 = (EComp2 => { EComp2[EComp2['C'] = C] = 'C'; return EComp2; })(EComp2 || {}); -export var EComp2 = (EComp2 => { + _M2.EComp2 = EComp2; + var EComp2 = (EComp2 => { const D = 'foo'.length; EComp2[EComp2['D'] = D] = 'D'; const E = 'foo'.length; @@ -573,6 +576,7 @@ export var EComp2 = (EComp2 => { EComp2[EComp2['F'] = F] = 'F'; return EComp2; })(EComp2 || {}); + _M2.EComp2 = EComp2; var x = [EComp2.A, EComp2.B, EComp2.C, EComp2.D, EComp2.E, EComp2.F]; })(M2 || (M2 = {})); let M3; @@ -596,7 +600,7 @@ let M3; })(M3 || (M3 = {})); let M4; (function(_M4) { -export var Color = (Color => { + var Color = (Color => { const Red = 0; Color[Color['Red'] = Red] = 'Red'; const Green = 1 + Red; @@ -605,10 +609,11 @@ export var Color = (Color => { Color[Color['Blue'] = Blue] = 'Blue'; return Color; })(Color || {}); + _M4.Color = Color; })(M4 || (M4 = {})); let M5; (function(_M5) { -export var Color = (Color => { + var Color = (Color => { const Red = 0; Color[Color['Red'] = Red] = 'Red'; const Green = 1 + Red; @@ -617,11 +622,13 @@ export var Color = (Color => { Color[Color['Blue'] = Blue] = 'Blue'; return Color; })(Color || {}); + _M5.Color = Color; })(M5 || (M5 = {})); let M6; (function(_M6) { + let A; (function(_A) { -export var Color = (Color => { + var Color = (Color => { const Red = 0; Color[Color['Red'] = Red] = 'Red'; const Green = 1 + Red; @@ -630,17 +637,19 @@ export var Color = (Color => { Color[Color['Blue'] = Blue] = 'Blue'; return Color; })(Color || {}); - })(A || (A = {})); + _A.Color = Color; + })(A || (A = _M6.A || (_M6.A = {}))); })(M6 || (M6 = {})); -(function(_M62) { -export let A; +(function(_M6) { + let A; (function(_A) { -export var Color = (Color => { + var Color = (Color => { const Yellow = 1; Color[Color['Yellow'] = Yellow] = 'Yellow'; return Color; })(Color || {}); - })(A || (A = {})); + _A.Color = Color; + })(A || (A = _M6.A || (_M6.A = {}))); var t = A.Color.Yellow; t = A.Color.Red; })(M6 || (M6 = {})); @@ -651,99 +660,114 @@ export var Color = (Color => { ```typescript let M; (function(_M) { -export var E1 = (E1 => { + var E1 = (E1 => { const A = 0; E1[E1['A'] = A] = 'A'; return E1; })(E1 || {}); -export var E2 = (E2 => { + _M.E1 = E1; + var E2 = (E2 => { const C = 0; E2[E2['C'] = C] = 'C'; return E2; })(E2 || {}); -export var E3 = (E3 => { + _M.E2 = E2; + var E3 = (E3 => { const A = 0; E3[E3['A'] = A] = 'A'; return E3; })(E3 || {}); + _M.E3 = E3; })(M || (M = {})); -(function(_M2) { -export var E1 = (E1 => { +(function(_M) { + var E1 = (E1 => { const B = 'foo'.length; E1[E1['B'] = B] = 'B'; return E1; })(E1 || {}); -export var E2 = (E2 => { + _M.E1 = E1; + var E2 = (E2 => { const B = 'foo'.length; E2[E2['B'] = B] = 'B'; return E2; })(E2 || {}); -export var E3 = (E3 => { + _M.E2 = E2; + var E3 = (E3 => { const C = 0; E3[E3['C'] = C] = 'C'; return E3; })(E3 || {}); + _M.E3 = E3; })(M || (M = {})); -(function(_M3) { -export var E1 = (E1 => { +(function(_M) { + var E1 = (E1 => { const C = 0; E1[E1['C'] = C] = 'C'; return E1; })(E1 || {}); -export var E2 = (E2 => { + _M.E1 = E1; + var E2 = (E2 => { const A = 0; E2[E2['A'] = A] = 'A'; return E2; })(E2 || {}); -export var E3 = (E3 => { + _M.E2 = E2; + var E3 = (E3 => { const B = 'foo'.length; E3[E3['B'] = B] = 'B'; return E3; })(E3 || {}); + _M.E3 = E3; })(M || (M = {})); let M1; (function(_M1) { -export var E1 = (E1 => { + var E1 = (E1 => { const A = 0; E1[E1['A'] = A] = 'A'; return E1; })(E1 || {}); + _M1.E1 = E1; })(M1 || (M1 = {})); -(function(_M12) { -export var E1 = (E1 => { +(function(_M1) { + var E1 = (E1 => { const B = 0; E1[E1['B'] = B] = 'B'; return E1; })(E1 || {}); + _M1.E1 = E1; })(M1 || (M1 = {})); -(function(_M13) { -export var E1 = (E1 => { +(function(_M1) { + var E1 = (E1 => { const C = 0; E1[E1['C'] = C] = 'C'; return E1; })(E1 || {}); + _M1.E1 = E1; })(M1 || (M1 = {})); let M2; (function(_M2) { -export var E1 = (E1 => { + var E1 = (E1 => { const A = 0; E1[E1['A'] = A] = 'A'; return E1; })(E1 || {}); + _M2.E1 = E1; })(M2 || (M2 = {})); -(function(_M22) { -export var E1 = (E1 => { +(function(_M2) { + var E1 = (E1 => { const B = 0; E1[E1['B'] = B] = 'B'; return E1; })(E1 || {}); + _M2.E1 = E1; })(M2 || (M2 = {})); -(function(_M23) { -export var E1 = (E1 => { +(function(_M2) { + var E1 = (E1 => { const C = 0; E1[E1['C'] = C] = 'C'; return E1; })(E1 || {}); + _M2.E1 = E1; })(M2 || (M2 = {})); ```