Skip to content

Commit

Permalink
Fix nested classes reference private fields (#11405)
Browse files Browse the repository at this point in the history
* Fix nested classes reference private fields

* Process only visible private fields when redeclaring

* Comments

* Skip class traversal if there are no private fields

* Handle redeclared private field in computed key
  • Loading branch information
jridgewell committed Apr 14, 2020
1 parent aaced01 commit 9b48a8e
Show file tree
Hide file tree
Showing 21 changed files with 597 additions and 10 deletions.
48 changes: 38 additions & 10 deletions packages/babel-helper-create-class-features-plugin/src/fields.js
Expand Up @@ -85,24 +85,50 @@ const privateNameVisitor = {
const { privateNamesMap } = this;
const body = path.get("body.body");

const visiblePrivateNames = new Map(privateNamesMap);
const redeclared = [];
for (const prop of body) {
if (!prop.isPrivate()) {
continue;
}
if (!privateNamesMap.has(prop.node.key.id.name)) continue;
if (!prop.isPrivate()) continue;
const { name } = prop.node.key.id;
visiblePrivateNames.delete(name);
redeclared.push(name);
}

// This class redeclares the private name.
// So, we can only evaluate the things in the outer scope.
path.traverse(privateNameInnerVisitor, this);
path.skip();
break;
// If the class doesn't redeclare any private fields, we can continue with
// our overall traversal.
if (!redeclared.length) {
return;
}

// This class redeclares some private field. We need to process the outer
// environment with access to all the outer privates, then we can process
// the inner environment with only the still-visible outer privates.
path.get("superClass").traverse(privateNameVisitor, this);
path.get("body").traverse(privateNameNestedVisitor, {
...this,
redeclared,
});
path.traverse(privateNameVisitor, {
...this,
privateNamesMap: visiblePrivateNames,
});

// We'll eventually hit this class node again with the overall Class
// Features visitor, which'll process the redeclared privates.
path.skip();
},
};

