feat(es/compat): Support readonly and writeonly for private fields (#6182)

This commit is contained in:
Austaras 2022-10-18 13:38:36 +08:00 committed by GitHub
parent a029ca2430
commit aefc11b8a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 296 additions and 131 deletions

View File

@ -57,7 +57,7 @@ fn init_helpers() -> Arc<PathBuf> {
let helper_dir = project_root.join("packages").join("swc-helpers");
let yarn = find_executable("yarn").expect("failed to find yarn");
let npm = find_executable("npm").expect("failed to find yarn");
let npm = find_executable("npm").expect("failed to find npm");
{
let mut cmd = if cfg!(target_os = "windows") {
let mut c = Command::new("cmd");

View File

@ -2,6 +2,7 @@
import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
import _read_only_error from "@swc/helpers/src/_read_only_error.mjs";
var _prop = /*#__PURE__*/ new WeakMap(), _roProp = /*#__PURE__*/ new WeakMap();
class A1 {
constructor(name){
@ -14,7 +15,7 @@ class A1 {
set: void 0
});
_class_private_field_set(this, _prop, "");
_class_private_field_set(this, _roProp, ""); // Error
this, _read_only_error("#roProp"); // Error
console.log(_class_private_field_get(this, _prop));
console.log(_class_private_field_get(this, _roProp));
}

View File

@ -1,18 +1,18 @@
//// [privateNameMethodAssignment.ts]
import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
import _class_private_field_update from "@swc/helpers/src/_class_private_field_update.mjs";
import _class_private_method_get from "@swc/helpers/src/_class_private_method_get.mjs";
import _class_private_method_init from "@swc/helpers/src/_class_private_method_init.mjs";
import _read_only_error from "@swc/helpers/src/_read_only_error.mjs";
import _class_private_field_destructure from "@swc/helpers/src/_class_private_field_destructure.mjs";
var _method = /*#__PURE__*/ new WeakSet();
class A3 {
constructor(a, b){
_class_private_method_init(this, _method);
_class_private_field_set(this, _method, ()=>{} // Error, not writable
);
_class_private_field_set(a, _method, ()=>{}); // Error, not writable
_class_private_field_set(b, _method, ()=>{} //Error, not writable
);
this, _read_only_error("#method") // Error, not writable
;
a, _read_only_error("#method"); // Error, not writable
b, _read_only_error("#method") //Error, not writable
;
({ x: _class_private_field_destructure(this, _method).value } = {
x: ()=>{}
}); //Error, not writable

View File

@ -1,10 +1,10 @@
//// [privateNameReadonly.ts]
import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
import _class_private_method_init from "@swc/helpers/src/_class_private_method_init.mjs";
import _read_only_error from "@swc/helpers/src/_read_only_error.mjs";
var _bar, _class;
const C = (_bar = /*#__PURE__*/ new WeakSet(), _class = class {
foo() {
_class_private_field_set(this, _bar, console.log("should log this then throw"));
this, console.log("should log this then throw"), _read_only_error("#bar");
}
constructor(){
_class_private_method_init(this, _bar);

View File

@ -1,10 +1,10 @@
//// [privateNameReadonly.ts]
var _bar;
import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
import _class_private_method_init from "@swc/helpers/src/_class_private_method_init.mjs";
import _read_only_error from "@swc/helpers/src/_read_only_error.mjs";
let C = (_bar = new WeakSet(), class {
foo() {
_class_private_field_set(this, _bar, console.log("should log this then throw"));
console.log("should log this then throw"), _read_only_error("#bar");
}
constructor(){
_class_private_method_init(this, _bar);

View File

@ -1,11 +1,11 @@
//// [privateNameSetterNoGetter.ts]
import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
import _write_only_error from "@swc/helpers/src/_write_only_error.mjs";
var _x, _class;
const C = (_x = /*#__PURE__*/ new WeakMap(), _class = class {
m() {
_class_private_field_set(this, _x, _class_private_field_get(this, _x) + 2); // Error
_class_private_field_set(this, _x, (this, _write_only_error("#x")) + 2); // Error
}
constructor(){
_class_private_field_init(this, _x, {

View File

@ -1,11 +1,11 @@
//// [privateNameSetterNoGetter.ts]
var _x;
import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
import _write_only_error from "@swc/helpers/src/_write_only_error.mjs";
let C = (_x = new WeakMap(), class {
m() {
_class_private_field_set(this, _x, _class_private_field_get(this, _x) + 2);
_class_private_field_set(this, _x, _write_only_error("#x") + 2);
}
constructor(){
_class_private_field_init(this, _x, {

View File

@ -1,8 +1,8 @@
//// [privateWriteOnlyAccessorRead.ts]
import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
import _extends from "@swc/helpers/src/_extends.mjs";
import _write_only_error from "@swc/helpers/src/_write_only_error.mjs";
import _class_private_field_destructure from "@swc/helpers/src/_class_private_field_destructure.mjs";
var _value = /*#__PURE__*/ new WeakMap(), _valueRest = /*#__PURE__*/ new WeakMap(), _valueOne = /*#__PURE__*/ new WeakMap(), _valueCompound = /*#__PURE__*/ new WeakMap();
class Test {
@ -10,14 +10,14 @@ class Test {
const foo = {
bar: 1
};
console.log(_class_private_field_get(this, _value)); // error
console.log((this, _write_only_error("#value"))); // error
_class_private_field_set(this, _value, {
foo
}); // ok
_class_private_field_set(this, _value, {
foo
}); // ok
_class_private_field_get(this, _value).foo = foo; // error
(this, _write_only_error("#value")).foo = foo; // error
({ o: _class_private_field_destructure(this, _value).value } = {
o: {
foo
@ -27,15 +27,15 @@ class Test {
_tmp = {
foo
}, _class_private_field_destructure(this, _value).value = _extends({}, _tmp), _tmp; //ok
({ foo: _class_private_field_get(this, _value).foo } = {
({ foo: (this, _write_only_error("#value")).foo } = {
foo
}); //error
var _tmp1;
_tmp1 = {
foo
}, _class_private_field_get(this, _value).foo = _extends({}, _tmp1.foo), ({ foo: {} } = _tmp1), _tmp1; //error
}, (this, _write_only_error("#value")).foo = _extends({}, _tmp1.foo), ({ foo: {} } = _tmp1), _tmp1; //error
let r = {
o: _class_private_field_get(this, _value)
o: (this, _write_only_error("#value"))
}; //error
[_class_private_field_destructure(this, _valueOne).value, ..._class_private_field_destructure(this, _valueRest).value] = [
1,
@ -43,10 +43,10 @@ class Test {
3
];
let arr = [
_class_private_field_get(this, _valueOne),
..._class_private_field_get(this, _valueRest)
(this, _write_only_error("#valueOne")),
...(this, _write_only_error("#valueRest"))
];
_class_private_field_set(this, _valueCompound, _class_private_field_get(this, _valueCompound) + 3);
_class_private_field_set(this, _valueCompound, (this, _write_only_error("#valueCompound")) + 3);
}
constructor(){
_class_private_field_init(this, _value, {

View File

@ -1,8 +1,8 @@
//// [privateWriteOnlyAccessorRead.ts]
import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
import _extends from "@swc/helpers/src/_extends.mjs";
import _write_only_error from "@swc/helpers/src/_write_only_error.mjs";
import _class_private_field_destructure from "@swc/helpers/src/_class_private_field_destructure.mjs";
var _value = new WeakMap(), _valueRest = new WeakMap(), _valueOne = new WeakMap(), _valueCompound = new WeakMap();
function set_value(v) {}
@ -15,28 +15,28 @@ new class {
let foo = {
bar: 1
};
console.log(_class_private_field_get(this, _value)), _class_private_field_set(this, _value, {
console.log(_write_only_error("#value")), _class_private_field_set(this, _value, {
foo
}), _class_private_field_set(this, _value, {
foo
}), _class_private_field_get(this, _value).foo = foo, ({ o: _class_private_field_destructure(this, _value).value } = {
}), _write_only_error("#value").foo = foo, ({ o: _class_private_field_destructure(this, _value).value } = {
o: {
foo
}
}), _class_private_field_destructure(this, _value).value = _extends({}, {
foo
}), ({ foo: _class_private_field_get(this, _value).foo } = {
}), ({ foo: _write_only_error("#value").foo } = {
foo
}), _tmp = {
foo
}, _class_private_field_get(this, _value).foo = _extends({}, _tmp.foo), _class_private_field_get(this, _value), [_class_private_field_destructure(this, _valueOne).value, ..._class_private_field_destructure(this, _valueRest).value] = [
}, _write_only_error("#value").foo = _extends({}, _tmp.foo), _write_only_error("#value"), [_class_private_field_destructure(this, _valueOne).value, ..._class_private_field_destructure(this, _valueRest).value] = [
1,
2,
3
], [
_class_private_field_get(this, _valueOne),
..._class_private_field_get(this, _valueRest)
], _class_private_field_set(this, _valueCompound, _class_private_field_get(this, _valueCompound) + 3);
_write_only_error("#valueOne"),
..._write_only_error("#valueRest")
], _class_private_field_set(this, _valueCompound, _write_only_error("#valueCompound") + 3);
}
constructor(){
_class_private_field_init(this, _value, {

View File

@ -1,5 +1,8 @@
function _classApplyDescriptorUpdate(receiver, descriptor) {
if (descriptor.set) {
if (!descriptor.get) {
throw new TypeError("attempted to read set only private field");
}
if (!("__destrWrapper" in descriptor)) {
descriptor.__destrWrapper = {
set value(v) {

View File

@ -1,3 +1,3 @@
function _readOnlyError(name) {
throw new Error("\"" + name + "\" is read-only");
throw new TypeError("\"" + name + "\" is read-only");
}

View File

@ -0,0 +1,3 @@
function _writeOnlyError(name) {
throw new TypeError("\"" + name + "\" is write-only");
}

View File

@ -334,6 +334,7 @@ define_helpers!(Helpers {
set_prototype_of,
is_native_function
),
write_only_error: (),
class_private_field_destructure: (
class_extract_field_descriptor,

View File

@ -70,6 +70,15 @@ pub(super) struct PrivateKind {
}
impl PrivateKind {
fn is_readonly(&self) -> bool {
self.is_method && !self.has_setter
}
fn is_writeonly(&self) -> bool {
// a private method can still be read
self.is_method && !self.has_getter && self.has_setter
}
fn is_method(&self) -> bool {
self.is_method && !self.has_getter && !self.has_setter
}
@ -274,9 +283,9 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
let var = alias_ident_for(&obj, "_ref");
let this = if matches!(*obj, Expr::This(..)) {
ThisExpr { span: DUMMY_SP }.as_arg()
Box::new(ThisExpr { span: DUMMY_SP }.into())
} else if *op == op!("=") {
obj.as_arg()
obj
} else {
self.vars.push(VarDeclarator {
span: DUMMY_SP,
@ -284,27 +293,31 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
init: None,
definite: false,
});
Box::new(
AssignExpr {
span: obj.span(),
left: PatOrExpr::Pat(var.clone().into()),
op: op!("="),
right: obj,
}
.as_arg()
.into(),
)
};
let value = if *op == op!("=") {
right.take().as_arg()
right.take()
} else {
let left = Box::new(self.visit_mut_private_get(&mut left, Some(var)).0);
Box::new(
BinExpr {
span: DUMMY_SP,
left,
op: op.to_update().unwrap(),
right: right.take(),
}
.as_arg()
.into(),
)
};
if kind.is_static {
@ -314,17 +327,34 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
class_static_private_field_spec_set,
"classStaticPrivateFieldSpecSet"
),
args: vec![this, class_name.clone().as_arg(), ident.as_arg(), value],
args: vec![
this.as_arg(),
class_name.clone().as_arg(),
ident.as_arg(),
value.as_arg(),
],
type_args: Default::default(),
});
} else if kind.is_readonly() {
let err = Expr::Call(CallExpr {
span: DUMMY_SP,
callee: helper!(read_only_error, "readOnlyError"),
args: vec![format!("#{}", n.id.sym).as_arg()],
type_args: None,
})
.into();
*e = Expr::Seq(SeqExpr {
span: *span,
exprs: vec![this, value, err],
});
} else {
let set = helper!(class_private_field_set, "classPrivateFieldSet");
*e = Expr::Call(CallExpr {
span: DUMMY_SP,
callee: set,
args: vec![this, ident.as_arg(), value],
args: vec![this.as_arg(), ident.as_arg(), value.as_arg()],
type_args: Default::default(),
});
@ -642,7 +672,7 @@ impl<'a> PrivateAccessVisitor<'a> {
"classPrivateFieldDestructureSet"
);
return (
(
CallExpr {
span: DUMMY_SP,
callee: set,
@ -652,12 +682,12 @@ impl<'a> PrivateAccessVisitor<'a> {
}
.make_member(quote_ident!("value")),
Some(*obj),
);
)
}
PrivateAccessType::Update => {
let set = helper!(class_private_field_update, "classPrivateFieldUpdate");
return (
(
CallExpr {
span: DUMMY_SP,
callee: set,
@ -667,11 +697,31 @@ impl<'a> PrivateAccessVisitor<'a> {
}
.make_member(quote_ident!("value")),
Some(*obj),
);
}
_ => {}
)
}
PrivateAccessType::Get if kind.is_writeonly() => {
let helper = helper!(write_only_error, "writeOnlyError");
let expr = Box::new(
CallExpr {
span: DUMMY_SP,
callee: helper,
args: vec![format!("#{}", n.id.sym).as_arg()],
type_args: None,
}
.into(),
);
(
SeqExpr {
span: DUMMY_SP,
exprs: vec![obj.clone(), expr],
}
.into(),
Some(*obj),
)
}
PrivateAccessType::Get => {
let get = if self.c.private_as_properties {
helper!(class_private_field_loose_base, "classPrivateFieldLooseBase")
} else if kind.is_method() {
@ -686,7 +736,11 @@ impl<'a> PrivateAccessVisitor<'a> {
CallExpr {
span: DUMMY_SP,
callee: get,
args: vec![obj.clone().as_arg(), ident.as_arg(), method_name.as_arg()],
args: vec![
obj.clone().as_arg(),
ident.as_arg(),
method_name.as_arg(),
],
type_args: Default::default(),
}
.into()
@ -752,6 +806,8 @@ impl<'a> PrivateAccessVisitor<'a> {
}
}
}
}
}
}
/// only getter and setter in same scope could coexist

View File

@ -6549,6 +6549,100 @@ function set_privateFieldValue(newValue) {
"#
);
test!(
syntax(),
|t| class_properties(Some(t.comments.clone()), Default::default()),
set_only_getter,
r#"
class Cl {
#privateField = 0;
counter = 0;
get #privateFieldValue() {
return this.#privateField;
}
get self() {
this.counter++;
return this;
}
constructor() {
this.self.#privateFieldValue = 1;
([this.self.#privateFieldValue] = [1]);
}
}
const cl = new Cl();
"#,
r##"
var _privateField = new WeakMap(), _privateFieldValue = new WeakMap();
class Cl {
get self() {
this.counter++;
return this;
}
constructor(){
_classPrivateFieldInit(this, _privateFieldValue, {
get: get_privateFieldValue,
set: void 0
});
_classPrivateFieldInit(this, _privateField, {
writable: true,
value: 0
});
_defineProperty(this, "counter", 0);
this.self, _readOnlyError("#privateFieldValue");
[_classPrivateFieldDestructureSet(this.self, _privateFieldValue).value] = [
1
];
}
}
function get_privateFieldValue() {
return _classPrivateFieldGet(this, _privateField);
}
const cl = new Cl();
"##
);
test!(
syntax(),
|t| class_properties(Some(t.comments.clone()), Default::default()),
get_only_setter,
r#"
class Cl {
#privateField = 0;
set #privateFieldValue(newValue) {
this.#privateField = newValue;
}
constructor() {
this.publicField = this.#privateFieldValue;
}
}
"#,
r##"
var _privateField = new WeakMap(), _privateFieldValue = new WeakMap();
class Cl {
constructor(){
_classPrivateFieldInit(this, _privateFieldValue, {
get: void 0,
set: set_privateFieldValue
});
_classPrivateFieldInit(this, _privateField, {
writable: true,
value: 0
});
this.publicField = (this, _writeOnlyError("#privateFieldValue"));
}
}
function set_privateFieldValue(newValue) {
_classPrivateFieldSet(this, _privateField, newValue);
}
"##
);
#[testing::fixture("tests/classes/**/exec.js")]
fn exec(input: PathBuf) {
let src = read_to_string(&input).unwrap();

View File

@ -1,5 +1,8 @@
export default function _classApplyDescriptorUpdate(receiver, descriptor) {
if (descriptor.set) {
if (!descriptor.get) {
throw new TypeError("attempted to read set only private field");
}
if (!("__destrWrapper" in descriptor)) {
descriptor.__destrWrapper = {
set value(v) {

View File

@ -1,3 +1,3 @@
export default function _readOnlyError(name) {
throw new Error("\"" + name + "\" is read-only");
throw new TypeError("\"" + name + "\" is read-only");
}

View File

@ -0,0 +1,3 @@
export default function _writeOnlyError(name) {
throw new TypeError("\"" + name + "\" is write-only");
}

View File

@ -78,4 +78,5 @@ export { default as typeOf } from './_type_of.mjs';
export { default as unsupportedIterableToArray } from './_unsupported_iterable_to_array.mjs';
export { default as wrapAsyncGenerator } from './_wrap_async_generator.mjs';
export { default as wrapNativeSuper } from './_wrap_native_super.mjs';
export { default as writeOnlyError } from './_write_only_error.mjs';
export { __decorate, __generator, __metadata, __param, __values } from 'tslib'