From a61b6ee925bb6127e9e2226d7683a5b0eeacc70c Mon Sep 17 00:00:00 2001 From: xixixao Date: Wed, 27 Nov 2013 20:29:45 +0000 Subject: [PATCH] Fixed leading whitespace before interpolation in simple strings --- lib/coffee-script/lexer.js | 43 +++++++++++++++++++------------------- src/lexer.coffee | 31 +++++++++++++-------------- test/strings.coffee | 8 +++++++ 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index a5fdc62f..b5e1f01c 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -166,30 +166,25 @@ }; Lexer.prototype.stringToken = function() { - var match, octalEsc, string; - switch (this.chunk.charAt(0)) { + var octalEsc, quote, string, trimmed; + switch (quote = this.chunk.charAt(0)) { case "'": - if (!(match = SIMPLESTR.exec(this.chunk))) { - return 0; - } - string = match[0]; - this.token('STRING', this.escapeLines(string), 0, string.length); + string = SIMPLESTR.exec(this.chunk)[0]; break; case '"': - if (!(string = this.balancedString(this.chunk, '"'))) { - return 0; - } - if (0 < string.indexOf('#{', 1)) { - this.interpolateString(string.slice(1, -1), { - strOffset: 1, - lexedLength: string.length - }); - } else { - this.token('STRING', this.escapeLines(string), 0, string.length); - } - break; - default: - return 0; + string = this.balancedString(this.chunk, '"'); + } + if (!string) { + return 0; + } + trimmed = this.removeNewlines(string.slice(1, -1)); + if (quote === '"' && 0 < string.indexOf('#{', 1)) { + this.interpolateString(trimmed, { + strOffset: 1, + lexedLength: string.length + }); + } else { + this.token('STRING', quote + this.escapeLines(trimmed) + quote, 0, string.length); } if (octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test(string)) { this.error("octal escape sequences " + string + " are not allowed"); @@ -763,6 +758,10 @@ return LINE_CONTINUER.test(this.chunk) || ((_ref2 = this.tag()) === '\\' || _ref2 === '.' || _ref2 === '?.' || _ref2 === '?::' || _ref2 === 'UNARY' || _ref2 === 'MATH' || _ref2 === '+' || _ref2 === '-' || _ref2 === 'SHIFT' || _ref2 === 'RELATION' || _ref2 === 'COMPARE' || _ref2 === 'LOGIC' || _ref2 === 'THROW' || _ref2 === 'EXTENDS'); }; + Lexer.prototype.removeNewlines = function(str) { + return str.replace(/^\s*\n\s*/, '').replace(/([^\\]|\\\\)\s*\n\s*$/, '$1'); + }; + Lexer.prototype.escapeLines = function(str, heredoc) { str = str.replace(/\\[^\S\n]*(\n|\\)\s*/g, function(escaped, character) { if (character === '\n') { @@ -774,7 +773,7 @@ if (heredoc) { return str.replace(MULTILINER, '\\n'); } else { - return str.replace(/^(.)\s*\n\s*/, '$1').replace(/\s*\n\s*(.)$/, '$1').replace(/\s*\n\s*/g, ' '); + return str.replace(/\s*\n\s*/g, ' '); } }; diff --git a/src/lexer.coffee b/src/lexer.coffee index 5340c11f..385afd3c 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -186,19 +186,15 @@ exports.Lexer = class Lexer # Matches strings, including multi-line strings. Ensures that quotation marks # are balanced within the string's contents, and within nested interpolations. stringToken: -> - switch @chunk.charAt 0 - when "'" - return 0 unless match = SIMPLESTR.exec @chunk - string = match[0] - @token 'STRING', @escapeLines(string), 0, string.length - when '"' - return 0 unless string = @balancedString @chunk, '"' - if 0 < string.indexOf '#{', 1 - @interpolateString string[1...-1], strOffset: 1, lexedLength: string.length - else - @token 'STRING', @escapeLines(string), 0, string.length - else - return 0 + switch quote = @chunk.charAt 0 + when "'" then [string] = SIMPLESTR.exec @chunk + when '"' then string = @balancedString @chunk, '"' + return 0 unless string + trimmed = @removeNewlines string[1...-1] + if quote is '"' and 0 < string.indexOf '#{', 1 + @interpolateString trimmed, strOffset: 1, lexedLength: string.length + else + @token 'STRING', quote + @escapeLines(trimmed) + quote, 0, string.length if octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test string @error "octal escape sequences #{string} are not allowed" string.length @@ -686,6 +682,11 @@ exports.Lexer = class Lexer @tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION' 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS'] + # Remove newlines from beginning and (non escaped) from end of string literals. + removeNewlines: (str) -> + str.replace(/^\s*\n\s*/, '') + .replace(/([^\\]|\\\\)\s*\n\s*$/, '$1') + # Converts newlines for string literals. escapeLines: (str, heredoc) -> # Ignore escaped backslashes and remove escaped newlines @@ -695,9 +696,7 @@ exports.Lexer = class Lexer str.replace MULTILINER, '\\n' else # Trim leading and trailing whitespace, string includes quotes - str.replace(/^(.)\s*\n\s*/, '$1') - .replace(/\s*\n\s*(.)$/, '$1') - .replace(/\s*\n\s*/g, ' ') + str.replace /\s*\n\s*/g, ' ' # Constructs a string token by escaping quotes and newlines. makeString: (body, quote, heredoc) -> diff --git a/test/strings.coffee b/test/strings.coffee index 536c1c15..803981ac 100644 --- a/test/strings.coffee +++ b/test/strings.coffee @@ -81,6 +81,9 @@ test "#3229, multiline strings", -> 'string ' + "inside interpolation" }", "a string inside interpolation" + eq " + #{1} + ", '1' # Handle escaped backslashes correctly. eq '\\', `'\\'` @@ -155,6 +158,11 @@ test "#3249, escape newlines in heredocs with backslashes", -> too #{3}\ ! """, 'interpolation 1\n follows 2 too 3!' + eq """ + + #{1} #{2} + + """, '\n1 2\n' # TODO: uncomment when #2388 is fixed # eq """a heredoc #{