Skip to content

Commit

Permalink
feat(transformer/decorators): handling the coexistence of class decor…
Browse files Browse the repository at this point in the history
…ators 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">
  • Loading branch information
Dunqing committed Mar 7, 2024
1 parent 8e3e404 commit 308b780
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 41 deletions.
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 @@ -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))
});

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 @@ -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(
Expand All @@ -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;

Expand Down Expand Up @@ -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,
Expand All @@ -718,17 +770,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

0 comments on commit 308b780

Please sign in to comment.