aoc/node_modules/ts-node/dist-raw/node-internal-repl-await.js

255 lines
7.8 KiB
JavaScript

// copied from https://github.com/nodejs/node/blob/88799930794045795e8abac874730f9eba7e2300/lib/internal/repl/await.js
'use strict';
const {
ArrayFrom,
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
ArrayPrototypePop,
ArrayPrototypePush,
FunctionPrototype,
ObjectKeys,
RegExpPrototypeSymbolReplace,
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSplit,
StringPrototypeStartsWith,
SyntaxError,
} = require('./node-primordials');
const parser = require('acorn').Parser;
const walk = require('acorn-walk');
const { Recoverable } = require('repl');
function isTopLevelDeclaration(state) {
return state.ancestors[state.ancestors.length - 2] === state.body;
}
const noop = FunctionPrototype;
const visitorsWithoutAncestors = {
ClassDeclaration(node, state, c) {
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) {
if (node.await === true) {
state.containsAwait = true;
}
walk.base.ForOfStatement(node, state, c);
},
FunctionDeclaration(node, state, c) {
state.prepend(node, `${node.id.name}=`);
ArrayPrototypePush(
state.hoistedDeclarationStatements,
`var ${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) {
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) => {
registerVariableDeclarationIdentifiers(decl.id);
});
ArrayPrototypeForEach(
variableIdentifiersToHoist,
({ 0: kind, 1: identifiers }) => {
if (identifiers.length > 0) {
ArrayPrototypePush(
state.hoistedDeclarationStatements,
`${kind} ${ArrayPrototypeJoin(identifiers, ', ')}; `
);
}
}
);
}
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) {
ArrayPrototypePush(state.ancestors, node);
}
callback(node, state, c);
if (isNew) {
ArrayPrototypePop(state.ancestors);
}
};
}
function processTopLevelAwait(src) {
const wrapPrefix = '(async () => { ';
const wrapped = `${wrapPrefix}${src} })()`;
const wrappedArray = ArrayFrom(wrapped);
let root;
try {
root = parser.parse(wrapped, { ecmaVersion: 'latest' });
} catch (e) {
if (StringPrototypeStartsWith(e.message, 'Unterminated '))
throw new Recoverable(e);
// If the parse error is before the first "await", then use the execution
// error. Otherwise we must emit this parse error, making it look like a
// proper syntax error.
const awaitPos = StringPrototypeIndexOf(src, 'await');
const errPos = e.pos - wrapPrefix.length;
if (awaitPos > errPos)
return null;
// Convert keyword parse errors on await into their original errors when
// possible.
if (errPos === awaitPos + 6 &&
StringPrototypeIncludes(e.message, 'Expecting Unicode escape sequence'))
return null;
if (errPos === awaitPos + 7 &&
StringPrototypeIncludes(e.message, 'Unexpected token'))
return null;
const line = e.loc.line;
const column = line === 1 ? e.loc.column - wrapPrefix.length : e.loc.column;
let message = '\n' + StringPrototypeSplit(src, '\n')[line - 1] + '\n' +
StringPrototypeRepeat(' ', column) +
'^\n\n' + RegExpPrototypeSymbolReplace(/ \([^)]+\)/, e.message, '');
// V8 unexpected token errors include the token string.
if (StringPrototypeEndsWith(message, 'Unexpected token'))
message += " '" +
// Wrapper end may cause acorn to report error position after the source
((src.length - 1) >= (e.pos - wrapPrefix.length)
? src[e.pos - wrapPrefix.length]
: src[src.length - 1]) +
"'";
// eslint-disable-next-line no-restricted-syntax
throw new SyntaxError(message);
}
const body = root.body[0].expression.callee.body;
const state = {
body,
ancestors: [],
hoistedDeclarationStatements: [],
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 (
ArrayPrototypeJoin(state.hoistedDeclarationStatements, '') +
ArrayPrototypeJoin(wrappedArray, '')
);
}
module.exports = {
processTopLevelAwait
};