Skip to content

Commit

Permalink
Add support for destructuring ...rest in an array destructuring pattern.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121893000
  • Loading branch information
brad4d authored and blickly committed May 10, 2016
1 parent 461c2ad commit 7563187
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 28 deletions.
14 changes: 10 additions & 4 deletions src/com/google/javascript/jscomp/AstValidator.java
Expand Up @@ -746,7 +746,7 @@ private void validateParametersEs6(Node n) {
if (c.getNext() != null) { if (c.getNext() != null) {
violation("Rest parameters must come after all other parameters.", c); violation("Rest parameters must come after all other parameters.", c);
} }
validateRest(c); validateRest(Token.PARAM_LIST, c);
} else if (c.isDefaultValue()) { } else if (c.isDefaultValue()) {
defaultParams = true; defaultParams = true;
validateDefaultValue(Token.PARAM_LIST, c); validateDefaultValue(Token.PARAM_LIST, c);
Expand Down Expand Up @@ -789,10 +789,16 @@ private void validateCall(Node n) {
} }
} }


private void validateRest(Node n) { private void validateRest(int type, Node n) {
validateNodeType(Token.REST, n); validateNodeType(Token.REST, n);
validateChildCount(n); validateChildCount(n);
validateNonEmptyString(n.getFirstChild()); if (type == Token.PARAM_LIST) {
// TODO(bradfordcsmith): Make destructuring rest parameters work.
// https://github.com/google/closure-compiler/issues/1383
validateNonEmptyString(n.getFirstChild());
} else {
validateLHS(type, n.getFirstChild());
}
} }


