repl.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.createEvalAwarePartialHost = exports.EvalState = exports.createRepl = exports.EVAL_FILENAME = void 0;
  4. const diff_1 = require("diff");
  5. const os_1 = require("os");
  6. const path_1 = require("path");
  7. const repl_1 = require("repl");
  8. const vm_1 = require("vm");
  9. const index_1 = require("./index");
  10. const fs_1 = require("fs");
  11. const console_1 = require("console");
  12. /**
  13. * Eval filename for REPL/debug.
  14. * @internal
  15. */
  16. exports.EVAL_FILENAME = `[eval].ts`;
  17. function createRepl(options = {}) {
  18. var _a, _b, _c, _d, _e;
  19. let service = options.service;
  20. const state = (_a = options.state) !== null && _a !== void 0 ? _a : new EvalState(path_1.join(process.cwd(), exports.EVAL_FILENAME));
  21. const evalAwarePartialHost = createEvalAwarePartialHost(state);
  22. const stdin = (_b = options.stdin) !== null && _b !== void 0 ? _b : process.stdin;
  23. const stdout = (_c = options.stdout) !== null && _c !== void 0 ? _c : process.stdout;
  24. const stderr = (_d = options.stderr) !== null && _d !== void 0 ? _d : process.stderr;
  25. const _console = stdout === process.stdout && stderr === process.stderr ? console : new console_1.Console(stdout, stderr);
  26. const replService = {
  27. state: (_e = options.state) !== null && _e !== void 0 ? _e : new EvalState(path_1.join(process.cwd(), exports.EVAL_FILENAME)),
  28. setService,
  29. evalCode,
  30. nodeEval,
  31. evalAwarePartialHost,
  32. start,
  33. stdin,
  34. stdout,
  35. stderr,
  36. console: _console
  37. };
  38. return replService;
  39. function setService(_service) {
  40. service = _service;
  41. }
  42. function evalCode(code) {
  43. return _eval(service, state, code);
  44. }
  45. function nodeEval(code, _context, _filename, callback) {
  46. let err = null;
  47. let result;
  48. // TODO: Figure out how to handle completion here.
  49. if (code === '.scope') {
  50. callback(err);
  51. return;
  52. }
  53. try {
  54. result = evalCode(code);
  55. }
  56. catch (error) {
  57. if (error instanceof index_1.TSError) {
  58. // Support recoverable compilations using >= node 6.
  59. if (repl_1.Recoverable && isRecoverable(error)) {
  60. err = new repl_1.Recoverable(error);
  61. }
  62. else {
  63. console.error(error);
  64. }
  65. }
  66. else {
  67. err = error;
  68. }
  69. }
  70. return callback(err, result);
  71. }
  72. function start(code) {
  73. // TODO assert that service is set; remove all ! postfixes
  74. return startRepl(replService, service, state, code);
  75. }
  76. }
  77. exports.createRepl = createRepl;
  78. /**
  79. * Eval state management. Stores virtual `[eval].ts` file
  80. */
  81. class EvalState {
  82. constructor(path) {
  83. this.path = path;
  84. /** @internal */
  85. this.input = '';
  86. /** @internal */
  87. this.output = '';
  88. /** @internal */
  89. this.version = 0;
  90. /** @internal */
  91. this.lines = 0;
  92. }
  93. }
  94. exports.EvalState = EvalState;
  95. function createEvalAwarePartialHost(state) {
  96. function readFile(path) {
  97. if (path === state.path)
  98. return state.input;
  99. try {
  100. return fs_1.readFileSync(path, 'utf8');
  101. }
  102. catch (err) { /* Ignore. */ }
  103. }
  104. function fileExists(path) {
  105. if (path === state.path)
  106. return true;
  107. try {
  108. const stats = fs_1.statSync(path);
  109. return stats.isFile() || stats.isFIFO();
  110. }
  111. catch (err) {
  112. return false;
  113. }
  114. }
  115. return { readFile, fileExists };
  116. }
  117. exports.createEvalAwarePartialHost = createEvalAwarePartialHost;
  118. /**
  119. * Evaluate the code snippet.
  120. */
  121. function _eval(service, state, input) {
  122. const lines = state.lines;
  123. const isCompletion = !/\n$/.test(input);
  124. const undo = appendEval(state, input);
  125. let output;
  126. try {
  127. output = service.compile(state.input, state.path, -lines);
  128. }
  129. catch (err) {
  130. undo();
  131. throw err;
  132. }
  133. // Use `diff` to check for new JavaScript to execute.
  134. const changes = diff_1.diffLines(state.output, output);
  135. if (isCompletion) {
  136. undo();
  137. }
  138. else {
  139. state.output = output;
  140. }
  141. return changes.reduce((result, change) => {
  142. return change.added ? exec(change.value, state.path) : result;
  143. }, undefined);
  144. }
  145. /**
  146. * Execute some code.
  147. */
  148. function exec(code, filename) {
  149. const script = new vm_1.Script(code, { filename: filename });
  150. return script.runInThisContext();
  151. }
  152. /**
  153. * Start a CLI REPL.
  154. */
  155. function startRepl(replService, service, state, code) {
  156. // Eval incoming code before the REPL starts.
  157. if (code) {
  158. try {
  159. replService.evalCode(`${code}\n`);
  160. }
  161. catch (err) {
  162. replService.console.error(err);
  163. process.exit(1);
  164. }
  165. }
  166. const repl = repl_1.start({
  167. prompt: '> ',
  168. input: replService.stdin,
  169. output: replService.stdout,
  170. // Mimicking node's REPL implementation: https://github.com/nodejs/node/blob/168b22ba073ee1cbf8d0bcb4ded7ff3099335d04/lib/internal/repl.js#L28-L30
  171. terminal: replService.stdout.isTTY && !parseInt(process.env.NODE_NO_READLINE, 10),
  172. eval: replService.nodeEval,
  173. useGlobal: true
  174. });
  175. // Bookmark the point where we should reset the REPL state.
  176. const resetEval = appendEval(state, '');
  177. function reset() {
  178. resetEval();
  179. // Hard fix for TypeScript forcing `Object.defineProperty(exports, ...)`.
  180. exec('exports = module.exports', state.path);
  181. }
  182. reset();
  183. repl.on('reset', reset);
  184. repl.defineCommand('type', {
  185. help: 'Check the type of a TypeScript identifier',
  186. action: function (identifier) {
  187. if (!identifier) {
  188. repl.displayPrompt();
  189. return;
  190. }
  191. const undo = appendEval(state, identifier);
  192. const { name, comment } = service.getTypeInfo(state.input, state.path, state.input.length);
  193. undo();
  194. if (name)
  195. repl.outputStream.write(`${name}\n`);
  196. if (comment)
  197. repl.outputStream.write(`${comment}\n`);
  198. repl.displayPrompt();
  199. }
  200. });
  201. // Set up REPL history when available natively via node.js >= 11.
  202. if (repl.setupHistory) {
  203. const historyPath = process.env.TS_NODE_HISTORY || path_1.join(os_1.homedir(), '.ts_node_repl_history');
  204. repl.setupHistory(historyPath, err => {
  205. if (!err)
  206. return;
  207. replService.console.error(err);
  208. process.exit(1);
  209. });
  210. }
  211. }
  212. /**
  213. * Append to the eval instance and return an undo function.
  214. */
  215. function appendEval(state, input) {
  216. const undoInput = state.input;
  217. const undoVersion = state.version;
  218. const undoOutput = state.output;
  219. const undoLines = state.lines;
  220. // Handle ASI issues with TypeScript re-evaluation.
  221. if (undoInput.charAt(undoInput.length - 1) === '\n' && /^\s*[\/\[(`-]/.test(input) && !/;\s*$/.test(undoInput)) {
  222. state.input = `${state.input.slice(0, -1)};\n`;
  223. }
  224. state.input += input;
  225. state.lines += lineCount(input);
  226. state.version++;
  227. return function () {
  228. state.input = undoInput;
  229. state.output = undoOutput;
  230. state.version = undoVersion;
  231. state.lines = undoLines;
  232. };
  233. }
  234. /**
  235. * Count the number of lines.
  236. */
  237. function lineCount(value) {
  238. let count = 0;
  239. for (const char of value) {
  240. if (char === '\n') {
  241. count++;
  242. }
  243. }
  244. return count;
  245. }
  246. const RECOVERY_CODES = new Set([
  247. 1003,
  248. 1005,
  249. 1109,
  250. 1126,
  251. 1160,
  252. 1161,
  253. 2355 // "A function whose declared type is neither 'void' nor 'any' must return a value."
  254. ]);
  255. /**
  256. * Check if a function can recover gracefully.
  257. */
  258. function isRecoverable(error) {
  259. return error.diagnosticCodes.every(code => RECOVERY_CODES.has(code));
  260. }
  261. //# sourceMappingURL=repl.js.map