From b1f5ffdc98be714eddd688dcd5061be72ba110fb Mon Sep 17 00:00:00 2001 From: Julian Rosse Date: Tue, 29 Aug 2017 09:23:53 -0400 Subject: [PATCH 1/3] add parens to chained do iife [Fixes #3736] --- lib/coffeescript/rewriter.js | 30 ++++++++++++++++++++++++++++++ src/rewriter.coffee | 18 ++++++++++++++++++ test/formatting.coffee | 16 ++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index 6923f407d3..586c358785 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -86,6 +86,7 @@ this.normalizeLines(); this.tagPostfixConditionals(); this.addImplicitBracesAndParens(); + this.addParensToChainedDoIife(); this.rescueStowawayComments(); this.addLocationDataToGeneratedTokens(); this.enforceValidCSXAttributes(); @@ -734,6 +735,35 @@ }); } + // Add parens around a `do` IIFE followed by a chained `.` so that the + // chaining applies to the executed function rather than the function + // object (see #3736) + addParensToChainedDoIife() { + var action, condition, doIndex; + condition = function(token, i) { + return this.tag(i - 1) === 'OUTDENT'; + }; + action = function(token, i) { + if (token[0] !== '.') { + return; + } + this.tokens.splice(doIndex, 0, generate('(', '(', this.tokens[doIndex][2])); + return this.tokens.splice(i + 1, 0, generate(')', ')', ['', 'implicit parentheses', token[2]])); + }; + doIndex = null; + return this.scanTokens(function(token, i, tokens) { + var ref; + if (!(token[1] === 'do' && ((ref = this.tag(i + 1)) === '->' || ref === '=>') && this.tag(i + 2) === 'INDENT')) { + return 1; + } + doIndex = i; + this.detectEnd(i + 2, condition, action, { + log: true + }); + return 2; + }); + } + // Because our grammar is LALR(1), it can’t handle some single-line // expressions that lack ending delimiters. The **Rewriter** adds the implicit // blocks, so it doesn’t need to. To keep the grammar clean and tidy, trailing diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 1b41e3c29d..3c1f9946ab 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -53,6 +53,7 @@ exports.Rewriter = class Rewriter @normalizeLines() @tagPostfixConditionals() @addImplicitBracesAndParens() + @addParensToChainedDoIife() @rescueStowawayComments() @addLocationDataToGeneratedTokens() @enforceValidCSXAttributes() @@ -512,6 +513,23 @@ exports.Rewriter = class Rewriter last_column: prevLocationData.last_column return 1 + # Add parens around a `do` IIFE followed by a chained `.` so that the + # chaining applies to the executed function rather than the function + # object (see #3736) + addParensToChainedDoIife: -> + condition = (token, i) -> + @tag(i - 1) is 'OUTDENT' + action = (token, i) -> + return unless token[0] is '.' + @tokens.splice doIndex, 0, generate '(', '(', @tokens[doIndex][2] + @tokens.splice i + 1, 0, generate ')', ')', ['', 'implicit parentheses', token[2]] + doIndex = null + @scanTokens (token, i, tokens) -> + return 1 unless token[1] is 'do' and @tag(i + 1) in ['->', '=>'] and @tag(i + 2) is 'INDENT' + doIndex = i + @detectEnd i + 2, condition, action, log: yes + return 2 + # Because our grammar is LALR(1), it can’t handle some single-line # expressions that lack ending delimiters. The **Rewriter** adds the implicit # blocks, so it doesn’t need to. To keep the grammar clean and tidy, trailing diff --git a/test/formatting.coffee b/test/formatting.coffee index 98c6de7edd..b2bee6d240 100644 --- a/test/formatting.coffee +++ b/test/formatting.coffee @@ -420,3 +420,19 @@ test "#4576: function chaining on separate rows", -> .then -> yes .then ok + +test "#3736: chaining after do IIFE", -> + eq 3, + do -> + a: 3 + .a + + eq 3, + do -> a: 3 + .a + + # preserve existing chaining behavior for non-IIFE `do` + b = c: -> 4 + eq 4, + do b + .c From 7e185c14dc95eb04215633164a44a5e5ffce137e Mon Sep 17 00:00:00 2001 From: Julian Rosse Date: Tue, 29 Aug 2017 09:50:54 -0400 Subject: [PATCH 2/3] remove debug code --- lib/coffeescript/rewriter.js | 4 +--- src/rewriter.coffee | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index 586c358785..c7560e767d 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -757,9 +757,7 @@ return 1; } doIndex = i; - this.detectEnd(i + 2, condition, action, { - log: true - }); + this.detectEnd(i + 2, condition, action); return 2; }); } diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 3c1f9946ab..57dbf3ca36 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -527,7 +527,7 @@ exports.Rewriter = class Rewriter @scanTokens (token, i, tokens) -> return 1 unless token[1] is 'do' and @tag(i + 1) in ['->', '=>'] and @tag(i + 2) is 'INDENT' doIndex = i - @detectEnd i + 2, condition, action, log: yes + @detectEnd i + 2, condition, action return 2 # Because our grammar is LALR(1), it can’t handle some single-line From b8b7c4aedf6600ba466b18030a976950f04c8e77 Mon Sep 17 00:00:00 2001 From: Julian Rosse Date: Tue, 29 Aug 2017 13:27:22 -0400 Subject: [PATCH 3/3] fixes from code review --- lib/coffeescript/rewriter.js | 7 ++++--- src/rewriter.coffee | 6 +++--- test/formatting.coffee | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index c7560e767d..8a5526ab36 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -744,11 +744,12 @@ return this.tag(i - 1) === 'OUTDENT'; }; action = function(token, i) { - if (token[0] !== '.') { + var ref; + if (ref = token[0], indexOf.call(CALL_CLOSERS, ref) < 0) { return; } - this.tokens.splice(doIndex, 0, generate('(', '(', this.tokens[doIndex][2])); - return this.tokens.splice(i + 1, 0, generate(')', ')', ['', 'implicit parentheses', token[2]])); + this.tokens.splice(doIndex, 0, generate('(', '(', this.tokens[doIndex])); + return this.tokens.splice(i + 1, 0, generate(')', ')', this.tokens[i])); }; doIndex = null; return this.scanTokens(function(token, i, tokens) { diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 57dbf3ca36..33ee7efa4f 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -520,9 +520,9 @@ exports.Rewriter = class Rewriter condition = (token, i) -> @tag(i - 1) is 'OUTDENT' action = (token, i) -> - return unless token[0] is '.' - @tokens.splice doIndex, 0, generate '(', '(', @tokens[doIndex][2] - @tokens.splice i + 1, 0, generate ')', ')', ['', 'implicit parentheses', token[2]] + return unless token[0] in CALL_CLOSERS + @tokens.splice doIndex, 0, generate '(', '(', @tokens[doIndex] + @tokens.splice i + 1, 0, generate ')', ')', @tokens[i] doIndex = null @scanTokens (token, i, tokens) -> return 1 unless token[1] is 'do' and @tag(i + 1) in ['->', '=>'] and @tag(i + 2) is 'INDENT' diff --git a/test/formatting.coffee b/test/formatting.coffee index b2bee6d240..49505e3b1e 100644 --- a/test/formatting.coffee +++ b/test/formatting.coffee @@ -429,7 +429,7 @@ test "#3736: chaining after do IIFE", -> eq 3, do -> a: 3 - .a + ?.a # preserve existing chaining behavior for non-IIFE `do` b = c: -> 4