From 51f24e06413baced351981f2eba696f035ded929 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Wed, 14 Sep 2016 23:30:58 -0700 Subject: [PATCH] Be much more careful about parsing `*` in import and export statements; handle export expressions that use `*` on the same line as `export` --- lib/coffee-script/lexer.js | 23 +++++++++++++++-------- src/lexer.coffee | 10 +++++++--- test/modules.coffee | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index d8acb0b706..f1b095c0df 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -68,7 +68,7 @@ }; Lexer.prototype.identifierToken = function() { - var alias, colon, colonOffset, id, idLength, input, match, poppedToken, prev, ref2, ref3, ref4, ref5, ref6, tag, tagToken; + var alias, colon, colonOffset, id, idLength, input, match, poppedToken, prev, ref2, ref3, ref4, ref5, tag, tagToken; if (!(match = IDENTIFIER.exec(this.chunk))) { return 0; } @@ -83,7 +83,14 @@ this.token('FROM', id); return id.length; } - if (id === 'as' && (this.seenImport || this.seenExport) && ((ref2 = this.tag()) === 'IDENTIFIER' || ref2 === 'IMPORT_ALL' || ref2 === 'EXPORT_ALL')) { + if (id === 'as' && this.seenImport && (this.tag() === 'IDENTIFIER' || this.value() === '*')) { + if (this.value() === '*') { + this.tokens[this.tokens.length - 1][0] = 'IMPORT_ALL'; + } + this.token('AS', id); + return id.length; + } + if (id === 'as' && this.seenExport && this.tag() === 'IDENTIFIER') { this.token('AS', id); return id.length; } @@ -91,11 +98,11 @@ this.token('DEFAULT', id); return id.length; } - ref3 = this.tokens, prev = ref3[ref3.length - 1]; - tag = colon || (prev != null) && (((ref4 = prev[0]) === '.' || ref4 === '?.' || ref4 === '::' || ref4 === '?::') || !prev.spaced && prev[0] === '@') ? 'PROPERTY' : 'IDENTIFIER'; + ref2 = this.tokens, prev = ref2[ref2.length - 1]; + tag = colon || (prev != null) && (((ref3 = prev[0]) === '.' || ref3 === '?.' || ref3 === '::' || ref3 === '?::') || !prev.spaced && prev[0] === '@') ? 'PROPERTY' : 'IDENTIFIER'; if (tag === 'IDENTIFIER' && (indexOf.call(JS_KEYWORDS, id) >= 0 || indexOf.call(COFFEE_KEYWORDS, id) >= 0)) { tag = id.toUpperCase(); - if (tag === 'WHEN' && (ref5 = this.tag(), indexOf.call(LINE_BREAK, ref5) >= 0)) { + if (tag === 'WHEN' && (ref4 = this.tag(), indexOf.call(LINE_BREAK, ref4) >= 0)) { tag = 'LEADING_WHEN'; } else if (tag === 'FOR') { this.seenFor = true; @@ -157,7 +164,7 @@ tagToken.origin = [tag, alias, tagToken[2]]; } if (poppedToken) { - ref6 = [poppedToken[2].first_line, poppedToken[2].first_column], tagToken[2].first_line = ref6[0], tagToken[2].first_column = ref6[1]; + ref5 = [poppedToken[2].first_line, poppedToken[2].first_column], tagToken[2].first_line = ref5[0], tagToken[2].first_column = ref5[1]; } if (colon) { colonOffset = input.lastIndexOf(':'); @@ -540,8 +547,8 @@ if (value === ';') { this.seenFor = this.seenImport = this.seenExport = false; tag = 'TERMINATOR'; - } else if (value === '*' && this.indent === 0 && (this.seenImport || this.seenExport)) { - tag = this.seenImport ? 'IMPORT_ALL' : 'EXPORT_ALL'; + } else if (value === '*' && prev[0] === 'EXPORT') { + tag = 'EXPORT_ALL'; } else if (indexOf.call(MATH, value) >= 0) { tag = 'MATH'; } else if (indexOf.call(COMPARE, value) >= 0) { diff --git a/src/lexer.coffee b/src/lexer.coffee index c17962614d..e0d3febd9b 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -115,7 +115,11 @@ exports.Lexer = class Lexer if id is 'from' and @tag() is 'YIELD' @token 'FROM', id return id.length - if id is 'as' and (@seenImport or @seenExport) and @tag() in ['IDENTIFIER', 'IMPORT_ALL', 'EXPORT_ALL'] + if id is 'as' and @seenImport and (@tag() is 'IDENTIFIER' or @value() is '*') + @tokens[@tokens.length - 1][0] = 'IMPORT_ALL' if @value() is '*' + @token 'AS', id + return id.length + if id is 'as' and @seenExport and @tag() is 'IDENTIFIER' @token 'AS', id return id.length if id is 'default' and @seenExport @@ -450,8 +454,8 @@ exports.Lexer = class Lexer if value is ';' @seenFor = @seenImport = @seenExport = no tag = 'TERMINATOR' - else if value is '*' and @indent is 0 and (@seenImport or @seenExport) - tag = if @seenImport then 'IMPORT_ALL' else 'EXPORT_ALL' + else if value is '*' and prev[0] is 'EXPORT' + tag = 'EXPORT_ALL' else if value in MATH then tag = 'MATH' else if value in COMPARE then tag = 'COMPARE' else if value in COMPOUND_ASSIGN then tag = 'COMPOUND_ASSIGN' diff --git a/test/modules.coffee b/test/modules.coffee index 58ce313ebf..d51f5fb9e4 100644 --- a/test/modules.coffee +++ b/test/modules.coffee @@ -608,6 +608,22 @@ test "`as` can be used as an alias name", -> } from 'lib';""" eq toJS(input), output +test "`*` can be used in an expression on the same line as an export keyword", -> + input = "export foo = (x) -> x * x" + output = """ + export var foo = function(x) { + return x * x; + };""" + eq toJS(input), output + input = "export default foo = (x) -> x * x" + output = """ + var foo; + + export default foo = function(x) { + return x * x; + };""" + eq toJS(input), output + test "`*` and `from` can be used in an export default expression", -> input = """ export default foo.extend