diff --git a/doc/api/repl.md b/doc/api/repl.md index 47984e67123648..26f2889f0b45f3 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -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 @@ -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. diff --git a/lib/repl.js b/lib/repl.js index c7a0030244ec6d..b58cf499cda226 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1177,3 +1177,4 @@ function Recoverable(err) { this.err = err; } inherits(Recoverable, SyntaxError); +exports.Recoverable = Recoverable; diff --git a/test/parallel/test-repl-recoverable.js b/test/parallel/test-repl-recoverable.js new file mode 100644 index 00000000000000..6788d84595066c --- /dev/null +++ b/test/parallel/test-repl-recoverable.js @@ -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); +});