Skip to content

Commit fce4b98

Browse files
committed
repl,util: insert carriage returns in output
`\n` is not enough for Linux with some custom stream add carriage returns to ensure that the output is displayed correctly using `\r\n` should not be a problem, even on non-Windows platforms. Fixes: #7954 PR-URL: #8028 Reviewed-By: Brian White <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 6a3dbda commit fce4b98

19 files changed

+201
-191
lines changed

lib/repl.js

+28-26
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ function REPLServer(prompt,
242242
var err, result, retry = false, input = code, wrappedErr;
243243
// first, create the Script object to check the syntax
244244

245-
if (code === '\n')
245+
if (code === '\n' || code === '\r\n')
246246
return cb(null);
247247

248248
while (true) {
@@ -251,7 +251,7 @@ function REPLServer(prompt,
251251
(self.replMode === exports.REPL_MODE_STRICT || retry)) {
252252
// "void 0" keeps the repl from returning "use strict" as the
253253
// result value for let/const statements.
254-
code = `'use strict'; void 0;\n${code}`;
254+
code = `'use strict'; void 0;\r\n${code}`;
255255
}
256256
var script = vm.createScript(code, {
257257
filename: file,
@@ -265,7 +265,7 @@ function REPLServer(prompt,
265265
if (self.wrappedCmd) {
266266
self.wrappedCmd = false;
267267
// unwrap and try again
268-
code = `${input.substring(1, input.length - 2)}\n`;
268+
code = `${input.substring(1, input.length - 2)}\r\n`;
269269
wrappedErr = e;
270270
} else {
271271
retry = true;
@@ -367,7 +367,7 @@ function REPLServer(prompt,
367367
e.stack = e.stack.replace(/(\s+at\s+repl:)(\d+)/,
368368
(_, pre, line) => pre + (line - 1));
369369
}
370-
top.outputStream.write((e.stack || e) + '\n');
370+
top.outputStream.write((e.stack || e) + '\r\n');
371371
top.lineParser.reset();
372372
top.bufferedCommand = '';
373373
top.lines.level = [];
@@ -453,7 +453,7 @@ function REPLServer(prompt,
453453
sawSIGINT = false;
454454
return;
455455
}
456-
self.output.write('(To exit, press ^C again or type .exit)\n');
456+
self.output.write('(To exit, press ^C again or type .exit)\r\n');
457457
sawSIGINT = true;
458458
} else {
459459
sawSIGINT = false;
@@ -470,7 +470,7 @@ function REPLServer(prompt,
470470
sawSIGINT = false;
471471

472472
if (self.editorMode) {
473-
self.bufferedCommand += cmd + '\n';
473+
self.bufferedCommand += cmd + '\r\n';
474474
return;
475475
}
476476

@@ -490,7 +490,7 @@ function REPLServer(prompt,
490490
if (self.parseREPLKeyword(keyword, rest) === true) {
491491
return;
492492
} else if (!self.bufferedCommand) {
493-
self.outputStream.write('Invalid REPL keyword\n');
493+
self.outputStream.write('Invalid REPL keyword\r\n');
494494
finish(null);
495495
return;
496496
}
@@ -509,8 +509,8 @@ function REPLServer(prompt,
509509
self.wrappedCmd = false;
510510
if (e && !self.bufferedCommand && cmd.trim().startsWith('npm ')) {
511511
self.outputStream.write('npm should be run outside of the ' +
512-
'node repl, in your normal shell.\n' +
513-
'(Press Control-D to exit.)\n');
512+
'node repl, in your normal shell.\r\n' +
513+
'(Press Control-D to exit.)\r\n');
514514
self.lineParser.reset();
515515
self.bufferedCommand = '';
516516
self.displayPrompt();
@@ -525,7 +525,7 @@ function REPLServer(prompt,
525525
// {
526526
// ... x: 1
527527
// ... }
528-
self.bufferedCommand += cmd + '\n';
528+
self.bufferedCommand += cmd + '\r\n';
529529
self.displayPrompt();
530530
return;
531531
} else {
@@ -548,7 +548,7 @@ function REPLServer(prompt,
548548
if (!self.underscoreAssigned) {
549549
self.last = ret;
550550
}
551-
self.outputStream.write(self.writer(ret) + '\n');
551+
self.outputStream.write(self.writer(ret) + '\r\n');
552552
}
553553

554554
// Display prompt again
@@ -578,10 +578,10 @@ function REPLServer(prompt,
578578

579579
self.on('SIGCONT', function() {
580580
if (self.editorMode) {
581-
self.outputStream.write(`${self._initialPrompt}.editor\n`);
581+
self.outputStream.write(`${self._initialPrompt}.editor\r\n`);
582582
self.outputStream.write(
583-
'// Entering editor mode (^D to finish, ^C to cancel)\n');
584-
self.outputStream.write(`${self.bufferedCommand}\n`);
583+
'// Entering editor mode (^D to finish, ^C to cancel)\r\n');
584+
self.outputStream.write(`${self.bufferedCommand}\r\n`);
585585
self.prompt(true);
586586
} else {
587587
self.displayPrompt(true);
@@ -713,7 +713,7 @@ REPLServer.prototype.createContext = function() {
713713
this.last = value;
714714
if (!this.underscoreAssigned) {
715715
this.underscoreAssigned = true;
716-
this.outputStream.write('Expression assignment to _ now disabled.\n');
716+
this.outputStream.write('Expression assignment to _ now disabled.\r\n');
717717
}
718718
}
719719
});
@@ -762,7 +762,7 @@ function ArrayStream() {
762762
this.run = function(data) {
763763
var self = this;
764764
data.forEach(function(line) {
765-
self.emit('data', line + '\n');
765+
self.emit('data', line + '\r\n');
766766
});
767767
};
768768
}
@@ -1232,7 +1232,7 @@ function defineDefaultCommands(repl) {
12321232
this.lineParser.reset();
12331233
this.bufferedCommand = '';
12341234
if (!this.useGlobal) {
1235-
this.outputStream.write('Clearing context...\n');
1235+
this.outputStream.write('Clearing context...\r\n');
12361236
this.resetContext();
12371237
}
12381238
this.displayPrompt();
@@ -1252,7 +1252,7 @@ function defineDefaultCommands(repl) {
12521252
var self = this;
12531253
Object.keys(this.commands).sort().forEach(function(name) {
12541254
var cmd = self.commands[name];
1255-
self.outputStream.write(name + '\t' + (cmd.help || '') + '\n');
1255+
self.outputStream.write(name + '\t' + (cmd.help || '') + '\r\n');
12561256
});
12571257
this.displayPrompt();
12581258
}
@@ -1262,10 +1262,10 @@ function defineDefaultCommands(repl) {
12621262
help: 'Save all evaluated commands in this REPL session to a file',
12631263
action: function(file) {
12641264
try {
1265-
fs.writeFileSync(file, this.lines.join('\n') + '\n');
1266-
this.outputStream.write('Session saved to:' + file + '\n');
1265+
fs.writeFileSync(file, this.lines.join('\r\n') + '\r\n');
1266+
this.outputStream.write('Session saved to:' + file + '\r\n');
12671267
} catch (e) {
1268-
this.outputStream.write('Failed to save:' + file + '\n');
1268+
this.outputStream.write('Failed to save:' + file + '\r\n');
12691269
}
12701270
this.displayPrompt();
12711271
}
@@ -1279,19 +1279,21 @@ function defineDefaultCommands(repl) {
12791279
if (stats && stats.isFile()) {
12801280
var self = this;
12811281
var data = fs.readFileSync(file, 'utf8');
1282-
var lines = data.split('\n');
1282+
// \r\n, \n, or \r followed by something other than \n
1283+
const lineEnding = /\r?\n|\r(?!\n)/;
1284+
var lines = data.split(lineEnding);
12831285
this.displayPrompt();
12841286
lines.forEach(function(line) {
12851287
if (line) {
1286-
self.write(line + '\n');
1288+
self.write(line + '\r\n');
12871289
}
12881290
});
12891291
} else {
12901292
this.outputStream.write('Failed to load:' + file +
1291-
' is not a valid file\n');
1293+
' is not a valid file\r\n');
12921294
}
12931295
} catch (e) {
1294-
this.outputStream.write('Failed to load:' + file + '\n');
1296+
this.outputStream.write('Failed to load:' + file + '\r\n');
12951297
}
12961298
this.displayPrompt();
12971299
}
@@ -1304,7 +1306,7 @@ function defineDefaultCommands(repl) {
13041306
this.editorMode = true;
13051307
REPLServer.super_.prototype.setPrompt.call(this, '');
13061308
this.outputStream.write(
1307-
'// Entering editor mode (^D to finish, ^C to cancel)\n');
1309+
'// Entering editor mode (^D to finish, ^C to cancel)\r\n');
13081310
}
13091311
});
13101312
}

lib/util.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -836,9 +836,9 @@ function reduceToSingleString(output, base, braces, breakLength) {
836836
// If the opening "brace" is too large, like in the case of "Set {",
837837
// we need to force the first item to be on the next line or the
838838
// items will not line up correctly.
839-
(base === '' && braces[0].length === 1 ? '' : base + '\n ') +
839+
(base === '' && braces[0].length === 1 ? '' : base + '\r\n ') +
840840
' ' +
841-
output.join(',\n ') +
841+
output.join(',\r\n ') +
842842
' ' +
843843
braces[1];
844844
}
@@ -1001,19 +1001,19 @@ exports.print = internalUtil.deprecate(function() {
10011001

10021002
exports.puts = internalUtil.deprecate(function() {
10031003
for (var i = 0, len = arguments.length; i < len; ++i) {
1004-
process.stdout.write(arguments[i] + '\n');
1004+
process.stdout.write(arguments[i] + '\r\n');
10051005
}
10061006
}, 'util.puts is deprecated. Use console.log instead.');
10071007

10081008

10091009
exports.debug = internalUtil.deprecate(function(x) {
1010-
process.stderr.write('DEBUG: ' + x + '\n');
1010+
process.stderr.write('DEBUG: ' + x + '\r\n');
10111011
}, 'util.debug is deprecated. Use console.error instead.');
10121012

10131013

10141014
exports.error = internalUtil.deprecate(function(x) {
10151015
for (var i = 0, len = arguments.length; i < len; ++i) {
1016-
process.stderr.write(arguments[i] + '\n');
1016+
process.stderr.write(arguments[i] + '\r\n');
10171017
}
10181018
}, 'util.error is deprecated. Use console.error instead.');
10191019

test/parallel/test-preload.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ const interactive = childProcess.exec(nodeBinary + ' '
123123
+ '-i',
124124
common.mustCall(function(err, stdout, stderr) {
125125
assert.ifError(err);
126-
assert.strictEqual(stdout, `> 'test'\n> `);
126+
assert.strictEqual(stdout, `> 'test'\r\n> `);
127127
}));
128128

129129
interactive.stdin.write('a\n');

test/parallel/test-repl-.save.load.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ putIn.run(testFile);
2727
putIn.run(['.save ' + saveFileName]);
2828

2929
// the file should have what I wrote
30-
assert.equal(fs.readFileSync(saveFileName, 'utf8'), testFile.join('\n') + '\n');
30+
assert.equal(fs.readFileSync(saveFileName, 'utf8'), testFile.join('\r\n')
31+
+ '\r\n');
3132

3233
// make sure that the REPL data is "correct"
3334
// so when I load it back I know I'm good
@@ -54,7 +55,7 @@ var loadFile = join(common.tmpDir, 'file.does.not.exist');
5455
// should not break
5556
putIn.write = function(data) {
5657
// make sure I get a failed to load message and not some crazy error
57-
assert.equal(data, 'Failed to load:' + loadFile + '\n');
58+
assert.equal(data, 'Failed to load:' + loadFile + '\r\n');
5859
// eat me to avoid work
5960
putIn.write = function() {};
6061
};
@@ -63,7 +64,7 @@ putIn.run(['.load ' + loadFile]);
6364
// throw error on loading directory
6465
loadFile = common.tmpDir;
6566
putIn.write = function(data) {
66-
assert.equal(data, 'Failed to load:' + loadFile + ' is not a valid file\n');
67+
assert.equal(data, 'Failed to load:' + loadFile + ' is not a valid file\r\n');
6768
putIn.write = function() {};
6869
};
6970
putIn.run(['.load ' + loadFile]);
@@ -78,7 +79,7 @@ const invalidFileName = join(common.tmpDir, '\0\0\0\0\0');
7879
// should not break
7980
putIn.write = function(data) {
8081
// make sure I get a failed to save message and not some other error
81-
assert.equal(data, 'Failed to save:' + invalidFileName + '\n');
82+
assert.equal(data, 'Failed to save:' + invalidFileName + '\r\n');
8283
// reset to no-op
8384
putIn.write = function() {};
8485
};

test/parallel/test-repl-autolibs.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function test1() {
1919
if (data.length) {
2020

2121
// inspect output matches repl output
22-
assert.equal(data, util.inspect(require('fs'), null, 2, false) + '\n');
22+
assert.equal(data, util.inspect(require('fs'), null, 2, false) + '\r\n');
2323
// globally added lib matches required lib
2424
assert.equal(global.fs, require('fs'));
2525
test2();
@@ -36,7 +36,7 @@ function test2() {
3636
gotWrite = true;
3737
if (data.length) {
3838
// repl response error message
39-
assert.equal(data, '{}\n');
39+
assert.equal(data, '{}\r\n');
4040
// original value wasn't overwritten
4141
assert.equal(val, global.url);
4242
}

test/parallel/test-repl-definecommand.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ r.defineCommand('say2', function() {
3434
this.displayPrompt();
3535
});
3636

37-
inputStream.write('.help\n');
38-
assert(/\nsay1\thelp for say1\n/.test(output), 'help for say1 not present');
39-
assert(/\nsay2\t\n/.test(output), 'help for say2 not present');
40-
inputStream.write('.say1 node developer\n');
37+
inputStream.write('.help\r\n');
38+
assert(/\r\nsay1\thelp for say1\r\n/.test(output), 'help for say1 not present');
39+
assert(/\r\nsay2\t\r\n/.test(output), 'help for say2 not present');
40+
inputStream.write('.say1 node developer\r\n');
4141
assert(/> hello node developer/.test(output), 'say1 outputted incorrectly');
42-
inputStream.write('.say2 node developer\n');
42+
inputStream.write('.say2 node developer\r\n');
4343
assert(/> hello from say2/.test(output), 'say2 outputted incorrectly');

test/parallel/test-repl-mode.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -21,45 +21,45 @@ function testSloppyMode() {
2121

2222
cli.input.emit('data', `
2323
x = 3
24-
`.trim() + '\n');
25-
assert.equal(cli.output.accumulator.join(''), '> 3\n> ');
24+
`.trim() + '\r\n');
25+
assert.equal(cli.output.accumulator.join(''), '> 3\r\n> ');
2626
cli.output.accumulator.length = 0;
2727

2828
cli.input.emit('data', `
2929
let y = 3
30-
`.trim() + '\n');
31-
assert.equal(cli.output.accumulator.join(''), 'undefined\n> ');
30+
`.trim() + '\r\n');
31+
assert.equal(cli.output.accumulator.join(''), 'undefined\r\n> ');
3232
}
3333

3434
function testStrictMode() {
3535
var cli = initRepl(repl.REPL_MODE_STRICT);
3636

3737
cli.input.emit('data', `
3838
x = 3
39-
`.trim() + '\n');
39+
`.trim() + '\r\n');
4040
assert.ok(/ReferenceError: x is not defined/.test(
4141
cli.output.accumulator.join('')));
4242
cli.output.accumulator.length = 0;
4343

4444
cli.input.emit('data', `
4545
let y = 3
46-
`.trim() + '\n');
47-
assert.equal(cli.output.accumulator.join(''), 'undefined\n> ');
46+
`.trim() + '\r\n');
47+
assert.equal(cli.output.accumulator.join(''), 'undefined\r\n> ');
4848
}
4949

5050
function testAutoMode() {
5151
var cli = initRepl(repl.REPL_MODE_MAGIC);
5252

5353
cli.input.emit('data', `
5454
x = 3
55-
`.trim() + '\n');
56-
assert.equal(cli.output.accumulator.join(''), '> 3\n> ');
55+
`.trim() + '\r\n');
56+
assert.equal(cli.output.accumulator.join(''), '> 3\r\n> ');
5757
cli.output.accumulator.length = 0;
5858

5959
cli.input.emit('data', `
6060
let y = 3
61-
`.trim() + '\n');
62-
assert.equal(cli.output.accumulator.join(''), 'undefined\n> ');
61+
`.trim() + '\r\n');
62+
assert.equal(cli.output.accumulator.join(''), 'undefined\r\n> ');
6363
}
6464

6565
function initRepl(mode) {

test/parallel/test-repl-persistent-history.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ActionStream extends stream.Stream {
3737
if (typeof action === 'object') {
3838
self.emit('keypress', '', action);
3939
} else {
40-
self.emit('data', action + '\n');
40+
self.emit('data', action + '\r\n');
4141
}
4242
setImmediate(doAction);
4343
}
@@ -138,7 +138,7 @@ const tests = [
138138
env: { NODE_REPL_HISTORY_FILE: oldHistoryPath },
139139
test: [UP, CLEAR, '\'42\'', ENTER],
140140
expected: [prompt, convertMsg, prompt, prompt + '\'=^.^=\'', prompt, '\'',
141-
'4', '2', '\'', '\'42\'\n', prompt, prompt],
141+
'4', '2', '\'', '\'42\'\r\n', prompt, prompt],
142142
after: function ensureHistoryFixture() {
143143
// XXX(Fishrock123) Make sure nothing weird happened to our fixture
144144
// or it's temporary copy.
@@ -154,7 +154,7 @@ const tests = [
154154
{ // Requires the above testcase
155155
env: {},
156156
test: [UP, UP, ENTER],
157-
expected: [prompt, prompt + '\'42\'', prompt + '\'=^.^=\'', '\'=^.^=\'\n',
157+
expected: [prompt, prompt + '\'42\'', prompt + '\'=^.^=\'', '\'=^.^=\'\r\n',
158158
prompt]
159159
},
160160
{

0 commit comments

Comments
 (0)