From 7f89bfea0d808f701c5328decedb42b068db4db7 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Wed, 24 Jan 2024 11:19:29 +0800 Subject: [PATCH] feat(transformer/decorators): support version 2023-05 (#2152) --- crates/oxc_ast/src/ast/js.rs | 9 + crates/oxc_ast/src/ast_builder.rs | 9 + .../src/proposals/decorators.rs | 250 +++++++++++++++--- tasks/transform_conformance/babel.snap.md | 5 +- 4 files changed, 239 insertions(+), 34 deletions(-) diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 044cbcad6..06fdc48e4 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -897,6 +897,15 @@ pub struct ArrayAssignmentTarget<'a> { pub trailing_comma: Option, } +impl<'a> ArrayAssignmentTarget<'a> { + pub fn new_with_elements( + span: Span, + elements: Vec<'a, Option>>, + ) -> 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> { diff --git a/crates/oxc_ast/src/ast_builder.rs b/crates/oxc_ast/src/ast_builder.rs index 4cd70d834..70c27a840 100644 --- a/crates/oxc_ast/src/ast_builder.rs +++ b/crates/oxc_ast/src/ast_builder.rs @@ -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, diff --git a/crates/oxc_transformer/src/proposals/decorators.rs b/crates/oxc_transformer/src/proposals/decorators.rs index 53303496b..eff76ed49 100644 --- a/crates/oxc_transformer/src/proposals/decorators.rs +++ b/crates/oxc_transformer/src/proposals/decorators.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::{collections::HashMap, rc::Rc}; use oxc_allocator::{Box, Vec}; use oxc_ast::{ast::*, AstBuilder}; @@ -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, } #[derive(Debug, Clone, Copy, Default, Deserialize)] @@ -37,7 +37,7 @@ enum Version { Legacy, #[serde(rename = "2023-05")] #[default] - Year2023May, + Year202305, } impl Version { fn is_legacy(self) -> bool { @@ -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>) { @@ -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( @@ -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, @@ -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; } } } @@ -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) { + 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, ) -> 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 diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index cb25d6a0b..700d6c166 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 326/1369 +Passed: 327/1369 # All Passed: * babel-plugin-transform-numeric-separator @@ -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 @@ -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