diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index de544eac11..04902294d3 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -633,6 +633,12 @@ helpers.readOnlyError = defineHelper(` } `); +helpers.classNameTDZError = defineHelper(` + export default function _classNameTDZError(name) { + throw new Error("Class \\"" + name + "\\" cannot be referenced in computed property keys."); + } +`); + helpers.temporalUndefined = defineHelper(` export default {}; `); diff --git a/packages/babel-plugin-proposal-class-properties/src/index.js b/packages/babel-plugin-proposal-class-properties/src/index.js index ddd94ebc05..25d9507d6b 100644 --- a/packages/babel-plugin-proposal-class-properties/src/index.js +++ b/packages/babel-plugin-proposal-class-properties/src/index.js @@ -25,6 +25,26 @@ export default function(api, options) { }, }; + const ClassFieldDefinitionEvaluationTDZVisitor = { + Expression(path) { + if (path === this.shouldSkip) { + path.skip(); + } + }, + + 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(); + } + }, + }; + const buildClassPropertySpec = (ref, { key, value, computed }, scope) => { return template.statement` Object.defineProperty(REF, KEY, { @@ -95,6 +115,11 @@ export default function(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, { + classRef: path.scope.getBinding(ref.name), + file: this.file, + shouldSkip: computedPath.get("value"), + }); const ident = path.scope.generateUidIdentifierBasedOnNode( computedNode.key, ); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/edgest-case/actual.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/edgest-case/actual.js new file mode 100644 index 0000000000..dcca5c770c --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/edgest-case/actual.js @@ -0,0 +1,3 @@ +class A { + static [{ x: A || 0 }.x]; +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/edgest-case/expected.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/edgest-case/expected.js new file mode 100644 index 0000000000..dba70bfd69 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/edgest-case/expected.js @@ -0,0 +1,17 @@ +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _classNameTDZError(name) { throw new Error("Class \"" + name + "\" cannot be referenced in computed property keys."); } + +let A = function A() { + _classCallCheck(this, A); +}; + +var _x$x = { + x: (_classNameTDZError("A"), A) || 0 +}.x; +Object.defineProperty(A, _x$x, { + configurable: true, + enumerable: true, + writable: true, + value: void 0 +}); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/actual.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/actual.js new file mode 100644 index 0000000000..907262b4a5 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/actual.js @@ -0,0 +1,3 @@ +class C { + static [C + 3] = 3; +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/exec.js new file mode 100644 index 0000000000..907262b4a5 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/exec.js @@ -0,0 +1,3 @@ +class C { + static [C + 3] = 3; +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/expected.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/expected.js new file mode 100644 index 0000000000..de444cb26b --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/expected.js @@ -0,0 +1,16 @@ +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _classNameTDZError(name) { throw new Error("Class \"" + name + "\" cannot be referenced in computed property keys."); } + +let C = function C() { + _classCallCheck(this, C); +}; + +var _ref = (_classNameTDZError("C"), C) + 3; + +Object.defineProperty(C, _ref, { + configurable: true, + enumerable: true, + writable: true, + value: 3 +}); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/options.json new file mode 100644 index 0000000000..758a66fe92 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/general/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Class \"C\" cannot be referenced in computed property keys." +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/actual.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/actual.js new file mode 100644 index 0000000000..907262b4a5 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/actual.js @@ -0,0 +1,3 @@ +class C { + static [C + 3] = 3; +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/exec.js new file mode 100644 index 0000000000..907262b4a5 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/exec.js @@ -0,0 +1,3 @@ +class C { + static [C + 3] = 3; +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/expected.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/expected.js new file mode 100644 index 0000000000..c3cec96efe --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/expected.js @@ -0,0 +1,7 @@ +function _classNameTDZError(name) { throw new Error("Class \"" + name + "\" cannot be referenced in computed property keys."); } + +class C {} + +var _ref = (_classNameTDZError("C"), C) + 3; + +C[_ref] = 3; diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/options.json new file mode 100644 index 0000000000..5aef1db272 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/loose/options.json @@ -0,0 +1,4 @@ +{ + "throws": "Class \"C\" cannot be referenced in computed property keys.", + "plugins": [["proposal-class-properties", {"loose": true}]] +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/options.json new file mode 100644 index 0000000000..925ad282ea --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/static-property-tdz/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["proposal-class-properties", "transform-classes"], + "minNodeVersion": "6.0.0" +}