From 15a70f863ce5158ee949426386f6068c13eb422d Mon Sep 17 00:00:00 2001 From: xixixao Date: Wed, 27 Nov 2013 03:41:52 +0000 Subject: [PATCH 1/2] Implemented method call chaining --- lib/coffee-script/rewriter.js | 6 ++--- src/rewriter.coffee | 11 +++++++-- test/formatting.coffee | 42 ++++++++++++++++++++++++++++++----- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index 59e3eb93e5..af14dbc4dc 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -149,9 +149,9 @@ var stack; stack = []; return this.scanTokens(function(token, i, tokens) { - var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, nextTag, offset, prevTag, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, nextTag, offset, prevTag, prevToken, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; tag = token[0]; - prevTag = (i > 0 ? tokens[i - 1] : [])[0]; + prevTag = (prevToken = i > 0 ? tokens[i - 1] : [])[0]; nextTag = (i < tokens.length - 1 ? tokens[i + 1] : [])[0]; stackTop = function() { return stack[stack.length - 1]; @@ -285,7 +285,7 @@ startImplicitObject(s, !!startsLine); return forward(2); } - if (prevTag === 'OUTDENT' && inImplicitCall() && (tag === '.' || tag === '?.' || tag === '::' || tag === '?::')) { + if ((prevTag === 'OUTDENT' || prevToken.newLine) && inImplicitCall() && (tag === '.' || tag === '?.' || tag === '::' || tag === '?::')) { endImplicitCall(); return forward(1); } diff --git a/src/rewriter.coffee b/src/rewriter.coffee index a1f9f2f3d7..b66bbb9ac1 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -131,7 +131,7 @@ class exports.Rewriter @scanTokens (token, i, tokens) -> [tag] = token - [prevTag] = if i > 0 then tokens[i - 1] else [] + [prevTag] = prevToken = if i > 0 then tokens[i - 1] else [] [nextTag] = if i < tokens.length - 1 then tokens[i + 1] else [] stackTop = -> stack[stack.length - 1] startIdx = i @@ -275,7 +275,14 @@ class exports.Rewriter # c # .h a # - if prevTag is 'OUTDENT' and inImplicitCall() and tag in ['.', '?.', '::', '?::'] + # and also + # + # f a + # .g b + # .h a + # + if (prevTag is 'OUTDENT' or prevToken.newLine) and inImplicitCall() and + tag in ['.', '?.', '::', '?::'] endImplicitCall() return forward(1) diff --git a/test/formatting.coffee b/test/formatting.coffee index 7823de798d..dfad59559e 100644 --- a/test/formatting.coffee +++ b/test/formatting.coffee @@ -44,17 +44,47 @@ test "chained accesses split on period/newline, backwards and forwards", -> .reverse() .reverse() arrayEq ['c','b','a'], result - arrayEq ['c','b','a'], str + arrayEq ['c','b','a'], + str .split('') .reverse() .reverse() .reverse() - arrayEq ['c','b','a'], str. + arrayEq ['c','b','a'], + str. split('') .reverse(). reverse() .reverse() +test "#1495, method call chaining", -> + str = 'abc' + + result = str.split '' + .join ', ' + eq 'a, b, c', result + + result = str + .split '' + .join ', ' + eq 'a, b, c', result + + eq 'a, b, c', (str + .split '' + .join ', ' + ) + + eq 'abc', + 'aaabbbccc'.replace /(\w)\1\1/g, '$1$1' + .replace /([abc])\1/g, '$1' + + # Unreadable code, not a real-life use case + result = str.split ''. + split '' + .join('') + .join ', ' + eq 'a, b, c', result + # Operators test "newline suppression for operators", -> @@ -65,9 +95,11 @@ test "newline suppression for operators", -> eq 6, six test "`?.` and `::` should continue lines", -> - ok not Date - :: - ?.foo + ok not ( + Date + :: + ?.foo + ) #eq Object::toString, Date?. #prototype #:: From ee9febe399d87e9709d8a84ae1d7680c199d746f Mon Sep 17 00:00:00 2001 From: xixixao Date: Wed, 27 Nov 2013 04:52:52 +0000 Subject: [PATCH 2/2] Handle nested calls and function oneliners when chaining --- lib/coffee-script/rewriter.js | 30 +++++++++++++++++++++++------- src/rewriter.coffee | 21 ++++++++++++++++----- test/formatting.coffee | 21 +++++++++++++++++---- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index af14dbc4dc..cf6bb869b1 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, generate, left, rite, _i, _len, _ref, + 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, left, rite, _i, _len, _ref, __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; @@ -149,7 +149,7 @@ var stack; stack = []; return this.scanTokens(function(token, i, tokens) { - var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, nextTag, offset, prevTag, prevToken, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + var endAllImplicitCalls, endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, nextTag, offset, prevTag, prevToken, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; tag = token[0]; prevTag = (prevToken = i > 0 ? tokens[i - 1] : [])[0]; nextTag = (i < tokens.length - 1 ? tokens[i + 1] : [])[0]; @@ -194,6 +194,14 @@ tokens.splice(i, 0, generate('CALL_END', ')')); return i += 1; }; + endAllImplicitCalls = function() { + var _results; + _results = []; + while (inImplicitCall()) { + _results.push(endImplicitCall()); + } + return _results; + }; startImplicitObject = function(j, startsLine) { var idx; if (startsLine == null) { @@ -285,9 +293,15 @@ startImplicitObject(s, !!startsLine); return forward(2); } - if ((prevTag === 'OUTDENT' || prevToken.newLine) && inImplicitCall() && (tag === '.' || tag === '?.' || tag === '::' || tag === '?::')) { - endImplicitCall(); - return forward(1); + if (inImplicitCall() && __indexOf.call(CALL_CLOSERS, tag) >= 0) { + if (prevTag === 'OUTDENT') { + endImplicitCall(); + return forward(1); + } + if (prevToken.newLine) { + endAllImplicitCalls(); + return forward(1); + } } if (inImplicitObject() && __indexOf.call(LINEBREAKS, tag) >= 0) { stackTop()[2].sameLine = false; @@ -346,8 +360,8 @@ var action, condition, indent, outdent, starter; starter = indent = outdent = null; condition = function(token, i) { - var _ref, _ref1, _ref2; - 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 === '=>')); + 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; }; action = function(token, i) { return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent); @@ -472,4 +486,6 @@ LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT']; + CALL_CLOSERS = ['.', '?.', '::', '?::']; + }).call(this); diff --git a/src/rewriter.coffee b/src/rewriter.coffee index b66bbb9ac1..b96004fe2b 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -159,6 +159,10 @@ class exports.Rewriter tokens.splice i, 0, generate 'CALL_END', ')' i += 1 + endAllImplicitCalls = -> + while inImplicitCall() + endImplicitCall() + startImplicitObject = (j, startsLine = yes) -> idx = j ? i stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes] @@ -281,10 +285,13 @@ class exports.Rewriter # .g b # .h a # - if (prevTag is 'OUTDENT' or prevToken.newLine) and inImplicitCall() and - tag in ['.', '?.', '::', '?::'] - endImplicitCall() - return forward(1) + if inImplicitCall() and tag in CALL_CLOSERS + if prevTag is 'OUTDENT' + endImplicitCall() + return forward(1) + if prevToken.newLine + endAllImplicitCalls() + return forward(1) stackTop()[2].sameLine = no if inImplicitObject() and tag in LINEBREAKS @@ -363,7 +370,8 @@ class exports.Rewriter token[1] isnt ';' and token[0] in SINGLE_CLOSERS and 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 ['->', '=>']) + not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>']) or + token[0] in CALL_CLOSERS and @tokens[i - 1].newLine action = (token, i) -> @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent @@ -478,3 +486,6 @@ SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADIN # Tokens that end a line. LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT'] + +# Tokens that close open calls when they follow a newline. +CALL_CLOSERS = ['.', '?.', '::', '?::'] diff --git a/test/formatting.coffee b/test/formatting.coffee index dfad59559e..1a35bc1db2 100644 --- a/test/formatting.coffee +++ b/test/formatting.coffee @@ -78,11 +78,24 @@ test "#1495, method call chaining", -> 'aaabbbccc'.replace /(\w)\1\1/g, '$1$1' .replace /([abc])\1/g, '$1' - # Unreadable code, not a real-life use case - result = str.split ''. + # Nested calls + result = [1..3] + .slice Math.max 0, 1 + .concat [3] + arrayEq result, [2, 3, 3] + + # Single line function arguments. + result = [1..6] + .map (x) -> x * x + .filter (x) -> x % 2 is 0 + .reverse() + arrayEq result, [36, 16, 4] + + # The parens are forced + result = str.split(''. split '' - .join('') - .join ', ' + .join '' + ).join ', ' eq 'a, b, c', result # Operators