diff options
author | Timothy Gu <timothygu99@gmail.com> | 2017-09-23 10:10:47 +0300 |
---|---|---|
committer | Timothy Gu <timothygu99@gmail.com> | 2017-11-17 02:42:46 +0300 |
commit | eeab7bc0688256247c47099a90c67741e6637e42 (patch) | |
tree | ea2e94851b343855a68f362b57155b44f4fe510b /lib/internal/repl | |
parent | ab64b6d7992efb9c558719b6e7e63f280d73048b (diff) |
repl: support top-level await
Much of the AST visitor code was ported from Chrome DevTools code
written by Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>.
PR-URL: https://github.com/nodejs/node/pull/15566
Fixes: https://github.com/nodejs/node/issues/13209
Refs: https://chromium.googlesource.com/chromium/src/+/e8111c396fef38da6654093433b4be93bed01dce
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Diffstat (limited to 'lib/internal/repl')
-rw-r--r-- | lib/internal/repl/await.js | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/lib/internal/repl/await.js b/lib/internal/repl/await.js new file mode 100644 index 00000000000..dfd4e930859 --- /dev/null +++ b/lib/internal/repl/await.js @@ -0,0 +1,128 @@ +'use strict'; + +const acorn = require('internal/deps/acorn/dist/acorn'); +const walk = require('internal/deps/acorn/dist/walk'); + +const noop = () => {}; +const visitorsWithoutAncestors = { + ClassDeclaration(node, state, c) { + if (state.ancestors[state.ancestors.length - 2] === state.body) { + state.prepend(node, `${node.id.name}=`); + } + walk.base.ClassDeclaration(node, state, c); + }, + FunctionDeclaration(node, state, c) { + state.prepend(node, `${node.id.name}=`); + }, + FunctionExpression: noop, + ArrowFunctionExpression: noop, + MethodDefinition: noop, + AwaitExpression(node, state, c) { + state.containsAwait = true; + walk.base.AwaitExpression(node, state, c); + }, + ReturnStatement(node, state, c) { + state.containsReturn = true; + 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 ('); + } + + for (const decl of node.declarations) { + state.prepend(decl, '('); + state.append(decl, decl.init ? ')' : '=undefined)'); + } + + if (node.declarations.length !== 1) { + state.append(node.declarations[node.declarations.length - 1], ')'); + } + } + + walk.base.VariableDeclaration(node, state, c); + } +}; + +const visitors = {}; +for (const nodeType of Object.keys(walk.base)) { + const callback = visitorsWithoutAncestors[nodeType] || walk.base[nodeType]; + visitors[nodeType] = (node, state, c) => { + const isNew = node !== state.ancestors[state.ancestors.length - 1]; + if (isNew) { + state.ancestors.push(node); + } + callback(node, state, c); + if (isNew) { + state.ancestors.pop(); + } + }; +} + +function processTopLevelAwait(src) { + const wrapped = `(async () => { ${src} })()`; + const wrappedArray = wrapped.split(''); + let root; + try { + root = acorn.parse(wrapped, { ecmaVersion: 8 }); + } catch (err) { + return null; + } + const body = root.body[0].expression.callee.body; + const state = { + body, + ancestors: [], + replace(from, to, str) { + for (var i = from; i < to; i++) { + wrappedArray[i] = ''; + } + if (from === to) str += wrappedArray[from]; + wrappedArray[from] = str; + }, + prepend(node, str) { + wrappedArray[node.start] = str + wrappedArray[node.start]; + }, + append(node, str) { + wrappedArray[node.end - 1] += str; + }, + containsAwait: false, + containsReturn: false + }; + + walk.recursive(body, state, visitors); + + // Do not transform if + // 1. False alarm: there isn't actually an await expression. + // 2. There is a top-level return, which is not allowed. + if (!state.containsAwait || state.containsReturn) { + return null; + } + + const last = body.body[body.body.length - 1]; + if (last.type === 'ExpressionStatement') { + // For an expression statement of the form + // ( expr ) ; + // ^^^^^^^^^^ // last + // ^^^^ // last.expression + // + // We do not want the left parenthesis before the `return` keyword; + // therefore we prepend the `return (` to `last`. + // + // On the other hand, we do not want the right parenthesis after the + // semicolon. Since there can only be more right parentheses between + // last.expression.end and the semicolon, appending one more to + // last.expression should be fine. + state.prepend(last, 'return ('); + state.append(last.expression, ')'); + } + + return wrappedArray.join(''); +} + +module.exports = { + processTopLevelAwait +}; |