// Traverses the outer portion of a class, without touching the class's inner
// scope, for private names.
const privateNameInnerVisitor = traverse.visitors.merge([
const privateNameNestedVisitor = traverse.visitors.merge([
{
PrivateName(path) {
const { redeclared } = this;
const { name } = path.node.id;
if (redeclared.includes(name)) path.skip();
},
},
{
PrivateName: privateNameVisitor.PrivateName,
},
Expand Down Expand Up @@ -263,6 +289,8 @@ export function transformPrivateNamesUsage(
loose,
state,
) {
if (!privateNamesMap.size) return;

const body = path.get("body");

if (loose) {
Expand Down
@@ -0,0 +1,14 @@
class Foo {
#foo = 1;

test() {
class Nested {
#foo = 2;

[this.#foo]() {
}
}

this.#foo;
}
}
@@ -0,0 +1,43 @@
var Foo = /*#__PURE__*/function () {
"use strict";

function Foo() {
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _foo, {
writable: true,
value: 1
});
}

babelHelpers.createClass(Foo, [{
key: "test",
value: function test() {
var _babelHelpers$classPr;

_babelHelpers$classPr = babelHelpers.classPrivateFieldLooseBase(this, _foo2)[_foo2];

var Nested = /*#__PURE__*/function () {
function Nested() {
babelHelpers.classCallCheck(this, Nested);
Object.defineProperty(this, _foo2, {
writable: true,
value: 2
});
}

babelHelpers.createClass(Nested, [{
key: _babelHelpers$classPr,
value: function () {}
}]);
return Nested;
}();

var _foo2 = babelHelpers.classPrivateFieldLooseKey("foo");

babelHelpers.classPrivateFieldLooseBase(this, _foo)[_foo];
}
}]);
return Foo;
}();

var _foo = babelHelpers.classPrivateFieldLooseKey("foo");
@@ -0,0 +1,12 @@
class Foo {
#foo = 1;

test() {
class Nested {
[this.#foo]() {
}
}

this.#foo;
}
}
@@ -0,0 +1,35 @@
var Foo = /*#__PURE__*/function () {
"use strict";

function Foo() {
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _foo, {
writable: true,
value: 1
});
}

babelHelpers.createClass(Foo, [{
key: "test",
value: function test() {
var _this = this;

var Nested = /*#__PURE__*/function () {
function Nested() {
babelHelpers.classCallCheck(this, Nested);
}

babelHelpers.createClass(Nested, [{
key: babelHelpers.classPrivateFieldLooseBase(_this, _foo)[_foo],
value: function () {}
}]);
return Nested;
}();

babelHelpers.classPrivateFieldLooseBase(this, _foo)[_foo];
}
}]);
return Foo;
}();

var _foo = babelHelpers.classPrivateFieldLooseKey("foo");
@@ -0,0 +1,18 @@
class Foo {
#foo = 1;
#bar = 1;

test() {
class Nested {
#bar = 2;

test() {
this.#foo;
this.#bar;
}
}

this.#foo;
this.#bar;
}
}
@@ -0,0 +1,49 @@
var Foo = /*#__PURE__*/function () {
"use strict";

function Foo() {
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _foo, {
writable: true,
value: 1
});
Object.defineProperty(this, _bar, {
writable: true,
value: 1
});
}

babelHelpers.createClass(Foo, [{
key: "test",
value: function test() {
var Nested = /*#__PURE__*/function () {
function Nested() {
babelHelpers.classCallCheck(this, Nested);
Object.defineProperty(this, _bar2, {
writable: true,
value: 2
});
}

babelHelpers.createClass(Nested, [{
key: "test",
value: function test() {
babelHelpers.classPrivateFieldLooseBase(this, _foo)[_foo];
babelHelpers.classPrivateFieldLooseBase(this, _bar2)[_bar2];
}
}]);
return Nested;
}();

var _bar2 = babelHelpers.classPrivateFieldLooseKey("bar");

babelHelpers.classPrivateFieldLooseBase(this, _foo)[_foo];
babelHelpers.classPrivateFieldLooseBase(this, _bar)[_bar];
}
}]);
return Foo;
}();

var _foo = babelHelpers.classPrivateFieldLooseKey("foo");

var _bar = babelHelpers.classPrivateFieldLooseKey("bar");
@@ -0,0 +1,15 @@
class Foo {
#foo = 1;

test() {
class Nested {
#foo = 2;

test() {
this.#foo;
}
}

this.#foo;
}
}
@@ -0,0 +1,41 @@
var Foo = /*#__PURE__*/function () {
"use strict";

function Foo() {
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _foo, {
writable: true,
value: 1
});
}

babelHelpers.createClass(Foo, [{
key: "test",
value: function test() {
var Nested = /*#__PURE__*/function () {
function Nested() {
babelHelpers.classCallCheck(this, Nested);
Object.defineProperty(this, _foo2, {
writable: true,
value: 2
});
}

babelHelpers.createClass(Nested, [{
key: "test",
value: function test() {
babelHelpers.classPrivateFieldLooseBase(this, _foo2)[_foo2];
}
}]);
return Nested;
}();

var _foo2 = babelHelpers.classPrivateFieldLooseKey("foo");

babelHelpers.classPrivateFieldLooseBase(this, _foo)[_foo];
}
}]);
return Foo;
}();

var _foo = babelHelpers.classPrivateFieldLooseKey("foo");
@@ -0,0 +1,13 @@
class Foo {
#foo = 1;

test() {
class Nested {
test() {
this.#foo;
}
}

this.#foo;
}
}
@@ -0,0 +1,35 @@
var Foo = /*#__PURE__*/function () {
"use strict";

function Foo() {
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _foo, {
writable: true,
value: 1
});
}

babelHelpers.createClass(Foo, [{
key: "test",
value: function test() {
var Nested = /*#__PURE__*/function () {
function Nested() {
babelHelpers.classCallCheck(this, Nested);
}

babelHelpers.createClass(Nested, [{
key: "test",
value: function test() {
babelHelpers.classPrivateFieldLooseBase(this, _foo)[_foo];
}
}]);
return Nested;
}();

babelHelpers.classPrivateFieldLooseBase(this, _foo)[_foo];
}
}]);
return Foo;
}();

var _foo = babelHelpers.classPrivateFieldLooseKey("foo");
@@ -0,0 +1,14 @@
class Foo {
#foo = 1;

test() {
class Nested {
#foo = 2;

[this.#foo]() {
}
}

this.#foo;
}
}

0 comments on commit 9b48a8e

Please sign in to comment.