From 6742c9b22f29bac0d078bddbd42962505242be44 Mon Sep 17 00:00:00 2001 From: Sakthipriyan Vairamani Date: Fri, 18 Sep 2015 21:30:29 +0530 Subject: [PATCH] repl: revert 81ea52a to fix newlines in copy-paste When some string is copied and pasted in REPL, if it has a newline (\n) in it, then it is considered as end of line and REPL throws a SyntaxError, because a line cannot end with a unclosed string literal. This behavior is because of the fact that `readline` module breaks at all the `\n`s in the input string and treats them as a seperate line. So whenever it encounters a new line character it will emit a `line` event. This commit basically reverts https://github.com/nodejs/node/commit/81ea52aa01aa264b1424e00bc240b956b19ddaa5, which was an attempt to improve the way string literals were handled by REPL. Fixes: https://github.com/nodejs/node/issues/2749 --- lib/repl.js | 90 +++++++------------------------------- test/parallel/test-repl.js | 27 ------------ 2 files changed, 15 insertions(+), 102 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index e61a5edb519aeb..57284b55208d1c 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -190,7 +190,6 @@ function REPLServer(prompt, self._domain.on('error', function(e) { debug('domain error'); self.outputStream.write((e.stack || e) + '\n'); - self._currentStringLiteral = null; self.bufferedCommand = ''; self.lines.level = []; self.displayPrompt(); @@ -217,8 +216,6 @@ function REPLServer(prompt, self.outputStream = output; self.resetContext(); - // Initialize the current string literal found, to be null - self._currentStringLiteral = null; self.bufferedCommand = ''; self.lines.level = []; @@ -277,86 +274,21 @@ function REPLServer(prompt, sawSIGINT = false; } - self._currentStringLiteral = null; self.bufferedCommand = ''; self.lines.level = []; self.displayPrompt(); }); - function parseLine(line, currentStringLiteral) { - var previous = null, current = null; - - for (var i = 0; i < line.length; i += 1) { - if (previous === '\\') { - // if it is a valid escaping, then skip processing - previous = current; - continue; - } - - current = line.charAt(i); - if (current === currentStringLiteral) { - currentStringLiteral = null; - } else if (current === '\'' || - current === '"' && - currentStringLiteral === null) { - currentStringLiteral = current; - } - previous = current; - } - - return currentStringLiteral; - } - - function getFinisherFunction(cmd, defaultFn) { - if ((self._currentStringLiteral === null && - cmd.charAt(cmd.length - 1) === '\\') || - (self._currentStringLiteral !== null && - cmd.charAt(cmd.length - 1) !== '\\')) { - - // If the line continuation is used outside string literal or if the - // string continuation happens with out line continuation, then fail hard. - // Even if the error is recoverable, get the underlying error and use it. - return function(e, ret) { - var error = e instanceof Recoverable ? e.err : e; - - if (arguments.length === 2) { - // using second argument only if it is actually passed. Otherwise - // `undefined` will be printed when invalid REPL commands are used. - return defaultFn(error, ret); - } - - return defaultFn(error); - }; - } - return defaultFn; - } - self.on('line', function(cmd) { debug('line %j', cmd); sawSIGINT = false; var skipCatchall = false; - var finisherFn = finish; // leading whitespaces in template literals should not be trimmed. if (self._inTemplateLiteral) { self._inTemplateLiteral = false; } else { - const wasWithinStrLiteral = self._currentStringLiteral !== null; - self._currentStringLiteral = parseLine(cmd, self._currentStringLiteral); - const isWithinStrLiteral = self._currentStringLiteral !== null; - - if (!wasWithinStrLiteral && !isWithinStrLiteral) { - // Current line has nothing to do with String literals, trim both ends - cmd = cmd.trim(); - } else if (wasWithinStrLiteral && !isWithinStrLiteral) { - // was part of a string literal, but it is over now, trim only the end - cmd = cmd.trimRight(); - } else if (isWithinStrLiteral && !wasWithinStrLiteral) { - // was not part of a string literal, but it is now, trim only the start - cmd = cmd.trimLeft(); - } - - finisherFn = getFinisherFunction(cmd, finish); + cmd = trimWhitespace(cmd); } // Check to see if a REPL keyword was used. If it returns true, @@ -389,9 +321,9 @@ function REPLServer(prompt, } debug('eval %j', evalCmd); - self.eval(evalCmd, self.context, 'repl', finisherFn); + self.eval(evalCmd, self.context, 'repl', finish); } else { - finisherFn(null); + finish(null); } function finish(e, ret) { @@ -402,7 +334,6 @@ function REPLServer(prompt, self.outputStream.write('npm should be run outside of the ' + 'node repl, in your normal shell.\n' + '(Press Control-D to exit.)\n'); - self._currentStringLiteral = null; self.bufferedCommand = ''; self.displayPrompt(); return; @@ -424,7 +355,6 @@ function REPLServer(prompt, } // Clear buffer if no SyntaxErrors - self._currentStringLiteral = null; self.bufferedCommand = ''; // If we got any output - print it (if no error) @@ -965,7 +895,6 @@ function defineDefaultCommands(repl) { repl.defineCommand('break', { help: 'Sometimes you get stuck, this gets you out', action: function() { - this._currentStringLiteral = null; this.bufferedCommand = ''; this.displayPrompt(); } @@ -980,7 +909,6 @@ function defineDefaultCommands(repl) { repl.defineCommand('clear', { help: clearMessage, action: function() { - this._currentStringLiteral = null; this.bufferedCommand = ''; if (!this.useGlobal) { this.outputStream.write('Clearing context...\n'); @@ -1046,6 +974,18 @@ function defineDefaultCommands(repl) { }); } + +function trimWhitespace(cmd) { + const trimmer = /^\s*(.+)\s*$/m; + var matches = trimmer.exec(cmd); + + if (matches && matches.length === 2) { + return matches[1]; + } + return ''; +} + + function regexpEscape(s) { return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); } diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index ac890cf75844db..1c8e9b7257d4c1 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -198,33 +198,6 @@ function error_test() { // a REPL command { client: client_unix, send: '.toString', expect: 'Invalid REPL keyword\n' + prompt_unix }, - // fail when we are not inside a String and a line continuation is used - { client: client_unix, send: '[] \\', - expect: /^SyntaxError: Unexpected token ILLEGAL/ }, - // do not fail when a String is created with line continuation - { client: client_unix, send: '\'the\\\nfourth\\\neye\'', - expect: prompt_multiline + prompt_multiline + - '\'thefourtheye\'\n' + prompt_unix }, - // Don't fail when a partial String is created and line continuation is used - // with whitespace characters at the end of the string. We are to ignore it. - // This test is to make sure that we properly remove the whitespace - // characters at the end of line, unlike the buggy `trimWhitespace` function - { client: client_unix, send: ' \t .break \t ', - expect: prompt_unix }, - // multiline strings preserve whitespace characters in them - { client: client_unix, send: '\'the \\\n fourth\t\t\\\n eye \'', - expect: prompt_multiline + prompt_multiline + - '\'the fourth\\t\\t eye \'\n' + prompt_unix }, - // more than one multiline strings also should preserve whitespace chars - { client: client_unix, send: '\'the \\\n fourth\' + \'\t\t\\\n eye \'', - expect: prompt_multiline + prompt_multiline + - '\'the fourth\\t\\t eye \'\n' + prompt_unix }, - // using REPL commands within a string literal should still work - { client: client_unix, send: '\'\\\n.break', - expect: prompt_unix }, - // using REPL command "help" within a string literal should still work - { client: client_unix, send: '\'thefourth\\\n.help\neye\'', - expect: /'thefourtheye'/ }, // empty lines in the REPL should be allowed { client: client_unix, send: '\n\r\n\r\n', expect: prompt_unix + prompt_unix + prompt_unix },