From e14ac17c7200da97fd3ef1456f6c1ee579168a1d Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 18 Apr 2024 18:53:01 +0800 Subject: [PATCH] feat(transformer/typescript): insert this assignment after the super call (#3018) --- crates/oxc_ast/src/ast/js.rs | 10 ++ crates/oxc_transformer/src/lib.rs | 7 ++ .../src/typescript/annotations.rs | 95 +++++++++++++++---- crates/oxc_transformer/src/typescript/mod.rs | 8 ++ tasks/transform_conformance/babel.snap.md | 7 +- 5 files changed, 106 insertions(+), 21 deletions(-) diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 7d460c2a3..fc33fbdbc 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -275,6 +275,10 @@ impl<'a> Expression<'a> { matches!(self, Expression::CallExpression(_)) } + pub fn is_super_call_expression(&self) -> bool { + matches!(self, Expression::CallExpression(expr) if matches!(&expr.callee, Expression::Super(_))) + } + pub fn is_call_like_expression(&self) -> bool { self.is_call_expression() && matches!(self, Expression::NewExpression(_) | Expression::ImportExpression(_)) @@ -1910,6 +1914,12 @@ pub struct FormalParameter<'a> { pub decorators: Vec<'a, Decorator<'a>>, } +impl<'a> FormalParameter<'a> { + pub fn is_public(&self) -> bool { + matches!(self.accessibility, Some(TSAccessibility::Public)) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] pub enum FormalParameterKind { diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 788fcd0fb..bc6fc856c 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -162,6 +162,8 @@ impl<'a> VisitMut<'a> for Transformer<'a> { self.x0_typescript.transform_method_definition(def); walk_mut::walk_method_definition_mut(self, def); + + self.x0_typescript.transform_method_definition_on_exit(def); } fn visit_new_expression(&mut self, expr: &mut NewExpression<'a>) { @@ -216,4 +218,9 @@ impl<'a> VisitMut<'a> for Transformer<'a> { self.x0_typescript.transform_declaration(decl); walk_mut::walk_declaration_mut(self, decl); } + + fn visit_if_statement(&mut self, stmt: &mut IfStatement<'a>) { + self.x0_typescript.transform_if_statement(stmt); + walk_mut::walk_if_statement_mut(self, stmt); + } } diff --git a/crates/oxc_transformer/src/typescript/annotations.rs b/crates/oxc_transformer/src/typescript/annotations.rs index 63f1bb943..55c81f12e 100644 --- a/crates/oxc_transformer/src/typescript/annotations.rs +++ b/crates/oxc_transformer/src/typescript/annotations.rs @@ -17,11 +17,19 @@ pub struct TypeScriptAnnotations<'a> { #[allow(dead_code)] options: Rc, ctx: Ctx<'a>, + /// Assignments to be added to the constructor body + assignments: Vec<'a, Statement<'a>>, + has_super_call: bool, } impl<'a> TypeScriptAnnotations<'a> { pub fn new(options: &Rc, ctx: &Ctx<'a>) -> Self { - Self { options: Rc::clone(options), ctx: Rc::clone(ctx) } + Self { + has_super_call: false, + assignments: ctx.ast.new_vec(), + options: Rc::clone(options), + ctx: Rc::clone(ctx), + } } // Convert `export = expr` into `module.exports = expr` @@ -237,35 +245,44 @@ impl<'a> TypeScriptAnnotations<'a> { } pub fn transform_method_definition(&mut self, def: &mut MethodDefinition<'a>) { - def.accessibility = None; - def.optional = false; - def.r#override = false; - // Collects parameter properties so that we can add an assignment // for each of them in the constructor body. if def.kind == MethodDefinitionKind::Constructor { - let mut assigns = self.ctx.ast.new_vec(); - for param in &def.value.params.items { - if param.pattern.type_annotation.is_none() { + if !param.is_public() { continue; } if let Some(id) = param.pattern.get_identifier() { - assigns.push(self.create_this_property_assignment(id)); + let assignment = self.create_this_property_assignment(id); + self.assignments.push(assignment); } } + } - if !assigns.is_empty() { + def.accessibility = None; + def.optional = false; + def.r#override = false; + } + + pub fn transform_method_definition_on_exit(&mut self, def: &mut MethodDefinition<'a>) { + if def.kind == MethodDefinitionKind::Constructor && !self.assignments.is_empty() { + // When the constructor doesn't have a super call, + // we simply add assignments to the bottom of the function body + if self.has_super_call { + self.assignments.clear(); + } else { def.value .body - .get_or_insert(self.ctx.ast.function_body( - SPAN, - self.ctx.ast.new_vec(), - self.ctx.ast.new_vec(), - )) + .get_or_insert_with(|| { + self.ctx.ast.function_body( + SPAN, + self.ctx.ast.new_vec(), + self.ctx.ast.new_vec(), + ) + }) .statements - .extend(assigns); + .extend(self.assignments.drain(..)); } } } @@ -302,6 +319,52 @@ impl<'a> TypeScriptAnnotations<'a> { // Ignore ModuleDeclaration as it's handled in the program _ => true, }); + + // Add assignments after super calls + if !self.assignments.is_empty() { + let mut super_indexes = vec![]; + for (index, stmt) in stmts.iter().rev().enumerate() { + if matches!(stmt, Statement::ExpressionStatement(stmt) if stmt.expression.is_super_call_expression()) + { + super_indexes.push(index); + } + } + if !super_indexes.is_empty() { + self.has_super_call = true; + for index in super_indexes.iter().rev() { + stmts.splice((index + 1)..=*index, self.ctx.ast.copy(&self.assignments)); + } + } + } + } + + /// Transform if statement's consequent and alternate to block statements if they are super calls + /// ```ts + /// if (true) super() else super(); + /// // to + /// if (true) { super() } else { super() } + /// ``` + pub fn transform_if_statement(&mut self, stmt: &mut IfStatement<'a>) { + if !self.assignments.is_empty() { + if matches!(&stmt.consequent, Statement::ExpressionStatement(expr) if expr.expression.is_super_call_expression()) + { + stmt.consequent = + self.ctx.ast.block_statement(self.ctx.ast.block( + SPAN, + self.ctx.ast.new_vec_single(self.ctx.ast.copy(&stmt.consequent)), + )); + } + if let Some(alternate) = &stmt.alternate { + if matches!(alternate, Statement::ExpressionStatement(expr) if expr.expression.is_super_call_expression()) + { + stmt.alternate = + Some(self.ctx.ast.block_statement(self.ctx.ast.block( + SPAN, + self.ctx.ast.new_vec_single(self.ctx.ast.copy(alternate)), + ))); + } + } + } } pub fn transform_tagged_template_expression( diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index 88e6ef948..d06a69ee9 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -120,6 +120,10 @@ impl<'a> TypeScript<'a> { self.annotations.transform_method_definition(def); } + pub fn transform_method_definition_on_exit(&mut self, def: &mut MethodDefinition<'a>) { + self.annotations.transform_method_definition_on_exit(def); + } + pub fn transform_new_expression(&mut self, expr: &mut NewExpression<'a>) { self.annotations.transform_new_expression(expr); } @@ -136,6 +140,10 @@ impl<'a> TypeScript<'a> { self.annotations.transform_statements_on_exit(stmts); } + pub fn transform_if_statement(&mut self, stmt: &mut IfStatement<'a>) { + self.annotations.transform_if_statement(stmt); + } + pub fn transform_tagged_template_expression( &mut self, expr: &mut TaggedTemplateExpression<'a>, diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 1492eccea..f241c3093 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 148/209 +Passed: 151/209 # All Passed: * babel-plugin-transform-react-jsx-source @@ -17,12 +17,9 @@ Passed: 148/209 * opts/optimizeConstEnums/input.ts * opts/rewriteImportExtensions/input.ts -# babel-plugin-transform-typescript (92/139) +# babel-plugin-transform-typescript (95/139) * class/accessor-allowDeclareFields-false/input.ts * class/accessor-allowDeclareFields-true/input.ts -* class/parameter-properties/input.ts -* class/parameter-properties-late-super/input.ts -* class/parameter-properties-with-super/input.ts * exports/declared-types/input.ts * exports/export-const-enums/input.ts * exports/export-type-star-from/input.ts