diff --git a/crates/swc_ecma_ast/src/decl.rs b/crates/swc_ecma_ast/src/decl.rs index eae1493f826d..bb493ca06ddf 100644 --- a/crates/swc_ecma_ast/src/decl.rs +++ b/crates/swc_ecma_ast/src/decl.rs @@ -86,6 +86,16 @@ pub struct ClassDecl { pub class: Box, } +impl Take for ClassDecl { + fn dummy() -> Self { + ClassDecl { + ident: Take::dummy(), + declare: Default::default(), + class: Take::dummy(), + } + } +} + #[ast_node("VariableDeclaration")] #[derive(Eq, Hash, EqIgnoreSpan)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] diff --git a/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs b/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs index 4926cf87e212..c21de0b2efca 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs @@ -39,6 +39,8 @@ struct Decorator202203 { extra_lets: Vec, + extra_exports: Vec, + rename_map: FxHashMap, } @@ -345,6 +347,249 @@ impl Decorator202203 { new_class_name } + + fn handle_class_decl(&mut self, c: &mut ClassDecl) -> Option { + if !c.class.decorators.is_empty() { + let decorators = self.preserve_side_effect_of_decorators(c.class.decorators.take()); + + let init_class = private_ident!("_initClass"); + + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(init_class.clone().into()), + init: None, + definite: false, + }); + + let preserved_class_name = c.ident.clone().private(); + let new_class_name = private_ident!(format!("_{}", c.ident.sym)); + + self.extra_lets.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(new_class_name.clone().into()), + init: None, + definite: false, + }); + + self.rename_map + .insert(c.ident.to_id(), new_class_name.to_id()); + + self.class_lhs.push(Some(new_class_name.clone().into())); + self.class_lhs.push(Some(init_class.clone().into())); + + self.class_decorators.extend(decorators); + + let mut body = c.class.body.take(); + + let has_static_member = body.iter().any(|m| match m { + ClassMember::Method(m) => m.is_static, + ClassMember::PrivateMethod(m) => m.is_static, + ClassMember::ClassProp(ClassProp { is_static, .. }) + | ClassMember::PrivateProp(PrivateProp { is_static, .. }) => *is_static, + ClassMember::StaticBlock(_) => true, + _ => false, + }); + + if has_static_member { + let mut last_static_block = None; + + for m in body.iter_mut() { + match m { + ClassMember::Method(method) => { + if method.is_static { + c.class.body.push(m.take()); + } + } + ClassMember::PrivateMethod(m) => { + m.is_static = false; + } + ClassMember::ClassProp(ClassProp { value, .. }) + | ClassMember::PrivateProp(PrivateProp { value, .. }) => { + if let Some(value) = value { + if let Some(last_static_block) = last_static_block.take() { + **value = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs: vec![ + Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: ArrowExpr { + span: DUMMY_SP, + params: vec![], + body: Box::new(BlockStmtOrExpr::BlockStmt( + BlockStmt { + span: DUMMY_SP, + stmts: last_static_block, + }, + )), + is_async: false, + is_generator: false, + type_params: Default::default(), + return_type: Default::default(), + } + .as_callee(), + args: vec![], + type_args: Default::default(), + })), + value.take(), + ], + }) + } + } + } + ClassMember::StaticBlock(s) => match &mut last_static_block { + None => { + last_static_block = Some(s.body.stmts.take()); + } + Some(v) => { + v.append(&mut s.body.stmts); + } + }, + _ => {} + } + } + + body.retain(|m| { + !matches!(m, ClassMember::StaticBlock(..) | ClassMember::Empty(..)) + }); + + body.visit_mut_with(self); + + self.cur_inits.splice(0..0, self.static_inits.drain(..)); + + c.visit_mut_with(self); + + // Make static members non-static + for m in body.iter_mut() { + match m { + ClassMember::Method(m) => { + m.is_static = false; + } + ClassMember::PrivateMethod(m) => { + m.is_static = false; + } + ClassMember::ClassProp(ClassProp { is_static, .. }) + | ClassMember::PrivateProp(PrivateProp { is_static, .. }) => { + *is_static = false; + } + _ => {} + } + } + + replace_ident(&mut c.class, c.ident.to_id(), &preserved_class_name); + + return Some( + NewExpr { + span: DUMMY_SP, + callee: ClassExpr { + ident: None, + class: Box::new(Class { + span: DUMMY_SP, + decorators: vec![], + body: once(ClassMember::StaticBlock(StaticBlock { + span: DUMMY_SP, + body: BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Decl(Decl::Class(ClassDecl { + ident: preserved_class_name, + declare: Default::default(), + class: c.class.take(), + }))], + }, + })) + .chain(body) + .chain(once(ClassMember::Constructor(Constructor { + span: DUMMY_SP, + key: PropName::Ident(quote_ident!("constructor")), + params: vec![], + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![SeqExpr { + span: DUMMY_SP, + exprs: once(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: Callee::Super(Super { span: DUMMY_SP }), + args: vec![new_class_name.as_arg()], + type_args: Default::default(), + }))) + .chain(last_static_block.map(|stmts| { + Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: ArrowExpr { + span: DUMMY_SP, + params: vec![], + body: Box::new(BlockStmtOrExpr::BlockStmt( + BlockStmt { + span: DUMMY_SP, + stmts, + }, + )), + is_async: false, + is_generator: false, + type_params: Default::default(), + return_type: Default::default(), + } + .as_callee(), + args: vec![], + type_args: Default::default(), + })) + })) + .chain(once(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init_class.as_callee(), + args: vec![], + type_args: Default::default(), + })))) + .collect(), + } + .into_stmt()], + }), + accessibility: Default::default(), + is_optional: Default::default(), + }))) + .collect(), + super_class: Some(Box::new(helper_expr!(identity))), + is_abstract: Default::default(), + type_params: Default::default(), + super_type_params: Default::default(), + implements: Default::default(), + }), + } + .into(), + args: Some(vec![]), + type_args: Default::default(), + } + .into_stmt(), + ); + } else { + body.visit_mut_with(self); + + c.visit_mut_with(self); + + c.ident = preserved_class_name.clone(); + replace_ident(&mut c.class, c.ident.to_id(), &preserved_class_name); + + c.class.body.extend(body); + + c.class.body.push(ClassMember::StaticBlock(StaticBlock { + span: DUMMY_SP, + body: BlockStmt { + span: DUMMY_SP, + stmts: vec![CallExpr { + span: DUMMY_SP, + callee: init_class.as_callee(), + args: vec![], + type_args: Default::default(), + } + .into_stmt()], + }, + })); + + return Some(Stmt::Decl(Decl::Class(c.take()))); + } + } + + None + } } impl VisitMut for Decorator202203 { @@ -633,6 +878,36 @@ impl VisitMut for Decorator202203 { e.visit_mut_children_with(self); } + fn visit_mut_module_item(&mut self, s: &mut ModuleItem) { + match s { + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + span, + decl: Decl::Class(c), + })) => { + let ident = c.ident.clone(); + let span = *span; + let new_stmt = self.handle_class_decl(c); + + if let Some(new_stmt) = new_stmt { + *s = ModuleItem::Stmt(new_stmt); + self.extra_exports + .push(ExportSpecifier::Named(ExportNamedSpecifier { + span, + orig: ModuleExportName::Ident(ident), + exported: None, + is_type_only: false, + })); + return; + } + + s.visit_mut_children_with(self); + } + _ => { + s.visit_mut_children_with(self); + } + } + } + fn visit_mut_module_items(&mut self, n: &mut Vec) { let old_extra_lets = self.extra_lets.take(); @@ -676,6 +951,18 @@ impl VisitMut for Decorator202203 { ); } + if !self.extra_exports.is_empty() { + new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed( + NamedExport { + span: DUMMY_SP, + specifiers: self.extra_exports.take(), + src: None, + type_only: false, + asserts: None, + }, + ))); + } + *n = new; if !self.rename_map.is_empty() { @@ -797,240 +1084,10 @@ impl VisitMut for Decorator202203 { fn visit_mut_stmt(&mut self, s: &mut Stmt) { if let Stmt::Decl(Decl::Class(c)) = s { - if !c.class.decorators.is_empty() { - let decorators = self.preserve_side_effect_of_decorators(c.class.decorators.take()); - - let init_class = private_ident!("_initClass"); - - self.extra_vars.push(VarDeclarator { - span: DUMMY_SP, - name: Pat::Ident(init_class.clone().into()), - init: None, - definite: false, - }); - - let preserved_class_name = c.ident.clone().private(); - let new_class_name = private_ident!(format!("_{}", c.ident.sym)); - - self.extra_lets.push(VarDeclarator { - span: DUMMY_SP, - name: Pat::Ident(new_class_name.clone().into()), - init: None, - definite: false, - }); - - self.rename_map - .insert(c.ident.to_id(), new_class_name.to_id()); - - self.class_lhs.push(Some(new_class_name.clone().into())); - self.class_lhs.push(Some(init_class.clone().into())); - - self.class_decorators.extend(decorators); - - let mut body = c.class.body.take(); - - let has_static_member = body.iter().any(|m| match m { - ClassMember::Method(m) => m.is_static, - ClassMember::PrivateMethod(m) => m.is_static, - ClassMember::ClassProp(ClassProp { is_static, .. }) - | ClassMember::PrivateProp(PrivateProp { is_static, .. }) => *is_static, - ClassMember::StaticBlock(_) => true, - _ => false, - }); - - if has_static_member { - let mut last_static_block = None; - - for m in body.iter_mut() { - match m { - ClassMember::Method(method) => { - if method.is_static { - c.class.body.push(m.take()); - } - } - ClassMember::PrivateMethod(m) => { - m.is_static = false; - } - ClassMember::ClassProp(ClassProp { value, .. }) - | ClassMember::PrivateProp(PrivateProp { value, .. }) => { - if let Some(value) = value { - if let Some(last_static_block) = last_static_block.take() { - **value = Expr::Seq(SeqExpr { - span: DUMMY_SP, - exprs: vec![ - Box::new(Expr::Call(CallExpr { - span: DUMMY_SP, - callee: ArrowExpr { - span: DUMMY_SP, - params: vec![], - body: Box::new(BlockStmtOrExpr::BlockStmt( - BlockStmt { - span: DUMMY_SP, - stmts: last_static_block, - }, - )), - is_async: false, - is_generator: false, - type_params: Default::default(), - return_type: Default::default(), - } - .as_callee(), - args: vec![], - type_args: Default::default(), - })), - value.take(), - ], - }) - } - } - } - ClassMember::StaticBlock(s) => match &mut last_static_block { - None => { - last_static_block = Some(s.body.stmts.take()); - } - Some(v) => { - v.append(&mut s.body.stmts); - } - }, - _ => {} - } - } - - body.retain(|m| { - !matches!(m, ClassMember::StaticBlock(..) | ClassMember::Empty(..)) - }); - - body.visit_mut_with(self); - - self.cur_inits.splice(0..0, self.static_inits.drain(..)); - - c.visit_mut_with(self); - - // Make static members non-static - for m in body.iter_mut() { - match m { - ClassMember::Method(m) => { - m.is_static = false; - } - ClassMember::PrivateMethod(m) => { - m.is_static = false; - } - ClassMember::ClassProp(ClassProp { is_static, .. }) - | ClassMember::PrivateProp(PrivateProp { is_static, .. }) => { - *is_static = false; - } - _ => {} - } - } - - replace_ident(&mut c.class, c.ident.to_id(), &preserved_class_name); - - *s = NewExpr { - span: DUMMY_SP, - callee: ClassExpr { - ident: None, - class: Box::new(Class { - span: DUMMY_SP, - decorators: vec![], - body: once(ClassMember::StaticBlock(StaticBlock { - span: DUMMY_SP, - body: BlockStmt { - span: DUMMY_SP, - stmts: vec![Stmt::Decl(Decl::Class(ClassDecl { - ident: preserved_class_name, - declare: Default::default(), - class: c.class.take(), - }))], - }, - })) - .chain(body) - .chain(once(ClassMember::Constructor(Constructor { - span: DUMMY_SP, - key: PropName::Ident(quote_ident!("constructor")), - params: vec![], - body: Some(BlockStmt { - span: DUMMY_SP, - stmts: vec![SeqExpr { - span: DUMMY_SP, - exprs: once(Box::new(Expr::Call(CallExpr { - span: DUMMY_SP, - callee: Callee::Super(Super { span: DUMMY_SP }), - args: vec![new_class_name.as_arg()], - type_args: Default::default(), - }))) - .chain(last_static_block.map(|stmts| { - Box::new(Expr::Call(CallExpr { - span: DUMMY_SP, - callee: ArrowExpr { - span: DUMMY_SP, - params: vec![], - body: Box::new(BlockStmtOrExpr::BlockStmt( - BlockStmt { - span: DUMMY_SP, - stmts, - }, - )), - is_async: false, - is_generator: false, - type_params: Default::default(), - return_type: Default::default(), - } - .as_callee(), - args: vec![], - type_args: Default::default(), - })) - })) - .chain(once(Box::new(Expr::Call(CallExpr { - span: DUMMY_SP, - callee: init_class.as_callee(), - args: vec![], - type_args: Default::default(), - })))) - .collect(), - } - .into_stmt()], - }), - accessibility: Default::default(), - is_optional: Default::default(), - }))) - .collect(), - super_class: Some(Box::new(helper_expr!(identity))), - is_abstract: Default::default(), - type_params: Default::default(), - super_type_params: Default::default(), - implements: Default::default(), - }), - } - .into(), - args: Some(vec![]), - type_args: Default::default(), - } - .into_stmt(); - } else { - body.visit_mut_with(self); - - c.visit_mut_with(self); - - c.ident = preserved_class_name.clone(); - replace_ident(&mut c.class, c.ident.to_id(), &preserved_class_name); - - c.class.body.extend(body); - - c.class.body.push(ClassMember::StaticBlock(StaticBlock { - span: DUMMY_SP, - body: BlockStmt { - span: DUMMY_SP, - stmts: vec![CallExpr { - span: DUMMY_SP, - callee: init_class.as_callee(), - args: vec![], - type_args: Default::default(), - } - .into_stmt()], - }, - })); - } + let new_stmt = self.handle_class_decl(c); + if let Some(new_stmt) = new_stmt { + *s = new_stmt; return; } } diff --git a/crates/swc_ecma_transforms_proposal/tests/decorators.rs b/crates/swc_ecma_transforms_proposal/tests/decorators.rs index 06054daa2a0c..35d5e12a126c 100644 --- a/crates/swc_ecma_transforms_proposal/tests/decorators.rs +++ b/crates/swc_ecma_transforms_proposal/tests/decorators.rs @@ -18,6 +18,7 @@ fn syntax_default() -> Syntax { decorators: true, auto_accessors: true, allow_super_outside_method: true, + decorators_before_export: true, ..Default::default() }) } diff --git a/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/input.js b/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/input.js new file mode 100644 index 000000000000..b5b686912249 --- /dev/null +++ b/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/input.js @@ -0,0 +1,19 @@ +@decorate() +export class Foo { + + @decorate() + get name() { + return "hello" + } + + @decorate() + sayHi() { + return "hello" + } +} + +function decorate() { + return function (target, { kind }) { + console.log(target, kind) + } +} \ No newline at end of file diff --git a/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/options.json b/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/options.json new file mode 100644 index 000000000000..4ad37b6e477c --- /dev/null +++ b/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["proposal-decorators", { "version": "2022-03" }]] +} diff --git a/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/output.js b/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/output.js new file mode 100644 index 000000000000..f14444dce77c --- /dev/null +++ b/crates/swc_ecma_transforms_proposal/tests/decorators/issue-7358/1/output.js @@ -0,0 +1,39 @@ +var _dec, _initClass, _dec1, _dec2, _initProto; +let _Foo; +_dec = decorate(), _dec1 = decorate(), _dec2 = decorate(); +class Foo { + static{ + ({ e: [_initProto] , c: [_Foo, _initClass] } = _apply_decs_2203_r(this, [ + [ + _dec1, + 3, + "name" + ], + [ + _dec2, + 2, + "sayHi" + ] + ], [ + _dec + ])); + } + constructor(){ + _initProto(this); + } + get name() { + return "hello"; + } + sayHi() { + return "hello"; + } + static{ + _initClass(); + } +} +function decorate() { + return function(target, { kind }) { + console.log(target, kind); + }; +} +export { _Foo as Foo }; diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs index 938755a14ff8..df0fbd412f42 100644 --- a/crates/swc_ecma_utils/src/lib.rs +++ b/crates/swc_ecma_utils/src/lib.rs @@ -2949,6 +2949,25 @@ impl VisitMut for IdentRenamer<'_> { node.span.ctxt = new.1; } } + + fn visit_mut_export_named_specifier(&mut self, node: &mut ExportNamedSpecifier) { + if node.exported.is_some() { + node.orig.visit_mut_children_with(self); + return; + } + + match &mut node.orig { + ModuleExportName::Ident(orig) => { + if let Some(new) = self.map.get(&orig.to_id()) { + node.exported = Some(ModuleExportName::Ident(orig.clone())); + + orig.sym = new.0.clone(); + orig.span.ctxt = new.1; + } + } + ModuleExportName::Str(_) => {} + } + } } #[cfg(test)]