Skip to content

Commit

Permalink
fix(node6): fix one line await arrow functions puppeteer#2198
Browse files Browse the repository at this point in the history
When the start of the function body was await, the async function transformer behaves
non-deterministically and can break.
  • Loading branch information
JoelEinbinder authored and aslushnikov committed Mar 15, 2018
1 parent 7d387d8 commit d79eb70
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 11 deletions.
51 changes: 40 additions & 11 deletions utils/node6-transform/TransformAsyncFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -72,7 +80,7 @@ function transformAsyncFunctions(text) {
/**
* @param {ESTree.Node} node
*/
function onFunction(node) {
function onBeforeFunction(node) {
if (!node.async) return;

let range;
Expand All @@ -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);
}

/**
* @param {ESTree.Node} 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 onAwait(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});
Expand Down
6 changes: 6 additions & 0 deletions utils/node6-transform/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

0 comments on commit d79eb70

Please sign in to comment.