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): support version 2023-05 #2152

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,15 @@ pub struct ArrayAssignmentTarget<'a> {
pub trailing_comma: Option<Span>,
}

impl<'a> ArrayAssignmentTarget<'a> {
pub fn new_with_elements(
span: Span,
elements: Vec<'a, Option<AssignmentTargetMaybeDefault<'a>>>,
) -> Self {
Self { span, elements, rest: None, trailing_comma: None }
}
}

#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
pub struct ObjectAssignmentTarget<'a> {
Expand Down
9 changes: 9 additions & 0 deletions crates/oxc_ast/src/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,15 @@ impl<'a> AstBuilder<'a> {
}))
}

pub fn array_assignment_target(
&self,
array: ArrayAssignmentTarget<'a>,
) -> AssignmentTarget<'a> {
AssignmentTarget::AssignmentTargetPattern(AssignmentTargetPattern::ArrayAssignmentTarget(
self.alloc(array),
))
}

pub fn simple_assignment_target_identifier(
&self,
ident: IdentifierReference,
Expand Down
250 changes: 219 additions & 31 deletions crates/oxc_transformer/src/proposals/decorators.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::rc::Rc;
use std::{collections::HashMap, rc::Rc};

use oxc_allocator::{Box, Vec};
use oxc_ast::{ast::*, AstBuilder};
Expand All @@ -22,7 +22,7 @@ pub struct Decorators<'a> {
top_statements: Vec<'a, Statement<'a>>,
// Insert to the bottom of the program
bottom_statements: Vec<'a, Statement<'a>>,
class_name_uid: u32,
uid_map: HashMap<Atom, u32>,
}

#[derive(Debug, Clone, Copy, Default, Deserialize)]
Expand All @@ -37,7 +37,7 @@ enum Version {
Legacy,
#[serde(rename = "2023-05")]
#[default]
Year2023May,
Year202305,
}
impl Version {
fn is_legacy(self) -> bool {
Expand All @@ -59,16 +59,27 @@ impl<'a> Decorators<'a> {
options,
top_statements,
bottom_statements,
class_name_uid: 0,
uid_map: HashMap::new(),
})
}

pub fn get_class_name(&mut self) -> Atom {
self.class_name_uid += 1;
if self.class_name_uid == 1 {
return "_class".into();
}
Atom::from(format!("_class{}", self.class_name_uid))
pub fn get_variable_declarator(&self, name: &str) -> VariableDeclarator<'a> {
self.ast.variable_declarator(
SPAN,
VariableDeclarationKind::Var,
BindingPattern::new_with_kind(
self.ast.binding_pattern_identifier(BindingIdentifier::new(SPAN, name.into())),
),
None,
false,
)
}

// TODO: use generate_uid of scope to generate unique name
pub fn get_unique_name(&mut self, name: &Atom) -> Atom {
let uid = self.uid_map.entry(name.clone()).or_insert(0);
*uid += 1;
Atom::from(format!("_{name}{}", if *uid == 1 { String::new() } else { uid.to_string() }))
}

