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: Deleting super property should throw #15223

Merged
merged 7 commits into from Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 14 additions & 0 deletions packages/babel-helper-member-expression-to-functions/src/index.ts
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -562,6 +573,9 @@ export interface Handler<State> {
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> & State, member: Member): t.Expression;
SuperSodaSea marked this conversation as resolved.
Show resolved Hide resolved
}

export interface HandlerState<State = {}> extends Handler<State> {
Expand Down
1 change: 1 addition & 0 deletions packages/babel-helper-replace-supers/package.json
Expand Up @@ -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:^"
},
Expand Down
34 changes: 29 additions & 5 deletions 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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 = {
Expand Down
@@ -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);
@@ -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]; }
@@ -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");
}()));
@@ -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);
@@ -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();
@@ -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();
@@ -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);
@@ -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();
@@ -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();
1 change: 1 addition & 0 deletions yarn.lock
Expand Up @@ -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
Expand Down