mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
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:
parent
8e3e4043bc
commit
308b780ff6
4 changed files with 156 additions and 41 deletions
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue