Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 96f0bec

Browse filesBrowse files
committed
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: #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>
1 parent 5c4f703 commit 96f0bec
Copy full SHA for 96f0bec

File tree

Expand file treeCollapse file tree

3 files changed

+103
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+103
-0
lines changed
Open diff view settings
Collapse file

‎doc/api/repl.md‎

Copy file name to clipboardExpand all lines: doc/api/repl.md
+17Lines changed: 17 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ global or scoped variable, the input `fs` will be evaluated on-demand as
142142
```
143143

144144
#### Assignment of the `_` (underscore) variable
145+
<!-- YAML
146+
changes:
147+
- version: REPLACEME
148+
pr-url: https://github.com/nodejs/node/pull/18919
149+
description: Added `_error` support.
150+
-->
145151

146152
The default evaluator will, by default, assign the result of the most recently
147153
evaluated expression to the special variable `_` (underscore).
@@ -162,6 +168,17 @@ Expression assignment to _ now disabled.
162168
4
163169
```
164170

171+
Similarly, `_error` will refer to the last seen error, if there was any.
172+
Explicitly setting `_error` to a value will disable this behavior.
173+
174+
<!-- eslint-skip -->
175+
```js
176+
> throw new Error('foo');
177+
Error: foo
178+
> _error.message
179+
'foo'
180+
```
181+
165182
### Custom Evaluation Functions
166183

167184
When a new `repl.REPLServer` is created, a custom evaluation function may be
Collapse file

‎lib/repl.js‎

Copy file name to clipboardExpand all lines: lib/repl.js
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ function REPLServer(prompt,
155155
self.replMode = replMode || exports.REPL_MODE_SLOPPY;
156156
self.underscoreAssigned = false;
157157
self.last = undefined;
158+
self.underscoreErrAssigned = false;
159+
self.lastError = undefined;
158160
self.breakEvalOnSigint = !!breakEvalOnSigint;
159161
self.editorMode = false;
160162
// Context id for use with the inspector protocol.
@@ -295,6 +297,8 @@ function REPLServer(prompt,
295297
internalUtil.decorateErrorStack(e);
296298
Error.prepareStackTrace = pstrace;
297299
const isError = internalUtil.isError(e);
300+
if (!self.underscoreErrAssigned)
301+
self.lastError = e;
298302
if (e instanceof SyntaxError && e.stack) {
299303
// remove repl:line-number and stack trace
300304
e.stack = e.stack
@@ -693,6 +697,7 @@ REPLServer.prototype.createContext = function() {
693697
REPLServer.prototype.resetContext = function() {
694698
this.context = this.createContext();
695699
this.underscoreAssigned = false;
700+
this.underscoreErrAssigned = false;
696701
this.lines = [];
697702
this.lines.level = [];
698703

@@ -708,6 +713,19 @@ REPLServer.prototype.resetContext = function() {
708713
}
709714
});
710715

716+
Object.defineProperty(this.context, '_error', {
717+
configurable: true,
718+
get: () => this.lastError,
719+
set: (value) => {
720+
this.lastError = value;
721+
if (!this.underscoreErrAssigned) {
722+
this.underscoreErrAssigned = true;
723+
this.outputStream.write(
724+
'Expression assignment to _error now disabled.\n');
725+
}
726+
}
727+
});
728+
711729
// Allow REPL extensions to extend the new context
712730
this.emit('reset', this.context);
713731
};
Collapse file

‎test/parallel/test-repl-underscore.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-repl-underscore.js
+68Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ testStrictMode();
1010
testResetContext();
1111
testResetContextGlobal();
1212
testMagicMode();
13+
testError();
1314

1415
function testSloppyMode() {
1516
const r = initRepl(repl.REPL_MODE_SLOPPY);
@@ -153,6 +154,73 @@ function testResetContextGlobal() {
153154
delete global.require;
154155
}
155156

157+
function testError() {
158+
const r = initRepl(repl.REPL_MODE_STRICT);
159+
160+
r.write(`_error; // initial value undefined
161+
throw new Error('foo'); // throws error
162+
_error; // shows error
163+
fs.readdirSync('/nonexistent?'); // throws error, sync
164+
_error.code; // shows error code
165+
_error.syscall; // shows error syscall
166+
setImmediate(() => { throw new Error('baz'); }); undefined;
167+
// throws error, async
168+
`);
169+
170+
setImmediate(() => {
171+
const lines = r.output.accum.trim().split('\n');
172+
const expectedLines = [
173+
'undefined',
174+
175+
// The error, both from the original throw and the `_error` echo.
176+
'Error: foo',
177+
'Error: foo',
178+
179+
// The sync error, with individual property echoes
180+
/Error: ENOENT: no such file or directory, scandir '.*nonexistent.*'/,
181+
/fs\.readdirSync/,
182+
"'ENOENT'",
183+
"'scandir'",
184+
185+
// Dummy 'undefined' from the explicit silencer + one from the comment
186+
'undefined',
187+
'undefined',
188+
189+
// The message from the original throw
190+
'Error: baz',
191+
/setImmediate/,
192+
/^ at/,
193+
/^ at/,
194+
/^ at/,
195+
/^ at/,
196+
];
197+
for (const line of lines) {
198+
const expected = expectedLines.shift();
199+
if (typeof expected === 'string')
200+
assert.strictEqual(line, expected);
201+
else
202+
assert(expected.test(line), `${line} should match ${expected}`);
203+
}
204+
assert.strictEqual(expectedLines.length, 0);
205+
206+
// Reset output, check that '_error' is the asynchronously caught error.
207+
r.output.accum = '';
208+
r.write(`_error.message // show the message
209+
_error = 0; // disable auto-assignment
210+
throw new Error('quux'); // new error
211+
_error; // should not see the new error
212+
`);
213+
214+
assertOutput(r.output, [
215+
"'baz'",
216+
'Expression assignment to _error now disabled.',
217+
'0',
218+
'Error: quux',
219+
'0'
220+
]);
221+
});
222+
}
223+
156224
function initRepl(mode, useGlobal) {
157225
const inputStream = new stream.PassThrough();
158226
const outputStream = new stream.PassThrough();

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.