From 693a5df7a9c8bf96d0d4b94286575243e959ec54 Mon Sep 17 00:00:00 2001 From: Oliver Dunk Date: Fri, 20 Mar 2020 07:59:03 +0000 Subject: [PATCH] Memoize call expressions in optional chains in loose mode (#11261) * Memoize in loose mode when callee is a CallExpression * Handle more complex OptionalCallExpressions where memoization is needed * Convert calls to Function#call when member needs memoization * Only update call context for member expressions --- .../src/index.js | 17 ++++++++++++++--- .../fixtures/general/memoize-loose/input.js | 8 ++++++++ .../fixtures/general/memoize-loose/output.js | 15 ++++++++++----- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/babel-plugin-proposal-optional-chaining/src/index.js b/packages/babel-plugin-proposal-optional-chaining/src/index.js index 6a30dc74b909..2cfce5263b46 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/index.js +++ b/packages/babel-plugin-proposal-optional-chaining/src/index.js @@ -7,6 +7,16 @@ export default declare((api, options) => { const { loose = false } = options; + function isSimpleMemberExpression(expression) { + return ( + t.isIdentifier(expression) || + t.isSuper(expression) || + (t.isMemberExpression(expression) && + !expression.computed && + isSimpleMemberExpression(expression.object)) + ); + } + return { name: "proposal-optional-chaining", inherits: syntaxOptionalChaining, @@ -50,9 +60,10 @@ export default declare((api, options) => { let ref; let check; - if (loose && isCall) { + if (loose && isCall && isSimpleMemberExpression(chain)) { // If we are using a loose transform (avoiding a Function#call) and we are at the call, - // we can avoid a needless memoize. + // 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 = chain; } else { ref = scope.maybeGenerateMemoised(chain); @@ -73,7 +84,7 @@ export default declare((api, options) => { // Ensure call expressions have the proper `this` // `foo.bar()` has context `foo`. if (isCall && t.isMemberExpression(chain)) { - if (loose) { + if (loose && 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 = chain; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/memoize-loose/input.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/memoize-loose/input.js index ffe543adde41..bbb26771f34e 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/memoize-loose/input.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/memoize-loose/input.js @@ -7,6 +7,14 @@ function test(foo) { foo?.bar() + foo.get(bar)?.() + + foo.bar()?.() + foo[bar]()?.() + + foo.bar().baz?.() + foo[bar]().baz?.() + foo.bar?.(foo.bar, false) foo?.bar?.(foo.bar, true) diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/memoize-loose/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/memoize-loose/output.js index 00635776f339..806487a206aa 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/memoize-loose/output.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/memoize-loose/output.js @@ -1,14 +1,19 @@ function test(foo) { - var _foo$bar, _foo$bar2, _foo$bar3, _foo$bar4, _foo$bar5; + var _foo$bar, _foo$get, _foo$bar2, _foo$bar3, _foo$bar$baz, _foo$bar4, _foo$bar$baz2, _foo$bar5, _foo$bar6, _foo$bar7, _foo$bar8, _foo$bar9; foo == null ? void 0 : foo.bar; foo == null ? void 0 : (_foo$bar = foo.bar) == null ? void 0 : _foo$bar.baz; foo == null ? void 0 : foo(foo); foo == null ? void 0 : foo.bar(); + (_foo$get = foo.get(bar)) == null ? void 0 : _foo$get(); + (_foo$bar2 = foo.bar()) == null ? void 0 : _foo$bar2(); + (_foo$bar3 = foo[bar]()) == null ? void 0 : _foo$bar3(); + (_foo$bar$baz = (_foo$bar4 = foo.bar()).baz) == null ? void 0 : _foo$bar$baz.call(_foo$bar4); + (_foo$bar$baz2 = (_foo$bar5 = foo[bar]()).baz) == null ? void 0 : _foo$bar$baz2.call(_foo$bar5); foo.bar == null ? void 0 : foo.bar(foo.bar, false); foo == null ? void 0 : foo.bar == null ? void 0 : foo.bar(foo.bar, true); - (_foo$bar2 = foo.bar) == null ? void 0 : _foo$bar2.baz(foo.bar, false); - foo == null ? void 0 : (_foo$bar3 = foo.bar) == null ? void 0 : _foo$bar3.baz(foo.bar, true); - (_foo$bar4 = foo.bar) == null ? void 0 : _foo$bar4.baz == null ? void 0 : _foo$bar4.baz(foo.bar, false); - foo == null ? void 0 : (_foo$bar5 = foo.bar) == null ? void 0 : _foo$bar5.baz == null ? void 0 : _foo$bar5.baz(foo.bar, true); + (_foo$bar6 = foo.bar) == null ? void 0 : _foo$bar6.baz(foo.bar, false); + foo == null ? void 0 : (_foo$bar7 = foo.bar) == null ? void 0 : _foo$bar7.baz(foo.bar, true); + (_foo$bar8 = foo.bar) == null ? void 0 : _foo$bar8.baz == null ? void 0 : _foo$bar8.baz(foo.bar, false); + foo == null ? void 0 : (_foo$bar9 = foo.bar) == null ? void 0 : _foo$bar9.baz == null ? void 0 : _foo$bar9.baz(foo.bar, true); }