aboutsummaryrefslogtreecommitdiff
path: root/babel-plugin-async-to-promises/lib/refactor.js
diff options
context:
space:
mode:
Diffstat (limited to 'babel-plugin-async-to-promises/lib/refactor.js')
-rw-r--r--babel-plugin-async-to-promises/lib/refactor.js285
1 files changed, 285 insertions, 0 deletions
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