diff --git a/packages/babel-compat-data/data/overlapping-plugins.json b/packages/babel-compat-data/data/overlapping-plugins.json index 51976d9e2f43..324891309b36 100644 --- a/packages/babel-compat-data/data/overlapping-plugins.json +++ b/packages/babel-compat-data/data/overlapping-plugins.json @@ -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" ], diff --git a/packages/babel-compat-data/data/plugin-bugfixes.json b/packages/babel-compat-data/data/plugin-bugfixes.json index b1521be394f5..0d8fb8f83c79 100644 --- a/packages/babel-compat-data/data/plugin-bugfixes.json +++ b/packages/babel-compat-data/data/plugin-bugfixes.json @@ -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", diff --git a/packages/babel-compat-data/scripts/data/plugin-bugfixes.js b/packages/babel-compat-data/scripts/data/plugin-bugfixes.js index 75af0bc4fc0c..fac8a27f39e8 100644 --- a/packages/babel-compat-data/scripts/data/plugin-bugfixes.js +++ b/packages/babel-compat-data/scripts/data/plugin-bugfixes.js @@ -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", diff --git a/packages/babel-helper-replace-supers/src/index.ts b/packages/babel-helper-replace-supers/src/index.ts index 51b1a2215ca7..6fa0d113406c 100644 --- a/packages/babel-helper-replace-supers/src/index.ts +++ b/packages/babel-helper-replace-supers/src/index.ts @@ -139,6 +139,9 @@ const specHandlers = { }, _getThisRefs() { + if (this.thisRef) { + return { this: t.cloneNode(this.thisRef) }; + } if (!this.isDerivedConstructor) { return { this: t.thisExpression() }; } @@ -233,7 +236,7 @@ const looseHandlers = { return t.assignmentExpression( "=", - t.memberExpression(t.thisExpression(), prop, computed), + t.memberExpression(this._getThisRef(), prop, computed), value, ); }, @@ -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; - superRef: any; + superRef: t.Expression; + thisRef?: t.Expression; constantSuper?: boolean; file: any; // objectRef might have been shadowed in child scopes, @@ -324,6 +332,7 @@ export default class ReplaceSupers { isPrivateMethod: this.isPrivateMethod, getObjectRef: this.getObjectRef.bind(this), superRef: this.superRef, + thisRef: this.opts.thisRef, ...handler, }); } diff --git a/packages/babel-plugin-bugfix-super-spread/.npmignore b/packages/babel-plugin-bugfix-super-spread/.npmignore new file mode 100644 index 000000000000..f9806945836e --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-plugin-bugfix-super-spread/README.md b/packages/babel-plugin-bugfix-super-spread/README.md new file mode 100644 index 000000000000..aeca24cd090f --- /dev/null +++ b/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 +``` diff --git a/packages/babel-plugin-bugfix-super-spread/package.json b/packages/babel-plugin-bugfix-super-spread/package.json new file mode 100644 index 000000000000..6a0c2288c9bc --- /dev/null +++ b/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:*" + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/src/index.ts b/packages/babel-plugin-bugfix-super-spread/src/index.ts new file mode 100644 index 000000000000..2aa97183ef36 --- /dev/null +++ b/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(); + }, + }, + }; +}); diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/basic/input.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/basic/input.js new file mode 100644 index 000000000000..eafe0f648adb --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/basic/input.js @@ -0,0 +1,8 @@ +class A extends B { + constructor() { + super(1, ...foo, 2); + this.x; + super.x(); + super.y = 3; + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/basic/output.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/basic/output.js new file mode 100644 index 000000000000..ab03e7494725 --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/basic/output.js @@ -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; + } + +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/options.json b/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/options.json new file mode 100644 index 000000000000..be08664cc9f9 --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/assumption-constantSuper/options.json @@ -0,0 +1,6 @@ +{ + "plugins": ["bugfix-super-spread"], + "assumptions": { + "constantSuper": true + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/basic/input.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/basic/input.js new file mode 100644 index 000000000000..dfc94dac4768 --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/basic/input.js @@ -0,0 +1,5 @@ +class A extends B { + constructor() { + super(1, ...foo, 2); + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/basic/output.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/basic/output.js new file mode 100644 index 000000000000..38b88d74f5ee --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/basic/output.js @@ -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; + } + +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/in-arrow/input.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/in-arrow/input.js new file mode 100644 index 000000000000..0f65fe6a482d --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/in-arrow/input.js @@ -0,0 +1,7 @@ +class A extends B { + constructor() { + () => super(...args); + () => this; + () => super.foo; + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/in-arrow/output.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/in-arrow/output.js new file mode 100644 index 000000000000..4ee092a6c92a --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/in-arrow/output.js @@ -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; + } + +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/modify-return/input.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/modify-return/input.js new file mode 100644 index 000000000000..9416ae4b110c --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/modify-return/input.js @@ -0,0 +1,10 @@ +class A extends B { + constructor() { + super(...foo); + + () => { return x }; + + if (a) return; + if (b) return x; + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/modify-return/output.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/modify-return/output.js new file mode 100644 index 000000000000..935feb749b5f --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/modify-return/output.js @@ -0,0 +1,18 @@ +var _B; + +class A extends (_B = B) { + constructor() { + var _this; + + _this = Reflect.construct(_B, [...foo], new.target); + + () => { + return x; + }; + + if (a) return _this; + if (b) return babelHelpers.possibleConstructorReturn(_this, x); + return _this; + } + +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/not-needed/input.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/not-needed/input.js new file mode 100644 index 000000000000..f0f5b7104b03 --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/not-needed/input.js @@ -0,0 +1,8 @@ +class B { + constructor() {} +} +class A extends B { + constructor() { + super(1, 2, 3); + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/not-needed/output.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/not-needed/output.js new file mode 100644 index 000000000000..15c740d72007 --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/not-needed/output.js @@ -0,0 +1,11 @@ +class B { + constructor() {} + +} + +class A extends B { + constructor() { + super(1, 2, 3); + } + +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/only-transform-spread/input.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/only-transform-spread/input.js new file mode 100644 index 000000000000..582a91612edd --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/only-transform-spread/input.js @@ -0,0 +1,8 @@ +class A extends B { + constructor() { + if (a) super(1, 2, 3); + if (b) super(1, ...foo, 3); + if (c) super(4, 5, 6); + if (d) super(14, ...foo, 6); + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/only-transform-spread/output.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/only-transform-spread/output.js new file mode 100644 index 000000000000..72121acd3469 --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/only-transform-spread/output.js @@ -0,0 +1,14 @@ +var _B; + +class A extends (_B = B) { + constructor() { + var _this; + + if (a) _this = super(1, 2, 3); + if (b) _this = Reflect.construct(_B, [1, ...foo, 3], new.target); + if (c) _this = super(4, 5, 6); + if (d) _this = Reflect.construct(_B, [14, ...foo, 6], new.target); + return _this; + } + +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/super-property/input.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/super-property/input.js new file mode 100644 index 000000000000..430377c568ca --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/super-property/input.js @@ -0,0 +1,10 @@ +class A extends B { + constructor() { + super(...foo); + + super.x; + super.x(); + super.x = 2; + super.x?.(); + } +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/super-property/output.js b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/super-property/output.js new file mode 100644 index 000000000000..f12c9c4e7cbb --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/basic/super-property/output.js @@ -0,0 +1,15 @@ +var _B; + +class A extends (_B = B) { + constructor() { + var _this; + + _this = Reflect.construct(_B, [...foo], new.target); + babelHelpers.get(babelHelpers.getPrototypeOf(A.prototype), "x", _this); + babelHelpers.get(babelHelpers.getPrototypeOf(A.prototype), "x", _this).call(_this); + babelHelpers.set(babelHelpers.getPrototypeOf(A.prototype), "x", 2, _this, true); + babelHelpers.get(babelHelpers.getPrototypeOf(A.prototype), "x", _this)?.call(_this); + return _this; + } + +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/fixtures/options.json b/packages/babel-plugin-bugfix-super-spread/test/fixtures/options.json new file mode 100644 index 000000000000..35c5af34a1de --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/fixtures/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["bugfix-super-spread"] +} diff --git a/packages/babel-plugin-bugfix-super-spread/test/index.js b/packages/babel-plugin-bugfix-super-spread/test/index.js new file mode 100644 index 000000000000..21a55ce6b5e7 --- /dev/null +++ b/packages/babel-plugin-bugfix-super-spread/test/index.js @@ -0,0 +1,3 @@ +import runner from "@babel/helper-plugin-test-runner"; + +runner(import.meta.url); diff --git a/yarn.lock b/yarn.lock index 259619492fc5..c080d3340036 100644 --- a/yarn.lock +++ b/yarn.lock @@ -975,6 +975,19 @@ __metadata: languageName: unknown linkType: soft +"@babel/plugin-bugfix-super-spread@workspace:packages/babel-plugin-bugfix-super-spread": + version: 0.0.0-use.local + resolution: "@babel/plugin-bugfix-super-spread@workspace:packages/babel-plugin-bugfix-super-spread" + dependencies: + "@babel/core": "workspace:*" + "@babel/helper-plugin-test-runner": "workspace:*" + "@babel/helper-plugin-utils": "workspace:^7.13.0" + "@babel/helper-replace-supers": "workspace:^7.13.12" + peerDependencies: + "@babel/core": ^7.13.0 + languageName: unknown + linkType: soft + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@workspace:^7.13.12, @babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@workspace:packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining": version: 0.0.0-use.local resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@workspace:packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining"