Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix nested classes reference private fields #11405

Merged
merged 5 commits into from Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 24 additions & 10 deletions packages/babel-helper-create-class-features-plugin/src/fields.js
Expand Up @@ -85,24 +85,36 @@ const privateNameVisitor = {
const { privateNamesMap } = this;
const body = path.get("body.body");

const visiblePrivateNames = new Map(privateNamesMap);
for (const prop of body) {
if (!prop.isPrivate()) {
continue;
}
if (!privateNamesMap.has(prop.node.key.id.name)) continue;
if (!prop.isPrivate()) continue;
visiblePrivateNames.delete(prop.node.key.id.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 (visiblePrivateNames.size === privateNamesMap.size) {
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.traverse(privateNameNestedVisitor, this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JLHwung Maybe it would be enough to remove this line to fix it?

Copy link
Contributor

@JLHwung JLHwung Apr 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so but I haven't tried. The nested private name is still allowed as long as it does not access outer class private names. I think we shall whitelist computed element names for proper resolving behaviour.

class Foo {
  #foo = 1;

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

    new Nested;
  }
}
(new Foo).test();

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: privateNameVisitor.PrivateName,
},
Expand Down Expand Up @@ -263,6 +275,8 @@ export function transformPrivateNamesUsage(
loose,
state,
) {
if (!privateNamesMap.size) return;

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

if (loose) {
Expand Down
@@ -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 {
Copy link
Contributor

@JLHwung JLHwung Apr 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a case on redeclared private names with computed key reference? The following case should throw since #foo is shadowed.

class Foo {
  #foo = 1;

  test() {
    class Nested {
      #foo = 2;
      [this.#foo] = 3;
    }

    new Nested;
  }
}
(new Foo).test();

Given that it is not directly related to this PR, we can fix it in a separate one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we wait till the other PR to add the test case? It's going to be much harder to fix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I have a fix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I meant we can add the new case and the fixes in a separate PR, my bad. As long as you come up with a fix, that's awesome! 😄

#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,18 @@
class Foo {
#foo = 1;
#bar = 1;

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

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

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

function Foo() {
babelHelpers.classCallCheck(this, Foo);

_foo.set(this, {
writable: true,
value: 1
});

_bar.set(this, {
writable: true,
value: 1
});
}

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

_bar2.set(this, {
writable: true,
value: 2
});
}

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

var _bar2 = new WeakMap();

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

var _foo = new WeakMap();

var _bar = new WeakMap();
@@ -0,0 +1,15 @@
class Foo {
#foo = 1;

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

test() {
this.#foo;
}
}

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

function Foo() {
babelHelpers.classCallCheck(this, Foo);

_foo.set(this, {
writable: true,
value: 1
});
}

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

_foo2.set(this, {
writable: true,
value: 2
});
}

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

var _foo2 = new WeakMap();

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

var _foo = new WeakMap();
@@ -0,0 +1,13 @@
class Foo {
#foo = 1;

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

this.#foo;
}
}