From 2f9ab1d328de8704dafee948cdadd85332866bfb Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 1 May 2017 19:26:24 -0700 Subject: [PATCH] [CS2] `return` and `export default` can now accept implicit objects (#4532) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Start with the test I want to pass: return an implicit (braces-less) object * Update Rewriter class to follow pattern of nodes.coffee; move debugging snippet to where it’ll work in CS2 * Allow export default implicit object * `return` assumes a continuation onto the next line *if* the next line is indented * Fix comment; improve test --- lib/coffeescript/lexer.js | 4 ++-- lib/coffeescript/rewriter.js | 4 ++-- src/grammar.coffee | 1 - src/lexer.coffee | 6 +++--- src/rewriter.coffee | 10 ++++------ test/modules.coffee | 22 ++++++++++++++++++++++ test/objects.coffee | 12 ++++++++++++ 7 files changed, 45 insertions(+), 14 deletions(-) diff --git a/lib/coffeescript/lexer.js b/lib/coffeescript/lexer.js index 459b3db8..200cdee3 100644 --- a/lib/coffeescript/lexer.js +++ b/lib/coffeescript/lexer.js @@ -465,7 +465,7 @@ return indent.length; } if (size > this.indent) { - if (noNewlines) { + if (noNewlines || this.tag() === 'RETURN') { this.indebt = size - this.indent; this.suppressNewlines(); return indent.length; @@ -890,7 +890,7 @@ unfinished() { var ref; - return LINE_CONTINUER.test(this.chunk) || ((ref = this.tag()) === '\\' || ref === '.' || ref === '?.' || ref === '?::' || ref === 'UNARY' || ref === 'MATH' || ref === 'UNARY_MATH' || ref === '+' || ref === '-' || ref === '**' || ref === 'SHIFT' || ref === 'RELATION' || ref === 'COMPARE' || ref === '&' || ref === '^' || ref === '|' || ref === '&&' || ref === '||' || ref === 'BIN?' || ref === 'THROW' || ref === 'EXTENDS'); + return LINE_CONTINUER.test(this.chunk) || ((ref = this.tag()) === '\\' || ref === '.' || ref === '?.' || ref === '?::' || ref === 'UNARY' || ref === 'MATH' || ref === 'UNARY_MATH' || ref === '+' || ref === '-' || ref === '**' || ref === 'SHIFT' || ref === 'RELATION' || ref === 'COMPARE' || ref === '&' || ref === '^' || ref === '|' || ref === '&&' || ref === '||' || ref === 'BIN?' || ref === 'THROW' || ref === 'EXTENDS' || ref === 'DEFAULT'); } formatString(str, options) { diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index ebae87c5..7e29b4fe 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 2.0.0-beta1 (function() { - var BALANCED_PAIRS, CALL_CLOSERS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, generate, k, left, len, rite, + 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, indexOf = [].indexOf; generate = function(tag, value, origin) { @@ -13,7 +13,7 @@ return tok; }; - exports.Rewriter = (function() { + exports.Rewriter = Rewriter = (function() { class Rewriter { rewrite(tokens1) { this.tokens = tokens1; diff --git a/src/grammar.coffee b/src/grammar.coffee index 7f41808c..3b5601de 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -229,7 +229,6 @@ grammar = o 'AWAIT RETURN', -> new AwaitReturn ] - # A block comment. Comment: [ o 'HERECOMMENT', -> new Comment $1 diff --git a/src/lexer.coffee b/src/lexer.coffee index 51618e63..6a61c28b 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -403,7 +403,7 @@ exports.Lexer = class Lexer return indent.length if size > @indent - if noNewlines + if noNewlines or @tag() is 'RETURN' @indebt = size - @indent @suppressNewlines() return indent.length @@ -455,7 +455,7 @@ exports.Lexer = class Lexer this # Matches and consumes non-meaningful whitespace. Tag the previous token - # as being "spaced", because there are some cases where it makes a difference. + # as being “spaced”, because there are some cases where it makes a difference. whitespaceToken: -> return 0 unless (match = WHITESPACE.exec @chunk) or (nline = @chunk.charAt(0) is '\n') @@ -790,7 +790,7 @@ exports.Lexer = class Lexer LINE_CONTINUER.test(@chunk) or @tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', 'UNARY_MATH', '+', '-', '**', 'SHIFT', 'RELATION', 'COMPARE', '&', '^', '|', '&&', '||', - 'BIN?', 'THROW', 'EXTENDS'] + 'BIN?', 'THROW', 'EXTENDS', 'DEFAULT'] formatString: (str, options) -> @replaceUnicodeCodePointEscapes str.replace(STRING_OMIT, '$1'), options diff --git a/src/rewriter.coffee b/src/rewriter.coffee index f3492335..da4c26e6 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -14,11 +14,7 @@ generate = (tag, value, origin) -> # The **Rewriter** class is used by the [Lexer](lexer.html), directly against # its internal array of tokens. -class exports.Rewriter - - # Helpful snippet for debugging: - # - # console.log (t[0] + '/' + t[1] for t in @tokens).join ' ' +exports.Rewriter = class Rewriter # Rewrite the token stream in multiple passes, one logical filter at # a time. This could certainly be changed into a single pass through the @@ -26,6 +22,8 @@ class exports.Rewriter # like this. The order of these passes matters -- indentation must be # corrected before implicit parentheses can be wrapped around blocks of code. rewrite: (@tokens) -> + # Helpful snippet for debugging: + # console.log (t[0] + '/' + t[1] for t in @tokens).join ' ' @removeLeadingNewlines() @closeOpenCalls() @closeOpenIndexes() @@ -186,7 +184,7 @@ class exports.Rewriter # 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'] - stack.push ['CONTROL', i, ours: true] + stack.push ['CONTROL', i, ours: yes] return forward(1) if tag is 'INDENT' and inImplicit() diff --git a/test/modules.coffee b/test/modules.coffee index c26dabfb..33a9c30a 100644 --- a/test/modules.coffee +++ b/test/modules.coffee @@ -353,6 +353,28 @@ test "export default object", -> };""" eq toJS(input), output +test "export default implicit object", -> + input = "export default foo: 'bar', baz: 'qux'" + output = """ + export default { + foo: 'bar', + baz: 'qux' + };""" + eq toJS(input), output + +test "export default multiline implicit object", -> + input = """ + export default + foo: 'bar', + baz: 'qux' + """ + output = """ + export default { + foo: 'bar', + baz: 'qux' + };""" + eq toJS(input), output + test "export default assignment expression", -> input = "export default foo = 'bar'" output = """ diff --git a/test/objects.coffee b/test/objects.coffee index 21e431ad..d006d11f 100644 --- a/test/objects.coffee +++ b/test/objects.coffee @@ -575,3 +575,15 @@ test "#4324: Shorthand after interpolated key", -> obj = {"#{1}": 1, a} eq 1, obj[1] eq 2, obj.a + +test "#1263: Braceless object return", -> + fn = -> + return + a: 1 + b: 2 + c: -> 3 + + obj = fn() + eq 1, obj.a + eq 2, obj.b + eq 3, obj.c()