diff --git a/packages/babel-helper-member-expression-to-functions/src/index.ts b/packages/babel-helper-member-expression-to-functions/src/index.ts index 7f8f74ab361d..cf69866eaf2d 100644 --- a/packages/babel-helper-member-expression-to-functions/src/index.ts +++ b/packages/babel-helper-member-expression-to-functions/src/index.ts @@ -247,6 +247,11 @@ const handle = { } else if (parentIsCall) { // `(a?.#b)()` to `(a == null ? void 0 : a.#b.bind(a))()` member.replaceWith(this.boundGet(member)); + } else if ( + this.delete && + parentPath.isUnaryExpression({ operator: "delete" }) + ) { + parentPath.replaceWith(this.delete(member)); } else { member.replaceWith(this.get(member)); } @@ -491,6 +496,12 @@ const handle = { return; } + // delete MEMBER -> _delete(MEMBER) + if (this.delete && parentPath.isUnaryExpression({ operator: "delete" })) { + parentPath.replaceWith(this.delete(member)); + return; + } + // for (MEMBER of ARR) // for (MEMBER in ARR) // { KEY: MEMBER } = OBJ -> { KEY: _destructureSet(MEMBER) } = OBJ @@ -562,6 +573,9 @@ export interface Handler { member: Member, args: t.OptionalCallExpression["arguments"], ): t.Expression; + // TODO(Babel 8): Consider making this required, since `.get` doesn't + // really work as a fallback for `.delete` + delete?(this: HandlerState & State, member: Member): t.Expression; } export interface HandlerState extends Handler { diff --git a/packages/babel-helper-replace-supers/package.json b/packages/babel-helper-replace-supers/package.json index 2440757f93c6..145ae5fa8e91 100644 --- a/packages/babel-helper-replace-supers/package.json +++ b/packages/babel-helper-replace-supers/package.json @@ -17,6 +17,7 @@ "@babel/helper-environment-visitor": "workspace:^", "@babel/helper-member-expression-to-functions": "workspace:^", "@babel/helper-optimise-call-expression": "workspace:^", + "@babel/template": "workspace:^", "@babel/traverse": "workspace:^", "@babel/types": "workspace:^" }, diff --git a/packages/babel-helper-replace-supers/src/index.ts b/packages/babel-helper-replace-supers/src/index.ts index 4699b7fdddf0..95b8f1c75c48 100644 --- a/packages/babel-helper-replace-supers/src/index.ts +++ b/packages/babel-helper-replace-supers/src/index.ts @@ -1,9 +1,11 @@ -import type { NodePath, Scope } from "@babel/traverse"; -import traverse from "@babel/traverse"; +import type { File } from "@babel/core"; +import environmentVisitor from "@babel/helper-environment-visitor"; import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions"; import type { HandlerState } from "@babel/helper-member-expression-to-functions"; import optimiseCall from "@babel/helper-optimise-call-expression"; -import environmentVisitor from "@babel/helper-environment-visitor"; +import template from "@babel/template"; +import traverse from "@babel/traverse"; +import type { NodePath, Scope } from "@babel/traverse"; import { assignmentExpression, booleanLiteral, @@ -16,7 +18,6 @@ import { thisExpression, } from "@babel/types"; import type * as t from "@babel/types"; -import type { File } from "@babel/core"; // TODO (Babel 8): Don't export this. export { @@ -107,7 +108,13 @@ type SuperMember = NodePath< interface SpecHandler extends Pick< Handler, - "get" | "set" | "destructureSet" | "call" | "optionalCall" | "memoise" + | "memoise" + | "get" + | "set" + | "destructureSet" + | "call" + | "optionalCall" + | "delete" > { _get( this: Handler & SpecHandler, @@ -240,6 +247,23 @@ const specHandlers: SpecHandler = { true, ); }, + + delete(this: Handler & SpecHandler, superMember: SuperMember) { + if (superMember.node.computed) { + return sequenceExpression([ + callExpression(this.file.addHelper("toPropertyKey"), [ + cloneNode(superMember.node.property), + ]), + template.expression.ast` + function () { throw new ReferenceError("'delete super[expr]' is invalid"); }() + `, + ]); + } else { + return template.expression.ast` + function () { throw new ReferenceError("'delete super.prop' is invalid"); }() + `; + } + }, }; const looseHandlers = { diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/exec.js new file mode 100644 index 000000000000..81cd1316437e --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/exec.js @@ -0,0 +1,15 @@ +expect(() => { + new class { y = delete super.x; }; +}).toThrow(ReferenceError); + +expect(() => { + new class { y = delete super[0]; }; +}).toThrow(ReferenceError); + +expect(() => { + class X1 { static y = delete super.x; } +}).toThrow(ReferenceError); + +expect(() => { + class X2 { static y = delete super[0]; } +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/input.js new file mode 100644 index 000000000000..d26af64a6c62 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/input.js @@ -0,0 +1,7 @@ +new class { y = delete super.x; }; + +new class { y = delete super[0]; }; + +class X1 { static y = delete super.x; } + +class X2 { static y = delete super[0]; } diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/output.js new file mode 100644 index 000000000000..659f3fe45998 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/public/delete-super-property/output.js @@ -0,0 +1,38 @@ +new ( /*#__PURE__*/function () { + "use strict"; + + function _class2() { + babelHelpers.classCallCheck(this, _class2); + babelHelpers.defineProperty(this, "y", function () { + throw new ReferenceError("'delete super.prop' is invalid"); + }()); + } + return babelHelpers.createClass(_class2); +}())(); +new ( /*#__PURE__*/function () { + "use strict"; + + function _class4() { + babelHelpers.classCallCheck(this, _class4); + babelHelpers.defineProperty(this, "y", (babelHelpers.toPropertyKey(0), function () { + throw new ReferenceError("'delete super[expr]' is invalid"); + }())); + } + return babelHelpers.createClass(_class4); +}())(); +var X1 = /*#__PURE__*/babelHelpers.createClass(function X1() { + "use strict"; + + babelHelpers.classCallCheck(this, X1); +}); +babelHelpers.defineProperty(X1, "y", function () { + throw new ReferenceError("'delete super.prop' is invalid"); +}()); +var X2 = /*#__PURE__*/babelHelpers.createClass(function X2() { + "use strict"; + + babelHelpers.classCallCheck(this, X2); +}); +babelHelpers.defineProperty(X2, "y", (babelHelpers.toPropertyKey(0), function () { + throw new ReferenceError("'delete super[expr]' is invalid"); +}())); diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/exec.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/exec.js new file mode 100644 index 000000000000..1106e7bce89c --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/exec.js @@ -0,0 +1,23 @@ +// [Symbol.toPrimitive] must be called if exist +var counter = 0; +expect(() => { + (new class { + f() { + delete super[{ + [Symbol.toPrimitive]: function() { ++counter; return 0; }, + }]; + } + }).f(); +}).toThrow(ReferenceError); +expect(counter).toBe(1); + +// [Symbol.toPrimitive] must return a primitive value +expect(() => { + (new class { + f() { + delete super[{ + [Symbol.toPrimitive]: function() { return {}; }, + }]; + } + }).f(); +}).toThrow(TypeError); diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/input.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/input.js new file mode 100644 index 000000000000..27844647551a --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/input.js @@ -0,0 +1,18 @@ +// [Symbol.toPrimitive] must be called if exist +var counter = 0; +(new class { + f() { + delete super[{ + [Symbol.toPrimitive]: function() { ++counter; return 0; }, + }]; + } +}).f(); + +// [Symbol.toPrimitive] must return a primitive value +(new class { + f() { + delete super[{ + [Symbol.toPrimitive]: function() { return {}; }, + }]; + } +}).f(); diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/output.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/output.js new file mode 100644 index 000000000000..6d8decbf3295 --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property-to-property-key/output.js @@ -0,0 +1,45 @@ +// [Symbol.toPrimitive] must be called if exist +var counter = 0; +new ( /*#__PURE__*/function () { + "use strict"; + + function _class() { + babelHelpers.classCallCheck(this, _class); + } + babelHelpers.createClass(_class, [{ + key: "f", + value: function f() { + babelHelpers.toPropertyKey({ + [Symbol.toPrimitive]: function () { + ++counter; + return 0; + } + }), function () { + throw new ReferenceError("'delete super[expr]' is invalid"); + }(); + } + }]); + return _class; +}())().f(); + +// [Symbol.toPrimitive] must return a primitive value +new ( /*#__PURE__*/function () { + "use strict"; + + function _class2() { + babelHelpers.classCallCheck(this, _class2); + } + babelHelpers.createClass(_class2, [{ + key: "f", + value: function f() { + babelHelpers.toPropertyKey({ + [Symbol.toPrimitive]: function () { + return {}; + } + }), function () { + throw new ReferenceError("'delete super[expr]' is invalid"); + }(); + } + }]); + return _class2; +}())().f(); diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/exec.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/exec.js new file mode 100644 index 000000000000..b7263306e35f --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/exec.js @@ -0,0 +1,27 @@ +expect(() => { + (new class { + f() { delete super.x; } + }).f(); +}).toThrow(ReferenceError); + +expect(() => { + (new class { + f() { delete super[0]; } + }).f(); +}).toThrow(ReferenceError); + +// [expr] should be evaluated +var counter = 0; +expect(() => { + (new class { + f() { delete super[++counter]; } + }).f(); +}).toThrow(ReferenceError); +expect(counter).toBe(1); + +// TypeError before ReferenceError +expect(() => { + (new class { + f() { delete super[0()]; } + }).f(); +}).toThrow(TypeError); diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/input.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/input.js new file mode 100644 index 000000000000..7e7fea3ee5d2 --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/input.js @@ -0,0 +1,18 @@ +(new class { + f() { delete super.x; } +}).f(); + +(new class { + f() { delete super[0]; } +}).f(); + +// [expr] should be evaluated +var counter = 0; +(new class { + f() { delete super[++counter]; } +}).f(); + +// TypeError before ReferenceError +(new class { + f() { delete super[0()]; } +}).f(); diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/output.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/output.js new file mode 100644 index 000000000000..e486ce97555e --- /dev/null +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/delete-super-property/output.js @@ -0,0 +1,69 @@ +new ( /*#__PURE__*/function () { + "use strict"; + + function _class() { + babelHelpers.classCallCheck(this, _class); + } + babelHelpers.createClass(_class, [{ + key: "f", + value: function f() { + (function () { + throw new ReferenceError("'delete super.prop' is invalid"); + })(); + } + }]); + return _class; +}())().f(); +new ( /*#__PURE__*/function () { + "use strict"; + + function _class2() { + babelHelpers.classCallCheck(this, _class2); + } + babelHelpers.createClass(_class2, [{ + key: "f", + value: function f() { + babelHelpers.toPropertyKey(0), function () { + throw new ReferenceError("'delete super[expr]' is invalid"); + }(); + } + }]); + return _class2; +}())().f(); + +// [expr] should be evaluated +var counter = 0; +new ( /*#__PURE__*/function () { + "use strict"; + + function _class3() { + babelHelpers.classCallCheck(this, _class3); + } + babelHelpers.createClass(_class3, [{ + key: "f", + value: function f() { + babelHelpers.toPropertyKey(++counter), function () { + throw new ReferenceError("'delete super[expr]' is invalid"); + }(); + } + }]); + return _class3; +}())().f(); + +// TypeError before ReferenceError +new ( /*#__PURE__*/function () { + "use strict"; + + function _class4() { + babelHelpers.classCallCheck(this, _class4); + } + babelHelpers.createClass(_class4, [{ + key: "f", + value: function f() { + babelHelpers.toPropertyKey(0()), function () { + throw new ReferenceError("'delete super[expr]' is invalid"); + }(); + } + }]); + return _class4; +}())().f(); diff --git a/yarn.lock b/yarn.lock index 8ba1cf3aed27..5ba26097eed9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -882,6 +882,7 @@ __metadata: "@babel/helper-environment-visitor": "workspace:^" "@babel/helper-member-expression-to-functions": "workspace:^" "@babel/helper-optimise-call-expression": "workspace:^" + "@babel/template": "workspace:^" "@babel/traverse": "workspace:^" "@babel/types": "workspace:^" languageName: unknown