diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index 6e93267007..bedb0cb662 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -280,7 +280,7 @@ stack = []; start = null; return this.scanTokens(function(token, i, tokens) { - var endImplicitCall, endImplicitObject, forward, implicitObjectContinues, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, isImplicit, isImplicitCall, isImplicitObject, k, newLine, nextTag, nextToken, offset, prevTag, prevToken, ref, s, sameLine, stackIdx, stackItem, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag; + var endImplicitCall, endImplicitObject, forward, implicitObjectContinues, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, isImplicit, isImplicitCall, isImplicitObject, k, newLine, nextTag, nextToken, offset, prevTag, prevToken, ref, ref1, s, sameLine, stackIdx, stackItem, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag; [tag] = token; [prevTag] = prevToken = i > 0 ? tokens[i - 1] : []; [nextTag] = nextToken = i < tokens.length - 1 ? tokens[i + 1] : []; @@ -422,7 +422,7 @@ // Recognize standard implicit calls like // f a, f() b, f? c, h[0] d etc. // Added support for spread dots on the left side: f ...a - if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || nextTag === '...' || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine)) { + if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || (nextTag === '...' && (ref = this.tag(i + 2), indexOf.call(IMPLICIT_CALL, ref) >= 0) && !this.findTagsBackwards(i, ['INDEX_START', '['])) || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine)) { if (tag === '?') { tag = token[0] = 'FUNC_EXIST'; } @@ -456,9 +456,9 @@ if (tag === ':') { // Go back to the (implicit) start of the object. s = (function() { - var ref; + var ref1; switch (false) { - case ref = this.tag(i - 1), indexOf.call(EXPRESSION_END, ref) < 0: + case ref1 = this.tag(i - 1), indexOf.call(EXPRESSION_END, ref1) < 0: return start[1]; case this.tag(i - 2) !== '@': return i - 2; @@ -466,7 +466,7 @@ return i - 1; } }).call(this); - startsLine = s === 0 || (ref = this.tag(s - 1), indexOf.call(LINEBREAKS, ref) >= 0) || tokens[s - 1].newLine; + startsLine = s === 0 || (ref1 = this.tag(s - 1), indexOf.call(LINEBREAKS, ref1) >= 0) || tokens[s - 1].newLine; // Are we just continuing an already declared object? if (stackTop()) { [stackTag, stackIdx] = stackTop(); diff --git a/src/rewriter.coffee b/src/rewriter.coffee index b0c4fd55d9..6fd9e37acd 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -267,7 +267,8 @@ exports.Rewriter = class Rewriter # Added support for spread dots on the left side: f ...a if (tag in IMPLICIT_FUNC and token.spaced or tag is '?' and i > 0 and not tokens[i - 1].spaced) and - (nextTag in IMPLICIT_CALL or nextTag is '...' or + (nextTag in IMPLICIT_CALL or + (nextTag is '...' and @tag(i + 2) in IMPLICIT_CALL and not @findTagsBackwards(i, ['INDEX_START', '['])) or nextTag in IMPLICIT_UNSPACED_CALL and not nextToken.spaced and not nextToken.newLine) tag = token[0] = 'FUNC_EXIST' if tag is '?' diff --git a/test/assignment.coffee b/test/assignment.coffee index 84a728abca..7b5ea8409f 100644 --- a/test/assignment.coffee +++ b/test/assignment.coffee @@ -185,6 +185,12 @@ test "destructuring assignment with splats", -> arrayEq [b,c,d], y eq e, z + # Should not trigger implicit call, e.g. rest ... => rest(...) + [x,y ...,z] = [a,b,c,d,e] + eq a, x + arrayEq [b,c,d], y + eq e, z + test "deep destructuring assignment with splats", -> a={}; b={}; c={}; d={}; e={}; f={}; g={}; h={}; i={} [u, [v, w..., x], y..., z] = [a, [b, c, d, e], f, g, h, i] @@ -229,6 +235,11 @@ test "destructuring assignment with objects and splats", -> eq a, y arrayEq [b,c,d], z + # Should not trigger implicit call, e.g. rest ... => rest(...) + {a: b: [y, z ...]} = obj + eq a, y + arrayEq [b,c,d], z + test "destructuring assignment against an expression", -> a={}; b={} [y, z] = if true then [a, b] else [b, a] @@ -263,6 +274,15 @@ test "destructuring assignment with splats and default values", -> eq b, 1 deepEqual d, {} + # Should not trigger implicit call, e.g. rest ... => rest(...) + { + a: {b} = c + d ... + } = obj + + eq b, 1 + deepEqual d, {} + test "destructuring assignment with splat with default value", -> obj = {} c = {val: 1} @@ -276,6 +296,18 @@ test "destructuring assignment with multiple splats in different objects", -> deepEqual a, val: 1 deepEqual b, val: 2 + # Should not trigger implicit call, e.g. rest ... => rest(...) + { + a: { + a ... + } + b: { + b ... + } + } = obj + deepEqual a, val: 1 + deepEqual b, val: 2 + test "destructuring assignment with dynamic keys and splats", -> i = 0 foo = -> ++i @@ -299,6 +331,15 @@ test "destructuring assignment with objects and splats: Babel tests", -> n = { x, y, z... } deepEqual n, { x: 1, y: 2, a: 3, b: 4 } + # Should not trigger implicit call, e.g. rest ... => rest(...) + { x, y, z ... } = { x: 1, y: 2, a: 3, b: 4 } + eq x, 1 + eq y, 2 + deepEqual z, { a: 3, b: 4 } + + n = { x, y, z ... } + deepEqual n, { x: 1, y: 2, a: 3, b: 4 } + test "deep destructuring assignment with objects: ES2015", -> a1={}; b1={}; c1={}; d1={} obj = { @@ -320,6 +361,13 @@ test "deep destructuring assignment with objects: ES2015", -> eq bb, b1 eq r2.b2, obj.b2 + # Should not trigger implicit call, e.g. rest ... => rest(...) + {a: w, b: {c: {d: {b1: bb, r1 ...}}}, r2 ...} = obj + eq r1.e, c1 + eq r2.b, undefined + eq bb, b1 + eq r2.b2, obj.b2 + test "deep destructuring assignment with defaults: ES2015", -> obj = b: { c: 1, baz: 'qux' } @@ -343,16 +391,55 @@ test "deep destructuring assignment with defaults: ES2015", -> eq hello, 'world' deepEqual h, some: 'prop' + # Should not trigger implicit call, e.g. rest ... => rest(...) + { + a ... + b: { + c, + d ... + } + e: { + f: hello + g: { + h ... + } = i + } = j + } = obj + + deepEqual a, foo: 'bar' + eq c, 1 + deepEqual d, baz: 'qux' + eq hello, 'world' + deepEqual h, some: 'prop' + test "object spread properties: ES2015", -> obj = {a: 1, b: 2, c: 3, d: 4, e: 5} obj2 = {obj..., c:9} eq obj2.c, 9 eq obj.a, obj2.a + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj2 = { + obj ... + c:9 + } + eq obj2.c, 9 + eq obj.a, obj2.a + obj2 = {obj..., a: 8, c: 9, obj...} eq obj2.c, 3 eq obj.a, obj2.a + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj2 = { + obj ... + a: 8 + c: 9 + obj ... + } + eq obj2.c, 3 + eq obj.a, obj2.a + obj3 = {obj..., b: 7, g: {obj2..., c: 1}} eq obj3.g.c, 1 eq obj3.b, 7 @@ -370,10 +457,42 @@ test "object spread properties: ES2015", -> eq obj4.f.g, 5 deepEqual obj4.f, obj.c.f + # Should not trigger implicit call, e.g. rest ... => rest(...) + (({ + a + b + r ... + }) -> + eq 1, a + deepEqual r, {c: 3, d: 44, e: 55} + ) { + obj2 ... + d: 44 + e: 55 + } + + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj4 = { + a: 10 + obj.c ... + } + eq obj4.a, 10 + eq obj4.d, 3 + eq obj4.f.g, 5 + deepEqual obj4.f, obj.c.f + obj5 = {obj..., ((k) -> {b: k})(99)...} eq obj5.b, 99 deepEqual obj5.c, obj.c + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj5 = { + obj ... + ((k) -> {b: k})(99) ... + } + eq obj5.b, 99 + deepEqual obj5.c, obj.c + fn = -> {c: {d: 33, e: 44, f: {g: 55}}} obj6 = {obj..., fn()...} eq obj6.c.d, 33 @@ -382,7 +501,16 @@ test "object spread properties: ES2015", -> obj7 = {obj..., fn()..., {c: {d: 55, e: 66, f: {77}}}...} eq obj7.c.d, 55 deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}} - + + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj7 = { + obj ... + fn() ... + {c: {d: 55, e: 66, f: {77}}} ... + } + eq obj7.c.d, 55 + deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}} + obj = a: b: diff --git a/test/function_invocation.coffee b/test/function_invocation.coffee index 5c8779df94..237633c22c 100644 --- a/test/function_invocation.coffee +++ b/test/function_invocation.coffee @@ -347,12 +347,26 @@ test "passing splats to functions", -> arrayEq [2..6], others eq 7, last + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [0..4], id id [0..4] ... + fn = (a, b, c ..., d) -> [a, b, c, d] + range = [0..3] + [first, second, others, last] = fn range ..., 4, [5 ... 8] ... + eq 0, first + eq 1, second + arrayEq [2..6], others + eq 7, last + test "splat variables are local to the function", -> outer = "x" clobber = (avar, outer...) -> outer clobber "foo", "bar" eq "x", outer +test "Issue 4631: left and right spread dots with preceding space", -> + a = [] + f = (a) -> a + eq yes, (f ...a) is (f ... a) is (f a...) is (f a ...) is f(a...) is f(...a) is f(a ...) is f(... a) test "Issue 894: Splatting against constructor-chained functions.", -> @@ -387,6 +401,16 @@ test "splats with super() within classes.", -> super nums... ok (new Child).meth().join(' ') is '3 2 1' + # Should not trigger implicit call, e.g. rest ... => rest(...) + class Parent + meth: (args ...) -> + args + class Child extends Parent + meth: -> + nums = [3, 2, 1] + super nums ... + ok (new Child).meth().join(' ') is '3 2 1' + test "#1011: passing a splat to a method of a number", -> eq '1011', 11.toString [2]... @@ -394,12 +418,21 @@ test "#1011: passing a splat to a method of a number", -> eq '1011', 69.0.toString [4]... eq '1011', (131.0).toString [5]... + # Should not trigger implicit call, e.g. rest ... => rest(...) + eq '1011', 11.toString [2] ... + eq '1011', (31).toString [3] ... + eq '1011', 69.0.toString [4] ... + eq '1011', (131.0).toString [5] ... test "splats and the `new` operator: functions that return `null` should construct their instance", -> args = [] child = new (constructor = -> null) args... ok child instanceof constructor + # Should not trigger implicit call, e.g. rest ... => rest(...) + child = new (constructor = -> null) args ... + ok child instanceof constructor + test "splats and the `new` operator: functions that return functions should construct their return value", -> args = [] fn = -> diff --git a/test/functions.coffee b/test/functions.coffee index 47fa9ae305..34123af5c1 100644 --- a/test/functions.coffee +++ b/test/functions.coffee @@ -110,6 +110,12 @@ test "splats", -> arrayEq [0, 1], (((splat..., _, _1) -> splat) 0, 1, 2, 3) arrayEq [2], (((_, _1, splat..., _2) -> splat) 0, 1, 2, 3) + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [0, 1, 2], (((splat ...) -> splat) 0, 1, 2) + arrayEq [2, 3], (((_, _1, splat ...) -> splat) 0, 1, 2, 3) + arrayEq [0, 1], (((splat ..., _, _1) -> splat) 0, 1, 2, 3) + arrayEq [2], (((_, _1, splat ..., _2) -> splat) 0, 1, 2, 3) + test "destructured splatted parameters", -> arr = [0,1,2] splatArray = ([a...]) -> a @@ -117,6 +123,10 @@ test "destructured splatted parameters", -> arrayEq splatArray(arr), arr arrayEq splatArrayRest(arr,0,1,2), arr + # Should not trigger implicit call, e.g. rest ... => rest(...) + splatArray = ([a ...]) -> a + splatArrayRest = ([a ...],b ...) -> arrayEq(a,b); b + test "@-parameters: automatically assign an argument's value to a property of the context", -> nonce = {} @@ -127,10 +137,18 @@ test "@-parameters: automatically assign an argument's value to a property of th ((splat..., @prop) ->).apply context = {}, [0, 0, nonce] eq nonce, context.prop + # Should not trigger implicit call, e.g. rest ... => rest(...) + ((splat ..., @prop) ->).apply context = {}, [0, 0, nonce] + eq nonce, context.prop + # Allow the argument itself to be a splat ((@prop...) ->).call context = {}, 0, nonce, 0 eq nonce, context.prop[1] + # Should not trigger implicit call, e.g. rest ... => rest(...) + ((@prop ...) ->).call context = {}, 0, nonce, 0 + eq nonce, context.prop[1] + # The argument should not be able to be referenced normally code = '((@prop) -> prop).call {}' doesNotThrow -> CoffeeScript.compile code @@ -149,12 +167,26 @@ test "@-parameters and splats with constructors", -> eq a, obj.first eq b, obj.last + # Should not trigger implicit call, e.g. rest ... => rest(...) + class Klass + constructor: (@first, splat ..., @last) -> + + obj = new Klass a, 0, 0, b + eq a, obj.first + eq b, obj.last + test "destructuring in function definition", -> (([{a: [b], c}]...) -> eq 1, b eq 2, c ) {a: [1], c: 2} + # Should not trigger implicit call, e.g. rest ... => rest(...) + (([{a: [b], c}] ...) -> + eq 1, b + eq 2, c + ) {a: [1], c: 2} + context = {} (([{a: [b, c = 2], @d, e = 4}]...) -> eq 1, b @@ -198,6 +230,17 @@ test "rest element destructuring in function definition", -> deepEqual r, {c: 3, d: 4, e: 5} ) {a:1, b:2, c:3, d:4, e:5}, 9 + # Should not trigger implicit call, e.g. rest ... => rest(...) + (({ + a: p + b + r ... + }, q) -> + eq p, 1 + eq q, 9 + deepEqual r, {c: 3, d: 4, e: 5} + ) {a:1, b:2, c:3, d:4, e:5}, 9 + a1={}; b1={}; c1={}; d1={} obj1 = { a: a1 @@ -256,6 +299,10 @@ test "#4005: `([a = {}]..., b) ->` weirdness", -> fn = ([a = {}]..., b) -> [a, b] deepEqual fn(5), [{}, 5] + # Should not trigger implicit call, e.g. rest ... => rest(...) + fn = ([a = {}] ..., b) -> [a, b] + deepEqual fn(5), [{}, 5] + test "default values", -> nonceA = {} nonceB = {} @@ -299,6 +346,14 @@ test "default values with splatted arguments", -> eq 1, withSplats(1,1,1) eq 2, withSplats(1,1,1,1) + # Should not trigger implicit call, e.g. rest ... => rest(...) + withSplats = (a = 2, b ..., c = 3, d = 5) -> a * (b.length + 1) * c * d + eq 30, withSplats() + eq 15, withSplats(1) + eq 5, withSplats(1,1) + eq 1, withSplats(1,1,1) + eq 2, withSplats(1,1,1,1) + test "#156: parameter lists with expansion", -> expandArguments = (first, ..., lastButOne, last) -> eq 1, first @@ -328,6 +383,12 @@ test "variable definitions and splat", -> eq 0, a eq 0, b + # Should not trigger implicit call, e.g. rest ... => rest(...) + f = (a, middle ..., b) -> [a, middle, b] + arrayEq [1, [2, 3, 4], 5], f 1, 2, 3, 4, 5 + eq 0, a + eq 0, b + test "default values with function calls", -> doesNotThrow -> CoffeeScript.compile "(x = f()) ->" @@ -354,6 +415,12 @@ test "reserved keyword at-splat", -> eq 1, a eq 2, b + # Should not trigger implicit call, e.g. rest ... => rest(...) + f = (@case ...) -> @case + [a, b] = f(1, 2) + eq 1, a + eq 2, b + test "#1574: Destructuring and a parameter named _arg", -> f = ({a, b}, _arg, _arg1) -> [a, b, _arg, _arg1] arrayEq [1, 2, 3, 4], f a: 1, b: 2, 3, 4 diff --git a/test/ranges.coffee b/test/ranges.coffee index 8de444a83d..a6037c5dd4 100644 --- a/test/ranges.coffee +++ b/test/ranges.coffee @@ -28,6 +28,19 @@ test "basic exclusive ranges", -> arrayEq [], [0...0] arrayEq [], [-1...-1] + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [1, 2, 3] , [1 ... 4] + arrayEq [0, 1, 2] , [0 ... 3] + arrayEq [0, 1] , [0 ... 2] + arrayEq [0] , [0 ... 1] + arrayEq [-1] , [-1 ... 0] + arrayEq [-1, 0] , [-1 ... 1] + arrayEq [-1, 0, 1], [-1 ... 2] + + arrayEq [], [1 ... 1] + arrayEq [], [0 ... 0] + arrayEq [], [-1 ... -1] + test "downward ranges", -> arrayEq shared, [9..0].reverse() arrayEq [5, 4, 3, 2] , [5..2] @@ -66,6 +79,9 @@ test "ranges with expressions as endpoints", -> arrayEq [2, 3, 4, 5, 6], [(a+1)..2*b] arrayEq [2, 3, 4, 5] , [(a+1)...2*b] + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [2, 3, 4, 5] , [(a+1) ... 2*b] + test "large ranges are generated with looping constructs", -> down = [99..0] eq 100, (len = down.length) diff --git a/test/slicing_and_splicing.coffee b/test/slicing_and_splicing.coffee index a126ec00ed..65bd348540 100644 --- a/test/slicing_and_splicing.coffee +++ b/test/slicing_and_splicing.coffee @@ -64,6 +64,18 @@ test "#1722: operator precedence in unbounded slice compilation", -> test "#2349: inclusive slicing to numeric strings", -> arrayEq [0, 1], [0..10][.."1"] +test "#4631: slicing with space before and/or after the dots", -> + a = (s) -> s + b = [4, 5, 6] + c = [7, 8, 9] + arrayEq [2, 3, 4], shared[2 ... 5] + arrayEq [3, 4, 5], shared[3... 6] + arrayEq [4, 5, 6], shared[4 ...7] + arrayEq shared[(a b...)...(a c...)] , shared[(a ...b)...(a ...c)] + arrayEq shared[(a b...) ... (a c...)], shared[(a ...b) ... (a ...c)] + arrayEq shared[(a b...)... (a c...)] , shared[(a ...b)... (a ...c)] + arrayEq shared[(a b...) ...(a c...)] , shared[(a ...b) ...(a ...c)] + # Splicing