private void validateSpread(Node n) { private void validateSpread(Node n) {
Expand Down Expand Up @@ -869,7 +875,7 @@ private void validateArrayPattern(int type, Node n) {
if (c == n.getLastChild() && NodeUtil.isNameDeclaration(n.getParent())) { if (c == n.getLastChild() && NodeUtil.isNameDeclaration(n.getParent())) {
validateExpression(c); validateExpression(c);
} else if (c.isRest()) { } else if (c.isRest()) {
validateRest(c); validateRest(type, c);
} else if (c.isEmpty()) { } else if (c.isEmpty()) {
validateChildless(c); validateChildless(c);
} else { } else {
Expand Down
2 changes: 1 addition & 1 deletion src/com/google/javascript/jscomp/CodeGenerator.java
Expand Up @@ -361,7 +361,7 @@ void add(Node n, Context context) {
} }
case Token.REST: case Token.REST:
add("..."); add("...");
add(first.getString()); add(first);
maybeAddTypeDecl(n); maybeAddTypeDecl(n);
break; break;


Expand Down
10 changes: 8 additions & 2 deletions src/com/google/javascript/jscomp/Es6RewriteDestructuring.java
Expand Up @@ -166,7 +166,10 @@ private void visitObjectPattern(NodeTraversal t, Node objectPattern, Node parent
} else if (parent.isAssign() && parent.getParent().isExprResult()) { } else if (parent.isAssign() && parent.getParent().isExprResult()) {
rhs = parent.getLastChild(); rhs = parent.getLastChild();
nodeToDetach = parent.getParent(); nodeToDetach = parent.getParent();
} else if (parent.isStringKey() || parent.isArrayPattern() || parent.isDefaultValue()) { } else if (parent.isRest()
|| parent.isStringKey()
|| parent.isArrayPattern()
|| parent.isDefaultValue()) {
// Nested object pattern; do nothing. We will visit it after rewriting the parent. // Nested object pattern; do nothing. We will visit it after rewriting the parent.
return; return;
} else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) { } else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) {
Expand Down Expand Up @@ -275,7 +278,10 @@ private void visitArrayPattern(NodeTraversal t, Node arrayPattern, Node parent)
rhs = arrayPattern.getNext(); rhs = arrayPattern.getNext();
nodeToDetach = parent.getParent(); nodeToDetach = parent.getParent();
Preconditions.checkState(nodeToDetach.isExprResult()); Preconditions.checkState(nodeToDetach.isExprResult());
} else if (parent.isArrayPattern() || parent.isDefaultValue() || parent.isStringKey()) { } else if (parent.isArrayPattern()
|| parent.isRest()
|| parent.isDefaultValue()
|| parent.isStringKey()) {
// This is a nested array pattern. Don't do anything now; we'll visit it // This is a nested array pattern. Don't do anything now; we'll visit it
// after visiting the parent. // after visiting the parent.
return; return;
Expand Down
4 changes: 1 addition & 3 deletions src/com/google/javascript/jscomp/parsing/IRFactory.java
Expand Up @@ -1023,9 +1023,7 @@ Node processObjectPattern(ObjectPatternTree tree) {
} }


Node processAssignmentRestElement(AssignmentRestElementTree tree) { Node processAssignmentRestElement(AssignmentRestElementTree tree) {
Node name = newStringNode(Token.NAME, tree.identifier.value); return newNode(Token.REST, transformNodeWithInlineJsDoc(tree.assignmentTarget));
setSourceInfo(name, tree.identifier);
return newNode(Token.REST, name);
} }


Node processAstRoot(ProgramTree rootNode) { Node processAstRoot(ProgramTree rootNode) {
Expand Down
17 changes: 6 additions & 11 deletions src/com/google/javascript/jscomp/parsing/parser/Parser.java
Expand Up @@ -3174,21 +3174,16 @@ private ParseTree parseArrayPatternElement(PatternKind patternKind) {
private ParseTree parseArrayPatternRest(PatternKind patternKind) { private ParseTree parseArrayPatternRest(PatternKind patternKind) {
SourcePosition start = getTreeStartLocation(); SourcePosition start = getTreeStartLocation();
eat(TokenType.SPREAD); eat(TokenType.SPREAD);
ParseTree patternAssignmentTarget = parseRestAssignmentTarget(patternKind);
return new AssignmentRestElementTree(getTreeLocation(start), patternAssignmentTarget);
}

private ParseTree parseRestAssignmentTarget(PatternKind patternKind) {
ParseTree patternAssignmentTarget = parsePatternAssignmentTargetNoDefault(patternKind); ParseTree patternAssignmentTarget = parsePatternAssignmentTargetNoDefault(patternKind);
IdentifierToken identifierToken;
if (patternAssignmentTarget.type != ParseTreeType.IDENTIFIER_EXPRESSION) {
// TODO(bradfordcsmith): allow sub-patterns
// https://github.com/google/closure-compiler/issues/1383
reportError("lvalues in rest elements must be identifiers");
// Create a bogus token so compilation can continue to find possible other errors.
identifierToken = new IdentifierToken(getTreeLocation(start), "invalid_rest");
} else {
identifierToken = patternAssignmentTarget.asIdentifierExpression().identifierToken;
}
if (peek(TokenType.EQUAL)) { if (peek(TokenType.EQUAL)) {
reportError("A default value cannot be specified after '...'"); reportError("A default value cannot be specified after '...'");
} }
return new AssignmentRestElementTree(getTreeLocation(start), identifierToken); return patternAssignmentTarget;
} }


// Pattern ::= ... | "[" Element? ("," Element?)* "]" // Pattern ::= ... | "[" Element? ("," Element?)* "]"
Expand Down
Expand Up @@ -15,15 +15,14 @@
*/ */
package com.google.javascript.jscomp.parsing.parser.trees; package com.google.javascript.jscomp.parsing.parser.trees;


import com.google.javascript.jscomp.parsing.parser.IdentifierToken;
import com.google.javascript.jscomp.parsing.parser.util.SourceRange; import com.google.javascript.jscomp.parsing.parser.util.SourceRange;


public class AssignmentRestElementTree extends ParseTree { public class AssignmentRestElementTree extends ParseTree {
public final IdentifierToken identifier; public final ParseTree assignmentTarget;


public AssignmentRestElementTree(SourceRange location, IdentifierToken identifier) { public AssignmentRestElementTree(SourceRange location, ParseTree assignmentTarget) {
super(ParseTreeType.ASSIGNMENT_REST_ELEMENT, location); super(ParseTreeType.ASSIGNMENT_REST_ELEMENT, location);
this.identifier = identifier; this.assignmentTarget = assignmentTarget;
} }
} }


16 changes: 16 additions & 0 deletions test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java
Expand Up @@ -363,6 +363,22 @@ public void testArrayDestructuringRest() {
"}")); "}"));
} }


