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> {
|
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 {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.properties.is_empty() && self.rest.is_none()
|
self.properties.is_empty() && self.rest.is_none()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -947,6 +947,27 @@ pub struct Decorator<'a> {
|
||||||
pub expression: Expression<'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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
|
#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
|
||||||
#[cfg_attr(all(feature = "serde", feature = "wasm"), derive(tsify::Tsify))]
|
#[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(
|
pub fn simple_assignment_target_identifier(
|
||||||
&self,
|
&self,
|
||||||
ident: IdentifierReference<'a>,
|
ident: IdentifierReference<'a>,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use crate::{context::TransformerCtx, options::TransformOptions};
|
||||||
/// * <https://github.com/tc39/proposal-decorators>
|
/// * <https://github.com/tc39/proposal-decorators>
|
||||||
pub struct Decorators<'a> {
|
pub struct Decorators<'a> {
|
||||||
ast: Rc<AstBuilder<'a>>,
|
ast: Rc<AstBuilder<'a>>,
|
||||||
_ctx: TransformerCtx<'a>,
|
ctx: TransformerCtx<'a>,
|
||||||
options: DecoratorsOptions,
|
options: DecoratorsOptions,
|
||||||
// Insert to the top of the program
|
// Insert to the top of the program
|
||||||
top_statements: Vec<'a, Statement<'a>>,
|
top_statements: Vec<'a, Statement<'a>>,
|
||||||
|
|
@ -113,7 +113,7 @@ impl<'a> Decorators<'a> {
|
||||||
let bottom_statements = ast.new_vec();
|
let bottom_statements = ast.new_vec();
|
||||||
options.decorators.map(|options| Self {
|
options.decorators.map(|options| Self {
|
||||||
ast,
|
ast,
|
||||||
_ctx: ctx,
|
ctx,
|
||||||
options,
|
options,
|
||||||
top_statements,
|
top_statements,
|
||||||
bottom_statements,
|
bottom_statements,
|
||||||
|
|
@ -350,40 +350,73 @@ impl<'a> Decorators<'a> {
|
||||||
Argument::Expression(self.ast.array_expression(SPAN, self.ast.new_vec(), None));
|
Argument::Expression(self.ast.array_expression(SPAN, self.ast.new_vec(), None));
|
||||||
|
|
||||||
if has_decorator {
|
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");
|
let init_class_name = self.get_unique_name("initClass");
|
||||||
|
|
||||||
{
|
{
|
||||||
// insert var _initClass, _classDecs;
|
// insert var _initClass, _classDecs;
|
||||||
declarations.push(self.get_variable_declarator(&init_class_name));
|
declarations.push(self.get_variable_declarator(&init_class_name));
|
||||||
declarations.push(self.get_variable_declarator(&class_decs_name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// insert _classDecs = decorators;
|
let decorators_exists = class.decorators.iter().any(|decorator| {
|
||||||
let left = self.ast.simple_assignment_target_identifier(IdentifierReference::new(
|
decorator
|
||||||
SPAN,
|
.name()
|
||||||
self.ast.new_atom(&class_decs_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(
|
if decorators_exists {
|
||||||
SPAN,
|
let mut elements = self.ast.new_vec();
|
||||||
{
|
|
||||||
let mut elements = self.ast.new_vec();
|
elements.extend(class.decorators.drain(..).map(|decorator| {
|
||||||
elements.extend(class.decorators.drain(..).map(|d| {
|
ArrayExpressionElement::Expression(self.ast.copy(&decorator.expression))
|
||||||
ArrayExpressionElement::Expression(self.ast.copy(&d.expression))
|
}));
|
||||||
}));
|
class_decorators_argument =
|
||||||
elements
|
Argument::Expression(self.ast.array_expression(SPAN, elements, None));
|
||||||
},
|
} else {
|
||||||
None,
|
let class_decs_name = self.get_unique_name("classDecs");
|
||||||
);
|
|
||||||
let assign_class_decs = self.ast.expression_statement(
|
// insert var _classDecs;
|
||||||
SPAN,
|
declarations.push(self.get_variable_declarator(&class_decs_name));
|
||||||
self.ast.assignment_expression(SPAN, AssignmentOperator::Assign, left, right),
|
|
||||||
);
|
// insert _classDecs = decorators;
|
||||||
self.top_statements.push(assign_class_decs);
|
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(&class_name));
|
||||||
c_elements.push(self.get_assignment_target_maybe_default(&init_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
|
// call _initClass
|
||||||
let callee = self.ast.identifier_reference_expression(IdentifierReference::new(
|
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);
|
let static_block = self.ast.static_block(SPAN, statements);
|
||||||
class.body.body.insert(0, static_block);
|
class.body.body.insert(0, static_block);
|
||||||
}
|
}
|
||||||
} else if has_member_decorator {
|
}
|
||||||
|
|
||||||
|
if has_member_decorator {
|
||||||
let mut is_proto = false;
|
let mut is_proto = false;
|
||||||
let mut is_static = 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);
|
let mut call_expr = self.ast.call_expression(SPAN, callee, arguments, false, None);
|
||||||
|
|
||||||
if has_decorator && has_decorator == has_member_decorator {
|
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 {
|
} else if has_decorator || has_member_decorator {
|
||||||
call_expr = self.ast.static_member_expression(
|
call_expr = self.ast.static_member_expression(
|
||||||
SPAN,
|
SPAN,
|
||||||
|
|
@ -718,17 +770,23 @@ impl<'a> Decorators<'a> {
|
||||||
),
|
),
|
||||||
false,
|
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();
|
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 {
|
if let Some(init_static_name) = init_static_name {
|
||||||
// call initStatic
|
// call initStatic
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue