diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index b7ba7b6f6..8d566b014 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -891,6 +891,10 @@ impl<'a> Modifiers<'a> { .as_ref() .map_or(false, |modifiers| modifiers.iter().any(|modifier| modifier.kind == target)) } + + pub fn is_contains_declare(&self) -> bool { + self.contains(ModifierKind::Declare) + } } /// Export Assignment in non-module files diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index c9d6bf3b1..2b30c0d99 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -181,6 +181,8 @@ impl<'a> VisitMut<'a> for Transformer<'a> { } fn visit_statements(&mut self, stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>) { + self.typescript.as_mut().map(|t| t.transform_statements(stmts)); + for stmt in stmts.iter_mut() { self.visit_statement(stmt); } diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index 3f662918d..d4a80770f 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -49,6 +49,10 @@ impl<'a> TypeScript<'a> { } } + pub fn transform_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { + self.insert_let_decl_for_ts_module_block(stmts); + } + pub fn transform_statement(&mut self, stmt: &mut Statement<'a>) { let new_stmt = match stmt { Statement::ModuleDeclaration(module_decl) => { @@ -58,6 +62,13 @@ impl<'a> TypeScript<'a> { None } } + Statement::Declaration(Declaration::TSModuleDeclaration(ts_module_decl)) => { + if ts_module_decl.modifiers.is_contains_declare() { + None + } else { + Some(self.transform_ts_module_block(ts_module_decl)) + } + } _ => None, }; @@ -562,4 +573,128 @@ impl<'a> TypeScript<'a> { Some(Statement::Declaration(self.ast.move_declaration(declaration))) } + + /// Insert let declaration for ts module block + fn insert_let_decl_for_ts_module_block(&mut self, stmts: &mut Vec<'a, Statement<'a>>) { + let mut insert_var_decl = vec![]; + + for (index, stmt) in stmts.iter().enumerate() { + if let Statement::Declaration(Declaration::TSModuleDeclaration(decl)) = stmt { + if !decl.modifiers.is_contains_declare() { + insert_var_decl.push((index, decl.id.name().clone())); + } + } + } + + for (index, name) in insert_var_decl.into_iter().rev() { + let kind = VariableDeclarationKind::Let; + let decls = { + let binding_identifier = BindingIdentifier::new(SPAN, name.clone()); + let binding_pattern_kind = self.ast.binding_pattern_identifier(binding_identifier); + let binding = self.ast.binding_pattern(binding_pattern_kind, None, false); + let decl = self.ast.variable_declarator(SPAN, kind, binding, None, false); + self.ast.new_vec_single(decl) + }; + let variable_declaration = + self.ast.variable_declaration(SPAN, kind, decls, Modifiers::empty()); + + let stmt = + Statement::Declaration(Declaration::VariableDeclaration(variable_declaration)); + stmts.insert(index, stmt); + } + } + + /// ```TypeScript + /// // transform ts module block + /// namespace Foo { + /// } + /// // to + /// let Foo; // this line added in `insert_let_decl_for_ts_module_block` + /// (function (_Foo) { + /// })(Foo || (Foo = {})); + /// ``` + fn transform_ts_module_block( + &mut self, + block: &mut Box<'a, TSModuleDeclaration<'a>>, + ) -> Statement<'a> { + let body_statements = match &mut block.body { + TSModuleDeclarationBody::TSModuleDeclaration(decl) => { + let transformed_module_block = self.transform_ts_module_block(decl); + self.ast.new_vec_single(transformed_module_block) + } + TSModuleDeclarationBody::TSModuleBlock(ts_module_block) => { + self.ast.move_statement_vec(&mut ts_module_block.body) + } + }; + + let name = block.id.name(); + + let callee = { + let body = self.ast.function_body(SPAN, self.ast.new_vec(), body_statements); + let params = self.ast.formal_parameters( + SPAN, + FormalParameterKind::FormalParameter, + self.ast.new_vec_single(self.ast.formal_parameter( + SPAN, + self.ast.binding_pattern( + self.ast.binding_pattern_identifier(BindingIdentifier::new( + SPAN, + format!("_{}", name.clone()).into(), + )), + None, + false, + ), + None, + false, + self.ast.new_vec(), + )), + None, + ); + let function = self.ast.function( + FunctionType::FunctionExpression, + SPAN, + None, + false, + false, + false, + None, + params, + Some(body), + None, + None, + Modifiers::empty(), + ); + let function_expr = self.ast.function_expression(function); + self.ast.parenthesized_expression(SPAN, function_expr) + }; + + let arguments = { + let right = { + let left = AssignmentTarget::SimpleAssignmentTarget( + self.ast.simple_assignment_target_identifier(IdentifierReference::new( + SPAN, + name.clone(), + )), + ); + let right = self.ast.object_expression(SPAN, self.ast.new_vec(), None); + self.ast.parenthesized_expression( + SPAN, + self.ast.assignment_expression(SPAN, AssignmentOperator::Assign, left, right), + ) + }; + self.ast.new_vec_single(Argument::Expression( + self.ast.logical_expression( + SPAN, + self.ast.identifier_reference_expression(IdentifierReference::new( + SPAN, + name.clone(), + )), + LogicalOperator::Or, + right, + ), + )) + }; + let expr = self.ast.call_expression(SPAN, callee, arguments, false, None); + self.ast.expression_statement(SPAN, expr) + } } diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 4ce7423e8..3edf307df 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 314/1179 +Passed: 316/1179 # All Passed: * babel-plugin-transform-numeric-separator @@ -832,7 +832,7 @@ Passed: 314/1179 * general/function-duplicate-name/input.js * general/object/input.js -# babel-plugin-transform-typescript (83/158) +# babel-plugin-transform-typescript (85/158) * class/abstract-class-decorated/input.ts * class/abstract-class-decorated-method/input.ts * class/abstract-class-decorated-parameter/input.ts @@ -875,7 +875,6 @@ Passed: 314/1179 * namespace/clobber-import/input.ts * namespace/contentious-names/input.ts * namespace/declare/input.ts -* namespace/declare-global-nested-namespace/input.ts * namespace/empty-removed/input.ts * namespace/export/input.ts * namespace/export-type-only/input.ts @@ -891,7 +890,6 @@ Passed: 314/1179 * namespace/nested-shorthand/input.ts * namespace/nested-shorthand-export/input.ts * namespace/same-name/input.ts -* namespace/undeclared/input.ts * optimize-const-enums/custom-values/input.ts * optimize-const-enums/custom-values-exported/input.ts * optimize-const-enums/declare/input.ts diff --git a/tasks/transform_conformance/typescript.snap.md b/tasks/transform_conformance/typescript.snap.md index fd27c4e4e..280fab597 100644 --- a/tasks/transform_conformance/typescript.snap.md +++ b/tasks/transform_conformance/typescript.snap.md @@ -511,11 +511,237 @@ var Animals = (Animals => { # typescript/tests/cases/conformance/enums/enumMerging.ts ```typescript +let M1; +(function(_M1) { + var EImpl1 = (EImpl1 => { + const A = 0; + EImpl1[EImpl1['A'] = A] = 'A'; + const B = 1 + A; + EImpl1[EImpl1['B'] = B] = 'B'; + const C = 1 + B; + EImpl1[EImpl1['C'] = C] = 'C'; + return EImpl1; + })(EImpl1 || {}); + var EImpl1 = (EImpl1 => { + const D = 1; + EImpl1[EImpl1['D'] = D] = 'D'; + const E = 1 + D; + EImpl1[EImpl1['E'] = E] = 'E'; + const F = 1 + E; + EImpl1[EImpl1['F'] = F] = 'F'; + return EImpl1; + })(EImpl1 || {}); +export var EConst1 = (EConst1 => { + const A = 3; + EConst1[EConst1['A'] = A] = 'A'; + const B = 2; + EConst1[EConst1['B'] = B] = 'B'; + const C = 1; + EConst1[EConst1['C'] = C] = 'C'; + return EConst1; + })(EConst1 || {}); + var EConst1 = (EConst1 => { + const D = 7; + EConst1[EConst1['D'] = D] = 'D'; + const E = 9; + EConst1[EConst1['E'] = E] = 'E'; + const F = 8; + EConst1[EConst1['F'] = F] = 'F'; + return 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 => { + const A = 'foo'.length; + EComp2[EComp2['A'] = A] = 'A'; + const B = 'foo'.length; + EComp2[EComp2['B'] = B] = 'B'; + const C = 'foo'.length; + EComp2[EComp2['C'] = C] = 'C'; + return EComp2; + })(EComp2 || {}); + var EComp2 = (EComp2 => { + const D = 'foo'.length; + EComp2[EComp2['D'] = D] = 'D'; + const E = 'foo'.length; + EComp2[EComp2['E'] = E] = 'E'; + const F = 'foo'.length; + EComp2[EComp2['F'] = F] = 'F'; + return EComp2; + })(EComp2 || {}); + var x = [EComp2.A, EComp2.B, EComp2.C, EComp2.D, EComp2.E, EComp2.F]; +})(M2 || (M2 = {})); +let M3; +(function(_M3) { + var EInit = (EInit => { + const A = 0; + EInit[EInit['A'] = A] = 'A'; + const B = 1 + A; + EInit[EInit['B'] = B] = 'B'; + return EInit; + })(EInit || {}); + var EInit = (EInit => { + const C = 1; + EInit[EInit['C'] = C] = 'C'; + const D = 1 + C; + EInit[EInit['D'] = D] = 'D'; + const E = 1 + D; + EInit[EInit['E'] = E] = 'E'; + return EInit; + })(EInit || {}); +})(M3 || (M3 = {})); +let M4; +(function(_M4) { +export var Color = (Color => { + const Red = 0; + Color[Color['Red'] = Red] = 'Red'; + const Green = 1 + Red; + Color[Color['Green'] = Green] = 'Green'; + const Blue = 1 + Green; + Color[Color['Blue'] = Blue] = 'Blue'; + return Color; + })(Color || {}); +})(M4 || (M4 = {})); +let M5; +(function(_M5) { + var Color = (Color => { + const Red = 0; + Color[Color['Red'] = Red] = 'Red'; + const Green = 1 + Red; + Color[Color['Green'] = Green] = 'Green'; + const Blue = 1 + Green; + Color[Color['Blue'] = Blue] = 'Blue'; + return Color; + })(Color || {}); +})(M5 || (M5 = {})); +let M6; +(function(_M6) { + (function(_A) { + var Color = (Color => { + const Red = 0; + Color[Color['Red'] = Red] = 'Red'; + const Green = 1 + Red; + Color[Color['Green'] = Green] = 'Green'; + const Blue = 1 + Green; + Color[Color['Blue'] = Blue] = 'Blue'; + return Color; + })(Color || {}); + })(A || (A = {})); +})(M6 || (M6 = {})); +let M6; +(function(_M6) { + var t = A.Color.Yellow; + t = A.Color.Red; +})(M6 || (M6 = {})); ``` # typescript/tests/cases/conformance/enums/enumMergingErrors.ts ```typescript +let M; +(function(_M) { +export var E1 = (E1 => { + const A = 0; + E1[E1['A'] = A] = 'A'; + return E1; + })(E1 || {}); +export var E2 = (E2 => { + const C = 0; + E2[E2['C'] = C] = 'C'; + return E2; + })(E2 || {}); +export var E3 = (E3 => { + const A = 0; + E3[E3['A'] = A] = 'A'; + return E3; + })(E3 || {}); +})(M || (M = {})); +let M; +(function(_M) { + var E1 = (E1 => { + const B = 'foo'.length; + E1[E1['B'] = B] = 'B'; + return E1; + })(E1 || {}); + var E2 = (E2 => { + const B = 'foo'.length; + E2[E2['B'] = B] = 'B'; + return E2; + })(E2 || {}); + var E3 = (E3 => { + const C = 0; + E3[E3['C'] = C] = 'C'; + return E3; + })(E3 || {}); +})(M || (M = {})); +let M; +(function(_M) { + var E1 = (E1 => { + const C = 0; + E1[E1['C'] = C] = 'C'; + return E1; + })(E1 || {}); + var E2 = (E2 => { + const A = 0; + E2[E2['A'] = A] = 'A'; + return E2; + })(E2 || {}); + var E3 = (E3 => { + const B = 'foo'.length; + E3[E3['B'] = B] = 'B'; + return E3; + })(E3 || {}); +})(M || (M = {})); +let M1; +(function(_M1) { + var E1 = (E1 => { + const A = 0; + E1[E1['A'] = A] = 'A'; + return E1; + })(E1 || {}); +})(M1 || (M1 = {})); +let M1; +(function(_M1) { + var E1 = (E1 => { + const B = 0; + E1[E1['B'] = B] = 'B'; + return E1; + })(E1 || {}); +})(M1 || (M1 = {})); +let M1; +(function(_M1) { + var E1 = (E1 => { + const C = 0; + E1[E1['C'] = C] = 'C'; + return E1; + })(E1 || {}); +})(M1 || (M1 = {})); +let M2; +(function(_M2) { + var E1 = (E1 => { + const A = 0; + E1[E1['A'] = A] = 'A'; + return E1; + })(E1 || {}); +})(M2 || (M2 = {})); +let M2; +(function(_M2) { + var E1 = (E1 => { + const B = 0; + E1[E1['B'] = B] = 'B'; + return E1; + })(E1 || {}); +})(M2 || (M2 = {})); +let M2; +(function(_M2) { + var E1 = (E1 => { + const C = 0; + E1[E1['C'] = C] = 'C'; + return E1; + })(E1 || {}); +})(M2 || (M2 = {})); ```