Fix classNameTDZError in computed prototype methods with class fields (#11068)

* Draft fix for TDZError in computed prototype methods

* Added tests for loose:false and also extracted handleClassTDZ

* Added state parameter to handleClassTDZ function

* Extracted the state object to a variable

* Added helper comments for environmentVisitor

* Added else condition to traverse computed Path

* Cached computed path key into a variable
This commit is contained in:
Siddhant N Trivedi 2020-02-12 16:25:38 +05:30 committed by GitHub
parent 157bb6e831
commit 3fc904e1d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 78 additions and 24 deletions

View File

@ -25,26 +25,24 @@ const referenceVisitor = {
}
},
};
function handleClassTDZ(path, state) {
if (
state.classBinding &&
state.classBinding === path.scope.getBinding(path.node.name)
) {
const classNameTDZError = state.file.addHelper("classNameTDZError");
const throwNode = t.callExpression(classNameTDZError, [
t.stringLiteral(path.node.name),
]);
const classFieldDefinitionEvaluationTDZVisitor = traverse.visitors.merge([
{
ReferencedIdentifier(path) {
if (
this.classBinding &&
this.classBinding === 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 classFieldDefinitionEvaluationTDZVisitor = {
ReferencedIdentifier: handleClassTDZ,
};
export function injectInitialization(path, constructor, nodes, renamer) {
if (!nodes.length) return;
@ -84,17 +82,22 @@ export function injectInitialization(path, constructor, nodes, renamer) {
export function extractComputedKeys(ref, path, computedPaths, file) {
const declarations = [];
const state = {
classBinding: path.node.id && path.scope.getBinding(path.node.id.name),
file,
};
for (const computedPath of computedPaths) {
computedPath.traverse(classFieldDefinitionEvaluationTDZVisitor, {
classBinding: path.node.id && path.scope.getBinding(path.node.id.name),
file,
});
const computedKey = computedPath.get("key");
if (computedKey.isReferencedIdentifier()) {
handleClassTDZ(computedKey, state);
} else {
computedKey.traverse(classFieldDefinitionEvaluationTDZVisitor, state);
}
const computedNode = computedPath.node;
// 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()) {
if (!computedKey.isConstantExpression()) {
const ident = path.scope.generateUidIdentifierBasedOnNode(
computedNode.key,
);

View File

@ -0,0 +1,6 @@
class Foo {
static nickname = 'Tom';
['HELLO']() {
console.log('>>>>', Foo);
}
}

View File

@ -0,0 +1,10 @@
{
"plugins": [
[
"proposal-class-properties",
{
"loose": false
}
]
]
}

View File

@ -0,0 +1,10 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class Foo {
['HELLO']() {
console.log('>>>>', Foo);
}
}
_defineProperty(Foo, "nickname", 'Tom');

View File

@ -0,0 +1,6 @@
class Foo {
static nickname = 'Tom';
['HELLO']() {
console.log('>>>>', Foo);
}
}

View File

@ -0,0 +1,10 @@
{
"plugins": [
[
"proposal-class-properties",
{
"loose": true
}
]
]
}

View File

@ -0,0 +1,8 @@
class Foo {
['HELLO']() {
console.log('>>>>', Foo);
}
}
Foo.nickname = 'Tom';

View File

@ -41,6 +41,7 @@ function skipAllButComputedKey(path: NodePath) {
}
}
// environmentVisitor should be used when traversing the whole class and not for specific class elements/methods.
export const environmentVisitor = {
TypeAnnotation(path: NodePath) {
path.skip();