feat(transformer/decorators): support transform member decorators (#2171)

This commit is contained in:
Dunqing 2024-01-26 10:14:19 +08:00 committed by GitHub
parent fc1592bc97
commit e5719e9b4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 330 additions and 139 deletions

View file

@ -473,6 +473,14 @@ impl<'a> PropertyKey<'a> {
} }
} }
pub fn name(&self) -> Option<Atom> {
if self.is_private_identifier() {
self.private_name()
} else {
self.static_name()
}
}
pub fn is_specific_id(&self, name: &str) -> bool { pub fn is_specific_id(&self, name: &str) -> bool {
match self { match self {
PropertyKey::Identifier(ident) => ident.name == name, PropertyKey::Identifier(ident) => ident.name == name,
@ -1901,6 +1909,22 @@ impl<'a> ClassElement<'a> {
_ => false, _ => 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)] #[derive(Debug, Hash)]

View file

@ -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( self.ast.variable_declarator(
SPAN, SPAN,
VariableDeclarationKind::Var, VariableDeclarationKind::Var,
BindingPattern::new_with_kind( BindingPattern::new_with_kind(
self.ast.binding_pattern_identifier(BindingIdentifier::new(SPAN, name.into())), self.ast.binding_pattern_identifier(BindingIdentifier::new(SPAN, name)),
), ),
None, None,
false, false,
) )
} }
#[allow(clippy::unnecessary_wraps)]
pub fn get_assignment_target_maybe_default(
&self,
name: Atom,
) -> Option<AssignmentTargetMaybeDefault<'a>> {
Some(AssignmentTargetMaybeDefault::AssignmentTarget(
self.ast.simple_assignment_target_identifier(IdentifierReference::new(SPAN, name)),
))
}
// TODO: use generate_uid of scope to generate unique name // TODO: use generate_uid of scope to generate unique name
pub fn get_unique_name(&mut self, name: &Atom) -> Atom { pub fn get_unique_name(&mut self, name: &Atom) -> Atom {
let uid = self.uid_map.entry(name.clone()).or_insert(0); let uid = self.uid_map.entry(name.clone()).or_insert(0);
@ -96,40 +106,54 @@ impl<'a> Decorators<'a> {
|| None, || None,
|declaration| { |declaration| {
if let Declaration::ClassDeclaration(class) = declaration { if let Declaration::ClassDeclaration(class) = declaration {
if class.decorators.is_empty() { if !Self::can_transform(class) {
return None; return None;
} }
let class_name = class let has_decorator = !class.decorators.is_empty();
.id let class_name = if has_decorator {
.clone() class
.map(|id| self.get_unique_name(&id.name)) .id
.or_else(|| Some(self.get_unique_name(&"class".into()))); .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( if has_decorator {
ModuleDeclaration::ExportNamedDeclaration( self.bottom_statements.push(self.ast.module_declaration(
self.ast.export_named_declaration( ModuleDeclaration::ExportNamedDeclaration(
SPAN, self.ast.export_named_declaration(
None,
self.ast.new_vec_single(ExportSpecifier::new(
SPAN, SPAN,
ModuleExportName::Identifier(IdentifierName::new( None,
self.ast.new_vec_single(ExportSpecifier::new(
SPAN, 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( None,
SPAN, ImportOrExportKind::Value,
class.id.clone().unwrap().name, ),
)),
)),
None,
ImportOrExportKind::Value,
), ),
), ));
)); }
return Some(Statement::Declaration( let new_declaration = self.transform_class(class, class_name);
self.transform_class(class, class_name), if has_decorator {
)); return Some(Statement::Declaration(new_declaration));
}
*declaration = new_declaration;
return None;
} }
None None
}, },
@ -139,36 +163,43 @@ impl<'a> Decorators<'a> {
if let ExportDefaultDeclarationKind::ClassDeclaration(class) = if let ExportDefaultDeclarationKind::ClassDeclaration(class) =
&mut export.declaration &mut export.declaration
{ {
if class.decorators.is_empty() { if !Self::can_transform(class) {
return; return;
} }
let class_name = class let class_has_decorator = !class.decorators.is_empty();
.id let class_name = if class_has_decorator {
.clone() class
.map(|id| self.get_unique_name(&id.name)) .id
.or_else(|| Some(self.get_unique_name(&"class".into()))); .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( if class_has_decorator {
ModuleDeclaration::ExportNamedDeclaration( self.bottom_statements.push(self.ast.module_declaration(
self.ast.export_named_declaration( ModuleDeclaration::ExportNamedDeclaration(
SPAN, self.ast.export_named_declaration(
None,
self.ast.new_vec_single(ExportSpecifier::new(
SPAN, SPAN,
ModuleExportName::Identifier(IdentifierName::new( None,
self.ast.new_vec_single(ExportSpecifier::new(
SPAN, 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( None,
SPAN, ImportOrExportKind::Value,
Atom::from("default"), ),
)),
)),
None,
ImportOrExportKind::Value,
), ),
), ));
)); }
Some(Statement::Declaration(self.transform_class(class, class_name))) Some(Statement::Declaration(self.transform_class(class, class_name)))
} else { } else {
@ -185,10 +216,10 @@ impl<'a> Decorators<'a> {
pub fn transform_declaration(&mut self, decl: &mut Declaration<'a>) { pub fn transform_declaration(&mut self, decl: &mut Declaration<'a>) {
let new_decl = match decl { let new_decl = match decl {
Declaration::ClassDeclaration(class) => { Declaration::ClassDeclaration(class) => {
if class.decorators.is_empty() { if Self::can_transform(class) {
None
} else {
Some(self.transform_class(class, None)) Some(self.transform_class(class, None))
} else {
None
} }
} }
_ => 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 /// transform version: 2023-05
pub fn transform_class( pub fn transform_class(
&mut self, &mut self,
@ -207,37 +242,40 @@ impl<'a> Decorators<'a> {
if self.options.version.is_legacy() { if self.options.version.is_legacy() {
return self.transform_class_legacy(class, class_name); 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 has_decorator = !class.decorators.is_empty();
let init_class_name = self.get_unique_name(&"initClass".into()); let has_member_decorator = class.body.body.iter().any(ClassElement::has_decorator);
{ let mut declarations = self.ast.new_vec();
// 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(),
);
self.top_statements.push(Statement::Declaration(Declaration::VariableDeclaration( let mut c_elements = self.ast.new_vec();
variable_declaration, let mut e_elements = self.ast.new_vec();
)));
}
{ // insert member decorators
// insert _classDecs = decorators; let mut member_decorators_vec = self.ast.new_vec();
let left = self.ast.simple_assignment_target_identifier(IdentifierReference::new( let mut class_decorators_argument =
SPAN, Argument::Expression(self.ast.array_expression(SPAN, self.ast.new_vec(), None));
class_decs_name.clone(),
));
let right = if has_decorator {
self.ast.array_expression( 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, SPAN,
{ {
let mut elements = self.ast.new_vec(); let mut elements = self.ast.new_vec();
@ -248,26 +286,174 @@ impl<'a> Decorators<'a> {
}, },
None, None,
); );
let assign_class_decs = self.ast.expression_statement( let assign_class_decs = self.ast.expression_statement(
SPAN, SPAN,
self.ast.assignment_expression(SPAN, AssignmentOperator::Assign, left, right), self.ast.assignment_expression(SPAN, AssignmentOperator::Assign, left, right),
); );
self.top_statements.push(assign_class_decs); 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 // insert all variable_declarator in same variable_declaration
let declarations = self.ast.new_vec_single(self.get_variable_declarator(&class_name));
let variable_declaration = self.ast.variable_declaration( let variable_declaration = self.ast.variable_declaration(
SPAN, SPAN,
VariableDeclarationKind::Let, VariableDeclarationKind::Var,
declarations, declarations,
Modifiers::empty(), 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()), IdentifierName::new(SPAN, "applyDecs2305".into()),
false, 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(); let mut arguments =
elements.push(Some(AssignmentTargetMaybeDefault::AssignmentTarget( self.ast.new_vec_single(Argument::Expression(self.ast.this_expression(SPAN)));
self.ast.simple_assignment_target_identifier(IdentifierReference::new( arguments.push(Argument::Expression(self.ast.array_expression(
SPAN, class_name, SPAN,
)), member_decorators_vec,
None,
))); )));
elements.push(Some(AssignmentTargetMaybeDefault::AssignmentTarget( arguments.push(class_decorators_argument);
self.ast.simple_assignment_target_identifier(IdentifierReference::new(
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, SPAN,
init_class_name.clone(), 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, elements));
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 = let new_expr =
self.ast.assignment_expression(SPAN, AssignmentOperator::Assign, left, call_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); 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)) Declaration::ClassDeclaration(self.ast.copy(class))
} }
@ -361,7 +529,7 @@ impl<'a> Decorators<'a> {
let decl = self.ast.variable_declaration( let decl = self.ast.variable_declaration(
SPAN, SPAN,
VariableDeclarationKind::Var, 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(), Modifiers::empty(),
); );
self.top_statements self.top_statements

View file

@ -1,4 +1,4 @@
Passed: 329/1369 Passed: 330/1369
# All Passed: # All Passed:
* babel-plugin-transform-numeric-separator * babel-plugin-transform-numeric-separator
@ -911,7 +911,7 @@ Passed: 329/1369
* spread-transform/transform-to-babel-extend/input.js * spread-transform/transform-to-babel-extend/input.js
* spread-transform/transform-to-object-assign/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/async-generator-method/input.js
* 2018-09-transformation/class-decorators-yield-await/input.js * 2018-09-transformation/class-decorators-yield-await/input.js
* 2021-12-accessors/context-name/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/method-and-field/input.js
* 2023-05-duplicated-keys--to-es2015/methods-with-same-key/input.js * 2023-05-duplicated-keys--to-es2015/methods-with-same-key/input.js
* 2023-05-exported/default-named/input.mjs * 2023-05-exported/default-named/input.mjs
* 2023-05-exported/member-decorator/input.mjs
* 2023-05-fields/context-name/input.js * 2023-05-fields/context-name/input.js
* 2023-05-fields/private/input.js * 2023-05-fields/private/input.js
* 2023-05-fields/public/input.js * 2023-05-fields/public/input.js