fix(node6): fix one line await arrow functions #2198

When the start of the function body was await, the async function transformer behaves
non-deterministically and can break.
This commit is contained in:
JoelEinbinder 2018-03-15 14:54:23 -07:00 committed by Andrey Lushnikov
parent 7d387d8d75
commit d79eb70267
2 changed files with 47 additions and 12 deletions

View File

@ -52,18 +52,26 @@ const asyncToGenerator = fn => {
* @return {string}
*/
function transformAsyncFunctions(text) {
/**
* @type {!Array<{from: number, to: number, replacement: string}>}
*/
const edits = [];
const ast = esprima.parseScript(text, {range: true, tolerant: true});
const walker = new ESTreeWalker(node => {
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')
onFunction(node);
onBeforeFunction(node);
else if (node.type === 'AwaitExpression')
onAwait(node);
onBeforeAwait(node);
}, node => {
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')
onAfterFunction(node);
else if (node.type === 'AwaitExpression')
onAfterAwait(node);
});
walker.walk(ast);
edits.sort((a, b) => b.from - a.from);
edits.reverse();
for (const {replacement, from, to} of edits)
text = text.substring(0, from) + replacement + text.substring(to);
@ -72,7 +80,7 @@ function transformAsyncFunctions(text) {
/**
* @param {ESTree.Node} node
*/
function onFunction(node) {
function onBeforeFunction(node) {
if (!node.async) return;
let range;
@ -84,40 +92,61 @@ function transformAsyncFunctions(text) {
insertText(index, index + 'async'.length, '/* async */');
let before = `{return (${asyncToGenerator.toString()})(function*()`;
let after = `);}`;
if (node.body.type !== 'BlockStatement') {
before += `{ return `;
after = `; }` + after;
// 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 openParen = node.range[0] + beforeBody.lastIndexOf('(');
insertText(openParen, openParen + 1, ' ');
const closeParen = node.body.range[1] + afterBody.indexOf(')');
insertText(closeParen, closeParen + 1, ' ');
}
}
insertText(node.body.range[0], node.body.range[0], before);
insertText(node.body.range[1], node.body.range[1], after);
}
/**
* @param {ESTree.Node} node
*/
function onAwait(node) {
function onAfterFunction(node) {
if (!node.async) return;
let after = `);}`;
if (node.body.type !== 'BlockStatement')
after = `; }` + after;
insertText(node.body.range[1], node.body.range[1], after);
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, ' ');
}
}
}
/**
* @param {ESTree.Node} node
*/
function onBeforeAwait(node) {
const index = text.substring(node.range[0], node.range[1]).indexOf('await') + node.range[0];
insertText(index, index + 'await'.length, '(yield');
}
/**
* @param {ESTree.Node} node
*/
function onAfterAwait(node) {
insertText(node.range[1], node.range[1], ')');
}
/**
* @param {number} from
* @param {number} to
* @param {string} replacement
*/
function insertText(from, to, replacement) {
edits.push({from, to, replacement});

View File

@ -86,6 +86,12 @@ describe('TransformAsyncFunctions', function() {
expect(output instanceof Promise).toBe(true);
output.then(result => expect(result).toBe(123)).then(done);
});
it('should work async arrow with await', function(done) {
const input = `(async() => await 123)()`;
const output = eval(transformAsyncFunctions(input));
expect(output instanceof Promise).toBe(true);
output.then(result => expect(result).toBe(123)).then(done);
});
});
runner.run();