aboutsummaryrefslogtreecommitdiff
path: root/babel-plugin-async-to-promises/lib
diff options
context:
space:
mode:
Diffstat (limited to 'babel-plugin-async-to-promises/lib')
-rw-r--r--babel-plugin-async-to-promises/lib/ifrefactor.js158
-rw-r--r--babel-plugin-async-to-promises/lib/index.js123
-rw-r--r--babel-plugin-async-to-promises/lib/looprefactor.js257
-rw-r--r--babel-plugin-async-to-promises/lib/promisechain.js167
-rw-r--r--babel-plugin-async-to-promises/lib/refactor.js285
-rw-r--r--babel-plugin-async-to-promises/lib/utils.js55
6 files changed, 1045 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
diff --git a/babel-plugin-async-to-promises/lib/index.js b/babel-plugin-async-to-promises/lib/index.js
new file mode 100644
index 0000000..9d7952b
--- /dev/null
+++ b/babel-plugin-async-to-promises/lib/index.js
@@ -0,0 +1,123 @@
+'use strict';
+
+var _babelHelperHoistVariables = require('babel-helper-hoist-variables');
+
+var _babelHelperHoistVariables2 = _interopRequireDefault(_babelHelperHoistVariables);
+
+var _babelTypes = require('babel-types');
+
+var _refactor = require('./refactor');
+
+var _promisechain = require('./promisechain');
+
+var _promisechain2 = _interopRequireDefault(_promisechain);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+module.exports = function () {
+ return {
+ visitor: WrapperVisitor,
+ manipulateOptions: function manipulateOptions(opts, parserOpts) {
+ parserOpts.plugins.push('asyncFunctions');
+ }
+ };
+};
+
+var depth = 0;
+var respID = void 0,
+ errID = void 0;
+
+var WrapperVisitor = {
+ // Because only ES5 is really supported, force this plugin to run as late as
+ // possible. At least the normal (es2015 preset) transforms have happened by
+ // then.
+ Program: {
+ exit: function exit(path) {
+ respID = path.scope.generateUid('resp');
+ errID = path.scope.generateUid('err');
+ path.traverse(MainVisitor);
+ // inline functions
+ path.traverse(InliningVisitor);
+ }
+ }
+};
+
+var MainVisitor = {
+ Function: {
+ enter: function enter(path) {
+ depth++;
+ var node = path.node;
+
+ if (node.async) {
+ (function () {
+ var decls = [];
+ var addVarDecl = function addVarDecl(id) {
+ return decls.push((0, _babelTypes.variableDeclarator)(id));
+ };
+ // hoist variables
+ (0, _babelHelperHoistVariables2.default)(path, addVarDecl);
+
+ // info gathering for this/arguments during the refactoring
+ var argumentsID = (0, _babelTypes.identifier)(path.scope.generateUid('arguments'));
+ var used = { argumentsID: false };
+
+ var newBody = [];
+ var addFunctionDecl = function addFunctionDecl(func) {
+ return newBody.push(func);
+ };
+
+ // refactor code
+ var args = { argumentsID: argumentsID, used: used, addVarDecl: addVarDecl, addFunctionDecl: addFunctionDecl, respID: respID, errID: errID };
+ path.traverse(_refactor.RefactorVisitor, args);
+ // add this/arguments vars if necessary
+ if (used.argumentsID) {
+ decls.push((0, _babelTypes.variableDeclarator)(argumentsID, (0, _babelTypes.identifier)('arguments')));
+ }
+ if (decls.length) {
+ newBody.push((0, _babelTypes.variableDeclaration)('var', decls));
+ }
+
+ // transformations that can only be done after all others.
+ path.traverse(_refactor.IfRefactorVisitor);
+
+ // build the promise chain
+ var chain = new _promisechain2.default(depth > 1, node.dirtyAllowed, respID, errID);
+ chain.add(path.get('body.body'));
+ newBody.push((0, _babelTypes.returnStatement)(chain.toAST()));
+
+ // combine all the newly generated stuff.
+ node.body = (0, _babelTypes.blockStatement)(newBody);
+ node.async = false;
+ })();
+ }
+ },
+ exit: function exit() {
+ depth--;
+ }
+ }
+};
+
+var InliningVisitor = {
+ BlockStatement: function BlockStatement(path) {
+ // inline blocks. Included because babel-template otherwise creates empty
+ // blocks.
+ if ((0, _babelTypes.isBlockStatement)(path.parent)) {
+ path.replaceWithMultiple(path.node.body);
+ }
+ },
+ ReturnStatement: function ReturnStatement(path) {
+ // return function () { ...body... }() becomes: ...body...
+ var call = path.node.argument;
+ var inlineable = (0, _babelTypes.isCallExpression)(call) && !call.arguments.length && (0, _babelTypes.isFunctionExpression)(call.callee) && !call.callee.id && !call.callee.params.length && (0, _babelTypes.isBlockStatement)(call.callee.body) && !Object.keys(path.get('argument.callee').scope.bindings).length;
+ if (inlineable) {
+ path.replaceWithMultiple(call.callee.body.body);
+ }
+ },
+ CallExpression: function CallExpression(path) {
+ // function () { return x; }() becomes x
+ var inlineable = !path.node.arguments.length && (0, _babelTypes.isFunctionExpression)(path.node.callee) && !path.node.callee.id && !path.node.callee.params.length && (0, _babelTypes.isBlockStatement)(path.node.callee.body) && path.node.callee.body.body.length === 1 && (0, _babelTypes.isReturnStatement)(path.node.callee.body.body[0]) && path.node.callee.body.body[0].argument;
+ if (inlineable) {
+ path.replaceWith(path.node.callee.body.body[0].argument);
+ }
+ }
+}; \ No newline at end of file
diff --git a/babel-plugin-async-to-promises/lib/looprefactor.js b/babel-plugin-async-to-promises/lib/looprefactor.js
new file mode 100644
index 0000000..226dbc0
--- /dev/null
+++ b/babel-plugin-async-to-promises/lib/looprefactor.js
@@ -0,0 +1,257 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _babelTypes = require('babel-types');
+
+var _babelTemplate = require('babel-template');
+
+var _babelTemplate2 = _interopRequireDefault(_babelTemplate);
+
+var _jsExtend = require('js-extend');
+
+var _utils = require('./utils');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+exports.default = {
+ LabeledStatement: {
+ // Babel seems to auto-remove labels from the AST if they don't make sense
+ // in a position. That makes it hard to keep track of if you're in a loop
+ // with label. So we move the label onto the node itself, and handle it
+ // manually (at least, if we're touching the loop, i.e. if it has an await
+ // somewhere inside).
+
+ enter: function enter(path) {
+ if ((0, _utils.containsAwait)(path)) {
+ path.node.body.loopLabel = path.node.label;
+ }
+ }
+ },
+ DoWhileStatement: function DoWhileStatement(path) {
+ // converts
+ //
+ // do {
+ // newBody;
+ // } while (node.test)
+ //
+ // into:
+ //
+ // await async function _recursive() {
+ // newBody;
+ // if (node.test) {
+ // return await _recursive();
+ // }
+ // }()
+
+ refactorLoop(path, false, this.addVarDecl, function (functionID) {
+ var continueBlock = (0, _babelTypes.blockStatement)([continueStatementEquiv(functionID)]);
+ path.node.body.body.push((0, _babelTypes.ifStatement)(path.node.test, continueBlock));
+ path.replaceWith(recursiveWrapFunction(functionID, path.node.body));
+ });
+ },
+ WhileStatement: function WhileStatement(path) {
+ // converts
+ //
+ // while (node.test) {
+ // newBody;
+ // }
+ //
+ // into:
+ //
+ // await async function _recursive() {
+ // if (node.test) {
+ // newBody;
+ // return await _recursive();
+ // }
+ // }()
+
+ refactorLoop(path, false, this.addVarDecl, function (functionID) {
+ path.node.body.body.push(continueStatementEquiv(functionID));
+ var body = (0, _babelTypes.blockStatement)([(0, _babelTypes.ifStatement)(path.node.test, path.node.body)]);
+
+ path.replaceWith(recursiveWrapFunction(functionID, body));
+ });
+ },
+ ForStatement: function ForStatement(path) {
+ // converts
+ //
+ // for(node.init, node.test, node.update) {
+ // newBody;
+ // }
+ //
+ // into:
+ //
+ // {
+ // node.init;
+ // await async function _recursive() {
+ // if (node.test) {
+ // newBody;
+ // node.update;
+ // return await _recursive();
+ // }
+ // }()
+ // }
+ ifShouldRefactorLoop(path, (0, _utils.containsAwait)(path.get('update')), function () {
+ path.node.body.body.push((0, _babelTypes.expressionStatement)(path.node.update));
+ path.replaceWithMultiple([(0, _babelTypes.expressionStatement)(path.node.init), (0, _babelTypes.whileStatement)(path.node.test, path.node.body)]);
+ });
+ },
+ ForInStatement: function ForInStatement(path) {
+ var _this = this;
+
+ // converts
+ // for (node.left in node.right) {
+ // newBody;
+ // }
+ //
+ // info:
+ //
+ // var _items = [];
+ // for (var _item in node.right) {
+ // _items.push(_item);
+ // }
+ // _items.reverse();
+ // await async function _recursive() {
+ // if (_items.length) {
+ // node.left = _items.pop();
+ // node.body;
+ // return await _recursive();
+ // }
+ // }
+
+ ifShouldRefactorLoop(path, false, function () {
+ var KEYS = (0, _babelTypes.identifier)(path.scope.generateUid('keys'));
+ var OBJECT = (0, _babelTypes.identifier)(path.scope.generateUid('object'));
+ _this.addVarDecl(KEYS);
+ _this.addVarDecl(OBJECT);
+ path.replaceWithMultiple(forInEquiv({
+ KEYS: KEYS, OBJECT: OBJECT,
+ KEY: (0, _babelTypes.identifier)(path.scope.generateUid('key')),
+ LEFT: path.node.left,
+ RIGHT: path.node.right,
+ BODY: path.node.body
+ }));
+ });
+ }
+};
+
+
+var forInEquiv = (0, _babelTemplate2.default)('\n OBJECT = RIGHT;\n KEYS = [];\n for (var KEY in OBJECT) {\n KEYS.push(KEY);\n }\n KEYS.reverse();\n while(KEYS.length) {\n LEFT = KEYS.pop();\n if (LEFT in OBJECT) {\n BODY;\n }\n }\n');
+
+function recursiveWrapFunction(functionID, body) {
+ var func = (0, _utils.wrapFunction)(body);
+ func.callee.id = functionID;
+
+ return (0, _utils.awaitStatement)(func);
+}
+
+function insideAwaitContainingLabel(path) {
+ // walks the path tree to check if inside a label that also contains an await
+ // statement. (See also the LabeledStatement visitor.)
+ do {
+ if (path.node.loopLabel) {
+ return true;
+ }
+ } while (path = path.parentPath);
+
+ // no such label found
+ return false;
+}
+
+function ifShouldRefactorLoop(path, extraCheck, handler) {
+ // ensureBlock here is convenient, but has nothing to do with the method name
+ (0, _babelTypes.ensureBlock)(path.node);
+
+ if (extraCheck || insideAwaitContainingLabel(path) || loopContainsAwait(path.get('body'))) {
+ handler();
+ }
+}
+
+var NoSubLoopsVisitor = {
+ Loop: function Loop(path) {
+ path.skip();
+ }
+};
+
+// does the current loop (no subloops) contain an await statement?
+var loopContainsAwait = (0, _utils.matcher)(['AwaitExpression'], (0, _jsExtend.extend)({}, _utils.NoSubFunctionsVisitor, NoSubLoopsVisitor));
+
+function refactorLoop(path, extraCheck, addVarDecl, handler) {
+ ifShouldRefactorLoop(path, extraCheck, function () {
+ // gather info about the function & fix up its body (break + continue
+ // statements)
+ var label = path.node.loopLabel;
+ var functionID = label || (0, _babelTypes.identifier)(path.scope.generateUid('recursive'));
+ var info = { functionID: functionID };
+ path.get('body').traverse(BreakContinueReplacementVisitor, info);
+ // actual conversion
+ handler(functionID);
+
+ // if containing a return *or* a break statement that doesn't control the
+ // own loop (references a label of another loop), add:
+ //
+ // .then(function (_resp) {
+ // _temp = _resp;
+ // if (_temp !== _recursive) {
+ // return _temp;
+ // }
+ // });
+ if (info.addReturnHandler) {
+ var tmp = (0, _babelTypes.identifier)(path.scope.generateUid('temp'));
+ addVarDecl(tmp);
+ path.node.loopLabel = label;
+ path.replaceWithMultiple(loopReturnHandler({ TMP: tmp, BASE: path.node, FUNC: functionID }));
+ }
+ });
+}
+
+var loopReturnHandler = (0, _babelTemplate2.default)('\n TMP = BASE\n if (_temp !== FUNC) {\n return _temp;\n }\n');
+
+var continueStatementEquiv = function continueStatementEquiv(funcID) {
+ // continue label; -> return await label();
+ var stmt = (0, _babelTypes.returnStatement)((0, _babelTypes.awaitExpression)((0, _babelTypes.callExpression)(funcID, [])));
+ // not a 'real' return
+ stmt.noHandlerRequired = true;
+ return stmt;
+};
+
+var BreakContinueReplacementVisitor = (0, _jsExtend.extend)({
+ ReturnStatement: function ReturnStatement(path) {
+ if (!path.node.noHandlerRequired && path.node.argument) {
+ // if a return statement added by the user - and actually returning
+ // something, we need to add a return handler later.
+ this.addReturnHandler = true;
+ }
+ },
+
+ // replace continue/break with their recursive equivalents
+ BreakStatement: function BreakStatement(path) {
+ // a break statement is replaced by returning the name of the loop function
+ // that should be broken. It's a convenient unique value.
+ //
+ // So: break; becomes return _recursive;
+ //
+ // and break myLabel; becomes return myLabel;
+
+ var label = getLabel(path, this.functionID);
+
+ var returnStmt = (0, _babelTypes.returnStatement)(getLabel(path, this.functionID));
+ if (label === this.functionID) {
+ // only if this controls the current loop, a return handler is unnecessary
+ returnStmt.noHandlerRequired = true;
+ }
+ path.replaceWith(returnStmt);
+ },
+ ContinueStatement: function ContinueStatement(path) {
+ // see break, with the difference that the function is called (and thus)
+ // executed next
+ path.replaceWith(continueStatementEquiv(getLabel(path, this.functionID)));
+ }
+}, _utils.NoSubFunctionsVisitor, NoSubLoopsVisitor);
+
+var getLabel = function getLabel(path, functionID) {
+ return path.node.label || functionID;
+}; \ No newline at end of file
diff --git a/babel-plugin-async-to-promises/lib/promisechain.js b/babel-plugin-async-to-promises/lib/promisechain.js
new file mode 100644
index 0000000..d06f9ef
--- /dev/null
+++ b/babel-plugin-async-to-promises/lib/promisechain.js
@@ -0,0 +1,167 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _babelTypes = require('babel-types');
+
+var _jsExtend = require('js-extend');
+
+var _utils = require('./utils');
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var PromiseChain = function () {
+ // add, addCatch and addFinally were designed to be called only one time each
+ // at most. Call them more at your own risk.
+ //
+ // addCatch() and addFinally() are not guaranteed to handle return values
+ // correctly. FIXME.
+
+ function PromiseChain(inner, dirtyAllowed, respName, errName) {
+ _classCallCheck(this, PromiseChain);
+
+ this._inner = inner;
+ this._dirtyAllowed = dirtyAllowed;
+ this._respName = respName;
+ this._errName = errName;
+
+ this._ast = (0, _babelTypes.callExpression)((0, _babelTypes.memberExpression)((0, _babelTypes.identifier)('Promise'), (0, _babelTypes.identifier)('resolve')), []);
+ }
+
+ _createClass(PromiseChain, [{
+ key: 'add',
+ value: function add(block) {
+ var _this = this;
+
+ if (!block.length) {
+ return;
+ }
+ var current = this._addLink('then', []);
+ block.forEach(function (path) {
+ var awaitInfos = [];
+ path.traverse(PromisifyPrepVisitor, { awaitInfos: awaitInfos, respName: _this._respName });
+
+ awaitInfos.forEach(function (awaitInfo) {
+ current.body.push((0, _babelTypes.returnStatement)(awaitInfo.arg));
+ var params = awaitInfo.passID ? [(0, _babelTypes.identifier)(_this._respName)] : [];
+ current = _this._addLink('then', params);
+ });
+ if (path.node) {
+ current.body.push(path.node);
+ }
+ });
+ }
+ }, {
+ key: '_addLink',
+ value: function _addLink(type, params, secondParams) {
+ this._cleanup();
+
+ var current = { body: [] };
+ var handlerBody = (0, _babelTypes.blockStatement)(current.body);
+ var handlers = [(0, _babelTypes.arrowFunctionExpression)(params, handlerBody, false)];
+
+ if (secondParams) {
+ current.secondBody = [];
+ var secondHandlerBody = (0, _babelTypes.blockStatement)(current.secondBody);
+ handlers.push((0, _babelTypes.arrowFunctionExpression)(secondParams, secondHandlerBody, false));
+ }
+
+ var method = (0, _babelTypes.memberExpression)(this._ast, (0, _babelTypes.identifier)(type));
+ this._ast = (0, _babelTypes.callExpression)(method, handlers);
+
+ return current;
+ }
+ }, {
+ key: '_cleanup',
+ value: function _cleanup() {
+ // if resolving to non-undefined when there is no return is allowed, and
+ // the last part of the chain is .then(function () {}), then chop off that
+ // part
+ var chopOff = this._dirtyAllowed && this._ast.callee.property.name === 'then' && this._ast.arguments.length === 1 && !this._ast.arguments[0].body.body.length;
+ if (chopOff) {
+ this._ast = this._ast.callee.object;
+ }
+ }
+ }, {
+ key: 'addCatch',
+ value: function addCatch(block, errID) {
+ var current = this._addLink('catch', [errID]);
+ var catchChain = this._subChain();
+ catchChain.add(block);
+ current.body.push((0, _babelTypes.returnStatement)(catchChain.toAST()));
+ }
+ }, {
+ key: '_subChain',
+ value: function _subChain() {
+ return new PromiseChain(true, true, this._respName, this._errName);
+ }
+ }, {
+ key: 'addFinally',
+ value: function addFinally(block) {
+ var errID = (0, _babelTypes.identifier)(this._errName);
+ var current = this._addLink('then', [], [errID]);
+
+ var finallyChain = this._subChain();
+
+ // disable optimalizations
+ finallyChain._inner = false;
+ finallyChain._dirtyAllowed = false;
+ finallyChain.add(block);
+ var secondAST = (0, _babelTypes.cloneDeep)(finallyChain.toAST());
+ // smuggle in the throw statement
+ secondAST.arguments[0].body.body.push((0, _babelTypes.throwStatement)(errID));
+ current.secondBody.push((0, _babelTypes.returnStatement)(secondAST));
+
+ // re-enable optimalizations
+ finallyChain._inner = true;
+ finallyChain._dirtyAllowed = true;
+ var ast = (0, _babelTypes.returnStatement)(finallyChain.toAST());
+ current.body.push(ast);
+ }
+ }, {
+ key: 'toAST',
+ value: function toAST() {
+ this._cleanup();
+
+ var callee = this._ast.callee.object.callee;
+ if (this._inner && callee && callee.object.name === 'Promise') {
+ // only one handler to the promise - because we're in an inner function
+ // there's no reason to wrap the handler in promise code. Convenienly,
+ // such a handler is inlineable later on.
+ //
+ // Summary:
+ // ``Promise.resolve().then(function () {...})``
+ // becomes
+ // ``function () {...}()``
+ return (0, _babelTypes.callExpression)(this._ast.arguments[0], []);
+ }
+ return this._ast;
+ }
+ }]);
+
+ return PromiseChain;
+}();
+
+exports.default = PromiseChain;
+
+
+var PromisifyPrepVisitor = (0, _jsExtend.extend)({
+ AwaitExpression: {
+ exit: function exit(path) {
+ // exit so awaits are evaluated inside out if there are multiple in
+ // the expression
+ var info = { arg: path.node.argument };
+ if ((0, _babelTypes.isExpressionStatement)(path.parent)) {
+ path.remove();
+ } else {
+ info.passID = true;
+ path.replaceWith((0, _babelTypes.identifier)(this.respName));
+ }
+ this.awaitInfos.push(info);
+ }
+ }
+}, _utils.NoSubFunctionsVisitor); \ No newline at end of file
diff --git a/babel-plugin-async-to-promises/lib/refactor.js b/babel-plugin-async-to-promises/lib/refactor.js
new file mode 100644
index 0000000..608eeec
--- /dev/null
+++ b/babel-plugin-async-to-promises/lib/refactor.js
@@ -0,0 +1,285 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.RefactorVisitor = exports.IfRefactorVisitor = undefined;
+
+var _babelTypes = require('babel-types');
+
+var _jsExtend = require('js-extend');
+
+var _promisechain = require('./promisechain');
+
+var _promisechain2 = _interopRequireDefault(_promisechain);
+
+var _utils = require('./utils');
+
+var _ifrefactor = require('./ifrefactor');
+
+var _looprefactor = require('./looprefactor');
+
+var _looprefactor2 = _interopRequireDefault(_looprefactor);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var IfRefactorVisitor = exports.IfRefactorVisitor = _ifrefactor.SecondPassIfVisitor;
+
+var RefactorVisitor = exports.RefactorVisitor = (0, _jsExtend.extend)({
+ AwaitExpression: function AwaitExpression(path) {
+ // ``return await x`` becomes just ``return x``
+ if ((0, _babelTypes.isReturnStatement)(path.parent)) {
+ path.replaceWith(path.node.argument);
+ }
+ },
+ BinaryExpression: function BinaryExpression(path) {
+ // a() + await b
+ //
+ // ->
+ //
+ // _temp = a(), _temp + await b
+ //
+ // to make sure the execution order is correct. This provides a nice trick:
+ // if you don't care about evaluation order and have one await-ed item in
+ // your binary expression, put it on the left side of the operator.
+
+ if ((0, _utils.containsAwait)(path.get('right')) && !path.node.left.isTemp) {
+ var tmp = (0, _babelTypes.identifier)(path.scope.generateUid('temp'));
+ tmp.isTemp = true;
+ this.addVarDecl(tmp);
+ var assignment = (0, _utils.assign)(tmp, path.node.left);
+ path.node.left = tmp;
+ insertBefore(path, assignment);
+ }
+ },
+ ArrayExpression: function ArrayExpression(path) {
+ // [a(), await b()]
+ //
+ // ->
+ //
+ // await Promise.all([
+ // function () {return a();}(),
+ // function () {return await b();}()
+ // ])
+ //
+ // (which is optimized away to:)
+ //
+ // await Promise.all([a(), b()])
+
+ if (path.get('elements').slice(1).some(_utils.containsAwait)) {
+ var elements = path.node.elements.map(function (element) {
+ return (0, _utils.wrapFunction)((0, _babelTypes.blockStatement)([(0, _babelTypes.returnStatement)(element)]));
+ });
+ var promiseAll = (0, _babelTypes.memberExpression)((0, _babelTypes.identifier)('Promise'), (0, _babelTypes.identifier)('all'));
+ path.replaceWith((0, _babelTypes.awaitExpression)((0, _babelTypes.callExpression)(promiseAll, [(0, _babelTypes.arrayExpression)(elements)])));
+ }
+ },
+ CallExpression: function CallExpression(path) {
+ var _this = this;
+
+ // call(a(), await b())
+ //
+ // ->
+ //
+ // _temp = [a(), await b()], call(_temp[0], _temp[1])
+
+ if (path.get('arguments').slice(1).some(_utils.containsAwait)) {
+ (function () {
+ var tmp = (0, _babelTypes.identifier)(path.scope.generateUid('temp'));
+ _this.addVarDecl(tmp);
+ var assignment = (0, _utils.assign)(tmp, (0, _babelTypes.arrayExpression)(path.node.arguments));
+ path.node.arguments = path.node.arguments.map(function (_, i) {
+ return (0, _babelTypes.memberExpression)(tmp, (0, _babelTypes.numericLiteral)(i), true);
+ });
+ insertBefore(path, assignment);
+ })();
+ }
+ },
+ ObjectExpression: function ObjectExpression(path) {
+ var _this2 = this;
+
+ // {a: a(), b: await b()}
+ //
+ // ->
+ //
+ // _temp = {}, _temp.a = a(), _temp.b = await b(), _temp
+
+ if (path.get('properties').slice(1).some(_utils.containsAwait)) {
+ (function () {
+ var tmp = (0, _babelTypes.identifier)(path.scope.generateUid('temp'));
+ _this2.addVarDecl(tmp);
+ var assignments = [(0, _utils.assign)(tmp, (0, _babelTypes.objectExpression)([]))];
+ path.node.properties.forEach(function (property) {
+ var member = (0, _babelTypes.memberExpression)(tmp, property.key);
+ assignments.push((0, _utils.assign)(member, property.value));
+ });
+ path.replaceWith(tmp);
+ insertBefore(path, assignments);
+ })();
+ }
+ },
+
+ TryStatement: {
+ exit: function exit(path) {
+ // changes a try/catch that contains an await in a promise chain that uses
+ // .catch()
+ //
+ // uses exit() to make sure nested try/catch-es are converted correctly
+ // too.
+
+ if ((0, _utils.containsAwait)(path)) {
+ var subChain = new _promisechain2.default(true, true, this.respID, this.errID);
+ subChain.add(path.get('block.body'));
+ if (path.node.handler) {
+ subChain.addCatch(path.get('handler.body.body'), path.node.handler.param);
+ }
+ if (path.node.finalizer) {
+ subChain.addFinally(path.get('finalizer.body'));
+ }
+ path.replaceWith((0, _utils.awaitStatement)(subChain.toAST()));
+ }
+ }
+ },
+ ConditionalExpression: function ConditionalExpression(path) {
+ var node = path.node;
+
+ var leftHasAwait = (0, _utils.containsAwait)(path.get('consequent'));
+ var rightHasAwait = (0, _utils.containsAwait)(path.get('alternate'));
+ if (leftHasAwait) {
+ node.consequent = wrapAwaitContaining(node.consequent);
+ }
+ if (rightHasAwait) {
+ node.alternate = wrapAwaitContaining(node.alternate);
+ }
+ if (leftHasAwait || rightHasAwait) {
+ path.replaceWith((0, _babelTypes.awaitExpression)(path.node));
+ }
+ },
+ LogicalExpression: function LogicalExpression(path) {
+ // a && (await b) becomes:
+ // await a && async function () {
+ // return await b();
+ // }()
+ if ((0, _utils.containsAwait)(path.get('right'))) {
+ path.node.right = wrapAwaitContaining(path.node.right);
+ path.replaceWith((0, _babelTypes.awaitExpression)(path.node));
+ }
+ },
+ SequenceExpression: function SequenceExpression(path) {
+ // a, await b, await c becomes:
+ // await async function() {
+ // a;
+ // await b;
+ // return await c;
+ // }
+
+ if ((0, _utils.containsAwait)(path)) {
+ // don't include the last item yet
+ var exprs = path.node.expressions;
+ var body = exprs.slice(0, exprs.length - 1).map(function (expr) {
+ return (0, _babelTypes.expressionStatement)(expr);
+ });
+ // because that one gets a return statement
+ body.push((0, _babelTypes.returnStatement)(exprs[exprs.length - 1]));
+ path.replaceWith((0, _babelTypes.awaitExpression)((0, _utils.wrapFunction)((0, _babelTypes.blockStatement)(body))));
+ }
+ },
+ Identifier: function Identifier(path) {
+ if (path.node.name === 'arguments' && !path.scope.hasOwnBinding('arguments')) {
+ path.replaceWith(this.argumentsID);
+ this.used.argumentsID = true;
+ }
+ },
+ SwitchStatement: function SwitchStatement(path) {
+ // converts a switch statement in a bunch of if statements that compare the
+ // discriminant to each test. Falling through is handled by a 'match'
+ // variable, and the break statement is handled by a variable 'brokenOut'.
+ // Cases after the default case are repeated so the default case can fall
+ // through (but in such a way that they won't match again if the default
+ // isn't falling through)
+
+ var discrID = (0, _babelTypes.identifier)(path.scope.generateUid('discriminant'));
+ var matchID = (0, _babelTypes.identifier)(path.scope.generateUid('match'));
+ var brokenID = (0, _babelTypes.identifier)(path.scope.generateUid('brokenOut'));
+ this.addVarDecl(discrID);
+ this.addVarDecl(matchID);
+ this.addVarDecl(brokenID);
+
+ // replace break statements with assignment expressions
+ path.traverse(SwitchBreakReplacementVisitor, { brokenID: brokenID });
+
+ var stmts = [];
+ var notBroken = (0, _babelTypes.unaryExpression)('!', brokenID);
+ var defaultIdx = void 0;
+ path.node.cases.forEach(function (caseNode, i) {
+ // add normal checks
+ if (!caseNode.test) {
+ defaultIdx = i;
+ return;
+ }
+
+ // Seems like a weird order? Maybe, but it does prevent the
+ // BinaryExpression refactorer to make too much of a mess for the sake of
+ // strict execution order correctness.
+ var isOwnMatch = (0, _babelTypes.binaryExpression)('===', caseNode.test, discrID);
+ var isMatch = (0, _babelTypes.logicalExpression)('||', matchID, isOwnMatch);
+ var test = (0, _babelTypes.logicalExpression)('&&', notBroken, isMatch);
+ stmts.push((0, _babelTypes.ifStatement)(test, (0, _babelTypes.blockStatement)(caseNode.consequent.concat([(0, _utils.assign)(matchID, (0, _babelTypes.booleanLiteral)(true))]))));
+ });
+
+ if (typeof defaultIdx !== 'undefined') {
+ (function () {
+ // add default case
+ var notMatch = (0, _babelTypes.unaryExpression)('!', matchID);
+ var defaultTest = (0, _babelTypes.logicalExpression)('&&', notBroken, notMatch);
+ var body = path.node.cases[defaultIdx].consequent;
+ path.node.cases.slice(defaultIdx + 1).forEach(function (caseNode) {
+ // add fall through cases after default - still guarded by the default
+ // check
+ body.push((0, _babelTypes.ifStatement)(notBroken, (0, _babelTypes.blockStatement)(caseNode.consequent)));
+ });
+ stmts.push((0, _babelTypes.ifStatement)(defaultTest, (0, _babelTypes.blockStatement)(body)));
+ })();
+ }
+
+ path.replaceWithMultiple([(0, _utils.assign)(discrID, path.node.discriminant), (0, _utils.assign)(matchID, (0, _babelTypes.booleanLiteral)(false)), (0, _utils.assign)(brokenID, (0, _babelTypes.booleanLiteral)(false))].concat(stmts));
+ },
+ FunctionDeclaration: function FunctionDeclaration(path) {
+ this.addFunctionDecl(path.node);
+ path.remove();
+ },
+ FunctionExpression: function FunctionExpression(path) {
+ if (path.node.id && path.parent.type !== 'ObjectProperty') {
+ path.node.type = 'FunctionDeclaration';
+ this.addFunctionDecl(path.node);
+ path.replaceWith(path.node.id);
+ }
+ }
+}, _ifrefactor.FirstPassIfVisitor, _looprefactor2.default,
+ // TODO: don't touch sub switch statements. Enabling the following should be a
+ // start.
+ //SwitchStatement(path) {
+ // path.skip();
+ //}
+ _utils.NoSubFunctionsVisitor);
+
+function insertBefore(path, node) {
+ // prevent unnecessary sequence expressions. In normal JS they might be
+ // elegant and thus nice for Babel, but their async wrapper is ugly.
+ if ((0, _babelTypes.isExpressionStatement)(path.parent) || (0, _babelTypes.isReturnStatement)(path.parent)) {
+ path.parentPath.insertBefore(node);
+ } else {
+ path.insertBefore(node);
+ }
+}
+
+var SwitchBreakReplacementVisitor = (0, _jsExtend.extend)({
+ BreakStatement: function BreakStatement(path) {
+ // TODO: don't execute any code after the break assignment
+ path.replaceWith((0, _utils.assign)(this.brokenID, (0, _babelTypes.booleanLiteral)(true)));
+ }
+}, _utils.NoSubFunctionsVisitor);
+
+var wrapAwaitContaining = function wrapAwaitContaining(node) {
+ return (0, _utils.wrapFunction)((0, _babelTypes.blockStatement)([(0, _babelTypes.returnStatement)(node)]));
+}; \ No newline at end of file
diff --git a/babel-plugin-async-to-promises/lib/utils.js b/babel-plugin-async-to-promises/lib/utils.js
new file mode 100644
index 0000000..708adab
--- /dev/null
+++ b/babel-plugin-async-to-promises/lib/utils.js
@@ -0,0 +1,55 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.assign = exports.awaitStatement = exports.containsAwait = exports.NoSubFunctionsVisitor = undefined;
+exports.matcher = matcher;
+exports.wrapFunction = wrapFunction;
+
+var _babelTypes = require('babel-types');
+
+var _jsExtend = require('js-extend');
+
+var NoSubFunctionsVisitor = exports.NoSubFunctionsVisitor = {
+ Function: function Function(path) {
+ path.skip();
+ }
+};
+
+var containsAwait = exports.containsAwait = matcher(['AwaitExpression'], NoSubFunctionsVisitor);
+
+function matcher(types, base) {
+ var MatchVisitor = (0, _jsExtend.extend)({}, base);
+ types.forEach(function (type) {
+ MatchVisitor[type] = function (path) {
+ this.match.found = true;
+ path.stop();
+ };
+ });
+ return function (path) {
+ if (!path.node) {
+ return false;
+ }
+ if (types.indexOf(path.node.type) !== -1) {
+ return true;
+ }
+ var match = {};
+ path.traverse(MatchVisitor, { match: match });
+ return match.found;
+ };
+}
+
+function wrapFunction(body) {
+ var func = (0, _babelTypes.functionExpression)(null, [], body, false, true);
+ func.dirtyAllowed = true;
+ return (0, _babelTypes.callExpression)(func, []);
+}
+
+var awaitStatement = exports.awaitStatement = function awaitStatement(arg) {
+ return (0, _babelTypes.expressionStatement)((0, _babelTypes.awaitExpression)(arg));
+};
+
+var assign = exports.assign = function assign(a, b) {
+ return (0, _babelTypes.expressionStatement)((0, _babelTypes.assignmentExpression)('=', a, b));
+}; \ No newline at end of file