From ba85b097e01fc3a42231ff20b29b2e7b3fba7472 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 1 Feb 2024 11:36:22 +0800 Subject: [PATCH] feat(transformer/decorators): support method decorator and is not static (#2238) --- crates/oxc_ast/src/ast_builder.rs | 19 ++++- .../src/proposals/decorators.rs | 70 ++++++++++++++++++- tasks/transform_conformance/babel.snap.md | 9 +-- 3 files changed, 89 insertions(+), 9 deletions(-) diff --git a/crates/oxc_ast/src/ast_builder.rs b/crates/oxc_ast/src/ast_builder.rs index 7f73f83f4..a4fae7a2a 100644 --- a/crates/oxc_ast/src/ast_builder.rs +++ b/crates/oxc_ast/src/ast_builder.rs @@ -9,7 +9,7 @@ use num_bigint::BigInt; use std::mem; use oxc_allocator::{Allocator, Box, String, Vec}; -use oxc_span::{Atom, GetSpan, SourceType, Span}; +use oxc_span::{Atom, GetSpan, SourceType, Span, SPAN}; use oxc_syntax::{ operator::{ AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator, @@ -879,6 +879,23 @@ impl<'a> AstBuilder<'a> { })) } + pub fn class_constructor(&self, span: Span, value: Box<'a, Function<'a>>) -> ClassElement<'a> { + ClassElement::MethodDefinition(self.alloc(MethodDefinition { + span, + key: self.property_key_expression(self.identifier_reference_expression( + IdentifierReference::new(SPAN, "constructor".into()), + )), + kind: MethodDefinitionKind::Constructor, + value, + computed: false, + r#static: false, + r#override: false, + optional: false, + accessibility: None, + decorators: self.new_vec(), + })) + } + pub fn accessor_property( &self, span: Span, diff --git a/crates/oxc_transformer/src/proposals/decorators.rs b/crates/oxc_transformer/src/proposals/decorators.rs index 0dcd8df67..7fdddae83 100644 --- a/crates/oxc_transformer/src/proposals/decorators.rs +++ b/crates/oxc_transformer/src/proposals/decorators.rs @@ -117,6 +117,19 @@ impl<'a> Decorators<'a> { Atom::from(format!("_{name}{}", if *uid == 1 { String::new() } else { uid.to_string() })) } + pub fn get_call_with_this(&self, name: Atom) -> Statement<'a> { + self.ast.expression_statement( + SPAN, + self.ast.call_expression( + SPAN, + self.ast.identifier_reference_expression(IdentifierReference::new(SPAN, name)), + self.ast.new_vec_single(Argument::Expression(self.ast.this_expression(SPAN))), + false, + None, + ), + ) + } + pub fn transform_program(&mut self, program: &mut Program<'a>) { program.body.splice(0..0, self.top_statements.drain(..)); program.body.append(&mut self.bottom_statements); @@ -378,7 +391,9 @@ impl<'a> Decorators<'a> { ast.array_expression(SPAN, decorator_elements, None) }; + let mut is_proto = false; let mut is_static = false; + class.body.body.iter_mut().for_each(|element| { if !element.has_decorator() { return; @@ -387,6 +402,8 @@ impl<'a> Decorators<'a> { ClassElement::MethodDefinition(def) => { if def.r#static { is_static = def.r#static; + } else { + is_proto = true; } let name = def.key.name(); let mut flag = DecoratorFlags::get_flag_by_kind(def.kind).bits(); @@ -454,6 +471,57 @@ impl<'a> Decorators<'a> { } }); + if is_proto { + // The class has method decorator and is not static + let name = self.get_unique_name(&"initProto".into()); + e_elements.push(self.get_assignment_target_maybe_default(name.clone())); + declarations.push(self.get_variable_declarator(name.clone())); + + // constructor() { _initProto(this) } + if let Some(constructor_element) = class.body.body.iter_mut().find( + |element| matches!(element, ClassElement::MethodDefinition(def) if def.kind.is_constructor()), + ) { + if let ClassElement::MethodDefinition(def) = constructor_element { + if let Some(body) = &mut def.value.body { + body.statements.insert(0, self.get_call_with_this(name)); + } + } else { + unreachable!(); + }; + } else { + // if the class has no constructor, insert a empty constructor and call initProto + class.body.body.insert( + 0, + self.ast.class_constructor( + SPAN, + self.ast.function( + FunctionType::FunctionExpression, + SPAN, + None, + false, + false, + false, + None, + self.ast.formal_parameters( + SPAN, + FormalParameterKind::FormalParameter, + self.ast.new_vec(), + None, + ), + Some(self.ast.function_body( + SPAN, + self.ast.new_vec(), + self.ast.new_vec_single(self.get_call_with_this(name)), + )), + None, + None, + Modifiers::empty(), + ), + ), + ); + } + } + if is_static { let name = self.get_unique_name(&"initStatic".into()); e_elements.push(self.get_assignment_target_maybe_default(name.clone())); @@ -478,7 +546,7 @@ impl<'a> Decorators<'a> { } { - // call babelHelpers.applyDecs2305 + // call babelHelpers.applyDecs2305 let callee = self.ast.static_member_expression( SPAN, self.ast.identifier_reference_expression(IdentifierReference::new( diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 9d4e2bb12..81df6076f 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 335/1369 +Passed: 340/1369 # All Passed: * babel-plugin-transform-numeric-separator @@ -911,7 +911,7 @@ Passed: 335/1369 * spread-transform/transform-to-babel-extend/input.js * spread-transform/transform-to-object-assign/input.js -# babel-plugin-proposal-decorators (11/190) +# babel-plugin-proposal-decorators (16/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 @@ -1013,7 +1013,6 @@ Passed: 335/1369 * 2023-05-duplicated-keys/computed-keys-same-ast/input.js * 2023-05-duplicated-keys/computed-keys-same-value/input.js * 2023-05-duplicated-keys/method-and-field/input.js -* 2023-05-duplicated-keys/methods-with-same-key/input.js * 2023-05-duplicated-keys--to-es2015/computed-keys-same-ast/input.js * 2023-05-duplicated-keys--to-es2015/computed-keys-same-value/input.js * 2023-05-duplicated-keys--to-es2015/method-and-field/input.js @@ -1030,7 +1029,6 @@ Passed: 335/1369 * 2023-05-fields--to-es2015/static-public/input.js * 2023-05-getters/context-name/input.js * 2023-05-getters/private/input.js -* 2023-05-getters/public/input.js * 2023-05-getters/static-private/input.js * 2023-05-getters--to-es2015/context-name/input.js * 2023-05-getters--to-es2015/private/input.js @@ -1038,7 +1036,6 @@ Passed: 335/1369 * 2023-05-getters--to-es2015/static-private/input.js * 2023-05-getters--to-es2015/static-public/input.js * 2023-05-getters-and-setters/private/input.js -* 2023-05-getters-and-setters/public/input.js * 2023-05-getters-and-setters/static-private/input.js * 2023-05-getters-and-setters--to-es2015/private/input.js * 2023-05-getters-and-setters--to-es2015/public/input.js @@ -1046,7 +1043,6 @@ Passed: 335/1369 * 2023-05-getters-and-setters--to-es2015/static-public/input.js * 2023-05-methods/context-name/input.js * 2023-05-methods/private/input.js -* 2023-05-methods/public/input.js * 2023-05-methods/static-private/input.js * 2023-05-methods--to-es2015/context-name/input.js * 2023-05-methods--to-es2015/private/input.js @@ -1079,7 +1075,6 @@ Passed: 335/1369 * 2023-05-ordering--to-es2015/initializers-and-static-blocks/input.js * 2023-05-setters/context-name/input.js * 2023-05-setters/private/input.js -* 2023-05-setters/public/input.js * 2023-05-setters/static-private/input.js * 2023-05-setters--to-es2015/context-name/input.js * 2023-05-setters--to-es2015/private/input.js