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+
functioneval(cmd, context, filename, callback){
296+
var result;
297+
try{
298+
result =vm.runInThisContext(cmd);
299+
} catch (e){
300+
if (isRecoverableError(e)){
301+
returncallback(newrepl.Recoverable(e));
294302
}
303+
}
304+
callback(null, result);
305+
}
306+
307+
functionisRecoverableError(error){
308+
if (error.name==='SyntaxError'){
309+
return/^(Unexpected end of input|Unexpected token)/.test(error.message);
310+
}
311+
returnfalse;
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+
constcommon=require('../common');
4+
constassert=require('assert');
5+
constrepl=require('repl');
6+
7+
letevalCount=0;
8+
letrecovered=false;
9+
letrendered=false;
10+
11+
functioncustomEval(code,context,file,cb){
12+
evalCount++;
13+
14+
returncb(evalCount===1 ? newrepl.Recoverable() : null,true);
15+
}
16+
17+
constputIn=newcommon.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)