From ac1b2b5c30bc25d4777a7e118818f7c85cd99f4d Mon Sep 17 00:00:00 2001 From: Julian Rosse Date: Sat, 22 Apr 2017 13:10:10 -0500 Subject: [PATCH 1/3] Iss4248 unicode code point escapes (cleanup) (#4522) * Fix #4248: Unicode code point escapes * rewrite unicode code point escapes as unicode escapes * smarter defaults * and resimplify * correct surrogate pairs * fixes from code review * handle adjacent code point escapes * smarter regex * fix from code review * refactor toJS() to shared test helper --- test/modules.coffee | 6 ------ test/regexps.coffee | 6 ------ test/strings.coffee | 6 ------ test/support/helpers.coffee | 4 ++++ 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/test/modules.coffee b/test/modules.coffee index a0bcf1a3d1..60dd19083f 100644 --- a/test/modules.coffee +++ b/test/modules.coffee @@ -36,12 +36,6 @@ # CoffeeScript also supports optional commas within `{ … }`. -# Helper function -toJS = (str) -> - CoffeeScript.compile str, bare: yes - .replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace - - # Import statements test "backticked import statement", -> diff --git a/test/regexps.coffee b/test/regexps.coffee index 623ae41dd4..fc73caffca 100644 --- a/test/regexps.coffee +++ b/test/regexps.coffee @@ -6,12 +6,6 @@ # * Regexen # * Heregexen -# Helper function -toJS = (str) -> - CoffeeScript.compile str, bare: yes - .replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace - - test "basic regular expression literals", -> ok 'a'.match(/a/) ok 'a'.match /a/ diff --git a/test/strings.coffee b/test/strings.coffee index 1cd17efae0..66a1869fb8 100644 --- a/test/strings.coffee +++ b/test/strings.coffee @@ -7,12 +7,6 @@ # * Strings # * Heredocs -# Helper function -toJS = (str) -> - CoffeeScript.compile str, bare: yes - .replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace - - test "backslash escapes", -> eq "\\/\\\\", /\/\\/.source diff --git a/test/support/helpers.coffee b/test/support/helpers.coffee index 3fc46508af..5ec9684156 100644 --- a/test/support/helpers.coffee +++ b/test/support/helpers.coffee @@ -15,3 +15,7 @@ arrayEgal = (a, b) -> exports.eq = (a, b, msg) -> ok egal(a, b), msg or "Expected #{a} to equal #{b}" exports.arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg or "Expected #{a} to deep equal #{b}" + +exports.toJS = (str) -> + CoffeeScript.compile str, bare: yes + .replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace From 26cb24acc8d33affe9d91b888cca9841384fb7fc Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 1 May 2017 19:31:17 -0700 Subject: [PATCH 2/3] `return` and `export default` can now accept implicit objects (#4532) --- lib/coffee-script/lexer.js | 4 ++-- lib/coffee-script/rewriter.js | 4 ++-- src/lexer.coffee | 6 +++--- src/rewriter.coffee | 10 ++++------ test/modules.coffee | 22 ++++++++++++++++++++++ test/objects.coffee | 12 ++++++++++++ 6 files changed, 45 insertions(+), 13 deletions(-) diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index 8645685730..3b5ec268f4 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -450,7 +450,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; @@ -874,7 +874,7 @@ Lexer.prototype.unfinished = function() { var ref2; - return LINE_CONTINUER.test(this.chunk) || ((ref2 = this.tag()) === '\\' || ref2 === '.' || ref2 === '?.' || ref2 === '?::' || ref2 === 'UNARY' || ref2 === 'MATH' || ref2 === 'UNARY_MATH' || ref2 === '+' || ref2 === '-' || ref2 === '**' || ref2 === 'SHIFT' || ref2 === 'RELATION' || ref2 === 'COMPARE' || ref2 === '&' || ref2 === '^' || ref2 === '|' || ref2 === '&&' || ref2 === '||' || ref2 === 'BIN?' || ref2 === 'THROW' || ref2 === 'EXTENDS'); + return LINE_CONTINUER.test(this.chunk) || ((ref2 = this.tag()) === '\\' || ref2 === '.' || ref2 === '?.' || ref2 === '?::' || ref2 === 'UNARY' || ref2 === 'MATH' || ref2 === 'UNARY_MATH' || ref2 === '+' || ref2 === '-' || ref2 === '**' || ref2 === 'SHIFT' || ref2 === 'RELATION' || ref2 === 'COMPARE' || ref2 === '&' || ref2 === '^' || ref2 === '|' || ref2 === '&&' || ref2 === '||' || ref2 === 'BIN?' || ref2 === 'THROW' || ref2 === 'EXTENDS' || ref2 === 'DEFAULT'); }; Lexer.prototype.formatString = function(str, options) { diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index a18e8a6b61..fc3696a32b 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.12.5 (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, ref, 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, ref, rite, 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; @@ -14,7 +14,7 @@ return tok; }; - exports.Rewriter = (function() { + exports.Rewriter = Rewriter = (function() { function Rewriter() {} Rewriter.prototype.rewrite = function(tokens1) { diff --git a/src/lexer.coffee b/src/lexer.coffee index 669e8a4ba5..9ff8df764d 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -378,7 +378,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 @@ -430,7 +430,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') @@ -761,7 +761,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 04ab776aff..bb6a787a5c 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 60dd19083f..28195b6fc0 100644 --- a/test/modules.coffee +++ b/test/modules.coffee @@ -347,6 +347,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 21e431ad64..d006d11f7a 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() From 51c06574a084388ca9537415b6907a8cb3bac013 Mon Sep 17 00:00:00 2001 From: Michal Srb Date: Wed, 3 May 2017 07:00:21 +0100 Subject: [PATCH 3/3] Fix #4150: Correctly outdent ternary followed by method call (#4535) --- lib/coffee-script/rewriter.js | 2 +- src/rewriter.coffee | 3 ++- test/formatting.coffee | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index fc3696a32b..717e7fab53 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -395,7 +395,7 @@ starter = indent = outdent = null; condition = function(token, i) { var ref, ref1, ref2, ref3; - return token[1] !== ';' && (ref = token[0], indexOf.call(SINGLE_CLOSERS, ref) >= 0) && !(token[0] === 'TERMINATOR' && (ref1 = this.tag(i + 1), indexOf.call(EXPRESSION_CLOSE, ref1) >= 0)) && !(token[0] === 'ELSE' && starter !== 'THEN') && !(((ref2 = token[0]) === 'CATCH' || ref2 === 'FINALLY') && (starter === '->' || starter === '=>')) || (ref3 = token[0], indexOf.call(CALL_CLOSERS, ref3) >= 0) && this.tokens[i - 1].newLine; + return token[1] !== ';' && (ref = token[0], indexOf.call(SINGLE_CLOSERS, ref) >= 0) && !(token[0] === 'TERMINATOR' && (ref1 = this.tag(i + 1), indexOf.call(EXPRESSION_CLOSE, ref1) >= 0)) && !(token[0] === 'ELSE' && starter !== 'THEN') && !(((ref2 = token[0]) === 'CATCH' || ref2 === 'FINALLY') && (starter === '->' || starter === '=>')) || (ref3 = token[0], indexOf.call(CALL_CLOSERS, ref3) >= 0) && (this.tokens[i - 1].newLine || this.tokens[i - 1][0] === 'OUTDENT'); }; action = function(token, i) { return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent); diff --git a/src/rewriter.coffee b/src/rewriter.coffee index bb6a787a5c..5217e727af 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -396,7 +396,8 @@ exports.Rewriter = class Rewriter not (token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE) and not (token[0] is 'ELSE' and starter isnt 'THEN') and not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>']) or - token[0] in CALL_CLOSERS and @tokens[i - 1].newLine + token[0] in CALL_CLOSERS and + (@tokens[i - 1].newLine or @tokens[i - 1][0] is 'OUTDENT') action = (token, i) -> @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent diff --git a/test/formatting.coffee b/test/formatting.coffee index 31cc282840..d175fbe3a8 100644 --- a/test/formatting.coffee +++ b/test/formatting.coffee @@ -198,6 +198,29 @@ test "#1495, method call chaining", -> ).join ', ' eq 'a, b, c', result +test "chaining should not wrap spilling ternary", -> + throws -> CoffeeScript.compile """ + if 0 then 1 else g + a: 42 + .h() + """ + +test "chaining should wrap calls containing spilling ternary", -> + f = (x) -> h: x + id = (x) -> x + result = f if true then 42 else id + a: 2 + .h + eq 42, result + +test "chaining should work within spilling ternary", -> + f = (x) -> h: x + id = (x) -> x + result = f if false then 1 else id + a: 3 + .a + eq 3, result.h + # Nested blocks caused by paren unwrapping test "#1492: Nested blocks don't cause double semicolons", -> js = CoffeeScript.compile '(0;0)'