feat(transformer/decorators): handling the coexistence of class decorators and member decorators (#2636)

No snapshots have been updated. Because our output is a bit different
from babel's
<img width="961" alt="image"
src="https://github.com/oxc-project/oxc/assets/29533304/9926766c-b1ec-46c3-8c8f-53f053b14f84">
This commit is contained in:
Dunqing 2024-03-07 16:52:30 +08:00 committed by GitHub
parent 8e3e4043bc
commit 308b780ff6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 156 additions and 41 deletions

View file

@ -1004,6 +1004,13 @@ pub struct ObjectAssignmentTarget<'a> {
}
impl<'a> ObjectAssignmentTarget<'a> {
pub fn new_with_properties(
span: Span,
properties: Vec<'a, AssignmentTargetProperty<'a>>,
) -> Self {
Self { span, properties, rest: None }
}
pub fn is_empty(&self) -> bool {
self.properties.is_empty() && self.rest.is_none()
}

View file

@ -947,6 +947,27 @@ pub struct Decorator<'a> {
pub expression: Expression<'a>,
}
impl<'a> Decorator<'a> {
/// Get the name of the decorator
/// ```ts
/// @decorator
/// @decorator.a.b
/// @decorator(xx)
/// @decorator.a.b(xx)
/// The name of the decorator is `decorator`
/// ```
pub fn name(&self) -> Option<&str> {
match &self.expression {
Expression::Identifier(ident) => Some(&ident.name),
Expression::MemberExpression(member) => member.static_property_name(),
Expression::CallExpression(call) => {
call.callee.get_member_expr().map(|member| member.static_property_name())?
}
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
#[cfg_attr(all(feature = "serde", feature = "wasm"), derive(tsify::Tsify))]

View file

@ -468,6 +468,35 @@ impl<'a> AstBuilder<'a> {
))
}
pub fn array_assignment_target_maybe_default(
&self,
array: ArrayAssignmentTarget<'a>,
) -> AssignmentTargetMaybeDefault<'a> {
AssignmentTargetMaybeDefault::AssignmentTarget(AssignmentTarget::AssignmentTargetPattern(
AssignmentTargetPattern::ArrayAssignmentTarget(self.alloc(array)),
))
}
pub fn object_assignment_target(
&self,
array: ObjectAssignmentTarget<'a>,
) -> AssignmentTarget<'a> {
AssignmentTarget::AssignmentTargetPattern(AssignmentTargetPattern::ObjectAssignmentTarget(
self.alloc(array),
))
}
pub fn assignment_target_property_property(
&self,
span: Span,
name: PropertyKey<'a>,
binding: AssignmentTargetMaybeDefault<'a>,
) -> AssignmentTargetProperty<'a> {
AssignmentTargetProperty::AssignmentTargetPropertyProperty(
self.alloc(AssignmentTargetPropertyProperty { span, name, binding }),
)
}
pub fn simple_assignment_target_identifier(
&self,
ident: IdentifierReference<'a>,

View file

@ -17,7 +17,7 @@ use crate::{context::TransformerCtx, options::TransformOptions};
/// * <https://github.com/tc39/proposal-decorators>
pub struct Decorators<'a> {
ast: Rc<AstBuilder<'a>>,
_ctx: TransformerCtx<'a>,
ctx: TransformerCtx<'a>,
options: DecoratorsOptions,
// Insert to the top of the program
top_statements: Vec<'a, Statement<'a>>,
@ -113,7 +113,7 @@ impl<'a> Decorators<'a> {
let bottom_statements = ast.new_vec();
options.decorators.map(|options| Self {
ast,
_ctx: ctx,
ctx,
options,
top_statements,
bottom_statements,
@ -350,40 +350,73 @@ impl<'a> Decorators<'a> {
Argument::Expression(self.ast.array_expression(SPAN, self.ast.new_vec(), None));
if has_decorator {
let class_name = class_name.unwrap_or_else(|| self.get_unique_name("class"));
let class_name = class_name.unwrap_or_else(|| {
self.get_unique_name(
class.id.as_ref().map_or_else(|| "class".into(), |id| id.name.clone()).as_ref(),
)
});
let class_decs_name = self.get_unique_name("classDecs");
let init_class_name = self.get_unique_name("initClass");
{
// insert var _initClass, _classDecs;
declarations.push(self.get_variable_declarator(&init_class_name));
declarations.push(self.get_variable_declarator(&class_decs_name));
}
{
// insert _classDecs = decorators;
let left = self.ast.simple_assignment_target_identifier(IdentifierReference::new(
SPAN,
self.ast.new_atom(&class_decs_name),
));
let decorators_exists = class.decorators.iter().any(|decorator| {
decorator
.name()
.is_some_and(|name| // TODO: We should use current node's scope id
self.ctx.scopes().has_binding(self.ctx.scopes().root_scope_id(), name))
});
let right = self.ast.array_expression(
SPAN,
{
let mut elements = self.ast.new_vec();
elements.extend(class.decorators.drain(..).map(|d| {
ArrayExpressionElement::Expression(self.ast.copy(&d.expression))
}));
elements
},
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);
if decorators_exists {
let mut elements = self.ast.new_vec();
elements.extend(class.decorators.drain(..).map(|decorator| {
ArrayExpressionElement::Expression(self.ast.copy(&decorator.expression))
}));
class_decorators_argument =
Argument::Expression(self.ast.array_expression(SPAN, elements, None));
} else {
let class_decs_name = self.get_unique_name("classDecs");
// insert var _classDecs;
declarations.push(self.get_variable_declarator(&class_decs_name));
// insert _classDecs = decorators;
let left = self.ast.simple_assignment_target_identifier(
IdentifierReference::new(SPAN, self.ast.new_atom(&class_decs_name)),
);
let right = self.ast.array_expression(
SPAN,
{
let mut elements = self.ast.new_vec();
elements.extend(class.decorators.drain(..).map(|d| {
ArrayExpressionElement::Expression(self.ast.copy(&d.expression))
}));
elements
},
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);
class_decorators_argument =
Argument::Expression(self.ast.identifier_reference_expression(
IdentifierReference::new(SPAN, self.ast.new_atom(&class_decs_name)),
));
}
};
{
@ -404,11 +437,6 @@ impl<'a> Decorators<'a> {
c_elements.push(self.get_assignment_target_maybe_default(&class_name));
c_elements.push(self.get_assignment_target_maybe_default(&init_class_name));
class_decorators_argument =
Argument::Expression(self.ast.identifier_reference_expression(
IdentifierReference::new(SPAN, self.ast.new_atom(&class_decs_name)),
));
{
// call _initClass
let callee = self.ast.identifier_reference_expression(IdentifierReference::new(
@ -422,7 +450,9 @@ impl<'a> Decorators<'a> {
let static_block = self.ast.static_block(SPAN, statements);
class.body.body.insert(0, static_block);
}
} else if has_member_decorator {
}
if has_member_decorator {
let mut is_proto = false;
let mut is_static = false;
@ -707,7 +737,29 @@ impl<'a> Decorators<'a> {
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
let mut properties = self.ast.new_vec_with_capacity(2);
properties.push(self.ast.assignment_target_property_property(
SPAN,
self.ast.property_key_identifier(IdentifierName::new(SPAN, "e".into())),
self.ast.array_assignment_target_maybe_default(
ArrayAssignmentTarget::new_with_elements(SPAN, e_elements),
),
));
properties.push(self.ast.assignment_target_property_property(
SPAN,
self.ast.property_key_identifier(IdentifierName::new(SPAN, "c".into())),
self.ast.array_assignment_target_maybe_default(
ArrayAssignmentTarget::new_with_elements(SPAN, c_elements),
),
));
call_expr = self.ast.assignment_expression(
SPAN,
AssignmentOperator::Assign,
self.ast.object_assignment_target(ObjectAssignmentTarget::new_with_properties(
SPAN, properties,
)),
call_expr,
);
} else if has_decorator || has_member_decorator {
call_expr = self.ast.static_member_expression(
SPAN,
@ -718,17 +770,23 @@ impl<'a> Decorators<'a> {
),
false,
);
let left =
self.ast.array_assignment_target(ArrayAssignmentTarget::new_with_elements(
SPAN,
if has_decorator { c_elements } else { e_elements },
));
call_expr = self.ast.assignment_expression(
SPAN,
AssignmentOperator::Assign,
left,
call_expr,
);
}
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);
let mut statements = self.ast.new_vec();
statements.push(self.ast.expression_statement(SPAN, new_expr));
statements.push(self.ast.expression_statement(SPAN, call_expr));
if let Some(init_static_name) = init_static_name {
// call initStatic