diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index 59ce400e..82ed9316 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 2.0.0-beta2 (function() { - var BALANCED_PAIRS, CALL_CLOSERS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, generate, k, left, len, rite, throwSyntaxError, + var BALANCED_PAIRS, CALL_CLOSERS, CONTROL_IN_IMPLICIT, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, generate, k, left, len, rite, throwSyntaxError, indexOf = [].indexOf; ({throwSyntaxError} = require('./helpers')); @@ -255,7 +255,7 @@ } return this.looksObjectish(nextTerminatorIdx + 1); }; - if (inImplicitCall() && (tag === 'IF' || tag === 'TRY' || tag === 'FINALLY' || tag === 'CATCH' || tag === 'CLASS' || tag === 'SWITCH')) { + if ((inImplicitCall() || inImplicitObject()) && indexOf.call(CONTROL_IN_IMPLICIT, tag) >= 0 || inImplicitObject() && prevTag === ':' && tag === 'FOR') { stack.push([ 'CONTROL', i, { ours: true @@ -265,8 +265,12 @@ } if (tag === 'INDENT' && inImplicit()) { if (prevTag !== '=>' && prevTag !== '->' && prevTag !== '[' && prevTag !== '(' && prevTag !== ',' && prevTag !== '{' && prevTag !== 'ELSE' && prevTag !== '=') { - while (inImplicitCall()) { - endImplicitCall(); + while (inImplicitCall() || inImplicitObject() && prevTag !== ':') { + if (inImplicitCall()) { + endImplicitCall(); + } else { + endImplicitObject(); + } } } if (inImplicitControl()) { @@ -318,7 +322,6 @@ while (this.tag(s - 2) === 'HERECOMMENT') { s -= 2; } - this.insideForDeclaration = nextTag === 'FOR'; startsLine = s === 0 || (ref = this.tag(s - 1), indexOf.call(LINEBREAKS, ref) >= 0) || tokens[s - 1].newLine; if (stackTop()) { [stackTag, stackIdx] = stackTop(); @@ -343,7 +346,7 @@ [stackTag, stackIdx, {sameLine, startsLine}] = stackTop(); if (inImplicitCall() && prevTag !== ',') { endImplicitCall(); - } else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' && !(tag === 'POST_IF' && startsLine && implicitObjectContinues(i + 1))) { + } else if (inImplicitObject() && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' && !(tag === 'POST_IF' && startsLine && implicitObjectContinues(i + 1))) { endImplicitObject(); } else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) { if (nextTag === 'HERECOMMENT') { @@ -355,7 +358,7 @@ } } } - if (tag === ',' && !this.looksObjectish(i + 1) && inImplicitObject() && !this.insideForDeclaration && (nextTag !== 'TERMINATOR' || !this.looksObjectish(i + 2))) { + if (tag === ',' && !this.looksObjectish(i + 1) && inImplicitObject() && (nextTag !== 'TERMINATOR' || !this.looksObjectish(i + 2))) { offset = nextTag === 'OUTDENT' ? 1 : 0; while (inImplicitObject()) { endImplicitObject(i + offset); @@ -558,4 +561,6 @@ CALL_CLOSERS = ['.', '?.', '::', '?::']; + CONTROL_IN_IMPLICIT = ['IF', 'TRY', 'FINALLY', 'CATCH', 'CLASS', 'SWITCH']; + }).call(this); diff --git a/src/rewriter.coffee b/src/rewriter.coffee index fee7e4d5..b427e2da 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -192,9 +192,11 @@ exports.Rewriter = class Rewriter return no unless nextTerminatorIdx? @looksObjectish nextTerminatorIdx + 1 - # Don't end an implicit call on next indent if any of these are in an argument - if inImplicitCall() and tag in ['IF', 'TRY', 'FINALLY', 'CATCH', - 'CLASS', 'SWITCH'] + # Don't end an implicit call/object on next indent if any of these are in an argument/value + if ( + (inImplicitCall() or inImplicitObject()) and tag in CONTROL_IN_IMPLICIT or + inImplicitObject() and prevTag is ':' and tag is 'FOR' + ) stack.push ['CONTROL', i, ours: yes] return forward(1) @@ -206,7 +208,11 @@ exports.Rewriter = class Rewriter # 2. The last token before the indent is part of the list below # if prevTag not in ['=>', '->', '[', '(', ',', '{', 'ELSE', '='] - endImplicitCall() while inImplicitCall() + while inImplicitCall() or inImplicitObject() and prevTag isnt ':' + if inImplicitCall() + endImplicitCall() + else + endImplicitObject() stack.pop() if inImplicitControl() stack.push [tag, i] return forward(1) @@ -273,9 +279,6 @@ exports.Rewriter = class Rewriter else i - 1 s -= 2 while @tag(s - 2) is 'HERECOMMENT' - # Mark if the value is a for loop - @insideForDeclaration = nextTag is 'FOR' - startsLine = s is 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine # Are we just continuing an already declared object? if stackTop() @@ -316,7 +319,7 @@ exports.Rewriter = class Rewriter endImplicitCall() # Close implicit objects such as: # return a: 1, b: 2 unless true - else if inImplicitObject() and not @insideForDeclaration and sameLine and + else if inImplicitObject() and sameLine and tag isnt 'TERMINATOR' and prevTag isnt ':' and not (tag is 'POST_IF' and startsLine and implicitObjectContinues(i + 1)) endImplicitObject() @@ -344,7 +347,6 @@ exports.Rewriter = class Rewriter # f a, b: c, d: e, f, g: h: i, j # if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and - not @insideForDeclaration and (nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2)) # When nextTag is OUTDENT the comma is insignificant and # should just be ignored so embed it in the implicit object. @@ -540,3 +542,6 @@ LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT'] # Tokens that close open calls when they follow a newline. CALL_CLOSERS = ['.', '?.', '::', '?::'] + +# Tokens that prevent a subsequent indent from ending implicit calls/objects +CONTROL_IN_IMPLICIT = ['IF', 'TRY', 'FINALLY', 'CATCH', 'CLASS', 'SWITCH'] diff --git a/test/objects.coffee b/test/objects.coffee index 65178e28..5b19e7cc 100644 --- a/test/objects.coffee +++ b/test/objects.coffee @@ -594,6 +594,23 @@ test "#1263: Braceless object return", -> eq 2, obj.b eq 3, obj.c() +test "#4564: indent should close implicit object", -> + f = (x) -> x + + arrayEq ['a'], + for key of f a: 1 + key + + g = null + if f a: 1 + g = 3 + eq g, 3 + + h = null + if a: (i for i in [1, 2, 3]) + h = 4 + eq h, 4 + test "#4544: Postfix conditionals in first line of implicit object literals", -> two = foo: