Skip to content

Commit

Permalink
Add parsing of spread/rest in Objects #2303.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=179064975
  • Loading branch information
gardines authored and brad4d committed Dec 15, 2017
1 parent edf826f commit 359dfa0
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 32 deletions.
13 changes: 10 additions & 3 deletions src/com/google/javascript/jscomp/AstValidator.java
Expand Up @@ -764,9 +764,6 @@ private void validateParameters(Node n) {
validateNodeType(Token.PARAM_LIST, n);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.isRest()) {
if (c.getNext() != null) {
violation("Rest parameters must come after all other parameters.", c);
}
validateRest(Token.PARAM_LIST, c);
} else if (c.isDefaultValue()) {
validateDefaultValue(Token.PARAM_LIST, c);
Expand Down Expand Up @@ -807,6 +804,9 @@ private void validateRest(Token contextType, Node n) {
validateNodeType(Token.REST, n);
validateChildCount(n);
validateLHS(contextType, n.getFirstChild());
if (n.getNext() != null) {
violation("Rest parameters must come after all other parameters.", n);
}
}

private void validateSpread(Node n) {
Expand All @@ -821,6 +821,7 @@ private void validateSpread(Node n) {
}
break;
case ARRAYLIT:
case OBJECTLIT:
break;
default:
violation("SPREAD node should not be the child of a " + parent.getToken() + " node.", n);
Expand Down Expand Up @@ -957,6 +958,9 @@ private void validateObjectPattern(Token type, Node n) {
case DEFAULT_VALUE:
validateDefaultValue(type, c);
break;
case REST:
validateRest(type, c);
break;
case COMPUTED_PROP:
validateObjectPatternComputedPropKey(type, c);
break;
Expand Down Expand Up @@ -1270,6 +1274,9 @@ private void validateObjectLitKey(Node n) {
case COMPUTED_PROP:
validateObjectLitComputedPropKey(n);
return;
case SPREAD:
validateSpread(n);
return;
default:
violation("Expected object literal key expression but was " + n.getToken(), n);
}
Expand Down
2 changes: 1 addition & 1 deletion src/com/google/javascript/jscomp/CodeGenerator.java
Expand Up @@ -1001,7 +1001,7 @@ protected void add(Node n, Context context) {
cc.listSeparator();
}

checkState(NodeUtil.isObjLitProperty(c));
checkState(NodeUtil.isObjLitProperty(c) || c.isSpread(), c);
add(c);
}
add("}");
Expand Down
4 changes: 4 additions & 0 deletions src/com/google/javascript/jscomp/NodeUtil.java
Expand Up @@ -721,6 +721,10 @@ public static boolean isLiteralValue(Node n, boolean includeFunctions) {
|| !isLiteralValue(child.getLastChild(), includeFunctions)) {
return false;
}
} else if (child.isSpread()) {
if (!isLiteralValue(child.getOnlyChild(), includeFunctions)) {
return false;
}
} else {
// { key: value, ... }
// { "quoted_key": value, ... }
Expand Down
16 changes: 13 additions & 3 deletions src/com/google/javascript/jscomp/parsing/IRFactory.java
Expand Up @@ -1047,8 +1047,12 @@ Node processObjectPattern(ObjectPatternTree tree) {
maybeWarnForFeature(tree, Feature.DESTRUCTURING);

Node node = newNode(Token.OBJECT_PATTERN);
for (ParseTree child : tree.fields) {
node.addChildToBack(transformNodeWithInlineJsDoc(child));
for (ParseTree c : tree.fields) {
Node child = transformNodeWithInlineJsDoc(c);
if (child.isRest()) {
maybeWarnForFeature(c, Feature.OBJECT_PATTERN_REST);
}
node.addChildToBack(child);
}
return node;
}
Expand Down Expand Up @@ -1613,9 +1617,15 @@ Node processObjectLiteral(ObjectLiteralExpressionTree objTree) {
}

Node key = transform(el);
if (!key.isComputedProp() && !key.isQuotedString() && !currentFileIsExterns) {
if (!key.isComputedProp()
&& !key.isQuotedString()
&& !key.isSpread()
&& !currentFileIsExterns) {
maybeWarnKeywordProperty(key);
}
if (key.isSpread()) {
maybeWarnForFeature(el, Feature.OBJECT_LITERALS_WITH_SPREAD);
}
if (!key.hasChildren()) {
maybeWarn = true;
}
Expand Down
40 changes: 20 additions & 20 deletions src/com/google/javascript/jscomp/parsing/parser/Parser.java
Expand Up @@ -2395,6 +2395,7 @@ private ParseTree parseArrayLiteral() {
elements.add(new NullTree(getTreeLocation(getTreeStartLocation())));
} else {
if (peek(TokenType.SPREAD)) {
features = features.with(Feature.SPREAD_EXPRESSIONS);
elements.add(parseSpreadExpression());
} else {
elements.add(parseAssignmentExpression());
Expand All @@ -2419,7 +2420,9 @@ private ParseTree parseObjectLiteral() {

eat(TokenType.OPEN_CURLY);
Token commaToken = null;
while (peekPropertyNameOrComputedProp(0) || peek(TokenType.STAR)
while (peek(TokenType.SPREAD)
|| peekPropertyNameOrComputedProp(0)
|| peek(TokenType.STAR)
|| peekAccessibilityModifier()) {
commaToken = null;
result.add(parsePropertyAssignment());
Expand Down Expand Up @@ -2468,6 +2471,9 @@ private ParseTree parsePropertyAssignment() {
TokenType type = peekType();
if (type == TokenType.STAR) {
return parsePropertyAssignmentGenerator();
} else if (peek(TokenType.SPREAD)) {
features = features.with(Feature.OBJECT_LITERALS_WITH_SPREAD);
return parseSpreadExpression();
} else if (type == TokenType.STRING
|| type == TokenType.NUMBER
|| type == TokenType.IDENTIFIER
Expand Down Expand Up @@ -3570,21 +3576,10 @@ private ParseTree parsePattern(PatternKind kind) {
}

private boolean peekArrayPatternElement() {
return peekExpression() || peek(TokenType.SPREAD);
}

private ParseTree parseArrayPatternElement(PatternKind patternKind) {
ParseTree patternElement;

if (peek(TokenType.SPREAD)) {
patternElement = parseArrayPatternRest(patternKind);
} else {
patternElement = parsePatternAssignmentTarget(patternKind);
}
return patternElement;
return peekExpression();
}

private ParseTree parseArrayPatternRest(PatternKind patternKind) {
private ParseTree parsePatternRest(PatternKind patternKind) {
SourcePosition start = getTreeStartLocation();
eat(TokenType.SPREAD);
ParseTree patternAssignmentTarget = parseRestAssignmentTarget(patternKind);
Expand All @@ -3609,13 +3604,9 @@ private ParseTree parseArrayPattern(PatternKind kind) {
eat(TokenType.COMMA);
elements.add(new NullTree(getTreeLocation(getTreeStartLocation())));
} else {
ParseTree element = parseArrayPatternElement(kind);
elements.add(element);
elements.add(parsePatternAssignmentTarget(kind));

if (element.isAssignmentRestElement()) {
// Rest can only appear in the posterior, so we must be done
break;
} else if (peek(TokenType.COMMA)) {
if (peek(TokenType.COMMA)) {
// Consume the comma separator
eat(TokenType.COMMA);
} else {
Expand All @@ -3624,6 +3615,10 @@ private ParseTree parseArrayPattern(PatternKind kind) {
}
}
}
if (peek(TokenType.SPREAD)) {
features = features.with(Feature.ARRAY_PATTERN_REST);
elements.add(parsePatternRest(kind));
}
eat(TokenType.CLOSE_SQUARE);
return new ArrayPatternTree(getTreeLocation(start), elements.build());
}
Expand All @@ -3635,6 +3630,7 @@ private ParseTree parseObjectPattern(PatternKind kind) {
eat(TokenType.OPEN_CURLY);
while (peekObjectPatternField()) {
fields.add(parseObjectPatternField(kind));

if (peek(TokenType.COMMA)) {
// Consume the comma separator
eat(TokenType.COMMA);
Expand All @@ -3643,6 +3639,10 @@ private ParseTree parseObjectPattern(PatternKind kind) {
break;
}
}
if (peek(TokenType.SPREAD)) {
features = features.with(Feature.OBJECT_PATTERN_REST);
fields.add(parsePatternRest(kind));
}
eat(TokenType.CLOSE_CURLY);
return new ObjectPatternTree(getTreeLocation(start), fields.build());
}
Expand Down
14 changes: 14 additions & 0 deletions test/com/google/javascript/jscomp/AstValidatorTest.java
Expand Up @@ -312,6 +312,20 @@ public void testValidDestructuringAssignment2() {
valid("({['a']:b()['c'] = 1} = obj);");
}

public void testObjectRestAssignment() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT);
valid("var {a, ...rest} = obj;");
valid("({a:b, ...rest} = obj);");
valid("({a:b.c, ...rest} = obj);");
valid("({a:b().c, ...rest} = obj);");
valid("({a:b['c'], ...rest} = obj);");
valid("({a:b()['c'], ...rest} = obj);");
valid("({a:b.c = 1, ...rest} = obj);");
valid("({a:b().c = 1, ...rest} = obj);");
valid("({a:b['c'] = 1, ...rest} = obj);");
valid("({a:b()['c'] = 1, ...rest} = obj);");
}

public void testInvalidDestructuringAssignment() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);

Expand Down
22 changes: 22 additions & 0 deletions test/com/google/javascript/jscomp/CodePrinterTest.java
Expand Up @@ -53,6 +53,18 @@ public void testExponentiationAssignmentOperator() {
assertPrintSame("x**=y");
}

public void testObjectLiteralWithSpread() {
languageMode = LanguageMode.ECMASCRIPT_NEXT;
assertPrintSame("({...{}})");
assertPrintSame("({...x})");
assertPrintSame("({...x,a:1})");
assertPrintSame("({a:1,...x})");
assertPrintSame("({a:1,...x,b:1})");
assertPrintSame("({...x,...y})");
assertPrintSame("({...x,...f()})");
assertPrintSame("({...{...{}}})");
}

public void testPrint() {
assertPrint("10 + a + b", "10+a+b");
assertPrint("10 + (30*50)", "10+30*50");
Expand Down Expand Up @@ -448,6 +460,16 @@ public void testPrintObjectPatternInitializer() {
assertPrintSame("({a:{b=2},c}=foo())");
}

public void testPrintObjectPatternWithRest() {
languageMode = LanguageMode.ECMASCRIPT_NEXT;
assertPrintSame("const {a,...rest}=foo()");
assertPrintSame("var {a,...rest}=foo()");
assertPrintSame("let {a,...rest}=foo()");
assertPrintSame("({a,...rest}=foo())");
assertPrintSame("({a=2,...rest}=foo())");
assertPrintSame("({a:b=2,...rest}=foo())");
}

public void testPrettyPrintObjectPattern() {
languageMode = LanguageMode.ECMASCRIPT_2015;
assertPrettyPrint("const {a,b,c}=foo();", "const {a, b, c} = foo();\n");
Expand Down
10 changes: 10 additions & 0 deletions test/com/google/javascript/jscomp/NodeUtilTest.java
Expand Up @@ -529,6 +529,12 @@ public void testMayHaveSideEffects() {
assertSideEffect(false, "[...[1]]");
assertSideEffect(true, "[...[i++]]");
assertSideEffect(true, "[...f()]");
assertSideEffect(false, "({...x})");
assertSideEffect(false, "({...{}})");
assertSideEffect(false, "({...{a:1}})");
assertSideEffect(true, "({...{a:i++}})");
assertSideEffect(true, "({...{a:f()}})");
assertSideEffect(true, "({...f()})");
assertSideEffect(true, "i++");
assertSideEffect(true, "[b, [a, i++]]");
assertSideEffect(true, "i=3");
Expand Down Expand Up @@ -1699,6 +1705,10 @@ public void testLocalValueSpread() {
assertTrue(NodeUtil.evaluatesToLocalValue(getNode("[...x]")));
assertFalse(NodeUtil.isLiteralValue(getNode("[...x]"), false));
assertFalse(NodeUtil.isImmutableValue(getNode("[...x]")));

assertTrue(NodeUtil.evaluatesToLocalValue(getNode("{...x}")));
assertFalse(NodeUtil.isLiteralValue(getNode("{...x}"), false));
assertFalse(NodeUtil.isImmutableValue(getNode("{...x}")));
}

public void testLocalValueAwait() {
Expand Down
73 changes: 68 additions & 5 deletions test/com/google/javascript/jscomp/parsing/ParserTest.java
Expand Up @@ -1466,15 +1466,10 @@ public void testArrayDestructuringDeclarationRest() {

expectFeatures(Feature.DESTRUCTURING, Feature.ARRAY_PATTERN_REST);
parse("var [first, ...rest] = foo();");

expectFeatures(Feature.DESTRUCTURING, Feature.ARRAY_PATTERN_REST, Feature.LET_DECLARATIONS);
parse("let [first, ...rest] = foo();");

expectFeatures(Feature.DESTRUCTURING, Feature.ARRAY_PATTERN_REST, Feature.CONST_DECLARATIONS);
parse("const [first, ...rest] = foo();");

// nested destructuring in regular parameters and rest parameters
expectFeatures(Feature.DESTRUCTURING, Feature.ARRAY_PATTERN_REST);
parse("var [first, {a, b}, ...[re, st, ...{length}]] = foo();");

expectFeatures();
Expand All @@ -1491,6 +1486,59 @@ public void testArrayDestructuringDeclarationRest() {
getRequiresEs6Message(Feature.ARRAY_PATTERN_REST));
}

public void testObjectDestructuringDeclarationRest() {
mode = LanguageMode.ES_NEXT;
strictMode = SLOPPY;

expectFeatures(Feature.DESTRUCTURING, Feature.OBJECT_PATTERN_REST);
parse("var {first, ...rest} = foo();");
parse("let {first, ...rest} = foo();");
parse("const {first, ...rest} = foo();");

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

mode = LanguageMode.ECMASCRIPT6;
parseWarning(
"var {first, ...rest} = foo();",
getRequiresEsNextMessage(Feature.OBJECT_PATTERN_REST));
}

public void testArrayLiteralDeclarationSpread() {
mode = LanguageMode.ECMASCRIPT6;
strictMode = SLOPPY;

expectFeatures(Feature.SPREAD_EXPRESSIONS);
parse("var o = [first, ...spread];");

mode = LanguageMode.ECMASCRIPT5;
parseWarning(
"var o = [first, ...spread];",
getRequiresEs6Message(Feature.SPREAD_EXPRESSIONS));
}

public void testObjectLiteralDeclarationSpread() {
mode = LanguageMode.ES_NEXT;
strictMode = SLOPPY;

expectFeatures(Feature.OBJECT_LITERALS_WITH_SPREAD);
parse("var o = {first: 1, ...spread};");

mode = LanguageMode.ECMASCRIPT5;
parseWarning(
"var o = {first: 1, ...spread};",
getRequiresEs6Message(Feature.SPREAD_EXPRESSIONS),
getRequiresEsNextMessage(Feature.OBJECT_LITERALS_WITH_SPREAD));

mode = LanguageMode.ECMASCRIPT6;
parseWarning(
"var o = {first: 1, ...spread};",
getRequiresEsNextMessage(Feature.OBJECT_LITERALS_WITH_SPREAD));
}

public void testArrayDestructuringAssignRest() {
mode = LanguageMode.ECMASCRIPT6;
strictMode = SLOPPY;
Expand All @@ -1507,6 +1555,17 @@ public void testArrayDestructuringAssignRest() {
getRequiresEs6Message(Feature.ARRAY_PATTERN_REST));
}

public void testObjectDestructuringAssignRest() {
mode = LanguageMode.ES_NEXT;
strictMode = SLOPPY;
expectFeatures(Feature.DESTRUCTURING, Feature.OBJECT_PATTERN_REST);
parse("const {first, ...rest} = foo();");

mode = LanguageMode.ECMASCRIPT6;
parseWarning("var {first, ...rest} = foo();",
getRequiresEsNextMessage(Feature.OBJECT_PATTERN_REST));
}

public void testArrayDestructuringAssignRestInvalid() {
// arbitrary LHS assignment target not allowed
parseError(
Expand Down Expand Up @@ -3740,6 +3799,10 @@ private static String getRequiresEs6Message(Feature feature) {
return requiresLanguageModeMessage(LanguageMode.ECMASCRIPT6, feature);
}

private static String getRequiresEsNextMessage(Feature feature) {
return requiresLanguageModeMessage(LanguageMode.ES_NEXT, feature);
}

private static String requiresLanguageModeMessage(LanguageMode languageMode, Feature feature) {
return String.format(
"this language feature is only supported for %s mode or better: %s",
Expand Down

0 comments on commit 359dfa0

Please sign in to comment.