Fix class properties after nested class' bare super (#7671)

* Fix class properties after nested class' bare super

Fixes #7371.

* Fix node 4 test

* This damn node 4 test

* All of the ClassBody, but not the methods or field inits

* tmp

* tmp

* Use common class environment visitor

* Tests

* Use skipKey to avoid recursive traversal

* Remove old state

* Use jest expect
This commit is contained in:
Justin Ridgewell
2018-04-14 13:48:38 -04:00
committed by GitHub
parent 39b05598a0
commit 668358c4d0
12 changed files with 436 additions and 100 deletions

View File

@@ -11,6 +11,7 @@
"dependencies": {
"@babel/helper-function-name": "7.0.0-beta.44",
"@babel/helper-plugin-utils": "7.0.0-beta.44",
"@babel/helper-replace-supers": "7.0.0-beta.44",
"@babel/plugin-syntax-class-properties": "7.0.0-beta.44"
},
"peerDependencies": {

View File

@@ -1,25 +1,31 @@
import { declare } from "@babel/helper-plugin-utils";
import nameFunction from "@babel/helper-function-name";
import syntaxClassProperties from "@babel/plugin-syntax-class-properties";
import { template, types as t } from "@babel/core";
import { template, traverse, types as t } from "@babel/core";
import { environmentVisitor } from "@babel/helper-replace-supers";
export default declare((api, options) => {
api.assertVersion(7);
const { loose } = options;
const findBareSupers = {
Super(path) {
if (path.parentPath.isCallExpression({ callee: path.node })) {
this.push(path.parentPath);
}
const findBareSupers = traverse.visitors.merge([
{
Super(path) {
const { node, parentPath } = path;
if (parentPath.isCallExpression({ callee: node })) {
this.push(parentPath);
}
},
},
};
environmentVisitor,
]);
const referenceVisitor = {
"TSTypeAnnotation|TypeAnnotation"(path) {
path.skip();
},
ReferencedIdentifier(path) {
if (this.scope.hasOwnBinding(path.node.name)) {
this.scope.rename(path.node.name);
@@ -28,25 +34,22 @@ export default declare((api, options) => {
},
};
const ClassFieldDefinitionEvaluationTDZVisitor = {
Expression(path) {
if (path === this.shouldSkip) {
path.skip();
}
},
const classFieldDefinitionEvaluationTDZVisitor = traverse.visitors.merge([
{
ReferencedIdentifier(path) {
if (this.classRef === path.scope.getBinding(path.node.name)) {
const classNameTDZError = this.file.addHelper("classNameTDZError");
const throwNode = t.callExpression(classNameTDZError, [
t.stringLiteral(path.node.name),
]);
ReferencedIdentifier(path) {
if (this.classRef === path.scope.getBinding(path.node.name)) {
const classNameTDZError = this.file.addHelper("classNameTDZError");
const throwNode = t.callExpression(classNameTDZError, [
t.stringLiteral(path.node.name),
]);
path.replaceWith(t.sequenceExpression([throwNode, path.node]));
path.skip();
}
path.replaceWith(t.sequenceExpression([throwNode, path.node]));
path.skip();
}
},
},
};
environmentVisitor,
]);
const buildClassPropertySpec = (ref, { key, value, computed }, scope) => {
return template.statement`
@@ -122,10 +125,9 @@ export default declare((api, options) => {
// Make sure computed property names are only evaluated once (upon class definition)
// and in the right order in combination with static properties
if (!computedPath.get("key").isConstantExpression()) {
computedPath.traverse(ClassFieldDefinitionEvaluationTDZVisitor, {
computedPath.traverse(classFieldDefinitionEvaluationTDZVisitor, {
classRef: path.scope.getBinding(ref.name),
file: this.file,
shouldSkip: computedPath.get("value"),
});
const ident = path.scope.generateUidIdentifierBasedOnNode(
computedNode.key,

View File

@@ -0,0 +1,99 @@
"use strict";
class C {
}
class A extends C {
field = 1;
constructor() {
super();
class B extends C {
constructor() {
super();
expect(this.field).toBeUndefined();
}
}
expect(this.field).toBe(1)
new B();
}
}
new A();
class Obj {
constructor() {
return {};
}
}
// ensure superClass is still transformed
class SuperClass extends Obj {
field = 1;
constructor() {
class B extends (super(), Obj) {
constructor() {
super();
expect(this.field).toBeUndefined()
}
}
expect(this.field).toBe(1)
new B();
}
}
new SuperClass();
// ensure ComputedKey Method is still transformed
class ComputedMethod extends Obj {
field = 1;
constructor() {
class B extends Obj {
constructor() {
super();
expect(this.field).toBeUndefined()
}
[super()]() { }
}
expect(this.field).toBe(1)
new B();
}
}
new ComputedMethod();
// ensure ComputedKey Field is still transformed
class ComputedField extends Obj {
field = 1;
constructor() {
class B extends Obj {
constructor() {
super();
expect(this.field).toBeUndefined()
}
[super()] = 1;
}
expect(this.field).toBe(1)
new B();
}
}
new ComputedField();

View File

@@ -0,0 +1,99 @@
"use strict";
class C {
}
class A extends C {
field = 1;
constructor() {
super();
class B extends C {
constructor() {
super();
expect(this.field).toBeUndefined();
}
}
expect(this.field).toBe(1)
new B();
}
}
new A();
class Obj {
constructor() {
return {};
}
}
// ensure superClass is still transformed
class SuperClass extends Obj {
field = 1;
constructor() {
class B extends (super(), Obj) {
constructor() {
super();
expect(this.field).toBeUndefined()
}
}
expect(this.field).toBe(1)
new B();
}
}
new SuperClass();
// ensure ComputedKey Method is still transformed
class ComputedMethod extends Obj {
field = 1;
constructor() {
class B extends Obj {
constructor() {
super();
expect(this.field).toBeUndefined()
}
[super()]() { }
}
expect(this.field).toBe(1)
new B();
}
}
new ComputedMethod();
// ensure ComputedKey Field is still transformed
class ComputedField extends Obj {
field = 1;
constructor() {
class B extends Obj {
constructor() {
super();
expect(this.field).toBeUndefined()
}
[super()] = 1;
}
expect(this.field).toBe(1)
new B();
}
}
new ComputedField();

View File

@@ -0,0 +1,3 @@
{
"plugins": ["external-helpers", "proposal-class-properties", "transform-arrow-functions"]
}

View File

@@ -0,0 +1,122 @@
"use strict";
class C {}
class A extends C {
constructor() {
super();
Object.defineProperty(this, "field", {
configurable: true,
enumerable: true,
writable: true,
value: 1
});
class B extends C {
constructor() {
super();
expect(this.field).toBeUndefined();
}
}
expect(this.field).toBe(1);
new B();
}
}
new A();
class Obj {
constructor() {
return {};
}
} // ensure superClass is still transformed
class SuperClass extends Obj {
constructor() {
var _temp;
class B extends ((_temp = super(), Object.defineProperty(this, "field", {
configurable: true,
enumerable: true,
writable: true,
value: 1
}), _temp), Obj) {
constructor() {
super();
expect(this.field).toBeUndefined();
}
}
expect(this.field).toBe(1);
new B();
}
}
new SuperClass(); // ensure ComputedKey Method is still transformed
class ComputedMethod extends Obj {
constructor() {
var _temp2;
class B extends Obj {
constructor() {
super();
expect(this.field).toBeUndefined();
}
[(_temp2 = super(), Object.defineProperty(this, "field", {
configurable: true,
enumerable: true,
writable: true,
value: 1
}), _temp2)]() {}
}
expect(this.field).toBe(1);
new B();
}
}
new ComputedMethod(); // ensure ComputedKey Field is still transformed
class ComputedField extends Obj {
constructor() {
var _temp3;
var _ref = (_temp3 = super(), Object.defineProperty(this, "field", {
configurable: true,
enumerable: true,
writable: true,
value: 1
}), _temp3);
class B extends Obj {
constructor() {
super();
Object.defineProperty(this, _ref, {
configurable: true,
enumerable: true,
writable: true,
value: 1
});
expect(this.field).toBeUndefined();
}
}
expect(this.field).toBe(1);
new B();
}
}
new ComputedField();