diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index 0de186ef39..8332f05f7f 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.12.0 (function() { - var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INVALID_ESCAPE, INVERSES, JSTOKEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, VALID_FLAGS, WHITESPACE, compact, count, invertLiterate, isUnassignable, key, locationDataToString, ref, ref1, repeat, starts, throwSyntaxError, + var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INVALID_ESCAPE, INVERSES, JSTOKEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, VALID_FLAGS, WHITESPACE, compact, count, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, ref, ref1, repeat, starts, throwSyntaxError, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, slice = [].slice; @@ -131,7 +131,7 @@ } } } - } else if (tag === 'IDENTIFIER' && this.seenFor && id === 'from') { + } else if (tag === 'IDENTIFIER' && this.seenFor && id === 'from' && isForFrom(prev)) { tag = 'FORFROM'; this.seenFor = false; } @@ -963,6 +963,23 @@ exports.isUnassignable = isUnassignable; + isForFrom = function(prev) { + var ref2; + if (prev[0] === 'IDENTIFIER') { + if (prev[1] === 'from') { + prev[1][0] = 'IDENTIFIER'; + true; + } + return true; + } else if (prev[0] === 'FOR') { + return false; + } else if ((ref2 = prev[1]) === '{' || ref2 === '[' || ref2 === ',' || ref2 === ':') { + return false; + } else { + return true; + } + }; + JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super', 'import', 'export', 'default']; COFFEE_KEYWORDS = ['undefined', 'Infinity', 'NaN', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when']; diff --git a/src/lexer.coffee b/src/lexer.coffee index 4844cfb16f..197b69dad4 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -165,7 +165,8 @@ exports.Lexer = class Lexer if @value() is '!' poppedToken = @tokens.pop() id = '!' + id - else if tag is 'IDENTIFIER' and @seenFor and id is 'from' + else if tag is 'IDENTIFIER' and @seenFor and id is 'from' and + isForFrom(prev) tag = 'FORFROM' @seenFor = no @@ -824,6 +825,27 @@ isUnassignable = (name, displayName = name) -> switch exports.isUnassignable = isUnassignable +# `from` isn’t a CoffeeScript keyword, but it behaves like one in `import` and +# `export` statements (handled above) and in the declaration line of a `for` +# loop. Try to detect when `from` is a variable identifier and when it is this +# “sometimes” keyword. +isForFrom = (prev) -> + if prev[0] is 'IDENTIFIER' + # `for i from from`, `for from from iterable` + if prev[1] is 'from' + prev[1][0] = 'IDENTIFIER' + yes + # `for i from iterable` + yes + # `for from…` + else if prev[0] is 'FOR' + no + # `for {from}…`, `for [from]…`, `for {a, from}…`, `for {a: from}…` + else if prev[1] in ['{', '[', ',', ':'] + no + else + yes + # Constants # --------- diff --git a/test/generators.coffee b/test/generators.coffee index 7ae3ae9c6c..771f30c937 100644 --- a/test/generators.coffee +++ b/test/generators.coffee @@ -277,7 +277,6 @@ test "for-from loops over generators", -> ok array3.length is 0 or array3.join(',') is '70,20' arrayEq array4, [] - test "for-from comprehensions over generators", -> gen = -> yield from [30, 41, 51, 60] @@ -288,3 +287,59 @@ test "for-from comprehensions over generators", -> ok array1.join(' ') is '41 51' ok array2.length is 0 + +test "from as an iterable variable name in a for loop declaration", -> + from = [1, 2, 3] + out = [] + for i from from + out.push i + arrayEq from, out + +test "from as an iterator variable name in a for loop declaration", -> + a = [1, 2, 3] + b = [] + for from from a + b.push from + arrayEq a, b + +test "from as a destructured object variable name in a for loop declaration", -> + a = [ + from: 1 + to: 2 + , + from: 3 + to: 4 + ] + b = [] + for {from, to} in a + b.push from + arrayEq b, [1, 3] + + c = [] + for {to, from} in a + c.push from + arrayEq c, [1, 3] + +test "from as a destructured, aliased object variable name in a for loop declaration", -> + a = [ + b: 1 + c: 2 + , + b: 3 + c: 4 + ] + out = [] + + for {b: from} in a + out.push from + arrayEq out, [1, 3] + +test "from as a destructured array variable name in a for loop declaration", -> + a = [ + [1, 2] + [3, 4] + ] + b = [] + for [from, to] from a + b.push from + arrayEq b, [1, 3]