diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index cd47d17f..11b78147 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -281,7 +281,7 @@ stack = []; start = null; return this.scanTokens(function(token, i, tokens) { - var endImplicitCall, endImplicitObject, forward, implicitObjectContinues, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, isImplicit, isImplicitCall, isImplicitObject, k, newLine, nextTag, nextToken, offset, prevTag, prevToken, ref, ref1, ref2, s, sameLine, stackIdx, stackItem, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag; + var endImplicitCall, endImplicitObject, forward, implicitObjectContinues, inControlFlow, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, isImplicit, isImplicitCall, isImplicitObject, k, newLine, nextTag, nextToken, offset, prevTag, prevToken, ref, ref1, ref2, s, sameLine, stackIdx, stackItem, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag; [tag] = token; [prevTag] = prevToken = i > 0 ? tokens[i - 1] : []; [nextTag] = nextToken = i < tokens.length - 1 ? tokens[i + 1] : []; @@ -420,10 +420,31 @@ } start = stack.pop(); } + inControlFlow = () => { + var controlFlow, isFunc, seenFor, tagCurrentLine; + seenFor = this.findTagsBackwards(i, ['FOR']) && this.findTagsBackwards(i, ['FORIN', 'FOROF', 'FORFROM']); + controlFlow = seenFor || this.findTagsBackwards(i, ['WHILE', 'UNTIL', 'LOOP', 'LEADING_WHEN']); + if (!controlFlow) { + return false; + } + isFunc = false; + tagCurrentLine = token[2].first_line; + this.detectEnd(i, function(token, i) { + var ref; + return ref = token[0], indexOf.call(LINEBREAKS, ref) >= 0; + }, function(token, i) { + var first_line; + [prevTag, , {first_line}] = tokens[i - 1] || []; + return isFunc = tagCurrentLine === first_line && (prevTag === '->' || prevTag === '=>'); + }, { + returnOnNegativeLevel: true + }); + return isFunc; + }; // Recognize standard implicit calls like // f a, f() b, f? c, h[0] d etc. // Added support for spread dots on the left side: f ...a - if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || (nextTag === '...' && (ref = this.tag(i + 2), indexOf.call(IMPLICIT_CALL, ref) >= 0) && !this.findTagsBackwards(i, ['INDEX_START', '['])) || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine)) { + if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || (nextTag === '...' && (ref = this.tag(i + 2), indexOf.call(IMPLICIT_CALL, ref) >= 0) && !this.findTagsBackwards(i, ['INDEX_START', '['])) || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine) && !inControlFlow()) { if (tag === '?') { tag = token[0] = 'FUNC_EXIST'; } diff --git a/src/rewriter.coffee b/src/rewriter.coffee index ee8bc4b7..70b30be1 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -263,6 +263,20 @@ exports.Rewriter = class Rewriter stack.pop() start = stack.pop() + inControlFlow = => + seenFor = @findTagsBackwards(i, ['FOR']) and @findTagsBackwards(i, ['FORIN', 'FOROF', 'FORFROM']) + controlFlow = seenFor or @findTagsBackwards i, ['WHILE', 'UNTIL', 'LOOP', 'LEADING_WHEN'] + return no unless controlFlow + isFunc = no + tagCurrentLine = token[2].first_line + @detectEnd i, + (token, i) -> token[0] in LINEBREAKS + (token, i) -> + [prevTag, ,{first_line}] = tokens[i - 1] || [] + isFunc = tagCurrentLine is first_line and prevTag in ['->', '=>'] + returnOnNegativeLevel: yes + isFunc + # Recognize standard implicit calls like # f a, f() b, f? c, h[0] d etc. # Added support for spread dots on the left side: f ...a @@ -271,7 +285,8 @@ exports.Rewriter = class Rewriter (nextTag in IMPLICIT_CALL or (nextTag is '...' and @tag(i + 2) in IMPLICIT_CALL and not @findTagsBackwards(i, ['INDEX_START', '['])) or nextTag in IMPLICIT_UNSPACED_CALL and - not nextToken.spaced and not nextToken.newLine) + not nextToken.spaced and not nextToken.newLine) and + not inControlFlow() tag = token[0] = 'FUNC_EXIST' if tag is '?' startImplicitCall i + 1 return forward(2) diff --git a/test/error_messages.coffee b/test/error_messages.coffee index f6b32ee5..5e254769 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1777,3 +1777,53 @@ test "#4811: '///' inside a heregex comment does not close the heregex", -> /// .* # comment /// ^^^ ''' + +test "#3933: prevent implicit calls when cotrol flow is missing `THEN`", -> + assertErrorFormat ''' + for a in b do -> + ''',''' + [stdin]:1:12: error: unexpected do + for a in b do -> + ^^ + ''' + + assertErrorFormat ''' + for a in b -> + ''',''' + [stdin]:1:12: error: unexpected -> + for a in b -> + ^^ + ''' + + assertErrorFormat ''' + for a in b do => + ''',''' + [stdin]:1:12: error: unexpected do + for a in b do => + ^^ + ''' + + assertErrorFormat ''' + while a do -> + ''',''' + [stdin]:1:9: error: unexpected do + while a do -> + ^^ + ''' + + assertErrorFormat ''' + until a do => + ''',''' + [stdin]:1:9: error: unexpected do + until a do => + ^^ + ''' + + assertErrorFormat ''' + switch + when a -> + ''',''' + [stdin]:2:10: error: unexpected -> + when a -> + ^^ + ''' \ No newline at end of file