summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/repl.md29
-rw-r--r--lib/repl.js1
-rw-r--r--test/parallel/test-repl-recoverable.js40
3 files changed, 65 insertions, 5 deletions
diff --git a/doc/api/repl.md b/doc/api/repl.md
index 47984e6712..26f2889f0b 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 c7a0030244..b58cf499cd 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 0000000000..6788d84595
--- /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);
+});