Skip to content

Commit

Permalink
Add bugfix plugin for spread in super()
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Mar 30, 2021
1 parent f074690 commit 90730e4
Show file tree
Hide file tree
Showing 26 changed files with 432 additions and 5 deletions.
3 changes: 3 additions & 0 deletions packages/babel-compat-data/data/overlapping-plugins.json
Expand Up @@ -12,6 +12,9 @@
"bugfix/transform-safari-block-shadowing",
"bugfix/transform-safari-for-shadowing"
],
"transform-classes": [
"bugfix/transform-spread-super"
],
"transform-template-literals": [
"bugfix/transform-tagged-template-caching"
],
Expand Down
22 changes: 22 additions & 0 deletions packages/babel-compat-data/data/plugin-bugfixes.json
Expand Up @@ -100,6 +100,28 @@
"samsung": "5",
"electron": "0.37"
},
"transform-classes": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"ios": "10",
"samsung": "5",
"electron": "0.36"
},
"bugfix/transform-spread-super": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "36",
"safari": "10",
"node": "5",
"ios": "10",
"samsung": "5",
"electron": "0.36"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
Expand Down
4 changes: 4 additions & 0 deletions packages/babel-compat-data/scripts/data/plugin-bugfixes.js
Expand Up @@ -31,6 +31,10 @@ module.exports = {
],
replaces: "transform-block-scoping",
},
"bugfix/transform-spread-super": {
features: ["spread syntax for iterable objects"],
replaces: "transform-spread",
},
"bugfix/transform-tagged-template-caching": {
features: ["template literals / TemplateStrings permanent caching"],
replaces: "transform-template-literals",
Expand Down
19 changes: 14 additions & 5 deletions packages/babel-helper-replace-supers/src/index.ts
Expand Up @@ -139,6 +139,9 @@ const specHandlers = {
},

_getThisRefs() {
if (this.thisRef) {
return { this: t.cloneNode(this.thisRef) };
}
if (!this.isDerivedConstructor) {
return { this: t.thisExpression() };
}
Expand Down Expand Up @@ -233,7 +236,7 @@ const looseHandlers = {

return t.assignmentExpression(
"=",
t.memberExpression(t.thisExpression(), prop, computed),
t.memberExpression(this._getThisRef(), prop, computed),
value,
);
},
Expand All @@ -242,21 +245,26 @@ const looseHandlers = {
const { computed } = superMember.node;
const prop = this.prop(superMember);

return t.memberExpression(t.thisExpression(), prop, computed);
return t.memberExpression(this._getThisRef(), prop, computed);
},

call(superMember, args) {
return optimiseCall(this.get(superMember), t.thisExpression(), args, false);
return optimiseCall(this.get(superMember), this._getThisRef(), args, false);
},

optionalCall(superMember, args) {
return optimiseCall(this.get(superMember), t.thisExpression(), args, true);
return optimiseCall(this.get(superMember), this._getThisRef(), args, true);
},

_getThisRef() {
return this.thisRef ? t.cloneNode(this.thisRef) : t.thisExpression();
},
};

type ReplaceSupersOptionsBase = {
methodPath: NodePath<any>;
superRef: any;
superRef: t.Expression;
thisRef?: t.Expression;
constantSuper?: boolean;
file: any;
// objectRef might have been shadowed in child scopes,
Expand Down Expand Up @@ -324,6 +332,7 @@ export default class ReplaceSupers {
isPrivateMethod: this.isPrivateMethod,
getObjectRef: this.getObjectRef.bind(this),
superRef: this.superRef,
thisRef: this.opts.thisRef,
...handler,
});
}
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-plugin-bugfix-super-spread/.npmignore
@@ -0,0 +1,3 @@
src
test
*.log
19 changes: 19 additions & 0 deletions packages/babel-plugin-bugfix-super-spread/README.md
@@ -0,0 +1,19 @@
# @babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining

