lts/lib/internal/repl/await.js
'use strict';
const {
ObjectKeys,
} = primordials;
const acorn = require('internal/deps/acorn/acorn/dist/acorn');
const walk = require('internal/deps/acorn/acorn-walk/dist/walk');
const privateMethods =
require('internal/deps/acorn-plugins/acorn-private-methods/index');
const classFields =
require('internal/deps/acorn-plugins/acorn-class-fields/index');
const numericSeparator =
require('internal/deps/acorn-plugins/acorn-numeric-separator/index');
const staticClassFeatures =
require('internal/deps/acorn-plugins/acorn-static-class-features/index');
const parser = acorn.Parser.extend(
privateMethods,
classFields,
numericSeparator,
staticClassFeatures
);
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);
},
ForOfStatement(node, state, c) {
if (node.await === true) {
state.containsAwait = true;
}
walk.base.ForOfStatement(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 ObjectKeys(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 = parser.parse(wrapped, { ecmaVersion: 11 });
} catch {
return null;
}
const body = root.body[0].expression.callee.body;
const state = {
body,
ancestors: [],
replace(from, to, str) {
for (let 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
};