diff --git a/lib/coffeescript/lexer.js b/lib/coffeescript/lexer.js index 884f9881..3a250189 100644 --- a/lib/coffeescript/lexer.js +++ b/lib/coffeescript/lexer.js @@ -672,9 +672,7 @@ if (dent) { this.outdebt -= moveOut; } - while (this.value() === ';') { - this.tokens.pop(); - } + this.suppressSemicolons(); if (!(this.tag() === 'TERMINATOR' || noNewlines)) { this.token('TERMINATOR', '\n', outdentLength, 0); } @@ -703,9 +701,7 @@ // Generate a newline token. Consecutive newlines get merged together. newlineToken(offset) { - while (this.value() === ';') { - this.tokens.pop(); - } + this.suppressSemicolons(); if (this.tag() !== 'TERMINATOR') { this.token('TERMINATOR', '\n', offset, 0); } @@ -844,7 +840,7 @@ // here. `;` and newlines are both treated as a `TERMINATOR`, we distinguish // parentheses that indicate a method call from regular parentheses, and so on. literalToken() { - var match, message, origin, prev, ref, ref1, ref2, ref3, skipToken, tag, token, value; + var match, message, origin, prev, ref, ref1, ref2, ref3, ref4, skipToken, tag, token, value; if (match = OPERATOR.exec(this.chunk)) { [value] = match; if (CODE.test(value)) { @@ -884,6 +880,9 @@ this.exportSpecifierList = false; } if (value === ';') { + if (ref2 = prev != null ? prev[0] : void 0, indexOf.call(['=', ...UNFINISHED], ref2) >= 0) { + this.error('unexpected ;'); + } this.seenFor = this.seenImport = this.seenExport = false; tag = 'TERMINATOR'; } else if (value === '*' && (prev != null ? prev[0] : void 0) === 'EXPORT') { @@ -903,12 +902,12 @@ } else if (value === '?' && (prev != null ? prev.spaced : void 0)) { tag = 'BIN?'; } else if (prev) { - if (value === '(' && !prev.spaced && (ref2 = prev[0], indexOf.call(CALLABLE, ref2) >= 0)) { + if (value === '(' && !prev.spaced && (ref3 = prev[0], indexOf.call(CALLABLE, ref3) >= 0)) { if (prev[0] === '?') { prev[0] = 'FUNC_EXIST'; } tag = 'CALL_START'; - } else if (value === '[' && (((ref3 = prev[0], indexOf.call(INDEXABLE, ref3) >= 0) && !prev.spaced) || (prev[0] === '::'))) { // `.prototype` can’t be a method you can call. + } else if (value === '[' && (((ref4 = prev[0], indexOf.call(INDEXABLE, ref4) >= 0) && !prev.spaced) || (prev[0] === '::'))) { // `.prototype` can’t be a method you can call. tag = 'INDEX_START'; switch (prev[0]) { case '?': @@ -1407,6 +1406,20 @@ return `${options.delimiter}${body}${options.delimiter}`; } + suppressSemicolons() { + var ref, ref1, results; + results = []; + while (this.value() === ';') { + this.tokens.pop(); + if (ref = (ref1 = this.prev()) != null ? ref1[0] : void 0, indexOf.call(['=', ...UNFINISHED], ref) >= 0) { + results.push(this.error('unexpected ;')); + } else { + results.push(void 0); + } + } + return results; + } + // Throws an error at either a given offset from the current chunk or at the // location of a token (`token[2]`). error(message, options = {}) { diff --git a/src/lexer.coffee b/src/lexer.coffee index b31ac13d..f3b2509a 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -509,7 +509,7 @@ exports.Lexer = class Lexer @token 'OUTDENT', moveOut, 0, outdentLength moveOut -= dent @outdebt -= moveOut if dent - @tokens.pop() while @value() is ';' + @suppressSemicolons() @token 'TERMINATOR', '\n', outdentLength, 0 unless @tag() is 'TERMINATOR' or noNewlines @indent = decreasedIndent @@ -527,7 +527,7 @@ exports.Lexer = class Lexer # Generate a newline token. Consecutive newlines get merged together. newlineToken: (offset) -> - @tokens.pop() while @value() is ';' + @suppressSemicolons() @token 'TERMINATOR', '\n', offset, 0 unless @tag() is 'TERMINATOR' this @@ -662,6 +662,7 @@ exports.Lexer = class Lexer @exportSpecifierList = no if value is ';' + @error 'unexpected ;' if prev?[0] in ['=', UNFINISHED...] @seenFor = @seenImport = @seenExport = no tag = 'TERMINATOR' else if value is '*' and prev?[0] is 'EXPORT' @@ -1053,6 +1054,11 @@ exports.Lexer = class Lexer when other then (if options.double then "\\#{other}" else other) "#{options.delimiter}#{body}#{options.delimiter}" + suppressSemicolons: -> + while @value() is ';' + @tokens.pop() + @error 'unexpected ;' if @prev()?[0] in ['=', UNFINISHED...] + # Throws an error at either a given offset from the current chunk or at the # location of a token (`token[2]`). error: (message, options = {}) -> diff --git a/test/error_messages.coffee b/test/error_messages.coffee index d5aeda60..640e46c4 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1759,3 +1759,12 @@ test "#3199: error message for throw indented comprehension", -> x for x in [1, 2, 3] ^ ''' + +test "#3098: suppressed newline should be unsuppressed by semicolon", -> + assertErrorFormat ''' + a = ; 5 + ''', ''' + [stdin]:1:5: error: unexpected ; + a = ; 5 + ^ + '''