From ac6f5dd0e77f6b3d90d26451872661053fa81f42 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 21 Feb 2018 21:50:20 +0100 Subject: [PATCH 1/3] repl: make last error available as `_error` This is pretty useful when trying to inspect the last error caught by a REPL, and is made to be analogous to `_`, which contains the last successful completion value. --- doc/api/repl.md | 11 +++++ lib/repl.js | 18 +++++++ test/parallel/test-repl-underscore.js | 68 +++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/doc/api/repl.md b/doc/api/repl.md index 873e7449f53065..8c0ecd5e696fbb 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -162,6 +162,17 @@ Expression assignment to _ now disabled. 4 ``` +Similarly, `_err` will refer to the last seen error, if there was any. +Explicitly setting `_err` to a value will disable this behavior. + + +```js +> throw new Error('foo'); +Error: foo +> _err.message +'foo' +``` + ### Custom Evaluation Functions When a new `repl.REPLServer` is created, a custom evaluation function may be diff --git a/lib/repl.js b/lib/repl.js index 8201630c3a6389..76b894d1b51b49 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -156,6 +156,8 @@ function REPLServer(prompt, self.replMode = replMode || exports.REPL_MODE_SLOPPY; self.underscoreAssigned = false; self.last = undefined; + self.underscoreErrAssigned = false; + self.lastError = undefined; self.breakEvalOnSigint = !!breakEvalOnSigint; self.editorMode = false; // Context id for use with the inspector protocol. @@ -388,6 +390,8 @@ function REPLServer(prompt, internalUtil.decorateErrorStack(e); Error.prepareStackTrace = pstrace; const isError = internalUtil.isError(e); + if (!self.underscoreErrAssigned) + self.lastError = e; if (e instanceof SyntaxError && e.stack) { // remove repl:line-number and stack trace e.stack = e.stack @@ -796,6 +800,7 @@ REPLServer.prototype.createContext = function() { REPLServer.prototype.resetContext = function() { this.context = this.createContext(); this.underscoreAssigned = false; + this.underscoreErrAssigned = false; this.lines = []; this.lines.level = []; @@ -811,6 +816,19 @@ REPLServer.prototype.resetContext = function() { } }); + Object.defineProperty(this.context, '_err', { + configurable: true, + get: () => this.lastError, + set: (value) => { + this.lastError = value; + if (!this.underscoreErrAssigned) { + this.underscoreErrAssigned = true; + this.outputStream.write( + 'Expression assignment to _err now disabled.\n'); + } + } + }); + // Allow REPL extensions to extend the new context this.emit('reset', this.context); }; diff --git a/test/parallel/test-repl-underscore.js b/test/parallel/test-repl-underscore.js index 91f32223e180b9..4b609d83bbf38a 100644 --- a/test/parallel/test-repl-underscore.js +++ b/test/parallel/test-repl-underscore.js @@ -10,6 +10,7 @@ testStrictMode(); testResetContext(); testResetContextGlobal(); testMagicMode(); +testError(); function testSloppyMode() { const r = initRepl(repl.REPL_MODE_SLOPPY); @@ -153,6 +154,73 @@ function testResetContextGlobal() { delete global.require; } +function testError() { + const r = initRepl(repl.REPL_MODE_STRICT); + + r.write(`_err; // initial value undefined + throw new Error('foo'); // throws error + _err; // shows error + fs.readdirSync('/nonexistent?'); // throws error, sync + _err.code; // shows error code + _err.syscall; // shows error syscall + setImmediate(() => { throw new Error('baz'); }); undefined; + // throws error, async + `); + + setImmediate(() => { + const lines = r.output.accum.trim().split('\n'); + const expectedLines = [ + 'undefined', + + // The error, both from the original throw and the `_err` echo. + 'Error: foo', + 'Error: foo', + + // The sync error, with individual property echoes + /Error: ENOENT: no such file or directory, scandir '.*nonexistent.*'/, + /fs\.readdirSync/, + "'ENOENT'", + "'scandir'", + + // Dummy 'undefined' from the explicit silencer + one from the comment + 'undefined', + 'undefined', + + // The message from the original throw + 'Error: baz', + /setImmediate/, + /^ at/, + /^ at/, + /^ at/, + /^ at/, + ]; + for (const line of lines) { + const expected = expectedLines.shift(); + if (typeof expected === 'string') + assert.strictEqual(line, expected); + else + assert(expected.test(line), `${line} should match ${expected}`); + } + assert.strictEqual(expectedLines.length, 0); + + // Reset output, check that '_err' is the asynchronously caught error. + r.output.accum = ''; + r.write(`_err.message // show the message + _err = 0; // disable auto-assignment + throw new Error('quux'); // new error + _err; // should not see the new error + `); + + assertOutput(r.output, [ + "'baz'", + 'Expression assignment to _err now disabled.', + '0', + 'Error: quux', + '0' + ]); + }); +} + function initRepl(mode, useGlobal) { const inputStream = new stream.PassThrough(); const outputStream = new stream.PassThrough(); From fb8b01ff9fbd2304df94001739f87962c9080e69 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 21 Feb 2018 22:48:49 +0100 Subject: [PATCH 2/3] [squash] add changes: section --- doc/api/repl.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/api/repl.md b/doc/api/repl.md index 8c0ecd5e696fbb..ebb83e89069495 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -142,6 +142,12 @@ global or scoped variable, the input `fs` will be evaluated on-demand as ``` #### Assignment of the `_` (underscore) variable + The default evaluator will, by default, assign the result of the most recently evaluated expression to the special variable `_` (underscore). From 186769087139f57245a473e18b83dac82af0b3d9 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 26 Feb 2018 20:41:01 +0100 Subject: [PATCH 3/3] [squash] switch to error --- doc/api/repl.md | 8 ++++---- lib/repl.js | 4 ++-- test/parallel/test-repl-underscore.js | 22 +++++++++++----------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/api/repl.md b/doc/api/repl.md index ebb83e89069495..c868106f2f946a 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -146,7 +146,7 @@ global or scoped variable, the input `fs` will be evaluated on-demand as changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/18919 - description: Added `_err` support. + description: Added `_error` support. --> The default evaluator will, by default, assign the result of the most recently @@ -168,14 +168,14 @@ Expression assignment to _ now disabled. 4 ``` -Similarly, `_err` will refer to the last seen error, if there was any. -Explicitly setting `_err` to a value will disable this behavior. +Similarly, `_error` will refer to the last seen error, if there was any. +Explicitly setting `_error` to a value will disable this behavior. ```js > throw new Error('foo'); Error: foo -> _err.message +> _error.message 'foo' ``` diff --git a/lib/repl.js b/lib/repl.js index 76b894d1b51b49..4bcbd1a2db09f4 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -816,7 +816,7 @@ REPLServer.prototype.resetContext = function() { } }); - Object.defineProperty(this.context, '_err', { + Object.defineProperty(this.context, '_error', { configurable: true, get: () => this.lastError, set: (value) => { @@ -824,7 +824,7 @@ REPLServer.prototype.resetContext = function() { if (!this.underscoreErrAssigned) { this.underscoreErrAssigned = true; this.outputStream.write( - 'Expression assignment to _err now disabled.\n'); + 'Expression assignment to _error now disabled.\n'); } } }); diff --git a/test/parallel/test-repl-underscore.js b/test/parallel/test-repl-underscore.js index 4b609d83bbf38a..57929244ae4374 100644 --- a/test/parallel/test-repl-underscore.js +++ b/test/parallel/test-repl-underscore.js @@ -157,12 +157,12 @@ function testResetContextGlobal() { function testError() { const r = initRepl(repl.REPL_MODE_STRICT); - r.write(`_err; // initial value undefined + r.write(`_error; // initial value undefined throw new Error('foo'); // throws error - _err; // shows error + _error; // shows error fs.readdirSync('/nonexistent?'); // throws error, sync - _err.code; // shows error code - _err.syscall; // shows error syscall + _error.code; // shows error code + _error.syscall; // shows error syscall setImmediate(() => { throw new Error('baz'); }); undefined; // throws error, async `); @@ -172,7 +172,7 @@ function testError() { const expectedLines = [ 'undefined', - // The error, both from the original throw and the `_err` echo. + // The error, both from the original throw and the `_error` echo. 'Error: foo', 'Error: foo', @@ -203,17 +203,17 @@ function testError() { } assert.strictEqual(expectedLines.length, 0); - // Reset output, check that '_err' is the asynchronously caught error. + // Reset output, check that '_error' is the asynchronously caught error. r.output.accum = ''; - r.write(`_err.message // show the message - _err = 0; // disable auto-assignment - throw new Error('quux'); // new error - _err; // should not see the new error + r.write(`_error.message // show the message + _error = 0; // disable auto-assignment + throw new Error('quux'); // new error + _error; // should not see the new error `); assertOutput(r.output, [ "'baz'", - 'Expression assignment to _err now disabled.', + 'Expression assignment to _error now disabled.', '0', 'Error: quux', '0'