Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/oxc_ast/src/ast/js.rs
Expand Up @@ -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()
}
Expand Down
21 changes: 21 additions & 0 deletions crates/oxc_ast/src/ast/ts.rs
Expand Up @@ -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))]
Expand Down
29 changes: 29 additions & 0 deletions crates/oxc_ast/src/ast_builder.rs
Expand Up @@ -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>,
Expand Down
140 changes: 99 additions & 41 deletions crates/oxc_transformer/src/proposals/decorators.rs
Expand Up @@ -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>>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -348,40 +348,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))
});

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");

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);
// 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)),
));
}
};

{
Expand All @@ -402,11 +435,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(
Expand All @@ -420,7 +448,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;

Expand Down Expand Up @@ -705,7 +735,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,
Expand All @@ -716,17 +768,23 @@ impl<'a> Decorators<'a> {
),
false,
);
}

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 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 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
Expand Down