2017-08-24 19:20:05 +00:00
|
|
|
/**
|
|
|
|
* Copyright 2017 Google Inc. All rights reserved.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const esprima = require('esprima');
|
|
|
|
const ESTreeWalker = require('../ESTreeWalker');
|
|
|
|
|
|
|
|
// This is converted from Babel's "transform-async-to-generator"
|
|
|
|
// https://babeljs.io/docs/plugins/transform-async-to-generator/
|
|
|
|
const asyncToGenerator = fn => {
|
|
|
|
const gen = fn.call(this);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
function step(key, arg) {
|
|
|
|
let info, value;
|
|
|
|
try {
|
|
|
|
info = gen[key](arg);
|
|
|
|
value = info.value;
|
|
|
|
} catch (error) {
|
|
|
|
reject(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (info.done) {
|
|
|
|
resolve(value);
|
|
|
|
} else {
|
|
|
|
return Promise.resolve(value).then(
|
|
|
|
value => {
|
|
|
|
step('next', value);
|
|
|
|
},
|
|
|
|
err => {
|
|
|
|
step('throw', err);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return step('next');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} text
|
2017-08-26 02:28:49 +00:00
|
|
|
* @return {string}
|
2017-08-24 19:20:05 +00:00
|
|
|
*/
|
|
|
|
function transformAsyncFunctions(text) {
|
2018-03-15 21:54:23 +00:00
|
|
|
/**
|
|
|
|
* @type {!Array<{from: number, to: number, replacement: string}>}
|
|
|
|
*/
|
2017-08-24 19:20:05 +00:00
|
|
|
const edits = [];
|
|
|
|
|
2017-08-25 22:13:59 +00:00
|
|
|
const ast = esprima.parseScript(text, {range: true, tolerant: true});
|
2017-08-24 19:20:05 +00:00
|
|
|
const walker = new ESTreeWalker(node => {
|
|
|
|
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')
|
2018-03-15 21:54:23 +00:00
|
|
|
onBeforeFunction(node);
|
2017-08-24 19:20:05 +00:00
|
|
|
else if (node.type === 'AwaitExpression')
|
2018-03-15 21:54:23 +00:00
|
|
|
onBeforeAwait(node);
|
|
|
|
}, node => {
|
|
|
|
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')
|
|
|
|
onAfterFunction(node);
|
|
|
|
else if (node.type === 'AwaitExpression')
|
|
|
|
onAfterAwait(node);
|
2017-08-24 19:20:05 +00:00
|
|
|
});
|
|
|
|
walker.walk(ast);
|
|
|
|
|
2018-03-15 21:54:23 +00:00
|
|
|
edits.reverse();
|
2017-08-24 19:20:05 +00:00
|
|
|
for (const {replacement, from, to} of edits)
|
|
|
|
text = text.substring(0, from) + replacement + text.substring(to);
|
|
|
|
|
|
|
|
return text;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {ESTree.Node} node
|
|
|
|
*/
|
2018-03-15 21:54:23 +00:00
|
|
|
function onBeforeFunction(node) {
|
2017-08-24 19:20:05 +00:00
|
|
|
if (!node.async) return;
|
|
|
|
|
|
|
|
let range;
|
|
|
|
if (node.parent.type === 'MethodDefinition')
|
|
|
|
range = node.parent.range;
|
|
|
|
else
|
|
|
|
range = node.range;
|
|
|
|
const index = text.substring(range[0], range[1]).indexOf('async') + range[0];
|
|
|
|
insertText(index, index + 'async'.length, '/* async */');
|
|
|
|
|
|
|
|
let before = `{return (${asyncToGenerator.toString()})(function*()`;
|
|
|
|
if (node.body.type !== 'BlockStatement') {
|
|
|
|
before += `{ return `;
|
2017-10-03 01:25:11 +00:00
|
|
|
|
|
|
|
// Remove parentheses that might wrap an arrow function
|
|
|
|
const beforeBody = text.substring(node.range[0], node.body.range[0]);
|
|
|
|
if (/\(\s*$/.test(beforeBody)) {
|
|
|
|
const openParen = node.range[0] + beforeBody.lastIndexOf('(');
|
|
|
|
insertText(openParen, openParen + 1, ' ');
|
|
|
|
}
|
2017-08-24 19:20:05 +00:00
|
|
|
}
|
2017-10-03 01:25:11 +00:00
|
|
|
|
|
|
|
|
2017-08-24 19:20:05 +00:00
|
|
|
insertText(node.body.range[0], node.body.range[0], before);
|
2018-03-15 21:54:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {ESTree.Node} node
|
|
|
|
*/
|
|
|
|
function onAfterFunction(node) {
|
|
|
|
if (!node.async) return;
|
|
|
|
|
|
|
|
let after = `);}`;
|
|
|
|
if (node.body.type !== 'BlockStatement')
|
|
|
|
after = `; }` + after;
|
2017-08-24 19:20:05 +00:00
|
|
|
insertText(node.body.range[1], node.body.range[1], after);
|
2018-03-15 21:54:23 +00:00
|
|
|
|
|
|
|
if (node.body.type !== 'BlockStatement') {
|
|
|
|
// Remove parentheses that might wrap an arrow function
|
|
|
|
const beforeBody = text.substring(node.range[0], node.body.range[0]);
|
|
|
|
if (/\(\s*$/.test(beforeBody)) {
|
|
|
|
const afterBody = text.substring(node.body.range[1], node.range[1]);
|
|
|
|
const closeParen = node.body.range[1] + afterBody.indexOf(')');
|
|
|
|
insertText(closeParen, closeParen + 1, ' ');
|
|
|
|
}
|
|
|
|
}
|
2017-08-24 19:20:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {ESTree.Node} node
|
|
|
|
*/
|
2018-03-15 21:54:23 +00:00
|
|
|
function onBeforeAwait(node) {
|
2017-08-24 19:20:05 +00:00
|
|
|
const index = text.substring(node.range[0], node.range[1]).indexOf('await') + node.range[0];
|
|
|
|
insertText(index, index + 'await'.length, '(yield');
|
2018-03-15 21:54:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {ESTree.Node} node
|
|
|
|
*/
|
|
|
|
function onAfterAwait(node) {
|
2017-08-24 19:20:05 +00:00
|
|
|
insertText(node.range[1], node.range[1], ')');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {number} from
|
|
|
|
* @param {number} to
|
|
|
|
*/
|
|
|
|
function insertText(from, to, replacement) {
|
|
|
|
edits.push({from, to, replacement});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = transformAsyncFunctions;
|