summaryrefslogtreecommitdiff
path: root/lib/internal/repl
diff options
context:
space:
mode:
authorTimothy Gu <timothygu99@gmail.com>2017-09-23 00:10:47 -0700
committerTimothy Gu <timothygu99@gmail.com>2017-11-16 15:42:46 -0800
commiteeab7bc0688256247c47099a90c67741e6637e42 (patch)
treeea2e94851b343855a68f362b57155b44f4fe510b /lib/internal/repl
parentab64b6d7992efb9c558719b6e7e63f280d73048b (diff)
downloadandroid-node-v8-eeab7bc0688256247c47099a90c67741e6637e42.tar.gz
android-node-v8-eeab7bc0688256247c47099a90c67741e6637e42.tar.bz2
android-node-v8-eeab7bc0688256247c47099a90c67741e6637e42.zip
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.js128
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 0000000000..dfd4e93085
--- /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
+};