mirror of https://github.com/nodejs/node.git
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. PR-URL: https://github.com/nodejs/node/pull/18919 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Evan Lucas <evanlucas@me.com> Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Prince John Wesley <princejohnwesley@gmail.com> Reviewed-By: Shingo Inoue <leko.noor@gmail.com>
This commit is contained in:
parent
648d668fcc
commit
a8b5192fef
|
@ -142,6 +142,12 @@ global or scoped variable, the input `fs` will be evaluated on-demand as
|
|||
```
|
||||
|
||||
#### Assignment of the `_` (underscore) variable
|
||||
<!-- YAML
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/18919
|
||||
description: Added `_error` support.
|
||||
-->
|
||||
|
||||
The default evaluator will, by default, assign the result of the most recently
|
||||
evaluated expression to the special variable `_` (underscore).
|
||||
|
@ -162,6 +168,17 @@ Expression assignment to _ now disabled.
|
|||
4
|
||||
```
|
||||
|
||||
Similarly, `_error` will refer to the last seen error, if there was any.
|
||||
Explicitly setting `_error` to a value will disable this behavior.
|
||||
|
||||
<!-- eslint-skip -->
|
||||
```js
|
||||
> throw new Error('foo');
|
||||
Error: foo
|
||||
> _error.message
|
||||
'foo'
|
||||
```
|
||||
|
||||
### Custom Evaluation Functions
|
||||
|
||||
When a new `repl.REPLServer` is created, a custom evaluation function may be
|
||||
|
|
18
lib/repl.js
18
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, '_error', {
|
||||
configurable: true,
|
||||
get: () => this.lastError,
|
||||
set: (value) => {
|
||||
this.lastError = value;
|
||||
if (!this.underscoreErrAssigned) {
|
||||
this.underscoreErrAssigned = true;
|
||||
this.outputStream.write(
|
||||
'Expression assignment to _error now disabled.\n');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Allow REPL extensions to extend the new context
|
||||
this.emit('reset', this.context);
|
||||
};
|
||||
|
|
|
@ -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(`_error; // initial value undefined
|
||||
throw new Error('foo'); // throws error
|
||||
_error; // shows error
|
||||
fs.readdirSync('/nonexistent?'); // throws error, sync
|
||||
_error.code; // shows error code
|
||||
_error.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 `_error` 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 '_error' is the asynchronously caught error.
|
||||
r.output.accum = '';
|
||||
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 _error now disabled.',
|
||||
'0',
|
||||
'Error: quux',
|
||||
'0'
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
function initRepl(mode, useGlobal) {
|
||||
const inputStream = new stream.PassThrough();
|
||||
const outputStream = new stream.PassThrough();
|
||||
|
|
Loading…
Reference in New Issue