fix: ensure (a?.b)() has proper this (#11623)

* fix: ensure (a?.b)() has proper this

* let test be more restrictive

* fix: transformed member call should preserve computed

* chore: revamp test files

* refactor: simplify

* fix: unwrap parthenthesizedExpression

* add loose test cases

* add `(a?.#b)()` support

* add with-transform test cases

* Update packages/babel-plugin-proposal-optional-chaining/src/index.js

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>

* address review comments

* update test fixtures

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
This commit is contained in:
Huáng Jùnliàng 2020-06-01 10:25:22 -04:00 committed by GitHub
parent 3a3457d808
commit 1e115aed33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 929 additions and 8 deletions

View File

@ -241,6 +241,15 @@ const privateNameHandlerSpec = {
]);
},
boundGet(member) {
this.memoise(member, 1);
return t.callExpression(
t.memberExpression(this.get(member), t.identifier("bind")),
[this.receiver(member)],
);
},
set(member, value) {
const { classRef, privateNamesMap, file } = this;
const { name } = member.node.property.id;
@ -323,6 +332,13 @@ const privateNameHandlerLoose = {
});
},
boundGet(member) {
return t.callExpression(
t.memberExpression(this.get(member), t.identifier("bind")),
[t.cloneNode(member.node.object)],
);
},
simpleSet(member) {
return this.get(member);
},

View File

