Skip to content

Commit

Permalink
repl: exports Recoverable
Browse files Browse the repository at this point in the history
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 <cjihrig@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
  • Loading branch information
blakeembrey authored and evanlucas committed May 17, 2016
1 parent 29b28a2 commit ce2d5be
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 5 deletions.
29 changes: 24 additions & 5 deletions doc/api/repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,10 @@ the following values:
have ANSI/VT100 escape codes written to it. Defaults to checking `isTTY`
on the `output` stream upon instantiation.

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

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

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

function eval(cmd, context, filename, callback) {
callback(null, result);
```js
function eval(cmd, context, filename, callback) {
var result;
try {
result = vm.runInThisContext(cmd);
} catch (e) {
if (isRecoverableError(e)) {
return callback(new repl.Recoverable(e));
}
}
callback(null, result);
}

function isRecoverableError(error) {
if (error.name === 'SyntaxError') {
return /^(Unexpected end of input|Unexpected token)/.test(error.message);
}
return false;
}
```

On tab completion, `eval` will be called with `.scope` as an input string. It
is expected to return an array of scope names to be used for the auto-completion.
Expand Down
1 change: 1 addition & 0 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1177,3 +1177,4 @@ function Recoverable(err) {
this.err = err;
}
inherits(Recoverable, SyntaxError);
exports.Recoverable = Recoverable;
40 changes: 40 additions & 0 deletions test/parallel/test-repl-recoverable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const repl = require('repl');

let evalCount = 0;
let recovered = false;
let rendered = false;

function customEval(code, context, file, cb) {
evalCount++;

return cb(evalCount === 1 ? new repl.Recoverable() : null, true);
}

const putIn = new common.ArrayStream();

putIn.write = function(msg) {
if (msg === '... ') {
recovered = true;
}

if (msg === 'true\n') {
rendered = true;
}
};

repl.start('', putIn, customEval);

// https://github.com/nodejs/node/issues/2939
// Expose recoverable errors to the consumer.
putIn.emit('data', '1\n');
putIn.emit('data', '2\n');

process.on('exit', function() {
assert(recovered, 'REPL never recovered');
assert(rendered, 'REPL never rendered the result');
assert.strictEqual(evalCount, 2);
});

0 comments on commit ce2d5be

Please sign in to comment.