From 35a58f92784bf62490e9b7c96c51a1e169c59254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 11 Nov 2020 16:04:30 -0500 Subject: [PATCH 1/3] add tests on @babel/helper-optimise-call-expression --- .../package.json | 4 + .../src/index.js | 22 +++- .../test/index.js | 103 ++++++++++++++++++ yarn.lock | 2 + 4 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 packages/babel-helper-optimise-call-expression/test/index.js diff --git a/packages/babel-helper-optimise-call-expression/package.json b/packages/babel-helper-optimise-call-expression/package.json index 91b7a364ccea..2ddf6eff9a31 100644 --- a/packages/babel-helper-optimise-call-expression/package.json +++ b/packages/babel-helper-optimise-call-expression/package.json @@ -14,5 +14,9 @@ "main": "lib/index.js", "dependencies": { "@babel/types": "workspace:^7.10.4" + }, + "devDependencies": { + "@babel/generator": "workspace:*", + "@babel/parser": "workspace:*" } } diff --git a/packages/babel-helper-optimise-call-expression/src/index.js b/packages/babel-helper-optimise-call-expression/src/index.js index e6f889442e15..b272da8cd729 100644 --- a/packages/babel-helper-optimise-call-expression/src/index.js +++ b/packages/babel-helper-optimise-call-expression/src/index.js @@ -1,17 +1,34 @@ 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 ( args.length === 1 && t.isSpreadElement(args[0]) && t.isIdentifier(args[0].argument, { name: "arguments" }) ) { - // eg. super(...arguments); + // a.b(...arguments); return t.callExpression(t.memberExpression(callee, t.identifier("apply")), [ thisNode, args[0].argument, ]); } else { + // a.b?.(arg1, arg2) if (optional) { return t.optionalCallExpression( t.optionalMemberExpression(callee, t.identifier("call"), false, true), @@ -19,6 +36,7 @@ export default function (callee, thisNode, args, optional) { false, ); } + // a.b(arg1, arg2) return t.callExpression(t.memberExpression(callee, t.identifier("call")), [ thisNode, ...args, diff --git a/packages/babel-helper-optimise-call-expression/test/index.js b/packages/babel-helper-optimise-call-expression/test/index.js new file mode 100644 index 000000000000..012f5b7d8d7c --- /dev/null +++ b/packages/babel-helper-optimise-call-expression/test/index.js @@ -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)"`, + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index cbe4498804fc..bf2a1df5494d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -610,6 +610,8 @@ __metadata: version: 0.0.0-use.local resolution: "@babel/helper-optimise-call-expression@workspace:packages/babel-helper-optimise-call-expression" dependencies: + "@babel/generator": "workspace:*" + "@babel/parser": "workspace:*" "@babel/types": "workspace:^7.10.4" languageName: unknown linkType: soft From b0cc07ddd0584b9171fb0cb8752a3eb29fe459fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 11 Nov 2020 16:08:47 -0500 Subject: [PATCH 2/3] fix: correctly optimise `a.b?.(...arguments)` --- .../babel-helper-optimise-call-expression/src/index.js | 8 ++++++++ .../babel-helper-optimise-call-expression/test/index.js | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/babel-helper-optimise-call-expression/src/index.js b/packages/babel-helper-optimise-call-expression/src/index.js index b272da8cd729..362588bd8598 100644 --- a/packages/babel-helper-optimise-call-expression/src/index.js +++ b/packages/babel-helper-optimise-call-expression/src/index.js @@ -22,6 +22,14 @@ export default function ( t.isSpreadElement(args[0]) && t.isIdentifier(args[0].argument, { name: "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")), [ thisNode, diff --git a/packages/babel-helper-optimise-call-expression/test/index.js b/packages/babel-helper-optimise-call-expression/test/index.js index 012f5b7d8d7c..a98d91d01b48 100644 --- a/packages/babel-helper-optimise-call-expression/test/index.js +++ b/packages/babel-helper-optimise-call-expression/test/index.js @@ -27,10 +27,10 @@ describe("@babel/helper-optimise-call-expression", () => { `"a[b].apply(a, arguments)"`, ); expect(transformInput("a.b?.(...arguments)")).toMatchInlineSnapshot( - `"a.b.apply(a, arguments)"`, + `"a.b?.apply(a, arguments)"`, ); expect(transformInput("a[b]?.(...arguments)")).toMatchInlineSnapshot( - `"a[b].apply(a, arguments)"`, + `"a[b]?.apply(a, arguments)"`, ); expect(transformInput("a.b(...args)")).toMatchInlineSnapshot( @@ -68,10 +68,10 @@ describe("@babel/helper-optimise-call-expression", () => { `"a[b].apply(c, arguments)"`, ); expect(transformInput("a.b?.(...arguments)", "c")).toMatchInlineSnapshot( - `"a.b.apply(c, arguments)"`, + `"a.b?.apply(c, arguments)"`, ); expect(transformInput("a[b]?.(...arguments)", "c")).toMatchInlineSnapshot( - `"a[b].apply(c, arguments)"`, + `"a[b]?.apply(c, arguments)"`, ); expect(transformInput("a.b(...args)", "c")).toMatchInlineSnapshot( From b783079aecf1b9dd77263f96ef1210b807caa108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 12 Nov 2020 09:38:43 -0500 Subject: [PATCH 3/3] add integration test with properties transform --- .../exec.js | 19 +++++++++++ .../input.js | 16 ++++++++++ .../options.json | 4 +++ .../output.js | 32 +++++++++++++++++++ .../exec.js | 19 +++++++++++ .../input.js | 16 ++++++++++ .../options.json | 4 +++ .../output.js | 30 +++++++++++++++++ 8 files changed, 140 insertions(+) create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/exec.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/input.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/options.json create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/output.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/exec.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/input.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/options.json create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/output.js diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/exec.js new file mode 100644 index 000000000000..97dfd39e04c0 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/exec.js @@ -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); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/input.js new file mode 100644 index 000000000000..64e760b89f09 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/input.js @@ -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); + } +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/options.json new file mode 100644 index 000000000000..2d5cfe8e8095 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/options.json @@ -0,0 +1,4 @@ +{ + "plugins": [["proposal-class-properties", { "loose": true }]], + "minNodeVersion": "14.0.0" +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/output.js new file mode 100644 index 000000000000..938752d7e1c7 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-member-optional-call-spread-arguments/output.js @@ -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); + } + +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/exec.js new file mode 100644 index 000000000000..97dfd39e04c0 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/exec.js @@ -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); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/input.js new file mode 100644 index 000000000000..64e760b89f09 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/input.js @@ -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); + } +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/options.json new file mode 100644 index 000000000000..3b59e1bbfcc8 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["proposal-class-properties"], + "minNodeVersion": "14.0.0" +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/output.js new file mode 100644 index 000000000000..d3912187f468 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-member-optional-call-spread-arguments/output.js @@ -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); + } + +}