Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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.
  • Loading branch information
addaleax committed Feb 26, 2018
commit ac6f5dd0e77f6b3d90d26451872661053fa81f42
11 changes: 11 additions & 0 deletions doc/api/repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ Expression assignment to _ now disabled.
4
```

Similarly, `_err` will refer to the last seen error, if there was any.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way we could add a Changes section for this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evanlucas done!

Explicitly setting `_err` to a value will disable this behavior.

<!-- eslint-skip -->
```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
Expand Down
18 changes: 18 additions & 0 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = [];

Expand All @@ -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);
};
Expand Down
68 changes: 68 additions & 0 deletions test/parallel/test-repl-underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ testStrictMode();
testResetContext();
testResetContextGlobal();
testMagicMode();
testError();

function testSloppyMode() {
const r = initRepl(repl.REPL_MODE_SLOPPY);
Expand Down Expand Up @@ -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();
Expand Down