2021-11-22 09:39:27 -06:00

261 lines
7.9 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEvalAwarePartialHost = exports.EvalState = exports.createRepl = exports.EVAL_FILENAME = void 0;
const diff_1 = require("diff");
const os_1 = require("os");
const path_1 = require("path");
const repl_1 = require("repl");
const vm_1 = require("vm");
const index_1 = require("./index");
const fs_1 = require("fs");
const console_1 = require("console");
/**
* Eval filename for REPL/debug.
* @internal
*/
exports.EVAL_FILENAME = `[eval].ts`;
function createRepl(options = {}) {
var _a, _b, _c, _d, _e;
let service = options.service;
const state = (_a = options.state) !== null && _a !== void 0 ? _a : new EvalState(path_1.join(process.cwd(), exports.EVAL_FILENAME));
const evalAwarePartialHost = createEvalAwarePartialHost(state);
const stdin = (_b = options.stdin) !== null && _b !== void 0 ? _b : process.stdin;
const stdout = (_c = options.stdout) !== null && _c !== void 0 ? _c : process.stdout;
const stderr = (_d = options.stderr) !== null && _d !== void 0 ? _d : process.stderr;
const _console = stdout === process.stdout && stderr === process.stderr ? console : new console_1.Console(stdout, stderr);
const replService = {
state: (_e = options.state) !== null && _e !== void 0 ? _e : new EvalState(path_1.join(process.cwd(), exports.EVAL_FILENAME)),
setService,
evalCode,
nodeEval,
evalAwarePartialHost,
start,
stdin,
stdout,
stderr,
console: _console
};
return replService;
function setService(_service) {
service = _service;
}
function evalCode(code) {
return _eval(service, state, code);
}
function nodeEval(code, _context, _filename, callback) {
let err = null;
let result;
// TODO: Figure out how to handle completion here.
if (code === '.scope') {
callback(err);
return;
}
try {
result = evalCode(code);
}
catch (error) {
if (error instanceof index_1.TSError) {
// Support recoverable compilations using >= node 6.
if (repl_1.Recoverable && isRecoverable(error)) {
err = new repl_1.Recoverable(error);
}
else {
console.error(error);
}
}
else {
err = error;
}
}
return callback(err, result);
}
function start(code) {
// TODO assert that service is set; remove all ! postfixes
return startRepl(replService, service, state, code);
}
}
exports.createRepl = createRepl;
/**
* Eval state management. Stores virtual `[eval].ts` file
*/
class EvalState {
constructor(path) {
this.path = path;
/** @internal */
this.input = '';
/** @internal */
this.output = '';
/** @internal */
this.version = 0;
/** @internal */
this.lines = 0;
}
}
exports.EvalState = EvalState;
function createEvalAwarePartialHost(state) {
function readFile(path) {
if (path === state.path)
return state.input;
try {
return fs_1.readFileSync(path, 'utf8');
}
catch (err) { /* Ignore. */ }
}
function fileExists(path) {
if (path === state.path)
return true;
try {
const stats = fs_1.statSync(path);
return stats.isFile() || stats.isFIFO();
}
catch (err) {
return false;
}
}
return { readFile, fileExists };
}
exports.createEvalAwarePartialHost = createEvalAwarePartialHost;
/**
* Evaluate the code snippet.
*/
function _eval(service, state, input) {
const lines = state.lines;
const isCompletion = !/\n$/.test(input);
const undo = appendEval(state, input);
let output;
try {
output = service.compile(state.input, state.path, -lines);
}
catch (err) {
undo();
throw err;
}
// Use `diff` to check for new JavaScript to execute.
const changes = diff_1.diffLines(state.output, output);
if (isCompletion) {
undo();
}
else {
state.output = output;
}
return changes.reduce((result, change) => {
return change.added ? exec(change.value, state.path) : result;
}, undefined);
}
/**
* Execute some code.
*/
function exec(code, filename) {
const script = new vm_1.Script(code, { filename: filename });
return script.runInThisContext();
}
/**
* Start a CLI REPL.
*/
function startRepl(replService, service, state, code) {
// Eval incoming code before the REPL starts.
if (code) {
try {
replService.evalCode(`${code}\n`);
}
catch (err) {
replService.console.error(err);
process.exit(1);
}
}
const repl = repl_1.start({
prompt: '> ',
input: replService.stdin,
output: replService.stdout,
// Mimicking node's REPL implementation: https://github.com/nodejs/node/blob/168b22ba073ee1cbf8d0bcb4ded7ff3099335d04/lib/internal/repl.js#L28-L30
terminal: replService.stdout.isTTY && !parseInt(process.env.NODE_NO_READLINE, 10),
eval: replService.nodeEval,
useGlobal: true
});
// Bookmark the point where we should reset the REPL state.
const resetEval = appendEval(state, '');
function reset() {
resetEval();
// Hard fix for TypeScript forcing `Object.defineProperty(exports, ...)`.
exec('exports = module.exports', state.path);
}
reset();
repl.on('reset', reset);
repl.defineCommand('type', {
help: 'Check the type of a TypeScript identifier',
action: function (identifier) {
if (!identifier) {
repl.displayPrompt();
return;
}
const undo = appendEval(state, identifier);
const { name, comment } = service.getTypeInfo(state.input, state.path, state.input.length);
undo();
if (name)
repl.outputStream.write(`${name}\n`);
if (comment)
repl.outputStream.write(`${comment}\n`);
repl.displayPrompt();
}
});
// Set up REPL history when available natively via node.js >= 11.
if (repl.setupHistory) {
const historyPath = process.env.TS_NODE_HISTORY || path_1.join(os_1.homedir(), '.ts_node_repl_history');
repl.setupHistory(historyPath, err => {
if (!err)
return;
replService.console.error(err);
process.exit(1);
});
}
}
/**
* Append to the eval instance and return an undo function.
*/
function appendEval(state, input) {
const undoInput = state.input;
const undoVersion = state.version;
const undoOutput = state.output;
const undoLines = state.lines;
// Handle ASI issues with TypeScript re-evaluation.
if (undoInput.charAt(undoInput.length - 1) === '\n' && /^\s*[\/\[(`-]/.test(input) && !/;\s*$/.test(undoInput)) {
state.input = `${state.input.slice(0, -1)};\n`;
}
state.input += input;
state.lines += lineCount(input);
state.version++;
return function () {
state.input = undoInput;
state.output = undoOutput;
state.version = undoVersion;
state.lines = undoLines;
};
}
/**
* Count the number of lines.
*/
function lineCount(value) {
let count = 0;
for (const char of value) {
if (char === '\n') {
count++;
}
}
return count;
}
const RECOVERY_CODES = new Set([
1003,
1005,
1109,
1126,
1160,
1161,
2355 // "A function whose declared type is neither 'void' nor 'any' must return a value."
]);
/**
* Check if a function can recover gracefully.
*/
function isRecoverable(error) {
return error.diagnosticCodes.every(code => RECOVERY_CODES.has(code));
}
//# sourceMappingURL=repl.js.map