Skip to content

Commit

Permalink
repl: correctly hoist top level await declarations
Browse files Browse the repository at this point in the history
PR-URL: #39265
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
ejose19 authored and targos committed Jul 11, 2021
1 parent 2d552a3 commit a101fe6
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 24 deletions.
94 changes: 81 additions & 13 deletions lib/internal/repl/await.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const {
ArrayFrom,
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
ArrayPrototypePop,
ArrayPrototypePush,
Expand All @@ -22,12 +23,21 @@ const parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
const walk = require('internal/deps/acorn/acorn-walk/dist/walk');
const { Recoverable } = require('internal/repl');

function isTopLevelDeclaration(state) {
return state.ancestors[state.ancestors.length - 2] === state.body;
}

const noop = FunctionPrototype;
const visitorsWithoutAncestors = {
ClassDeclaration(node, state, c) {
if (state.ancestors[state.ancestors.length - 2] === state.body) {
if (isTopLevelDeclaration(state)) {
state.prepend(node, `${node.id.name}=`);
ArrayPrototypePush(
state.hoistedDeclarationStatements,
`let ${node.id.name}; `
);
}

walk.base.ClassDeclaration(node, state, c);
},
ForOfStatement(node, state, c) {
Expand All @@ -38,6 +48,10 @@ const visitorsWithoutAncestors = {
},
FunctionDeclaration(node, state, c) {
state.prepend(node, `${node.id.name}=`);
ArrayPrototypePush(
state.hoistedDeclarationStatements,
`var ${node.id.name}; `
);
},
FunctionExpression: noop,
ArrowFunctionExpression: noop,
Expand All @@ -51,22 +65,72 @@ const visitorsWithoutAncestors = {
walk.base.ReturnStatement(node, state, c);
},
VariableDeclaration(node, state, c) {
if (node.kind === 'var' ||
state.ancestors[state.ancestors.length - 2] === state.body) {
if (node.declarations.length === 1) {
state.replace(node.start, node.start + node.kind.length, 'void');
} else {
state.replace(node.start, node.start + node.kind.length, 'void (');
const variableKind = node.kind;
const isIterableForDeclaration = ArrayPrototypeIncludes(
['ForOfStatement', 'ForInStatement'],
state.ancestors[state.ancestors.length - 2].type
);

if (variableKind === 'var' || isTopLevelDeclaration(state)) {
state.replace(
node.start,
node.start + variableKind.length + (isIterableForDeclaration ? 1 : 0),
variableKind === 'var' && isIterableForDeclaration ?
'' :
'void' + (node.declarations.length === 1 ? '' : ' (')
);

if (!isIterableForDeclaration) {
ArrayPrototypeForEach(node.declarations, (decl) => {
state.prepend(decl, '(');
state.append(decl, decl.init ? ')' : '=undefined)');
});

if (node.declarations.length !== 1) {
state.append(node.declarations[node.declarations.length - 1], ')');
}
}

const variableIdentifiersToHoist = [
['var', []],
['let', []],
];
function registerVariableDeclarationIdentifiers(node) {
switch (node.type) {
case 'Identifier':
ArrayPrototypePush(
variableIdentifiersToHoist[variableKind === 'var' ? 0 : 1][1],
node.name
);
break;
case 'ObjectPattern':
ArrayPrototypeForEach(node.properties, (property) => {
registerVariableDeclarationIdentifiers(property.value);
});
break;
case 'ArrayPattern':
ArrayPrototypeForEach(node.elements, (element) => {
registerVariableDeclarationIdentifiers(element);
});
break;
}
}

ArrayPrototypeForEach(node.declarations, (decl) => {
state.prepend(decl, '(');
state.append(decl, decl.init ? ')' : '=undefined)');
registerVariableDeclarationIdentifiers(decl.id);
});

if (node.declarations.length !== 1) {
state.append(node.declarations[node.declarations.length - 1], ')');
}
ArrayPrototypeForEach(
variableIdentifiersToHoist,
({ 0: kind, 1: identifiers }) => {
if (identifiers.length > 0) {
ArrayPrototypePush(
state.hoistedDeclarationStatements,
`${kind} ${ArrayPrototypeJoin(identifiers, ', ')}; `
);
}
}
);
}

walk.base.VariableDeclaration(node, state, c);
Expand Down Expand Up @@ -128,6 +192,7 @@ function processTopLevelAwait(src) {
const state = {
body,
ancestors: [],
hoistedDeclarationStatements: [],
replace(from, to, str) {
for (let i = from; i < to; i++) {
wrappedArray[i] = '';
Expand Down Expand Up @@ -172,7 +237,10 @@ function processTopLevelAwait(src) {
state.append(last.expression, ')');
}

return ArrayPrototypeJoin(wrappedArray, '');
return (
ArrayPrototypeJoin(state.hoistedDeclarationStatements, '') +
ArrayPrototypeJoin(wrappedArray, '')
);
}

module.exports = {
Expand Down
77 changes: 66 additions & 11 deletions test/parallel/test-repl-preprocess-top-level-await.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,38 +29,93 @@ const testCases = [
[ 'await 0; return 0;',
null ],
[ 'var a = await 1',
'(async () => { void (a = await 1) })()' ],
'var a; (async () => { void (a = await 1) })()' ],
[ 'let a = await 1',
'(async () => { void (a = await 1) })()' ],
'let a; (async () => { void (a = await 1) })()' ],
[ 'const a = await 1',
'(async () => { void (a = await 1) })()' ],
'let a; (async () => { void (a = await 1) })()' ],
[ 'for (var i = 0; i < 1; ++i) { await i }',
'(async () => { for (void (i = 0); i < 1; ++i) { await i } })()' ],
'var i; (async () => { for (void (i = 0); i < 1; ++i) { await i } })()' ],
[ 'for (let i = 0; i < 1; ++i) { await i }',
'(async () => { for (let i = 0; i < 1; ++i) { await i } })()' ],
[ 'var {a} = {a:1}, [b] = [1], {c:{d}} = {c:{d: await 1}}',
'(async () => { void ( ({a} = {a:1}), ([b] = [1]), ' +
'var a, b, d; (async () => { void ( ({a} = {a:1}), ([b] = [1]), ' +
'({c:{d}} = {c:{d: await 1}})) })()' ],
[ 'let [a, b, c] = await ([1, 2, 3])',
'let a, b, c; (async () => { void ([a, b, c] = await ([1, 2, 3])) })()'],
[ 'let {a,b,c} = await ({a: 1, b: 2, c: 3})',
'let a, b, c; (async () => { void ({a,b,c} = ' +
'await ({a: 1, b: 2, c: 3})) })()'],
[ 'let {a: [b]} = {a: [await 1]}, [{d}] = [{d: 3}]',
'let b, d; (async () => { void ( ({a: [b]} = {a: [await 1]}),' +
' ([{d}] = [{d: 3}])) })()'],
/* eslint-disable no-template-curly-in-string */
[ 'console.log(`${(await { a: 1 }).a}`)',
'(async () => { return (console.log(`${(await { a: 1 }).a}`)) })()' ],
/* eslint-enable no-template-curly-in-string */
[ 'await 0; function foo() {}',
'(async () => { await 0; foo=function foo() {} })()' ],
'var foo; (async () => { await 0; foo=function foo() {} })()' ],
[ 'await 0; class Foo {}',
'(async () => { await 0; Foo=class Foo {} })()' ],
'let Foo; (async () => { await 0; Foo=class Foo {} })()' ],
[ 'if (await true) { function foo() {} }',
'(async () => { if (await true) { foo=function foo() {} } })()' ],
'var foo; (async () => { if (await true) { foo=function foo() {} } })()' ],
[ 'if (await true) { class Foo{} }',
'(async () => { if (await true) { class Foo{} } })()' ],
[ 'if (await true) { var a = 1; }',
'(async () => { if (await true) { void (a = 1); } })()' ],
'var a; (async () => { if (await true) { void (a = 1); } })()' ],
[ 'if (await true) { let a = 1; }',
'(async () => { if (await true) { let a = 1; } })()' ],
[ 'var a = await 1; let b = 2; const c = 3;',
'(async () => { void (a = await 1); void (b = 2); void (c = 3); })()' ],
'var a; let b; let c; (async () => { void (a = await 1); void (b = 2);' +
' void (c = 3); })()' ],
[ 'let o = await 1, p',
'(async () => { void ( (o = await 1), (p=undefined)) })()' ],
'let o, p; (async () => { void ( (o = await 1), (p=undefined)) })()' ],
[ 'await (async () => { let p = await 1; return p; })()',
'(async () => { return (await (async () => ' +
'{ let p = await 1; return p; })()) })()' ],
[ '{ let p = await 1; }',
'(async () => { { let p = await 1; } })()' ],
[ 'var p = await 1',
'var p; (async () => { void (p = await 1) })()' ],
[ 'await (async () => { var p = await 1; return p; })()',
'(async () => { return (await (async () => ' +
'{ var p = await 1; return p; })()) })()' ],
[ '{ var p = await 1; }',
'var p; (async () => { { void (p = await 1); } })()' ],
[ 'for await (var i of asyncIterable) { i; }',
'var i; (async () => { for await (i of asyncIterable) { i; } })()'],
[ 'for await (var [i] of asyncIterable) { i; }',
'var i; (async () => { for await ([i] of asyncIterable) { i; } })()'],
[ 'for await (var {i} of asyncIterable) { i; }',
'var i; (async () => { for await ({i} of asyncIterable) { i; } })()'],
[ 'for await (var [{i}, [j]] of asyncIterable) { i; }',
'var i, j; (async () => { for await ([{i}, [j]] of asyncIterable)' +
' { i; } })()'],
[ 'for await (let i of asyncIterable) { i; }',
'(async () => { for await (let i of asyncIterable) { i; } })()'],
[ 'for await (const i of asyncIterable) { i; }',
'(async () => { for await (const i of asyncIterable) { i; } })()'],
[ 'for (var i of [1,2,3]) { await 1; }',
'var i; (async () => { for (i of [1,2,3]) { await 1; } })()'],
[ 'for (var [i] of [[1], [2]]) { await 1; }',
'var i; (async () => { for ([i] of [[1], [2]]) { await 1; } })()'],
[ 'for (var {i} of [{i: 1}, {i: 2}]) { await 1; }',
'var i; (async () => { for ({i} of [{i: 1}, {i: 2}]) { await 1; } })()'],
[ 'for (var [{i}, [j]] of [[{i: 1}, [2]]]) { await 1; }',
'var i, j; (async () => { for ([{i}, [j]] of [[{i: 1}, [2]]])' +
' { await 1; } })()'],
[ 'for (let i of [1,2,3]) { await 1; }',
'(async () => { for (let i of [1,2,3]) { await 1; } })()'],
[ 'for (const i of [1,2,3]) { await 1; }',
'(async () => { for (const i of [1,2,3]) { await 1; } })()'],
[ 'for (var i in {x:1}) { await 1 }',
'var i; (async () => { for (i in {x:1}) { await 1 } })()'],
[ 'for (var [a,b] in {xy:1}) { await 1 }',
'var a, b; (async () => { for ([a,b] in {xy:1}) { await 1 } })()'],
[ 'for (let i in {x:1}) { await 1 }',
'(async () => { for (let i in {x:1}) { await 1 } })()'],
[ 'for (const i in {x:1}) { await 1 }',
'(async () => { for (const i in {x:1}) { await 1 } })()'],
];

for (const [input, expected] of testCases) {
Expand Down

0 comments on commit a101fe6

Please sign in to comment.