'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 };