diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index 2ee88b9890..41bcd4c240 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -26,6 +26,7 @@ this.tagPostfixConditionals(); this.addImplicitBracesAndParens(); this.addLocationDataToGeneratedTokens(); + this.fixOutdentLocationData(); return this.tokens; }; @@ -372,6 +373,23 @@ }); }; + Rewriter.prototype.fixOutdentLocationData = function() { + return this.scanTokens(function(token, i, tokens) { + var prevLocationData; + if (!(token[0] === 'OUTDENT' || (token.generated && token[0] === 'CALL_END'))) { + return 1; + } + prevLocationData = tokens[i - 1][2]; + token[2] = { + first_line: prevLocationData.last_line, + first_column: prevLocationData.last_column, + last_line: prevLocationData.last_line, + last_column: prevLocationData.last_column + }; + return 1; + }); + }; + Rewriter.prototype.normalizeLines = function() { var action, condition, indent, outdent, starter; starter = indent = outdent = null; diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 5c4d44ea06..79d8eb710b 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -33,6 +33,7 @@ class exports.Rewriter @tagPostfixConditionals() @addImplicitBracesAndParens() @addLocationDataToGeneratedTokens() + @fixOutdentLocationData() @tokens # Rewrite the token stream, looking one token ahead and behind. @@ -368,6 +369,21 @@ class exports.Rewriter last_column: column return 1 + # OUTDENT tokens should always be positioned at the last character of the + # previous token, so that AST nodes ending in an OUTDENT token end up with a + # location corresponding to the last "real" token under the node. + fixOutdentLocationData: -> + @scanTokens (token, i, tokens) -> + return 1 unless token[0] is 'OUTDENT' or + (token.generated and token[0] is 'CALL_END') + prevLocationData = tokens[i - 1][2] + token[2] = + first_line: prevLocationData.last_line + first_column: prevLocationData.last_column + last_line: prevLocationData.last_line + last_column: prevLocationData.last_column + return 1 + # Because our grammar is LALR(1), it can't handle some single-line # expressions that lack ending delimiters. The **Rewriter** adds the implicit # blocks, so it doesn't need to. To keep the grammar clean and tidy, trailing diff --git a/test/location.coffee b/test/location.coffee index 35b9584c51..a2f855a7ba 100644 --- a/test/location.coffee +++ b/test/location.coffee @@ -469,6 +469,65 @@ test "Verify tokens have locations that are in order", -> ok token[2].first_column >= lastToken[2].last_column lastToken = token +test "Verify OUTDENT tokens are located at the end of the previous token", -> + source = ''' + SomeArr = [ -> + if something + lol = + count: 500 + ] + ''' + tokens = CoffeeScript.tokens source + [..., number, curly, outdent1, outdent2, outdent3, bracket, terminator] = tokens + eq number[0], 'NUMBER' + for outdent in [outdent1, outdent2, outdent3] + eq outdent[0], 'OUTDENT' + eq outdent[2].first_line, number[2].last_line + eq outdent[2].first_column, number[2].last_column + eq outdent[2].last_line, number[2].last_line + eq outdent[2].last_column, number[2].last_column + +test "Verify OUTDENT and CALL_END tokens are located at the end of the previous token", -> + source = ''' + a = b { + c: -> + d e, + if f + g {}, + if h + i {} + } + ''' + tokens = CoffeeScript.tokens source + [..., closeCurly1, callEnd1, outdent1, outdent2, callEnd2, outdent3, outdent4, + callEnd3, outdent5, outdent6, closeCurly2, callEnd4, terminator] = tokens + eq closeCurly1[0], '}' + assertAtCloseCurly = (token) -> + eq token[2].first_line, closeCurly1[2].last_line + eq token[2].first_column, closeCurly1[2].last_column + eq token[2].last_line, closeCurly1[2].last_line + eq token[2].last_column, closeCurly1[2].last_column + + for token in [outdent1, outdent2, outdent3, outdent4, outdent5, outdent6] + eq token[0], 'OUTDENT' + assertAtCloseCurly(token) + for token in [callEnd1, callEnd2, callEnd3] + eq token[0], 'CALL_END' + assertAtCloseCurly(token) + +test "Verify real CALL_END tokens have the right position", -> + source = ''' + a() + ''' + tokens = CoffeeScript.tokens source + [identifier, callStart, callEnd, terminator] = tokens + startIndex = identifier[2].first_column + eq identifier[2].last_column, startIndex + eq callStart[2].first_column, startIndex + 1 + eq callStart[2].last_column, startIndex + 1 + eq callEnd[2].first_column, startIndex + 2 + eq callEnd[2].last_column, startIndex + 2 + test "Verify all tokens get a location", -> doesNotThrow -> tokens = CoffeeScript.tokens testScript