From 0598b7275b4e8f7342ade785de920867961e32ef Mon Sep 17 00:00:00 2001 From: Alan Pierce Date: Wed, 27 Jul 2016 23:16:30 -0700 Subject: [PATCH] fix: properly set location data in OUTDENT nodes Backport of https://github.com/jashkenas/coffeescript/pull/4291 . In f609036beef3aa1529c210ffad3ced818ee94cce, a line was changed from `if length > 0 then (length - 1) else 0` to `Math.max 0, length - 1`. However, in some cases, the `length` variable can be `undefined`. The previous code would correctly compute `lastCharacter` as 0, but the new code would compute it as `NaN`. This would cause trouble later on: the end location would just be the end of the current chunk, which would be incorrect. Here's a specific case where the parser was behaving incorrectly: ``` a { b: -> return c d, if e f } g ``` The OUTDENT tokens after the `f` had an undefined length, so the `NaN` made it so the end location was at the end of the file. That meant that various nodes in the AST, like the `return` node, would incorrectly have an end location at the end of the file. To fix, I just reverted the change to that particular line. Also add a test that tokens have locations that are in order --- lib/coffee-script/lexer.js | 2 +- src/lexer.coffee | 2 +- test/location.coffee | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index 857bf29bec..de9632f977 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -764,7 +764,7 @@ } locationData = {}; ref2 = this.getLineAndColumnFromChunk(offsetInChunk), locationData.first_line = ref2[0], locationData.first_column = ref2[1]; - lastCharacter = Math.max(0, length - 1); + lastCharacter = length > 0 ? length - 1 : 0; ref3 = this.getLineAndColumnFromChunk(offsetInChunk + lastCharacter), locationData.last_line = ref3[0], locationData.last_column = ref3[1]; token = [tag, value, locationData]; return token; diff --git a/src/lexer.coffee b/src/lexer.coffee index 48a1e8564f..773c9c775d 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -649,7 +649,7 @@ exports.Lexer = class Lexer # Use length - 1 for the final offset - we're supplying the last_line and the last_column, # so if last_column == first_column, then we're looking at a character of length 1. - lastCharacter = Math.max 0, length - 1 + lastCharacter = if length > 0 then (length - 1) else 0 [locationData.last_line, locationData.last_column] = @getLineAndColumnFromChunk offsetInChunk + lastCharacter diff --git a/test/location.coffee b/test/location.coffee index 783b44afbd..35b9584c51 100644 --- a/test/location.coffee +++ b/test/location.coffee @@ -450,6 +450,25 @@ test "#3621: Multiline regex and manual `Regex` call with interpolation should eq tokenA.origin?[1], tokenB.origin?[1] eq tokenA.stringEnd, tokenB.stringEnd +test "Verify tokens have locations that are in order", -> + source = ''' + a { + b: -> + return c d, + if e + f + } + g + ''' + tokens = CoffeeScript.tokens source + lastToken = null + for token in tokens + if lastToken + ok token[2].first_line >= lastToken[2].last_line + if token[2].first_line == lastToken[2].last_line + ok token[2].first_column >= lastToken[2].last_column + lastToken = token + test "Verify all tokens get a location", -> doesNotThrow -> tokens = CoffeeScript.tokens testScript