Implement @babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining (#13009)
This commit is contained in:
parent
974f9c5b01
commit
c2a42492db
@ -426,6 +426,7 @@ const libBundles = [
|
|||||||
"packages/babel-plugin-proposal-optional-chaining",
|
"packages/babel-plugin-proposal-optional-chaining",
|
||||||
"packages/babel-preset-typescript",
|
"packages/babel-preset-typescript",
|
||||||
"packages/babel-helper-member-expression-to-functions",
|
"packages/babel-helper-member-expression-to-functions",
|
||||||
|
"packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining",
|
||||||
].map(src => ({
|
].map(src => ({
|
||||||
src,
|
src,
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
|
|||||||
@ -166,7 +166,9 @@ declare module "@babel/helper-function-name" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module "@babel/helper-split-export-declaration" {
|
declare module "@babel/helper-split-export-declaration" {
|
||||||
declare export default function splitExportDeclaration(exportDeclaration: any): any;
|
declare export default function splitExportDeclaration(
|
||||||
|
exportDeclaration: any
|
||||||
|
): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "@babel/traverse" {
|
declare module "@babel/traverse" {
|
||||||
@ -196,6 +198,13 @@ declare module "@babel/highlight" {
|
|||||||
/**
|
/**
|
||||||
* Highlight `code`.
|
* Highlight `code`.
|
||||||
*/
|
*/
|
||||||
declare export default function highlight(code: string, options?: Options): string;
|
declare export default function highlight(
|
||||||
|
code: string,
|
||||||
|
options?: Options
|
||||||
|
): string;
|
||||||
declare export { getChalk, shouldHighlight };
|
declare export { getChalk, shouldHighlight };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" {
|
||||||
|
declare module.exports: any;
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
src
|
||||||
|
test
|
||||||
|
*.log
|
||||||
@ -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
|
||||||
|
```
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining",
|
||||||
|
"version": "7.13.8",
|
||||||
|
"description": "Transform optional chaining operators to workaround https://crbug.com/v8/11558",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/babel/babel.git",
|
||||||
|
"directory": "packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining"
|
||||||
|
},
|
||||||
|
"homepage": "https://babel.dev/docs/en/next/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining",
|
||||||
|
"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-skip-transparent-expression-wrappers": "workspace:^7.12.1",
|
||||||
|
"@babel/plugin-proposal-optional-chaining": "workspace:^7.13.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.13.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "workspace:*",
|
||||||
|
"@babel/helper-plugin-test-runner": "workspace:*",
|
||||||
|
"@babel/traverse": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { declare } from "@babel/helper-plugin-utils";
|
||||||
|
import { transform } from "@babel/plugin-proposal-optional-chaining";
|
||||||
|
import { shouldTransform } from "./util";
|
||||||
|
|
||||||
|
export default declare(api => {
|
||||||
|
api.assertVersion(7);
|
||||||
|
|
||||||
|
const noDocumentAll = api.assumption("noDocumentAll");
|
||||||
|
const pureGetters = api.assumption("pureGetters");
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "bugfix-v8-spread-parameters-in-optional-chaining",
|
||||||
|
|
||||||
|
visitor: {
|
||||||
|
"OptionalCallExpression|OptionalMemberExpression"(path) {
|
||||||
|
if (shouldTransform(path)) {
|
||||||
|
transform(path, { noDocumentAll, pureGetters });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import { skipTransparentExprWrappers } from "@babel/helper-skip-transparent-expression-wrappers";
|
||||||
|
import type { NodePath } from "@babel/traverse";
|
||||||
|
import { types as t } from "@babel/core";
|
||||||
|
// https://crbug.com/v8/11558
|
||||||
|
|
||||||
|
// check if there is a spread element followed by another argument.
|
||||||
|
// (...[], 0) or (...[], ...[])
|
||||||
|
|
||||||
|
function matchAffectedArguments(argumentNodes) {
|
||||||
|
const spreadIndex = argumentNodes.findIndex(node => t.isSpreadElement(node));
|
||||||
|
return spreadIndex >= 0 && spreadIndex !== argumentNodes.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the optional chain is affected by https://crbug.com/v8/11558.
|
||||||
|
* This routine MUST not manipulate NodePath
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {(NodePath<t.OptionalMemberExpression | t.OptionalCallExpression>)} path
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function shouldTransform(
|
||||||
|
path: NodePath<t.OptionalMemberExpression | t.OptionalCallExpression>,
|
||||||
|
): boolean {
|
||||||
|
let optionalPath = path;
|
||||||
|
const chains = [];
|
||||||
|
while (
|
||||||
|
optionalPath.isOptionalMemberExpression() ||
|
||||||
|
optionalPath.isOptionalCallExpression()
|
||||||
|
) {
|
||||||
|
const { node } = optionalPath;
|
||||||
|
chains.push(node);
|
||||||
|
|
||||||
|
if (optionalPath.isOptionalMemberExpression()) {
|
||||||
|
optionalPath = skipTransparentExprWrappers(optionalPath.get("object"));
|
||||||
|
} else if (optionalPath.isOptionalCallExpression()) {
|
||||||
|
optionalPath = skipTransparentExprWrappers(optionalPath.get("callee"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < chains.length; i++) {
|
||||||
|
const node = chains[i];
|
||||||
|
if (
|
||||||
|
t.isOptionalCallExpression(node) &&
|
||||||
|
matchAffectedArguments(node.arguments)
|
||||||
|
) {
|
||||||
|
// f?.(...[], 0)
|
||||||
|
if (node.optional) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// o?.m(...[], 0)
|
||||||
|
// when node.optional is false, chains[i + 1] is always well defined
|
||||||
|
const callee = chains[i + 1];
|
||||||
|
if (t.isOptionalMemberExpression(callee, { optional: true })) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
fn?.(...b, 1);
|
||||||
|
|
||||||
|
a?.b(...c, 1);
|
||||||
|
|
||||||
|
a?.b?.(...c, 1);
|
||||||
|
|
||||||
|
a.b?.(...c, 1);
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
var _fn, _a, _a2, _a2$b, _a$b, _a3;
|
||||||
|
|
||||||
|
(_fn = fn) === null || _fn === void 0 ? void 0 : _fn(...b, 1);
|
||||||
|
(_a = a) === null || _a === void 0 ? void 0 : _a.b(...c, 1);
|
||||||
|
(_a2 = a) === null || _a2 === void 0 ? void 0 : (_a2$b = _a2.b) === null || _a2$b === void 0 ? void 0 : _a2$b.call(_a2, ...c, 1);
|
||||||
|
(_a$b = (_a3 = a).b) === null || _a$b === void 0 ? void 0 : _a$b.call(_a3, ...c, 1);
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
foo?.(...[], 1);
|
||||||
|
|
||||||
|
foo?.bar(...[], 1)
|
||||||
|
|
||||||
|
foo.bar?.(foo.bar, ...[], 1)
|
||||||
|
|
||||||
|
foo?.bar?.(foo.bar, ...[], 1)
|
||||||
|
|
||||||
|
foo?.(...[], 1).bar
|
||||||
|
|
||||||
|
foo?.(...[], 1)?.bar
|
||||||
|
|
||||||
|
foo.bar?.(...[], 1).baz
|
||||||
|
|
||||||
|
foo.bar?.(...[], 1)?.baz
|
||||||
|
|
||||||
|
foo?.bar?.(...[], 1).baz
|
||||||
|
|
||||||
|
foo?.bar?.(...[], 1)?.baz
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
var _foo, _foo2, _foo$bar, _foo3, _foo4, _foo4$bar, _foo5, _foo6, _foo7, _foo$bar2, _foo8, _foo$bar3, _foo9, _foo$bar3$call, _foo10, _foo10$bar, _foo11, _foo11$bar, _foo11$bar$call;
|
||||||
|
|
||||||
|
(_foo = foo) === null || _foo === void 0 ? void 0 : _foo(...[], 1);
|
||||||
|
(_foo2 = foo) === null || _foo2 === void 0 ? void 0 : _foo2.bar(...[], 1);
|
||||||
|
(_foo$bar = (_foo3 = foo).bar) === null || _foo$bar === void 0 ? void 0 : _foo$bar.call(_foo3, foo.bar, ...[], 1);
|
||||||
|
(_foo4 = foo) === null || _foo4 === void 0 ? void 0 : (_foo4$bar = _foo4.bar) === null || _foo4$bar === void 0 ? void 0 : _foo4$bar.call(_foo4, foo.bar, ...[], 1);
|
||||||
|
(_foo5 = foo) === null || _foo5 === void 0 ? void 0 : _foo5(...[], 1).bar;
|
||||||
|
(_foo6 = foo) === null || _foo6 === void 0 ? void 0 : (_foo7 = _foo6(...[], 1)) === null || _foo7 === void 0 ? void 0 : _foo7.bar;
|
||||||
|
(_foo$bar2 = (_foo8 = foo).bar) === null || _foo$bar2 === void 0 ? void 0 : _foo$bar2.call(_foo8, ...[], 1).baz;
|
||||||
|
(_foo$bar3 = (_foo9 = foo).bar) === null || _foo$bar3 === void 0 ? void 0 : (_foo$bar3$call = _foo$bar3.call(_foo9, ...[], 1)) === null || _foo$bar3$call === void 0 ? void 0 : _foo$bar3$call.baz;
|
||||||
|
(_foo10 = foo) === null || _foo10 === void 0 ? void 0 : (_foo10$bar = _foo10.bar) === null || _foo10$bar === void 0 ? void 0 : _foo10$bar.call(_foo10, ...[], 1).baz;
|
||||||
|
(_foo11 = foo) === null || _foo11 === void 0 ? void 0 : (_foo11$bar = _foo11.bar) === null || _foo11$bar === void 0 ? void 0 : (_foo11$bar$call = _foo11$bar.call(_foo11, ...[], 1)) === null || _foo11$bar$call === void 0 ? void 0 : _foo11$bar$call.baz;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
fn?.(...b, 1);
|
||||||
|
|
||||||
|
a?.b(...c, 1);
|
||||||
|
|
||||||
|
a?.b?.(...c, 1);
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"proposal-optional-chaining", "bugfix-v8-spread-parameters-in-optional-chaining"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
var _fn, _a, _a2, _a2$b;
|
||||||
|
|
||||||
|
(_fn = fn) === null || _fn === void 0 ? void 0 : _fn(...b, 1);
|
||||||
|
(_a = a) === null || _a === void 0 ? void 0 : _a.b(...c, 1);
|
||||||
|
(_a2 = a) === null || _a2 === void 0 ? void 0 : (_a2$b = _a2.b) === null || _a2$b === void 0 ? void 0 : _a2$b.call(_a2, ...c, 1);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
fn?.(...b, 1);
|
||||||
|
|
||||||
|
a?.b(...c, 1);
|
||||||
|
|
||||||
|
a?.b?.(...c, 1);
|
||||||
|
|
||||||
|
a.b?.(...c, 1);
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
var _fn, _a, _a2, _a2$b, _a$b, _a3;
|
||||||
|
|
||||||
|
(_fn = fn) === null || _fn === void 0 ? void 0 : _fn(...b, 1);
|
||||||
|
(_a = a) === null || _a === void 0 ? void 0 : _a.b(...c, 1);
|
||||||
|
(_a2 = a) === null || _a2 === void 0 ? void 0 : (_a2$b = _a2.b) === null || _a2$b === void 0 ? void 0 : _a2$b.call(_a2, ...c, 1);
|
||||||
|
(_a$b = (_a3 = a).b) === null || _a$b === void 0 ? void 0 : _a$b.call(_a3, ...c, 1);
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class C {
|
||||||
|
#m;
|
||||||
|
constructor() {
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o?.#m(...c, 1);
|
||||||
|
const q = n?.#m?.(...c, 1);
|
||||||
|
expect(p).toBe(undefined);
|
||||||
|
expect(q).toBe(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new C();
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
class C {
|
||||||
|
#m;
|
||||||
|
constructor() {
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o?.#m(...c, 1);
|
||||||
|
const q = n?.#m?.(...c, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"proposal-class-properties", "proposal-private-methods", "proposal-optional-chaining", "bugfix-v8-spread-parameters-in-optional-chaining"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
var _m = new WeakMap();
|
||||||
|
|
||||||
|
class C {
|
||||||
|
constructor() {
|
||||||
|
var _babelHelpers$classPr;
|
||||||
|
|
||||||
|
_m.set(this, {
|
||||||
|
writable: true,
|
||||||
|
value: void 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldGet(o, _m).call(o, ...c, 1);
|
||||||
|
const q = n === null || n === void 0 ? void 0 : (_babelHelpers$classPr = babelHelpers.classPrivateFieldGet(n, _m)) === null || _babelHelpers$classPr === void 0 ? void 0 : _babelHelpers$classPr.call(n, ...c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class C {
|
||||||
|
#m;
|
||||||
|
constructor() {
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o?.#m(...c, 1);
|
||||||
|
const q = n?.#m?.(...c, 1);
|
||||||
|
expect(p).toBe(undefined);
|
||||||
|
expect(q).toBe(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new C();
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
class C {
|
||||||
|
#m;
|
||||||
|
constructor() {
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o?.#m(...c, 1);
|
||||||
|
const q = n?.#m?.(...c, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"proposal-class-properties", "proposal-private-methods", "bugfix-v8-spread-parameters-in-optional-chaining"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
var _m = new WeakMap();
|
||||||
|
|
||||||
|
class C {
|
||||||
|
constructor() {
|
||||||
|
var _babelHelpers$classPr;
|
||||||
|
|
||||||
|
_m.set(this, {
|
||||||
|
writable: true,
|
||||||
|
value: void 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldGet(o, _m).call(o, ...c, 1);
|
||||||
|
const q = n === null || n === void 0 ? void 0 : (_babelHelpers$classPr = babelHelpers.classPrivateFieldGet(n, _m)) === null || _babelHelpers$classPr === void 0 ? void 0 : _babelHelpers$classPr.call(n, ...c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class C {
|
||||||
|
#m;
|
||||||
|
constructor() {
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o?.#m(...c, 1);
|
||||||
|
const q = n?.#m?.(...c, 1);
|
||||||
|
expect(p).toBe(undefined);
|
||||||
|
expect(q).toBe(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new C;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
class C {
|
||||||
|
#m;
|
||||||
|
constructor() {
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o?.#m(...c, 1);
|
||||||
|
const q = n?.#m?.(...c, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"minNodeVersion": "14.0.0",
|
||||||
|
"parserOpts": {
|
||||||
|
"plugins": [
|
||||||
|
"classPrivateMethods",
|
||||||
|
"classPrivateProperties",
|
||||||
|
"classProperties"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class C {
|
||||||
|
#m;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
var _n$m;
|
||||||
|
|
||||||
|
const o = null;
|
||||||
|
const n = this;
|
||||||
|
const p = o === null || o === void 0 ? void 0 : o.#m(...c, 1);
|
||||||
|
const q = n === null || n === void 0 ? void 0 : (_n$m = n.#m) === null || _n$m === void 0 ? void 0 : _n$m.call(n, ...c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["bugfix-v8-spread-parameters-in-optional-chaining"]
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import runner from "@babel/helper-plugin-test-runner";
|
||||||
|
|
||||||
|
runner(import.meta.url);
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
import { parseSync, traverse } from "@babel/core";
|
||||||
|
import { shouldTransform } from "../src/util.ts";
|
||||||
|
|
||||||
|
function getPath(input, parserOpts = {}) {
|
||||||
|
let targetPath;
|
||||||
|
traverse(
|
||||||
|
parseSync(input, {
|
||||||
|
parserOpts: {
|
||||||
|
plugins: [
|
||||||
|
"classPrivateMethods",
|
||||||
|
"classPrivateProperties",
|
||||||
|
"classProperties",
|
||||||
|
...(parserOpts.plugins || []),
|
||||||
|
],
|
||||||
|
...parserOpts,
|
||||||
|
},
|
||||||
|
filename: "example.js",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
"OptionalMemberExpression|OptionalCallExpression"(path) {
|
||||||
|
targetPath = path;
|
||||||
|
path.stop();
|
||||||
|
},
|
||||||
|
noScope: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return targetPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("shouldTransform", () => {
|
||||||
|
const positiveCases = [
|
||||||
|
"fn?.(...[], 0)",
|
||||||
|
"fn?.(...[], ...[])",
|
||||||
|
"fn?.(0, ...[], ...[])",
|
||||||
|
"a?.b(...[], 0)",
|
||||||
|
"a?.[b](...[], 0)",
|
||||||
|
"a?.b?.(...[], 0)",
|
||||||
|
"fn?.(0, ...[], 0)",
|
||||||
|
"a?.b?.(0, ...[], 0)",
|
||||||
|
"(a?.b)?.(...[], 0)",
|
||||||
|
"a?.b.c?.(...[], 0)",
|
||||||
|
"class C { #c; p = obj?.#c(...[], 0) }",
|
||||||
|
"class C { #c; p = obj.#c?.(...[], 0) }",
|
||||||
|
];
|
||||||
|
|
||||||
|
const negativeCases = [
|
||||||
|
"a?.b",
|
||||||
|
"fn?.(1)",
|
||||||
|
"fn?.(...[])",
|
||||||
|
"fn?.(1, ...[])",
|
||||||
|
"a?.b(...[])",
|
||||||
|
"a?.()(...[], 1)", // optional call under optional call is not affected
|
||||||
|
"(a?.b)(...[], 1)", // not an optional call
|
||||||
|
"a?.b.c(...[], 1)",
|
||||||
|
"a?.[fn?.(...[], 0)]", // optional chain in property will be handled when traversed
|
||||||
|
"a?.(fn?.(...[], 0))", // optional chain in arguments will be handled when traversed
|
||||||
|
"class C { #c; p = obj?.#c(...[]) }",
|
||||||
|
];
|
||||||
|
|
||||||
|
const typescriptPositiveCases = [
|
||||||
|
"(a?.(...[], 0) as any)?.b",
|
||||||
|
"(a?.(...[], 0) as any)?.()",
|
||||||
|
];
|
||||||
|
|
||||||
|
const typescriptNegativeCases = ["(a?.b as any)(...[], 0)"];
|
||||||
|
|
||||||
|
describe("default parser options", () => {
|
||||||
|
test.each(positiveCases)(
|
||||||
|
"shouldTransform(a?.b in %p) should return true",
|
||||||
|
input => {
|
||||||
|
expect(shouldTransform(getPath(input))).toBe(true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
test.each(negativeCases)(
|
||||||
|
"shouldTransform(a?.b in %p) should return false",
|
||||||
|
input => {
|
||||||
|
expect(shouldTransform(getPath(input))).toBe(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createParenthesizedExpressions", () => {
|
||||||
|
test.each(positiveCases)(
|
||||||
|
"shouldTransform(a?.b in %p with { createParenthesizedExpressions: true }) should return true",
|
||||||
|
input => {
|
||||||
|
const parserOpts = { createParenthesizedExpressions: true };
|
||||||
|
expect(shouldTransform(getPath(input, parserOpts))).toBe(true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
test.each(negativeCases)(
|
||||||
|
"shouldTransform(a?.b in %p with { createParenthesizedExpressions: true }) should return false",
|
||||||
|
input => {
|
||||||
|
const parserOpts = { createParenthesizedExpressions: true };
|
||||||
|
expect(shouldTransform(getPath(input, parserOpts))).toBe(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("plugins: [typescript]", () => {
|
||||||
|
test.each(positiveCases.concat(typescriptPositiveCases))(
|
||||||
|
"shouldTransform(a?.b in %p with { plugins: [typescript] }) should return true",
|
||||||
|
input => {
|
||||||
|
const parserOpts = { plugins: ["typescript"] };
|
||||||
|
expect(shouldTransform(getPath(input, parserOpts))).toBe(true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
test.each(negativeCases.concat(typescriptNegativeCases))(
|
||||||
|
"shouldTransform(a?.b in %p with { plugins: [typescript] }) should return false",
|
||||||
|
input => {
|
||||||
|
const parserOpts = { plugins: ["typescript"] };
|
||||||
|
expect(shouldTransform(getPath(input, parserOpts))).toBe(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,13 +1,6 @@
|
|||||||
import { declare } from "@babel/helper-plugin-utils";
|
import { declare } from "@babel/helper-plugin-utils";
|
||||||
import {
|
|
||||||
isTransparentExprWrapper,
|
|
||||||
skipTransparentExprWrappers,
|
|
||||||
} from "@babel/helper-skip-transparent-expression-wrappers";
|
|
||||||
import syntaxOptionalChaining from "@babel/plugin-syntax-optional-chaining";
|
import syntaxOptionalChaining from "@babel/plugin-syntax-optional-chaining";
|
||||||
import { types as t, template } from "@babel/core";
|
import { transform } from "./transform";
|
||||||
import { willPathCastToBoolean, findOutermostTransparentParent } from "./util";
|
|
||||||
|
|
||||||
const { ast } = template.expression;
|
|
||||||
|
|
||||||
export default declare((api, options) => {
|
export default declare((api, options) => {
|
||||||
api.assertVersion(7);
|
api.assertVersion(7);
|
||||||
@ -16,229 +9,16 @@ export default declare((api, options) => {
|
|||||||
const noDocumentAll = api.assumption("noDocumentAll") ?? loose;
|
const noDocumentAll = api.assumption("noDocumentAll") ?? loose;
|
||||||
const pureGetters = api.assumption("pureGetters") ?? loose;
|
const pureGetters = api.assumption("pureGetters") ?? loose;
|
||||||
|
|
||||||
function isSimpleMemberExpression(expression) {
|
|
||||||
expression = skipTransparentExprWrappers(expression);
|
|
||||||
return (
|
|
||||||
t.isIdentifier(expression) ||
|
|
||||||
t.isSuper(expression) ||
|
|
||||||
(t.isMemberExpression(expression) &&
|
|
||||||
!expression.computed &&
|
|
||||||
isSimpleMemberExpression(expression.object))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if a given optional chain `path` needs to be memoized
|
|
||||||
* @param {NodePath} path
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function needsMemoize(path) {
|
|
||||||
let optionalPath = path;
|
|
||||||
const { scope } = path;
|
|
||||||
while (
|
|
||||||
optionalPath.isOptionalMemberExpression() ||
|
|
||||||
optionalPath.isOptionalCallExpression()
|
|
||||||
) {
|
|
||||||
const { node } = optionalPath;
|
|
||||||
const childKey = optionalPath.isOptionalMemberExpression()
|
|
||||||
? "object"
|
|
||||||
: "callee";
|
|
||||||
const childPath = skipTransparentExprWrappers(optionalPath.get(childKey));
|
|
||||||
if (node.optional) {
|
|
||||||
return !scope.isStatic(childPath.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
optionalPath = childPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "proposal-optional-chaining",
|
name: "proposal-optional-chaining",
|
||||||
inherits: syntaxOptionalChaining,
|
inherits: syntaxOptionalChaining,
|
||||||
|
|
||||||
visitor: {
|
visitor: {
|
||||||
"OptionalCallExpression|OptionalMemberExpression"(path) {
|
"OptionalCallExpression|OptionalMemberExpression"(path) {
|
||||||
const { scope } = path;
|
transform(path, { noDocumentAll, pureGetters });
|
||||||
// maybeWrapped points to the outermost transparent expression wrapper
|
|
||||||
// or the path itself
|
|
||||||
const maybeWrapped = findOutermostTransparentParent(path);
|
|
||||||
const { parentPath } = maybeWrapped;
|
|
||||||
const willReplacementCastToBoolean = willPathCastToBoolean(
|
|
||||||
maybeWrapped,
|
|
||||||
);
|
|
||||||
let isDeleteOperation = false;
|
|
||||||
const parentIsCall =
|
|
||||||
parentPath.isCallExpression({ callee: maybeWrapped.node }) &&
|
|
||||||
// note that the first condition must implies that `path.optional` is `true`,
|
|
||||||
// otherwise the parentPath should be an OptionalCallExpressioin
|
|
||||||
path.isOptionalMemberExpression();
|
|
||||||
|
|
||||||
const optionals = [];
|
|
||||||
|
|
||||||
let optionalPath = path;
|
|
||||||
// Replace `function (a, x = a.b?.c) {}` to `function (a, x = (() => a.b?.c)() ){}`
|
|
||||||
// so the temporary variable can be injected in correct scope
|
|
||||||
if (scope.path.isPattern() && needsMemoize(optionalPath)) {
|
|
||||||
path.replaceWith(template.ast`(() => ${path.node})()`);
|
|
||||||
// The injected optional chain will be queued and eventually transformed when visited
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (
|
|
||||||
optionalPath.isOptionalMemberExpression() ||
|
|
||||||
optionalPath.isOptionalCallExpression()
|
|
||||||
) {
|
|
||||||
const { node } = optionalPath;
|
|
||||||
if (node.optional) {
|
|
||||||
optionals.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (optionalPath.isOptionalMemberExpression()) {
|
|
||||||
optionalPath.node.type = "MemberExpression";
|
|
||||||
optionalPath = skipTransparentExprWrappers(
|
|
||||||
optionalPath.get("object"),
|
|
||||||
);
|
|
||||||
} else if (optionalPath.isOptionalCallExpression()) {
|
|
||||||
optionalPath.node.type = "CallExpression";
|
|
||||||
optionalPath = skipTransparentExprWrappers(
|
|
||||||
optionalPath.get("callee"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let replacementPath = path;
|
|
||||||
if (parentPath.isUnaryExpression({ operator: "delete" })) {
|
|
||||||
replacementPath = parentPath;
|
|
||||||
isDeleteOperation = true;
|
|
||||||
}
|
|
||||||
for (let i = optionals.length - 1; i >= 0; i--) {
|
|
||||||
const node = optionals[i];
|
|
||||||
|
|
||||||
const isCall = t.isCallExpression(node);
|
|
||||||
const replaceKey = isCall ? "callee" : "object";
|
|
||||||
|
|
||||||
const chainWithTypes = node[replaceKey];
|
|
||||||
let chain = chainWithTypes;
|
|
||||||
|
|
||||||
while (isTransparentExprWrapper(chain)) {
|
|
||||||
chain = chain.expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ref;
|
|
||||||
let check;
|
|
||||||
if (isCall && t.isIdentifier(chain, { name: "eval" })) {
|
|
||||||
check = ref = chain;
|
|
||||||
// `eval?.()` is an indirect eval call transformed to `(0,eval)()`
|
|
||||||
node[replaceKey] = t.sequenceExpression([t.numericLiteral(0), ref]);
|
|
||||||
} else if (pureGetters && isCall && isSimpleMemberExpression(chain)) {
|
|
||||||
// If we assume getters are pure (avoiding a Function#call) and we are at the call,
|
|
||||||
// we can avoid a needless memoize. We only do this if the callee is a simple member
|
|
||||||
// expression, to avoid multiple calls to nested call expressions.
|
|
||||||
check = ref = chainWithTypes;
|
|
||||||
} else {
|
|
||||||
ref = scope.maybeGenerateMemoised(chain);
|
|
||||||
if (ref) {
|
|
||||||
check = t.assignmentExpression(
|
|
||||||
"=",
|
|
||||||
t.cloneNode(ref),
|
|
||||||
// Here `chainWithTypes` MUST NOT be cloned because it could be
|
|
||||||
// updated when generating the memoised context of a call
|
|
||||||
// expression
|
|
||||||
chainWithTypes,
|
|
||||||
);
|
|
||||||
|
|
||||||
node[replaceKey] = ref;
|
|
||||||
} else {
|
|
||||||
check = ref = chainWithTypes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure call expressions have the proper `this`
|
|
||||||
// `foo.bar()` has context `foo`.
|
|
||||||
if (isCall && t.isMemberExpression(chain)) {
|
|
||||||
if (pureGetters && isSimpleMemberExpression(chain)) {
|
|
||||||
// To avoid a Function#call, we can instead re-grab the property from the context object.
|
|
||||||
// `a.?b.?()` translates roughly to `_a.b != null && _a.b()`
|
|
||||||
node.callee = chainWithTypes;
|
|
||||||
} else {
|
|
||||||
// Otherwise, we need to memoize the context object, and change the call into a Function#call.
|
|
||||||
// `a.?b.?()` translates roughly to `(_b = _a.b) != null && _b.call(_a)`
|
|
||||||
const { object } = chain;
|
|
||||||
let context = scope.maybeGenerateMemoised(object);
|
|
||||||
if (context) {
|
|
||||||
chain.object = t.assignmentExpression("=", context, object);
|
|
||||||
} else if (t.isSuper(object)) {
|
|
||||||
context = t.thisExpression();
|
|
||||||
} else {
|
|
||||||
context = object;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.arguments.unshift(t.cloneNode(context));
|
|
||||||
node.callee = t.memberExpression(
|
|
||||||
node.callee,
|
|
||||||
t.identifier("call"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let replacement = replacementPath.node;
|
|
||||||
// Ensure (a?.b)() has proper `this`
|
|
||||||
// The `parentIsCall` is constant within loop, we should check i === 0
|
|
||||||
// to ensure that it is only applied to the first optional chain element
|
|
||||||
// i.e. `?.b` in `(a?.b.c)()`
|
|
||||||
if (i === 0 && parentIsCall) {
|
|
||||||
// `(a?.b)()` to `(a == null ? undefined : a.b.bind(a))()`
|
|
||||||
const object = skipTransparentExprWrappers(
|
|
||||||
replacementPath.get("object"),
|
|
||||||
).node;
|
|
||||||
let baseRef;
|
|
||||||
if (!pureGetters || !isSimpleMemberExpression(object)) {
|
|
||||||
// memoize the context object when getters are not always pure
|
|
||||||
// or the object is not a simple member expression
|
|
||||||
// `(a?.b.c)()` to `(a == null ? undefined : (_a$b = a.b).c.bind(_a$b))()`
|
|
||||||
baseRef = scope.maybeGenerateMemoised(object);
|
|
||||||
if (baseRef) {
|
|
||||||
replacement.object = t.assignmentExpression(
|
|
||||||
"=",
|
|
||||||
baseRef,
|
|
||||||
object,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
replacement = t.callExpression(
|
|
||||||
t.memberExpression(replacement, t.identifier("bind")),
|
|
||||||
[t.cloneNode(baseRef ?? object)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (willReplacementCastToBoolean) {
|
|
||||||
// `if (a?.b) {}` transformed to `if (a != null && a.b) {}`
|
|
||||||
// we don't need to return `void 0` because the returned value will
|
|
||||||
// eveutally cast to boolean.
|
|
||||||
const nonNullishCheck = noDocumentAll
|
|
||||||
? ast`${t.cloneNode(check)} != null`
|
|
||||||
: ast`
|
|
||||||
${t.cloneNode(check)} !== null && ${t.cloneNode(ref)} !== void 0`;
|
|
||||||
replacementPath.replaceWith(
|
|
||||||
t.logicalExpression("&&", nonNullishCheck, replacement),
|
|
||||||
);
|
|
||||||
replacementPath = skipTransparentExprWrappers(
|
|
||||||
replacementPath.get("right"),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const nullishCheck = noDocumentAll
|
|
||||||
? ast`${t.cloneNode(check)} == null`
|
|
||||||
: ast`
|
|
||||||
${t.cloneNode(check)} === null || ${t.cloneNode(ref)} === void 0`;
|
|
||||||
|
|
||||||
const returnValue = isDeleteOperation ? ast`true` : ast`void 0`;
|
|
||||||
replacementPath.replaceWith(
|
|
||||||
t.conditionalExpression(nullishCheck, returnValue, replacement),
|
|
||||||
);
|
|
||||||
replacementPath = skipTransparentExprWrappers(
|
|
||||||
replacementPath.get("alternate"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export { transform };
|
||||||
|
|||||||
@ -0,0 +1,219 @@
|
|||||||
|
import { types as t, template } from "@babel/core";
|
||||||
|
import {
|
||||||
|
isTransparentExprWrapper,
|
||||||
|
skipTransparentExprWrappers,
|
||||||
|
} from "@babel/helper-skip-transparent-expression-wrappers";
|
||||||
|
import { willPathCastToBoolean, findOutermostTransparentParent } from "./util";
|
||||||
|
|
||||||
|
const { ast } = template.expression;
|
||||||
|
|
||||||
|
function isSimpleMemberExpression(expression) {
|
||||||
|
expression = skipTransparentExprWrappers(expression);
|
||||||
|
return (
|
||||||
|
t.isIdentifier(expression) ||
|
||||||
|
t.isSuper(expression) ||
|
||||||
|
(t.isMemberExpression(expression) &&
|
||||||
|
!expression.computed &&
|
||||||
|
isSimpleMemberExpression(expression.object))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a given optional chain `path` needs to be memoized
|
||||||
|
* @param {NodePath} path
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function needsMemoize(path) {
|
||||||
|
let optionalPath = path;
|
||||||
|
const { scope } = path;
|
||||||
|
while (
|
||||||
|
optionalPath.isOptionalMemberExpression() ||
|
||||||
|
optionalPath.isOptionalCallExpression()
|
||||||
|
) {
|
||||||
|
const { node } = optionalPath;
|
||||||
|
const childKey = optionalPath.isOptionalMemberExpression()
|
||||||
|
? "object"
|
||||||
|
: "callee";
|
||||||
|
const childPath = skipTransparentExprWrappers(optionalPath.get(childKey));
|
||||||
|
if (node.optional) {
|
||||||
|
return !scope.isStatic(childPath.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
optionalPath = childPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transform(
|
||||||
|
path: NodePath<t.OptionalCallExpression | t.OptionalMemberExpression>,
|
||||||
|
{
|
||||||
|
pureGetters,
|
||||||
|
noDocumentAll,
|
||||||
|
}: { pureGetters: boolean, noDocumentAll: boolean },
|
||||||
|
) {
|
||||||
|
const { scope } = path;
|
||||||
|
// maybeWrapped points to the outermost transparent expression wrapper
|
||||||
|
// or the path itself
|
||||||
|
const maybeWrapped = findOutermostTransparentParent(path);
|
||||||
|
const { parentPath } = maybeWrapped;
|
||||||
|
const willReplacementCastToBoolean = willPathCastToBoolean(maybeWrapped);
|
||||||
|
let isDeleteOperation = false;
|
||||||
|
const parentIsCall =
|
||||||
|
parentPath.isCallExpression({ callee: maybeWrapped.node }) &&
|
||||||
|
// note that the first condition must implies that `path.optional` is `true`,
|
||||||
|
// otherwise the parentPath should be an OptionalCallExpression
|
||||||
|
path.isOptionalMemberExpression();
|
||||||
|
|
||||||
|
const optionals = [];
|
||||||
|
|
||||||
|
let optionalPath = path;
|
||||||
|
// Replace `function (a, x = a.b?.c) {}` to `function (a, x = (() => a.b?.c)() ){}`
|
||||||
|
// so the temporary variable can be injected in correct scope
|
||||||
|
if (scope.path.isPattern() && needsMemoize(optionalPath)) {
|
||||||
|
path.replaceWith(template.ast`(() => ${path.node})()`);
|
||||||
|
// The injected optional chain will be queued and eventually transformed when visited
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (
|
||||||
|
optionalPath.isOptionalMemberExpression() ||
|
||||||
|
optionalPath.isOptionalCallExpression()
|
||||||
|
) {
|
||||||
|
const { node } = optionalPath;
|
||||||
|
if (node.optional) {
|
||||||
|
optionals.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionalPath.isOptionalMemberExpression()) {
|
||||||
|
optionalPath.node.type = "MemberExpression";
|
||||||
|
optionalPath = skipTransparentExprWrappers(optionalPath.get("object"));
|
||||||
|
} else if (optionalPath.isOptionalCallExpression()) {
|
||||||
|
optionalPath.node.type = "CallExpression";
|
||||||
|
optionalPath = skipTransparentExprWrappers(optionalPath.get("callee"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let replacementPath = path;
|
||||||
|
if (parentPath.isUnaryExpression({ operator: "delete" })) {
|
||||||
|
replacementPath = parentPath;
|
||||||
|
isDeleteOperation = true;
|
||||||
|
}
|
||||||
|
for (let i = optionals.length - 1; i >= 0; i--) {
|
||||||
|
const node = optionals[i];
|
||||||
|
|
||||||
|
const isCall = t.isCallExpression(node);
|
||||||
|
const replaceKey = isCall ? "callee" : "object";
|
||||||
|
|
||||||
|
const chainWithTypes = node[replaceKey];
|
||||||
|
let chain = chainWithTypes;
|
||||||
|
|
||||||
|
while (isTransparentExprWrapper(chain)) {
|
||||||
|
chain = chain.expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ref;
|
||||||
|
let check;
|
||||||
|
if (isCall && t.isIdentifier(chain, { name: "eval" })) {
|
||||||
|
check = ref = chain;
|
||||||
|
// `eval?.()` is an indirect eval call transformed to `(0,eval)()`
|
||||||
|
node[replaceKey] = t.sequenceExpression([t.numericLiteral(0), ref]);
|
||||||
|
} else if (pureGetters && isCall && isSimpleMemberExpression(chain)) {
|
||||||
|
// If we assume getters are pure (avoiding a Function#call) and we are at the call,
|
||||||
|
// we can avoid a needless memoize. We only do this if the callee is a simple member
|
||||||
|
// expression, to avoid multiple calls to nested call expressions.
|
||||||
|
check = ref = chainWithTypes;
|
||||||
|
} else {
|
||||||
|
ref = scope.maybeGenerateMemoised(chain);
|
||||||
|
if (ref) {
|
||||||
|
check = t.assignmentExpression(
|
||||||
|
"=",
|
||||||
|
t.cloneNode(ref),
|
||||||
|
// Here `chainWithTypes` MUST NOT be cloned because it could be
|
||||||
|
// updated when generating the memoised context of a call
|
||||||
|
// expression
|
||||||
|
chainWithTypes,
|
||||||
|
);
|
||||||
|
|
||||||
|
node[replaceKey] = ref;
|
||||||
|
} else {
|
||||||
|
check = ref = chainWithTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure call expressions have the proper `this`
|
||||||
|
// `foo.bar()` has context `foo`.
|
||||||
|
if (isCall && t.isMemberExpression(chain)) {
|
||||||
|
if (pureGetters && isSimpleMemberExpression(chain)) {
|
||||||
|
// To avoid a Function#call, we can instead re-grab the property from the context object.
|
||||||
|
// `a.?b.?()` translates roughly to `_a.b != null && _a.b()`
|
||||||
|
node.callee = chainWithTypes;
|
||||||
|
} else {
|
||||||
|
// Otherwise, we need to memoize the context object, and change the call into a Function#call.
|
||||||
|
// `a.?b.?()` translates roughly to `(_b = _a.b) != null && _b.call(_a)`
|
||||||
|
const { object } = chain;
|
||||||
|
let context = scope.maybeGenerateMemoised(object);
|
||||||
|
if (context) {
|
||||||
|
chain.object = t.assignmentExpression("=", context, object);
|
||||||
|
} else if (t.isSuper(object)) {
|
||||||
|
context = t.thisExpression();
|
||||||
|
} else {
|
||||||
|
context = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.arguments.unshift(t.cloneNode(context));
|
||||||
|
node.callee = t.memberExpression(node.callee, t.identifier("call"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let replacement = replacementPath.node;
|
||||||
|
// Ensure (a?.b)() has proper `this`
|
||||||
|
// The `parentIsCall` is constant within loop, we should check i === 0
|
||||||
|
// to ensure that it is only applied to the first optional chain element
|
||||||
|
// i.e. `?.b` in `(a?.b.c)()`
|
||||||
|
if (i === 0 && parentIsCall) {
|
||||||
|
// `(a?.b)()` to `(a == null ? undefined : a.b.bind(a))()`
|
||||||
|
const object = skipTransparentExprWrappers(replacementPath.get("object"))
|
||||||
|
.node;
|
||||||
|
let baseRef;
|
||||||
|
if (!pureGetters || !isSimpleMemberExpression(object)) {
|
||||||
|
// memoize the context object when getters are not always pure
|
||||||
|
// or the object is not a simple member expression
|
||||||
|
// `(a?.b.c)()` to `(a == null ? undefined : (_a$b = a.b).c.bind(_a$b))()`
|
||||||
|
baseRef = scope.maybeGenerateMemoised(object);
|
||||||
|
if (baseRef) {
|
||||||
|
replacement.object = t.assignmentExpression("=", baseRef, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replacement = t.callExpression(
|
||||||
|
t.memberExpression(replacement, t.identifier("bind")),
|
||||||
|
[t.cloneNode(baseRef ?? object)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willReplacementCastToBoolean) {
|
||||||
|
// `if (a?.b) {}` transformed to `if (a != null && a.b) {}`
|
||||||
|
// we don't need to return `void 0` because the returned value will
|
||||||
|
// eveutally cast to boolean.
|
||||||
|
const nonNullishCheck = noDocumentAll
|
||||||
|
? ast`${t.cloneNode(check)} != null`
|
||||||
|
: ast`
|
||||||
|
${t.cloneNode(check)} !== null && ${t.cloneNode(ref)} !== void 0`;
|
||||||
|
replacementPath.replaceWith(
|
||||||
|
t.logicalExpression("&&", nonNullishCheck, replacement),
|
||||||
|
);
|
||||||
|
replacementPath = skipTransparentExprWrappers(
|
||||||
|
replacementPath.get("right"),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const nullishCheck = noDocumentAll
|
||||||
|
? ast`${t.cloneNode(check)} == null`
|
||||||
|
: ast`
|
||||||
|
${t.cloneNode(check)} === null || ${t.cloneNode(ref)} === void 0`;
|
||||||
|
|
||||||
|
const returnValue = isDeleteOperation ? ast`true` : ast`void 0`;
|
||||||
|
replacementPath.replaceWith(
|
||||||
|
t.conditionalExpression(nullishCheck, returnValue, replacement),
|
||||||
|
);
|
||||||
|
replacementPath = skipTransparentExprWrappers(
|
||||||
|
replacementPath.get("alternate"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,6 +20,7 @@
|
|||||||
"@babel/helper-compilation-targets": "workspace:^7.13.10",
|
"@babel/helper-compilation-targets": "workspace:^7.13.10",
|
||||||
"@babel/helper-plugin-utils": "workspace:^7.13.0",
|
"@babel/helper-plugin-utils": "workspace:^7.13.0",
|
||||||
"@babel/helper-validator-option": "workspace:^7.12.17",
|
"@babel/helper-validator-option": "workspace:^7.12.17",
|
||||||
|
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "workspace:^7.13.8",
|
||||||
"@babel/plugin-proposal-async-generator-functions": "workspace:^7.13.8",
|
"@babel/plugin-proposal-async-generator-functions": "workspace:^7.13.8",
|
||||||
"@babel/plugin-proposal-class-properties": "workspace:^7.13.0",
|
"@babel/plugin-proposal-class-properties": "workspace:^7.13.0",
|
||||||
"@babel/plugin-proposal-dynamic-import": "workspace:^7.13.8",
|
"@babel/plugin-proposal-dynamic-import": "workspace:^7.13.8",
|
||||||
|
|||||||
@ -65,6 +65,7 @@ import bugfixEdgeFunctionName from "@babel/preset-modules/lib/plugins/transform-
|
|||||||
import bugfixTaggedTemplateCaching from "@babel/preset-modules/lib/plugins/transform-tagged-template-caching";
|
import bugfixTaggedTemplateCaching from "@babel/preset-modules/lib/plugins/transform-tagged-template-caching";
|
||||||
import bugfixSafariBlockShadowing from "@babel/preset-modules/lib/plugins/transform-safari-block-shadowing";
|
import bugfixSafariBlockShadowing from "@babel/preset-modules/lib/plugins/transform-safari-block-shadowing";
|
||||||
import bugfixSafariForShadowing from "@babel/preset-modules/lib/plugins/transform-safari-for-shadowing";
|
import bugfixSafariForShadowing from "@babel/preset-modules/lib/plugins/transform-safari-for-shadowing";
|
||||||
|
import bugfixV8SpreadParametersInOptionalChaining from "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
"bugfix/transform-async-arrows-in-class": bugfixAsyncArrowsInClass,
|
"bugfix/transform-async-arrows-in-class": bugfixAsyncArrowsInClass,
|
||||||
@ -73,6 +74,7 @@ export default {
|
|||||||
"bugfix/transform-safari-block-shadowing": bugfixSafariBlockShadowing,
|
"bugfix/transform-safari-block-shadowing": bugfixSafariBlockShadowing,
|
||||||
"bugfix/transform-safari-for-shadowing": bugfixSafariForShadowing,
|
"bugfix/transform-safari-for-shadowing": bugfixSafariForShadowing,
|
||||||
"bugfix/transform-tagged-template-caching": bugfixTaggedTemplateCaching,
|
"bugfix/transform-tagged-template-caching": bugfixTaggedTemplateCaching,
|
||||||
|
"bugfix/transform-v8-spread-parameters-in-optional-chaining": bugfixV8SpreadParametersInOptionalChaining,
|
||||||
"proposal-async-generator-functions": proposalAsyncGeneratorFunctions,
|
"proposal-async-generator-functions": proposalAsyncGeneratorFunctions,
|
||||||
"proposal-class-properties": proposalClassProperties,
|
"proposal-class-properties": proposalClassProperties,
|
||||||
"proposal-dynamic-import": proposalDynamicImport,
|
"proposal-dynamic-import": proposalDynamicImport,
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
fn?.();
|
||||||
|
|
||||||
|
fn?.(...[], 0);
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"debug": true,
|
||||||
|
"bugfixes": false,
|
||||||
|
"targets": {
|
||||||
|
"chrome": "89"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
var _fn, _fn2;
|
||||||
|
|
||||||
|
(_fn = fn) === null || _fn === void 0 ? void 0 : _fn();
|
||||||
|
(_fn2 = fn) === null || _fn2 === void 0 ? void 0 : _fn2(...[], 0);
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
@babel/preset-env: `DEBUG` option
|
||||||
|
|
||||||
|
Using targets:
|
||||||
|
{
|
||||||
|
"chrome": "89"
|
||||||
|
}
|
||||||
|
|
||||||
|
Using modules transform: auto
|
||||||
|
|
||||||
|
Using plugins:
|
||||||
|
syntax-numeric-separator { "chrome":"89" }
|
||||||
|
syntax-nullish-coalescing-operator { "chrome":"89" }
|
||||||
|
proposal-optional-chaining { "chrome":"89" }
|
||||||
|
syntax-json-strings { "chrome":"89" }
|
||||||
|
syntax-optional-catch-binding { "chrome":"89" }
|
||||||
|
syntax-async-generators { "chrome":"89" }
|
||||||
|
syntax-object-rest-spread { "chrome":"89" }
|
||||||
|
transform-modules-commonjs { "chrome":"89" }
|
||||||
|
proposal-dynamic-import { "chrome":"89" }
|
||||||
|
proposal-export-namespace-from {}
|
||||||
|
|
||||||
|
Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
fn?.();
|
||||||
|
|
||||||
|
fn?.(...[], 0);
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"debug": true,
|
||||||
|
"bugfixes": true,
|
||||||
|
"targets": {
|
||||||
|
"chrome": "89"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
var _fn;
|
||||||
|
|
||||||
|
fn?.();
|
||||||
|
(_fn = fn) === null || _fn === void 0 ? void 0 : _fn(...[], 0);
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
@babel/preset-env: `DEBUG` option
|
||||||
|
|
||||||
|
Using targets:
|
||||||
|
{
|
||||||
|
"chrome": "89"
|
||||||
|
}
|
||||||
|
|
||||||
|
Using modules transform: auto
|
||||||
|
|
||||||
|
Using plugins:
|
||||||
|
syntax-numeric-separator { "chrome":"89" }
|
||||||
|
syntax-nullish-coalescing-operator { "chrome":"89" }
|
||||||
|
syntax-optional-chaining { "chrome":"89" }
|
||||||
|
syntax-json-strings { "chrome":"89" }
|
||||||
|
syntax-optional-catch-binding { "chrome":"89" }
|
||||||
|
syntax-async-generators { "chrome":"89" }
|
||||||
|
syntax-object-rest-spread { "chrome":"89" }
|
||||||
|
bugfix/transform-v8-spread-parameters-in-optional-chaining { "chrome":"89" }
|
||||||
|
transform-modules-commonjs { "chrome":"89" }
|
||||||
|
proposal-dynamic-import { "chrome":"89" }
|
||||||
|
proposal-export-namespace-from {}
|
||||||
|
|
||||||
|
Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.
|
||||||
16
yarn.lock
16
yarn.lock
@ -973,6 +973,21 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@workspace:^7.13.8, @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"
|
||||||
|
dependencies:
|
||||||
|
"@babel/core": "workspace:*"
|
||||||
|
"@babel/helper-plugin-test-runner": "workspace:*"
|
||||||
|
"@babel/helper-plugin-utils": "workspace:^7.13.0"
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "workspace:^7.12.1"
|
||||||
|
"@babel/plugin-proposal-optional-chaining": "workspace:^7.13.8"
|
||||||
|
"@babel/traverse": "workspace:*"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/core": ^7.13.0
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@babel/plugin-codemod-object-assign-to-object-spread@workspace:codemods/babel-plugin-codemod-object-assign-to-object-spread":
|
"@babel/plugin-codemod-object-assign-to-object-spread@workspace:codemods/babel-plugin-codemod-object-assign-to-object-spread":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@babel/plugin-codemod-object-assign-to-object-spread@workspace:codemods/babel-plugin-codemod-object-assign-to-object-spread"
|
resolution: "@babel/plugin-codemod-object-assign-to-object-spread@workspace:codemods/babel-plugin-codemod-object-assign-to-object-spread"
|
||||||
@ -3068,6 +3083,7 @@ __metadata:
|
|||||||
"@babel/helper-plugin-test-runner": "workspace:*"
|
"@babel/helper-plugin-test-runner": "workspace:*"
|
||||||
"@babel/helper-plugin-utils": "workspace:^7.13.0"
|
"@babel/helper-plugin-utils": "workspace:^7.13.0"
|
||||||
"@babel/helper-validator-option": "workspace:^7.12.17"
|
"@babel/helper-validator-option": "workspace:^7.12.17"
|
||||||
|
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "workspace:^7.13.8"
|
||||||
"@babel/plugin-proposal-async-generator-functions": "workspace:^7.13.8"
|
"@babel/plugin-proposal-async-generator-functions": "workspace:^7.13.8"
|
||||||
"@babel/plugin-proposal-class-properties": "workspace:^7.13.0"
|
"@babel/plugin-proposal-class-properties": "workspace:^7.13.0"
|
||||||
"@babel/plugin-proposal-dynamic-import": "workspace:^7.13.8"
|
"@babel/plugin-proposal-dynamic-import": "workspace:^7.13.8"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user