Skip to content

Commit

Permalink
fix: ensure (a?.b)() has proper this (#11623)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
JLHwung and jridgewell committed Jun 1, 2020
1 parent 3a3457d commit 1e115ae
Show file tree
Hide file tree
Showing 30 changed files with 929 additions and 8 deletions.
16 changes: 16 additions & 0 deletions packages/babel-helper-create-class-features-plugin/src/fields.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
},
Expand Down
Expand Up @@ -167,13 +167,19 @@ 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) {
parentPath.replaceWith(this.optionalCall(member, parent.arguments));
} 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));
}
Expand Down
@@ -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();
@@ -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();
@@ -0,0 +1,7 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["proposal-class-properties", { "loose": true }],
["proposal-optional-chaining", { "loose": true }]
]
}
@@ -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();
@@ -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();
@@ -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();
@@ -0,0 +1,7 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
"proposal-class-properties"
],
"minNodeVersion": "14.0.0"
}
@@ -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();
@@ -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();
@@ -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();

0 comments on commit 1e115ae

Please sign in to comment.