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 2 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
6 changes: 6 additions & 0 deletions packages/babel-helper-replace-supers/package.json
Expand Up @@ -20,6 +20,12 @@
"@babel/traverse": "workspace:^",
"@babel/types": "workspace:^"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory adding a peer dependency is a breaking change, however this is an internal package only used by other packages that already depend on @babel/core, so it should be fine.

Could you remove the @babel/traverse and @babel/types dependencies, and import them from @babel/core? Or as an alternative, we could just add @babel/template as a dependency and avoid adding @babel/core.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now template is imported directly from @babel/template.

By the way, it seems that the original code (on main branch) of @babel/helper-replace-supers have an import type { File } from "@babel/core"; without declaring @babel/core dependency in package.json. Does it matter?

import type { File } from "@babel/core";

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't matter because we remove type-only imports before publishing, so the published code is fine.

},
"devDependencies": {
"@babel/core": "workspace:^"
},
"engines": {
"node": ">=6.9.0"
},
Expand Down
29 changes: 25 additions & 4 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 { template } from "@babel/core";
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 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 @@ -64,6 +65,26 @@ const visitor = traverse.visitors.merge<
Super(path, state) {
const { node, parentPath } = path;
if (!parentPath.isMemberExpression({ object: node })) return;
if (parentPath.parentPath.isUnaryExpression({ operator: "delete" })) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic should be moved in a different place: instead of special-casing delete here, we should add an optional delete handler to

, and implement it in
const specHandlers: SpecHandler = {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 38dcf9d :)

const deletePath = parentPath.parentPath;
if (parentPath.node.computed) {
deletePath.replaceWith(
sequenceExpression([
callExpression(this.file.addHelper("toPropertyKey"), [
cloneNode(parentPath.node.property as t.Expression),
]),
template.expression.ast`
function () { throw new ReferenceError("'delete super[expr]' is invalid"); }()
`,
]),
);
} else {
deletePath.replaceWith(template.expression.ast`
function () { throw new ReferenceError("'delete super.prop' is invalid"); }()
`);
}
return;
}
state.handle(parentPath);
},
},
Expand Down
@@ -0,0 +1,3 @@
expect(() => {
class x { static y = delete super[0]; }
}).toThrow(ReferenceError);
@@ -0,0 +1 @@
class x { static y = delete super[0]; }
@@ -0,0 +1,8 @@
var x = /*#__PURE__*/babelHelpers.createClass(function x() {
"use strict";

babelHelpers.classCallCheck(this, x);
});
babelHelpers.defineProperty(x, "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();
3 changes: 3 additions & 0 deletions yarn.lock
Expand Up @@ -879,11 +879,14 @@ __metadata:
version: 0.0.0-use.local
resolution: "@babel/helper-replace-supers@workspace:packages/babel-helper-replace-supers"
dependencies:
"@babel/core": "workspace:^"
"@babel/helper-environment-visitor": "workspace:^"
"@babel/helper-member-expression-to-functions": "workspace:^"
"@babel/helper-optimise-call-expression": "workspace:^"
"@babel/traverse": "workspace:^"
"@babel/types": "workspace:^"
peerDependencies:
"@babel/core": ^7.0.0-0
languageName: unknown
linkType: soft

Expand Down