[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:
parent
58c608620e
commit
a7a6006533
|
@ -8,3 +8,4 @@ test/*.js
|
||||||
parser.output
|
parser.output
|
||||||
/node_modules
|
/node_modules
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
yarn.lock
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
<script type="text/coffeescript">
|
<script type="text/coffeescript">
|
||||||
@testingBrowser = yes
|
@testingBrowser = yes
|
||||||
@global = window
|
@global = window
|
||||||
|
bold = red = green = reset = ''
|
||||||
stdout = document.getElementById 'stdout'
|
stdout = document.getElementById 'stdout'
|
||||||
start = new Date
|
start = new Date
|
||||||
@currentFile = ''
|
@currentFile = ''
|
||||||
|
|
|
@ -123,7 +123,7 @@
|
||||||
AssignObj: [
|
AssignObj: [
|
||||||
o('ObjAssignable', function() {
|
o('ObjAssignable', function() {
|
||||||
return new Value($1);
|
return new Value($1);
|
||||||
}), o('ObjAssignable : Expression', function() {
|
}), o('ObjRestValue'), o('ObjAssignable : Expression', function() {
|
||||||
return new Assign(LOC(1)(new Value($1)), $3, 'object', {
|
return new Assign(LOC(1)(new Value($1)), $3, 'object', {
|
||||||
operatorToken: LOC(2)(new Literal($2))
|
operatorToken: LOC(2)(new Literal($2))
|
||||||
});
|
});
|
||||||
|
@ -143,6 +143,29 @@
|
||||||
],
|
],
|
||||||
SimpleObjAssignable: [o('Identifier'), o('Property'), o('ThisProperty')],
|
SimpleObjAssignable: [o('Identifier'), o('Property'), o('ThisProperty')],
|
||||||
ObjAssignable: [o('SimpleObjAssignable'), o('AlphaNumeric')],
|
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: [
|
Return: [
|
||||||
o('RETURN Expression', function() {
|
o('RETURN Expression', function() {
|
||||||
return new Return($2);
|
return new Return($2);
|
||||||
|
|
|
@ -1543,8 +1543,20 @@
|
||||||
return !this.isAssignable();
|
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) {
|
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;
|
props = this.properties;
|
||||||
if (this.generated) {
|
if (this.generated) {
|
||||||
for (j = 0, len1 = props.length; j < len1; j++) {
|
for (j = 0, len1 = props.length; j < len1; j++) {
|
||||||
|
@ -1554,6 +1566,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.hasSplat()) {
|
||||||
|
return this.compileSpread(o);
|
||||||
|
}
|
||||||
idt = o.indent += TAB;
|
idt = o.indent += TAB;
|
||||||
lastNoncom = this.lastNonComment(this.properties);
|
lastNoncom = this.lastNonComment(this.properties);
|
||||||
if (this.lhs) {
|
if (this.lhs) {
|
||||||
|
@ -1581,7 +1596,7 @@
|
||||||
}
|
}
|
||||||
answer = [];
|
answer = [];
|
||||||
answer.push(this.makeCode(isCompact ? '' : '\n'));
|
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];
|
prop = props[i];
|
||||||
join = i === props.length - 1 ? '' : isCompact && this.csx ? ' ' : isCompact ? ', ' : prop === lastNoncom || prop instanceof Comment || this.csx ? '\n' : ',\n';
|
join = i === props.length - 1 ? '' : isCompact && this.csx ? ' ' : isCompact ? ', ' : prop === lastNoncom || prop instanceof Comment || this.csx ? '\n' : ',\n';
|
||||||
indent = isCompact || prop instanceof Comment ? '' : idt;
|
indent = isCompact || prop instanceof Comment ? '' : idt;
|
||||||
|
@ -1660,6 +1675,38 @@
|
||||||
return results;
|
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'];
|
Obj.prototype.children = ['properties'];
|
||||||
|
@ -2445,6 +2492,11 @@
|
||||||
if (!this.variable.isAssignable()) {
|
if (!this.variable.isAssignable()) {
|
||||||
return this.compileDestructuring(o);
|
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()) {
|
if (this.variable.isSplice()) {
|
||||||
return this.compileSplice(o);
|
return this.compileSplice(o);
|
||||||
|
@ -2501,13 +2553,117 @@
|
||||||
return compiledName.concat(this.makeCode(this.csx ? '=' : ': '), val);
|
return compiledName.concat(this.makeCode(this.csx ? '=' : ': '), val);
|
||||||
}
|
}
|
||||||
answer = compiledName.concat(this.makeCode(` ${this.context || '='} `), 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);
|
return this.wrapInParentheses(answer);
|
||||||
} else {
|
} else {
|
||||||
return answer;
|
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) {
|
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;
|
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;
|
top = o.level === LEVEL_TOP;
|
||||||
|
@ -2825,17 +2981,13 @@
|
||||||
if (param.name instanceof Arr) {
|
if (param.name instanceof Arr) {
|
||||||
splatParamName = o.scope.freeVariable('arg');
|
splatParamName = o.scope.freeVariable('arg');
|
||||||
params.push(ref = new Value(new IdentifierLiteral(splatParamName)));
|
params.push(ref = new Value(new IdentifierLiteral(splatParamName)));
|
||||||
exprs.push(new Assign(new Value(param.name), ref, null, {
|
exprs.push(new Assign(new Value(param.name), ref));
|
||||||
param: true
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
params.push(ref = param.asReference(o));
|
params.push(ref = param.asReference(o));
|
||||||
splatParamName = fragmentsToText(ref.compileNode(o));
|
splatParamName = fragmentsToText(ref.compileNode(o));
|
||||||
}
|
}
|
||||||
if (param.shouldCache()) {
|
if (param.shouldCache()) {
|
||||||
exprs.push(new Assign(new Value(param.name), ref, null, {
|
exprs.push(new Assign(new Value(param.name), ref));
|
||||||
param: true
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
splatParamName = o.scope.freeVariable('args');
|
splatParamName = o.scope.freeVariable('args');
|
||||||
|
@ -2848,14 +3000,10 @@
|
||||||
haveBodyParam = true;
|
haveBodyParam = true;
|
||||||
if (param.value != null) {
|
if (param.value != null) {
|
||||||
condition = new Op('===', param, new UndefinedLiteral);
|
condition = new Op('===', param, new UndefinedLiteral);
|
||||||
ifTrue = new Assign(new Value(param.name), param.value, null, {
|
ifTrue = new Assign(new Value(param.name), param.value);
|
||||||
param: true
|
|
||||||
});
|
|
||||||
exprs.push(new If(condition, ifTrue));
|
exprs.push(new If(condition, ifTrue));
|
||||||
} else {
|
} else {
|
||||||
exprs.push(new Assign(new Value(param.name), param.asReference(o), null, {
|
exprs.push(new Assign(new Value(param.name), param.asReference(o)));
|
||||||
param: true
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!haveSplatParam) {
|
if (!haveSplatParam) {
|
||||||
|
@ -2875,6 +3023,17 @@
|
||||||
param.name.eachName(function(prop) {
|
param.name.eachName(function(prop) {
|
||||||
return o.scope.parameter(prop.value);
|
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 {
|
} else {
|
||||||
o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o)));
|
o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o)));
|
||||||
}
|
}
|
||||||
|
@ -3060,12 +3219,12 @@
|
||||||
|
|
||||||
exports.Param = Param = (function() {
|
exports.Param = Param = (function() {
|
||||||
class Param extends Base {
|
class Param extends Base {
|
||||||
constructor(name1, value1, splat) {
|
constructor(name1, value1, splat1) {
|
||||||
var message, token;
|
var message, token;
|
||||||
super();
|
super();
|
||||||
this.name = name1;
|
this.name = name1;
|
||||||
this.value = value1;
|
this.value = value1;
|
||||||
this.splat = splat;
|
this.splat = splat1;
|
||||||
message = isUnassignable(this.name.unwrapAll().value);
|
message = isUnassignable(this.name.unwrapAll().value);
|
||||||
if (message) {
|
if (message) {
|
||||||
this.name.error(message);
|
this.name.error(message);
|
||||||
|
@ -4296,6 +4455,9 @@
|
||||||
modulo: function() {
|
modulo: function() {
|
||||||
return 'function(a, b) { return (+a % (b = +b) + b) % b; }';
|
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() {
|
boundMethodCheck: function() {
|
||||||
return "function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } }";
|
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
|
@ -190,6 +190,7 @@ grammar =
|
||||||
# the ordinary **Assign** is that these allow numbers and strings as keys.
|
# the ordinary **Assign** is that these allow numbers and strings as keys.
|
||||||
AssignObj: [
|
AssignObj: [
|
||||||
o 'ObjAssignable', -> new Value $1
|
o 'ObjAssignable', -> new Value $1
|
||||||
|
o 'ObjRestValue'
|
||||||
o 'ObjAssignable : Expression', -> new Assign LOC(1)(new Value $1), $3, 'object',
|
o 'ObjAssignable : Expression', -> new Assign LOC(1)(new Value $1), $3, 'object',
|
||||||
operatorToken: LOC(2)(new Literal $2)
|
operatorToken: LOC(2)(new Literal $2)
|
||||||
o 'ObjAssignable :
|
o 'ObjAssignable :
|
||||||
|
@ -214,6 +215,28 @@ grammar =
|
||||||
o 'AlphaNumeric'
|
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.
|
# A return statement from a function body.
|
||||||
Return: [
|
Return: [
|
||||||
o 'RETURN Expression', -> new Return $2
|
o 'RETURN Expression', -> new Return $2
|
||||||
|
@ -701,7 +724,6 @@ grammar =
|
||||||
Expression', -> new Assign $1, $4, $2
|
Expression', -> new Assign $1, $4, $2
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Precedence
|
# Precedence
|
||||||
# ----------
|
# ----------
|
||||||
|
|
||||||
|
|
166
src/nodes.coffee
166
src/nodes.coffee
|
@ -1157,11 +1157,20 @@ exports.Obj = class Obj extends Base
|
||||||
shouldCache: ->
|
shouldCache: ->
|
||||||
not @isAssignable()
|
not @isAssignable()
|
||||||
|
|
||||||
|
# Check if object contains splat.
|
||||||
|
hasSplat: ->
|
||||||
|
splat = yes for prop in @properties when prop instanceof Splat
|
||||||
|
splat ? no
|
||||||
|
|
||||||
compileNode: (o) ->
|
compileNode: (o) ->
|
||||||
props = @properties
|
props = @properties
|
||||||
if @generated
|
if @generated
|
||||||
for node in props when node instanceof Value
|
for node in props when node instanceof Value
|
||||||
node.error 'cannot have an implicit value in an implicit object'
|
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
|
idt = o.indent += TAB
|
||||||
lastNoncom = @lastNonComment @properties
|
lastNoncom = @lastNonComment @properties
|
||||||
|
|
||||||
|
@ -1203,12 +1212,10 @@ exports.Obj = class Obj extends Base
|
||||||
prop.variable
|
prop.variable
|
||||||
else if prop not instanceof Comment
|
else if prop not instanceof Comment
|
||||||
prop
|
prop
|
||||||
|
|
||||||
if key instanceof Value and key.hasProperties()
|
if key instanceof Value and key.hasProperties()
|
||||||
key.error 'invalid object key' if prop.context is 'object' or not key.this
|
key.error 'invalid object key' if prop.context is 'object' or not key.this
|
||||||
key = key.properties[0].name
|
key = key.properties[0].name
|
||||||
prop = new Assign key, prop, 'object'
|
prop = new Assign key, prop, 'object'
|
||||||
|
|
||||||
if key is prop
|
if key is prop
|
||||||
if prop.shouldCache()
|
if prop.shouldCache()
|
||||||
[key, value] = prop.base.cache o
|
[key, value] = prop.base.cache o
|
||||||
|
@ -1216,7 +1223,6 @@ exports.Obj = class Obj extends Base
|
||||||
prop = new Assign key, value, 'object'
|
prop = new Assign key, value, 'object'
|
||||||
else if not prop.bareLiteral?(IdentifierLiteral)
|
else if not prop.bareLiteral?(IdentifierLiteral)
|
||||||
prop = new Assign prop, prop, 'object'
|
prop = new Assign prop, prop, 'object'
|
||||||
|
|
||||||
if indent then answer.push @makeCode indent
|
if indent then answer.push @makeCode indent
|
||||||
prop.csx = yes if @csx
|
prop.csx = yes if @csx
|
||||||
answer.push @makeCode ' ' if @csx and i is 0
|
answer.push @makeCode ' ' if @csx and i is 0
|
||||||
|
@ -1236,6 +1242,29 @@ exports.Obj = class Obj extends Base
|
||||||
prop = prop.unwrapAll()
|
prop = prop.unwrapAll()
|
||||||
prop.eachName iterator if prop.eachName?
|
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
|
#### Arr
|
||||||
|
|
||||||
# An array literal.
|
# An array literal.
|
||||||
|
@ -1796,6 +1825,9 @@ exports.Assign = class Assign extends Base
|
||||||
# destructured variables.
|
# destructured variables.
|
||||||
@variable.base.lhs = yes
|
@variable.base.lhs = yes
|
||||||
return @compileDestructuring o unless @variable.isAssignable()
|
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 @compileSplice o if @variable.isSplice()
|
||||||
return @compileConditional o if @context in ['||=', '&&=', '?=']
|
return @compileConditional o if @context in ['||=', '&&=', '?=']
|
||||||
|
@ -1839,11 +1871,105 @@ exports.Assign = class Assign extends Base
|
||||||
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
|
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
|
||||||
# Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
|
# Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
|
||||||
# if we’re destructuring without declaring, the destructuring assignment must be wrapped in parentheses.
|
# if we’re 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
|
@wrapInParentheses answer
|
||||||
else
|
else
|
||||||
answer
|
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 we’re 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
|
# Brief implementation of recursive pattern matching, when assigning array or
|
||||||
# object literals to a value. Peeks at their properties to assign inner names.
|
# object literals to a value. Peeks at their properties to assign inner names.
|
||||||
compileDestructuring: (o) ->
|
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
|
# 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
|
# `{a, b} = fn()` must be cached, for example. Make vvar into a simple
|
||||||
# variable if it isn't already.
|
# variable if it isn’t already.
|
||||||
if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
|
if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
|
||||||
ref = o.scope.freeVariable 'ref'
|
ref = o.scope.freeVariable 'ref'
|
||||||
assigns.push [@makeCode(ref + ' = '), vvar...]
|
assigns.push [@makeCode(ref + ' = '), vvar...]
|
||||||
|
@ -2098,7 +2224,6 @@ exports.Code = class Code extends Base
|
||||||
@eachParamName (name, node, param) ->
|
@eachParamName (name, node, param) ->
|
||||||
node.error "multiple parameters named '#{name}'" if name in paramNames
|
node.error "multiple parameters named '#{name}'" if name in paramNames
|
||||||
paramNames.push name
|
paramNames.push name
|
||||||
|
|
||||||
if node.this
|
if node.this
|
||||||
name = node.properties[0].name.value
|
name = node.properties[0].name.value
|
||||||
name = "_#{name}" if name in JS_FORBIDDEN
|
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'
|
param.error 'only one splat or expansion parameter is allowed per function definition'
|
||||||
else if param instanceof Expansion and @params.length is 1
|
else if param instanceof Expansion and @params.length is 1
|
||||||
param.error 'an expansion parameter cannot be the only parameter in a function definition'
|
param.error 'an expansion parameter cannot be the only parameter in a function definition'
|
||||||
|
|
||||||
haveSplatParam = yes
|
haveSplatParam = yes
|
||||||
if param.splat
|
if param.splat
|
||||||
if param.name instanceof Arr
|
if param.name instanceof Arr
|
||||||
|
@ -2132,12 +2256,12 @@ exports.Code = class Code extends Base
|
||||||
# function parameter list, and if so, how?
|
# function parameter list, and if so, how?
|
||||||
splatParamName = o.scope.freeVariable 'arg'
|
splatParamName = o.scope.freeVariable 'arg'
|
||||||
params.push ref = new Value new IdentifierLiteral splatParamName
|
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
|
else
|
||||||
params.push ref = param.asReference o
|
params.push ref = param.asReference o
|
||||||
splatParamName = fragmentsToText ref.compileNode o
|
splatParamName = fragmentsToText ref.compileNode o
|
||||||
if param.shouldCache()
|
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
|
else # `param` is an Expansion
|
||||||
splatParamName = o.scope.freeVariable 'args'
|
splatParamName = o.scope.freeVariable 'args'
|
||||||
params.push new Value new IdentifierLiteral splatParamName
|
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.
|
# `(arg) => { var a = arg.a; }`, with a default value if it has one.
|
||||||
if param.value?
|
if param.value?
|
||||||
condition = new Op '===', param, new UndefinedLiteral
|
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
|
exprs.push new If condition, ifTrue
|
||||||
else
|
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
|
# If this parameter comes before the splat or expansion, it will go
|
||||||
# in the function definition parameter list.
|
# in the function definition parameter list.
|
||||||
|
@ -2182,6 +2306,16 @@ exports.Code = class Code extends Base
|
||||||
param.name.lhs = yes
|
param.name.lhs = yes
|
||||||
param.name.eachName (prop) ->
|
param.name.eachName (prop) ->
|
||||||
o.scope.parameter prop.value
|
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
|
else
|
||||||
o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o
|
o.scope.parameter fragmentsToText (if param.value? then param else ref).compileToFragments o
|
||||||
params.push ref
|
params.push ref
|
||||||
|
@ -2194,7 +2328,8 @@ exports.Code = class Code extends Base
|
||||||
condition = new Op '===', param, new UndefinedLiteral
|
condition = new Op '===', param, new UndefinedLiteral
|
||||||
ifTrue = new Assign new Value(param.name), param.value
|
ifTrue = new Assign new Value(param.name), param.value
|
||||||
exprs.push new If condition, ifTrue
|
exprs.push new If condition, ifTrue
|
||||||
# Add this parameter to the scope, since it wouldn’t have been added yet since it was skipped earlier.
|
# Add this parameter to the scope, since it wouldn’t have been added
|
||||||
|
# yet since it was skipped earlier.
|
||||||
o.scope.add param.name.value, 'var', yes if param.name?.value?
|
o.scope.add param.name.value, 'var', yes if param.name?.value?
|
||||||
|
|
||||||
# If there were parameters after the splat or expansion parameter, those
|
# If there were parameters after the splat or expansion parameter, those
|
||||||
|
@ -3181,6 +3316,13 @@ exports.If = class If extends Base
|
||||||
|
|
||||||
UTILITIES =
|
UTILITIES =
|
||||||
modulo: -> 'function(a, b) { return (+a % (b = +b) + b) % b; }'
|
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: -> "
|
boundMethodCheck: -> "
|
||||||
function(instance, Constructor) {
|
function(instance, Constructor) {
|
||||||
if (!(instance instanceof Constructor)) {
|
if (!(instance instanceof Constructor)) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
return unless require?
|
return unless require?
|
||||||
|
|
||||||
{buildCSOptionParser} = require '../lib/coffeescript/command'
|
{buildCSOptionParser} = require '../lib/coffeescript/command'
|
||||||
|
|
||||||
optionParser = buildCSOptionParser()
|
optionParser = buildCSOptionParser()
|
||||||
|
|
|
@ -235,6 +235,154 @@ test "destructuring assignment against an expression", ->
|
||||||
eq a, y
|
eq a, y
|
||||||
eq b, z
|
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", ->
|
test "bracket insertion when necessary", ->
|
||||||
[a] = [0] ? [1]
|
[a] = [0] ? [1]
|
||||||
eq a, 0
|
eq a, 0
|
||||||
|
|
|
@ -804,24 +804,25 @@ test "unexpected object keys", ->
|
||||||
[[]]: 1
|
[[]]: 1
|
||||||
^
|
^
|
||||||
'''
|
'''
|
||||||
|
|
||||||
assertErrorFormat '''
|
assertErrorFormat '''
|
||||||
{(a + "b")}
|
{(a + "b")}
|
||||||
''', '''
|
''', '''
|
||||||
[stdin]:1:2: error: unexpected (
|
[stdin]:1:11: error: unexpected }
|
||||||
{(a + "b")}
|
{(a + "b")}
|
||||||
^
|
^
|
||||||
'''
|
'''
|
||||||
assertErrorFormat '''
|
assertErrorFormat '''
|
||||||
{(a + "b"): 1}
|
{(a + "b"): 1}
|
||||||
''', '''
|
''', '''
|
||||||
[stdin]:1:2: error: unexpected (
|
[stdin]:1:11: error: unexpected :
|
||||||
{(a + "b"): 1}
|
{(a + "b"): 1}
|
||||||
^
|
^
|
||||||
'''
|
'''
|
||||||
assertErrorFormat '''
|
assertErrorFormat '''
|
||||||
(a + "b"): 1
|
(a + "b"): 1
|
||||||
''', '''
|
''', '''
|
||||||
[stdin]:1:1: error: unexpected (
|
[stdin]:1:10: error: unexpected :
|
||||||
(a + "b"): 1
|
(a + "b"): 1
|
||||||
^
|
^
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -183,6 +183,69 @@ test "destructuring in function definition", ->
|
||||||
url: '/home', async: true, beforeSend: fn, cache: true, method: 'post', data: {}
|
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", ->
|
test "#4005: `([a = {}]..., b) ->` weirdness", ->
|
||||||
fn = ([a = {}]..., b) -> [a, b]
|
fn = ([a = {}]..., b) -> [a, b]
|
||||||
deepEqual fn(5), [{}, 5]
|
deepEqual fn(5), [{}, 5]
|
||||||
|
|
Loading…
Reference in New Issue