From 394fdecc9141aa4c6a8ea7f1aecaf5fcaaaf5c72 Mon Sep 17 00:00:00 2001 From: ejose19 <8742215+ejose19@users.noreply.github.com> Date: Sun, 4 Jul 2021 23:42:06 -0300 Subject: [PATCH] repl: correctly hoist top level await declarations PR-URL: https://github.com/nodejs/node/pull/39265 Reviewed-By: Guy Bedford Reviewed-By: James M Snell --- lib/internal/repl/await.js | 94 ++++++++++++++++--- .../test-repl-preprocess-top-level-await.js | 77 ++++++++++++--- 2 files changed, 147 insertions(+), 24 deletions(-) diff --git a/lib/internal/repl/await.js b/lib/internal/repl/await.js index 09547117a6565a..3d0caa17650a04 100644 --- a/lib/internal/repl/await.js +++ b/lib/internal/repl/await.js @@ -3,6 +3,7 @@ const { ArrayFrom, ArrayPrototypeForEach, + ArrayPrototypeIncludes, ArrayPrototypeJoin, ArrayPrototypePop, ArrayPrototypePush, @@ -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) { @@ -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, @@ -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); @@ -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] = ''; @@ -172,7 +237,10 @@ function processTopLevelAwait(src) { state.append(last.expression, ')'); } - return ArrayPrototypeJoin(wrappedArray, ''); + return ( + ArrayPrototypeJoin(state.hoistedDeclarationStatements, '') + + ArrayPrototypeJoin(wrappedArray, '') + ); } module.exports = { diff --git a/test/parallel/test-repl-preprocess-top-level-await.js b/test/parallel/test-repl-preprocess-top-level-await.js index ed1fe90e43e459..3ec4da7e8fb72f 100644 --- a/test/parallel/test-repl-preprocess-top-level-await.js +++ b/test/parallel/test-repl-preprocess-top-level-await.js @@ -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) {