public void testArrayDestructuringMixedRest() {
test(
"let [first, ...[re, st, ...{length: num_left}]] = f();",
LINE_JOINER.join(
"var $jscomp$destructuring$var0 = $jscomp.makeIterator(f());",
"let first = $jscomp$destructuring$var0.next().value;",
"var $jscomp$destructuring$var1 = "
+ "$jscomp.makeIterator("
+ "$jscomp.arrayFromIterator($jscomp$destructuring$var0));",
"let re = $jscomp$destructuring$var1.next().value;",
"let st = $jscomp$destructuring$var1.next().value;",
"var $jscomp$destructuring$var2 = "
+ "$jscomp.arrayFromIterator($jscomp$destructuring$var1);",
"let num_left = $jscomp$destructuring$var2.length;"));
}

public void testArrayDestructuringArguments() { public void testArrayDestructuringArguments() {
test( test(
"function f() { var [x, y] = arguments; }", "function f() { var [x, y] = arguments; }",
Expand Down
30 changes: 27 additions & 3 deletions test/com/google/javascript/jscomp/parsing/ParserTest.java
Expand Up @@ -1338,20 +1338,44 @@ public void testArrayDestructuringTrailingComma() {
parseError("var [x,] = ['x',];", "Array pattern may not end with a comma"); parseError("var [x,] = ['x',];", "Array pattern may not end with a comma");
} }


public void testArrayDestructuringRest() { public void testArrayDestructuringDeclarationRest() {
mode = LanguageMode.ECMASCRIPT6; mode = LanguageMode.ECMASCRIPT6;
expectedFeatures = FeatureSet.ES6; expectedFeatures = FeatureSet.ES6;
parse("var [first, ...rest] = foo();"); parse("var [first, ...rest] = foo();");
parse("let [first, ...rest] = foo();"); parse("let [first, ...rest] = foo();");
parse("const [first, ...rest] = foo();"); parse("const [first, ...rest] = foo();");
// nested destructuring in regular parameters and rest parameters
parse("var [first, {a, b}, ...[re, st, ...{length}]] = foo();");


parseError( parseError(
"var [first, ...more = 'default'] = foo();", "var [first, ...more = 'default'] = foo();",
"A default value cannot be specified after '...'"); "A default value cannot be specified after '...'");
parseError("var [first, ...more, last] = foo();", "']' expected"); parseError("var [first, ...more, last] = foo();", "']' expected");


// TODO(tbreisacher): This should parse without error. This is valid in ES6.
parseError("var [first, ...[re, st]] = foo();", "lvalues in rest elements must be identifiers"); mode = LanguageMode.ECMASCRIPT5;
parseWarning(
"var [first, ...rest] = foo();",
"this language feature is only supported in es6 mode: destructuring");
}

public void testArrayDestructuringAssignRest() {
mode = LanguageMode.ECMASCRIPT6;
expectedFeatures = FeatureSet.ES6;
parse("[first, ...rest] = foo();");
// nested destructuring in regular parameters and rest parameters
parse("[first, {a, b}, ...[re, st, ...{length}]] = foo();");
// arbitrary LHS assignment target is allowed
parse("[x, ...y[15]] = foo();");
// arbitrary LHS assignment target not allowed
parseError(
"var [x, ...y[15]] = foo();",
"Only an identifier or destructuring pattern is allowed here.");

parseError(
"[first, ...more = 'default'] = foo();", "A default value cannot be specified after '...'");
parseError("var [first, ...more, last] = foo();", "']' expected");



mode = LanguageMode.ECMASCRIPT5; mode = LanguageMode.ECMASCRIPT5;
parseWarning("var [first, ...rest] = foo();", parseWarning("var [first, ...rest] = foo();",
Expand Down

0 comments on commit 7563187

Please sign in to comment.