Fix: correctly transform this.#m?.(...arguments) (#12350)
* add tests on @babel/helper-optimise-call-expression * fix: correctly optimise `a.b?.(...arguments)` * add integration test with properties transform
This commit is contained in:
parent
7850682387
commit
f54f1ee492
@ -14,5 +14,9 @@
|
|||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "workspace:^7.10.4"
|
"@babel/types": "workspace:^7.10.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/generator": "workspace:*",
|
||||||
|
"@babel/parser": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,42 @@
|
|||||||
import * as t from "@babel/types";
|
import * as t from "@babel/types";
|
||||||
|
|
||||||
export default function (callee, thisNode, args, optional) {
|
/**
|
||||||
|
* A helper function that generates a new call expression with given thisNode.
|
||||||
|
It will also optimize `(...arguments)` to `.apply(arguments)`
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {Node} callee The callee of call expression
|
||||||
|
* @param {Node} thisNode The desired this of call expression
|
||||||
|
* @param {Node[]} args The arguments of call expression
|
||||||
|
* @param {boolean} optional Whether the call expression is optional
|
||||||
|
* @returns {CallExpression | OptionalCallExpression} The generated new call expression
|
||||||
|
*/
|
||||||
|
export default function (
|
||||||
|
callee: Node,
|
||||||
|
thisNode: Node,
|
||||||
|
args: Node[],
|
||||||
|
optional: boolean,
|
||||||
|
): CallExpression | OptionalCallExpression {
|
||||||
if (
|
if (
|
||||||
args.length === 1 &&
|
args.length === 1 &&
|
||||||
t.isSpreadElement(args[0]) &&
|
t.isSpreadElement(args[0]) &&
|
||||||
t.isIdentifier(args[0].argument, { name: "arguments" })
|
t.isIdentifier(args[0].argument, { name: "arguments" })
|
||||||
) {
|
) {
|
||||||
// eg. super(...arguments);
|
// a.b?.(...arguments);
|
||||||
|
if (optional) {
|
||||||
|
return t.optionalCallExpression(
|
||||||
|
t.optionalMemberExpression(callee, t.identifier("apply"), false, true),
|
||||||
|
[thisNode, args[0].argument],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// a.b(...arguments);
|
||||||
return t.callExpression(t.memberExpression(callee, t.identifier("apply")), [
|
return t.callExpression(t.memberExpression(callee, t.identifier("apply")), [
|
||||||
thisNode,
|
thisNode,
|
||||||
args[0].argument,
|
args[0].argument,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
// a.b?.(arg1, arg2)
|
||||||
if (optional) {
|
if (optional) {
|
||||||
return t.optionalCallExpression(
|
return t.optionalCallExpression(
|
||||||
t.optionalMemberExpression(callee, t.identifier("call"), false, true),
|
t.optionalMemberExpression(callee, t.identifier("call"), false, true),
|
||||||
@ -19,6 +44,7 @@ export default function (callee, thisNode, args, optional) {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// a.b(arg1, arg2)
|
||||||
return t.callExpression(t.memberExpression(callee, t.identifier("call")), [
|
return t.callExpression(t.memberExpression(callee, t.identifier("call")), [
|
||||||
thisNode,
|
thisNode,
|
||||||
...args,
|
...args,
|
||||||
|
|||||||
103
packages/babel-helper-optimise-call-expression/test/index.js
Normal file
103
packages/babel-helper-optimise-call-expression/test/index.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { parse } from "@babel/parser";
|
||||||
|
import generator from "@babel/generator";
|
||||||
|
import * as t from "@babel/types";
|
||||||
|
import optimizeCallExpression from "..";
|
||||||
|
|
||||||
|
function transformInput(input, thisIdentifier) {
|
||||||
|
const ast = parse(input);
|
||||||
|
const callExpression = ast.program.body[0].expression;
|
||||||
|
return generator(
|
||||||
|
optimizeCallExpression(
|
||||||
|
callExpression.callee,
|
||||||
|
thisIdentifier
|
||||||
|
? t.identifier(thisIdentifier)
|
||||||
|
: callExpression.callee.object,
|
||||||
|
callExpression.arguments,
|
||||||
|
callExpression.type === "OptionalCallExpression",
|
||||||
|
),
|
||||||
|
).code;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("@babel/helper-optimise-call-expression", () => {
|
||||||
|
test("optimizeCallExpression should work when thisNode is implied from callee", () => {
|
||||||
|
expect(transformInput("a.b(...arguments)")).toMatchInlineSnapshot(
|
||||||
|
`"a.b.apply(a, arguments)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b](...arguments)")).toMatchInlineSnapshot(
|
||||||
|
`"a[b].apply(a, arguments)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a.b?.(...arguments)")).toMatchInlineSnapshot(
|
||||||
|
`"a.b?.apply(a, arguments)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b]?.(...arguments)")).toMatchInlineSnapshot(
|
||||||
|
`"a[b]?.apply(a, arguments)"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(transformInput("a.b(...args)")).toMatchInlineSnapshot(
|
||||||
|
`"a.b.call(a, ...args)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b](...args)")).toMatchInlineSnapshot(
|
||||||
|
`"a[b].call(a, ...args)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a.b?.(...args)")).toMatchInlineSnapshot(
|
||||||
|
`"a.b?.call(a, ...args)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b]?.(...args)")).toMatchInlineSnapshot(
|
||||||
|
`"a[b]?.call(a, ...args)"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(transformInput("a.b(arg1, arg2)")).toMatchInlineSnapshot(
|
||||||
|
`"a.b.call(a, arg1, arg2)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b](arg1, arg2)")).toMatchInlineSnapshot(
|
||||||
|
`"a[b].call(a, arg1, arg2)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a.b?.(arg1, arg2)")).toMatchInlineSnapshot(
|
||||||
|
`"a.b?.call(a, arg1, arg2)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b]?.(arg1, arg2)")).toMatchInlineSnapshot(
|
||||||
|
`"a[b]?.call(a, arg1, arg2)"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("optimizeCallExpression should work when thisNode is provided", () => {
|
||||||
|
expect(transformInput("a.b(...arguments)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a.b.apply(c, arguments)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b](...arguments)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a[b].apply(c, arguments)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a.b?.(...arguments)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a.b?.apply(c, arguments)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b]?.(...arguments)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a[b]?.apply(c, arguments)"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(transformInput("a.b(...args)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a.b.call(c, ...args)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b](...args)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a[b].call(c, ...args)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a.b?.(...args)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a.b?.call(c, ...args)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b]?.(...args)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a[b]?.call(c, ...args)"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(transformInput("a.b(arg1, arg2)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a.b.call(c, arg1, arg2)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b](arg1, arg2)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a[b].call(c, arg1, arg2)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a.b?.(arg1, arg2)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a.b?.call(c, arg1, arg2)"`,
|
||||||
|
);
|
||||||
|
expect(transformInput("a[b]?.(arg1, arg2)", "c")).toMatchInlineSnapshot(
|
||||||
|
`"a[b]?.call(c, arg1, arg2)"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
class Foo {
|
||||||
|
#m;
|
||||||
|
init() {
|
||||||
|
this.#m = (...args) => args;
|
||||||
|
}
|
||||||
|
static test() {
|
||||||
|
const f = new Foo();
|
||||||
|
f.init();
|
||||||
|
return f.#m?.(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
static testNull() {
|
||||||
|
const f = new Foo();
|
||||||
|
return f.#m?.(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(Foo.test(1, 2)).toEqual([1, 2]);
|
||||||
|
expect(Foo.testNull(1, 2)).toBe(undefined);
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
class Foo {
|
||||||
|
#m;
|
||||||
|
init() {
|
||||||
|
this.#m = (...args) => args;
|
||||||
|
}
|
||||||
|
static test() {
|
||||||
|
const f = new Foo();
|
||||||
|
f.init();
|
||||||
|
return f.#m?.(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
static testNull() {
|
||||||
|
const f = new Foo();
|
||||||
|
return f.#m?.(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"plugins": [["proposal-class-properties", { "loose": true }]],
|
||||||
|
"minNodeVersion": "14.0.0"
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
|
||||||
|
|
||||||
|
var id = 0;
|
||||||
|
|
||||||
|
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
||||||
|
|
||||||
|
var _m = _classPrivateFieldLooseKey("m");
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
constructor() {
|
||||||
|
Object.defineProperty(this, _m, {
|
||||||
|
writable: true,
|
||||||
|
value: void 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
_classPrivateFieldLooseBase(this, _m)[_m] = (...args) => args;
|
||||||
|
}
|
||||||
|
|
||||||
|
static test() {
|
||||||
|
const f = new Foo();
|
||||||
|
f.init();
|
||||||
|
return _classPrivateFieldLooseBase(f, _m)[_m]?.(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
static testNull() {
|
||||||
|
const f = new Foo();
|
||||||
|
return _classPrivateFieldLooseBase(f, _m)[_m]?.(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
class Foo {
|
||||||
|
#m;
|
||||||
|
init() {
|
||||||
|
this.#m = (...args) => args;
|
||||||
|
}
|
||||||
|
static test() {
|
||||||
|
const f = new Foo();
|
||||||
|
f.init();
|
||||||
|
return f.#m?.(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
static testNull() {
|
||||||
|
const f = new Foo();
|
||||||
|
return f.#m?.(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(Foo.test(1, 2)).toEqual([1, 2]);
|
||||||
|
expect(Foo.testNull(1, 2)).toBe(undefined);
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
class Foo {
|
||||||
|
#m;
|
||||||
|
init() {
|
||||||
|
this.#m = (...args) => args;
|
||||||
|
}
|
||||||
|
static test() {
|
||||||
|
const f = new Foo();
|
||||||
|
f.init();
|
||||||
|
return f.#m?.(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
static testNull() {
|
||||||
|
const f = new Foo();
|
||||||
|
return f.#m?.(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["proposal-class-properties"],
|
||||||
|
"minNodeVersion": "14.0.0"
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
function _classPrivateFieldGet(receiver, privateMap) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to get private field on non-instance"); } if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
|
||||||
|
|
||||||
|
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to set private field on non-instance"); } if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } return value; }
|
||||||
|
|
||||||
|
var _m = new WeakMap();
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
constructor() {
|
||||||
|
_m.set(this, {
|
||||||
|
writable: true,
|
||||||
|
value: void 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
_classPrivateFieldSet(this, _m, (...args) => args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static test() {
|
||||||
|
const f = new Foo();
|
||||||
|
f.init();
|
||||||
|
return _classPrivateFieldGet(f, _m)?.apply(f, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
static testNull() {
|
||||||
|
const f = new Foo();
|
||||||
|
return _classPrivateFieldGet(f, _m)?.apply(f, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -610,6 +610,8 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@babel/helper-optimise-call-expression@workspace:packages/babel-helper-optimise-call-expression"
|
resolution: "@babel/helper-optimise-call-expression@workspace:packages/babel-helper-optimise-call-expression"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@babel/generator": "workspace:*"
|
||||||
|
"@babel/parser": "workspace:*"
|
||||||
"@babel/types": "workspace:^7.10.4"
|
"@babel/types": "workspace:^7.10.4"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user