From 01500a54e04b88d08edff09f218166c862763657 Mon Sep 17 00:00:00 2001 From: Austaras Date: Thu, 3 Feb 2022 16:54:30 +0800 Subject: [PATCH] fix(es/compat): Handle private fields in nested classes (#3431) --- .../fixture/issue-1333/case1/input/index.js | 2 + .../fixture/issue-1333/case1/output/index.js | 12 ++ .../privateNameAndAny_es2015.1.normal.js | 21 --- .../privateNameAndAny_es2015.2.minified.js | 1 - .../privateNameAndAny_es5.1.normal.js | 48 ------ .../privateNameAndAny_es5.2.minified.js | 30 ---- ...teNameAndIndexSignature_es2015.1.normal.js | 29 ---- ...NameAndIndexSignature_es2015.2.minified.js | 2 - ...ivateNameAndIndexSignature_es5.1.normal.js | 34 ---- ...ateNameAndIndexSignature_es5.2.minified.js | 15 -- ...NameImplicitDeclaration_es2015.1.normal.js | 15 -- ...ateNameImplicitDeclaration_es5.1.normal.js | 16 -- ...eNameImplicitDeclaration_es5.2.minified.js | 9 - crates/swc/tests/tsc.rs | 4 + .../src/es2022/class_properties/mod.rs | 139 +++++++--------- .../es2022/class_properties/private_field.rs | 157 +++++++++--------- .../tests/es2022_class_properties.rs | 151 ++++++++++++++++- .../nested-class-other-redeclared/output.js | 6 +- .../private/nested-class/output.js | 6 +- 19 files changed, 318 insertions(+), 379 deletions(-) delete mode 100644 crates/swc/tests/tsc-references/privateNameAndAny_es2015.1.normal.js delete mode 100644 crates/swc/tests/tsc-references/privateNameAndAny_es2015.2.minified.js delete mode 100644 crates/swc/tests/tsc-references/privateNameAndAny_es5.1.normal.js delete mode 100644 crates/swc/tests/tsc-references/privateNameAndAny_es5.2.minified.js delete mode 100644 crates/swc/tests/tsc-references/privateNameAndIndexSignature_es2015.1.normal.js delete mode 100644 crates/swc/tests/tsc-references/privateNameAndIndexSignature_es2015.2.minified.js delete mode 100644 crates/swc/tests/tsc-references/privateNameAndIndexSignature_es5.1.normal.js delete mode 100644 crates/swc/tests/tsc-references/privateNameAndIndexSignature_es5.2.minified.js delete mode 100644 crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es2015.1.normal.js delete mode 100644 crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es5.1.normal.js delete mode 100644 crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es5.2.minified.js diff --git a/crates/swc/tests/fixture/issue-1333/case1/input/index.js b/crates/swc/tests/fixture/issue-1333/case1/input/index.js index bd1eb7435f5..f682057f06b 100644 --- a/crates/swc/tests/fixture/issue-1333/case1/input/index.js +++ b/crates/swc/tests/fixture/issue-1333/case1/input/index.js @@ -1,4 +1,6 @@ class Foo { + #ws; + #ws2; get connected() { return this.#ws2 && this.#ws.readyState === _ws1.default.OPEN; } diff --git a/crates/swc/tests/fixture/issue-1333/case1/output/index.js b/crates/swc/tests/fixture/issue-1333/case1/output/index.js index dcbd640187e..f205ac31596 100644 --- a/crates/swc/tests/fixture/issue-1333/case1/output/index.js +++ b/crates/swc/tests/fixture/issue-1333/case1/output/index.js @@ -9,4 +9,16 @@ class Foo { get connected() { return _classPrivateFieldGet(this, _ws2) && _classPrivateFieldGet(this, _ws).readyState === _ws1.default.OPEN; } + constructor(){ + _ws.set(this, { + writable: true, + value: void 0 + }); + _ws2.set(this, { + writable: true, + value: void 0 + }); + } } +var _ws = new WeakMap(); +var _ws2 = new WeakMap(); diff --git a/crates/swc/tests/tsc-references/privateNameAndAny_es2015.1.normal.js b/crates/swc/tests/tsc-references/privateNameAndAny_es2015.1.normal.js deleted file mode 100644 index 530aec3d837..00000000000 --- a/crates/swc/tests/tsc-references/privateNameAndAny_es2015.1.normal.js +++ /dev/null @@ -1,21 +0,0 @@ -function _classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to get private field on non-instance"); - } - return privateMap.get(receiver).value; -} -// @strict: true -// @target: es6 -class A { - method(thing) { - _classPrivateFieldGet(thing, _foo); // OK - _classPrivateFieldGet(thing, _bar); // Error - } - constructor(){ - _foo.set(this, { - writable: true, - value: true - }); - } -} -var _foo = new WeakMap(); diff --git a/crates/swc/tests/tsc-references/privateNameAndAny_es2015.2.minified.js b/crates/swc/tests/tsc-references/privateNameAndAny_es2015.2.minified.js deleted file mode 100644 index 73263530f4c..00000000000 --- a/crates/swc/tests/tsc-references/privateNameAndAny_es2015.2.minified.js +++ /dev/null @@ -1 +0,0 @@ -new WeakMap(); diff --git a/crates/swc/tests/tsc-references/privateNameAndAny_es5.1.normal.js b/crates/swc/tests/tsc-references/privateNameAndAny_es5.1.normal.js deleted file mode 100644 index 3e815cd97f5..00000000000 --- a/crates/swc/tests/tsc-references/privateNameAndAny_es5.1.normal.js +++ /dev/null @@ -1,48 +0,0 @@ -function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -} -function _classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to get private field on non-instance"); - } - return privateMap.get(receiver).value; -} -function _defineProperties(target, props) { - for(var i = 0; i < props.length; i++){ - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } -} -function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; -} -var A = // @strict: true -// @target: es6 -/*#__PURE__*/ function() { - "use strict"; - function A() { - _classCallCheck(this, A); - _foo.set(this, { - writable: true, - value: true - }); - } - _createClass(A, [ - { - key: "method", - value: function method(thing) { - _classPrivateFieldGet(thing, _foo); // OK - _classPrivateFieldGet(thing, _bar); // Error - } - } - ]); - return A; -}(); -var _foo = new WeakMap(); diff --git a/crates/swc/tests/tsc-references/privateNameAndAny_es5.2.minified.js b/crates/swc/tests/tsc-references/privateNameAndAny_es5.2.minified.js deleted file mode 100644 index c5509d1d5d7..00000000000 --- a/crates/swc/tests/tsc-references/privateNameAndAny_es5.2.minified.js +++ /dev/null @@ -1,30 +0,0 @@ -function _classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) throw new TypeError("attempted to get private field on non-instance"); - return privateMap.get(receiver).value; -} -function _defineProperties(target, props) { - for(var i = 0; i < props.length; i++){ - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || !1, descriptor.configurable = !0, "value" in descriptor && (descriptor.writable = !0), Object.defineProperty(target, descriptor.key, descriptor); - } -} -var A = function() { - "use strict"; - var Constructor, protoProps, staticProps; - function A() { - !function(instance, Constructor) { - if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function"); - }(this, A), _foo.set(this, { - writable: !0, - value: !0 - }); - } - return Constructor = A, protoProps = [ - { - key: "method", - value: function(thing) { - _classPrivateFieldGet(thing, _foo), _classPrivateFieldGet(thing, _bar); - } - } - ], _defineProperties(Constructor.prototype, protoProps), staticProps && _defineProperties(Constructor, staticProps), A; -}(), _foo = new WeakMap(); diff --git a/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es2015.1.normal.js b/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es2015.1.normal.js deleted file mode 100644 index dda38250801..00000000000 --- a/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es2015.1.normal.js +++ /dev/null @@ -1,29 +0,0 @@ -function _classPrivateFieldSet(receiver, privateMap, value) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to set private field on non-instance"); - } - var descriptor = privateMap.get(receiver); - if (!descriptor.writable) { - throw new TypeError("attempted to set read only private field"); - } - descriptor.value = value; - return value; -} -var _key; -// @strict: true -// @target: es6 -class A { - constructor(message){ - _foo.set(this, { - writable: true, - value: 3 - }); - this[_key] // Error (private identifiers should not prevent circularity checking for computeds) - = this["#bar"]; - _classPrivateFieldSet(this, _f, 3 // Error (index signatures do not implicitly declare private names) - ); - this["#foo"] = 3; // Okay (type has index signature and "#foo" does not collide with private identifier #foo) - } -} -var _foo = new WeakMap(); -_key = "#bar"; diff --git a/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es2015.2.minified.js b/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es2015.2.minified.js deleted file mode 100644 index c8b32e02753..00000000000 --- a/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es2015.2.minified.js +++ /dev/null @@ -1,2 +0,0 @@ -var _key; -new WeakMap(), _key = "#bar"; diff --git a/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es5.1.normal.js b/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es5.1.normal.js deleted file mode 100644 index b33db9f303c..00000000000 --- a/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es5.1.normal.js +++ /dev/null @@ -1,34 +0,0 @@ -function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -} -function _classPrivateFieldSet(receiver, privateMap, value) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to set private field on non-instance"); - } - var descriptor = privateMap.get(receiver); - if (!descriptor.writable) { - throw new TypeError("attempted to set read only private field"); - } - descriptor.value = value; - return value; -} -var _key; -var A = function A(message) { - "use strict"; - _classCallCheck(this, A); - _foo.set(this, { - writable: true, - value: 3 - }); - // @strict: true - // @target: es6 - this[_key] // Error (private identifiers should not prevent circularity checking for computeds) - = this["#bar"]; - _classPrivateFieldSet(this, _f, 3 // Error (index signatures do not implicitly declare private names) - ); - this["#foo"] = 3; // Okay (type has index signature and "#foo" does not collide with private identifier #foo) -}; -var _foo = new WeakMap(); -_key = "#bar"; diff --git a/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es5.2.minified.js b/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es5.2.minified.js deleted file mode 100644 index a68ff088d0e..00000000000 --- a/crates/swc/tests/tsc-references/privateNameAndIndexSignature_es5.2.minified.js +++ /dev/null @@ -1,15 +0,0 @@ -var _key, A = function(message) { - "use strict"; - !function(instance, Constructor) { - if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function"); - }(this, A), _foo.set(this, { - writable: !0, - value: 3 - }), this[_key] = this["#bar"], (function(receiver, privateMap, value) { - if (!privateMap.has(receiver)) throw new TypeError("attempted to set private field on non-instance"); - var descriptor = privateMap.get(receiver); - if (!descriptor.writable) throw new TypeError("attempted to set read only private field"); - descriptor.value = 3; - })(this, _f, 3), this["#foo"] = 3; -}, _foo = new WeakMap(); -_key = "#bar"; diff --git a/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es2015.1.normal.js b/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es2015.1.normal.js deleted file mode 100644 index 269a6daa4c1..00000000000 --- a/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es2015.1.normal.js +++ /dev/null @@ -1,15 +0,0 @@ -function _classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to get private field on non-instance"); - } - return privateMap.get(receiver).value; -} -// @allowJs: true -// @checkJs: true -// @noEmit: true -// @Filename: privateNameImplicitDeclaration.js -class C { - constructor(){ - _classPrivateFieldGet(/** @type {string} */ this, _x); - } -} diff --git a/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es5.1.normal.js b/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es5.1.normal.js deleted file mode 100644 index c7266e96907..00000000000 --- a/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es5.1.normal.js +++ /dev/null @@ -1,16 +0,0 @@ -function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -} -function _classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to get private field on non-instance"); - } - return privateMap.get(receiver).value; -} -var C = function C() { - "use strict"; - _classCallCheck(this, C); - _classPrivateFieldGet(/** @type {string} */ this, _x); -}; diff --git a/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es5.2.minified.js b/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es5.2.minified.js deleted file mode 100644 index 5641969f922..00000000000 --- a/crates/swc/tests/tsc-references/privateNameImplicitDeclaration_es5.2.minified.js +++ /dev/null @@ -1,9 +0,0 @@ -var C = function() { - "use strict"; - !function(instance, Constructor) { - if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function"); - }(this, C), (function(receiver, privateMap) { - if (!privateMap.has(receiver)) throw new TypeError("attempted to get private field on non-instance"); - privateMap.get(receiver).value; - })(this, _x); -}; diff --git a/crates/swc/tests/tsc.rs b/crates/swc/tests/tsc.rs index d9bd2289c8d..167dac81a29 100644 --- a/crates/swc/tests/tsc.rs +++ b/crates/swc/tests/tsc.rs @@ -20,6 +20,10 @@ use testing::{NormalizedOutput, Tester}; "propertyAccessChain\\.3.ts", "objectRestNegative.ts", "objectRestPropertyMustBeLast.ts", + // shall panic because not finding private field + "privateNameAndAny.ts", + "privateNameAndIndexSignature.ts", + "privateNameImplicitDeclaration.ts" ) )] #[testing::fixture("../swc_ecma_parser/tests/tsc/**/*.tsx")] diff --git a/crates/swc_ecma_transforms_compat/src/es2022/class_properties/mod.rs b/crates/swc_ecma_transforms_compat/src/es2022/class_properties/mod.rs index 5ea89902238..931d6d869da 100644 --- a/crates/swc_ecma_transforms_compat/src/es2022/class_properties/mod.rs +++ b/crates/swc_ecma_transforms_compat/src/es2022/class_properties/mod.rs @@ -2,12 +2,11 @@ use self::{ class_name_tdz::ClassNameTdzFolder, - private_field::{BrandCheckHandler, FieldAccessFolder}, + private_field::{BrandCheckHandler, FieldAccessFolder, Private, PrivateKind, PrivateRecord}, this_in_static::ThisInStaticFolder, used_name::UsedNameCollector, }; -use std::collections::HashSet; -use swc_common::{util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP}; +use swc_common::{collections::AHashSet, util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::{helper, perf::Check}; use swc_ecma_transforms_classes::super_field::SuperFieldAccessFolder; @@ -35,8 +34,7 @@ mod used_name; pub fn class_properties(config: Config) -> impl Fold + VisitMut { as_folder(ClassProperties { config, - mark: Mark::root(), - method_mark: Mark::root(), + private: PrivateRecord::new(), }) } @@ -45,11 +43,9 @@ pub struct Config { pub loose: bool, } -#[derive(Clone)] struct ClassProperties { config: Config, - mark: Mark, - method_mark: Mark, + private: PrivateRecord, } #[fast_path(ShouldWork)] @@ -111,8 +107,6 @@ impl VisitMut for ClassProperties { } fn visit_mut_expr(&mut self, expr: &mut Expr) { - expr.visit_mut_children_with(self); - if let Expr::Class(ClassExpr { ident, class }) = expr { let ident = ident.take().unwrap_or_else(|| private_ident!("_class")); let mut stmts = vec![]; @@ -168,6 +162,8 @@ impl VisitMut for ClassProperties { args: vec![], type_args: Default::default(), }); + } else { + expr.visit_mut_children_with(self); }; } } @@ -183,8 +179,6 @@ impl ClassProperties { match T::try_into_stmt(stmt) { Err(node) => match node.try_into_module_decl() { Ok(mut decl) => { - decl.visit_mut_children_with(self); - match decl { ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { span, @@ -261,16 +255,18 @@ impl ClassProperties { ); buf.extend(stmts.into_iter().map(T::from_stmt)); } - _ => buf.push(match T::try_from_module_decl(decl) { - Ok(t) => t, - Err(..) => unreachable!(), - }), + _ => { + decl.visit_mut_children_with(self); + buf.push(match T::try_from_module_decl(decl) { + Ok(t) => t, + Err(..) => unreachable!(), + }) + } }; } Err(..) => unreachable!(), }, Ok(mut stmt) => { - stmt.visit_mut_children_with(self); // Fold class match stmt { Stmt::Decl(Decl::Class(ClassDecl { @@ -290,7 +286,10 @@ impl ClassProperties { buf.push(T::from_stmt(Stmt::Decl(Decl::Class(decl)))); buf.extend(stmts.into_iter().map(T::from_stmt)); } - _ => buf.push(T::from_stmt(stmt)), + _ => { + stmt.visit_mut_children_with(self); + buf.push(T::from_stmt(stmt)) + } } } } @@ -307,8 +306,38 @@ impl ClassProperties { mut class: Class, ) -> (Vec, ClassDecl, Vec) { // Create one mark per class - self.mark = Mark::fresh(Mark::root()); - self.method_mark = Mark::fresh(Mark::root()); + let private = Private { + mark: Mark::fresh(Mark::root()), + class_name: class_ident.clone(), + ident: class + .body + .iter() + .filter_map(|member| match member { + ClassMember::PrivateMethod(method) => Some(( + method.key.id.sym.clone(), + PrivateKind { + is_method: true, + is_static: method.is_static, + }, + )), + + ClassMember::PrivateProp(prop) => Some(( + prop.key.id.sym.clone(), + PrivateKind { + is_method: false, + is_static: prop.is_static, + }, + )), + + _ => None, + }) + .collect(), + }; + + self.private.push(private); + + // we must collect outer class's private first + class.visit_mut_children_with(self); let has_super = class.super_class.is_some(); @@ -322,48 +351,11 @@ impl ClassProperties { let mut constructor = None; let mut used_names = vec![]; let mut used_key_names = vec![]; - let mut names_used_for_brand_checks = HashSet::default(); - - let statics = { - let mut s = HashSet::default(); - - for member in &class.body { - match member { - ClassMember::PrivateMethod(method) => { - if method.is_static { - s.insert(method.key.id.sym.clone()); - } - } - - ClassMember::PrivateProp(prop) => { - if prop.is_static { - s.insert(prop.key.id.sym.clone()); - } - } - - _ => {} - } - } - - s - }; - let private_methods = { - let mut s = HashSet::default(); - - for member in &class.body { - if let ClassMember::PrivateMethod(method) = member { - s.insert(method.key.id.sym.clone()); - } - } - - s - }; + let mut names_used_for_brand_checks = AHashSet::default(); class.body.visit_mut_with(&mut BrandCheckHandler { - mark: self.mark, - class_name: &class_ident, names: &mut names_used_for_brand_checks, - statics: &statics, + private: &self.private, }); for member in class.body { @@ -505,7 +497,7 @@ impl ClassProperties { let ident = Ident::new( format!("_{}", prop.key.id.sym).into(), // We use `self.mark` for private variables. - prop.key.span.apply_mark(self.mark), + prop.key.span.apply_mark(self.private.curr_mark()), ); prop.value.visit_with(&mut UsedNameCollector { used_names: &mut used_names, @@ -594,30 +586,31 @@ impl ClassProperties { } ClassMember::PrivateMethod(method) => { + let is_static = method.is_static; let prop_span = method.span; let fn_name = Ident::new( method.key.id.sym.clone(), method .span .with_ctxt(SyntaxContext::empty()) - .apply_mark(self.method_mark), + .apply_mark(self.private.curr_mark()), ); let should_use_map = matches!(method.kind, MethodKind::Getter | MethodKind::Setter) && names_used_for_brand_checks.contains(&method.key.id.sym) - && !statics.contains(&method.key.id.sym); + && !is_static; let weak_coll_var = Ident::new( format!("_{}", method.key.id.sym).into(), // We use `self.mark` for private variables. - method.key.span.apply_mark(self.mark), + method.key.span.apply_mark(self.private.curr_mark()), ); method.function.visit_with(&mut UsedNameCollector { used_names: &mut used_names, }); - if should_use_map || !statics.contains(&method.key.id.sym) { + if should_use_map || !is_static { vars.push(VarDeclarator { span: DUMMY_SP, definite: false, @@ -650,7 +643,7 @@ impl ClassProperties { props: vec![get, set], }; - let obj = if statics.contains(&method.key.id.sym) { + let obj = if is_static { let var_name = private_ident!("static_method"); vars.push(VarDeclarator { @@ -674,7 +667,7 @@ impl ClassProperties { args: vec![ThisExpr { span: DUMMY_SP }.as_arg(), obj], type_args: Default::default(), }))); - } else if !statics.contains(&method.key.id.sym) { + } else if !is_static { // Add `_get.add(this);` to the constructor where `_get` is the name of the // weak set. constructor_exprs.push(Box::new(Expr::Call(CallExpr { @@ -712,27 +705,21 @@ impl ClassProperties { } private_method_fn_decls.visit_mut_with(&mut FieldAccessFolder { - mark: self.mark, - method_mark: self.method_mark, - private_methods: &private_methods, - statics: &statics, + private: &self.private, vars: vec![], - class_name: &class_ident, in_assign_pat: false, }); extra_stmts.extend(private_method_fn_decls); members.visit_mut_with(&mut FieldAccessFolder { - mark: self.mark, - method_mark: self.method_mark, - private_methods: &private_methods, - statics: &statics, + private: &self.private, vars: vec![], - class_name: &class_ident, in_assign_pat: false, }); + self.private.pop(); + ( vars, ClassDecl { diff --git a/crates/swc_ecma_transforms_compat/src/es2022/class_properties/private_field.rs b/crates/swc_ecma_transforms_compat/src/es2022/class_properties/private_field.rs index 0fc310f7e28..6864df7d527 100644 --- a/crates/swc_ecma_transforms_compat/src/es2022/class_properties/private_field.rs +++ b/crates/swc_ecma_transforms_compat/src/es2022/class_properties/private_field.rs @@ -1,21 +1,66 @@ use std::iter; use swc_atoms::JsWord; -use swc_common::{collections::AHashSet, util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP}; +use swc_common::{ + collections::{AHashMap, AHashSet}, + util::take::Take, + Mark, Spanned, SyntaxContext, DUMMY_SP, +}; use swc_ecma_ast::*; use swc_ecma_transforms_base::helper; use swc_ecma_utils::{alias_ident_for, alias_if_required, prepend, quote_ident, ExprFactory}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; -pub(super) struct BrandCheckHandler<'a> { - /// Mark for the private `WeakSet` variable. +pub(super) struct Private { pub mark: Mark, + pub class_name: Ident, + pub ident: AHashMap, +} - pub class_name: &'a Ident, +pub(super) struct PrivateRecord(Vec); +impl PrivateRecord { + pub fn new() -> Self { + PrivateRecord(Vec::new()) + } + + pub fn curr_class(&self) -> &Ident { + &self.0.last().unwrap().class_name + } + + pub fn curr_mark(&self) -> Mark { + self.0.last().unwrap().mark + } + + pub fn push(&mut self, p: Private) { + self.0.push(p) + } + + pub fn pop(&mut self) { + self.0.pop(); + } + + pub fn get(&self, name: &JsWord) -> (Mark, PrivateKind, &Ident) { + for p in self.0.iter().rev() { + if let Some(kind) = p.ident.get(name) { + return (p.mark, kind.clone(), &p.class_name); + } + } + // TODO: better error information with span + panic!("Private name #{name} is not defined."); + } +} + +#[derive(Clone, PartialEq)] +pub(super) struct PrivateKind { + pub is_static: bool, + pub is_method: bool, +} + +pub(super) struct BrandCheckHandler<'a> { /// Private names used for brand checks. pub names: &'a mut AHashSet, - pub statics: &'a AHashSet, + pub private: &'a PrivateRecord, } impl VisitMut for BrandCheckHandler<'_> { @@ -31,20 +76,14 @@ impl VisitMut for BrandCheckHandler<'_> { left, right, }) if left.is_private_name() => { - let n = match &**left { - Expr::PrivateName(ref n) => n, - _ => { - unreachable!() - } - }; + let n = left.as_private_name().unwrap(); if let Expr::Ident(right) = &**right { - if self.class_name.sym == right.sym - && self.class_name.span.ctxt == right.span.ctxt - { + let curr_class = self.private.curr_class(); + if curr_class.sym == right.sym && curr_class.span.ctxt == right.span.ctxt { *e = Expr::Bin(BinExpr { span: *span, op: op!("==="), - left: Box::new(Expr::Ident(self.class_name.clone())), + left: Box::new(Expr::Ident(curr_class.clone())), right: Box::new(Expr::Ident(right.clone())), }); return; @@ -53,22 +92,20 @@ impl VisitMut for BrandCheckHandler<'_> { self.names.insert(n.id.sym.clone()); - let is_static = self.statics.contains(&n.id.sym); + let (mark, kind, class_name) = self.private.get(&n.id.sym); - if is_static { + if kind.is_static { *e = Expr::Bin(BinExpr { span: *span, op: op!("==="), left: right.take(), - right: Box::new(Expr::Ident(self.class_name.clone())), + right: Box::new(Expr::Ident(class_name.clone())), }); return; } - let weak_coll_ident = Ident::new( - format!("_{}", n.id.sym).into(), - n.id.span.apply_mark(self.mark), - ); + let weak_coll_ident = + Ident::new(format!("_{}", n.id.sym).into(), n.id.span.apply_mark(mark)); *e = Expr::Call(CallExpr { span: *span, @@ -84,14 +121,8 @@ impl VisitMut for BrandCheckHandler<'_> { } pub(super) struct FieldAccessFolder<'a> { - /// Mark for the private `WeakSet` variable. - pub mark: Mark, - pub method_mark: Mark, - - pub class_name: &'a Ident, - pub private_methods: &'a AHashSet, pub vars: Vec, - pub statics: &'a AHashSet, + pub private: &'a PrivateRecord, pub in_assign_pat: bool, } @@ -158,17 +189,14 @@ impl<'a> VisitMut for FieldAccessFolder<'a> { let obj = arg.obj.clone(); - let is_static = self.statics.contains(&n.id.sym); - let ident = Ident::new( - format!("_{}", n.id.sym).into(), - n.id.span.apply_mark(self.mark), - ); + let (mark, kind, class_name) = self.private.get(&n.id.sym); + let ident = Ident::new(format!("_{}", n.id.sym).into(), n.id.span.apply_mark(mark)); let var = alias_ident_for(&obj, "_ref"); let this = if matches!(*obj, Expr::This(..)) { ThisExpr { span: DUMMY_SP }.as_arg() - } else if is_static { + } else if kind.is_static { obj.as_arg() } else { self.vars.push(VarDeclarator { @@ -234,19 +262,14 @@ impl<'a> VisitMut for FieldAccessFolder<'a> { .as_arg() }; - let expr = if is_static { + let expr = if kind.is_static { Expr::Call(CallExpr { span: DUMMY_SP, callee: helper!( class_static_private_field_spec_set, "classStaticPrivateFieldSpecSet" ), - args: vec![ - this, - self.class_name.clone().as_arg(), - ident.as_arg(), - value, - ], + args: vec![this, class_name.clone().as_arg(), ident.as_arg(), value], type_args: Default::default(), }) @@ -297,11 +320,8 @@ impl<'a> VisitMut for FieldAccessFolder<'a> { let obj = left.obj.clone(); - let is_static = self.statics.contains(&n.id.sym); - let ident = Ident::new( - format!("_{}", n.id.sym).into(), - n.id.span.apply_mark(self.mark), - ); + let (mark, kind, class_name) = self.private.get(&n.id.sym); + let ident = Ident::new(format!("_{}", n.id.sym).into(), n.id.span.apply_mark(mark)); let var = alias_ident_for(&obj, "_ref"); @@ -339,19 +359,14 @@ impl<'a> VisitMut for FieldAccessFolder<'a> { .as_arg() }; - if is_static { + if kind.is_static { *e = Expr::Call(CallExpr { span: DUMMY_SP, callee: helper!( class_static_private_field_spec_set, "classStaticPrivateFieldSpecSet" ), - args: vec![ - this, - self.class_name.clone().as_arg(), - ident.as_arg(), - value, - ], + args: vec![this, class_name.clone().as_arg(), ident.as_arg(), value], type_args: Default::default(), }); @@ -488,21 +503,15 @@ impl<'a> FieldAccessFolder<'a> { let mut obj = e.obj.take(); - let is_method = self.private_methods.contains(&n.id.sym); - let is_static = self.statics.contains(&n.id.sym); + let (mark, kind, class_name) = self.private.get(&n.id.sym); let method_name = Ident::new( n.id.sym.clone(), - n.id.span - .with_ctxt(SyntaxContext::empty()) - .apply_mark(self.method_mark), - ); - let ident = Ident::new( - format!("_{}", n.id.sym).into(), - n.id.span.apply_mark(self.mark), + n.id.span.with_ctxt(SyntaxContext::empty()).apply_mark(mark), ); + let ident = Ident::new(format!("_{}", n.id.sym).into(), n.id.span.apply_mark(mark)); - if is_static { - if is_method { + if kind.is_static { + if kind.is_method { let h = helper!( class_static_private_method_get, "classStaticPrivateMethodGet" @@ -514,12 +523,12 @@ impl<'a> FieldAccessFolder<'a> { callee: h, args: vec![ obj.as_arg(), - self.class_name.clone().as_arg(), + class_name.clone().as_arg(), method_name.as_arg(), ], type_args: Default::default(), }), - Some(Expr::Ident(self.class_name.clone())), + Some(Expr::Ident(class_name.clone())), ); } @@ -532,14 +541,10 @@ impl<'a> FieldAccessFolder<'a> { Expr::Call(CallExpr { span: DUMMY_SP, callee: get, - args: vec![ - obj.as_arg(), - self.class_name.clone().as_arg(), - ident.as_arg(), - ], + args: vec![obj.as_arg(), class_name.clone().as_arg(), ident.as_arg()], type_args: Default::default(), }), - Some(Expr::Ident(self.class_name.clone())), + Some(Expr::Ident(class_name.clone())), ) } else { if self.in_assign_pat { @@ -564,7 +569,7 @@ impl<'a> FieldAccessFolder<'a> { }; } - let get = if is_method { + let get = if kind.is_method { helper!(class_private_method_get, "classPrivateMethodGet") } else { helper!(class_private_field_get, "classPrivateFieldGet") @@ -572,7 +577,7 @@ impl<'a> FieldAccessFolder<'a> { match &*obj { Expr::This(this) => ( - if is_method { + if kind.is_method { CallExpr { span: DUMMY_SP, callee: get, @@ -622,7 +627,7 @@ impl<'a> FieldAccessFolder<'a> { var.clone().as_arg() }; - let args = if is_method { + let args = if kind.is_method { vec![first_arg, ident.as_arg(), method_name.as_arg()] } else { vec![first_arg, ident.as_arg()] diff --git a/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs b/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs index ae43ef539a3..cc5cb695218 100644 --- a/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs +++ b/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs @@ -4930,6 +4930,8 @@ test!( issue_1333_1, " class Foo { + #ws; + #ws2; get connected() { return this.#ws2 && this.#ws.readyState === _ws1.default.OPEN; } @@ -4941,7 +4943,19 @@ test!( return _classPrivateFieldGet(this, _ws2) && _classPrivateFieldGet(this, _ws).readyState \ === _ws1.default.OPEN; } + constructor(){ + _ws.set(this, { + writable: true, + value: void 0 + }); + _ws2.set(this, { + writable: true, + value: void 0 + }); + } } + var _ws = new WeakMap(); + var _ws2 = new WeakMap(); " ); @@ -4952,6 +4966,8 @@ test!( " class Test { #ws; + #serialization; + #seq; _packet(raw) { /** @type {DiscordPacket} */ @@ -5112,9 +5128,19 @@ test!( writable: true, value: void 0 }); + _serialization.set(this, { + writable: true, + value: void 0 + }); + _seq.set(this, { + writable: true, + value: void 0 + }); } } var _ws = new WeakMap(); + var _serialization = new WeakMap(); + var _seq = new WeakMap(); " ); @@ -5125,6 +5151,7 @@ test!( " class Test { #ws; + #serialization; _packet(raw) { /** @type {DiscordPacket} */ @@ -5165,10 +5192,14 @@ test!( writable: true, value: void 0 }); + _serialization.set(this, { + writable: true, + value: void 0 + }); } } var _ws = new WeakMap(); - + var _serialization = new WeakMap(); " ); @@ -5179,6 +5210,7 @@ test!( " class Test { #ws; + #serialization; _packet(raw) { /** @type {DiscordPacket} */ @@ -5206,9 +5238,14 @@ test!( writable: true, value: void 0 }); + _serialization.set(this, { + writable: true, + value: void 0 + }); } } var _ws = new WeakMap(); + var _serialization = new WeakMap(); " ); @@ -5218,6 +5255,7 @@ test!( issue_1333_5, " class Test { + #serialization; _packet(raw) { pak = this.#serialization.decode(raw); } @@ -5228,7 +5266,14 @@ test!( _packet(raw) { pak = _classPrivateFieldGet(this, _serialization).decode(raw); } + constructor(){ + _serialization.set(this, { + writable: true, + value: void 0 + }); + } } + var _serialization = new WeakMap(); " ); @@ -5238,6 +5283,7 @@ test!( issue_1333_6, " class Test { + #serialization; _packet(raw) { this.#serialization.decode(raw); } @@ -5248,7 +5294,14 @@ test!( _packet(raw) { _classPrivateFieldGet(this, _serialization).decode(raw); } + constructor(){ + _serialization.set(this, { + writable: true, + value: void 0 + }); + } } + var _serialization = new WeakMap(); " ); @@ -5638,6 +5691,7 @@ test!( issue_3229_1, " class A { + #D; B() { 1; C.#D++; @@ -5653,7 +5707,14 @@ class A { _classPrivateFieldSet(_C = C, _D, (_this_D = +_classPrivateFieldGet(_C, _D)) + 1), _this_D; E(function() {}); } + constructor(){ + _D.set(this, { + writable: true, + value: void 0 + }); + } } +var _D = new WeakMap(); " ); @@ -5663,6 +5724,7 @@ test!( issue_3229_2, " class A { + #b; foo() { A.#b += 123 class B { @@ -5680,9 +5742,96 @@ class A { foo() {} } } + constructor(){ + _b.set(this, { + writable: true, + value: void 0 + }); + } } +var _b = new WeakMap(); " ); + +test!( + syntax(), + |_| class_properties(class_properties::Config { loose: false }), + issue_3368, + " +class A { + #a = 'fff' + static #b = 123 + foo() { + return class B { + bar() { + console.log(this.#a, this.#b, this.#bar) + } + } + } + #bar() {} +} +", + " +var _bar = new WeakSet(); +class A { + foo() { + return class B { + bar() { + console.log(_classPrivateFieldGet(this, _a), _classStaticPrivateFieldSpecGet(this, \ + A, _b), _classPrivateMethodGet(this, _bar, bar)); + } + }; + } + constructor(){ + _a.set(this, { + writable: true, + value: 'fff' + }); + _bar.add(this); + } +} +var _a = new WeakMap(); +var _b = { + writable: true, + value: 123 +}; +function bar() {} +" +); + +test!( + syntax(), + |_| class_properties(class_properties::Config { loose: false }), + nested_class_in_arrow, + " +const a = () => class { + a = 123 + foo() { + return class B { + b = 456 + } + } +} +", + " +const a = ()=>{ + class _class { + foo() { + return class B { + constructor() { + _defineProperty(this, 'b', 456); + } + }; + } + constructor(){ + _defineProperty(this, 'a', 123); + } + } + return _class; +}; +" +); + #[testing::fixture("tests/fixture/classes/**/exec.js")] fn exec(input: PathBuf) { let src = read_to_string(&input).unwrap(); diff --git a/crates/swc_ecma_transforms_compat/tests/private-in-object/private/nested-class-other-redeclared/output.js b/crates/swc_ecma_transforms_compat/tests/private-in-object/private/nested-class-other-redeclared/output.js index ec96a83e2eb..8f498ec1271 100644 --- a/crates/swc_ecma_transforms_compat/tests/private-in-object/private/nested-class-other-redeclared/output.js +++ b/crates/swc_ecma_transforms_compat/tests/private-in-object/private/nested-class-other-redeclared/output.js @@ -2,7 +2,7 @@ let Foo = function() { "use strict"; function Foo() { _classCallCheck(this, Foo); - _foo1.set(this, { + _foo.set(this, { writable: true, value: 1 }); @@ -35,12 +35,12 @@ let Foo = function() { return Nested; }(); var _bar1 = new WeakMap(); - _foo1.has(this); + _foo.has(this); _bar.has(this); } } ]); return Foo; }(); -var _foo1 = new WeakMap(); +var _foo = new WeakMap(); var _bar = new WeakMap(); diff --git a/crates/swc_ecma_transforms_compat/tests/private-in-object/private/nested-class/output.js b/crates/swc_ecma_transforms_compat/tests/private-in-object/private/nested-class/output.js index 778a02a0294..48884ce30f1 100644 --- a/crates/swc_ecma_transforms_compat/tests/private-in-object/private/nested-class/output.js +++ b/crates/swc_ecma_transforms_compat/tests/private-in-object/private/nested-class/output.js @@ -2,7 +2,7 @@ let Foo = function() { "use strict"; function Foo() { _classCallCheck(this, Foo); - _foo1.set(this, { + _foo.set(this, { writable: true, value: 1 }); @@ -25,10 +25,10 @@ let Foo = function() { ]); return Nested; }(); - _foo1.has(this); + _foo.has(this); } } ]); return Foo; }(); -var _foo1 = new WeakMap(); +var _foo = new WeakMap();