From f04aa4114981638f88a09de2da66f83828467cc7 Mon Sep 17 00:00:00 2001 From: Tony Quetano Date: Sat, 16 Dec 2023 13:37:38 -0500 Subject: [PATCH] support usage in conditionals --- CHANGELOG.md | 1 + README.md | 51 -------- .../__fixtures__/bailout/early-return/code.js | 14 ++- .../bailout/early-return/output.js | 42 ++++--- .../{conditional => conditional-if}/code.js | 4 +- .../complex/conditional-if/output.js | 14 +++ .../complex/conditional-logical/code.js | 5 + .../complex/conditional-logical/output.js | 14 +++ .../complex/conditional-ternary/code.js | 5 + .../complex/conditional-ternary/output.js | 13 ++ .../complex/conditional/output.js | 12 -- __tests__/__fixtures__/complex/jsx/output.js | 34 +++--- __tests__/__fixtures__/complex/this/output.js | 22 ++-- dist/handlers.js | 111 ++++++++--------- dist/templates.js | 24 ++-- dist/utils.js | 39 +++++- src/handlers.ts | 114 ++++++++---------- src/templates.ts | 7 ++ src/types.ts | 3 +- src/utils.ts | 62 ++++++++-- 20 files changed, 332 insertions(+), 259 deletions(-) rename __tests__/__fixtures__/complex/{conditional => conditional-if}/code.js (60%) create mode 100644 __tests__/__fixtures__/complex/conditional-if/output.js create mode 100644 __tests__/__fixtures__/complex/conditional-logical/code.js create mode 100644 __tests__/__fixtures__/complex/conditional-logical/output.js create mode 100644 __tests__/__fixtures__/complex/conditional-ternary/code.js create mode 100644 __tests__/__fixtures__/complex/conditional-ternary/output.js delete mode 100644 __tests__/__fixtures__/complex/conditional/output.js diff --git a/CHANGELOG.md b/CHANGELOG.md index faad7b27..7e73d39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ## Enhancements +- Support for conditional usage in ternaries / logical expressions - Better TS typing - Better inline handling of complex logic diff --git a/README.md b/README.md index 1ba99b50..d60cc080 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ Iteration helpers that inline to native loops for performance - [Aggressive inlining](#aggressive-inlining) - [Bailout scenarios](#bailout-scenarios) - [Gotchas](#gotchas) - - [Conditionals do not delay execution](#conditionals-do-not-delay-execution) - [`*Object` methods do not perform `hasOwnProperty` check](#object-methods-do-not-perform-hasownproperty-check) - [`find*` methods](#find-methods) - [Development](#development) @@ -181,56 +180,6 @@ const inlined = map(array, (value) => (value % 2 === 0 ? 'even' : 'odd')); 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 -const _length = array.length; -const _results = Array(_length); -for (let _key = 0, _value; _key < _length; ++_key) { - _value = array[_key]; - _results[_key] = fn(_value, _key, array); -} -return isFoo ? _results : 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) { - const _length = array.length; - const _results = Array(_length); - for (let _key = 0, _value; _key < _length; ++_key) { - _value = array[_key]; - _results[_key] = fn(_value, _key, array); - } - return _results; -} - -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 index 0c6b03a5..287582cf 100644 --- a/__tests__/__fixtures__/bailout/early-return/code.js +++ b/__tests__/__fixtures__/bailout/early-return/code.js @@ -1,7 +1,11 @@ import { filter } from '../../../../src/inline-loops.macro'; -const result = filter([1, 2, 3], (value) => { - if (value === 2) { - return true; - } -}); +function getFoo(config) { + const collection = config.collection || [1, 2, 3]; + + return filter(collection, (value) => { + if (value === 2) { + return true; + } + }); +} diff --git a/__tests__/__fixtures__/bailout/early-return/output.js b/__tests__/__fixtures__/bailout/early-return/output.js index 222c045b..de24365d 100644 --- a/__tests__/__fixtures__/bailout/early-return/output.js +++ b/__tests__/__fixtures__/bailout/early-return/output.js @@ -1,19 +1,23 @@ -const _collection = [1, 2, 3]; -const _fn = (_value) => { - if (_value === 2) { - return true; - } -}; -const _results = []; -for ( - let _key = 0, _length = _collection.length, _value, _result; - _key < _length; - ++_key -) { - _value = _collection[_key]; - _result = _fn(_value, _key, _collection); - if (_result) { - _results.push(_value); - } -} -const result = _results; \ No newline at end of file +function getFoo(config) { + const collection = config.collection || [1, 2, 3]; + return (() => { + const _fn = (_value) => { + if (_value === 2) { + return true; + } + }; + const _results = []; + for ( + let _key = 0, _length = collection.length, _value, _result; + _key < _length; + ++_key + ) { + _value = collection[_key]; + _result = _fn(_value, _key, collection); + if (_result) { + _results.push(_value); + } + } + return _results; + })(); +} \ No newline at end of file diff --git a/__tests__/__fixtures__/complex/conditional/code.js b/__tests__/__fixtures__/complex/conditional-if/code.js similarity index 60% rename from __tests__/__fixtures__/complex/conditional/code.js rename to __tests__/__fixtures__/complex/conditional-if/code.js index 2f0fb644..36e127e5 100644 --- a/__tests__/__fixtures__/complex/conditional/code.js +++ b/__tests__/__fixtures__/complex/conditional-if/code.js @@ -1,8 +1,8 @@ import { map } from '../../../../src/inline-loops.macro'; -function getStuff() { +function getStuff(array, foo) { if (foo === 'bar') { - return map(array, v => v * 2); + return map(array, (v) => v * 2); } return array; diff --git a/__tests__/__fixtures__/complex/conditional-if/output.js b/__tests__/__fixtures__/complex/conditional-if/output.js new file mode 100644 index 00000000..69b290a4 --- /dev/null +++ b/__tests__/__fixtures__/complex/conditional-if/output.js @@ -0,0 +1,14 @@ +function getStuff(array, foo) { + if (foo === 'bar') { + return (() => { + const _length = array.length; + const _results = Array(_length); + for (let _key = 0, _v; _key < _length; ++_key) { + _v = array[_key]; + _results[_key] = _v * 2; + } + return _results; + })(); + } + return array; +} \ No newline at end of file diff --git a/__tests__/__fixtures__/complex/conditional-logical/code.js b/__tests__/__fixtures__/complex/conditional-logical/code.js new file mode 100644 index 00000000..cc7cb3fa --- /dev/null +++ b/__tests__/__fixtures__/complex/conditional-logical/code.js @@ -0,0 +1,5 @@ +import { map } from '../../../../src/inline-loops.macro'; + +function getStuff(array) { + return array || map(array, (v) => v * 2); +} diff --git a/__tests__/__fixtures__/complex/conditional-logical/output.js b/__tests__/__fixtures__/complex/conditional-logical/output.js new file mode 100644 index 00000000..ceab1eaa --- /dev/null +++ b/__tests__/__fixtures__/complex/conditional-logical/output.js @@ -0,0 +1,14 @@ +function getStuff(array) { + return ( + array || + (() => { + const _length = array.length; + const _results = Array(_length); + for (let _key = 0, _v; _key < _length; ++_key) { + _v = array[_key]; + _results[_key] = _v * 2; + } + return _results; + })() + ); +} \ No newline at end of file diff --git a/__tests__/__fixtures__/complex/conditional-ternary/code.js b/__tests__/__fixtures__/complex/conditional-ternary/code.js new file mode 100644 index 00000000..caccbf06 --- /dev/null +++ b/__tests__/__fixtures__/complex/conditional-ternary/code.js @@ -0,0 +1,5 @@ +import { map } from '../../../../src/inline-loops.macro'; + +function getStuff(array, foo) { + return foo === 'bar' ? map(array, (v) => v * 2) : array; +} diff --git a/__tests__/__fixtures__/complex/conditional-ternary/output.js b/__tests__/__fixtures__/complex/conditional-ternary/output.js new file mode 100644 index 00000000..a78eb031 --- /dev/null +++ b/__tests__/__fixtures__/complex/conditional-ternary/output.js @@ -0,0 +1,13 @@ +function getStuff(array, foo) { + return foo === 'bar' + ? (() => { + const _length = array.length; + const _results = Array(_length); + for (let _key = 0, _v; _key < _length; ++_key) { + _v = array[_key]; + _results[_key] = _v * 2; + } + return _results; + })() + : array; +} \ No newline at end of file diff --git a/__tests__/__fixtures__/complex/conditional/output.js b/__tests__/__fixtures__/complex/conditional/output.js deleted file mode 100644 index e3b7e4fd..00000000 --- a/__tests__/__fixtures__/complex/conditional/output.js +++ /dev/null @@ -1,12 +0,0 @@ -function getStuff() { - if (foo === 'bar') { - const _length = array.length; - const _results = Array(_length); - for (let _key = 0, _v; _key < _length; ++_key) { - _v = array[_key]; - _results[_key] = _v * 2; - } - return _results; - } - return array; -} \ No newline at end of file diff --git a/__tests__/__fixtures__/complex/jsx/output.js b/__tests__/__fixtures__/complex/jsx/output.js index 31170b05..fe1a8a78 100644 --- a/__tests__/__fixtures__/complex/jsx/output.js +++ b/__tests__/__fixtures__/complex/jsx/output.js @@ -1,17 +1,23 @@ import React from 'react'; function List(props) { - const _collection = props.items; - const _length = _collection.length; - const _results = Array(_length); - for (let _key = 0, _item; _key < _length; ++_key) { - _item = _collection[_key]; - _results[_key] = /*#__PURE__*/ React.createElement( - 'li', - { - key: _item.id, - }, - _item.value, - ); - } - return /*#__PURE__*/ React.createElement('ul', null, _results); + return /*#__PURE__*/ React.createElement( + 'ul', + null, + (() => { + const _collection = props.items; + const _length = _collection.length; + const _results = Array(_length); + for (let _key = 0, _item; _key < _length; ++_key) { + _item = _collection[_key]; + _results[_key] = /*#__PURE__*/ React.createElement( + 'li', + { + key: _item.id, + }, + _item.value, + ); + } + return _results; + })(), + ); } \ No newline at end of file diff --git a/__tests__/__fixtures__/complex/this/output.js b/__tests__/__fixtures__/complex/this/output.js index 718b074b..2fb5ffac 100644 --- a/__tests__/__fixtures__/complex/this/output.js +++ b/__tests__/__fixtures__/complex/this/output.js @@ -1,12 +1,14 @@ function foo(array) { - const _fn = function (_value) { - return this && this.foo ? _value : null; - }; - const _length = array.length; - const _results = Array(_length); - for (let _key = 0, _value; _key < _length; ++_key) { - _value = array[_key]; - _results[_key] = _fn(_value, _key, array); - } - return _results; + return (() => { + const _fn = function (_value) { + return this && this.foo ? _value : null; + }; + const _length = array.length; + const _results = Array(_length); + for (let _key = 0, _value; _key < _length; ++_key) { + _value = array[_key]; + _results[_key] = _fn(_value, _key, array); + } + return _results; + })(); } \ No newline at end of file diff --git a/dist/handlers.js b/dist/handlers.js index 428ff724..7f03f005 100644 --- a/dist/handlers.js +++ b/dist/handlers.js @@ -47,8 +47,7 @@ function createHandlers(babel) { isForEach = _ref.isForEach, isReduce = _ref.isReduce, local = _ref.local, - path = _ref.path, - statement = _ref.statement; + path = _ref.path; var body = handler.get('body'); var traverseState = { containsThis: false, @@ -85,7 +84,7 @@ function createHandlers(babel) { LOCAL: localFnName, VALUE: handler.node }); - statement.insertBefore(localFn); + local.contents.push(localFn); var _logic = t.callExpression(localFnName, (0, _utils.getCachedFnArgs)(local, isReduce)); return { injectedBody: [], @@ -114,7 +113,7 @@ function createHandlers(babel) { logic: logic }; } - function getLocalReferences(path, statement, isReduce) { + function getLocalReferences(path, isReduce) { var _path$get = path.get('arguments'), _path$get2 = (0, _slicedToArray2["default"])(_path$get, 2), collection = _path$get2[0], @@ -122,6 +121,7 @@ function createHandlers(babel) { if (!collection || !handler) { throw new _babelPluginMacros.MacroError('Must pass both a collection and a handler'); } + var contents = []; var localCollection = collection.node; if (!collection.isIdentifier()) { localCollection = (0, _utils.getLocalName)(path, 'collection'); @@ -129,7 +129,7 @@ function createHandlers(babel) { LOCAL: localCollection, VALUE: collection.node }); - statement.insertBefore(localVariable); + contents.push(localVariable); } var accumulated; var value; @@ -189,6 +189,7 @@ function createHandlers(babel) { return { accumulated: localAccumulated, collection: localCollection, + contents: contents, key: localKey, length: localLength, value: localValue @@ -210,22 +211,20 @@ function createHandlers(babel) { throw new _babelPluginMacros.MacroError('Must pass both a collection and a handler'); } (0, _utils.processNestedInlineLoopMacros)(collection, handlers); - var statement = path.getStatementParent(); - var local = getLocalReferences(path, statement); + var local = getLocalReferences(path); var _getInjectedBodyAndLo = getInjectedBodyAndLogic({ handler: handler, local: local, - path: path, - statement: statement + path: path }), injectedBody = _getInjectedBodyAndLo.injectedBody, logic = _getInjectedBodyAndLo.logic; var result = path.scope.generateUidIdentifier('result'); var determination = path.scope.generateUidIdentifier('determination'); - var forLoop; + var loop; switch (type) { case 'every-left': - forLoop = templates.every({ + loop = templates.every({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -237,7 +236,7 @@ function createHandlers(babel) { }); break; case 'some-left': - forLoop = templates.some({ + loop = templates.some({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -249,7 +248,7 @@ function createHandlers(babel) { }); break; case 'every-right': - forLoop = templates.everyRight({ + loop = templates.everyRight({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -260,7 +259,7 @@ function createHandlers(babel) { }); break; case 'some-right': - forLoop = templates.someRight({ + loop = templates.someRight({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -271,7 +270,7 @@ function createHandlers(babel) { }); break; case 'every-object': - forLoop = templates.everyObject({ + loop = templates.everyObject({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -282,7 +281,7 @@ function createHandlers(babel) { }); break; case 'some-object': - forLoop = templates.someObject({ + loop = templates.someObject({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -295,8 +294,8 @@ function createHandlers(babel) { default: throw new _babelPluginMacros.MacroError("Invalid type ".concat(type, " provided")); } - statement.insertBefore(forLoop); - (0, _utils.replaceOrRemove)(path, determination); + local.contents.push(loop); + (0, _utils.replaceOrRemove)(babel, path, local, templates, determination); }; } function createHandleFind(type) { @@ -315,22 +314,20 @@ function createHandlers(babel) { throw new _babelPluginMacros.MacroError('Must pass both a collection and a handler'); } (0, _utils.processNestedInlineLoopMacros)(collection, handlers); - var statement = path.getStatementParent(); - var local = getLocalReferences(path, statement); + var local = getLocalReferences(path); var _getInjectedBodyAndLo2 = getInjectedBodyAndLogic({ handler: handler, local: local, - path: path, - statement: statement + path: path }), injectedBody = _getInjectedBodyAndLo2.injectedBody, logic = _getInjectedBodyAndLo2.logic; var result = path.scope.generateUidIdentifier('result'); var match = path.scope.generateUidIdentifier('match'); - var forLoop; + var loop; switch (type) { case 'find-left': - forLoop = templates.find({ + loop = templates.find({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -342,7 +339,7 @@ function createHandlers(babel) { }); break; case 'find-index': - forLoop = templates.findIndex({ + loop = templates.findIndex({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -354,7 +351,7 @@ function createHandlers(babel) { }); break; case 'find-last': - forLoop = templates.findLast({ + loop = templates.findLast({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -365,7 +362,7 @@ function createHandlers(babel) { }); break; case 'find-last-index': - forLoop = templates.findLastIndex({ + loop = templates.findLastIndex({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -376,7 +373,7 @@ function createHandlers(babel) { }); break; case 'find-object': - forLoop = templates.findObject({ + loop = templates.findObject({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -387,7 +384,7 @@ function createHandlers(babel) { }); break; case 'find-key': - forLoop = templates.findKey({ + loop = templates.findKey({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -400,8 +397,8 @@ function createHandlers(babel) { default: throw new _babelPluginMacros.MacroError("Invalid type ".concat(type, " provided")); } - statement.insertBefore(forLoop); - (0, _utils.replaceOrRemove)(path, match); + local.contents.push(loop); + (0, _utils.replaceOrRemove)(babel, path, local, templates, match); }; } function createHandleMapFilterForEach(type) { @@ -420,8 +417,7 @@ function createHandlers(babel) { throw new _babelPluginMacros.MacroError('Must pass both a collection and a handler'); } (0, _utils.processNestedInlineLoopMacros)(collection, handlers); - var statement = path.getStatementParent(); - var local = getLocalReferences(path, statement); + var local = getLocalReferences(path); var localResults = (0, _utils.getLocalName)(path, 'results'); var isForEach = type.includes('for-each'); var result = path.scope.generateUidIdentifier('result'); @@ -429,15 +425,14 @@ function createHandlers(babel) { handler: handler, isForEach: isForEach, local: local, - path: path, - statement: statement + path: path }), injectedBody = _getInjectedBodyAndLo3.injectedBody, logic = _getInjectedBodyAndLo3.logic; - var forLoop; + var loop; switch (type) { case 'map-left': - forLoop = templates.map({ + loop = templates.map({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -448,7 +443,7 @@ function createHandlers(babel) { }); break; case 'filter-left': - forLoop = templates.filter({ + loop = templates.filter({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -460,7 +455,7 @@ function createHandlers(babel) { }); break; case 'flat-map-left': - forLoop = templates.flatMap({ + loop = templates.flatMap({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -472,7 +467,7 @@ function createHandlers(babel) { }); break; case 'for-each-left': - forLoop = templates.forEach({ + loop = templates.forEach({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -481,7 +476,7 @@ function createHandlers(babel) { }); break; case 'map-right': - forLoop = templates.mapRight({ + loop = templates.mapRight({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -492,7 +487,7 @@ function createHandlers(babel) { }); break; case 'filter-right': - forLoop = templates.filterRight({ + loop = templates.filterRight({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -503,7 +498,7 @@ function createHandlers(babel) { }); break; case 'flat-map-right': - forLoop = templates.flatMapRight({ + loop = templates.flatMapRight({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -514,7 +509,7 @@ function createHandlers(babel) { }); break; case 'for-each-right': - forLoop = templates.forEachRight({ + loop = templates.forEachRight({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -522,7 +517,7 @@ function createHandlers(babel) { }); break; case 'map-object': - forLoop = templates.mapObject({ + loop = templates.mapObject({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -533,7 +528,7 @@ function createHandlers(babel) { }); break; case 'filter-object': - forLoop = templates.filterObject({ + loop = templates.filterObject({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -544,7 +539,7 @@ function createHandlers(babel) { }); break; case 'for-each-object': - forLoop = templates.forEachObject({ + loop = templates.forEachObject({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -554,8 +549,8 @@ function createHandlers(babel) { default: throw new _babelPluginMacros.MacroError("Invalid type ".concat(type, " provided")); } - statement.insertBefore(forLoop); - (0, _utils.replaceOrRemove)(path, isForEach ? t.identifier('undefined') : localResults); + local.contents.push(loop); + (0, _utils.replaceOrRemove)(babel, path, local, templates, isForEach ? t.identifier('undefined') : localResults); }; } function createHandleReduce(type) { @@ -575,14 +570,12 @@ function createHandlers(babel) { throw new _babelPluginMacros.MacroError('Must pass both a collection and a handler'); } (0, _utils.processNestedInlineLoopMacros)(collection, handlers); - var statement = path.getStatementParent(); - var local = getLocalReferences(path, statement, true); + var local = getLocalReferences(path, true); var _getInjectedBodyAndLo4 = getInjectedBodyAndLogic({ handler: handler, isReduce: true, local: local, - path: path, - statement: statement + path: path }), injectedBody = _getInjectedBodyAndLo4.injectedBody, logic = _getInjectedBodyAndLo4.logic; @@ -596,10 +589,10 @@ function createHandlers(babel) { initial = initialValue ? initialValue.node : t.memberExpression(local.collection, t.numericLiteral(0), true); } var start = t.numericLiteral(initialValue ? 0 : 1); - var forLoop; + var loop; switch (type) { case 'left': - forLoop = templates.reduce({ + loop = templates.reduce({ ACCUMULATED: local.accumulated, BODY: injectedBody, COLLECTION: local.collection, @@ -612,7 +605,7 @@ function createHandlers(babel) { }); break; case 'right': - forLoop = templates.reduceRight({ + loop = templates.reduceRight({ ACCUMULATED: local.accumulated, BODY: injectedBody, COLLECTION: local.collection, @@ -627,7 +620,7 @@ function createHandlers(babel) { { var skip = path.scope.generateUidIdentifier('skip'); var shouldSkip = t.booleanLiteral(!initialValue); - forLoop = templates.reduceObject({ + loop = templates.reduceObject({ ACCUMULATED: local.accumulated, BODY: injectedBody, COLLECTION: local.collection, @@ -643,8 +636,8 @@ function createHandlers(babel) { default: throw new _babelPluginMacros.MacroError("Invalid type ".concat(type, " provided")); } - statement.insertBefore(forLoop); - (0, _utils.replaceOrRemove)(path, local.accumulated); + local.contents.push(loop); + (0, _utils.replaceOrRemove)(babel, path, local, templates, local.accumulated); }; } return handlers; diff --git a/dist/templates.js b/dist/templates.js index cb0cd14a..0f710339 100644 --- a/dist/templates.js +++ b/dist/templates.js @@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { }); exports.createTemplates = createTemplates; var _taggedTemplateLiteral2 = _interopRequireDefault(require("@babel/runtime/helpers/taggedTemplateLiteral")); -var _templateObject, _templateObject2, _templateObject3, _templateObject4, _templateObject5, _templateObject6, _templateObject7, _templateObject8, _templateObject9, _templateObject10, _templateObject11, _templateObject12, _templateObject13, _templateObject14, _templateObject15, _templateObject16, _templateObject17, _templateObject18, _templateObject19, _templateObject20, _templateObject21, _templateObject22, _templateObject23, _templateObject24, _templateObject25, _templateObject26, _templateObject27, _templateObject28; +var _templateObject, _templateObject2, _templateObject3, _templateObject4, _templateObject5, _templateObject6, _templateObject7, _templateObject8, _templateObject9, _templateObject10, _templateObject11, _templateObject12, _templateObject13, _templateObject14, _templateObject15, _templateObject16, _templateObject17, _templateObject18, _templateObject19, _templateObject20, _templateObject21, _templateObject22, _templateObject23, _templateObject24, _templateObject25, _templateObject26, _templateObject27, _templateObject28, _templateObject29; function createTemplates(_ref) { var template = _ref.template; var every = template(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2["default"])(["\n let DETERMINATION = true;\n for (let KEY = 0, LENGTH = COLLECTION.length, VALUE, RESULT; KEY < LENGTH; ++KEY) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULT = LOGIC;\n\n if (!RESULT) {\n DETERMINATION = false;\n break;\n }\n }\n"]))); @@ -27,16 +27,17 @@ function createTemplates(_ref) { var forEachObject = template(_templateObject16 || (_templateObject16 = (0, _taggedTemplateLiteral2["default"])(["\n let VALUE;\n for (const KEY in COLLECTION) {\n VALUE = COLLECTION[KEY];\n BODY\n }\n"]))); var forEachRight = template(_templateObject17 || (_templateObject17 = (0, _taggedTemplateLiteral2["default"])([" \n for (let KEY = COLLECTION.length, VALUE; --KEY >= 0;) {\n VALUE = COLLECTION[KEY];\n BODY\n }\n"]))); var localVariable = template(_templateObject18 || (_templateObject18 = (0, _taggedTemplateLiteral2["default"])(["\n const LOCAL = VALUE;\n"]))); - var map = template(_templateObject19 || (_templateObject19 = (0, _taggedTemplateLiteral2["default"])(["\n const LENGTH = COLLECTION.length;\n const RESULTS = Array(LENGTH);\n\tfor (let KEY = 0, VALUE; KEY < LENGTH; ++KEY) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULTS[KEY] = LOGIC;\n }\n"]))); - var mapObject = template(_templateObject20 || (_templateObject20 = (0, _taggedTemplateLiteral2["default"])(["\n const RESULTS = {};\n let VALUE,\n RESULT;\n for (const KEY in COLLECTION) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULTS[KEY] = LOGIC;\n }\n"]))); - var mapRight = template(_templateObject21 || (_templateObject21 = (0, _taggedTemplateLiteral2["default"])(["\n const LENGTH = COLLECTION.length;\n let KEY = LENGTH;\n const RESULTS = Array(LENGTH);\n for (let VALUE; --KEY >= 0;) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULTS[LENGTH - KEY - 1] = LOGIC;\n }\n"]))); - var nthLastItem = template(_templateObject22 || (_templateObject22 = (0, _taggedTemplateLiteral2["default"])(["\n COLLECTION[COLLECTION.length - COUNT]\n"]))); - var reduce = template(_templateObject23 || (_templateObject23 = (0, _taggedTemplateLiteral2["default"])(["\n let ACCUMULATED = INITIAL;\n for (let KEY = START, LENGTH = COLLECTION.length, VALUE; KEY < LENGTH; ++KEY) {\n VALUE = COLLECTION[KEY];\n BODY\n ACCUMULATED = LOGIC;\n }\n"]))); - var reduceObject = template(_templateObject24 || (_templateObject24 = (0, _taggedTemplateLiteral2["default"])(["\n let SKIP = SHOULD_SKIP,\n ACCUMULATED = INITIAL,\n VALUE;\n for (const KEY in COLLECTION) {\n VALUE = COLLECTION[KEY];\n\n if (SKIP) {\n ACCUMULATED = VALUE;\n SKIP = false;\n continue;\n }\n\n BODY\n ACCUMULATED = LOGIC;\n }\n"]))); - var reduceRight = template(_templateObject25 || (_templateObject25 = (0, _taggedTemplateLiteral2["default"])(["\n let ACCUMULATED = INITIAL;\n for (let KEY = COLLECTION.length - START, VALUE; --KEY >= START;) {\n VALUE = COLLECTION[KEY];\n BODY\n ACCUMULATED = LOGIC;\n }\n"]))); - var some = template(_templateObject26 || (_templateObject26 = (0, _taggedTemplateLiteral2["default"])(["\n let DETERMINATION = false;\n for (let KEY = 0, LENGTH = COLLECTION.length, VALUE, RESULT; KEY < LENGTH; ++KEY) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULT = LOGIC;\n\n if (RESULT) {\n DETERMINATION = true;\n break;\n }\n }\n"]))); - var someObject = template(_templateObject27 || (_templateObject27 = (0, _taggedTemplateLiteral2["default"])(["\n let DETERMINATION = false,\n VALUE,\n RESULT;\n for (const KEY in COLLECTION) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULT = LOGIC;\n\n if (RESULT) {\n DETERMINATION = true;\n break;\n }\n }\n"]))); - var someRight = template(_templateObject28 || (_templateObject28 = (0, _taggedTemplateLiteral2["default"])(["\n const DETERMINATION = false;\n for (let KEY = COLLECTION.length, VALUE, RESULT; --KEY >= 0;) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULT = LOGIC;\n\n if (RESULT) {\n DETERMINATION = true;\n break;\n }\n }\n"]))); + var iife = template(_templateObject19 || (_templateObject19 = (0, _taggedTemplateLiteral2["default"])(["\n (() => {\n BODY\n })();\n"]))); + var map = template(_templateObject20 || (_templateObject20 = (0, _taggedTemplateLiteral2["default"])(["\n const LENGTH = COLLECTION.length;\n const RESULTS = Array(LENGTH);\n\tfor (let KEY = 0, VALUE; KEY < LENGTH; ++KEY) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULTS[KEY] = LOGIC;\n }\n"]))); + var mapObject = template(_templateObject21 || (_templateObject21 = (0, _taggedTemplateLiteral2["default"])(["\n const RESULTS = {};\n let VALUE,\n RESULT;\n for (const KEY in COLLECTION) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULTS[KEY] = LOGIC;\n }\n"]))); + var mapRight = template(_templateObject22 || (_templateObject22 = (0, _taggedTemplateLiteral2["default"])(["\n const LENGTH = COLLECTION.length;\n let KEY = LENGTH;\n const RESULTS = Array(LENGTH);\n for (let VALUE; --KEY >= 0;) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULTS[LENGTH - KEY - 1] = LOGIC;\n }\n"]))); + var nthLastItem = template(_templateObject23 || (_templateObject23 = (0, _taggedTemplateLiteral2["default"])(["\n COLLECTION[COLLECTION.length - COUNT]\n"]))); + var reduce = template(_templateObject24 || (_templateObject24 = (0, _taggedTemplateLiteral2["default"])(["\n let ACCUMULATED = INITIAL;\n for (let KEY = START, LENGTH = COLLECTION.length, VALUE; KEY < LENGTH; ++KEY) {\n VALUE = COLLECTION[KEY];\n BODY\n ACCUMULATED = LOGIC;\n }\n"]))); + var reduceObject = template(_templateObject25 || (_templateObject25 = (0, _taggedTemplateLiteral2["default"])(["\n let SKIP = SHOULD_SKIP,\n ACCUMULATED = INITIAL,\n VALUE;\n for (const KEY in COLLECTION) {\n VALUE = COLLECTION[KEY];\n\n if (SKIP) {\n ACCUMULATED = VALUE;\n SKIP = false;\n continue;\n }\n\n BODY\n ACCUMULATED = LOGIC;\n }\n"]))); + var reduceRight = template(_templateObject26 || (_templateObject26 = (0, _taggedTemplateLiteral2["default"])(["\n let ACCUMULATED = INITIAL;\n for (let KEY = COLLECTION.length - START, VALUE; --KEY >= START;) {\n VALUE = COLLECTION[KEY];\n BODY\n ACCUMULATED = LOGIC;\n }\n"]))); + var some = template(_templateObject27 || (_templateObject27 = (0, _taggedTemplateLiteral2["default"])(["\n let DETERMINATION = false;\n for (let KEY = 0, LENGTH = COLLECTION.length, VALUE, RESULT; KEY < LENGTH; ++KEY) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULT = LOGIC;\n\n if (RESULT) {\n DETERMINATION = true;\n break;\n }\n }\n"]))); + var someObject = template(_templateObject28 || (_templateObject28 = (0, _taggedTemplateLiteral2["default"])(["\n let DETERMINATION = false,\n VALUE,\n RESULT;\n for (const KEY in COLLECTION) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULT = LOGIC;\n\n if (RESULT) {\n DETERMINATION = true;\n break;\n }\n }\n"]))); + var someRight = template(_templateObject29 || (_templateObject29 = (0, _taggedTemplateLiteral2["default"])(["\n const DETERMINATION = false;\n for (let KEY = COLLECTION.length, VALUE, RESULT; --KEY >= 0;) {\n VALUE = COLLECTION[KEY];\n BODY\n RESULT = LOGIC;\n\n if (RESULT) {\n DETERMINATION = true;\n break;\n }\n }\n"]))); return { every: every, everyObject: everyObject, @@ -55,6 +56,7 @@ function createTemplates(_ref) { forEach: forEach, forEachObject: forEachObject, forEachRight: forEachRight, + iife: iife, localVariable: localVariable, map: map, mapObject: mapObject, diff --git a/dist/utils.js b/dist/utils.js index 079dc2ca..d9aa9a3a 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -9,6 +9,7 @@ exports.getImportedHandlerName = getImportedHandlerName; exports.getLocalName = getLocalName; exports.handleArrowFunctionExpressionUse = handleArrowFunctionExpressionUse; exports.handleInvalidUsage = handleInvalidUsage; +exports.isConditionalUsage = isConditionalUsage; exports.isMacroHandlerName = isMacroHandlerName; exports.processNestedInlineLoopMacros = processNestedInlineLoopMacros; exports.rename = rename; @@ -92,6 +93,10 @@ function handleInvalidUsage(path, handlers) { throw new _babelPluginMacros.MacroError('You cannot use a method from `inline-loops.macro` directly as a handler; please wrap it in a export function call.'); } } +function isConditionalUsage(path) { + var parentPath = path.parentPath; + return parentPath.isConditionalExpression() || parentPath.isLogicalExpression(); +} function isMacroHandlerName(handlers, name) { return !!(name && handlers[name]); } @@ -111,11 +116,35 @@ function processNestedInlineLoopMacros(path, handlers) { function rename(path, newName) { path.scope.rename(path.node.name, newName); } -function replaceOrRemove(path, replacement) { - var parentPath = path.parentPath; - if (parentPath !== null && parentPath !== void 0 && parentPath.isExpressionStatement()) { - path.remove(); +function replaceOrRemove(_ref, path, local, templates, replacement) { + var _functionParent$get; + var t = _ref.types; + var functionParent = path.getFunctionParent(); + var contents = functionParent === null || functionParent === void 0 || (_functionParent$get = functionParent.get('body')) === null || _functionParent$get === void 0 ? void 0 : _functionParent$get.get('body'); + var shouldWrapInIife = functionParent && (Array.isArray(contents) && contents.length > 1 || local.contents.length > 1 || isConditionalUsage(path)); + if (shouldWrapInIife) { + if (!t.isIdentifier(replacement, { + name: 'undefined' + })) { + local.contents.push(t.returnStatement(replacement)); + } + var iife = templates.iife({ + BODY: local.contents.flat() + }); + path.replaceWith(iife.expression); } else { - path.replaceWith(replacement); + var statement = path.getStatementParent(); + if (!statement) { + throw new _babelPluginMacros.MacroError('Could not insert contents because the statement was indeterminable.'); + } + local.contents.forEach(function (content) { + statement.insertBefore(content); + }); + var parentPath = path.parentPath; + if (parentPath !== null && parentPath !== void 0 && parentPath.isExpressionStatement()) { + path.remove(); + } else { + path.replaceWith(replacement); + } } } \ No newline at end of file diff --git a/src/handlers.ts b/src/handlers.ts index 0d8b829b..5f7cc746 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -7,7 +7,6 @@ import { ExpressionStatement, Identifier, ObjectExpression, - Statement, } from '@babel/types'; import { MacroError } from 'babel-plugin-macros'; import type { MacroParams } from 'babel-plugin-macros'; @@ -30,7 +29,6 @@ interface InjectedBodyAndLogicConfig { isReduce?: boolean; local: LocalReferences; path: Path; - statement: Path; } export function createHandlers(babel: MacroParams['babel']) { @@ -74,7 +72,6 @@ export function createHandlers(babel: MacroParams['babel']) { isReduce, local, path, - statement, }: InjectedBodyAndLogicConfig) { const body = handler.get('body') as Path; const traverseState = { @@ -106,10 +103,7 @@ export function createHandlers(babel: MacroParams['babel']) { } if (returnCount === 1) { - return { - injectedBody: body.node.body, - logic: returnValue, - }; + return { injectedBody: body.node.body, logic: returnValue }; } } @@ -119,17 +113,14 @@ export function createHandlers(babel: MacroParams['babel']) { VALUE: handler.node, }); - statement.insertBefore(localFn); + local.contents.push(localFn); const logic = t.callExpression( localFnName, getCachedFnArgs(local, isReduce), ); - return { - injectedBody: [], - logic, - }; + return { injectedBody: [], logic }; } if (handler.isFunction()) { @@ -161,7 +152,6 @@ export function createHandlers(babel: MacroParams['babel']) { function getLocalReferences( path: Path, - statement: Path, isReduce?: boolean, ): LocalReferences { const [collection, handler] = path.get('arguments'); @@ -170,6 +160,8 @@ export function createHandlers(babel: MacroParams['babel']) { throw new MacroError('Must pass both a collection and a handler'); } + const contents: LocalReferences['contents'] = []; + let localCollection = collection.node; if (!collection.isIdentifier()) { @@ -180,7 +172,7 @@ export function createHandlers(babel: MacroParams['babel']) { VALUE: collection.node, }); - statement.insertBefore(localVariable); + contents.push(localVariable); } let accumulated: Path | undefined; @@ -256,6 +248,7 @@ export function createHandlers(babel: MacroParams['babel']) { return { accumulated: localAccumulated, collection: localCollection as Identifier, + contents, key: localKey, length: localLength, value: localValue, @@ -281,24 +274,22 @@ export function createHandlers(babel: MacroParams['babel']) { processNestedInlineLoopMacros(collection, handlers); - const statement = path.getStatementParent()!; - const local = getLocalReferences(path, statement); + const local = getLocalReferences(path); const { injectedBody, logic } = getInjectedBodyAndLogic({ handler, local, path, - statement, }); const result = path.scope.generateUidIdentifier('result'); const determination = path.scope.generateUidIdentifier('determination'); - let forLoop; + let loop; switch (type) { case 'every-left': - forLoop = templates.every({ + loop = templates.every({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -311,7 +302,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'some-left': - forLoop = templates.some({ + loop = templates.some({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -324,7 +315,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'every-right': - forLoop = templates.everyRight({ + loop = templates.everyRight({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -336,7 +327,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'some-right': - forLoop = templates.someRight({ + loop = templates.someRight({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -348,7 +339,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'every-object': - forLoop = templates.everyObject({ + loop = templates.everyObject({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -360,7 +351,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'some-object': - forLoop = templates.someObject({ + loop = templates.someObject({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -375,9 +366,9 @@ export function createHandlers(babel: MacroParams['babel']) { throw new MacroError(`Invalid type ${type} provided`); } - statement.insertBefore(forLoop); + local.contents.push(loop); - replaceOrRemove(path, determination); + replaceOrRemove(babel, path, local, templates, determination); }; } @@ -400,24 +391,22 @@ export function createHandlers(babel: MacroParams['babel']) { processNestedInlineLoopMacros(collection, handlers); - const statement = path.getStatementParent()!; - const local = getLocalReferences(path, statement); + const local = getLocalReferences(path); const { injectedBody, logic } = getInjectedBodyAndLogic({ handler, local, path, - statement, }); const result = path.scope.generateUidIdentifier('result'); const match = path.scope.generateUidIdentifier('match'); - let forLoop; + let loop; switch (type) { case 'find-left': - forLoop = templates.find({ + loop = templates.find({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -430,7 +419,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'find-index': - forLoop = templates.findIndex({ + loop = templates.findIndex({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -443,7 +432,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'find-last': - forLoop = templates.findLast({ + loop = templates.findLast({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -455,7 +444,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'find-last-index': - forLoop = templates.findLastIndex({ + loop = templates.findLastIndex({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -467,7 +456,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'find-object': - forLoop = templates.findObject({ + loop = templates.findObject({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -479,7 +468,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'find-key': - forLoop = templates.findKey({ + loop = templates.findKey({ BODY: injectedBody, RESULT: result, COLLECTION: local.collection, @@ -494,9 +483,9 @@ export function createHandlers(babel: MacroParams['babel']) { throw new MacroError(`Invalid type ${type} provided`); } - statement.insertBefore(forLoop); + local.contents.push(loop); - replaceOrRemove(path, match); + replaceOrRemove(babel, path, local, templates, match); }; } @@ -519,8 +508,7 @@ export function createHandlers(babel: MacroParams['babel']) { processNestedInlineLoopMacros(collection, handlers); - const statement = path.getStatementParent()!; - const local = getLocalReferences(path, statement); + const local = getLocalReferences(path); const localResults = getLocalName(path, 'results'); const isForEach = type.includes('for-each'); const result = path.scope.generateUidIdentifier('result'); @@ -530,14 +518,13 @@ export function createHandlers(babel: MacroParams['babel']) { isForEach, local, path, - statement, }); - let forLoop; + let loop; switch (type) { case 'map-left': - forLoop = templates.map({ + loop = templates.map({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -549,7 +536,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'filter-left': - forLoop = templates.filter({ + loop = templates.filter({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -562,7 +549,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'flat-map-left': - forLoop = templates.flatMap({ + loop = templates.flatMap({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -575,7 +562,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'for-each-left': - forLoop = templates.forEach({ + loop = templates.forEach({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -585,7 +572,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'map-right': - forLoop = templates.mapRight({ + loop = templates.mapRight({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -597,7 +584,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'filter-right': - forLoop = templates.filterRight({ + loop = templates.filterRight({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -609,7 +596,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'flat-map-right': - forLoop = templates.flatMapRight({ + loop = templates.flatMapRight({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -621,7 +608,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'for-each-right': - forLoop = templates.forEachRight({ + loop = templates.forEachRight({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -630,7 +617,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'map-object': - forLoop = templates.mapObject({ + loop = templates.mapObject({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -642,7 +629,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'filter-object': - forLoop = templates.filterObject({ + loop = templates.filterObject({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -654,7 +641,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'for-each-object': - forLoop = templates.forEachObject({ + loop = templates.forEachObject({ BODY: injectedBody, COLLECTION: local.collection, KEY: local.key, @@ -666,10 +653,13 @@ export function createHandlers(babel: MacroParams['babel']) { throw new MacroError(`Invalid type ${type} provided`); } - statement.insertBefore(forLoop); + local.contents.push(loop); replaceOrRemove( + babel, path, + local, + templates, isForEach ? t.identifier('undefined') : localResults, ); }; @@ -694,15 +684,13 @@ export function createHandlers(babel: MacroParams['babel']) { processNestedInlineLoopMacros(collection, handlers); - const statement = path.getStatementParent()!; - const local = getLocalReferences(path, statement, true); + const local = getLocalReferences(path, true); const { injectedBody, logic } = getInjectedBodyAndLogic({ handler, isReduce: true, local, path, - statement, }); let initial; @@ -724,11 +712,11 @@ export function createHandlers(babel: MacroParams['babel']) { const start = t.numericLiteral(initialValue ? 0 : 1); - let forLoop; + let loop; switch (type) { case 'left': - forLoop = templates.reduce({ + loop = templates.reduce({ ACCUMULATED: local.accumulated, BODY: injectedBody, COLLECTION: local.collection, @@ -742,7 +730,7 @@ export function createHandlers(babel: MacroParams['babel']) { break; case 'right': - forLoop = templates.reduceRight({ + loop = templates.reduceRight({ ACCUMULATED: local.accumulated, BODY: injectedBody, COLLECTION: local.collection, @@ -758,7 +746,7 @@ export function createHandlers(babel: MacroParams['babel']) { const skip = path.scope.generateUidIdentifier('skip'); const shouldSkip = t.booleanLiteral(!initialValue); - forLoop = templates.reduceObject({ + loop = templates.reduceObject({ ACCUMULATED: local.accumulated, BODY: injectedBody, COLLECTION: local.collection, @@ -776,9 +764,9 @@ export function createHandlers(babel: MacroParams['babel']) { throw new MacroError(`Invalid type ${type} provided`); } - statement.insertBefore(forLoop); + local.contents.push(loop); - replaceOrRemove(path, local.accumulated); + replaceOrRemove(babel, path, local, templates, local.accumulated); }; } diff --git a/src/templates.ts b/src/templates.ts index 73390dc0..8a10d5c5 100644 --- a/src/templates.ts +++ b/src/templates.ts @@ -222,6 +222,12 @@ export function createTemplates({ template }: MacroParams['babel']) { const LOCAL = VALUE; `; + const iife = template` + (() => { + BODY + })(); +`; + const map = template` const LENGTH = COLLECTION.length; const RESULTS = Array(LENGTH); @@ -356,6 +362,7 @@ export function createTemplates({ template }: MacroParams['babel']) { forEach, forEachObject, forEachRight, + iife, localVariable, map, mapObject, diff --git a/src/types.ts b/src/types.ts index 34c7fb30..55276961 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,10 @@ import type { NodePath as Path } from '@babel/core'; -import type { Identifier } from '@babel/types'; +import type { Identifier, Statement } from '@babel/types'; export interface LocalReferences { accumulated: Identifier; collection: Identifier; + contents: Array; key: Identifier; length: Identifier; value: Identifier; diff --git a/src/utils.ts b/src/utils.ts index b5ffe940..5fda0960 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,13 +1,14 @@ import type { NodePath as Path } from '@babel/core'; import { CallExpression, + Expression, Identifier, - Node, ObjectProperty, StringLiteral, } from '@babel/types'; -import { MacroError } from 'babel-plugin-macros'; +import { MacroError, MacroParams } from 'babel-plugin-macros'; import { Handlers, LocalReferences } from './types'; +import { createTemplates } from 'templates'; export function getCachedFnArgs(local: LocalReferences, isReduce?: boolean) { return isReduce @@ -127,6 +128,14 @@ export function handleInvalidUsage( } } +export function isConditionalUsage(path: Path): boolean { + const parentPath = path.parentPath; + + return ( + parentPath.isConditionalExpression() || parentPath.isLogicalExpression() + ); +} + export function isMacroHandlerName( handlers: Handlers, name: string | undefined, @@ -156,12 +165,51 @@ export function rename(path: Path, newName?: string) { path.scope.rename(path.node.name, newName); } -export function replaceOrRemove(path: Path, replacement: Node) { - const parentPath = path.parentPath; +export function replaceOrRemove( + { types: t }: MacroParams['babel'], + path: Path, + local: LocalReferences, + templates: ReturnType, + replacement: Expression, +) { + const functionParent = path.getFunctionParent(); + const contents = functionParent?.get('body')?.get('body'); + + const shouldWrapInIife = + functionParent && + ((Array.isArray(contents) && contents.length > 1) || + local.contents.length > 1 || + isConditionalUsage(path)); + + if (shouldWrapInIife) { + if (!t.isIdentifier(replacement, { name: 'undefined' })) { + local.contents.push(t.returnStatement(replacement)); + } - if (parentPath?.isExpressionStatement()) { - path.remove(); + const iife = templates.iife({ + BODY: local.contents.flat(), + }) as { expression: Expression }; + + path.replaceWith(iife.expression); } else { - path.replaceWith(replacement); + const statement = path.getStatementParent(); + + if (!statement) { + throw new MacroError( + 'Could not insert contents because the statement was indeterminable.', + ); + } + + local.contents.forEach((content) => { + statement.insertBefore(content); + }); + + const parentPath = path.parentPath; + + if (parentPath?.isExpressionStatement()) { + path.remove(); + } else { + path.replaceWith(replacement); + } } }