[CS2] Destructuring object spreads (#4493)

* Don’t confuse the syntax highlighter

* Comment Assign::compilePatternMatch a bit

* Assignment expressions in conditionals are a bad practice

* Rename `wrapInBraces` to `wrapInParentheses`, to set the stage for future `wrapInBraces` that uses `{` and `wrapInBrackets` that uses `[`

* Correct comment

* object destructuring

* Allow custom position of the rest element.

* Output simple array destructuring assignments to ES2015

* Output simple object destructured assignments to ES2015

* Compile shorthand object properties to ES2015 shorthand properties

This dramatically improves the appearance of destructured imports.

* Don’t confuse the syntax highlighter

* Comment Assign::compilePatternMatch a bit

* Assignment expressions in conditionals are a bad practice

* Rename `wrapInBraces` to `wrapInParentheses`, to set the stage for future `wrapInBraces` that uses `{` and `wrapInBrackets` that uses `[`

* object destructuring

* Allow custom position of the rest element.

* rest element in object destructuring

* rest element in object destructuring

* fix string interpolation

* merging

* fixing splats in object literal

* Rest element in parameter destructuring

* merging with CS2

* merged with CS2

* Add support for the object spread initializer. https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md

* Fix misspellings, trailing whitespace, other minor details

* merging with beta2

* refactor object spread properties

* small fix

* - Fixed object spread function parameters.
- Clean up "Assign" and moved all logic for object rest properties in single method (compileObjectDestruct).
- Add helper function "objectWithoutKeys" to the "UTILITIES" for use with object rest properties,
  e.g. {a, b, r...} = obj => {a, b} = obj, r = objectWithoutKeys(...)
- Clean up "Obj" and moved all logic for object spread properties in single method (compileSpread).
- Clean up "Code".
- Add method "hasSplat" to "Obj" and "Value" for checking if Obj contains the splat.
- Enable placing spread syntax triple dots on either right or left, per #85 (https://github.com/coffeescript6/discuss/issues/85)

* Fixed typos

* Remove unused code

* Removed dots (e.g. splat) on the left side from the grammar

* Initial release for deep spread properties, e.g. obj2 = {obj.b..., a: 1} or {obj[b][c]..., d: 7}
Tests need to be prepared!

* 1. Object literal spread properties

Object literals:
- obj = { {b:{c:{d:1}}}..., a:1 }

Parenthetical:
- obj = { ( body ), a:1 }
- obj = { ( body )..., a:1 }

Invocation:
- obj = { ( (args) -> ... )(params), a:1 }
- obj = { ( (args) -> ... )(params)..., a:1 }
- obj = { foo(), a:1 }
- obj = { foo()..., a:1 }

2. Refactor, cleanup & other optimizations.

* Merged with 2.0

* Cleanup

* Some more cleanup.

* Fixed error with freeVariable and object destructuring.

* Fixed errors with object spread properties.

* Improvements, fixed errors.

* Minor improvement.

* Minor improvements.

* Typo.

* Remove unnecessary whitespace.

* Remove unnecessary whitespace.

* Changed few "assertErrorFormat" tests since parentheses are now allowed in the Obj.

* Whitespace cleanup

* Comments cleanup

* fix destructured obj param declarations

* refine fix; add test

* Refactor function args ({a, b...})

* Additional tests for object destructuring in function argument.

* Minor improvement for object destructuring variable declaration.

* refactor function args ({a, b...}) and ({a, b...} = {}); Obj And Param cleanup

* fix comment

* Fix object destructuring variable declaration.

* more tests with default values

* fix typo

* Fixed default values in object destructuring.

* small fix

* Babel’s tests for object rest spread

* Style: spaces after colons in object declarations

* Cleanup comments

* Simplify Babel tests

* Fix comments

* Fix destructuring with splats in multiple objects

* Add test for default values in detsructuring assignment with splats

* Handle default values when assigning to object splats

* Rewrite traverseRest to fix handling of dynamic keys

* Fix double parens around destructuring with splats

* Update compileObjectDestruct comments

* Improve formatting of top-level destructures with splats and tidy parens

* Added a bigger destructuring-with-defaults test and fixed a bug

* Refactor destructuring grammar to allow additional forms

* Add a missing case to ObjSpreadExpr

* These tests shouldn’t run in the browser

* Fix test.html
This commit is contained in:
Geoffrey Booth 2017-06-29 22:57:42 -07:00 committed by GitHub
parent 58c608620e
commit a7a6006533
11 changed files with 822 additions and 240 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ test/*.js
parser.output
/node_modules
npm-debug.log*
yarn.lock

View File

@ -39,6 +39,7 @@
<script type="text/coffeescript">
@testingBrowser = yes
@global = window
bold = red = green = reset = ''
stdout = document.getElementById 'stdout'
start = new Date
@currentFile = ''

View File

@ -123,7 +123,7 @@
AssignObj: [
o('ObjAssignable', function() {
return new Value($1);
}), o('ObjAssignable : Expression', function() {
}), o('ObjRestValue'), o('ObjAssignable : Expression', function() {
return new Assign(LOC(1)(new Value($1)), $3, 'object', {
operatorToken: LOC(2)(new Literal($2))
});
@ -143,6 +143,29 @@
],
SimpleObjAssignable: [o('Identifier'), o('Property'), o('ThisProperty')],
ObjAssignable: [o('SimpleObjAssignable'), o('AlphaNumeric')],
ObjRestValue: [
o('SimpleObjAssignable ...', function() {
return new Splat(new Value($1));
}), o('ObjSpreadExpr ...', function() {
return new Splat($1);
})
],
ObjSpreadExpr: [
o('ObjSpreadIdentifier'), o('Object'), o('Parenthetical'), o('Super'), o('This'), o('SUPER Arguments', function() {
return new SuperCall(LOC(1)(new Super), $2);
}), o('SimpleObjAssignable Arguments', function() {
return new Call(new Value($1), $2);
}), o('ObjSpreadExpr Arguments', function() {
return new Call($1, $2);
})
],
ObjSpreadIdentifier: [
o('SimpleObjAssignable . Property', function() {
return (new Value($1)).add(new Access($3));
}), o('SimpleObjAssignable INDEX_START IndexValue INDEX_END', function() {
return (new Value($1)).add($3);
})
],
Return: [
o('RETURN Expression', function() {
return new Return($2);

View File

@ -1543,8 +1543,20 @@
return !this.isAssignable();
}
hasSplat() {
var j, len1, prop, ref1, splat;
ref1 = this.properties;
for (j = 0, len1 = ref1.length; j < len1; j++) {
prop = ref1[j];
if (prop instanceof Splat) {
splat = true;
}
}
return splat != null ? splat : false;
}
compileNode(o) {
var answer, i, idt, indent, isCompact, j, join, k, key, l, lastNoncom, len1, len2, len3, len4, node, p, prop, props, ref1, unwrappedVal, value;
var answer, i, idt, indent, isCompact, j, join, k, key, l, lastNoncom, len1, len2, len3, len4, node, prop, props, q, ref1, unwrappedVal, value;
props = this.properties;
if (this.generated) {
for (j = 0, len1 = props.length; j < len1; j++) {
@ -1554,6 +1566,9 @@
}
}
}
if (this.hasSplat()) {
return this.compileSpread(o);
}
idt = o.indent += TAB;
lastNoncom = this.lastNonComment(this.properties);
if (this.lhs) {
@ -1581,7 +1596,7 @@
}
answer = [];
answer.push(this.makeCode(isCompact ? '' : '\n'));
for (i = p = 0, len4 = props.length; p < len4; i = ++p) {
for (i = q = 0, len4 = props.length; q < len4; i = ++q) {
prop = props[i];
join = i === props.length - 1 ? '' : isCompact && this.csx ? ' ' : isCompact ? ', ' : prop === lastNoncom || prop instanceof Comment || this.csx ? '\n' : ',\n';
indent = isCompact || prop instanceof Comment ? '' : idt;
@ -1660,6 +1675,38 @@
return results;
}
compileSpread(o) {
var addSlice, j, len1, prop, propSlices, props, slices, splatSlice;
props = this.properties;
splatSlice = [];
propSlices = [];
slices = [];
addSlice = function() {
if (propSlices.length) {
slices.push(new Obj(propSlices));
}
if (splatSlice.length) {
slices.push(...splatSlice);
}
splatSlice = [];
return propSlices = [];
};
for (j = 0, len1 = props.length; j < len1; j++) {
prop = props[j];
if (prop instanceof Splat) {
splatSlice.push(new Value(prop.name));
addSlice();
} else {
propSlices.push(prop);
}
}
addSlice();
if (!(slices[0] instanceof Obj)) {
slices.unshift(new Obj);
}
return (new Call(new Literal('Object.assign'), slices)).compileToFragments(o);
}
};
Obj.prototype.children = ['properties'];
@ -2445,6 +2492,11 @@
if (!this.variable.isAssignable()) {
return this.compileDestructuring(o);
}
if (this.variable.isObject() && this.variable.contains(function(node) {
return node instanceof Obj && node.hasSplat();
})) {
return this.compileObjectDestruct(o);
}
}
if (this.variable.isSplice()) {
return this.compileSplice(o);
@ -2501,13 +2553,117 @@
return compiledName.concat(this.makeCode(this.csx ? '=' : ': '), val);
}
answer = compiledName.concat(this.makeCode(` ${this.context || '='} `), val);
if (o.level > LEVEL_LIST || (isValue && this.variable.base instanceof Obj && !this.nestedLhs && !this.param)) {
if (o.level > LEVEL_LIST || (o.level === LEVEL_TOP && isValue && this.variable.base instanceof Obj && !this.nestedLhs && !this.param)) {
return this.wrapInParentheses(answer);
} else {
return answer;
}
}
compileObjectDestruct(o) {
var fragments, getPropKey, getPropName, j, len1, restElement, restElements, result, setScopeVar, traverseRest, value, valueRef;
setScopeVar = function(prop) {
var newVar;
newVar = false;
if (prop instanceof Assign && prop.value.base instanceof Obj) {
return;
}
if (prop instanceof Assign) {
if (prop.value.base instanceof IdentifierLiteral) {
newVar = prop.value.base.compile(o);
} else {
newVar = prop.variable.base.compile(o);
}
} else {
newVar = prop.compile(o);
}
if (newVar) {
return o.scope.add(newVar, 'var', true);
}
};
getPropKey = function(prop) {
var key;
if (prop instanceof Assign) {
[prop.variable, key] = prop.variable.cache(o);
return key;
} else {
return prop;
}
};
getPropName = function(prop) {
var cached, key;
key = getPropKey(prop);
cached = prop instanceof Assign && prop.variable !== key;
if (cached || !key.isAssignable()) {
return key;
} else {
return new Literal(`'${key.compile(o)}'`);
}
};
traverseRest = (properties, source) => {
var base1, index, j, len1, nestedProperties, nestedSource, nestedSourceDefault, p, prop, restElements, restIndex;
restElements = [];
restIndex = void 0;
for (index = j = 0, len1 = properties.length; j < len1; index = ++j) {
prop = properties[index];
setScopeVar(prop.unwrap());
if (prop instanceof Assign) {
if (typeof (base1 = prop.value).isObject === "function" ? base1.isObject() : void 0) {
nestedProperties = prop.value.base.properties;
} else if (prop.value instanceof Assign && prop.value.variable.isObject()) {
nestedProperties = prop.value.variable.base.properties;
[prop.value.value, nestedSourceDefault] = prop.value.value.cache(o);
}
if (nestedProperties) {
nestedSource = new Value(source.base, source.properties.concat([new Access(getPropKey(prop))]));
if (nestedSourceDefault) {
nestedSource = new Value(new Op('?', nestedSource, nestedSourceDefault));
}
restElements = restElements.concat(traverseRest(nestedProperties, nestedSource));
}
} else if (prop instanceof Splat) {
if (restIndex != null) {
prop.error("multiple rest elements are disallowed in object destructuring");
}
restIndex = index;
restElements.push({
name: prop.name.unwrapAll(),
source,
excludeProps: new Arr((function() {
var k, len2, results;
results = [];
for (k = 0, len2 = properties.length; k < len2; k++) {
p = properties[k];
if (p !== prop) {
results.push(getPropName(p));
}
}
return results;
})())
});
}
}
if (restIndex != null) {
properties.splice(restIndex, 1);
}
return restElements;
};
[this.value, valueRef] = this.value.cache(o);
restElements = traverseRest(this.variable.base.properties, valueRef);
result = new Block([this]);
for (j = 0, len1 = restElements.length; j < len1; j++) {
restElement = restElements[j];
value = new Call(new Value(new Literal(utility('objectWithoutKeys', o))), [restElement.source, restElement.excludeProps]);
result.push(new Assign(restElement.name, value));
}
fragments = result.compileToFragments(o);
if (o.level === LEVEL_TOP) {
fragments.shift();
fragments.pop();
}
return fragments;
}
compileDestructuring(o) {
var acc, assigns, code, defaultValue, expandedIdx, fragments, i, idx, isObject, ivar, j, len1, message, name, obj, objects, olen, ref, rest, top, val, value, vvar, vvarText;
top = o.level === LEVEL_TOP;
@ -2825,17 +2981,13 @@
if (param.name instanceof Arr) {
splatParamName = o.scope.freeVariable('arg');
params.push(ref = new Value(new IdentifierLiteral(splatParamName)));
exprs.push(new Assign(new Value(param.name), ref, null, {
param: true
}));
exprs.push(new Assign(new Value(param.name), ref));
} else {
params.push(ref = param.asReference(o));
splatParamName = fragmentsToText(ref.compileNode(o));
}
if (param.shouldCache()) {
exprs.push(new Assign(new Value(param.name), ref, null, {
param: true
}));
exprs.push(new Assign(new Value(param.name), ref));
}
} else {
splatParamName = o.scope.freeVariable('args');
@ -2848,14 +3000,10 @@
haveBodyParam = true;
if (param.value != null) {
condition = new Op('===', param, new UndefinedLiteral);
ifTrue = new Assign(new Value(param.name), param.value, null, {
param: true
});
ifTrue = new Assign(new Value(param.name), param.value);
exprs.push(new If(condition, ifTrue));
} else {
exprs.push(new Assign(new Value(param.name), param.asReference(o), null, {
param: true
}));
exprs.push(new Assign(new Value(param.name), param.asReference(o)));
}
}
if (!haveSplatParam) {
@ -2875,6 +3023,17 @@
param.name.eachName(function(prop) {
return o.scope.parameter(prop.value);
});
if (param.name instanceof Obj && param.name.hasSplat()) {
splatParamName = o.scope.freeVariable('arg');
o.scope.parameter(splatParamName);
ref = new Value(new IdentifierLiteral(splatParamName));
exprs.push(new Assign(new Value(param.name), ref));
if ((param.value != null) && !param.assignedInBody) {
ref = new Assign(ref, param.value, null, {
param: true
});
}
}
} else {
o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o)));
}
@ -3060,12 +3219,12 @@
exports.Param = Param = (function() {
class Param extends Base {
constructor(name1, value1, splat) {
constructor(name1, value1, splat1) {
var message, token;
super();
this.name = name1;
this.value = value1;
this.splat = splat;
this.splat = splat1;
message = isUnassignable(this.name.unwrapAll().value);
if (message) {
this.name.error(message);
@ -4296,6 +4455,9 @@
modulo: function() {
return 'function(a, b) { return (+a % (b = +b) + b) % b; }';
},
objectWithoutKeys: function() {
return "function(o, ks) { var res = {}; for (var k in o) ([].indexOf.call(ks, k) < 0 && {}.hasOwnProperty.call(o, k)) && (res[k] = o[k]); return res; }";
},
boundMethodCheck: function() {
return "function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } }";
},

File diff suppressed because one or more lines are too long

View File

@ -190,6 +190,7 @@ grammar =
# the ordinary **Assign** is that these allow numbers and strings as keys.
AssignObj: [
o 'ObjAssignable', -> new Value $1
o 'ObjRestValue'
o 'ObjAssignable : Expression', -> new Assign LOC(1)(new Value $1), $3, 'object',
operatorToken: LOC(2)(new Literal $2)
o 'ObjAssignable :
@ -214,6 +215,28 @@ grammar =
o 'AlphaNumeric'
]
# Object literal spread properties.
ObjRestValue: [
o 'SimpleObjAssignable ...', -> new Splat new Value $1
o 'ObjSpreadExpr ...', -> new Splat $1
]
ObjSpreadExpr: [
o 'ObjSpreadIdentifier'
o 'Object'
o 'Parenthetical'
o 'Super'
o 'This'
o 'SUPER Arguments', -> new SuperCall LOC(1)(new Super), $2
o 'SimpleObjAssignable Arguments', -> new Call (new Value $1), $2
o 'ObjSpreadExpr Arguments', -> new Call $1, $2
]
ObjSpreadIdentifier: [
o 'SimpleObjAssignable . Property', -> (new Value $1).add(new Access $3)
o 'SimpleObjAssignable INDEX_START IndexValue INDEX_END', -> (new Value $1).add($3)
]
# A return statement from a function body.
Return: [
o 'RETURN Expression', -> new Return $2
@ -701,7 +724,6 @@ grammar =
Expression', -> new Assign $1, $4, $2
]
# Precedence
# ----------

View File

@ -1157,11 +1157,20 @@ exports.Obj = class Obj extends Base
shouldCache: ->
not @isAssignable()
# Check if object contains splat.
hasSplat: ->
splat = yes for prop in @properties when prop instanceof Splat
splat ? no
compileNode: (o) ->
props = @properties
if @generated
for node in props when node instanceof Value
node.error 'cannot have an implicit value in an implicit object'
# Object spread properties. https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
return @compileSpread o if @hasSplat()
idt = o.indent += TAB
lastNoncom = @lastNonComment @properties
@ -1203,12 +1212,10 @@ exports.Obj = class Obj extends Base
prop.variable
else if prop not instanceof Comment
prop
if key instanceof Value and key.hasProperties()
key.error 'invalid object key' if prop.context is 'object' or not key.this
key = key.properties[0].name
prop = new Assign key, prop, 'object'
if key is prop
if prop.shouldCache()
[key, value] = prop.base.cache o
@ -1216,7 +1223,6 @@ exports.Obj = class Obj extends Base
prop = new Assign key, value, 'object'
else if not prop.bareLiteral?(IdentifierLiteral)
prop = new Assign prop, prop, 'object'
if indent then answer.push @makeCode indent
prop.csx = yes if @csx
answer.push @makeCode ' ' if @csx and i is 0
@ -1236,6 +1242,29 @@ exports.Obj = class Obj extends Base
prop = prop.unwrapAll()
prop.eachName iterator if prop.eachName?
# Object spread properties. https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
# `obj2 = {a: 1, obj..., c: 3, d: 4}` `obj2 = Object.assign({}, {a: 1}, obj, {c: 3, d: 4})`
compileSpread: (o) ->
props = @properties
# Store object spreads.
splatSlice = []
propSlices = []
slices = []
addSlice = ->
slices.push new Obj propSlices if propSlices.length
slices.push splatSlice... if splatSlice.length
splatSlice = []
propSlices = []
for prop in props
if prop instanceof Splat
splatSlice.push new Value prop.name
addSlice()
else
propSlices.push prop
addSlice()
slices.unshift new Obj unless slices[0] instanceof Obj
(new Call new Literal('Object.assign'), slices).compileToFragments o
#### Arr
# An array literal.
@ -1796,6 +1825,9 @@ exports.Assign = class Assign extends Base
# destructured variables.
@variable.base.lhs = yes
return @compileDestructuring o unless @variable.isAssignable()
# Object destructuring. Can be removed once ES proposal hits Stage 4.
return @compileObjectDestruct(o) if @variable.isObject() and @variable.contains (node) ->
node instanceof Obj and node.hasSplat()
return @compileSplice o if @variable.isSplice()
return @compileConditional o if @context in ['||=', '&&=', '?=']
@ -1839,11 +1871,105 @@ exports.Assign = class Assign extends Base
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
# Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
# if were destructuring without declaring, the destructuring assignment must be wrapped in parentheses.
if o.level > LEVEL_LIST or (isValue and @variable.base instanceof Obj and not @nestedLhs and not @param)
if o.level > LEVEL_LIST or (o.level is LEVEL_TOP and isValue and @variable.base instanceof Obj and not @nestedLhs and not @param)
@wrapInParentheses answer
else
answer
# Check object destructuring variable for rest elements;
# can be removed once ES proposal hits Stage 4.
compileObjectDestruct: (o) ->
# Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
# if were destructuring without declaring, the destructuring assignment
# must be wrapped in parentheses: `({a, b} = obj)`. Helper function
# `setScopeVar()` declares variables `a` and `b` at the top of the
# current scope.
setScopeVar = (prop) ->
newVar = false
return if prop instanceof Assign and prop.value.base instanceof Obj
if prop instanceof Assign
if prop.value.base instanceof IdentifierLiteral
newVar = prop.value.base.compile o
else
newVar = prop.variable.base.compile o
else
newVar = prop.compile o
o.scope.add(newVar, 'var', true) if newVar
# Returns a safe (cached) reference to the key for a given property
getPropKey = (prop) ->
if prop instanceof Assign
[prop.variable, key] = prop.variable.cache o
key
else
prop
# Returns the name of a given property for use with excludeProps
# Property names are quoted (e.g. `a: b` -> 'a'), and everything else uses the key reference
# (e.g. `'a': b -> 'a'`, `"#{a}": b` -> <cached>`)
getPropName = (prop) ->
key = getPropKey prop
cached = prop instanceof Assign and prop.variable != key
if cached or not key.isAssignable()
key
else
new Literal "'#{key.compile o}'"
# Recursive function for searching and storing rest elements in objects.
# e.g. `{[properties...]} = source`.
traverseRest = (properties, source) =>
restElements = []
restIndex = undefined
for prop, index in properties
setScopeVar prop.unwrap()
if prop instanceof Assign
# prop is `k: expr`, we need to check `expr` for nested splats
if prop.value.isObject?()
# prop is `k: {...}`
nestedProperties = prop.value.base.properties
else if prop.value instanceof Assign and prop.value.variable.isObject()
# prop is `k: {...} = default`
nestedProperties = prop.value.variable.base.properties
[prop.value.value, nestedSourceDefault] = prop.value.value.cache o
if nestedProperties
nestedSource = new Value source.base, source.properties.concat [new Access getPropKey prop]
nestedSource = new Value new Op '?', nestedSource, nestedSourceDefault if nestedSourceDefault
restElements = restElements.concat traverseRest nestedProperties, nestedSource
else if prop instanceof Splat
prop.error "multiple rest elements are disallowed in object destructuring" if restIndex?
restIndex = index
restElements.push {
name: prop.name.unwrapAll()
source
excludeProps: new Arr (getPropName p for p in properties when p isnt prop)
}
if restIndex?
# Remove rest element from the properties after iteration
properties.splice restIndex, 1
restElements
# Cache the value for reuse with rest elements
[@value, valueRef] = @value.cache o
# Find all rest elements.
restElements = traverseRest @variable.base.properties, valueRef
result = new Block [@]
for restElement in restElements
value = new Call new Value(new Literal utility 'objectWithoutKeys', o), [restElement.source, restElement.excludeProps]
result.push new Assign restElement.name, value
fragments = result.compileToFragments o
if o.level is LEVEL_TOP
# Remove leading tab and trailing semicolon
fragments.shift()
fragments.pop()
fragments
# Brief implementation of recursive pattern matching, when assigning array or
# object literals to a value. Peeks at their properties to assign inner names.
compileDestructuring: (o) ->
@ -1907,7 +2033,7 @@ exports.Assign = class Assign extends Base
# At this point, there are several things to destructure. So the `fn()` in
# `{a, b} = fn()` must be cached, for example. Make vvar into a simple
# variable if it isn't already.
# variable if it isnt already.
if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
ref = o.scope.freeVariable 'ref'
assigns.push [@makeCode(ref + ' = '), vvar...]
@ -2098,7 +2224,6 @@ exports.Code = class Code extends Base
@eachParamName (name, node, param) ->
node.error "multiple parameters named '#{name}'" if name in paramNames
paramNames.push name
if node.this
name = node.properties[0].name.value
name = "_#{name}" if name in JS_FORBIDDEN
@ -2123,7 +2248,6 @@ exports.Code = class Code extends Base
param.error 'only one splat or expansion parameter is allowed per function definition'
else if param instanceof Expansion and @params.length is 1
param.error 'an expansion parameter cannot be the only parameter in a function definition'
haveSplatParam = yes
if param.splat
if param.name instanceof Arr
@ -2132,12 +2256,12 @@ exports.Code = class Code extends Base
# function parameter list, and if so, how?
splatParamName = o.scope.freeVariable 'arg'
params.push ref = new Value new IdentifierLiteral splatParamName
exprs.push new Assign new Value(param.name), ref, null, param: yes
exprs.push new Assign new Value(param.name), ref
else
params.push ref = param.asReference o
splatParamName = fragmentsToText ref.compileNode o
if param.shouldCache()
exprs.push new Assign new Value(param.name), ref, null, param: yes
exprs.push new Assign new Value(param.name), ref
else # `param` is an Expansion
splatParamName = o.scope.freeVariable 'args'
params.push new Value new IdentifierLiteral splatParamName
@ -2157,10 +2281,10 @@ exports.Code = class Code extends Base
# `(arg) => { var a = arg.a; }`, with a default value if it has one.
if param.value?
condition = new Op '===', param, new UndefinedLiteral
ifTrue = new Assign new Value(param.name), param.value, null, param: yes
ifTrue = new Assign new Value(param.name), param.value
exprs.push new If condition, ifTrue
else
exprs.push new Assign new Value(param.name), param.asReference(o), null, param: yes
exprs.push new Assign new Value(param.name), param.asReference(o)
# If this parameter comes before the splat or expansion, it will go
# in the function definition parameter list.
@ -2182,6 +2306,16 @@ exports.Code = class Code extends Base
param.name.lhs = yes
param.name.eachName (prop) ->
o.scope.parameter prop.value
# Compile `foo({a, b...}) ->` to `foo(arg) -> {a, b...} = arg`.
# Can be removed once ES proposal hits Stage 4.
if param.name instanceof Obj and param.name.hasSplat()
splatParamName = o.scope.freeVariable 'arg'
o.scope.parameter splatParamName
ref = new Value new IdentifierLiteral splatParamName
exprs.push new Assign new Value(param.name), ref
# Compile `foo({a, b...} = {}) ->` to `foo(arg = {}) -> {a, b...} = arg`.
if param.value? and not param.assignedInBody
ref = new Assign ref, param.value, null, param: yes
else
o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o
params.push ref
@ -2194,7 +2328,8 @@ exports.Code = class Code extends Base
condition = new Op '===', param, new UndefinedLiteral
ifTrue = new Assign new Value(param.name), param.value
exprs.push new If condition, ifTrue
# Add this parameter to the scope, since it wouldnt have been added yet since it was skipped earlier.
# Add this parameter to the scope, since it wouldnt have been added
# yet since it was skipped earlier.
o.scope.add param.name.value, 'var', yes if param.name?.value?
# If there were parameters after the splat or expansion parameter, those
@ -3181,6 +3316,13 @@ exports.If = class If extends Base
UTILITIES =
modulo: -> 'function(a, b) { return (+a % (b = +b) + b) % b; }'
objectWithoutKeys: -> "
function(o, ks) {
var res = {};
for (var k in o) ([].indexOf.call(ks, k) < 0 && {}.hasOwnProperty.call(o, k)) && (res[k] = o[k]);
return res;
}
"
boundMethodCheck: -> "
function(instance, Constructor) {
if (!(instance instanceof Constructor)) {

View File

@ -1,4 +1,5 @@
return unless require?
{buildCSOptionParser} = require '../lib/coffeescript/command'
optionParser = buildCSOptionParser()

View File

@ -235,6 +235,154 @@ test "destructuring assignment against an expression", ->
eq a, y
eq b, z
test "destructuring assignment with objects and splats: ES2015", ->
obj = {a: 1, b: 2, c: 3, d: 4, e: 5}
throws (-> CoffeeScript.compile "{a, r..., s...} = x"), null, "multiple rest elements are disallowed"
throws (-> CoffeeScript.compile "{a, r..., s..., b} = x"), null, "multiple rest elements are disallowed"
prop = "b"
{a, b, r...} = obj
eq a, 1
eq b, 2
eq r.e, obj.e
eq r.a, undefined
{d, c: x, r...} = obj
eq x, 3
eq d, 4
eq r.c, undefined
eq r.b, 2
{a, 'b': z, g = 9, r...} = obj
eq g, 9
eq z, 2
eq r.b, undefined
test "destructuring assignment with splats and default values", ->
obj = {}
c = {b: 1}
{ a: {b} = c, d...} = obj
eq b, 1
deepEqual d, {}
test "destructuring assignment with splat with default value", ->
obj = {}
c = {val: 1}
{ a: {b...} = c } = obj
deepEqual b, val: 1
test "destructuring assignment with multiple splats in different objects", ->
obj = { a: {val: 1}, b: {val: 2} }
{ 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
obj = {1: 'a', 2: 'b'}
{ "#{foo()}": a, b... } = obj
eq a, 'a'
eq i, 1
deepEqual b, 2: 'b'
# Tests from https://babeljs.io/docs/plugins/transform-object-rest-spread/.
test "destructuring assignment with objects and splats: Babel tests", ->
# What Babel calls rest properties:
{ x, y, z... } = { x: 1, y: 2, a: 3, b: 4 }
eq x, 1
eq y, 2
deepEqual z, { a: 3, b: 4 }
# What Babel calls spread properties:
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 = {
a: a1
b: {
'c': {
d: {
b1
e: c1
f: d1
}
}
}
b2: {b1, c1}
}
{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' }
foo: 'bar'
j =
f: 'world'
i =
some: 'prop'
{
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
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
deepEqual obj3.g, {obj..., c: 1}
(({a, b, r...}) ->
eq 1, a
deepEqual r, {c: 3, d: 44, e: 55}
) {obj2..., d: 44, e: 55}
obj = {a: 1, b: 2, c: {d: 3, e: 4, f: {g: 5}}}
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
fn = -> {c: {d: 33, e: 44, f: {g: 55}}}
obj6 = {obj..., fn()...}
eq obj6.c.d, 33
deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}}
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}}
test "bracket insertion when necessary", ->
[a] = [0] ? [1]
eq a, 0

View File

@ -804,26 +804,27 @@ test "unexpected object keys", ->
[[]]: 1
^
'''
assertErrorFormat '''
{(a + "b")}
''', '''
[stdin]:1:2: error: unexpected (
[stdin]:1:11: error: unexpected }
{(a + "b")}
^
^
'''
assertErrorFormat '''
{(a + "b"): 1}
''', '''
[stdin]:1:2: error: unexpected (
[stdin]:1:11: error: unexpected :
{(a + "b"): 1}
^
^
'''
assertErrorFormat '''
(a + "b"): 1
''', '''
[stdin]:1:1: error: unexpected (
[stdin]:1:10: error: unexpected :
(a + "b"): 1
^
^
'''
assertErrorFormat '''
a: 1, [[]]: 2

View File

@ -183,6 +183,69 @@ test "destructuring in function definition", ->
url: '/home', async: true, beforeSend: fn, cache: true, method: 'post', data: {}
}
test "rest element destructuring in function definition", ->
obj = {a: 1, b: 2, c: 3, d: 4, e: 5}
(({a, b, r...}) ->
eq 1, a
eq 2, b,
deepEqual r, {c: 3, d: 4, e: 5}
) obj
(({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
b: {
'c': {
d: {
b1
e: c1
f: d1
}
}
}
b2: {b1, c1}
}
(({a: w, b: {c: {d: {b1: bb, r1...}}}, r2...}) ->
eq a1, w
eq bb, b1
eq r2.b, undefined
deepEqual r1, {e: c1, f: d1}
deepEqual r2.b2, {b1, c1}
) obj1
b = 3
f = ({a, b...}) ->
f {}
eq 3, b
(({a, r...} = {}) ->
eq a, undefined
deepEqual r, {}
)()
(({a, r...} = {}) ->
eq a, 1
deepEqual r, {b: 2, c: 3}
) {a: 1, b: 2, c: 3}
f = ({a, r...} = {}) -> [a, r]
deepEqual [undefined, {}], f()
deepEqual [1, {b: 2}], f {a: 1, b: 2}
deepEqual [1, {}], f {a: 1}
f = ({a, r...} = {a: 1, b: 2}) -> [a, r]
deepEqual [1, {b:2}], f()
deepEqual [2, {}], f {a:2}
deepEqual [3, {c:5}], f {a:3, c:5}
test "#4005: `([a = {}]..., b) ->` weirdness", ->
fn = ([a = {}]..., b) -> [a, b]
deepEqual fn(5), [{}, 5]