pub fn transform_program(&mut self, program: &mut Program<'a>) {
Expand All @@ -78,28 +89,65 @@ impl<'a> Decorators<'a> {

pub fn transform_statement(&mut self, stmt: &mut Statement<'a>) {
if let Statement::ModuleDeclaration(decl) = stmt {
match &mut **decl {
let new_stmt = match &mut **decl {
ModuleDeclaration::ExportNamedDeclaration(export) => {
// remove export
export.declaration.as_mut().map_or_else(
|| (),
|| None,
|declaration| {
self.transform_declaration(declaration);
if let Declaration::ClassDeclaration(class) = declaration {
if class.decorators.is_empty() {
return None;
}
let class_name = class
.id
.clone()
.map(|id| self.get_unique_name(&id.name))
.or_else(|| Some(self.get_unique_name(&"class".into())));

self.transform_class(class, class_name.clone());

self.bottom_statements.push(self.ast.module_declaration(
ModuleDeclaration::ExportNamedDeclaration(
self.ast.export_named_declaration(
SPAN,
None,
self.ast.new_vec_single(ExportSpecifier::new(
SPAN,
ModuleExportName::Identifier(IdentifierName::new(
SPAN,
class_name.unwrap(),
)),
ModuleExportName::Identifier(IdentifierName::new(
SPAN,
class.id.clone().unwrap().name,
)),
)),
None,
ImportOrExportKind::Value,
),
),
));

return Some(Statement::Declaration(self.ast.copy(declaration)));
}
None
},
);
)
}
ModuleDeclaration::ExportDefaultDeclaration(export) => {
if let ExportDefaultDeclarationKind::ClassDeclaration(class) =
&mut export.declaration
{
if class.decorators.is_empty() {
return;
}
let class_name = class
.id
.clone()
.map(|id| id.name)
.or_else(|| Some(self.get_class_name()));
.or_else(|| Some(self.get_unique_name(&"class".into())));

*stmt = Statement::Declaration(
self.transform_class_legacy(class, class_name.clone()),
);
self.bottom_statements.push(self.ast.module_declaration(
ModuleDeclaration::ExportNamedDeclaration(
self.ast.export_named_declaration(
Expand All @@ -109,7 +157,7 @@ impl<'a> Decorators<'a> {
SPAN,
ModuleExportName::Identifier(IdentifierName::new(
SPAN,
class_name.unwrap(),
class_name.clone().unwrap(),
)),
ModuleExportName::Identifier(IdentifierName::new(
SPAN,
Expand All @@ -121,9 +169,16 @@ impl<'a> Decorators<'a> {
),
),
));

Some(Statement::Declaration(self.transform_class_legacy(class, class_name)))
} else {
None
}
}
_ => {}
_ => None,
};
if let Some(new_stmt) = new_stmt {
*stmt = new_stmt;
}
}
}
Expand All @@ -143,34 +198,167 @@ impl<'a> Decorators<'a> {
}
}

/// transform version: 2023-05
pub fn transform_class(&mut self, class: &mut Class<'a>, class_name: Option<Atom>) {
let class_name = class_name.unwrap_or_else(|| self.get_unique_name(&"class".into()));

let class_decs_name = self.get_unique_name(&"classDecs".into());
let init_class_name = self.get_unique_name(&"initClass".into());

{
// insert var _initClass, _classDecs;
let mut declarations = self.ast.new_vec_with_capacity(2);
declarations.push(self.get_variable_declarator(&init_class_name));
declarations.push(self.get_variable_declarator(&class_decs_name));
let variable_declaration = self.ast.variable_declaration(
SPAN,
VariableDeclarationKind::Var,
declarations,
Modifiers::empty(),
);

self.top_statements.push(Statement::Declaration(Declaration::VariableDeclaration(
variable_declaration,
)));
}

{
// insert _classDecs = decorators;
let left = AssignmentTarget::SimpleAssignmentTarget(
self.ast.simple_assignment_target_identifier(IdentifierReference::new(
SPAN,
class_decs_name.clone(),
)),
);

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 let _className

let declarations = self.ast.new_vec_single(self.get_variable_declarator(&class_name));
let variable_declaration = self.ast.variable_declaration(
SPAN,
VariableDeclarationKind::Let,
declarations,
Modifiers::empty(),
);
self.top_statements.push(Statement::Declaration(Declaration::VariableDeclaration(
variable_declaration,
)));
}

{
// call babelHelpers.applyDecs2305
let callee = self.ast.static_member_expression(
SPAN,
self.ast.identifier_reference_expression(IdentifierReference::new(
SPAN,
"babelHelpers".into(),
)),
IdentifierName::new(SPAN, "applyDecs2305".into()),
false,
);
let mut arguments = self.ast.new_vec();
arguments.push(Argument::Expression(self.ast.this_expression(SPAN)));
let decs = self.ast.new_vec();
arguments.push(Argument::Expression(self.ast.array_expression(SPAN, decs, None)));
arguments.push(Argument::Expression(
self.ast.identifier_reference_expression(IdentifierReference::new(
SPAN,
class_decs_name,
)),
));
let object = self.ast.call_expression(SPAN, callee, arguments, false, None);
let call_expr = self.ast.static_member_expression(
SPAN,
object,
IdentifierName::new(SPAN, "c".into()),
false,
);

let mut elements = self.ast.new_vec();
elements.push(Some(AssignmentTargetMaybeDefault::AssignmentTarget(
AssignmentTarget::SimpleAssignmentTarget(
self.ast.simple_assignment_target_identifier(IdentifierReference::new(
SPAN, class_name,
)),
),
)));
elements.push(Some(AssignmentTargetMaybeDefault::AssignmentTarget(
AssignmentTarget::SimpleAssignmentTarget(
self.ast.simple_assignment_target_identifier(IdentifierReference::new(
SPAN,
init_class_name.clone(),
)),
),
)));
let left = self
.ast
.array_assignment_target(ArrayAssignmentTarget::new_with_elements(SPAN, 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));
let static_block = self.ast.static_block(SPAN, statements);
class.body.body.insert(0, static_block);
}

{
// call _initClass
let callee = self
.ast
.identifier_reference_expression(IdentifierReference::new(SPAN, init_class_name));
let call_expr = self.ast.call_expression(SPAN, callee, self.ast.new_vec(), false, None);
let statements =
self.ast.new_vec_single(self.ast.expression_statement(SPAN, call_expr));
let static_block = self.ast.static_block(SPAN, statements);
class.body.body.insert(1, static_block);
}
}

/// transform version: legacy
pub fn transform_class_legacy(
&mut self,
class: &mut Box<'a, Class<'a>>,
class_name: Option<Atom>,
) -> Declaration<'a> {
let class_binding_identifier = &class.id.clone().unwrap_or_else(|| {
BindingIdentifier::new(SPAN, class_name.unwrap_or_else(|| self.get_class_name()))
BindingIdentifier::new(
SPAN,
class_name.unwrap_or_else(|| self.get_unique_name(&"class".into())),
)
});
let class_name = BindingPattern::new_with_kind(
self.ast.binding_pattern_identifier(self.ast.copy(class_binding_identifier)),
);

let init = {
let class_identifier_name: Atom = self.get_class_name();
let class_identifier_name: Atom = self.get_unique_name(&"class".into());
let class_identifier = IdentifierReference::new(SPAN, class_identifier_name.clone());

let decl = self.ast.variable_declaration(
SPAN,
VariableDeclarationKind::Var,
self.ast.new_vec_single(self.ast.variable_declarator(
SPAN,
VariableDeclarationKind::Var,
BindingPattern::new_with_kind(self.ast.binding_pattern_identifier(
BindingIdentifier::new(SPAN, class_identifier_name),
)),
None,
false,
)),
self.ast.new_vec_single(self.get_variable_declarator(&class_identifier_name)),
Modifiers::empty(),
);
self.top_statements
Expand Down
5 changes: 2 additions & 3 deletions tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Passed: 326/1369
Passed: 327/1369

# All Passed:
* babel-plugin-transform-numeric-separator
Expand Down Expand Up @@ -911,7 +911,7 @@ Passed: 326/1369
* spread-transform/transform-to-babel-extend/input.js
* spread-transform/transform-to-object-assign/input.js

# babel-plugin-proposal-decorators (2/190)
# babel-plugin-proposal-decorators (3/190)
* 2018-09-transformation/async-generator-method/input.js
* 2018-09-transformation/class-decorators-yield-await/input.js
* 2021-12-accessors/context-name/input.js
Expand Down Expand Up @@ -1021,7 +1021,6 @@ Passed: 326/1369
* 2023-05-exported/default-anonymous/input.mjs
* 2023-05-exported/default-named/input.mjs
* 2023-05-exported/member-decorator/input.mjs
* 2023-05-exported/named/input.mjs
* 2023-05-fields/context-name/input.js
* 2023-05-fields/private/input.js
* 2023-05-fields/public/input.js
Expand Down