@ -167,6 +167,9 @@ const handle = {
const parentIsOptionalCall = parentPath.isOptionalCallExpression({
callee: node,
});
const isParenthesizedMemberCall =
parentPath.isCallExpression({ callee: node }) &&
node.extra?.parenthesized;
startingOptional.replaceWith(toNonOptional(startingOptional, baseRef));
if (parentIsOptionalCall) {
if (parent.optional) {
@ -174,6 +177,9 @@ const handle = {
} else {
parentPath.replaceWith(this.call(member, parent.arguments));
}
} else if (isParenthesizedMemberCall) {
// `(a?.#b)()` to `(a == null ? void 0 : a.#b.bind(a))()`
member.replaceWith(this.boundGet(member));
} else {
member.replaceWith(this.get(member));
}

View File

@ -0,0 +1,48 @@
class Foo {
static #x = 1;
static self = Foo;
static #m = function() { return this.#x; };
static getSelf() { return Foo }
test() {
const o = { Foo: Foo };
const fn = function () {
return o;
};
expect((Foo?.#m)()).toEqual(1);
expect((Foo?.#m)().toString).toEqual(1..toString);
expect((Foo?.#m)().toString()).toEqual('1');
expect((o?.Foo.#m)()).toEqual(1);
expect((o?.Foo.#m)().toString).toEqual(1..toString);
expect((o?.Foo.#m)().toString()).toEqual('1');
expect((((o.Foo?.self.getSelf)())?.#m)()).toEqual(1);
expect((((o.Foo.self?.getSelf)())?.#m)()).toEqual(1);
expect((((fn()?.Foo?.self.getSelf)())?.#m)()).toEqual(1);
expect((((fn?.().Foo.self?.getSelf)())?.#m)()).toEqual(1);
}
testNull() {
const o = null;
const fn = function () {
return { o };
}
expect(() => { (o?.Foo.#m)() }).toThrow();
expect(() => { (o?.Foo.#m)().toString }).toThrow();
expect(() => { (o?.Foo.#m)().toString() }).toThrow();
expect(() => { (((o.Foo?.self.getSelf)())?.#m)() }).toThrow();
expect(() => { (((o.Foo.self?.getSelf)())?.#m)() }).toThrow();
expect(() => (((fn()?.Foo?.self.getSelf)())?.#m)()).toThrow();
expect(() => (((fn?.().Foo.self?.getSelf)())?.#m)()).toThrow();
}
}
(new Foo).test();
(new Foo).testNull();

View File

@ -0,0 +1,30 @@
class Foo {
static #x = 1;
static self = Foo;
static #m = function() { return this.#x; };
static getSelf() { return Foo }
test() {
const o = { Foo: Foo };
const fn = function () {
return o;
};
(Foo?.#m)();
(Foo?.#m)().toString;
(Foo?.#m)().toString();
(o?.Foo.#m)();
(o?.Foo.#m)().toString;
(o?.Foo.#m)().toString();
(((o.Foo?.self.getSelf)())?.#m)();
(((o.Foo.self?.getSelf)())?.#m)();
(((fn()?.Foo?.self.getSelf)())?.#m)();
(((fn?.().Foo.self?.getSelf)())?.#m)();
}
}
(new Foo).test();

View File

@ -0,0 +1,7 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["proposal-class-properties", { "loose": true }],
["proposal-optional-chaining", { "loose": true }]
]
}

View File

@ -0,0 +1,46 @@
class Foo {
static getSelf() {
return Foo;
}
test() {
var _o$Foo$self$getSelf, _o$Foo$self$getSelf2, _fn$Foo$self$getSelf, _fn$Foo$self$getSelf2, _o$Foo, _o$Foo$self, _fn, _fn$Foo, _fn$Foo$self, _fn$Foo$self2;
const o = {
Foo: Foo
};
const fn = function () {
return o;
};
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(Foo, _m)[_m].bind(Foo))();
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(Foo, _m)[_m].bind(Foo))().toString;
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(Foo, _m)[_m].bind(Foo))().toString();
(o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o.Foo, _m)[_m].bind(o.Foo))();
(o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o.Foo, _m)[_m].bind(o.Foo))().toString;
(o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o.Foo, _m)[_m].bind(o.Foo))().toString();
((_o$Foo$self$getSelf = ((_o$Foo = o.Foo) == null ? void 0 : _o$Foo.self.getSelf.bind(_o$Foo.self))()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$Foo$self$getSelf, _m)[_m].bind(_o$Foo$self$getSelf))();
((_o$Foo$self$getSelf2 = ((_o$Foo$self = o.Foo.self) == null ? void 0 : _o$Foo$self.getSelf.bind(_o$Foo$self))()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$Foo$self$getSelf2, _m)[_m].bind(_o$Foo$self$getSelf2))();
((_fn$Foo$self$getSelf = ((_fn = fn()) == null ? void 0 : (_fn$Foo = _fn.Foo) == null ? void 0 : _fn$Foo.self.getSelf.bind(_fn.Foo.self))()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_fn$Foo$self$getSelf, _m)[_m].bind(_fn$Foo$self$getSelf))();
((_fn$Foo$self$getSelf2 = (fn == null ? void 0 : (_fn$Foo$self2 = _fn$Foo$self = fn().Foo.self) == null ? void 0 : _fn$Foo$self2.getSelf.bind(_fn$Foo$self))()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_fn$Foo$self$getSelf2, _m)[_m].bind(_fn$Foo$self$getSelf2))();
}
}
var _x = babelHelpers.classPrivateFieldLooseKey("x");
var _m = babelHelpers.classPrivateFieldLooseKey("m");
Object.defineProperty(Foo, _x, {
writable: true,
value: 1
});
Foo.self = Foo;
Object.defineProperty(Foo, _m, {
writable: true,
value: function () {
return babelHelpers.classPrivateFieldLooseBase(this, _x)[_x];
}
});
new Foo().test();

View File

@ -0,0 +1,48 @@
class Foo {
static #x = 1;
static self = Foo;
static #m = function() { return this.#x; };
static getSelf() { return Foo }
test() {
const o = { Foo: Foo };
const fn = function () {
return o;
};
expect((Foo?.#m)()).toEqual(1);
expect((Foo?.#m)().toString).toEqual(1..toString);
expect((Foo?.#m)().toString()).toEqual('1');
expect((o?.Foo.#m)()).toEqual(1);
expect((o?.Foo.#m)().toString).toEqual(1..toString);
expect((o?.Foo.#m)().toString()).toEqual('1');
expect((((o.Foo?.self.getSelf)())?.#m)()).toEqual(1);
expect((((o.Foo.self?.getSelf)())?.#m)()).toEqual(1);
expect((((fn()?.Foo?.self.getSelf)())?.#m)()).toEqual(1);
expect((((fn?.().Foo.self?.getSelf)())?.#m)()).toEqual(1);
}
testNull() {
const o = null;
const fn = function () {
return { o };
}
expect(() => { (o?.Foo.#m)() }).toThrow();
expect(() => { (o?.Foo.#m)().toString }).toThrow();
expect(() => { (o?.Foo.#m)().toString() }).toThrow();
expect(() => { (((o.Foo?.self.getSelf)())?.#m)() }).toThrow();
expect(() => { (((o.Foo.self?.getSelf)())?.#m)() }).toThrow();
expect(() => (((fn()?.Foo?.self.getSelf)())?.#m)()).toThrow();
expect(() => (((fn?.().Foo.self?.getSelf)())?.#m)()).toThrow();
}
}
(new Foo).test();
(new Foo).testNull();

View File

@ -0,0 +1,30 @@
class Foo {
static #x = 1;
static self = Foo;
static #m = function() { return this.#x; };
static getSelf() { return Foo }
test() {
const o = { Foo: Foo };
const fn = function () {
return o;
};
(Foo?.#m)();
(Foo?.#m)().toString;
(Foo?.#m)().toString();
(o?.Foo.#m)();
(o?.Foo.#m)().toString;
(o?.Foo.#m)().toString();
(((o.Foo?.self.getSelf)())?.#m)();
(((o.Foo.self?.getSelf)())?.#m)();
(((fn()?.Foo?.self.getSelf)())?.#m)();
(((fn?.().Foo.self?.getSelf)())?.#m)();
}
}
(new Foo).test();

View File

@ -0,0 +1,7 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
"proposal-class-properties"
],
"minNodeVersion": "14.0.0"
}

View File

@ -0,0 +1,42 @@
class Foo {
static getSelf() {
return Foo;
}
test() {
var _o$Foo, _o$Foo2, _o$Foo3, _o$Foo$self$getSelf, _o$Foo$self$getSelf2, _fn$Foo$self$getSelf, _fn$Foo$self$getSelf2;
const o = {
Foo: Foo
};
const fn = function () {
return o;
};
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))();
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString;
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString();
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo = o.Foo, Foo, _m).bind(_o$Foo))();
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo2 = o.Foo, Foo, _m).bind(_o$Foo2))().toString;
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo3 = o.Foo, Foo, _m).bind(_o$Foo3))().toString();
((_o$Foo$self$getSelf = (o.Foo?.self.getSelf)()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf, Foo, _m).bind(_o$Foo$self$getSelf))();
((_o$Foo$self$getSelf2 = (o.Foo.self?.getSelf)()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf2, Foo, _m).bind(_o$Foo$self$getSelf2))();
((_fn$Foo$self$getSelf = (fn()?.Foo?.self.getSelf)()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf, Foo, _m).bind(_fn$Foo$self$getSelf))();
((_fn$Foo$self$getSelf2 = (fn?.().Foo.self?.getSelf)()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf2, Foo, _m).bind(_fn$Foo$self$getSelf2))();
}
}
var _x = {
writable: true,
value: 1
};
babelHelpers.defineProperty(Foo, "self", Foo);
var _m = {
writable: true,
value: function () {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _x);
}
};
new Foo().test();

View File

@ -0,0 +1,48 @@
class Foo {
static #x = 1;
static self = Foo;
static #m = function() { return this.#x; };
static getSelf() { return Foo }
test() {
const o = { Foo: Foo };
const fn = function () {
return o;
};
expect((Foo?.#m)()).toEqual(1);
expect((Foo?.#m)().toString).toEqual(1..toString);
expect((Foo?.#m)().toString()).toEqual('1');
expect((o?.Foo.#m)()).toEqual(1);
expect((o?.Foo.#m)().toString).toEqual(1..toString);
expect((o?.Foo.#m)().toString()).toEqual('1');
expect((((o.Foo?.self.getSelf)())?.#m)()).toEqual(1);
expect((((o.Foo.self?.getSelf)())?.#m)()).toEqual(1);
expect((((fn()?.Foo?.self.getSelf)())?.#m)()).toEqual(1);
expect((((fn?.().Foo.self?.getSelf)())?.#m)()).toEqual(1);
}
testNull() {
const o = null;
const fn = function () {
return { o };
}
expect(() => { (o?.Foo.#m)() }).toThrow();
expect(() => { (o?.Foo.#m)().toString }).toThrow();
expect(() => { (o?.Foo.#m)().toString() }).toThrow();
expect(() => { (((o.Foo?.self.getSelf)())?.#m)() }).toThrow();
expect(() => { (((o.Foo.self?.getSelf)())?.#m)() }).toThrow();
expect(() => (((fn()?.Foo?.self.getSelf)())?.#m)()).toThrow();
expect(() => (((fn?.().Foo.self?.getSelf)())?.#m)()).toThrow();
}
}
(new Foo).test();
(new Foo).testNull();

View File

@ -0,0 +1,30 @@
class Foo {
static #x = 1;
static self = Foo;
static #m = function() { return this.#x; };
static getSelf() { return Foo }
test() {
const o = { Foo: Foo };
const fn = function () {
return o;
};
(Foo?.#m)();
(Foo?.#m)().toString;
(Foo?.#m)().toString();
(o?.Foo.#m)();
(o?.Foo.#m)().toString;
(o?.Foo.#m)().toString();
(((o.Foo?.self.getSelf)())?.#m)();
(((o.Foo.self?.getSelf)())?.#m)();
(((fn()?.Foo?.self.getSelf)())?.#m)();
(((fn?.().Foo.self?.getSelf)())?.#m)();
}
}
(new Foo).test();

View File

@ -0,0 +1,7 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
"proposal-class-properties",
"proposal-optional-chaining"
]
}

View File

@ -0,0 +1,42 @@
class Foo {
static getSelf() {
return Foo;
}
test() {
var _o$Foo, _o$Foo2, _o$Foo3, _o$Foo$self$getSelf, _o$Foo$self$getSelf2, _fn$Foo$self$getSelf, _fn$Foo$self$getSelf2, _o$Foo4, _o$Foo4$self, _o$Foo$self, _fn, _fn$Foo$self, _fn$Foo, _fn$Foo$self2, _fn$Foo$self3;
const o = {
Foo: Foo
};
const fn = function () {
return o;
};
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))();
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString;
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString();
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo = o.Foo, Foo, _m).bind(_o$Foo))();
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo2 = o.Foo, Foo, _m).bind(_o$Foo2))().toString;
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo3 = o.Foo, Foo, _m).bind(_o$Foo3))().toString();
((_o$Foo$self$getSelf = ((_o$Foo4 = o.Foo) === null || _o$Foo4 === void 0 ? void 0 : (_o$Foo4$self = _o$Foo4.self).getSelf.bind(_o$Foo4$self))()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf, Foo, _m).bind(_o$Foo$self$getSelf))();
((_o$Foo$self$getSelf2 = ((_o$Foo$self = o.Foo.self) === null || _o$Foo$self === void 0 ? void 0 : _o$Foo$self.getSelf.bind(_o$Foo$self))()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf2, Foo, _m).bind(_o$Foo$self$getSelf2))();
((_fn$Foo$self$getSelf = ((_fn = fn()) === null || _fn === void 0 ? void 0 : (_fn$Foo = _fn.Foo) === null || _fn$Foo === void 0 ? void 0 : (_fn$Foo$self = _fn$Foo.self).getSelf.bind(_fn$Foo$self))()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf, Foo, _m).bind(_fn$Foo$self$getSelf))();
((_fn$Foo$self$getSelf2 = (fn === null || fn === void 0 ? void 0 : (_fn$Foo$self3 = _fn$Foo$self2 = fn().Foo.self) === null || _fn$Foo$self3 === void 0 ? void 0 : _fn$Foo$self3.getSelf.bind(_fn$Foo$self2))()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf2, Foo, _m).bind(_fn$Foo$self$getSelf2))();
}
}
var _x = {
writable: true,
value: 1
};
babelHelpers.defineProperty(Foo, "self", Foo);
var _m = {
writable: true,
value: function () {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _x);
}
};
new Foo().test();

View File

@ -0,0 +1,48 @@
class Foo {
static #x = 1;
static self = Foo;
static #m = function() { return this.#x; };
static getSelf() { return Foo }
test() {
const o = { Foo: Foo };
const fn = function () {
return o;
};
expect((Foo?.#m)()).toEqual(1);
expect((Foo?.#m)().toString).toEqual(1..toString);
expect((Foo?.#m)().toString()).toEqual('1');
expect((o?.Foo.#m)()).toEqual(1);
expect((o?.Foo.#m)().toString).toEqual(1..toString);
expect((o?.Foo.#m)().toString()).toEqual('1');
expect((((o.Foo?.self.getSelf)())?.#m)()).toEqual(1);
expect((((o.Foo.self?.getSelf)())?.#m)()).toEqual(1);
expect((((fn()?.Foo?.self.getSelf)())?.#m)()).toEqual(1);
expect((((fn?.().Foo.self?.getSelf)())?.#m)()).toEqual(1);
}
testNull() {
const o = null;
const fn = function () {
return { o };
}
expect(() => { (o?.Foo.#m)() }).toThrow();
expect(() => { (o?.Foo.#m)().toString }).toThrow();
expect(() => { (o?.Foo.#m)().toString() }).toThrow();
expect(() => { (((o.Foo?.self.getSelf)())?.#m)() }).toThrow();
expect(() => { (((o.Foo.self?.getSelf)())?.#m)() }).toThrow();
expect(() => (((fn()?.Foo?.self.getSelf)())?.#m)()).toThrow();
expect(() => (((fn?.().Foo.self?.getSelf)())?.#m)()).toThrow();
}
}
(new Foo).test();
(new Foo).testNull();

View File

@ -0,0 +1,30 @@
class Foo {
static #x = 1;
static self = Foo;
static #m = function() { return this.#x; };
static getSelf() { return Foo }
test() {
const o = { Foo: Foo };
const fn = function () {
return o;
};
(Foo?.#m)();
(Foo?.#m)().toString;
(Foo?.#m)().toString();
(o?.Foo.#m)();
(o?.Foo.#m)().toString;
(o?.Foo.#m)().toString();
(((o.Foo?.self.getSelf)())?.#m)();
(((o.Foo.self?.getSelf)())?.#m)();
(((fn()?.Foo?.self.getSelf)())?.#m)();
(((fn?.().Foo.self?.getSelf)())?.#m)();
}
}
(new Foo).test();

View File

@ -0,0 +1,7 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
"proposal-class-properties"
],
"minNodeVersion": "14.0.0"
}

View File

@ -0,0 +1,42 @@
class Foo {
static getSelf() {
return Foo;
}
test() {
var _o$Foo, _o$Foo2, _o$Foo3, _o$Foo$self$getSelf, _o$Foo$self$getSelf2, _fn$Foo$self$getSelf, _fn$Foo$self$getSelf2;
const o = {
Foo: Foo
};
const fn = function () {
return o;
};
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))();
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString;
(Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString();
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo = o.Foo, Foo, _m).bind(_o$Foo))();
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo2 = o.Foo, Foo, _m).bind(_o$Foo2))().toString;
(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo3 = o.Foo, Foo, _m).bind(_o$Foo3))().toString();
((_o$Foo$self$getSelf = (o.Foo?.self.getSelf)()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf, Foo, _m).bind(_o$Foo$self$getSelf))();
((_o$Foo$self$getSelf2 = (o.Foo.self?.getSelf)()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf2, Foo, _m).bind(_o$Foo$self$getSelf2))();
((_fn$Foo$self$getSelf = (fn()?.Foo?.self.getSelf)()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf, Foo, _m).bind(_fn$Foo$self$getSelf))();
((_fn$Foo$self$getSelf2 = (fn?.().Foo.self?.getSelf)()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf2, Foo, _m).bind(_fn$Foo$self$getSelf2))();
}
}
var _x = {
writable: true,
value: 1
};
babelHelpers.defineProperty(Foo, "self", Foo);
var _m = {
writable: true,
value: function () {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _x);
}
};
new Foo().test();

View File

@ -23,14 +23,17 @@ export default declare((api, options) => {
visitor: {
"OptionalCallExpression|OptionalMemberExpression"(path) {
const { parentPath, scope } = path;
const { scope } = path;
const parentPath = path.findParent(p => !p.isParenthesizedExpression());
let isDeleteOperation = false;
const optionals = [];
let optionalPath = path;
while (
optionalPath.isOptionalMemberExpression() ||
optionalPath.isOptionalCallExpression()
optionalPath.isOptionalCallExpression() ||
optionalPath.isParenthesizedExpression() ||
optionalPath.isTSNonNullExpression()
) {
const { node } = optionalPath;
if (node.optional) {
@ -43,10 +46,8 @@ export default declare((api, options) => {
} else if (optionalPath.isOptionalCallExpression()) {
optionalPath.node.type = "CallExpression";
optionalPath = optionalPath.get("callee");
}
// unwrap a TSNonNullExpression if need
if (optionalPath.isTSNonNullExpression()) {
} else {
// unwrap TSNonNullExpression/ParenthesizedExpression if needed
optionalPath = optionalPath.get("expression");
}
}
@ -113,7 +114,36 @@ export default declare((api, options) => {
);
}
}
let replacement = replacementPath.node;
// Ensure (a?.b)() has proper `this`
if (
t.isMemberExpression(replacement) &&
(replacement.extra?.parenthesized ||
// if replacementPath.parentPath does not equal parentPath,
// it must be unwrapped from parenthesized expression.
replacementPath.parentPath !== parentPath) &&
parentPath.isCallExpression()
) {
// `(a?.b)()` to `(a == null ? undefined : a.b.bind(a))()`
const { object } = replacement;
let baseRef;
if (!loose || !isSimpleMemberExpression(object)) {
// memoize the context object in non-loose mode
// `(a?.b.c)()` to `(a == null ? undefined : (_a$b = a.b).c.bind(_a$b))()`
baseRef = scope.maybeGenerateMemoised(object);
if (baseRef) {
replacement.object = t.assignmentExpression(
"=",
baseRef,
object,
);
}
}
replacement = t.callExpression(
t.memberExpression(replacement, t.identifier("bind")),
[t.cloneNode(baseRef ?? object)],
);
}
replacementPath.replaceWith(
t.conditionalExpression(
loose
@ -134,7 +164,7 @@ export default declare((api, options) => {
isDeleteOperation
? t.booleanLiteral(true)
: scope.buildUndefinedNode(),
replacementPath.node,
replacement,
),
);

View File

@ -0,0 +1,51 @@
class Foo {
constructor() {
this.x = 1;
this.self = this;
}
m() { return this.x; };
getSelf() { return this }
test() {
const Foo = this;
const o = { Foo: Foo };
const fn = function () {
return o;
};
expect((Foo?.["m"])()).toEqual(1);
expect((Foo?.["m"])().toString).toEqual(1..toString);
expect((Foo?.["m"])().toString()).toEqual('1');
expect(((Foo?.["m"]))()).toEqual(1);
expect(((Foo?.["m"]))().toString).toEqual(1..toString);
expect(((Foo?.["m"]))().toString()).toEqual('1');
expect((o?.Foo.m)()).toEqual(1);
expect((o?.Foo.m)().toString).toEqual(1..toString);
expect((o?.Foo.m)().toString()).toEqual('1');
expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1);
expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1);
expect((((fn()?.Foo?.self.getSelf)())?.m)()).toEqual(1);
expect((((fn?.().Foo.self?.getSelf)())?.m)()).toEqual(1);
}
testNull() {
const o = null;
expect(() => { (o?.Foo.m)() }).toThrow();
expect(() => { (o?.Foo.m)().toString }).toThrow();
expect(() => { (o?.Foo.m)().toString() }).toThrow();
expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow();
expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow();
expect(() => (((fn()?.Foo?.self.getSelf)())?.m)()).toThrow();
expect(() => (((fn?.().Foo.self?.getSelf)())?.m)()).toThrow();
}
}
(new Foo).test();
(new Foo).testNull();

View File

@ -0,0 +1,6 @@
{
"plugins": [["proposal-optional-chaining", { "loose": true }]],
"parserOpts": {
"createParenthesizedExpressions": true
}
}

View File

@ -0,0 +1,51 @@
class Foo {
constructor() {
this.x = 1;
this.self = this;
}
m() { return this.x; };
getSelf() { return this }
test() {
const Foo = this;
const o = { Foo: Foo };
const fn = function () {
return o;
};
expect((Foo?.["m"])()).toEqual(1);
expect((Foo?.["m"])().toString).toEqual(1..toString);
expect((Foo?.["m"])().toString()).toEqual('1');
expect(((Foo?.["m"]))()).toEqual(1);
expect(((Foo?.["m"]))().toString).toEqual(1..toString);
expect(((Foo?.["m"]))().toString()).toEqual('1');
expect((o?.Foo.m)()).toEqual(1);
expect((o?.Foo.m)().toString).toEqual(1..toString);
expect((o?.Foo.m)().toString()).toEqual('1');
expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1);
expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1);
expect((((fn()?.Foo?.self.getSelf)())?.m)()).toEqual(1);
expect((((fn?.().Foo.self?.getSelf)())?.m)()).toEqual(1);
}
testNull() {
const o = null;
expect(() => { (o?.Foo.m)() }).toThrow();
expect(() => { (o?.Foo.m)().toString }).toThrow();
expect(() => { (o?.Foo.m)().toString() }).toThrow();
expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow();
expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow();
expect(() => (((fn()?.Foo?.self.getSelf)())?.m)()).toThrow();
expect(() => (((fn?.().Foo.self?.getSelf)())?.m)()).toThrow();
}
}
(new Foo).test();
(new Foo).testNull();

View File

@ -0,0 +1,6 @@
{
"plugins": ["proposal-optional-chaining"],
"parserOpts": {
"createParenthesizedExpressions": true
}
}

View File

@ -0,0 +1,47 @@
class Foo {
constructor() {
this.x = 1;
this.self = this;
}
m() { return this.x; };
getSelf() { return this }
test() {
const Foo = this;
const o = { Foo: Foo };
const fn = function () {
return o;
};
expect((Foo?.["m"])()).toEqual(1);
expect((Foo?.["m"])().toString).toEqual(1..toString);
expect((Foo?.["m"])().toString()).toEqual('1');
expect((o?.Foo.m)()).toEqual(1);
expect((o?.Foo.m)().toString).toEqual(1..toString);
expect((o?.Foo.m)().toString()).toEqual('1');
expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1);
expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1);
expect((((fn()?.Foo?.self.getSelf)())?.m)()).toEqual(1);
expect((((fn?.().Foo.self?.getSelf)())?.m)()).toEqual(1);
}
testNull() {
const o = null;
expect(() => { (o?.Foo.m)() }).toThrow();
expect(() => { (o?.Foo.m)().toString }).toThrow();
expect(() => { (o?.Foo.m)().toString() }).toThrow();
expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow();
expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow();
expect(() => (((fn()?.Foo?.self.getSelf)())?.m)()).toThrow();
expect(() => (((fn?.().Foo.self?.getSelf)())?.m)()).toThrow();
}
}
(new Foo).test();
(new Foo).testNull();

View File

@ -0,0 +1,32 @@
class Foo {
constructor() {
this.x = 1;
this.self = this;
}
m() { return this.x; };
getSelf() { return this }
test() {
const Foo = this;
const o = { Foo: Foo };
const fn = function () {
return o;
};
(Foo?.["m"])();
(Foo?.["m"])().toString;
(Foo?.["m"])().toString();
(o?.Foo.m)();
(o?.Foo.m)().toString;
(o?.Foo.m)().toString();
(((o.Foo?.self.getSelf)())?.m)();
(((o.Foo.self?.getSelf)())?.m)();
(((fn()?.Foo?.self.getSelf)())?.m)();
(((fn?.().Foo.self?.getSelf)())?.m)();
}
}
(new Foo).test();

View File

@ -0,0 +1,3 @@
{
"plugins": [["proposal-optional-chaining", { "loose": true }]]
}

View File

@ -0,0 +1,41 @@
class Foo {
constructor() {
this.x = 1;
this.self = this;
}
m() {
return this.x;
}
getSelf() {
return this;
}
test() {
var _o$Foo$self$getSelf, _o$Foo, _o$Foo$self$getSelf2, _o$Foo$self, _fn$Foo$self$getSelf, _fn, _fn$Foo, _fn$Foo$self$getSelf2, _fn$Foo$self, _fn$Foo$self2;
const Foo = this;
const o = {
Foo: Foo
};
const fn = function () {
return o;
};
(Foo == null ? void 0 : Foo["m"].bind(Foo))();
(Foo == null ? void 0 : Foo["m"].bind(Foo))().toString;
(Foo == null ? void 0 : Foo["m"].bind(Foo))().toString();
(o == null ? void 0 : o.Foo.m.bind(o.Foo))();
(o == null ? void 0 : o.Foo.m.bind(o.Foo))().toString;
(o == null ? void 0 : o.Foo.m.bind(o.Foo))().toString();
((_o$Foo$self$getSelf = ((_o$Foo = o.Foo) == null ? void 0 : _o$Foo.self.getSelf.bind(_o$Foo.self))()) == null ? void 0 : _o$Foo$self$getSelf.m.bind(_o$Foo$self$getSelf))();
((_o$Foo$self$getSelf2 = ((_o$Foo$self = o.Foo.self) == null ? void 0 : _o$Foo$self.getSelf.bind(_o$Foo$self))()) == null ? void 0 : _o$Foo$self$getSelf2.m.bind(_o$Foo$self$getSelf2))();
((_fn$Foo$self$getSelf = ((_fn = fn()) == null ? void 0 : (_fn$Foo = _fn.Foo) == null ? void 0 : _fn$Foo.self.getSelf.bind(_fn.Foo.self))()) == null ? void 0 : _fn$Foo$self$getSelf.m.bind(_fn$Foo$self$getSelf))();
((_fn$Foo$self$getSelf2 = (fn == null ? void 0 : (_fn$Foo$self2 = _fn$Foo$self = fn().Foo.self) == null ? void 0 : _fn$Foo$self2.getSelf.bind(_fn$Foo$self))()) == null ? void 0 : _fn$Foo$self$getSelf2.m.bind(_fn$Foo$self$getSelf2))();
}
}
new Foo().test();

View File

@ -0,0 +1,47 @@
class Foo {
constructor() {
this.x = 1;
this.self = this;
}
m() { return this.x; };
getSelf() { return this }
test() {
const Foo = this;
const o = { Foo: Foo };
const fn = function () {
return o;
};
expect((Foo?.["m"])()).toEqual(1);
expect((Foo?.["m"])().toString).toEqual(1..toString);
expect((Foo?.["m"])().toString()).toEqual('1');
expect((o?.Foo.m)()).toEqual(1);
expect((o?.Foo.m)().toString).toEqual(1..toString);
expect((o?.Foo.m)().toString()).toEqual('1');
expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1);
expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1);
expect((((fn()?.Foo?.self.getSelf)())?.m)()).toEqual(1);
expect((((fn?.().Foo.self?.getSelf)())?.m)()).toEqual(1);
}
testNull() {
const o = null;
expect(() => { (o?.Foo.m)() }).toThrow();
expect(() => { (o?.Foo.m)().toString }).toThrow();
expect(() => { (o?.Foo.m)().toString() }).toThrow();
expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow();
expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow();
expect(() => (((fn()?.Foo?.self.getSelf)())?.m)()).toThrow();
expect(() => (((fn?.().Foo.self?.getSelf)())?.m)()).toThrow();
}
}
(new Foo).test();
(new Foo).testNull();

View File

@ -0,0 +1,32 @@
class Foo {
constructor() {
this.x = 1;
this.self = this;
}
m() { return this.x; };
getSelf() { return this }
test() {
const Foo = this;
const o = { Foo: Foo };
const fn = function () {
return o;
};
(Foo?.["m"])();
(Foo?.["m"])().toString;
(Foo?.["m"])().toString();
(o?.Foo.m)();
(o?.Foo.m)().toString;
(o?.Foo.m)().toString();
(((o.Foo?.self.getSelf)())?.m)();
(((o.Foo.self?.getSelf)())?.m)();
(((fn()?.Foo?.self.getSelf)())?.m)();
(((fn?.().Foo.self?.getSelf)())?.m)();
}
}
(new Foo).test();

View File

@ -0,0 +1,41 @@
class Foo {
constructor() {
this.x = 1;
this.self = this;
}
m() {
return this.x;
}
getSelf() {
return this;
}
test() {
var _o$Foo, _o$Foo2, _o$Foo3, _o$Foo$self$getSelf, _o$Foo4, _o$Foo4$self, _o$Foo$self$getSelf2, _o$Foo$self, _fn$Foo$self$getSelf, _fn, _fn$Foo$self, _fn$Foo, _fn$Foo$self$getSelf2, _fn$Foo$self2, _fn$Foo$self3;
const Foo = this;
const o = {
Foo: Foo
};
const fn = function () {
return o;
};
(Foo === null || Foo === void 0 ? void 0 : Foo["m"].bind(Foo))();
(Foo === null || Foo === void 0 ? void 0 : Foo["m"].bind(Foo))().toString;
(Foo === null || Foo === void 0 ? void 0 : Foo["m"].bind(Foo))().toString();
(o === null || o === void 0 ? void 0 : (_o$Foo = o.Foo).m.bind(_o$Foo))();
(o === null || o === void 0 ? void 0 : (_o$Foo2 = o.Foo).m.bind(_o$Foo2))().toString;
(o === null || o === void 0 ? void 0 : (_o$Foo3 = o.Foo).m.bind(_o$Foo3))().toString();
((_o$Foo$self$getSelf = ((_o$Foo4 = o.Foo) === null || _o$Foo4 === void 0 ? void 0 : (_o$Foo4$self = _o$Foo4.self).getSelf.bind(_o$Foo4$self))()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : _o$Foo$self$getSelf.m.bind(_o$Foo$self$getSelf))();
((_o$Foo$self$getSelf2 = ((_o$Foo$self = o.Foo.self) === null || _o$Foo$self === void 0 ? void 0 : _o$Foo$self.getSelf.bind(_o$Foo$self))()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : _o$Foo$self$getSelf2.m.bind(_o$Foo$self$getSelf2))();
((_fn$Foo$self$getSelf = ((_fn = fn()) === null || _fn === void 0 ? void 0 : (_fn$Foo = _fn.Foo) === null || _fn$Foo === void 0 ? void 0 : (_fn$Foo$self = _fn$Foo.self).getSelf.bind(_fn$Foo$self))()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : _fn$Foo$self$getSelf.m.bind(_fn$Foo$self$getSelf))();
((_fn$Foo$self$getSelf2 = (fn === null || fn === void 0 ? void 0 : (_fn$Foo$self3 = _fn$Foo$self2 = fn().Foo.self) === null || _fn$Foo$self3 === void 0 ? void 0 : _fn$Foo$self3.getSelf.bind(_fn$Foo$self2))()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : _fn$Foo$self$getSelf2.m.bind(_fn$Foo$self$getSelf2))();
}
}
new Foo().test();