From 570483ae48c532d6aa169d38bddca5b767ead8f3 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Mon, 4 Mar 2024 19:01:04 +0800 Subject: [PATCH] fix(es/decorators): Handle default exported class (#8691) **Related issue:** - Closes #8681 --- .../fixture/issues-8xxx/8681/input/.swcrc | 12 + .../fixture/issues-8xxx/8681/input/index.ts | 2 + .../fixture/issues-8xxx/8681/input/lib.ts | 2 + .../fixture/issues-8xxx/8681/output/index.ts | 14 + .../fixture/issues-8xxx/8681/output/lib.ts | 14 + .../src/decorator_2022_03.rs | 506 +++++++++--------- .../default-anonymous/output.mjs | 6 +- .../2022-03-exported/default-named/output.mjs | 8 +- 8 files changed, 302 insertions(+), 262 deletions(-) create mode 100644 crates/swc/tests/fixture/issues-8xxx/8681/input/.swcrc create mode 100644 crates/swc/tests/fixture/issues-8xxx/8681/input/index.ts create mode 100644 crates/swc/tests/fixture/issues-8xxx/8681/input/lib.ts create mode 100644 crates/swc/tests/fixture/issues-8xxx/8681/output/index.ts create mode 100644 crates/swc/tests/fixture/issues-8xxx/8681/output/lib.ts diff --git a/crates/swc/tests/fixture/issues-8xxx/8681/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8681/input/.swcrc new file mode 100644 index 000000000000..0b6fe8991865 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8681/input/.swcrc @@ -0,0 +1,12 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript", + "decorators": true + }, + "target": "es2022", + "transform": { + "decoratorVersion": "2022-03" + } + } +} \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8681/input/index.ts b/crates/swc/tests/fixture/issues-8xxx/8681/input/index.ts new file mode 100644 index 000000000000..aaeb740a3ee0 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8681/input/index.ts @@ -0,0 +1,2 @@ +@decorator +export default class Example { } \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8681/input/lib.ts b/crates/swc/tests/fixture/issues-8xxx/8681/input/lib.ts new file mode 100644 index 000000000000..21d83e9c5946 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8681/input/lib.ts @@ -0,0 +1,2 @@ +@decorator +export default class { } \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8681/output/index.ts b/crates/swc/tests/fixture/issues-8xxx/8681/output/index.ts new file mode 100644 index 000000000000..7738e496ecf4 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8681/output/index.ts @@ -0,0 +1,14 @@ +import { _ as _apply_decs_2203_r } from "@swc/helpers/_/_apply_decs_2203_r"; +var _initClass; +let _Example; +class Example { + static{ + ({ c: [_Example, _initClass] } = _apply_decs_2203_r(this, [], [ + decorator + ])); + } + static{ + _initClass(); + } +} +export { _Example as default }; diff --git a/crates/swc/tests/fixture/issues-8xxx/8681/output/lib.ts b/crates/swc/tests/fixture/issues-8xxx/8681/output/lib.ts new file mode 100644 index 000000000000..848f7ba7bd29 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8681/output/lib.ts @@ -0,0 +1,14 @@ +import { _ as _apply_decs_2203_r } from "@swc/helpers/_/_apply_decs_2203_r"; +var _initClass; +let __default; +class _default { + static{ + ({ c: [__default, _initClass] } = _apply_decs_2203_r(this, [], [ + decorator + ])); + } + static{ + _initClass(); + } +} +export { __default as default }; 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 1218f1661067..d60acea35ea6 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs @@ -421,289 +421,273 @@ impl Decorator202203 { new_class_name } - fn handle_class_decl(&mut self, c: &mut ClassDecl) -> Option { + // This function will call `visit` internally. + fn handle_class_decl(&mut self, c: &mut ClassDecl) -> Stmt { let old_state = take(&mut self.state); - if !c.class.decorators.is_empty() { - let decorators = self.preserve_side_effect_of_decorators(c.class.decorators.take()); + let decorators = self.preserve_side_effect_of_decorators(c.class.decorators.take()); - let init_class = private_ident!("_initClass"); + 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, - }); + 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)); + 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.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.rename_map + .insert(c.ident.to_id(), new_class_name.to_id()); - self.state - .class_lhs - .push(Some(new_class_name.clone().into())); - self.state.class_lhs.push(Some(init_class.clone().into())); - - self.state.class_decorators.extend(decorators); - self.handle_super_class(&mut c.class); - - 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, - }); + self.state + .class_lhs + .push(Some(new_class_name.clone().into())); + self.state.class_lhs.push(Some(init_class.clone().into())); - if has_static_member { - let mut last_static_block = None; + self.state.class_decorators.extend(decorators); + self.handle_super_class(&mut c.class); - self.process_decorators_of_class_members(&mut body); + let mut body = c.class.body.take(); - // Move static blocks into property initializers - for m in body.iter_mut() { - match m { - 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 { + 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; + + self.process_decorators_of_class_members(&mut body); + + // Move static blocks into property initializers + for m in body.iter_mut() { + match m { + 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, - 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(), - ], - }) - } + 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); - } - }, - _ => {} } + 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); + } + }, + _ => {} } + } - // Drop static blocks - body.retain(|m| { - !matches!(m, ClassMember::StaticBlock(..) | ClassMember::Empty(..)) - }); - - for m in body.iter_mut() { - match m { - ClassMember::ClassProp(..) | ClassMember::PrivateProp(..) => { - replace_ident(m, c.ident.to_id(), &new_class_name); - } + // Drop static blocks + body.retain(|m| !matches!(m, ClassMember::StaticBlock(..) | ClassMember::Empty(..))); - _ => {} + for m in body.iter_mut() { + match m { + ClassMember::ClassProp(..) | ClassMember::PrivateProp(..) => { + replace_ident(m, c.ident.to_id(), &new_class_name); } + + _ => {} } + } - let mut inner_class = ClassDecl { - ident: c.ident.clone(), - declare: Default::default(), - class: Box::new(Class { - span: DUMMY_SP, - decorators: vec![], - body, - super_class: c.class.super_class.take(), - is_abstract: Default::default(), - type_params: Default::default(), - super_type_params: Default::default(), - implements: Default::default(), - }), - }; - - inner_class.class.visit_mut_with(self); - - for m in inner_class.class.body.iter_mut() { - let mut should_move = false; - - match m { - ClassMember::PrivateProp(p) => { - if p.is_static { - should_move = true; - p.is_static = false; - } + let mut inner_class = ClassDecl { + ident: c.ident.clone(), + declare: Default::default(), + class: Box::new(Class { + span: DUMMY_SP, + decorators: vec![], + body, + super_class: c.class.super_class.take(), + is_abstract: Default::default(), + type_params: Default::default(), + super_type_params: Default::default(), + implements: Default::default(), + }), + }; + + inner_class.class.visit_mut_with(self); + + for m in inner_class.class.body.iter_mut() { + let mut should_move = false; + + match m { + ClassMember::PrivateProp(p) => { + if p.is_static { + should_move = true; + p.is_static = false; } - ClassMember::PrivateMethod(p) => { - if p.is_static { - should_move = true; - p.is_static = false; - } + } + ClassMember::PrivateMethod(p) => { + if p.is_static { + should_move = true; + p.is_static = false; } - _ => (), } + _ => (), + } - if should_move { - c.class.body.push(m.take()) - } + if should_move { + c.class.body.push(m.take()) } + } - c.class.body.insert( - 0, - ClassMember::StaticBlock(StaticBlock { + c.class.body.insert( + 0, + ClassMember::StaticBlock(StaticBlock { + span: DUMMY_SP, + body: BlockStmt { span: DUMMY_SP, - body: BlockStmt { - span: DUMMY_SP, - stmts: vec![Stmt::Decl(Decl::Class(inner_class))], - }, - }), - ); + stmts: vec![Stmt::Decl(Decl::Class(inner_class))], + }, + }), + ); - replace_ident(&mut c.class, c.ident.to_id(), &preserved_class_name); + replace_ident(&mut c.class, c.ident.to_id(), &preserved_class_name); - { - let constructor = self.ensure_identity_constructor(&mut c.class); + { + let constructor = self.ensure_identity_constructor(&mut c.class); - let super_call = CallExpr { + let super_call = CallExpr { + span: DUMMY_SP, + callee: Callee::Super(Super { span: DUMMY_SP }), + args: vec![c.ident.clone().as_arg()], + type_args: Default::default(), + } + .into(); + let static_call = last_static_block.map(|last| { + CallExpr { span: DUMMY_SP, - callee: Callee::Super(Super { span: DUMMY_SP }), - args: vec![c.ident.clone().as_arg()], - type_args: Default::default(), - } - .into(); - let static_call = last_static_block.map(|last| { - CallExpr { + callee: ArrowExpr { span: DUMMY_SP, - callee: ArrowExpr { + params: vec![], + body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt { span: DUMMY_SP, - params: vec![], - body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt { - span: DUMMY_SP, - stmts: last, - })), - is_async: false, - is_generator: false, - type_params: Default::default(), - return_type: Default::default(), - } - .as_callee(), - args: vec![], - type_args: Default::default(), + stmts: last, + })), + is_async: false, + is_generator: false, + type_params: Default::default(), + return_type: Default::default(), } - .into() - }); - - let init_class_call = CallExpr { - span: DUMMY_SP, - callee: init_class.as_callee(), - args: Vec::new(), + .as_callee(), + args: vec![], type_args: Default::default(), } - .into(); - - constructor.body.as_mut().unwrap().stmts.insert( - 0, - SeqExpr { - span: DUMMY_SP, - exprs: once(super_call) - .chain(static_call) - .chain(once(init_class_call)) - .collect(), - } - .into_stmt(), - ); - } - - let class = Box::new(Class { - span: DUMMY_SP, - decorators: Vec::new(), - body: c.class.body.take(), - 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() }); - self.state = old_state; + let init_class_call = CallExpr { + span: DUMMY_SP, + callee: init_class.as_callee(), + args: Vec::new(), + type_args: Default::default(), + } + .into(); - return Some( - NewExpr { + constructor.body.as_mut().unwrap().stmts.insert( + 0, + SeqExpr { span: DUMMY_SP, - callee: ClassExpr { ident: None, class }.into(), - args: Some(vec![]), - type_args: Default::default(), + exprs: once(super_call) + .chain(static_call) + .chain(once(init_class_call)) + .collect(), } .into_stmt(), ); - } else { - for m in body.iter_mut() { - if let ClassMember::Constructor(..) = m { - c.class.body.push(m.take()); - } - } - - 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 class = Box::new(Class { + span: DUMMY_SP, + decorators: Vec::new(), + body: c.class.body.take(), + super_class: Some(Box::new(helper_expr!(identity))), + is_abstract: Default::default(), + type_params: Default::default(), + super_type_params: Default::default(), + implements: Default::default(), + }); - self.state = old_state; + self.state = old_state; - return Some(Stmt::Decl(Decl::Class(c.take()))); + return NewExpr { + span: DUMMY_SP, + callee: ClassExpr { ident: None, class }.into(), + args: Some(vec![]), + type_args: Default::default(), } + .into_stmt(); } - + for m in body.iter_mut() { + if let ClassMember::Constructor(..) = m { + c.class.body.push(m.take()); + } + } + 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()], + }, + })); self.state = old_state; - None + Stmt::Decl(Decl::Class(c.take())) } fn process_decorators(&mut self, decorators: &mut [Decorator]) { @@ -1474,31 +1458,41 @@ impl VisitMut for Decorator202203 { ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { span, decl: Decl::Class(c), - })) => { + })) if !c.class.decorators.is_empty() => { 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 = ModuleItem::Stmt(new_stmt); + self.extra_exports + .push(ExportSpecifier::Named(ExportNamedSpecifier { + span, + orig: ModuleExportName::Ident(ident), + exported: None, + is_type_only: false, + })); } ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { - span: _, + span, decl: DefaultDecl::Class(c), })) if !c.class.decorators.is_empty() => { - self.handle_class_expr(&mut c.class, c.ident.as_ref()); - s.visit_mut_children_with(self); + let ident = c + .ident + .get_or_insert_with(|| private_ident!("_default")) + .clone(); + + let mut class_decl = c.take().as_class_decl().unwrap(); + let new_stmt = self.handle_class_decl(&mut class_decl); + + self.extra_exports + .push(ExportSpecifier::Named(ExportNamedSpecifier { + span: *span, + orig: ModuleExportName::Ident(ident), + exported: Some(quote_ident!("default").into()), + is_type_only: false, + })); + + *s = ModuleItem::Stmt(new_stmt); } _ => { s.visit_mut_children_with(self); @@ -1689,16 +1683,14 @@ impl VisitMut for Decorator202203 { } fn visit_mut_stmt(&mut self, s: &mut Stmt) { - if let Stmt::Decl(Decl::Class(c)) = s { - let new_stmt = self.handle_class_decl(c); - - if let Some(new_stmt) = new_stmt { - *s = new_stmt; - return; + match s { + Stmt::Decl(Decl::Class(c)) if !c.class.decorators.is_empty() => { + *s = self.handle_class_decl(c); + } + _ => { + s.visit_mut_children_with(self); } } - - s.visit_mut_children_with(self); } fn visit_mut_stmts(&mut self, n: &mut Vec) { diff --git a/crates/swc_ecma_transforms_proposal/tests/decorators/2022-03-exported/default-anonymous/output.mjs b/crates/swc_ecma_transforms_proposal/tests/decorators/2022-03-exported/default-anonymous/output.mjs index da1847afabee..1e195c59789c 100644 --- a/crates/swc_ecma_transforms_proposal/tests/decorators/2022-03-exported/default-anonymous/output.mjs +++ b/crates/swc_ecma_transforms_proposal/tests/decorators/2022-03-exported/default-anonymous/output.mjs @@ -1,5 +1,6 @@ -var _initClass, _A; -export default class A { +var _initClass; +let _A; +class A { static{ ({ c: [_A, _initClass] } = _apply_decs_2203_r(this, [], [ dec @@ -9,3 +10,4 @@ export default class A { _initClass(); } } +export { _A as default }; diff --git a/crates/swc_ecma_transforms_proposal/tests/decorators/2022-03-exported/default-named/output.mjs b/crates/swc_ecma_transforms_proposal/tests/decorators/2022-03-exported/default-named/output.mjs index 44309dff690d..47f8ce19f916 100644 --- a/crates/swc_ecma_transforms_proposal/tests/decorators/2022-03-exported/default-named/output.mjs +++ b/crates/swc_ecma_transforms_proposal/tests/decorators/2022-03-exported/default-named/output.mjs @@ -1,7 +1,8 @@ -var _initClass, _class; -export default class { +var _initClass; +let __default; +class _default { static{ - ({ c: [_class, _initClass] } = _apply_decs_2203_r(this, [], [ + ({ c: [__default, _initClass] } = _apply_decs_2203_r(this, [], [ dec ])); } @@ -9,3 +10,4 @@ export default class { _initClass(); } } +export { __default as default };