Skip to content

Commit

Permalink
Handle cases when ?? and ?. is in binding initializers (#12032)
Browse files Browse the repository at this point in the history
* test: add test for nullish coalescing

Co-Authored-By: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>

* test: add control group

* test: add tests for optional chaining

Co-Authored-By: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>

* test: add tests on optional chaining mixed with private class elements

* fix: wrap member chains to IIFE when it is in parameter default

* chore: add more testcases

* chore: update test fixtures

* fix: NodePath.get is always non nullish

Co-authored-by: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>
  • Loading branch information
JLHwung and nicolo-ribaudo committed Oct 3, 2020
1 parent 9808d25 commit 5f83a8c
Show file tree
Hide file tree
Showing 33 changed files with 932 additions and 9 deletions.
24 changes: 22 additions & 2 deletions packages/babel-helper-member-expression-to-functions/src/index.js
Expand Up @@ -84,7 +84,7 @@ const handle = {
},

handle(member) {
const { node, parent, parentPath } = member;
const { node, parent, parentPath, scope } = member;

if (member.isOptionalMemberExpression()) {
// Transforming optional chaining requires we replace ancestors.
Expand Down Expand Up @@ -118,6 +118,17 @@ const handle = {
return true;
});

// Replace `function (a, x = a.b?.#c) {}` to `function (a, x = (() => a.b?.#c)() ){}`
// so the temporary variable can be injected in correct scope
// This can be further optimized to avoid unecessary IIFE
if (scope.path.isPattern()) {
endPath.replaceWith(
// The injected member will be queued and eventually transformed when visited
t.callExpression(t.arrowFunctionExpression([], endPath.node), []),
);
return;
}

const rootParentPath = endPath.parentPath;
if (
rootParentPath.isUpdateExpression({ argument: node }) ||
Expand Down Expand Up @@ -165,7 +176,6 @@ const handle = {
);
}

const { scope } = member;
const startingProp = startingOptional.isOptionalMemberExpression()
? "object"
: "callee";
Expand Down Expand Up @@ -362,6 +372,16 @@ const handle = {

// MEMBER?.(ARGS) -> _optionalCall(MEMBER, ARGS)
if (parentPath.isOptionalCallExpression({ callee: node })) {
// Replace `function (a, x = a.b.#c?.()) {}` to `function (a, x = (() => a.b.#c?.())() ){}`
// so the temporary variable can be injected in correct scope
// This can be further optimized to avoid unecessary IIFE
if (scope.path.isPattern()) {
parentPath.replaceWith(
// The injected member will be queued and eventually transformed when visited
t.callExpression(t.arrowFunctionExpression([], parentPath.node), []),
);
return;
}
parentPath.replaceWith(this.optionalCall(member, parent.arguments));
return;
}
Expand Down
@@ -0,0 +1,46 @@
class Foo {
static #x = 1;
static #m = function() { return this.#x; };
static #self = Foo;
static self = Foo;
static getSelf() { return this }

static test() {
const o = { Foo: Foo };
const deep = { very: { o } };
function fn() {
return o;
}
function fnDeep() {
return deep;
}

function f(o, r = o?.Foo.#m()) {
return r;
}

function g(o, r = o?.Foo.#self.getSelf().#m()) {
return r;
}

function h(fnDeep, r = fnDeep?.().very.o?.Foo?.#m()) {
return r;
}

function i(fn, r = fn?.().Foo.#self?.getSelf()?.self.#m()) {
return r;
}

function j(fn, r = (fn().Foo.#self.getSelf().self.#m)?.()) {
return r;
}

expect(f(o)).toBe(1);
expect(g(o)).toBe(1);
expect(h(fnDeep)).toBe(1);
expect(i(fn)).toBe(1);
expect(j(fn)).toBe(1);
}
}

Foo.test();
@@ -0,0 +1,46 @@
class Foo {
static #x = 1;
static #m = function() { return this.#x; };
static #self = Foo;
static self = Foo;
static getSelf() { return this }

static test() {
const o = { Foo: Foo };
const deep = { very: { o } };
function fn() {
return o;
}
function fnDeep() {
return deep;
}

function f(o, r = o?.Foo.#m()) {
return r;
}

function g(o, r = o?.Foo.#self.getSelf().#m?.()) {
return r;
}

function h(fnDeep, r = fnDeep?.().very.o?.Foo?.#m()) {
return r;
}

function i(fn, r = fn?.().Foo.#self?.getSelf()?.self.#m()) {
return r;
}

function j(fn, r = (fn().Foo.#self.getSelf().self.#m)?.()) {
return r;
}

f(o);
g(o);
h(fnDeep);
i(fn);
j(fn);
}
}

Foo.test();
@@ -0,0 +1,6 @@
{
"plugins": [
["proposal-optional-chaining", { "loose": true }],
["proposal-class-properties", { "loose": true }]
]
}
@@ -0,0 +1,96 @@
function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }

var id = 0;

function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }

var _x = _classPrivateFieldLooseKey("x");

var _m = _classPrivateFieldLooseKey("m");

var _self = _classPrivateFieldLooseKey("self");

class Foo {
static getSelf() {
return this;
}

static test() {
const o = {
Foo: Foo
};
const deep = {
very: {
o
}
};

function fn() {
return o;
}

function fnDeep() {
return deep;
}

function f(o, r = (() => o === null || o === void 0 ? void 0 : _classPrivateFieldLooseBase(o.Foo, _m)[_m]())()) {
return r;
}

function g(o, r = (() => {
var _ref;

return (_ref = (() => o === null || o === void 0 ? void 0 : _classPrivateFieldLooseBase(_classPrivateFieldLooseBase(o.Foo, _self)[_self].getSelf(), _m)[_m])()) == null ? void 0 : _ref();
})()) {
return r;
}

function h(fnDeep, r = (() => {
var _fnDeep$very$o$Foo, _fnDeep$very$o;

return (_fnDeep$very$o$Foo = fnDeep == null ? void 0 : (_fnDeep$very$o = fnDeep().very.o) == null ? void 0 : _fnDeep$very$o.Foo) === null || _fnDeep$very$o$Foo === void 0 ? void 0 : _classPrivateFieldLooseBase(_fnDeep$very$o$Foo, _m)[_m]();
})()) {
return r;
}

function i(fn, r = (() => {
var _getSelf, _ref2;

return (_getSelf = (_ref2 = (() => fn === null || fn === void 0 ? void 0 : _classPrivateFieldLooseBase(fn().Foo, _self)[_self])()) == null ? void 0 : _ref2.getSelf()) === null || _getSelf === void 0 ? void 0 : _classPrivateFieldLooseBase(_getSelf.self, _m)[_m]();
})()) {
return r;
}

function j(fn, r = (() => {
var _classPrivateFieldLoo, _classPrivateFieldLoo2;

return (_classPrivateFieldLoo = (_classPrivateFieldLoo2 = _classPrivateFieldLooseBase(_classPrivateFieldLooseBase(fn().Foo, _self)[_self].getSelf().self, _m))[_m]) == null ? void 0 : _classPrivateFieldLoo.call(_classPrivateFieldLoo2);
})()) {
return r;
}

f(o);
g(o);
h(fnDeep);
i(fn);
j(fn);
}

}

Object.defineProperty(Foo, _x, {
writable: true,
value: 1
});
Object.defineProperty(Foo, _m, {
writable: true,
value: function () {
return _classPrivateFieldLooseBase(this, _x)[_x];
}
});
Object.defineProperty(Foo, _self, {
writable: true,
value: Foo
});
Foo.self = Foo;
Foo.test();
@@ -0,0 +1,46 @@
class Foo {
static #x = 1;
static #m = function() { return this.#x; };
static #self = Foo;
static self = Foo;
static getSelf() { return this }

static test() {
const o = { Foo: Foo };
const deep = { very: { o } };
function fn() {
return o;
}
function fnDeep() {
return deep;
}

function f(o, r = o?.Foo.#m()) {
return r;
}

function g(o, r = o?.Foo.#self.getSelf().#m()) {
return r;
}

function h(fnDeep, r = fnDeep?.().very.o?.Foo?.#m()) {
return r;
}

function i(fn, r = fn?.().Foo.#self?.getSelf()?.self.#m()) {
return r;
}

function j(fn, r = (fn().Foo.#self.getSelf().self.#m)?.()) {
return r;
}

expect(f(o)).toBe(1);
expect(g(o)).toBe(1);
expect(h(fnDeep)).toBe(1);
expect(i(fn)).toBe(1);
expect(j(fn)).toBe(1);
}
}

Foo.test();
@@ -0,0 +1,46 @@
class Foo {
static #x = 1;
static #m = function() { return this.#x; };
static #self = Foo;
static self = Foo;
static getSelf() { return this }

static test() {
const o = { Foo: Foo };
const deep = { very: { o } };
function fn() {
return o;
}
function fnDeep() {
return deep;
}

function f(o, r = o?.Foo.#m()) {
return r;
}

function g(o, r = o?.Foo.#self.getSelf().#m?.()) {
return r;
}

function h(fnDeep, r = fnDeep?.().very.o?.Foo?.#m()) {
return r;
}

function i(fn, r = fn?.().Foo.#self?.getSelf()?.self.#m()) {
return r;
}

function j(fn, r = (fn().Foo.#self.getSelf().self.#m)?.()) {
return r;
}

f(o);
g(o);
h(fnDeep);
i(fn);
j(fn);
}
}

Foo.test();
@@ -0,0 +1,4 @@
{
"plugins": [["proposal-class-properties", { "loose": true }]],
"minNodeVersion": "14.0.0"
}

0 comments on commit 5f83a8c

Please sign in to comment.