From 8502c1921b514d6c92df1eb70762a4720573f4a1 Mon Sep 17 00:00:00 2001 From: Tony Quetano Date: Sun, 16 Jun 2019 17:48:43 -0700 Subject: [PATCH] Expand aggressive inliner (#8) - Upgrade operation inliner to support larger function bodies - Only bailout scenarios involve conditional or multiple `return` statements - Support destructuring parameters - Improve documentation to show code style to avoid bailouts - Add tests for more use cases --- README.md | 84 ++++- .../__fixtures__/bailout/early-return/code.js | 7 + .../bailout/early-return/output.js | 16 + .../bailout/multiple-returns/code.js | 9 + .../bailout/multiple-returns/output.js | 18 + .../__fixtures__/complex/conditional/code.js | 9 + .../complex/conditional/output.js | 14 + .../code.js | 2 +- .../output.js | 8 +- .../destructured-params-object/code.js | 5 + .../destructured-params-object/output.js | 12 + .../complex/inlined-large-function/code.js | 30 ++ .../complex/inlined-large-function/output.js | 54 +++ __tests__/__fixtures__/complex/this/code.js | 7 + __tests__/__fixtures__/complex/this/output.js | 10 + .../__fixtures__/uncached/every/output.js | 10 +- .../uncached/everyObject/output.js | 10 +- .../__fixtures__/uncached/everyRight/code.js | 8 +- .../uncached/everyRight/output.js | 9 +- .../__fixtures__/uncached/filter/output.js | 11 +- .../uncached/filterObject/output.js | 11 +- .../uncached/filterRight/output.js | 11 +- .../__fixtures__/uncached/find/output.js | 9 +- .../__fixtures__/uncached/findIndex/output.js | 10 +- .../uncached/findIndexRight/output.js | 10 +- .../__fixtures__/uncached/findKey/output.js | 9 +- .../uncached/findObject/output.js | 9 +- .../__fixtures__/uncached/findRight/output.js | 9 +- .../__fixtures__/uncached/flatMap/output.js | 9 +- .../uncached/flatMapRight/output.js | 9 +- .../__fixtures__/uncached/forEach/output.js | 9 +- .../uncached/forEachObject/output.js | 9 +- .../uncached/forEachRight/output.js | 9 +- __tests__/__fixtures__/uncached/map/output.js | 11 +- .../__fixtures__/uncached/mapObject/output.js | 11 +- .../__fixtures__/uncached/mapRight/output.js | 11 +- .../uncached/reduce-no-initialValue/output.js | 8 +- .../__fixtures__/uncached/reduce/output.js | 8 +- .../reduceObject-no-initialValue/output.js | 7 +- .../uncached/reduceObject/output.js | 7 +- .../reduceRight-no-initialValue/output.js | 8 +- .../__fixtures__/uncached/reduceRight/code.js | 4 +- .../uncached/reduceRight/output.js | 8 +- .../__fixtures__/uncached/some/output.js | 10 +- .../uncached/someObject/output.js | 10 +- .../__fixtures__/uncached/someRight/output.js | 10 +- __tests__/__runtime__/complex-inliner.js | 89 +++++ __tests__/runtime.test.js | 4 + __tests__/transform.test.js | 8 + dist/handlers.js | 178 ++++++++-- dist/helpers.js | 331 +++++++++++------- package.json | 2 +- src/handlers.js | 251 ++++++++----- src/helpers.js | 254 +++++++++----- 54 files changed, 1130 insertions(+), 556 deletions(-) create mode 100644 __tests__/__fixtures__/bailout/early-return/code.js create mode 100644 __tests__/__fixtures__/bailout/early-return/output.js create mode 100644 __tests__/__fixtures__/bailout/multiple-returns/code.js create mode 100644 __tests__/__fixtures__/bailout/multiple-returns/output.js create mode 100644 __tests__/__fixtures__/complex/conditional/code.js create mode 100644 __tests__/__fixtures__/complex/conditional/output.js rename __tests__/__fixtures__/complex/{destructured-params => destructured-params-array}/code.js (75%) rename __tests__/__fixtures__/complex/{destructured-params => destructured-params-array}/output.js (61%) create mode 100644 __tests__/__fixtures__/complex/destructured-params-object/code.js create mode 100644 __tests__/__fixtures__/complex/destructured-params-object/output.js create mode 100644 __tests__/__fixtures__/complex/inlined-large-function/code.js create mode 100644 __tests__/__fixtures__/complex/inlined-large-function/output.js create mode 100644 __tests__/__fixtures__/complex/this/code.js create mode 100644 __tests__/__fixtures__/complex/this/output.js create mode 100644 __tests__/__runtime__/complex-inliner.js diff --git a/README.md b/README.md index 1b5d2781..90d4cca2 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Iteration helpers that inline to native loops for performance - [Usage](#usage) - [Methods](#methods) - [How it works](#how-it-works) + - [Aggressive inlining](#aggressive-inlining) + - [Bailout scenarios](#bailout-scenarios) - [Gotchas](#gotchas) - [`*Object` methods do not perform `hasOwnProperty` check](#object-methods-do-not-perform-hasownproperty-check) - [`findIndex` vs `findKey`](#findindex-vs-findkey) @@ -92,7 +94,9 @@ const foo = _result; If you are passing uncached values as the array or the handler, it will store those values as local variables and execute the same loop based on those variables. -One extra performance boost is that `inline-loops` will try to inline operations when possible. For example: +### Aggressive inlining + +One extra performance boost is that `inline-loops` will try to inline the callback operations when possible. For example: ```javascript // this @@ -141,10 +145,88 @@ for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { const isAllTuples = _result; ``` +### Bailout scenarios + +Inevitably not everything can be inlined, so there are known bailout scenarios: + +- When using a cached function reference (we can only inline functions that are statically declared in the macro scope) +- When there are multiple `return` statements (as there is no scope to return from, the conversion of the logic would be highly complex) +- When the `return` statement is not top-level (same reason as with multiple `return`s) + +That means if you are cranking every last ounce of performance out of this macro, you want to get cozy with ternaries. + +```js +import { map } from 'inline-loops.macro'; + +// this will bail out to storing the function and calling it in the loop +const deopted = map(array, value => { + if (value % 2 === 0) { + return 'even'; + } + + return 'odd'; +}); + +// this will inline the operation and avoid function calls +const inlined = map(array, value => (value % 2 === 0 ? 'even' : 'odd')); +``` + ## Gotchas Some aspects of implementing this macro that you should be aware of: +### Conditionals do not delay execution + +If you do something like this with standard JS: + +```js +return isFoo ? array.map(v => v * 2) : array; +``` + +The `array` is only mapped over if `isFoo` is true. However, because we are inlining these calls into `for` loops in the scope they operate in, this conditional calling does not apply with this macro. + +```js +// this +return isFoo ? map(array, v => v * 2) : array; + +// turns into this +let _result = []; + +for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { + _value = array[_key]; + _result[_key] = _value * 2; +} + +return isFoo ? _result : array; +``` + +Notice the mapping occurs whether the condition is met or not. If you want to ensure this conditionality is maintained, you should use an `if` block instead: + +```js +// this +if (isFoo) { + return map(array, v => v * 2); +} + +return array; + +// turns into this +if (isFoo) { + let _result = []; + + for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { + _value = array[_key]; + _result[_key] = _value * 2; + } + + return _result; +} + +return array; +``` + +This will ensure the potentially expensive computation only occurs when necessary. + ### `*Object` methods do not perform `hasOwnProperty` check The object methods will do operations in `for-in` loop, but will not guard via a `hasOwnProperty` check. For example: diff --git a/__tests__/__fixtures__/bailout/early-return/code.js b/__tests__/__fixtures__/bailout/early-return/code.js new file mode 100644 index 00000000..0c6b03a5 --- /dev/null +++ b/__tests__/__fixtures__/bailout/early-return/code.js @@ -0,0 +1,7 @@ +import { filter } from '../../../../src/inline-loops.macro'; + +const result = filter([1, 2, 3], (value) => { + if (value === 2) { + return true; + } +}); diff --git a/__tests__/__fixtures__/bailout/early-return/output.js b/__tests__/__fixtures__/bailout/early-return/output.js new file mode 100644 index 00000000..ca62d427 --- /dev/null +++ b/__tests__/__fixtures__/bailout/early-return/output.js @@ -0,0 +1,16 @@ +const _iterable = [1, 2, 3]; + +const _fn = value => { + if (value === 2) { + return true; + } +}; + +let _result = []; + +for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { + _value = _iterable[_key]; + if (_fn(_value, _key, _iterable)) _result.push(_value); +} + +const result = _result; diff --git a/__tests__/__fixtures__/bailout/multiple-returns/code.js b/__tests__/__fixtures__/bailout/multiple-returns/code.js new file mode 100644 index 00000000..8f434aff --- /dev/null +++ b/__tests__/__fixtures__/bailout/multiple-returns/code.js @@ -0,0 +1,9 @@ +import { map } from '../../../../src/inline-loops.macro'; + +const result = map([1, 2, 3], (value) => { + if (value === 2) { + return 82; + } + + return value; +}); diff --git a/__tests__/__fixtures__/bailout/multiple-returns/output.js b/__tests__/__fixtures__/bailout/multiple-returns/output.js new file mode 100644 index 00000000..f0765be7 --- /dev/null +++ b/__tests__/__fixtures__/bailout/multiple-returns/output.js @@ -0,0 +1,18 @@ +const _iterable = [1, 2, 3]; + +const _fn = value => { + if (value === 2) { + return 82; + } + + return value; +}; + +let _result = []; + +for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { + _value = _iterable[_key]; + _result[_key] = _fn(_value, _key, _iterable); +} + +const result = _result; diff --git a/__tests__/__fixtures__/complex/conditional/code.js b/__tests__/__fixtures__/complex/conditional/code.js new file mode 100644 index 00000000..2f0fb644 --- /dev/null +++ b/__tests__/__fixtures__/complex/conditional/code.js @@ -0,0 +1,9 @@ +import { map } from '../../../../src/inline-loops.macro'; + +function getStuff() { + if (foo === 'bar') { + return map(array, v => v * 2); + } + + return array; +} diff --git a/__tests__/__fixtures__/complex/conditional/output.js b/__tests__/__fixtures__/complex/conditional/output.js new file mode 100644 index 00000000..f88e881a --- /dev/null +++ b/__tests__/__fixtures__/complex/conditional/output.js @@ -0,0 +1,14 @@ +function getStuff() { + if (foo === 'bar') { + let _result = []; + + for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { + _value = array[_key]; + _result[_key] = _value * 2; + } + + return _result; + } + + return array; +} diff --git a/__tests__/__fixtures__/complex/destructured-params/code.js b/__tests__/__fixtures__/complex/destructured-params-array/code.js similarity index 75% rename from __tests__/__fixtures__/complex/destructured-params/code.js rename to __tests__/__fixtures__/complex/destructured-params-array/code.js index c820b4f3..33bf1900 100644 --- a/__tests__/__fixtures__/complex/destructured-params/code.js +++ b/__tests__/__fixtures__/complex/destructured-params-array/code.js @@ -1,5 +1,5 @@ import { forEach } from '../../../../src/inline-loops.macro'; -forEach([], ([a, b]) => { +forEach([], ([a, [b]]) => { console.log(a, b); }); diff --git a/__tests__/__fixtures__/complex/destructured-params/output.js b/__tests__/__fixtures__/complex/destructured-params-array/output.js similarity index 61% rename from __tests__/__fixtures__/complex/destructured-params/output.js rename to __tests__/__fixtures__/complex/destructured-params-array/output.js index c717f4ff..728ce7b7 100644 --- a/__tests__/__fixtures__/complex/destructured-params/output.js +++ b/__tests__/__fixtures__/complex/destructured-params-array/output.js @@ -1,11 +1,7 @@ const _iterable = []; -const _fn = ([a, b]) => { - console.log(a, b); -}; - for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - - _fn(_value, _key, _iterable); + const [_a, [_b]] = _value; + console.log(_a, _b); } diff --git a/__tests__/__fixtures__/complex/destructured-params-object/code.js b/__tests__/__fixtures__/complex/destructured-params-object/code.js new file mode 100644 index 00000000..74d5c685 --- /dev/null +++ b/__tests__/__fixtures__/complex/destructured-params-object/code.js @@ -0,0 +1,5 @@ +import { forEach } from '../../../../src/inline-loops.macro'; + +forEach([], ({ a, b: { c } }) => { + console.log(a, c); +}); diff --git a/__tests__/__fixtures__/complex/destructured-params-object/output.js b/__tests__/__fixtures__/complex/destructured-params-object/output.js new file mode 100644 index 00000000..c8dd8b50 --- /dev/null +++ b/__tests__/__fixtures__/complex/destructured-params-object/output.js @@ -0,0 +1,12 @@ +const _iterable = []; + +for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { + _value = _iterable[_key]; + const { + a: _a, + b: { + c: _c + } + } = _value; + console.log(_a, _c); +} diff --git a/__tests__/__fixtures__/complex/inlined-large-function/code.js b/__tests__/__fixtures__/complex/inlined-large-function/code.js new file mode 100644 index 00000000..ce1cda31 --- /dev/null +++ b/__tests__/__fixtures__/complex/inlined-large-function/code.js @@ -0,0 +1,30 @@ +import { filter, map, reduceObject } from '../../../../src/inline-loops.macro'; + +const result = filter(array, (value, index) => { + // usage inside + const mapped = map(array, value => value * 2); + + // custom for loop with let + for (let i = 0; i < mapped.length; i++) { + mapped[i] = mapped[i] ** 2; + } + + // custom for loop with var + for (var i = 0; i < mapped.length; i++) { + mapped[i] = mapped[i] ** 2; + } + + // another iteration, using the mapped values + const reduced = reduceObject(object, value => ({ + [value]: mapped, + })); + + // custom for-in + for (var key in reduced) { + if (reduced[key] < 0) { + delete reduced[key]; + } + } + + return reduced[100]; +}); diff --git a/__tests__/__fixtures__/complex/inlined-large-function/output.js b/__tests__/__fixtures__/complex/inlined-large-function/output.js new file mode 100644 index 00000000..c752a316 --- /dev/null +++ b/__tests__/__fixtures__/complex/inlined-large-function/output.js @@ -0,0 +1,54 @@ +let _result = []; + +for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { + _value = array[_key]; + let _result2 = []; + + for (let _key3 = 0, _length2 = array.length, _value2; _key3 < _length2; ++_key3) { + _value2 = array[_key3]; + _result2[_key3] = _value2 * 2; + } + + // usage inside + const _mapped = _result2; // custom for loop with let + + for (let i = 0; i < _mapped.length; i++) { + _mapped[i] = _mapped[i] ** 2; + } // custom for loop with var + + + for (var _i2 = 0; _i2 < _mapped.length; _i2++) { + _mapped[_i2] = _mapped[_i2] ** 2; + } // another iteration, using the mapped values + + + let _hasInitialValue = false; + + let _value3; + + let _result3; + + for (let _key4 in object) { + if (_hasInitialValue) { + _value3 = object[_key4]; + _result3 = { + [_result3]: _mapped + }; + } else { + _hasInitialValue = true; + _result3 = object[_key4]; + } + } + + const _reduced = _result3; // custom for-in + + for (var _key2 in _reduced) { + if (_reduced[_key2] < 0) { + delete _reduced[_key2]; + } + } + + if (_reduced[100]) _result.push(_value); +} + +const result = _result; diff --git a/__tests__/__fixtures__/complex/this/code.js b/__tests__/__fixtures__/complex/this/code.js new file mode 100644 index 00000000..15a25dde --- /dev/null +++ b/__tests__/__fixtures__/complex/this/code.js @@ -0,0 +1,7 @@ +import { map } from '../../../../src/inline-loops.macro'; + +function foo(array) { + return map(array, function (value) { + return this && this.foo ? value : null; + }); +} diff --git a/__tests__/__fixtures__/complex/this/output.js b/__tests__/__fixtures__/complex/this/output.js new file mode 100644 index 00000000..0acaa6f8 --- /dev/null +++ b/__tests__/__fixtures__/complex/this/output.js @@ -0,0 +1,10 @@ +function foo(array) { + let _result = []; + + for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { + _value = array[_key]; + _result[_key] = this && this.foo ? _value : null; + } + + return _result; +} diff --git a/__tests__/__fixtures__/uncached/every/output.js b/__tests__/__fixtures__/uncached/every/output.js index 059c9ac1..a7a0bbbd 100644 --- a/__tests__/__fixtures__/uncached/every/output.js +++ b/__tests__/__fixtures__/uncached/every/output.js @@ -1,16 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = true; for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - if (!_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (!_isValueEven) { _result = false; break; } diff --git a/__tests__/__fixtures__/uncached/everyObject/output.js b/__tests__/__fixtures__/uncached/everyObject/output.js index 1059de68..1d625318 100644 --- a/__tests__/__fixtures__/uncached/everyObject/output.js +++ b/__tests__/__fixtures__/uncached/everyObject/output.js @@ -4,12 +4,6 @@ const _iterable = { three: 3, four: 4 }; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = true; let _value; @@ -17,7 +11,9 @@ let _value; for (let _key in _iterable) { _value = _iterable[_key]; - if (!_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (!_isValueEven) { _result = false; break; } diff --git a/__tests__/__fixtures__/uncached/everyRight/code.js b/__tests__/__fixtures__/uncached/everyRight/code.js index f776c063..f0633df8 100644 --- a/__tests__/__fixtures__/uncached/everyRight/code.js +++ b/__tests__/__fixtures__/uncached/everyRight/code.js @@ -1,3 +1,7 @@ -import { everyRight } from "../../../../src/inline-loops.macro"; +import { everyRight } from '../../../../src/inline-loops.macro'; -const areAllEven = everyRight(array, value => value % 2 === 0); +const areAllEven = everyRight([1, 2, 3, 4], (value) => { + const isValueEven = value % 2 === 0; + + return isValueEven; +}); diff --git a/__tests__/__fixtures__/uncached/everyRight/output.js b/__tests__/__fixtures__/uncached/everyRight/output.js index e03f15a7..472e5a09 100644 --- a/__tests__/__fixtures__/uncached/everyRight/output.js +++ b/__tests__/__fixtures__/uncached/everyRight/output.js @@ -1,9 +1,12 @@ +const _iterable = [1, 2, 3, 4]; let _result = true; -for (let _key = array.length - 1, _value; _key >= 0; --_key) { - _value = array[_key]; +for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { + _value = _iterable[_key]; - if (!(_value % 2 === 0)) { + const _isValueEven = _value % 2 === 0; + + if (!_isValueEven) { _result = false; break; } diff --git a/__tests__/__fixtures__/uncached/filter/output.js b/__tests__/__fixtures__/uncached/filter/output.js index 32e34d66..d4bb0962 100644 --- a/__tests__/__fixtures__/uncached/filter/output.js +++ b/__tests__/__fixtures__/uncached/filter/output.js @@ -1,15 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = []; for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) _result.push(_value); + + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) _result.push(_value); } const onlyEven = _result; diff --git a/__tests__/__fixtures__/uncached/filterObject/output.js b/__tests__/__fixtures__/uncached/filterObject/output.js index a7c61441..853ce5b2 100644 --- a/__tests__/__fixtures__/uncached/filterObject/output.js +++ b/__tests__/__fixtures__/uncached/filterObject/output.js @@ -4,19 +4,16 @@ const _iterable = { three: 3, four: 4 }; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = {}; let _value; for (let _key in _iterable) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) _result[_key] = _value; + + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) _result[_key] = _value; } const onlyEven = _result; diff --git a/__tests__/__fixtures__/uncached/filterRight/output.js b/__tests__/__fixtures__/uncached/filterRight/output.js index 116e1551..cba76564 100644 --- a/__tests__/__fixtures__/uncached/filterRight/output.js +++ b/__tests__/__fixtures__/uncached/filterRight/output.js @@ -1,15 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = []; for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) _result.push(_value); + + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) _result.push(_value); } const onlyEven = _result; diff --git a/__tests__/__fixtures__/uncached/find/output.js b/__tests__/__fixtures__/uncached/find/output.js index 318e1ccd..980997aa 100644 --- a/__tests__/__fixtures__/uncached/find/output.js +++ b/__tests__/__fixtures__/uncached/find/output.js @@ -1,16 +1,13 @@ const _iterable = [1, 2, 3, 4]; -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result; for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = _value; break; } diff --git a/__tests__/__fixtures__/uncached/findIndex/output.js b/__tests__/__fixtures__/uncached/findIndex/output.js index 8f780956..fad72d31 100644 --- a/__tests__/__fixtures__/uncached/findIndex/output.js +++ b/__tests__/__fixtures__/uncached/findIndex/output.js @@ -1,16 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = -1; for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = _key; break; } diff --git a/__tests__/__fixtures__/uncached/findIndexRight/output.js b/__tests__/__fixtures__/uncached/findIndexRight/output.js index dc121f59..2b383b5b 100644 --- a/__tests__/__fixtures__/uncached/findIndexRight/output.js +++ b/__tests__/__fixtures__/uncached/findIndexRight/output.js @@ -1,16 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = -1; for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = _key; break; } diff --git a/__tests__/__fixtures__/uncached/findKey/output.js b/__tests__/__fixtures__/uncached/findKey/output.js index c5fd7493..86ca5653 100644 --- a/__tests__/__fixtures__/uncached/findKey/output.js +++ b/__tests__/__fixtures__/uncached/findKey/output.js @@ -5,11 +5,6 @@ const _iterable = { four: 4 }; -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result; let _value; @@ -17,7 +12,9 @@ let _value; for (let _key in _iterable) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = _key; break; } diff --git a/__tests__/__fixtures__/uncached/findObject/output.js b/__tests__/__fixtures__/uncached/findObject/output.js index c53cf4ba..89090b32 100644 --- a/__tests__/__fixtures__/uncached/findObject/output.js +++ b/__tests__/__fixtures__/uncached/findObject/output.js @@ -5,11 +5,6 @@ const _iterable = { four: 4 }; -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result; let _value; @@ -17,7 +12,9 @@ let _value; for (let _key in _iterable) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = _value; break; } diff --git a/__tests__/__fixtures__/uncached/findRight/output.js b/__tests__/__fixtures__/uncached/findRight/output.js index 377f8c8e..e026ee6e 100644 --- a/__tests__/__fixtures__/uncached/findRight/output.js +++ b/__tests__/__fixtures__/uncached/findRight/output.js @@ -1,16 +1,13 @@ const _iterable = [1, 2, 3, 4]; -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result; for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = _value; break; } diff --git a/__tests__/__fixtures__/uncached/flatMap/output.js b/__tests__/__fixtures__/uncached/flatMap/output.js index 739c8908..73ccc97c 100644 --- a/__tests__/__fixtures__/uncached/flatMap/output.js +++ b/__tests__/__fixtures__/uncached/flatMap/output.js @@ -1,16 +1,11 @@ const _iterable = [['foo', 'bar'], ['bar', 'baz']]; - -const _fn = entry => { - const [first] = entry; - return [first]; -}; - let _result = []; for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; + const [_first] = _value; - _result.push.apply(_result, _fn(_value, _key, _iterable)); + _result.push.apply(_result, [_first]); } const flattened = _result; diff --git a/__tests__/__fixtures__/uncached/flatMapRight/output.js b/__tests__/__fixtures__/uncached/flatMapRight/output.js index b45b99b2..d3838542 100644 --- a/__tests__/__fixtures__/uncached/flatMapRight/output.js +++ b/__tests__/__fixtures__/uncached/flatMapRight/output.js @@ -1,16 +1,11 @@ const _iterable = [['foo', 'bar'], ['bar', 'baz']]; - -const _fn = entry => { - const [first] = entry; - return [first]; -}; - let _result = []; for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { _value = _iterable[_key]; + const [_first] = _value; - _result.push.apply(_result, _fn(_value, _key, _iterable)); + _result.push.apply(_result, [_first]); } const flattened = _result; diff --git a/__tests__/__fixtures__/uncached/forEach/output.js b/__tests__/__fixtures__/uncached/forEach/output.js index a2b168a2..dbabc242 100644 --- a/__tests__/__fixtures__/uncached/forEach/output.js +++ b/__tests__/__fixtures__/uncached/forEach/output.js @@ -1,12 +1,9 @@ const _iterable = [1, 2, 3, 4]; -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - _fn(_value, _key, _iterable); + const _isValueEven = _value % 2 === 0; + + _isValueEven; } diff --git a/__tests__/__fixtures__/uncached/forEachObject/output.js b/__tests__/__fixtures__/uncached/forEachObject/output.js index 7aa8c743..6b3875f1 100644 --- a/__tests__/__fixtures__/uncached/forEachObject/output.js +++ b/__tests__/__fixtures__/uncached/forEachObject/output.js @@ -5,15 +5,12 @@ const _iterable = { four: 4 }; -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _value; for (let _key in _iterable) { _value = _iterable[_key]; - _fn(_value, _key, _iterable); + const _isValueEven = _value % 2 === 0; + + _isValueEven; } diff --git a/__tests__/__fixtures__/uncached/forEachRight/output.js b/__tests__/__fixtures__/uncached/forEachRight/output.js index c2aa82a3..045f7bcd 100644 --- a/__tests__/__fixtures__/uncached/forEachRight/output.js +++ b/__tests__/__fixtures__/uncached/forEachRight/output.js @@ -1,12 +1,9 @@ const _iterable = [1, 2, 3, 4]; -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { _value = _iterable[_key]; - _fn(_value, _key, _iterable); + const _isValueEven = _value % 2 === 0; + + _isValueEven; } diff --git a/__tests__/__fixtures__/uncached/map/output.js b/__tests__/__fixtures__/uncached/map/output.js index 63ba11e4..12d9673b 100644 --- a/__tests__/__fixtures__/uncached/map/output.js +++ b/__tests__/__fixtures__/uncached/map/output.js @@ -1,15 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const doubled = value * 2; - return doubled; -}; - let _result = []; for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - _result[_key] = _fn(_value, _key, _iterable); + + const _doubled = _value * 2; + + _result[_key] = _doubled; } const doubledValues = _result; diff --git a/__tests__/__fixtures__/uncached/mapObject/output.js b/__tests__/__fixtures__/uncached/mapObject/output.js index 098bc9b1..04ddc9c6 100644 --- a/__tests__/__fixtures__/uncached/mapObject/output.js +++ b/__tests__/__fixtures__/uncached/mapObject/output.js @@ -4,19 +4,16 @@ const _iterable = { three: 3, four: 4 }; - -const _fn = value => { - const doubled = value * 2; - return doubled; -}; - let _result = {}; let _value; for (let _key in _iterable) { _value = _iterable[_key]; - _result[_key] = _fn(_value, _key, _iterable); + + const _doubled = _value * 2; + + _result[_key] = _doubled; } const doubledValues = _result; diff --git a/__tests__/__fixtures__/uncached/mapRight/output.js b/__tests__/__fixtures__/uncached/mapRight/output.js index 1bcec909..fff2afa2 100644 --- a/__tests__/__fixtures__/uncached/mapRight/output.js +++ b/__tests__/__fixtures__/uncached/mapRight/output.js @@ -1,15 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const doubled = value * 2; - return doubled; -}; - let _result = []; for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { _value = _iterable[_key]; - _result[_result.length] = _fn(_value, _key, _iterable); + + const _doubled = _value * 2; + + _result[_result.length] = _doubled; } const doubledValues = _result; diff --git a/__tests__/__fixtures__/uncached/reduce-no-initialValue/output.js b/__tests__/__fixtures__/uncached/reduce-no-initialValue/output.js index 74eb7372..b1198ebf 100644 --- a/__tests__/__fixtures__/uncached/reduce-no-initialValue/output.js +++ b/__tests__/__fixtures__/uncached/reduce-no-initialValue/output.js @@ -1,15 +1,9 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = (agg, value, index) => { - agg[index] = value * 2; - return agg; -}; - let _result = _iterable[0]; for (let _key = 1, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - _result = _fn(_result, _value, _key, _iterable); + _result[_key] = _value * 2; } const doubledValues = _result; diff --git a/__tests__/__fixtures__/uncached/reduce/output.js b/__tests__/__fixtures__/uncached/reduce/output.js index 5489e979..3c195fe2 100644 --- a/__tests__/__fixtures__/uncached/reduce/output.js +++ b/__tests__/__fixtures__/uncached/reduce/output.js @@ -1,15 +1,9 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = (agg, value, index) => { - agg[index] = value * 2; - return agg; -}; - let _result = {}; for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - _result = _fn(_result, _value, _key, _iterable); + _result[_key] = _value * 2; } const doubledValues = _result; diff --git a/__tests__/__fixtures__/uncached/reduceObject-no-initialValue/output.js b/__tests__/__fixtures__/uncached/reduceObject-no-initialValue/output.js index b4d0b675..01ac06e1 100644 --- a/__tests__/__fixtures__/uncached/reduceObject-no-initialValue/output.js +++ b/__tests__/__fixtures__/uncached/reduceObject-no-initialValue/output.js @@ -8,17 +8,12 @@ let _hasInitialValue = false; let _value; -const _fn = (agg, value, index) => { - agg[index] = value * 2; - return agg; -}; - let _result; for (let _key in _iterable) { if (_hasInitialValue) { _value = _iterable[_key]; - _result = _fn(_result, _value, _key, _iterable); + _result[_key] = _value * 2; } else { _hasInitialValue = true; _result = _iterable[_key]; diff --git a/__tests__/__fixtures__/uncached/reduceObject/output.js b/__tests__/__fixtures__/uncached/reduceObject/output.js index 6921b100..dedd50a8 100644 --- a/__tests__/__fixtures__/uncached/reduceObject/output.js +++ b/__tests__/__fixtures__/uncached/reduceObject/output.js @@ -7,16 +7,11 @@ const _iterable = { let _value; -const _fn = (agg, value, index) => { - agg[index] = value * 2; - return agg; -}; - let _result = {}; for (let _key in _iterable) { _value = _iterable[_key]; - _result = _fn(_result, _value, _key, _iterable); + _result[_key] = _value * 2; } const doubledValues = _result; diff --git a/__tests__/__fixtures__/uncached/reduceRight-no-initialValue/output.js b/__tests__/__fixtures__/uncached/reduceRight-no-initialValue/output.js index 9d02169d..5b90a016 100644 --- a/__tests__/__fixtures__/uncached/reduceRight-no-initialValue/output.js +++ b/__tests__/__fixtures__/uncached/reduceRight-no-initialValue/output.js @@ -1,16 +1,10 @@ const _iterable = [1, 2, 3, 4]; const _length = _iterable.length; - -const _fn = (agg, value, index) => { - agg[index] = value * 2; - return agg; -}; - let _result = _iterable[_length - 1]; for (let _key = _length - 2, _value; _key >= 0; --_key) { _value = _iterable[_key]; - _result = _fn(_result, _value, _key, _iterable); + _result[_key] = _value * 2; } const doubledValues = _result; diff --git a/__tests__/__fixtures__/uncached/reduceRight/code.js b/__tests__/__fixtures__/uncached/reduceRight/code.js index 6f05dd6e..afffe901 100644 --- a/__tests__/__fixtures__/uncached/reduceRight/code.js +++ b/__tests__/__fixtures__/uncached/reduceRight/code.js @@ -1,4 +1,4 @@ -import { reduceRight } from "../../../../src/inline-loops.macro"; +import { reduceRight } from '../../../../src/inline-loops.macro'; const doubledValues = reduceRight( [1, 2, 3, 4], @@ -7,5 +7,5 @@ const doubledValues = reduceRight( return agg; }, - {} + {}, ); diff --git a/__tests__/__fixtures__/uncached/reduceRight/output.js b/__tests__/__fixtures__/uncached/reduceRight/output.js index 048490a2..7ce6bbb3 100644 --- a/__tests__/__fixtures__/uncached/reduceRight/output.js +++ b/__tests__/__fixtures__/uncached/reduceRight/output.js @@ -1,15 +1,9 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = (agg, value, index) => { - agg[index] = value * 2; - return agg; -}; - let _result = {}; for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { _value = _iterable[_key]; - _result = _fn(_result, _value, _key, _iterable); + _result[_key] = _value * 2; } const doubledValues = _result; diff --git a/__tests__/__fixtures__/uncached/some/output.js b/__tests__/__fixtures__/uncached/some/output.js index 391fb497..cd8adffd 100644 --- a/__tests__/__fixtures__/uncached/some/output.js +++ b/__tests__/__fixtures__/uncached/some/output.js @@ -1,16 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = false; for (let _key = 0, _length = _iterable.length, _value; _key < _length; ++_key) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = true; break; } diff --git a/__tests__/__fixtures__/uncached/someObject/output.js b/__tests__/__fixtures__/uncached/someObject/output.js index f2293112..ece3d35f 100644 --- a/__tests__/__fixtures__/uncached/someObject/output.js +++ b/__tests__/__fixtures__/uncached/someObject/output.js @@ -4,12 +4,6 @@ const _iterable = { three: 3, four: 4 }; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = false; let _value; @@ -17,7 +11,9 @@ let _value; for (let _key in _iterable) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = true; break; } diff --git a/__tests__/__fixtures__/uncached/someRight/output.js b/__tests__/__fixtures__/uncached/someRight/output.js index b8e5f606..d1bf50c3 100644 --- a/__tests__/__fixtures__/uncached/someRight/output.js +++ b/__tests__/__fixtures__/uncached/someRight/output.js @@ -1,16 +1,12 @@ const _iterable = [1, 2, 3, 4]; - -const _fn = value => { - const isValueEven = value % 2 === 0; - return isValueEven; -}; - let _result = false; for (let _key = _iterable.length - 1, _value; _key >= 0; --_key) { _value = _iterable[_key]; - if (_fn(_value, _key, _iterable)) { + const _isValueEven = _value % 2 === 0; + + if (_isValueEven) { _result = true; break; } diff --git a/__tests__/__runtime__/complex-inliner.js b/__tests__/__runtime__/complex-inliner.js new file mode 100644 index 00000000..f824f387 --- /dev/null +++ b/__tests__/__runtime__/complex-inliner.js @@ -0,0 +1,89 @@ +const { map, reduce } = require('../../src/inline-loops.macro'); + +const { deepEqual: isEqual } = require('fast-equals'); + +const ARRAY = [1, 2, 3, 4, 5, 6]; + +// eslint-disable-next-line +const shadowed = 100; + +module.exports = { + conditional: { + standard: { + true: isEqual( + (() => { + if (shadowed === 100) { + return map(ARRAY, v => v * 2); + } + + return ARRAY; + })(), + ARRAY.map(v => v * 2), + ), + }, + }, + ifStatement: { + standard: { + true: isEqual( + map(ARRAY, (value) => { + let transformed = value * 2; + + if (value % 2 === 0) { + transformed += 10; + } + + return transformed; + }), + ARRAY.map((value) => { + let transformed = value * 2; + + if (value % 2 === 0) { + transformed += 10; + } + + return transformed; + }), + ), + }, + }, + shadowName: { + standard: { + true: isEqual( + map(ARRAY, (value) => { + let shadowed = value ** 2; + + if (shadowed % 2 !== 0) { + shadowed += 1; + } + + return shadowed; + }), + ARRAY.map((value) => { + let shadowed = value ** 2; + + if (shadowed % 2 !== 0) { + shadowed += 1; + } + + return shadowed; + }), + ), + }, + }, + internalCall: { + standard: { + true: isEqual( + map(ARRAY, (value) => { + const sub = new Array(10).fill(3); + + return reduce(map(sub, v => value * v), (sum, v) => sum + v); + }), + ARRAY.map((value) => { + const sub = new Array(10).fill(3); + + return sub.map(v => value * v).reduce((sum, v) => sum + v); + }), + ), + }, + }, +}; diff --git a/__tests__/runtime.test.js b/__tests__/runtime.test.js index 93cb4cff..e407a16c 100644 --- a/__tests__/runtime.test.js +++ b/__tests__/runtime.test.js @@ -21,6 +21,10 @@ const TRANSFORMED_FILES = fs.readdirSync(TEST_FILES).reduce((tests, file) => { const transformed = transformFileSync(filename, TRANSFORM_OPTIONS).code; + // if (fn === 'complex-inliner') { + // console.log(transformed); + // } + tests[fn] = eval(transformed); return tests; diff --git a/__tests__/transform.test.js b/__tests__/transform.test.js index 7e4d0807..a4d8161a 100644 --- a/__tests__/transform.test.js +++ b/__tests__/transform.test.js @@ -48,3 +48,11 @@ pluginTester({ plugin, title: 'Complex references', }); + +pluginTester({ + babelOptions: BABEL_OPTIONS, + fixtures: path.join(__dirname, '__fixtures__', 'bailout'), + filename: __filename, + plugin, + title: 'Bailout scenarios', +}); diff --git a/dist/handlers.js b/dist/handlers.js index c106c5af..3c4fdba5 100644 --- a/dist/handlers.js +++ b/dist/handlers.js @@ -1,12 +1,16 @@ "use strict"; +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); + var _require = require('./helpers'), getDefaultResult = _require.getDefaultResult, getIds = _require.getIds, + getInjectedValues = _require.getInjectedValues, getLoop = _require.getLoop, getUid = _require.getUid, - getReduceResultStatement = _require.getReduceResultStatement, - getResultStatement = _require.getResultStatement, + getResultApplication = _require.getResultApplication, insertBeforeParent = _require.insertBeforeParent, isCachedReference = _require.isCachedReference; @@ -30,12 +34,23 @@ function handleEvery(_ref) { var isIterableCached = isCachedReference(t, object); var fnUsed = isHandlerCached ? handler : fn; var iterableUsed = isIterableCached ? object : iterable; - var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - var expr = t.ifStatement(t.unaryExpression('!', resultStatement), t.blockStatement([t.expressionStatement(t.assignmentExpression('=', result, t.booleanLiteral(false))), t.breakStatement()])); + + var _getInjectedValues = getInjectedValues(t, path, { + fn: fnUsed, + getResult: function getResult(resultStatement) { + return t.ifStatement(t.unaryExpression('!', resultStatement), t.blockStatement([t.expressionStatement(t.assignmentExpression('=', result, t.booleanLiteral(false))), t.breakStatement()])); + }, + handler: handler, + iterable: iterableUsed, + key: key, + value: value + }), + body = _getInjectedValues.body, + resultStatement = _getInjectedValues.resultStatement; + var loop = getLoop({ t: t, - body: t.blockStatement([valueAssignment, expr]), + body: body, iterable: iterableUsed, key: key, length: length, @@ -81,13 +96,23 @@ function handleFilter(_ref2) { var isIterableCached = isCachedReference(t, object); var fnUsed = isHandlerCached ? handler : fn; var iterableUsed = isIterableCached ? object : iterable; - var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultAssignment = isObject ? t.assignmentExpression('=', t.memberExpression(result, key, true), value) : t.callExpression(t.memberExpression(result, t.identifier('push')), [value]); - var resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - var expr = t.ifStatement(resultStatement, t.expressionStatement(resultAssignment)); + + var _getInjectedValues2 = getInjectedValues(t, path, { + fn: fnUsed, + getResult: function getResult(resultStatement) { + return t.ifStatement(resultStatement, t.expressionStatement(isObject ? t.assignmentExpression('=', t.memberExpression(result, key, true), value) : t.callExpression(t.memberExpression(result, t.identifier('push')), [value]))); + }, + handler: handler, + iterable: iterableUsed, + key: key, + value: value + }), + body = _getInjectedValues2.body, + resultStatement = _getInjectedValues2.resultStatement; + var loop = getLoop({ t: t, - body: t.blockStatement([valueAssignment, expr]), + body: body, iterable: iterableUsed, key: key, length: length, @@ -133,12 +158,23 @@ function handleFind(_ref3) { var isIterableCached = isCachedReference(t, object); var fnUsed = isHandlerCached ? handler : fn; var iterableUsed = isIterableCached ? object : iterable; - var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - var expr = t.ifStatement(resultStatement, t.blockStatement([t.expressionStatement(t.assignmentExpression('=', result, value)), t.breakStatement()])); + + var _getInjectedValues3 = getInjectedValues(t, path, { + fn: fnUsed, + getResult: function getResult(resultStatement) { + return t.ifStatement(resultStatement, t.blockStatement([t.expressionStatement(t.assignmentExpression('=', result, value)), t.breakStatement()])); + }, + handler: handler, + iterable: iterableUsed, + key: key, + value: value + }), + body = _getInjectedValues3.body, + resultStatement = _getInjectedValues3.resultStatement; + var loop = getLoop({ t: t, - body: t.blockStatement([valueAssignment, expr]), + body: body, iterable: iterableUsed, key: key, length: length, @@ -183,12 +219,23 @@ function handleFindKey(_ref4) { var isIterableCached = isCachedReference(t, object); var fnUsed = isHandlerCached ? handler : fn; var iterableUsed = isIterableCached ? object : iterable; - var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - var expr = t.ifStatement(resultStatement, t.blockStatement([t.expressionStatement(t.assignmentExpression('=', result, key)), t.breakStatement()])); + + var _getInjectedValues4 = getInjectedValues(t, path, { + fn: fnUsed, + getResult: function getResult(resultStatement) { + return t.ifStatement(resultStatement, t.blockStatement([t.expressionStatement(t.assignmentExpression('=', result, key)), t.breakStatement()])); + }, + handler: handler, + iterable: iterableUsed, + key: key, + value: value + }), + body = _getInjectedValues4.body, + resultStatement = _getInjectedValues4.resultStatement; + var loop = getLoop({ t: t, - body: t.blockStatement([valueAssignment, expr]), + body: body, iterable: iterableUsed, key: key, length: length, @@ -234,12 +281,23 @@ function handleFlatMap(_ref5) { var isIterableCached = isCachedReference(t, object); var fnUsed = isHandlerCached ? handler : fn; var iterableUsed = isIterableCached ? object : iterable; - var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - var expr = t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(result, t.identifier('push')), t.identifier('apply')), [result, resultStatement])); + + var _getInjectedValues5 = getInjectedValues(t, path, { + fn: fnUsed, + getResult: function getResult(resultStatement) { + return t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(result, t.identifier('push')), t.identifier('apply')), [result, resultStatement])); + }, + handler: handler, + iterable: iterableUsed, + key: key, + value: value + }), + body = _getInjectedValues5.body, + resultStatement = _getInjectedValues5.resultStatement; + var loop = getLoop({ t: t, - body: t.blockStatement([valueAssignment, expr]), + body: body, iterable: iterableUsed, key: key, length: length, @@ -284,12 +342,23 @@ function handleForEach(_ref6) { var isIterableCached = isCachedReference(t, object); var fnUsed = isHandlerCached ? handler : fn; var iterableUsed = isIterableCached ? object : iterable; - var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - var call = t.expressionStatement(resultStatement); + + var _getInjectedValues6 = getInjectedValues(t, path, { + fn: fnUsed, + getResult: function getResult(resultStatement) { + return t.isExpression(resultStatement) ? t.expressionStatement(resultStatement) : resultStatement; + }, + handler: handler, + iterable: iterableUsed, + key: key, + value: value + }), + body = _getInjectedValues6.body, + resultStatement = _getInjectedValues6.resultStatement; + var loop = getLoop({ t: t, - body: t.blockStatement([valueAssignment, call]), + body: body, iterable: iterableUsed, key: key, length: length, @@ -333,12 +402,23 @@ function handleMap(_ref7) { var isIterableCached = isCachedReference(t, object); var fnUsed = isHandlerCached ? handler : fn; var iterableUsed = isIterableCached ? object : iterable; - var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - var expr = t.expressionStatement(isDecrementing ? t.assignmentExpression('=', t.memberExpression(result, t.memberExpression(result, t.identifier('length')), true), resultStatement) : t.assignmentExpression('=', t.memberExpression(result, key, true), resultStatement)); + + var _getInjectedValues7 = getInjectedValues(t, path, { + fn: fnUsed, + getResult: function getResult(resultStatement) { + return t.expressionStatement(isDecrementing ? t.assignmentExpression('=', t.memberExpression(result, t.memberExpression(result, t.identifier('length')), true), resultStatement) : t.assignmentExpression('=', t.memberExpression(result, key, true), resultStatement)); + }, + handler: handler, + iterable: iterableUsed, + key: key, + value: value + }), + body = _getInjectedValues7.body, + resultStatement = _getInjectedValues7.resultStatement; + var loop = getLoop({ t: t, - body: t.blockStatement([valueAssignment, expr]), + body: body, iterable: iterableUsed, key: key, length: length, @@ -406,15 +486,26 @@ function handleReduce(_ref8) { } var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultStatement = getReduceResultStatement(t, handler, fnUsed, result, value, key, iterableUsed, path); + var resultApplication = getResultApplication(t, handler, fnUsed, value, key, iterableUsed, path, result); + var resultStatement = resultApplication.pop(); var resultAssignment = t.assignmentExpression('=', result, resultStatement); var block; if (!hasInitialValue && isObject) { - var ifHasInitialValue = t.ifStatement(hasInitialValueId, t.blockStatement([valueAssignment, t.expressionStatement(resultAssignment)]), t.blockStatement([t.expressionStatement(t.assignmentExpression('=', hasInitialValueId, t.booleanLiteral(true))), t.expressionStatement(t.assignmentExpression('=', result, t.memberExpression(iterableUsed, key, true)))])); + var mainBlock = [valueAssignment].concat((0, _toConsumableArray2["default"])(resultApplication)); + + if (resultAssignment.left.name !== resultAssignment.right.name) { + mainBlock.push(t.expressionStatement(resultAssignment)); + } + + var ifHasInitialValue = t.ifStatement(hasInitialValueId, t.blockStatement(mainBlock), t.blockStatement([t.expressionStatement(t.assignmentExpression('=', hasInitialValueId, t.booleanLiteral(true))), t.expressionStatement(t.assignmentExpression('=', result, t.memberExpression(iterableUsed, key, true)))])); block = [ifHasInitialValue]; } else { - block = [valueAssignment, t.expressionStatement(resultAssignment)]; + block = [valueAssignment].concat((0, _toConsumableArray2["default"])(resultApplication)); + + if (resultAssignment.left.name !== resultAssignment.right.name) { + block.push(t.expressionStatement(resultAssignment)); + } } var loop = getLoop({ @@ -484,12 +575,23 @@ function handleSome(_ref10) { var isIterableCached = isCachedReference(t, object); var fnUsed = isHandlerCached ? handler : fn; var iterableUsed = isIterableCached ? object : iterable; - var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true))); - var resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - var expr = t.ifStatement(resultStatement, t.blockStatement([t.expressionStatement(t.assignmentExpression('=', result, t.booleanLiteral(true))), t.breakStatement()])); + + var _getInjectedValues8 = getInjectedValues(t, path, { + fn: fnUsed, + getResult: function getResult(resultStatement) { + return t.ifStatement(resultStatement, t.blockStatement([t.expressionStatement(t.assignmentExpression('=', result, t.booleanLiteral(true))), t.breakStatement()])); + }, + handler: handler, + iterable: iterableUsed, + key: key, + value: value + }), + body = _getInjectedValues8.body, + resultStatement = _getInjectedValues8.resultStatement; + var loop = getLoop({ t: t, - body: t.blockStatement([valueAssignment, expr]), + body: body, iterable: iterableUsed, key: key, length: length, diff --git a/dist/helpers.js b/dist/helpers.js index 232a6c2e..5db04ff8 100644 --- a/dist/helpers.js +++ b/dist/helpers.js @@ -4,6 +4,8 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); +var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); + function getDefaultResult(t, isObject) { return isObject ? t.objectExpression([]) : t.arrayExpression(); } @@ -17,15 +19,39 @@ function getIds(scope) { }, {}); } -function getLoop(_ref) { - var t = _ref.t, - body = _ref.body, +function getInjectedValues(t, path, _ref) { + var fn = _ref.fn, + getResult = _ref.getResult, + handler = _ref.handler, iterable = _ref.iterable, key = _ref.key, - length = _ref.length, - value = _ref.value, - isDecrementing = _ref.isDecrementing, - isObject = _ref.isObject; + value = _ref.value; + var valueAssignment = t.expressionStatement(t.assignmentExpression('=', value, t.memberExpression(iterable, key, true))); + var resultApplication = getResultApplication(t, handler, fn, value, key, iterable, path); + var resultStatement = resultApplication.pop(); + var result = getResult(resultStatement); + var block = [valueAssignment]; + + if (resultApplication.length) { + block.push.apply(block, (0, _toConsumableArray2["default"])(resultApplication)); + } + + block.push(result); + return { + body: t.blockStatement(block), + resultStatement: resultStatement + }; +} + +function getLoop(_ref2) { + var t = _ref2.t, + body = _ref2.body, + iterable = _ref2.iterable, + key = _ref2.key, + length = _ref2.length, + value = _ref2.value, + isDecrementing = _ref2.isDecrementing, + isObject = _ref2.isObject; if (isObject) { var left = t.variableDeclaration('let', [t.variableDeclarator(key)]); @@ -54,102 +80,79 @@ function getLoop(_ref) { return t.forStatement(t.variableDeclaration('let', assignments), test, update, body); } -function getReduceResultStatement(t, handler, fn, result, value, key, iterable, path) { - function createRename(r, v, k, i) { +function normalizeHandler(t, handler, path, _ref3) { + var iterable = _ref3.iterable, + key = _ref3.key, + result = _ref3.result, + value = _ref3.value; + + function createRename(_ref4) { + var i = _ref4.i, + k = _ref4.k, + r = _ref4.r, + v = _ref4.v; return function rename(_path) { - if (r) { + if (r && result) { _path.scope.rename(r.name, result.name); } - if (v) { + if (v && value) { _path.scope.rename(v.name, value.name); } - if (k) { + if (k && key) { _path.scope.rename(k.name, key.name); } - if (i) { + if (i && iterable) { _path.scope.rename(i.name, iterable.name); } }; } - if (t.isArrowFunctionExpression(handler) || t.isFunctionExpression(handler)) { - var body = handler.body; - - if (t.isBlockStatement(body)) { - // eslint-disable-next-line prefer-destructuring - body = body.body; - - if (body.length === 1 && handler.params.every(function (param) { - return t.isIdentifier(param); - })) { - var _handler$params = (0, _slicedToArray2["default"])(handler.params, 4), - r = _handler$params[0], - v = _handler$params[1], - k = _handler$params[2], - i = _handler$params[3]; - - var node = body[0]; - - if (t.isArrowFunctionExpression(handler)) { - path.parentPath.traverse({ - ArrowFunctionExpression: createRename(r, v, k, i) - }); - } else { - path.parentPath.traverse({ - FunctionExpression: createRename(r, v, k, i) - }); - } + var r; + var v; + var k; + var i; - if (t.isExpression(node)) { - return node; - } + if (result) { + var _handler$params = (0, _slicedToArray2["default"])(handler.params, 4); - if (t.isExpressionStatement(node)) { - return node.expression; - } + r = _handler$params[0]; + v = _handler$params[1]; + k = _handler$params[2]; + i = _handler$params[3]; + } else { + var _handler$params2 = (0, _slicedToArray2["default"])(handler.params, 3); - if (t.isReturnStatement(node)) { - return node.argument; - } - } - } else if (t.isExpression(body)) { - var _handler$params2 = (0, _slicedToArray2["default"])(handler.params, 4), - _r = _handler$params2[0], - _v = _handler$params2[1], - _k = _handler$params2[2], - _i = _handler$params2[3]; - - path.parentPath.traverse({ - ArrowFunctionExpression: createRename(_r, _v, _k, _i) - }); - return body; - } + v = _handler$params2[0]; + k = _handler$params2[1]; + i = _handler$params2[2]; } - var callExpression = t.callExpression(fn, [result, value, key, iterable]); - callExpression.__inlineLoopsMacroFallback = true; - return callExpression; + if (t.isArrowFunctionExpression(handler)) { + path.parentPath.traverse({ + ArrowFunctionExpression: createRename({ + r: r, + v: v, + k: k, + i: i + }) + }); + } else { + path.parentPath.traverse({ + FunctionExpression: createRename({ + r: r, + v: v, + k: k, + i: i + }) + }); + } } -function getResultStatement(t, handler, fn, value, key, iterable, path) { - function createRename(v, k, i) { - return function rename(_path) { - if (v) { - _path.scope.rename(v.name, value.name); - } - - if (k) { - _path.scope.rename(k.name, key.name); - } - - if (i) { - _path.scope.rename(i.name, iterable.name); - } - }; - } +function getResultApplication(t, handler, fn, value, key, iterable, path, result) { + var callParams = result ? [result, value, key, iterable] : [value, key, iterable]; if (t.isArrowFunctionExpression(handler) || t.isFunctionExpression(handler)) { var body = handler.body; @@ -157,74 +160,103 @@ function getResultStatement(t, handler, fn, value, key, iterable, path) { if (t.isBlockStatement(body)) { // eslint-disable-next-line prefer-destructuring body = body.body; + var parentPath = path.parentPath; + var returnCount = 0; + parentPath.traverse({ + ReturnStatement: function ReturnStatement(_path) { + returnCount++; + + if (_path.parentPath.node !== handler.body) { + returnCount++; + } + } + }); - if (body.length === 1 && handler.params.every(function (param) { - return t.isIdentifier(param); - })) { - var _handler$params3 = (0, _slicedToArray2["default"])(handler.params, 3), - v = _handler$params3[0], - k = _handler$params3[1], - i = _handler$params3[2]; + if (returnCount < 2) { + renameLocalVariables(t, path); - var node = body[0]; + if (!handler.params.every(function (param) { + return t.isIdentifier(param); + })) { + var _body; - if (t.isArrowFunctionExpression(handler)) { - path.parentPath.traverse({ - ArrowFunctionExpression: createRename(v, k, i) - }); - } else { - path.parentPath.traverse({ - FunctionExpression: createRename(v, k, i) - }); - } + var injectedParamAssigns = handler.params.reduce(function (injected, param, index) { + if (t.isIdentifier(param)) { + return injected; + } - if (t.isExpression(node)) { - return node; - } + injected.push(t.variableDeclaration('const', [t.variableDeclarator(param, callParams[index])])); + handler.params[index] = callParams[index]; + return injected; + }, []); - if (t.isExpressionStatement(node)) { - return node.expression; + (_body = body).unshift.apply(_body, (0, _toConsumableArray2["default"])(injectedParamAssigns)); } - if (t.isReturnStatement(node)) { - return node.argument; + normalizeHandler(t, handler, path, { + result: result, + iterable: iterable, + key: key, + value: value + }); + + if (body.length === 1) { + var node = body[0]; + + if (t.isExpression(node)) { + return [node]; + } + + if (t.isExpressionStatement(node)) { + return [node.expression]; + } + + if (t.isReturnStatement(node)) { + return [node.argument]; + } + } else { + var ret = body[body.length - 1]; + + if (t.isReturnStatement(ret)) { + body[body.length - 1] = ret.argument; + } + + return body; } } } else if (t.isExpression(body)) { - var _handler$params4 = (0, _slicedToArray2["default"])(handler.params, 3), - _v2 = _handler$params4[0], - _k2 = _handler$params4[1], - _i2 = _handler$params4[2]; - - path.parentPath.traverse({ - ArrowFunctionExpression: createRename(_v2, _k2, _i2) + normalizeHandler(t, handler, path, { + result: result, + iterable: iterable, + key: key, + value: value }); - return body; + return [body]; } } - var callExpression = t.callExpression(fn, [value, key, iterable]); + var callExpression = t.callExpression(fn, callParams); callExpression.__inlineLoopsMacroFallback = true; - return callExpression; + return [callExpression]; } function getUid(scope, name) { return scope.generateUidIdentifier(name); } -function insertBeforeParent(_ref2) { - var fn = _ref2.fn, - handler = _ref2.handler, - isObject = _ref2.isObject, - iterable = _ref2.iterable, - loop = _ref2.loop, - object = _ref2.object, - path = _ref2.path, - result = _ref2.result, - resultStatement = _ref2.resultStatement, - resultValue = _ref2.resultValue, - t = _ref2.t, - value = _ref2.value; +function insertBeforeParent(_ref5) { + var fn = _ref5.fn, + handler = _ref5.handler, + isObject = _ref5.isObject, + iterable = _ref5.iterable, + loop = _ref5.loop, + object = _ref5.object, + path = _ref5.path, + result = _ref5.result, + resultStatement = _ref5.resultStatement, + resultValue = _ref5.resultValue, + t = _ref5.t, + value = _ref5.value; var insertBefore = []; if (!isCachedReference(t, object)) { @@ -255,13 +287,50 @@ function isCachedReference(t, node) { return t.isIdentifier(node); } +function renameLocalVariables(t, path) { + var containerPath = path.getStatementParent().parentPath; + + function renameLocalVariable(node, functionPath) { + var name = node.name; + var newId = containerPath.scope.generateUidIdentifier(name); + functionPath.scope.rename(name, newId.name); + } + + path.parentPath.traverse({ + ArrayPattern: function ArrayPattern(_path) { + var elements = _path.node.elements; + elements.forEach(function (element) { + if (t.isIdentifier(element)) { + renameLocalVariable(element, _path.getFunctionParent()); + } + }); + }, + ObjectPattern: function ObjectPattern(_path) { + var properties = _path.node.properties; + properties.forEach(function (property) { + if (t.isIdentifier(property.value)) { + renameLocalVariable(property.value, _path.getFunctionParent()); + } + }); + }, + VariableDeclarator: function VariableDeclarator(_path) { + var node = _path.node; + + if (t.isIdentifier(node.id)) { + renameLocalVariable(node.id, _path.getFunctionParent()); + } + } + }); +} + module.exports = { getDefaultResult: getDefaultResult, getIds: getIds, + getInjectedValues: getInjectedValues, getLoop: getLoop, getUid: getUid, - getReduceResultStatement: getReduceResultStatement, - getResultStatement: getResultStatement, + getResultApplication: getResultApplication, insertBeforeParent: insertBeforeParent, - isCachedReference: isCachedReference + isCachedReference: isCachedReference, + renameLocalVariables: renameLocalVariables }; \ No newline at end of file diff --git a/package.json b/package.json index eae8580a..0429b179 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "prepublishOnly": "npm run lint && npm run dist && npm run test", "release": "release-it", "release:beta": "release-it --config=.release-it.beta.json", - "test": "jest", + "test": "BABEL_DISABLE_CACHE=1 jest", "test:watch": "npm run test -- --watch" }, "typings": "./index.d.ts", diff --git a/src/handlers.js b/src/handlers.js index 30ad17bc..558bc3da 100644 --- a/src/handlers.js +++ b/src/handlers.js @@ -1,10 +1,10 @@ const { getDefaultResult, getIds, + getInjectedValues, getLoop, getUid, - getReduceResultStatement, - getResultStatement, + getResultApplication, insertBeforeParent, isCachedReference, } = require('./helpers'); @@ -22,21 +22,26 @@ function handleEvery({ const fnUsed = isHandlerCached ? handler : fn; const iterableUsed = isIterableCached ? object : iterable; - const valueAssignment = t.expressionStatement( - t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), - ); - const resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - const expr = t.ifStatement( - t.unaryExpression('!', resultStatement), - t.blockStatement([ - t.expressionStatement(t.assignmentExpression('=', result, t.booleanLiteral(false))), - t.breakStatement(), - ]), - ); + const { body, resultStatement } = getInjectedValues(t, path, { + fn: fnUsed, + getResult(resultStatement) { + return t.ifStatement( + t.unaryExpression('!', resultStatement), + t.blockStatement([ + t.expressionStatement(t.assignmentExpression('=', result, t.booleanLiteral(false))), + t.breakStatement(), + ]), + ); + }, + handler, + iterable: iterableUsed, + key, + value, + }); const loop = getLoop({ t, - body: t.blockStatement([valueAssignment, expr]), + body, iterable: iterableUsed, key, length, @@ -77,19 +82,27 @@ function handleFilter({ const fnUsed = isHandlerCached ? handler : fn; const iterableUsed = isIterableCached ? object : iterable; - const valueAssignment = t.expressionStatement( - t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), - ); - const resultAssignment = isObject - ? t.assignmentExpression('=', t.memberExpression(result, key, true), value) - : t.callExpression(t.memberExpression(result, t.identifier('push')), [value]); - - const resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - const expr = t.ifStatement(resultStatement, t.expressionStatement(resultAssignment)); + const { body, resultStatement } = getInjectedValues(t, path, { + fn: fnUsed, + getResult(resultStatement) { + return t.ifStatement( + resultStatement, + t.expressionStatement( + isObject + ? t.assignmentExpression('=', t.memberExpression(result, key, true), value) + : t.callExpression(t.memberExpression(result, t.identifier('push')), [value]), + ), + ); + }, + handler, + iterable: iterableUsed, + key, + value, + }); const loop = getLoop({ t, - body: t.blockStatement([valueAssignment, expr]), + body, iterable: iterableUsed, key, length, @@ -130,21 +143,26 @@ function handleFind({ const fnUsed = isHandlerCached ? handler : fn; const iterableUsed = isIterableCached ? object : iterable; - const valueAssignment = t.expressionStatement( - t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), - ); - const resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - const expr = t.ifStatement( - resultStatement, - t.blockStatement([ - t.expressionStatement(t.assignmentExpression('=', result, value)), - t.breakStatement(), - ]), - ); + const { body, resultStatement } = getInjectedValues(t, path, { + fn: fnUsed, + getResult(resultStatement) { + return t.ifStatement( + resultStatement, + t.blockStatement([ + t.expressionStatement(t.assignmentExpression('=', result, value)), + t.breakStatement(), + ]), + ); + }, + handler, + iterable: iterableUsed, + key, + value, + }); const loop = getLoop({ t, - body: t.blockStatement([valueAssignment, expr]), + body, iterable: iterableUsed, key, length, @@ -184,21 +202,26 @@ function handleFindKey({ const fnUsed = isHandlerCached ? handler : fn; const iterableUsed = isIterableCached ? object : iterable; - const valueAssignment = t.expressionStatement( - t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), - ); - const resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - const expr = t.ifStatement( - resultStatement, - t.blockStatement([ - t.expressionStatement(t.assignmentExpression('=', result, key)), - t.breakStatement(), - ]), - ); + const { body, resultStatement } = getInjectedValues(t, path, { + fn: fnUsed, + getResult(resultStatement) { + return t.ifStatement( + resultStatement, + t.blockStatement([ + t.expressionStatement(t.assignmentExpression('=', result, key)), + t.breakStatement(), + ]), + ); + }, + handler, + iterable: iterableUsed, + key, + value, + }); const loop = getLoop({ t, - body: t.blockStatement([valueAssignment, expr]), + body, iterable: iterableUsed, key, length, @@ -239,20 +262,28 @@ function handleFlatMap({ const fnUsed = isHandlerCached ? handler : fn; const iterableUsed = isIterableCached ? object : iterable; - const valueAssignment = t.expressionStatement( - t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), - ); - const resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - const expr = t.expressionStatement( - t.callExpression( - t.memberExpression(t.memberExpression(result, t.identifier('push')), t.identifier('apply')), - [result, resultStatement], - ), - ); + const { body, resultStatement } = getInjectedValues(t, path, { + fn: fnUsed, + getResult(resultStatement) { + return t.expressionStatement( + t.callExpression( + t.memberExpression( + t.memberExpression(result, t.identifier('push')), + t.identifier('apply'), + ), + [result, resultStatement], + ), + ); + }, + handler, + iterable: iterableUsed, + key, + value, + }); const loop = getLoop({ t, - body: t.blockStatement([valueAssignment, expr]), + body, iterable: iterableUsed, key, length, @@ -293,15 +324,22 @@ function handleForEach({ const fnUsed = isHandlerCached ? handler : fn; const iterableUsed = isIterableCached ? object : iterable; - const valueAssignment = t.expressionStatement( - t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), - ); - const resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - const call = t.expressionStatement(resultStatement); + const { body, resultStatement } = getInjectedValues(t, path, { + fn: fnUsed, + getResult(resultStatement) { + return t.isExpression(resultStatement) + ? t.expressionStatement(resultStatement) + : resultStatement; + }, + handler, + iterable: iterableUsed, + key, + value, + }); const loop = getLoop({ t, - body: t.blockStatement([valueAssignment, call]), + body, iterable: iterableUsed, key, length, @@ -340,23 +378,28 @@ function handleMap({ const fnUsed = isHandlerCached ? handler : fn; const iterableUsed = isIterableCached ? object : iterable; - const valueAssignment = t.expressionStatement( - t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), - ); - const resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - const expr = t.expressionStatement( - isDecrementing - ? t.assignmentExpression( - '=', - t.memberExpression(result, t.memberExpression(result, t.identifier('length')), true), - resultStatement, - ) - : t.assignmentExpression('=', t.memberExpression(result, key, true), resultStatement), - ); + const { body, resultStatement } = getInjectedValues(t, path, { + fn: fnUsed, + getResult(resultStatement) { + return t.expressionStatement( + isDecrementing + ? t.assignmentExpression( + '=', + t.memberExpression(result, t.memberExpression(result, t.identifier('length')), true), + resultStatement, + ) + : t.assignmentExpression('=', t.memberExpression(result, key, true), resultStatement), + ); + }, + handler, + iterable: iterableUsed, + key, + value, + }); const loop = getLoop({ t, - body: t.blockStatement([valueAssignment, expr]), + body, iterable: iterableUsed, key, length, @@ -435,24 +478,33 @@ function handleReduce({ const valueAssignment = t.expressionStatement( t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), ); - const resultStatement = getReduceResultStatement( + const resultApplication = getResultApplication( t, handler, fnUsed, - result, value, key, iterableUsed, path, + result, ); + + const resultStatement = resultApplication.pop(); + const resultAssignment = t.assignmentExpression('=', result, resultStatement); let block; if (!hasInitialValue && isObject) { + const mainBlock = [valueAssignment, ...resultApplication]; + + if (resultAssignment.left.name !== resultAssignment.right.name) { + mainBlock.push(t.expressionStatement(resultAssignment)); + } + const ifHasInitialValue = t.ifStatement( hasInitialValueId, - t.blockStatement([valueAssignment, t.expressionStatement(resultAssignment)]), + t.blockStatement(mainBlock), t.blockStatement([ t.expressionStatement( t.assignmentExpression('=', hasInitialValueId, t.booleanLiteral(true)), @@ -465,7 +517,11 @@ function handleReduce({ block = [ifHasInitialValue]; } else { - block = [valueAssignment, t.expressionStatement(resultAssignment)]; + block = [valueAssignment, ...resultApplication]; + + if (resultAssignment.left.name !== resultAssignment.right.name) { + block.push(t.expressionStatement(resultAssignment)); + } } const loop = getLoop({ @@ -531,21 +587,26 @@ function handleSome({ const fnUsed = isHandlerCached ? handler : fn; const iterableUsed = isIterableCached ? object : iterable; - const valueAssignment = t.expressionStatement( - t.assignmentExpression('=', value, t.memberExpression(iterableUsed, key, true)), - ); - const resultStatement = getResultStatement(t, handler, fnUsed, value, key, iterableUsed, path); - const expr = t.ifStatement( - resultStatement, - t.blockStatement([ - t.expressionStatement(t.assignmentExpression('=', result, t.booleanLiteral(true))), - t.breakStatement(), - ]), - ); + const { body, resultStatement } = getInjectedValues(t, path, { + fn: fnUsed, + getResult(resultStatement) { + return t.ifStatement( + resultStatement, + t.blockStatement([ + t.expressionStatement(t.assignmentExpression('=', result, t.booleanLiteral(true))), + t.breakStatement(), + ]), + ); + }, + handler, + iterable: iterableUsed, + key, + value, + }); const loop = getLoop({ t, - body: t.blockStatement([valueAssignment, expr]), + body, iterable: iterableUsed, key, length, diff --git a/src/helpers.js b/src/helpers.js index 1067435c..114eb194 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -12,6 +12,30 @@ function getIds(scope) { }, {}); } +function getInjectedValues(t, path, { + fn, getResult, handler, iterable, key, value, +}) { + const valueAssignment = t.expressionStatement( + t.assignmentExpression('=', value, t.memberExpression(iterable, key, true)), + ); + const resultApplication = getResultApplication(t, handler, fn, value, key, iterable, path); + const resultStatement = resultApplication.pop(); + const result = getResult(resultStatement); + + const block = [valueAssignment]; + + if (resultApplication.length) { + block.push(...resultApplication); + } + + block.push(result); + + return { + body: t.blockStatement(block), + resultStatement, + }; +} + function getLoop({ t, body, iterable, key, length, value, isDecrementing, isObject, }) { @@ -57,27 +81,66 @@ function getLoop({ return t.forStatement(t.variableDeclaration('let', assignments), test, update, body); } -function getReduceResultStatement(t, handler, fn, result, value, key, iterable, path) { - function createRename(r, v, k, i) { +function normalizeHandler(t, handler, path, { + iterable, key, result, value, +}) { + function createRename({ + i, k, r, v, + }) { return function rename(_path) { - if (r) { + if (r && result) { _path.scope.rename(r.name, result.name); } - if (v) { + if (v && value) { _path.scope.rename(v.name, value.name); } - if (k) { + if (k && key) { _path.scope.rename(k.name, key.name); } - if (i) { + if (i && iterable) { _path.scope.rename(i.name, iterable.name); } }; } + let r; + let v; + let k; + let i; + + if (result) { + [r, v, k, i] = handler.params; + } else { + [v, k, i] = handler.params; + } + + if (t.isArrowFunctionExpression(handler)) { + path.parentPath.traverse({ + ArrowFunctionExpression: createRename({ + r, + v, + k, + i, + }), + }); + } else { + path.parentPath.traverse({ + FunctionExpression: createRename({ + r, + v, + k, + i, + }), + }); + } +} + +function getResultApplication(t, handler, fn, value, key, iterable, path, result) { + const callParams = result ? [result, value, key, iterable] : [value, key, iterable]; + if (t.isArrowFunctionExpression(handler) || t.isFunctionExpression(handler)) { let { body } = handler; @@ -85,116 +148,89 @@ function getReduceResultStatement(t, handler, fn, result, value, key, iterable, // eslint-disable-next-line prefer-destructuring body = body.body; - if (body.length === 1 && handler.params.every(param => t.isIdentifier(param))) { - const [r, v, k, i] = handler.params; - const node = body[0]; + const { parentPath } = path; - if (t.isArrowFunctionExpression(handler)) { - path.parentPath.traverse({ - ArrowFunctionExpression: createRename(r, v, k, i), - }); - } else { - path.parentPath.traverse({ - FunctionExpression: createRename(r, v, k, i), - }); - } + let returnCount = 0; - if (t.isExpression(node)) { - return node; - } + parentPath.traverse({ + ReturnStatement(_path) { + returnCount++; - if (t.isExpressionStatement(node)) { - return node.expression; - } - - if (t.isReturnStatement(node)) { - return node.argument; - } - } - } else if (t.isExpression(body)) { - const [r, v, k, i] = handler.params; - - path.parentPath.traverse({ - ArrowFunctionExpression: createRename(r, v, k, i), + if (_path.parentPath.node !== handler.body) { + returnCount++; + } + }, }); - return body; - } - } + if (returnCount < 2) { + renameLocalVariables(t, path); - const callExpression = t.callExpression(fn, [result, value, key, iterable]); + if (!handler.params.every(param => t.isIdentifier(param))) { + const injectedParamAssigns = handler.params.reduce((injected, param, index) => { + if (t.isIdentifier(param)) { + return injected; + } - callExpression.__inlineLoopsMacroFallback = true; + injected.push( + t.variableDeclaration('const', [t.variableDeclarator(param, callParams[index])]), + ); - return callExpression; -} + handler.params[index] = callParams[index]; -function getResultStatement(t, handler, fn, value, key, iterable, path) { - function createRename(v, k, i) { - return function rename(_path) { - if (v) { - _path.scope.rename(v.name, value.name); - } + return injected; + }, []); - if (k) { - _path.scope.rename(k.name, key.name); - } + body.unshift(...injectedParamAssigns); + } - if (i) { - _path.scope.rename(i.name, iterable.name); - } - }; - } + normalizeHandler(t, handler, path, { + result, + iterable, + key, + value, + }); - if (t.isArrowFunctionExpression(handler) || t.isFunctionExpression(handler)) { - let { body } = handler; + if (body.length === 1) { + const node = body[0]; - if (t.isBlockStatement(body)) { - // eslint-disable-next-line prefer-destructuring - body = body.body; + if (t.isExpression(node)) { + return [node]; + } - if (body.length === 1 && handler.params.every(param => t.isIdentifier(param))) { - const [v, k, i] = handler.params; - const node = body[0]; + if (t.isExpressionStatement(node)) { + return [node.expression]; + } - if (t.isArrowFunctionExpression(handler)) { - path.parentPath.traverse({ - ArrowFunctionExpression: createRename(v, k, i), - }); + if (t.isReturnStatement(node)) { + return [node.argument]; + } } else { - path.parentPath.traverse({ - FunctionExpression: createRename(v, k, i), - }); - } - - if (t.isExpression(node)) { - return node; - } + const ret = body[body.length - 1]; - if (t.isExpressionStatement(node)) { - return node.expression; - } + if (t.isReturnStatement(ret)) { + body[body.length - 1] = ret.argument; + } - if (t.isReturnStatement(node)) { - return node.argument; + return body; } } } else if (t.isExpression(body)) { - const [v, k, i] = handler.params; - - path.parentPath.traverse({ - ArrowFunctionExpression: createRename(v, k, i), + normalizeHandler(t, handler, path, { + result, + iterable, + key, + value, }); - return body; + return [body]; } } - const callExpression = t.callExpression(fn, [value, key, iterable]); + const callExpression = t.callExpression(fn, callParams); callExpression.__inlineLoopsMacroFallback = true; - return callExpression; + return [callExpression]; } function getUid(scope, name) { @@ -254,13 +290,53 @@ function isCachedReference(t, node) { return t.isIdentifier(node); } +function renameLocalVariables(t, path) { + const containerPath = path.getStatementParent().parentPath; + + function renameLocalVariable(node, functionPath) { + const { name } = node; + const newId = containerPath.scope.generateUidIdentifier(name); + + functionPath.scope.rename(name, newId.name); + } + + path.parentPath.traverse({ + ArrayPattern(_path) { + const { elements } = _path.node; + + elements.forEach((element) => { + if (t.isIdentifier(element)) { + renameLocalVariable(element, _path.getFunctionParent()); + } + }); + }, + ObjectPattern(_path) { + const { properties } = _path.node; + + properties.forEach((property) => { + if (t.isIdentifier(property.value)) { + renameLocalVariable(property.value, _path.getFunctionParent()); + } + }); + }, + VariableDeclarator(_path) { + const { node } = _path; + + if (t.isIdentifier(node.id)) { + renameLocalVariable(node.id, _path.getFunctionParent()); + } + }, + }); +} + module.exports = { getDefaultResult, getIds, + getInjectedValues, getLoop, getUid, - getReduceResultStatement, - getResultStatement, + getResultApplication, insertBeforeParent, isCachedReference, + renameLocalVariables, };