diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index 944486b4..580798f6 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -767,10 +767,17 @@ }; Lexer.prototype.escapeLines = function(str, heredoc) { + str = str.replace(/\\[^\S\n]*(\n|\\)\s*/g, function(escaped, character) { + if (character === '\n') { + return ''; + } else { + return escaped; + } + }); if (heredoc) { return str.replace(MULTILINER, '\\n'); } else { - return str.replace(/((^|[^\\])(\\\\)+)\n/g, '$1 \\\n').replace(/\\\s*\n\s*/g, '').replace(/\s*\n\s*/g, ' '); + return str.replace(/\s*\n\s*/g, ' '); } }; @@ -778,8 +785,8 @@ if (!body) { return quote + quote; } - body = body.replace(/\\([\s\S])/g, function(match, contents) { - if (contents === quote || heredoc && contents === '\n') { + body = body.replace(RegExp("\\\\(" + quote + "|\\\\)", "g"), function(match, contents) { + if (contents === quote) { return contents; } else { return match; diff --git a/src/lexer.coffee b/src/lexer.coffee index fa910ec0..78e0e889 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -691,18 +691,20 @@ exports.Lexer = class Lexer # Converts newlines for string literals. escapeLines: (str, heredoc) -> + # Ignore escaped backslashes and remove escaped newlines + str = str.replace /\\[^\S\n]*(\n|\\)\s*/g, (escaped, character) -> + if character is '\n' then '' else escaped if heredoc str.replace MULTILINER, '\\n' else - str.replace(/((^|[^\\])(\\\\)+)\n/g, '$1 \\\n') # escaped backslashes - .replace(/\\\s*\n\s*/g, '') # backslash at EOL - .replace(/\s*\n\s*/g, ' ') + str.replace /\s*\n\s*/g, ' ' # Constructs a string token by escaping quotes and newlines. makeString: (body, quote, heredoc) -> return quote + quote unless body - body = body.replace /\\([\s\S])/g, (match, contents) -> - if contents is quote or heredoc and contents is '\n' then contents else match + # Ignore escaped backslashes and unescape quotes + body = body.replace /// \\( #{quote} | \\ ) ///g, (match, contents) -> + if contents is quote then contents else match body = body.replace /// #{quote} ///g, '\\$&' quote + @escapeLines(body, heredoc) + quote diff --git a/test/strings.coffee b/test/strings.coffee index 36101b04..ee276c20 100644 --- a/test/strings.coffee +++ b/test/strings.coffee @@ -42,6 +42,8 @@ test "#3229, multiline strings", -> indentation doesn\'t matter', 'indentation doesn\'t matter' + eq 'trailing ws + doesn\'t matter', 'trailing ws doesn\'t matter' # Use backslashes at the end of a line to specify whitespace between lines. eq 'a \ @@ -72,6 +74,7 @@ test "#3229, multiline strings", -> }", "a string inside interpolation" # Handle escaped backslashes correctly. + eq '\\', `'\\'` eq 'escaped backslash at EOL\\ next line', 'escaped backslash at EOL\\ next line' eq '\\ @@ -84,8 +87,10 @@ test "#3229, multiline strings", -> next line', 'triple backslash\\next line' eq 'several escaped backslashes\\\\\\ ok', 'several escaped backslashes\\\\\\ ok' - eq 'several escaped backslashes\\\\\\\ - ok', 'several escaped backslashes\\\\\\ok' + eq 'several escaped backslashes slash\\\\\\\ + ok', 'several escaped backslashes slash\\\\\\ok' + eq 'several escaped backslashes with trailing ws \\\\\\ + ok', 'several escaped backslashes with trailing ws \\\\\\ ok' # Backslashes at beginning of lines. eq 'first line @@ -100,6 +105,67 @@ test "#3229, multiline strings", -> backslash', 'lone backslash' +test "#3249, escape newlines in heredocs with backslashes", -> + # Ignore escaped newlines + eq ''' + Set whitespace \ + <- this is ignored\ + none + normal indentation + ''', 'Set whitespace <- this is ignorednone\n normal indentation' + eq """ + Set whitespace \ + <- this is ignored\ + none + normal indentation + """, 'Set whitespace <- this is ignorednone\n normal indentation' + + # Changed from #647 + eq ''' + Hello, World\ + + ''', 'Hello, World' + + # Backslash at the beginning of a literal string. + eq '''\ + ok''', 'ok' + eq ''' \ + ok''', ' ok' + + # Same behavior in interpolated strings. + eq """ + interpolation #{1} + follows #{2} \ + too #{3}\ + ! + """, 'interpolation 1\n follows 2 too 3!' + + # TODO: uncomment when #2388 is fixed + # eq """a heredoc #{ + # "inside \ + # interpolation" + # }""", "a heredoc inside interpolation" + + # Handle escaped backslashes correctly. + eq ''' + escaped backslash at EOL\\ + next line + ''', 'escaped backslash at EOL\\\n next line' + + # Backslashes at beginning of lines. + eq '''first line + \ backslash at BOL''', 'first line\n\ backslash at BOL' + eq """first line\ + \ backslash at BOL""", 'first line\ backslash at BOL' + +# Edge case. + eq '''lone + + \ + + + + backslash''', 'lone\n\n backslash' #647 eq "''Hello, World\\''", ''' @@ -108,10 +174,6 @@ eq "''Hello, World\\''", ''' eq '""Hello, World\\""', """ "\"Hello, World\\\"" """ -eq 'Hello, World\n', ''' -Hello, World\ - -''' a = """ basic heredoc