Skip to content

Commit ad8257f

Browse files
lancesilverwind
authored andcommitted
repl: Assignment of _ allowed with warning
This commit addresses #5431 by changing the way that the repl handles assignment to the global _ variable. Prior to this commit, node sets the result of the last expression evaluated in the repl to `_`. This causes problems for users of underscore, lodash and other packages where it is common to assign `_` to the package, e.g. `_ = require('lodash');`. Changes in this commit now result in the following behavior. - If unassigned on the repl, `_` continues to refer to the last evaluated expression. - If assigned, the default behavior of assigning `_` to the last evaluated expression is disabled, and `_` now references whatever value was explicitly set. A warning is issued on the repl - 'expression assignment to _ now disabled'. - If `_` is assigned multiple times, the warning is only displayed once. - When `.clear` is executed in the repl, `_` continues to refer to its most recent value, whatever that is (this is per existing behavior). If `_` had been explicitly set prior to `.clear` it will not change again with the evaluation of the next expression. PR-URL: #5535 Fixes: #5431 Reviewed-By: Roman Reiss <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent c67937b commit ad8257f

File tree

3 files changed

+183
-5
lines changed

3 files changed

+183
-5
lines changed

doc/api/repl.markdown

+2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ The special variable `_` (underscore) contains the result of the last expression
8686
4
8787
```
8888

89+
Explicitly setting `_` will disable this behavior until the context is reset.
90+
8991
The REPL provides access to any variables in the global scope. You can expose
9092
a variable to the REPL explicitly by assigning it to the `context` object
9193
associated with each `REPLServer`. For example:

lib/repl.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ function REPLServer(prompt,
205205
self.useGlobal = !!useGlobal;
206206
self.ignoreUndefined = !!ignoreUndefined;
207207
self.replMode = replMode || exports.REPL_MODE_SLOPPY;
208+
self.underscoreAssigned = false;
209+
self.last = undefined;
208210

209211
self._inTemplateLiteral = false;
210212

@@ -471,7 +473,9 @@ function REPLServer(prompt,
471473
// the second argument to this function is there, print it.
472474
arguments.length === 2 &&
473475
(!self.ignoreUndefined || ret !== undefined)) {
474-
self.context._ = ret;
476+
if (!self.underscoreAssigned) {
477+
self.last = ret;
478+
}
475479
self.outputStream.write(self.writer(ret) + '\n');
476480
}
477481

@@ -545,27 +549,43 @@ REPLServer.prototype.createContext = function() {
545549
context.module = module;
546550
context.require = require;
547551

552+
553+
this.underscoreAssigned = false;
548554
this.lines = [];
549555
this.lines.level = [];
550556

551557
// make built-in modules available directly
552558
// (loaded lazily)
553-
exports._builtinLibs.forEach(function(name) {
559+
exports._builtinLibs.forEach((name) => {
554560
Object.defineProperty(context, name, {
555-
get: function() {
561+
get: () => {
556562
var lib = require(name);
557-
context._ = context[name] = lib;
563+
this.last = context[name] = lib;
558564
return lib;
559565
},
560566
// allow the creation of other globals with this name
561-
set: function(val) {
567+
set: (val) => {
562568
delete context[name];
563569
context[name] = val;
564570
},
565571
configurable: true
566572
});
567573
});
568574

575+
Object.defineProperty(context, '_', {
576+
configurable: true,
577+
get: () => {
578+
return this.last;
579+
},
580+
set: (value) => {
581+
this.last = value;
582+
if (!this.underscoreAssigned) {
583+
this.underscoreAssigned = true;
584+
this.outputStream.write('Expression assignment to _ now disabled.\n');
585+
}
586+
}
587+
});
588+
569589
return context;
570590
};
571591

test/parallel/test-repl-underscore.js

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const repl = require('repl');
6+
const stream = require('stream');
7+
8+
testSloppyMode();
9+
testStrictMode();
10+
testResetContext();
11+
testMagicMode();
12+
13+
function testSloppyMode() {
14+
const r = initRepl(repl.REPL_MODE_SLOPPY);
15+
16+
// cannot use `let` in sloppy mode
17+
r.write(`_; // initial value undefined
18+
var x = 10; // evaluates to undefined
19+
_; // still undefined
20+
y = 10; // evaluates to 10
21+
_; // 10 from last eval
22+
_ = 20; // explicitly set to 20
23+
_; // 20 from user input
24+
_ = 30; // make sure we can set it twice and no prompt
25+
_; // 30 from user input
26+
y = 40; // make sure eval doesn't change _
27+
_; // remains 30 from user input
28+
`);
29+
30+
assertOutput(r.output, [
31+
'undefined',
32+
'undefined',
33+
'undefined',
34+
'10',
35+
'10',
36+
'Expression assignment to _ now disabled.',
37+
'20',
38+
'20',
39+
'30',
40+
'30',
41+
'40',
42+
'30'
43+
]);
44+
}
45+
46+
function testStrictMode() {
47+
const r = initRepl(repl.REPL_MODE_STRICT);
48+
49+
r.write(`_; // initial value undefined
50+
var x = 10; // evaluates to undefined
51+
_; // still undefined
52+
let _ = 20; // use 'let' only in strict mode - evals to undefined
53+
_; // 20 from user input
54+
_ = 30; // make sure we can set it twice and no prompt
55+
_; // 30 from user input
56+
var y = 40; // make sure eval doesn't change _
57+
_; // remains 30 from user input
58+
function f() { let _ = 50; } // undefined
59+
f(); // undefined
60+
_; // remains 30 from user input
61+
`);
62+
63+
assertOutput(r.output, [
64+
'undefined',
65+
'undefined',
66+
'undefined',
67+
'undefined',
68+
'20',
69+
'30',
70+
'30',
71+
'undefined',
72+
'30',
73+
'undefined',
74+
'undefined',
75+
'30'
76+
]);
77+
}
78+
79+
function testMagicMode() {
80+
const r = initRepl(repl.REPL_MODE_MAGIC);
81+
82+
r.write(`_; // initial value undefined
83+
x = 10; //
84+
_; // last eval - 10
85+
let _ = 20; // undefined
86+
_; // 20 from user input
87+
_ = 30; // make sure we can set it twice and no prompt
88+
_; // 30 from user input
89+
var y = 40; // make sure eval doesn't change _
90+
_; // remains 30 from user input
91+
function f() { let _ = 50; return _; } // undefined
92+
f(); // 50
93+
_; // remains 30 from user input
94+
`);
95+
96+
assertOutput(r.output, [
97+
'undefined',
98+
'10',
99+
'10',
100+
'undefined',
101+
'20',
102+
'30',
103+
'30',
104+
'undefined',
105+
'30',
106+
'undefined',
107+
'50',
108+
'30'
109+
]);
110+
}
111+
112+
function testResetContext() {
113+
const r = initRepl(repl.REPL_MODE_SLOPPY);
114+
115+
r.write(`_ = 10; // explicitly set to 10
116+
_; // 10 from user input
117+
.clear // Clearing context...
118+
_; // remains 10
119+
x = 20; // but behavior reverts to last eval
120+
_; // expect 20
121+
`);
122+
123+
assertOutput(r.output, [
124+
'Expression assignment to _ now disabled.',
125+
'10',
126+
'10',
127+
'Clearing context...',
128+
'10',
129+
'20',
130+
'20'
131+
]);
132+
}
133+
134+
function initRepl(mode) {
135+
const inputStream = new stream.PassThrough();
136+
const outputStream = new stream.PassThrough();
137+
outputStream.accum = '';
138+
139+
outputStream.on('data', (data) => {
140+
outputStream.accum += data;
141+
});
142+
143+
return repl.start({
144+
input: inputStream,
145+
output: outputStream,
146+
useColors: false,
147+
terminal: false,
148+
prompt: '',
149+
replMode: mode
150+
});
151+
}
152+
153+
function assertOutput(output, expected) {
154+
const lines = output.accum.trim().split('\n');
155+
assert.deepStrictEqual(lines, expected);
156+
}

0 commit comments

Comments
 (0)