Skip to content

Commit 96f0bec

Browse 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

3 files changed

Lines changed: 103 additions & 0 deletions

File tree

‎doc/api/repl.md‎

Lines changed: 17 additions & 0 deletions
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+
>thrownewError('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

‎lib/repl.js‎

Lines 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
constisError=internalUtil.isError(e);
300+
if(!self.underscoreErrAssigned)
301+
self.lastError=e;
298302
if(einstanceofSyntaxError&&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
};

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

Lines 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
functiontestSloppyMode(){
1516
constr=initRepl(repl.REPL_MODE_SLOPPY);
@@ -153,6 +154,73 @@ function testResetContextGlobal() {
153154
deleteglobal.require;
154155
}
155156

157+
functiontestError(){
158+
constr=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+
constlines=r.output.accum.trim().split('\n');
172+
constexpectedLines=[
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:nosuchfileordirectory,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(constlineoflines){
198+
constexpected=expectedLines.shift();
199+
if(typeofexpected==='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
functioninitRepl(mode,useGlobal){
157225
constinputStream=newstream.PassThrough();
158226
constoutputStream=newstream.PassThrough();

0 commit comments

Comments
 (0)