Skip to content

Commit ce2d5be

Browse files
blakeembreyevanlucas
authored andcommitted
repl: exports Recoverable
Allow REPL consumers to callback with a `Recoverable` error instance and trigger multi-line REPL prompts. Fixes: #2939 PR-URL: #3488 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
1 parent 29b28a2 commit ce2d5be

File tree

3 files changed

+65
-5
lines changed

3 files changed

+65
-5
lines changed

doc/api/repl.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,10 @@ the following values:
262262
have ANSI/VT100 escape codes written to it. Defaults to checking `isTTY`
263263
on the `output` stream upon instantiation.
264264

265-
- `eval` - function that will be used to eval each given line. Defaults to
266-
an async wrapper for `eval()`. See below for an example of a custom `eval`.
265+
- `eval` - a function that will be used to eval each given line. Defaults to
266+
an async wrapper for `eval()`. An `eval` function can error with
267+
`repl.Recoverable` to indicate the code was incomplete and prompt for more
268+
lines. See below for an example of a custom `eval`.
267269

268270
- `useColors` - a boolean which specifies whether or not the `writer` function
269271
should output colors. If a different `writer` function is set then this does
@@ -287,11 +289,28 @@ the following values:
287289
* `repl.REPL_MODE_MAGIC` - attempt to run commands in default mode. If they
288290
fail to parse, re-try in strict mode.
289291

290-
You can use your own `eval` function if it has following signature:
292+
It is possible to use a custom `eval` function as illustrated below:
291293

292-
function eval(cmd, context, filename, callback) {
293-
callback(null, result);
294+
```js
295+
function eval(cmd, context, filename, callback) {
296+
var result;
297+
try {
298+
result = vm.runInThisContext(cmd);
299+
} catch (e) {
300+
if (isRecoverableError(e)) {
301+
return callback(new repl.Recoverable(e));
294302
}
303+
}
304+
callback(null, result);
305+
}
306+
307+
function isRecoverableError(error) {
308+
if (error.name === 'SyntaxError') {
309+
return /^(Unexpected end of input|Unexpected token)/.test(error.message);
310+
}
311+
return false;
312+
}
313+
```
295314

296315
On tab completion, `eval` will be called with `.scope` as an input string. It
297316
is expected to return an array of scope names to be used for the auto-completion.

lib/repl.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,3 +1177,4 @@ function Recoverable(err) {
11771177
this.err = err;
11781178
}
11791179
inherits(Recoverable, SyntaxError);
1180+
exports.Recoverable = Recoverable;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const repl = require('repl');
6+
7+
let evalCount = 0;
8+
let recovered = false;
9+
let rendered = false;
10+
11+
function customEval(code, context, file, cb) {
12+
evalCount++;
13+
14+
return cb(evalCount === 1 ? new repl.Recoverable() : null, true);
15+
}
16+
17+
const putIn = new common.ArrayStream();
18+
19+
putIn.write = function(msg) {
20+
if (msg === '... ') {
21+
recovered = true;
22+
}
23+
24+
if (msg === 'true\n') {
25+
rendered = true;
26+
}
27+
};
28+
29+
repl.start('', putIn, customEval);
30+
31+
// https://github.com/nodejs/node/issues/2939
32+
// Expose recoverable errors to the consumer.
33+
putIn.emit('data', '1\n');
34+
putIn.emit('data', '2\n');
35+
36+
process.on('exit', function() {
37+
assert(recovered, 'REPL never recovered');
38+
assert(rendered, 'REPL never rendered the result');
39+
assert.strictEqual(evalCount, 2);
40+
});

0 commit comments

Comments
 (0)