diff --git a/.changeset/nasty-ligers-reflect.md b/.changeset/nasty-ligers-reflect.md new file mode 100644 index 000000000000..f1c395d9456f --- /dev/null +++ b/.changeset/nasty-ligers-reflect.md @@ -0,0 +1,6 @@ +--- +swc_ecma_transforms_proposal: patch +swc_core: patch +--- + +fix(es/decorator): Add support for private access expressions in legacy decorators diff --git a/crates/swc/tests/fixture/issues-9xxx/9429/input/.swcrc b/crates/swc/tests/fixture/issues-9xxx/9429/input/.swcrc new file mode 100644 index 000000000000..64dbf95348cb --- /dev/null +++ b/crates/swc/tests/fixture/issues-9xxx/9429/input/.swcrc @@ -0,0 +1,20 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": true, + "decorators": true + }, + "target": "es2022", + "loose": false, + "minify": { + "compress": false, + "mangle": false + } + }, + "module": { + "type": "es6" + }, + "minify": false, + "isModule": true +} \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-9xxx/9429/input/index.ts b/crates/swc/tests/fixture/issues-9xxx/9429/input/index.ts new file mode 100644 index 000000000000..7f2d82cc7624 --- /dev/null +++ b/crates/swc/tests/fixture/issues-9xxx/9429/input/index.ts @@ -0,0 +1,25 @@ +class Foo { + static #loggedMethod( + _prototype: typeof Foo.prototype, + _propertyKey: string, + descriptor: TypedPropertyDescriptor<(this: Foo, ...args: Args) => Promise>, + ) { + const method = descriptor.value!; + descriptor.value = function (...args) { + try { + console.log("before"); + return method.apply(this, args); + } finally { + console.log("after"); + } + }; + } + + @(Foo.#loggedMethod) + public greet(): any { + console.log("hi"); + } +} + +const foo = new Foo(); +foo.greet(); \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-9xxx/9429/input/test.ts b/crates/swc/tests/fixture/issues-9xxx/9429/input/test.ts new file mode 100644 index 000000000000..bab9a64bca65 --- /dev/null +++ b/crates/swc/tests/fixture/issues-9xxx/9429/input/test.ts @@ -0,0 +1,24 @@ +class Foo { + static { + class Bar { + static #x() {} + + @Bar.#x + foo() {} + } + } + + static #y() {} + + @Foo.#y + public foo() {} + + static { + class Bar { + static #x() {} + + @Bar.#x + foo() {} + } + } +} diff --git a/crates/swc/tests/fixture/issues-9xxx/9429/output/index.ts b/crates/swc/tests/fixture/issues-9xxx/9429/output/index.ts new file mode 100644 index 000000000000..4adbcd6230b3 --- /dev/null +++ b/crates/swc/tests/fixture/issues-9xxx/9429/output/index.ts @@ -0,0 +1,24 @@ +var _ts_decorate = require("@swc/helpers/_/_ts_decorate"); +class Foo { + static #loggedMethod(_prototype, _propertyKey, descriptor) { + const method = descriptor.value; + descriptor.value = function(...args) { + try { + console.log("before"); + return method.apply(this, args); + } finally{ + console.log("after"); + } + }; + } + greet() { + console.log("hi"); + } + static{ + _ts_decorate._([ + Foo.#loggedMethod + ], Foo.prototype, "greet", null); + } +} +const foo = new Foo(); +foo.greet(); diff --git a/crates/swc/tests/fixture/issues-9xxx/9429/output/test.ts b/crates/swc/tests/fixture/issues-9xxx/9429/output/test.ts new file mode 100644 index 000000000000..500e5c2bc9f5 --- /dev/null +++ b/crates/swc/tests/fixture/issues-9xxx/9429/output/test.ts @@ -0,0 +1,32 @@ +var _ts_decorate = require("@swc/helpers/_/_ts_decorate"); +class Foo { + static{ + class Bar { + static #x() {} + foo() {} + static{ + _ts_decorate._([ + Bar.#x + ], Bar.prototype, "foo", null); + } + } + } + static #y() {} + foo() {} + static{ + class Bar { + static #x() {} + foo() {} + static{ + _ts_decorate._([ + Bar.#x + ], Bar.prototype, "foo", null); + } + } + } + static{ + _ts_decorate._([ + Foo.#y + ], Foo.prototype, "foo", null); + } +} diff --git a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs index d422fa4e5c0b..80e54ea41398 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs @@ -26,6 +26,7 @@ pub(super) fn new(metadata: bool) -> TscDecorator { enums: Default::default(), vars: Default::default(), appended_exprs: Default::default(), + appended_private_access_exprs: Default::default(), prepended_exprs: Default::default(), class_name: Default::default(), @@ -41,6 +42,7 @@ pub(super) struct TscDecorator { /// Used for computed keys, and this variables are not initialized. vars: Vec, appended_exprs: Vec>, + appended_private_access_exprs: Vec>, prepended_exprs: Vec>, class_name: Option, @@ -155,6 +157,17 @@ impl TscDecorator { prop_name_to_expr_value(k.clone()) } + fn has_private_access(mut expr: &Expr) -> bool { + while let Some(MemberExpr { obj, prop, .. }) = expr.as_member() { + if prop.is_private_name() { + return true; + } + expr = obj; + } + + false + } + /// Creates `__decorate` calls. fn add_decorate_call( &mut self, @@ -163,10 +176,17 @@ impl TscDecorator { key: ExprOrSpread, mut desc: ExprOrSpread, ) { + let mut has_private_access = false; let decorators = ArrayLit { span: DUMMY_SP, elems: decorators .into_iter() + .inspect(|e| { + if has_private_access { + return; + } + has_private_access = Self::has_private_access(e); + }) .map(|mut v| { remove_span(&mut v); @@ -180,15 +200,18 @@ impl TscDecorator { remove_span(&mut target.expr); remove_span(&mut desc.expr); - self.appended_exprs.push( - CallExpr { - span: DUMMY_SP, - callee: helper!(ts, ts_decorate), - args: vec![decorators, target, key, desc], - ..Default::default() - } - .into(), - ); + let expr = CallExpr { + callee: helper!(ts, ts_decorate), + args: vec![decorators, target, key, desc], + ..Default::default() + } + .into(); + + if has_private_access { + self.appended_private_access_exprs.push(expr); + } else { + self.appended_exprs.push(expr); + } } } @@ -235,6 +258,8 @@ impl Visit for TscDecorator { impl VisitMut for TscDecorator { fn visit_mut_class(&mut self, n: &mut Class) { + let appended_private = self.appended_private_access_exprs.take(); + n.visit_mut_with(&mut ParamMetadata); if self.metadata { @@ -245,6 +270,32 @@ impl VisitMut for TscDecorator { n.visit_mut_children_with(self); + let appended_private = + mem::replace(&mut self.appended_private_access_exprs, appended_private); + + if !appended_private.is_empty() { + let expr = if appended_private.len() == 1 { + *appended_private.into_iter().next().unwrap() + } else { + SeqExpr { + exprs: appended_private, + ..Default::default() + } + .into() + }; + + n.body.push( + StaticBlock { + body: BlockStmt { + stmts: vec![expr.into_stmt()], + ..Default::default() + }, + ..Default::default() + } + .into(), + ) + } + if let Some(class_name) = self.class_name.clone() { if !n.decorators.is_empty() { let decorators = ArrayLit {