> Transform optional chaining operators to workaround a [v8 bug](https://crbug.com/v8/11558).
See our website [@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining](https://babeljs.io/docs/en/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining) for more information.

## Install

Using npm:

```sh
npm install --save-dev @babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining
```

or using yarn:

```sh
yarn add @babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining --dev
```
36 changes: 36 additions & 0 deletions packages/babel-plugin-bugfix-super-spread/package.json
@@ -0,0 +1,36 @@
{
"name": "@babel/plugin-bugfix-super-spread",
"version": "7.13.12",
"description": "Transform super() calls with spread arguments without compiling classes",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-plugin-bugfix-super-spread"
},
"homepage": "https://babel.dev/docs/en/next/babel-plugin-bugfix-super-spread",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "lib/index.js",
"exports": {
".": [
"./lib/index.js"
]
},
"keywords": [
"babel-plugin",
"bugfix"
],
"dependencies": {
"@babel/helper-plugin-utils": "workspace:^7.13.0",
"@babel/helper-replace-supers": "workspace:^7.13.12"
},
"peerDependencies": {
"@babel/core": "^7.13.0"
},
"devDependencies": {
"@babel/core": "workspace:*",
"@babel/helper-plugin-test-runner": "workspace:*"
}
}
149 changes: 149 additions & 0 deletions packages/babel-plugin-bugfix-super-spread/src/index.ts
@@ -0,0 +1,149 @@
import { declare } from "@babel/helper-plugin-utils";
import ReplaceSupers from "@babel/helper-replace-supers";

const last = arr => arr[arr.length - 1];

export default declare(({ assertVersion, types: t, template, assumption }) => {
assertVersion(7);

const constantSuper = assumption("constantSuper");

const constructorContextVisitor = {
ThisExpression(path, state) {
state.this.push(path);
},
Super(path, state) {
if (path.parentPath.isCallExpression()) {
state.superCalls.push(path.parentPath);

if (path.parent.arguments.some(arg => t.isSpreadElement(arg))) {
state.superSpreads.add(path.parent);
}
} else {
state.super.push(path.parentPath);
}
},
Function(path) {
if (!path.isArrowFunctionExpression()) path.skip();
},
};

const constructorReturnVisitor = {
ReturnStatement(path, state) {
state.return.push(path);
},
Function(path) {
path.skip();
},
};

return {
name: "bugfix-super-spread",

visitor: {
Class(path, file) {
if (!path.node.superClass) return;

const constructorPath = path
.get("body.body")
.find(el => el.isClassMethod({ kind: "constructor" }));
if (!constructorPath) return;

const state = {
this: [],
super: [],
superCalls: [],
superSpreads: new Set(),
return: [],
noScope: true,
};
constructorPath.traverse(constructorContextVisitor, state);
if (state.superSpreads.size === 0) return;
constructorPath.traverse(constructorReturnVisitor, state);

const superId = path.scope.generateUidIdentifierBasedOnNode(
path.node.superClass,
);
path.scope.push({ id: superId });
path.set(
"superClass",
t.assignmentExpression(
"=",
t.cloneNode(superId),
path.node.superClass,
),
);

const thisId = constructorPath.scope.generateUidIdentifier("this");
constructorPath.scope.push({ id: thisId });

for (const path of state.superCalls) {
if (state.superSpreads.has(path.node)) {
const args = t.arrayExpression(path.node.arguments);
const newTarget = t.metaProperty(
t.identifier("new"),
t.identifier("target"),
);

path.replaceWith(
template.ast`
${t.cloneNode(thisId)} = Reflect.construct(
${t.cloneNode(superId)},
${args},
${newTarget}
)
`,
);
} else {
path.replaceWith(
template.ast`
${t.cloneNode(thisId)} = ${path.node}`,
);
}
}

for (const path of state.this) {
path.replaceWith(t.cloneNode(thisId));
}

for (const path of state.return) {
if (!path.node.argument) {
path.set("argument", t.cloneNode(thisId));
} else {
path.set(
"argument",
t.callExpression(file.addHelper("possibleConstructorReturn"), [
t.cloneNode(thisId),
path.node.argument,
]),
);
}
}
const bodyPath = constructorPath.get("body");
if (!last(bodyPath.get("body")).isReturnStatement()) {
bodyPath.pushContainer(
"body",
t.returnStatement(t.cloneNode(thisId)),
);
}

let classId = path.node.id;
new ReplaceSupers({
methodPath: constructorPath,
superRef: superId,
thisRef: thisId,
constantSuper: constantSuper,
file: file,
refToPreserve: classId,
getObjectRef() {
if (!classId) {
classId = path.scope.generateUidIdentifier("class");
path.set("id", classId);
}
return classId;
},
}).replace();
},
},
};
});
@@ -0,0 +1,8 @@
class A extends B {
constructor() {
super(1, ...foo, 2);
this.x;
super.x();
super.y = 3;
}
}
@@ -0,0 +1,16 @@
var _B;

class A extends (_B = B) {
constructor() {
var _this;

_this = Reflect.construct(_B, [1, ...foo, 2], new.target);
_this.x;

_B.prototype.x.call(_this);

_this.y = 3;
return _this;
}

}
@@ -0,0 +1,6 @@
{
"plugins": ["bugfix-super-spread"],
"assumptions": {
"constantSuper": true
}
}
@@ -0,0 +1,5 @@
class A extends B {
constructor() {
super(1, ...foo, 2);
}
}
@@ -0,0 +1,11 @@
var _B;

class A extends (_B = B) {
constructor() {
var _this;

_this = Reflect.construct(_B, [1, ...foo, 2], new.target);
return _this;
}

}
@@ -0,0 +1,7 @@
class A extends B {
constructor() {
() => super(...args);
() => this;
() => super.foo;
}
}
@@ -0,0 +1,16 @@
var _B;

class A extends (_B = B) {
constructor() {
var _this;

() => _this = Reflect.construct(_B, [...args], new.target);

() => _this;

() => babelHelpers.get(babelHelpers.getPrototypeOf(A.prototype), "foo", _this);

return _this;
}

}

0 comments on commit 90730e4

Please sign in to comment.