mirror of
				https://github.com/jashkenas/coffeescript.git
				synced 2022-11-09 12:23:24 -05:00 
			
		
		
		
	[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
					
				
					 11 changed files with 822 additions and 240 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -8,3 +8,4 @@ test/*.js
 | 
			
		|||
parser.output
 | 
			
		||||
/node_modules
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
yarn.lock
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,7 @@
 | 
			
		|||
<script type="text/coffeescript">
 | 
			
		||||
@testingBrowser = yes
 | 
			
		||||
@global = window
 | 
			
		||||
bold = red = green = reset = ''
 | 
			
		||||
stdout = document.getElementById 'stdout'
 | 
			
		||||
start = new Date
 | 
			
		||||
@currentFile = ''
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
											
										
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
# ----------
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										166
									
								
								src/nodes.coffee
									
										
									
									
									
								
							
							
						
						
									
										166
									
								
								src/nodes.coffee
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -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 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
 | 
			
		||||
    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 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
 | 
			
		||||
  # 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 isn’t 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 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?
 | 
			
		||||
 | 
			
		||||
    # 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)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
return unless require?
 | 
			
		||||
 | 
			
		||||
{buildCSOptionParser} = require '../lib/coffeescript/command'
 | 
			
		||||
 | 
			
		||||
optionParser = buildCSOptionParser()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue