diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 04c864b85..27b54991c 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -473,6 +473,14 @@ impl<'a> PropertyKey<'a> { } } + pub fn name(&self) -> Option { + if self.is_private_identifier() { + self.private_name() + } else { + self.static_name() + } + } + pub fn is_specific_id(&self, name: &str) -> bool { match self { PropertyKey::Identifier(ident) => ident.name == name, @@ -1901,6 +1909,22 @@ impl<'a> ClassElement<'a> { _ => false, } } + + pub fn has_decorator(&self) -> bool { + #[allow(clippy::match_same_arms)] + match self { + Self::TSIndexSignature(_) + | Self::TSAbstractMethodDefinition(_) + | Self::TSAbstractPropertyDefinition(_) => false, + Self::MethodDefinition(method) => !method.decorators.is_empty(), + Self::PropertyDefinition(property) => !property.decorators.is_empty(), + Self::AccessorProperty(_) => { + // TODO: AccessorProperty doesn't have decorators property + false + } + Self::StaticBlock(_) => false, + } + } } #[derive(Debug, Hash)] diff --git a/crates/oxc_transformer/src/proposals/decorators.rs b/crates/oxc_transformer/src/proposals/decorators.rs index 98890e88d..c4150c264 100644 --- a/crates/oxc_transformer/src/proposals/decorators.rs +++ b/crates/oxc_transformer/src/proposals/decorators.rs @@ -63,18 +63,28 @@ impl<'a> Decorators<'a> { }) } - pub fn get_variable_declarator(&self, name: &str) -> VariableDeclarator<'a> { + pub fn get_variable_declarator(&self, name: Atom) -> VariableDeclarator<'a> { self.ast.variable_declarator( SPAN, VariableDeclarationKind::Var, BindingPattern::new_with_kind( - self.ast.binding_pattern_identifier(BindingIdentifier::new(SPAN, name.into())), + self.ast.binding_pattern_identifier(BindingIdentifier::new(SPAN, name)), ), None, false, ) } + #[allow(clippy::unnecessary_wraps)] + pub fn get_assignment_target_maybe_default( + &self, + name: Atom, + ) -> Option> { + Some(AssignmentTargetMaybeDefault::AssignmentTarget( + self.ast.simple_assignment_target_identifier(IdentifierReference::new(SPAN, name)), + )) + } + // TODO: use generate_uid of scope to generate unique name pub fn get_unique_name(&mut self, name: &Atom) -> Atom { let uid = self.uid_map.entry(name.clone()).or_insert(0); @@ -96,40 +106,54 @@ impl<'a> Decorators<'a> { || None, |declaration| { if let Declaration::ClassDeclaration(class) = declaration { - if class.decorators.is_empty() { + if !Self::can_transform(class) { return None; } - let class_name = class - .id - .clone() - .map(|id| self.get_unique_name(&id.name)) - .or_else(|| Some(self.get_unique_name(&"class".into()))); + let has_decorator = !class.decorators.is_empty(); + let class_name = if has_decorator { + class + .id + .clone() + .map(|id| self.get_unique_name(&id.name)) + .or_else(|| Some(self.get_unique_name(&"class".into()))) + } else { + None + }; - self.bottom_statements.push(self.ast.module_declaration( - ModuleDeclaration::ExportNamedDeclaration( - self.ast.export_named_declaration( - SPAN, - None, - self.ast.new_vec_single(ExportSpecifier::new( + if has_decorator { + self.bottom_statements.push(self.ast.module_declaration( + ModuleDeclaration::ExportNamedDeclaration( + self.ast.export_named_declaration( SPAN, - ModuleExportName::Identifier(IdentifierName::new( + None, + self.ast.new_vec_single(ExportSpecifier::new( SPAN, - class_name.clone().unwrap(), + ModuleExportName::Identifier( + IdentifierName::new( + SPAN, + class_name.clone().unwrap(), + ), + ), + ModuleExportName::Identifier( + IdentifierName::new( + SPAN, + class.id.clone().unwrap().name, + ), + ), )), - ModuleExportName::Identifier(IdentifierName::new( - SPAN, - class.id.clone().unwrap().name, - )), - )), - None, - ImportOrExportKind::Value, + None, + ImportOrExportKind::Value, + ), ), - ), - )); + )); + } - return Some(Statement::Declaration( - self.transform_class(class, class_name), - )); + let new_declaration = self.transform_class(class, class_name); + if has_decorator { + return Some(Statement::Declaration(new_declaration)); + } + *declaration = new_declaration; + return None; } None }, @@ -139,36 +163,43 @@ impl<'a> Decorators<'a> { if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &mut export.declaration { - if class.decorators.is_empty() { + if !Self::can_transform(class) { return; } - let class_name = class - .id - .clone() - .map(|id| self.get_unique_name(&id.name)) - .or_else(|| Some(self.get_unique_name(&"class".into()))); + let class_has_decorator = !class.decorators.is_empty(); + let class_name = if class_has_decorator { + class + .id + .clone() + .map(|id| self.get_unique_name(&id.name)) + .or_else(|| Some(self.get_unique_name(&"class".into()))) + } else { + None + }; - self.bottom_statements.push(self.ast.module_declaration( - ModuleDeclaration::ExportNamedDeclaration( - self.ast.export_named_declaration( - SPAN, - None, - self.ast.new_vec_single(ExportSpecifier::new( + if class_has_decorator { + self.bottom_statements.push(self.ast.module_declaration( + ModuleDeclaration::ExportNamedDeclaration( + self.ast.export_named_declaration( SPAN, - ModuleExportName::Identifier(IdentifierName::new( + None, + self.ast.new_vec_single(ExportSpecifier::new( SPAN, - class_name.clone().unwrap(), + ModuleExportName::Identifier(IdentifierName::new( + SPAN, + class_name.clone().unwrap(), + )), + ModuleExportName::Identifier(IdentifierName::new( + SPAN, + "default".into(), + )), )), - ModuleExportName::Identifier(IdentifierName::new( - SPAN, - Atom::from("default"), - )), - )), - None, - ImportOrExportKind::Value, + None, + ImportOrExportKind::Value, + ), ), - ), - )); + )); + } Some(Statement::Declaration(self.transform_class(class, class_name))) } else { @@ -185,10 +216,10 @@ impl<'a> Decorators<'a> { pub fn transform_declaration(&mut self, decl: &mut Declaration<'a>) { let new_decl = match decl { Declaration::ClassDeclaration(class) => { - if class.decorators.is_empty() { - None - } else { + if Self::can_transform(class) { Some(self.transform_class(class, None)) + } else { + None } } _ => None, @@ -198,6 +229,10 @@ impl<'a> Decorators<'a> { } } + pub fn can_transform(class: &Class<'a>) -> bool { + !class.decorators.is_empty() || class.body.body.iter().any(ClassElement::has_decorator) + } + /// transform version: 2023-05 pub fn transform_class( &mut self, @@ -207,37 +242,40 @@ impl<'a> Decorators<'a> { if self.options.version.is_legacy() { return self.transform_class_legacy(class, class_name); } - let class_name = class_name.unwrap_or_else(|| self.get_unique_name(&"class".into())); - let class_decs_name = self.get_unique_name(&"classDecs".into()); - let init_class_name = self.get_unique_name(&"initClass".into()); + let has_decorator = !class.decorators.is_empty(); + let has_member_decorator = class.body.body.iter().any(ClassElement::has_decorator); - { - // insert var _initClass, _classDecs; - let mut declarations = self.ast.new_vec_with_capacity(2); - declarations.push(self.get_variable_declarator(&init_class_name)); - declarations.push(self.get_variable_declarator(&class_decs_name)); - let variable_declaration = self.ast.variable_declaration( - SPAN, - VariableDeclarationKind::Var, - declarations, - Modifiers::empty(), - ); + let mut declarations = self.ast.new_vec(); - self.top_statements.push(Statement::Declaration(Declaration::VariableDeclaration( - variable_declaration, - ))); - } + let mut c_elements = self.ast.new_vec(); + let mut e_elements = self.ast.new_vec(); - { - // insert _classDecs = decorators; - let left = self.ast.simple_assignment_target_identifier(IdentifierReference::new( - SPAN, - class_decs_name.clone(), - )); + // insert member decorators + let mut member_decorators_vec = self.ast.new_vec(); + let mut class_decorators_argument = + Argument::Expression(self.ast.array_expression(SPAN, self.ast.new_vec(), None)); - let right = - self.ast.array_expression( + if has_decorator { + let class_name = class_name.unwrap_or_else(|| self.get_unique_name(&"class".into())); + + let class_decs_name = self.get_unique_name(&"classDecs".into()); + let init_class_name = self.get_unique_name(&"initClass".into()); + + { + // insert var _initClass, _classDecs; + declarations.push(self.get_variable_declarator(init_class_name.clone())); + declarations.push(self.get_variable_declarator(class_decs_name.clone())); + } + + { + // insert _classDecs = decorators; + let left = self.ast.simple_assignment_target_identifier(IdentifierReference::new( + SPAN, + class_decs_name.clone(), + )); + + let right = self.ast.array_expression( SPAN, { let mut elements = self.ast.new_vec(); @@ -248,26 +286,174 @@ impl<'a> Decorators<'a> { }, None, ); - let assign_class_decs = self.ast.expression_statement( - SPAN, - self.ast.assignment_expression(SPAN, AssignmentOperator::Assign, left, right), - ); - self.top_statements.push(assign_class_decs); - }; + let assign_class_decs = self.ast.expression_statement( + SPAN, + self.ast.assignment_expression(SPAN, AssignmentOperator::Assign, left, right), + ); + self.top_statements.push(assign_class_decs); + }; + + { + // insert let _className + let declarations = + self.ast.new_vec_single(self.get_variable_declarator(class_name.clone())); + let variable_declaration = self.ast.variable_declaration( + SPAN, + VariableDeclarationKind::Let, + declarations, + Modifiers::empty(), + ); + self.top_statements.push(Statement::Declaration(Declaration::VariableDeclaration( + variable_declaration, + ))); + } + + c_elements.push(self.get_assignment_target_maybe_default(class_name)); + c_elements.push(self.get_assignment_target_maybe_default(init_class_name.clone())); + + class_decorators_argument = + Argument::Expression(self.ast.identifier_reference_expression( + IdentifierReference::new(SPAN, class_decs_name), + )); + + { + // call _initClass + let callee = self.ast.identifier_reference_expression(IdentifierReference::new( + SPAN, + init_class_name, + )); + let call_expr = + self.ast.call_expression(SPAN, callee, self.ast.new_vec(), false, None); + let statements = + self.ast.new_vec_single(self.ast.expression_statement(SPAN, call_expr)); + let static_block = self.ast.static_block(SPAN, statements); + class.body.body.insert(0, static_block); + } + } else if has_member_decorator { + let elements: std::vec::Vec<_> = { + class + .body + .body + .iter_mut() + .enumerate() + .filter_map(|(index, element)| match element { + ClassElement::MethodDefinition(def) => { + if def.decorators.is_empty() { + None + } else { + Some((index, def.key.name(), def.computed, &def.decorators)) + } + } + ClassElement::PropertyDefinition(def) => { + if def.decorators.is_empty() { + None + } else { + Some((index, def.key.name(), def.computed, &def.decorators)) + } + } + _ => None, + }) + .collect() + }; + + let mut replace_elements = HashMap::new(); + + for (index, name, computed, decorators) in elements { + let init_name = self.get_unique_name(&if computed { + "init_computedKey".into() + } else { + format!("init_{}", name.clone().unwrap()).into() + }); + decorators.iter().for_each(|decorator| { + let dec_name = self.get_unique_name(&"dec".into()); + declarations.push(self.get_variable_declarator(dec_name.clone())); + + let left = self.ast.simple_assignment_target_identifier( + IdentifierReference::new(SPAN, dec_name.clone()), + ); + let right = self.ast.copy(&decorator.expression); + let dec_expr = self.ast.assignment_expression( + SPAN, + AssignmentOperator::Assign, + left, + right, + ); + e_elements.push(self.get_assignment_target_maybe_default(init_name.clone())); + let mut decorator_elements = self.ast.new_vec_with_capacity(2); + decorator_elements.push(ArrayExpressionElement::Expression( + self.ast.identifier_reference_expression(IdentifierReference::new( + SPAN, dec_name, + )), + )); + decorator_elements.push(ArrayExpressionElement::Expression( + self.ast.literal_number_expression(NumberLiteral::new( + SPAN, + 0f64, + "0", + oxc_syntax::NumberBase::Decimal, + )), + )); + if let Some(name) = name.clone() { + decorator_elements.push(ArrayExpressionElement::Expression( + self.ast.literal_string_expression(StringLiteral::new(SPAN, name)), + )); + } + member_decorators_vec.push(ArrayExpressionElement::Expression( + self.ast.array_expression(SPAN, decorator_elements, None), + )); + + self.top_statements.push(self.ast.expression_statement(SPAN, dec_expr)); + }); + + declarations.push(self.get_variable_declarator(init_name.clone())); + + if let Some(name) = name.clone() { + replace_elements.insert( + index, + self.ast.class_property( + SPAN, + self.ast + .property_key_identifier(IdentifierName::new(SPAN, name.clone())), + Some(self.ast.call_expression( + SPAN, + self.ast.identifier_reference_expression(IdentifierReference::new( + SPAN, init_name, + )), + self.ast.new_vec_single(Argument::Expression( + self.ast.this_expression(SPAN), + )), + false, + None, + )), + false, + false, + self.ast.new_vec(), + ), + ); + } + } + + // replace the element with `name = init_name(this)` + for (index, element) in class.body.body.iter_mut().enumerate() { + if let Some(new_element) = replace_elements.remove(&index) { + *element = new_element; + } + } + } { - // insert let _className - - let declarations = self.ast.new_vec_single(self.get_variable_declarator(&class_name)); + // insert all variable_declarator in same variable_declaration let variable_declaration = self.ast.variable_declaration( SPAN, - VariableDeclarationKind::Let, + VariableDeclarationKind::Var, declarations, Modifiers::empty(), ); - self.top_statements.push(Statement::Declaration(Declaration::VariableDeclaration( - variable_declaration, - ))); + + self.top_statements.insert( + 0, + Statement::Declaration(Declaration::VariableDeclaration(variable_declaration)), + ); } { @@ -281,39 +467,33 @@ impl<'a> Decorators<'a> { IdentifierName::new(SPAN, "applyDecs2305".into()), false, ); - let mut arguments = self.ast.new_vec(); - arguments.push(Argument::Expression(self.ast.this_expression(SPAN))); - let decs = self.ast.new_vec(); - arguments.push(Argument::Expression(self.ast.array_expression(SPAN, decs, None))); - arguments.push(Argument::Expression( - self.ast.identifier_reference_expression(IdentifierReference::new( - SPAN, - class_decs_name, - )), - )); - let object = self.ast.call_expression(SPAN, callee, arguments, false, None); - let call_expr = self.ast.static_member_expression( - SPAN, - object, - IdentifierName::new(SPAN, "c".into()), - false, - ); - let mut elements = self.ast.new_vec(); - elements.push(Some(AssignmentTargetMaybeDefault::AssignmentTarget( - self.ast.simple_assignment_target_identifier(IdentifierReference::new( - SPAN, class_name, - )), + let mut arguments = + self.ast.new_vec_single(Argument::Expression(self.ast.this_expression(SPAN))); + arguments.push(Argument::Expression(self.ast.array_expression( + SPAN, + member_decorators_vec, + None, ))); - elements.push(Some(AssignmentTargetMaybeDefault::AssignmentTarget( - self.ast.simple_assignment_target_identifier(IdentifierReference::new( + arguments.push(class_decorators_argument); + + let mut call_expr = self.ast.call_expression(SPAN, callee, arguments, false, None); + + if has_decorator && has_decorator == has_member_decorator { + // TODO: support this case + } else if has_decorator || has_member_decorator { + call_expr = self.ast.static_member_expression( SPAN, - init_class_name.clone(), - )), - ))); - let left = self - .ast - .array_assignment_target(ArrayAssignmentTarget::new_with_elements(SPAN, elements)); + call_expr, + IdentifierName::new(SPAN, if has_decorator { "c".into() } else { "e".into() }), + false, + ); + } + + let left = self.ast.array_assignment_target(ArrayAssignmentTarget::new_with_elements( + SPAN, + if c_elements.is_empty() { e_elements } else { c_elements }, + )); let new_expr = self.ast.assignment_expression(SPAN, AssignmentOperator::Assign, left, call_expr); @@ -323,18 +503,6 @@ impl<'a> Decorators<'a> { class.body.body.insert(0, static_block); } - { - // call _initClass - let callee = self - .ast - .identifier_reference_expression(IdentifierReference::new(SPAN, init_class_name)); - let call_expr = self.ast.call_expression(SPAN, callee, self.ast.new_vec(), false, None); - let statements = - self.ast.new_vec_single(self.ast.expression_statement(SPAN, call_expr)); - let static_block = self.ast.static_block(SPAN, statements); - class.body.body.insert(1, static_block); - } - Declaration::ClassDeclaration(self.ast.copy(class)) } @@ -361,7 +529,7 @@ impl<'a> Decorators<'a> { let decl = self.ast.variable_declaration( SPAN, VariableDeclarationKind::Var, - self.ast.new_vec_single(self.get_variable_declarator(&class_identifier_name)), + self.ast.new_vec_single(self.get_variable_declarator(class_identifier_name)), Modifiers::empty(), ); self.top_statements diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index c787b2bb8..684c25124 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 329/1369 +Passed: 330/1369 # All Passed: * babel-plugin-transform-numeric-separator @@ -911,7 +911,7 @@ Passed: 329/1369 * spread-transform/transform-to-babel-extend/input.js * spread-transform/transform-to-object-assign/input.js -# babel-plugin-proposal-decorators (5/190) +# babel-plugin-proposal-decorators (6/190) * 2018-09-transformation/async-generator-method/input.js * 2018-09-transformation/class-decorators-yield-await/input.js * 2021-12-accessors/context-name/input.js @@ -1019,7 +1019,6 @@ Passed: 329/1369 * 2023-05-duplicated-keys--to-es2015/method-and-field/input.js * 2023-05-duplicated-keys--to-es2015/methods-with-same-key/input.js * 2023-05-exported/default-named/input.mjs -* 2023-05-exported/member-decorator/input.mjs * 2023-05-fields/context-name/input.js * 2023-05-fields/private/input.js * 2023-05-fields/public/input.js