diff options
Diffstat (limited to 'babel-plugin-async-to-promises/lib/ifrefactor.js')
-rw-r--r-- | babel-plugin-async-to-promises/lib/ifrefactor.js | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/babel-plugin-async-to-promises/lib/ifrefactor.js b/babel-plugin-async-to-promises/lib/ifrefactor.js new file mode 100644 index 0000000..904d77d --- /dev/null +++ b/babel-plugin-async-to-promises/lib/ifrefactor.js @@ -0,0 +1,158 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SecondPassIfVisitor = exports.FirstPassIfVisitor = undefined; + +var _babelTypes = require('babel-types'); + +var _utils = require('./utils'); + +var _jsExtend = require('js-extend'); + +var FirstPassIfVisitor = exports.FirstPassIfVisitor = { + IfStatement: function IfStatement(path) { + var _this = this; + + var node = path.node; + + (0, _babelTypes.ensureBlock)(node, 'consequent'); + if (node.alternate) { + (0, _babelTypes.ensureBlock)(node, 'alternate'); + } + if (node.consequent.body.some(_babelTypes.isIfStatement) && containsReturnOrAwait(path)) { + (function () { + // flatten if statements. There are two ways to reach d() in the below. + // if a() && !b(), and if !a() && !b(). That's problematic during the + // promise conversion. + // + // if (a()) { + // if (b()) { + // return c(); + // } + // } + // return d(); + // + // this becomes instead: + // + // var _test = a(); + // if (_test && b()) { + // return c(); + // } + // return d(); + // + // which is better, but not quite the result we want yet. See for that + // the BlockStatement handler in the other IfRefactorVisitor below. + + var testID = (0, _babelTypes.identifier)(path.scope.generateUid('test')); + _this.addVarDecl(testID); + var block = [(0, _utils.assign)(testID, node.test)]; + + var stillToAdd = []; + var clearQueue = function clearQueue() { + if (stillToAdd.length) { + block.push((0, _babelTypes.ifStatement)(testID, (0, _babelTypes.blockStatement)(stillToAdd))); + stillToAdd = []; + } + }; + node.consequent.body.forEach(function (stmt) { + if ((0, _babelTypes.isIfStatement)(stmt)) { + clearQueue(); + stmt.test = (0, _babelTypes.logicalExpression)('&&', testID, stmt.test); + if (stmt.alternate) { + stmt.alternate = (0, _babelTypes.blockStatement)([(0, _babelTypes.ifStatement)(testID, stmt.alternate)]); + } + block.push(stmt); + } else { + stillToAdd.push(stmt); + } + }); + clearQueue(); + extendElse(block[block.length - 1], (node.alternate || {}).body || []); + path.replaceWithMultiple(block); + })(); + } + } +}; + +var containsReturnOrAwait = (0, _utils.matcher)(['ReturnStatement', 'AwaitExpression'], _utils.NoSubFunctionsVisitor); + +var SecondPassIfVisitor = exports.SecondPassIfVisitor = (0, _jsExtend.extend)({ + IfStatement: function IfStatement(path) { + var alt = path.node.alternate; + if (!path.node.consequent.body.length && alt && alt.body.length) { + path.node.consequent = path.node.alternate; + path.node.alternate = null; + path.node.test = (0, _babelTypes.unaryExpression)('!', path.node.test); + } + var ifContainsAwait = (0, _utils.containsAwait)(path.get('consequent')); + var elseContainsAwait = (0, _utils.containsAwait)(path.get('alternate')); + + var node = path.node; + + if (ifContainsAwait) { + node.consequent = wrapIfBranch(node.consequent); + } + if (elseContainsAwait) { + node.alternate = wrapIfBranch(node.alternate); + } + if (ifContainsAwait || elseContainsAwait) { + path.replaceWith((0, _babelTypes.awaitExpression)((0, _utils.wrapFunction)((0, _babelTypes.blockStatement)([node])))); + } + }, + BlockStatement: function BlockStatement(path) { + // Converts + // + // var _test = a(); + // if (_test && b()) { + // return c(); + // } + // return d(); + // + // into: + // + // var _test = a(); + // if (_test && b()) { + // return c(); + // } else { + // return d(); + // } + // + // ... which has at every point in time only two choices: returning + // directly out of the function, or continueing on. That's what's required + // for a nice conversion to Promise chains. + for (var i = 0; i < path.node.body.length; i++) { + var subNode = path.node.body[i]; + if ((0, _babelTypes.isReturnStatement)(subNode)) { + // remove everything in the block after the return - it's never going + // to be executed anyway. + path.node.body.splice(i + 1); + } + if (!(0, _babelTypes.isIfStatement)(subNode)) { + continue; + } + var lastStmt = subNode.consequent.body[subNode.consequent.body.length - 1]; + if (!(0, _babelTypes.isReturnStatement)(lastStmt)) { + continue; + } + var remainder = path.node.body.splice(i + 1); + if (!lastStmt.argument) { + // chop off the soon to be useless return statement + subNode.consequent.body.splice(-1); + } + extendElse(subNode, remainder); + } + } +}, _utils.NoSubFunctionsVisitor); + +var wrapIfBranch = function wrapIfBranch(branch) { + return (0, _babelTypes.blockStatement)([(0, _babelTypes.returnStatement)((0, _utils.wrapFunction)(branch))]); +}; + +function extendElse(ifStmt, extraBody) { + var body = ((ifStmt.alternate || {}).body || []).concat(extraBody); + if (body.length) { + ifStmt.alternate = (0, _babelTypes.blockStatement)(body); + } +}
\ No newline at end of file |