From c7d2139892a819a2aad80b26c4711331d2826457 Mon Sep 17 00:00:00 2001 From: nickreid Date: Thu, 12 Jul 2018 15:25:06 -0700 Subject: [PATCH] Update parser to record use of GETTER and SETTER features. Tests are added for the change as well as for parsing getters and setters within ES6 classes. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=204375343 --- .../jscomp/parsing/parser/Parser.java | 25 ++- .../javascript/jscomp/parsing/ParserTest.java | 205 ++++++++++++++---- 2 files changed, 181 insertions(+), 49 deletions(-) diff --git a/src/com/google/javascript/jscomp/parsing/parser/Parser.java b/src/com/google/javascript/jscomp/parsing/parser/Parser.java index c8fe624cc0f..461286fa519 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/Parser.java +++ b/src/com/google/javascript/jscomp/parsing/parser/Parser.java @@ -898,7 +898,7 @@ private ParseTree parseClassMemberDeclaration(PartialClassElement partial) { nameExpr = null; name = eatIdOrKeywordAsId(); if (Keywords.isKeyword(name.value, /* includeTypeScriptKeywords= */ false)) { - features = features.with(Feature.KEYWORDS_AS_PROPERTIES); + recordFeatureUsed(Feature.KEYWORDS_AS_PROPERTIES); } } else { // { 'str'() {} } @@ -1406,7 +1406,7 @@ private FormalParameterListTree parseFormalParameterList(ParamContext context) { if (!peek(TokenType.CLOSE_PAREN)) { Token comma = eat(TokenType.COMMA); if (peek(TokenType.CLOSE_PAREN)) { - features = features.with(Feature.TRAILING_COMMA_IN_PARAM_LIST); + recordFeatureUsed(Feature.TRAILING_COMMA_IN_PARAM_LIST); if (!config.atLeast8) { reportError(comma, "Invalid trailing comma in formal parameter list"); } @@ -2477,7 +2477,7 @@ private ParseTree parseArrayLiteral() { elements.add(new NullTree(getTreeLocation(getTreeStartLocation()))); } else { if (peek(TokenType.SPREAD)) { - features = features.with(Feature.SPREAD_EXPRESSIONS); + recordFeatureUsed(Feature.SPREAD_EXPRESSIONS); elements.add(parseSpreadExpression()); } else { elements.add(parseAssignmentExpression()); @@ -2522,7 +2522,7 @@ private ParseTree parseObjectLiteral() { void maybeReportTrailingComma(Token commaToken) { if (commaToken != null) { - features = features.with(Feature.TRAILING_COMMA); + recordFeatureUsed(Feature.TRAILING_COMMA); if (config.warnTrailingCommas) { // In ES3 mode warn about trailing commas which aren't accepted by // older browsers (such as IE8). @@ -2554,7 +2554,7 @@ private ParseTree parsePropertyAssignment() { if (type == TokenType.STAR) { return parsePropertyAssignmentGenerator(); } else if (peek(TokenType.SPREAD)) { - features = features.with(Feature.OBJECT_LITERALS_WITH_SPREAD); + recordFeatureUsed(Feature.OBJECT_LITERALS_WITH_SPREAD); return parseSpreadExpression(); } else if (type == TokenType.STRING || type == TokenType.NUMBER @@ -2657,6 +2657,7 @@ private ParseTree parseGetAccessor(PartialClassElement partial) { eat(TokenType.CLOSE_PAREN); ParseTree returnType = maybeParseColonType(); BlockTree body = parseFunctionBody(); + recordFeatureUsed(Feature.GETTER); return new GetAccessorTree( getTreeLocation(partial.start), propertyName, partial.isStatic, returnType, body); } else { @@ -2665,6 +2666,7 @@ private ParseTree parseGetAccessor(PartialClassElement partial) { eat(TokenType.CLOSE_PAREN); ParseTree returnType = maybeParseColonType(); BlockTree body = parseFunctionBody(); + recordFeatureUsed(Feature.GETTER); return new ComputedPropertyGetterTree( getTreeLocation(partial.start), property, @@ -2696,6 +2698,7 @@ private ParseTree parseSetAccessor(PartialClassElement partial) { BlockTree body = parseFunctionBody(); + recordFeatureUsed(Feature.SETTER); return new SetAccessorTree( getTreeLocation(partial.start), propertyName, partial.isStatic, parameter, body); } else { @@ -2703,6 +2706,7 @@ private ParseTree parseSetAccessor(PartialClassElement partial) { FormalParameterListTree parameter = parseSetterParameterList(); BlockTree body = parseFunctionBody(); + recordFeatureUsed(Feature.SETTER); return new ComputedPropertySetterTree( getTreeLocation(partial.start), property, @@ -3599,7 +3603,7 @@ private ArgumentListTree parseArguments() { if (!peek(TokenType.CLOSE_PAREN)) { Token comma = eat(TokenType.COMMA); if (peek(TokenType.CLOSE_PAREN)) { - features = features.with(Feature.TRAILING_COMMA_IN_PARAM_LIST); + recordFeatureUsed(Feature.TRAILING_COMMA_IN_PARAM_LIST); if (!config.atLeast8) { reportError(comma, "Invalid trailing comma in arguments list"); } @@ -3694,7 +3698,7 @@ private ParseTree parseArrayPattern(PatternKind kind) { } } if (peek(TokenType.SPREAD)) { - features = features.with(Feature.ARRAY_PATTERN_REST); + recordFeatureUsed(Feature.ARRAY_PATTERN_REST); elements.add(parsePatternRest(kind)); } eat(TokenType.CLOSE_SQUARE); @@ -3718,7 +3722,7 @@ private ParseTree parseObjectPattern(PatternKind kind) { } } if (peek(TokenType.SPREAD)) { - features = features.with(Feature.OBJECT_PATTERN_REST); + recordFeatureUsed(Feature.OBJECT_PATTERN_REST); fields.add(parsePatternRest(kind)); } eat(TokenType.CLOSE_CURLY); @@ -4204,4 +4208,9 @@ private void reportError(ParseTree parseTree, @FormatString String message, Obje private void reportError(@FormatString String message, Object... arguments) { errorReporter.reportError(scanner.getPosition(), message, arguments); } + + private Parser recordFeatureUsed(Feature feature) { + features = features.with(feature); + return this; + } } diff --git a/test/com/google/javascript/jscomp/parsing/ParserTest.java b/test/com/google/javascript/jscomp/parsing/ParserTest.java index cced4a2debd..0a128747676 100644 --- a/test/com/google/javascript/jscomp/parsing/ParserTest.java +++ b/test/com/google/javascript/jscomp/parsing/ParserTest.java @@ -2799,44 +2799,117 @@ public void testInvalidOldStyleOctalLiterals() { "Invalid octal digit in octal literal."); } - public void testGetter() { + public void testGetter_ObjectLiteral_Es3() { expectFeatures(Feature.GETTER); mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; - parseError("var x = {get 1(){}};", - IRFactory.GETTER_ERROR_MESSAGE); - parseError("var x = {get 'a'(){}};", - IRFactory.GETTER_ERROR_MESSAGE); - parseError("var x = {get a(){}};", - IRFactory.GETTER_ERROR_MESSAGE); + + parseError("var x = {get 1(){}};", IRFactory.GETTER_ERROR_MESSAGE); + parseError("var x = {get 'a'(){}};", IRFactory.GETTER_ERROR_MESSAGE); + parseError("var x = {get a(){}};", IRFactory.GETTER_ERROR_MESSAGE); + mode = LanguageMode.ECMASCRIPT5; + parse("var x = {get 1(){}};"); + parse("var x = {get 'a'(){}};"); + parse("var x = {get a(){}};"); + } + + public void testGetter_ObjectLiteral_Es5() { + expectFeatures(Feature.GETTER); mode = LanguageMode.ECMASCRIPT5; + strictMode = SLOPPY; + parse("var x = {get 1(){}};"); parse("var x = {get 'a'(){}};"); parse("var x = {get a(){}};"); + } + public void testGetterInvalid_ObjectLiteral_EsNext() { expectFeatures(); + mode = LanguageMode.ES_NEXT; + strictMode = SLOPPY; + parseError("var x = {get a(b){}};", "')' expected"); } - public void testSetter() { + public void testGetter_Computed_ObjectLiteral_Es6() { + expectFeatures(Feature.GETTER, Feature.COMPUTED_PROPERTIES); + mode = LanguageMode.ECMASCRIPT6; + strictMode = SLOPPY; + + parse("var x = {get [1](){}};"); + parse("var x = {get ['a'](){}};"); + parse("var x = {get [a](){}};"); + } + + public void testGetterInvalid_Computed_ObjectLiteral_EsNext() { + expectFeatures(); + mode = LanguageMode.ES_NEXT; + strictMode = SLOPPY; + + parseError("var x = {get [a](b){}};", "')' expected"); + } + + public void testGetter_ClassSyntax() { + expectFeatures(Feature.CLASSES, Feature.GETTER); + mode = LanguageMode.ECMASCRIPT6; + strictMode = SLOPPY; + + parse("class Foo { get 1() {} };"); + parse("class Foo { get 'a'() {} };"); + parse("class Foo { get a() {} };"); + } + + public void testGetterInvalid_ClassSyntax_EsNext() { + expectFeatures(); + mode = LanguageMode.ES_NEXT; + strictMode = SLOPPY; + + parseError("class Foo { get a(b) {} };", "')' expected"); + } + + public void testGetter_Computed_ClassSyntax() { + expectFeatures(Feature.CLASSES, Feature.GETTER, Feature.COMPUTED_PROPERTIES); + mode = LanguageMode.ECMASCRIPT6; + strictMode = SLOPPY; + + parse("class Foo { get [1]() {} };"); + parse("class Foo { get ['a']() {} };"); + parse("class Foo { get [a]() {} };"); + } + + public void testGetterInvalid_Computed_ClassSyntax_EsNext() { + expectFeatures(); + mode = LanguageMode.ES_NEXT; + strictMode = SLOPPY; + + parseError("class Foo { get [a](b) {} };", "')' expected"); + } + + public void testSetter_ObjectLiteral_Es3() { expectFeatures(Feature.SETTER); mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; - parseError("var x = {set 1(x){}};", - IRFactory.SETTER_ERROR_MESSAGE); - parseError("var x = {set 'a'(x){}};", - IRFactory.SETTER_ERROR_MESSAGE); - parseError("var x = {set a(x){}};", - IRFactory.SETTER_ERROR_MESSAGE); + parseError("var x = {set 1(x){}};", IRFactory.SETTER_ERROR_MESSAGE); + parseError("var x = {set 'a'(x){}};", IRFactory.SETTER_ERROR_MESSAGE); + parseError("var x = {set a(x){}};", IRFactory.SETTER_ERROR_MESSAGE); + } + public void testSetter_ObjectLiteral_Es5() { + expectFeatures(Feature.SETTER); mode = LanguageMode.ECMASCRIPT5; + strictMode = SLOPPY; parse("var x = {set 1(x){}};"); parse("var x = {set 'a'(x){}};"); parse("var x = {set a(x){}};"); + } - mode = LanguageMode.ECMASCRIPT6; // We only cover some of the common permutations though. + // We only cover some of the common permutations though. + public void testSetter_ObjectLiteral_Es6() { + expectFeatures(Feature.SETTER); + mode = LanguageMode.ECMASCRIPT6; + strictMode = SLOPPY; parse("var x = {set 1(x){}};"); parse("var x = {set 'a'(x){}};"); @@ -2854,19 +2927,23 @@ public void testSetter() { parse("var x = {set setter({x, y, z}) {}};"); parse("var x = {set setter({x, y, z} = {x: 1, y: 2, z: 3}) {}};"); parse("var x = {set setter({x = 1, y = 2, z = 3}) {}};"); + } - expectFeatures(); // Because these snippets don't contain valid setters. + public void testSetterInvalid_ObjectLiteral_EsNext() { + expectFeatures(); + mode = LanguageMode.ES_NEXT; + strictMode = SLOPPY; parseError("var x = {set a() {}};", "Setter must have exactly 1 parameter, found 0"); parseError("var x = {set a(x, y) {}};", "Setter must have exactly 1 parameter, found 2"); parseError("var x = {set a(...x, y) {}};", "Setter must have exactly 1 parameter, found 2"); - parseError("var x = {set a(...x) {}};", "Setter must not have a rest parameter"); } - public void testComputedSetter() { - expectFeatures(Feature.COMPUTED_PROPERTIES); // Because computed setters aren't setters. - mode = LanguageMode.ECMASCRIPT6; // We only cover some of the common permutations though. + // We only cover some of the common permutations though. + public void testSetter_Computed_ObjectLiteral_Es6() { + expectFeatures(Feature.SETTER, Feature.COMPUTED_PROPERTIES); + mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("var x = {set [setter](x = 5) {}};"); @@ -2881,17 +2958,82 @@ public void testComputedSetter() { parse("var x = {set [setter]({x, y, z}) {}};"); parse("var x = {set [setter]({x, y, z} = {x: 1, y: 2, z: 3}) {}};"); parse("var x = {set [setter]({x = 1, y = 2, z = 3}) {}};"); + } - expectFeatures(); // Because these snippets don't contain valid computed properties. + // We only cover some of the common permutations though. + public void testSetterInvalid_Computed_ObjectLiteral_EsNext() { + expectFeatures(); + mode = LanguageMode.ES_NEXT; + strictMode = SLOPPY; parseError("var x = {set [setter]() {}};", "Setter must have exactly 1 parameter, found 0"); parseError("var x = {set [setter](x, y) {}};", "Setter must have exactly 1 parameter, found 2"); parseError( "var x = {set [setter](...x, y) {}};", "Setter must have exactly 1 parameter, found 2"); - parseError("var x = {set [setter](...x) {}};", "Setter must not have a rest parameter"); } + public void testSetter_ClassSyntax() { + expectFeatures(Feature.CLASSES, Feature.SETTER); + mode = LanguageMode.ECMASCRIPT6; // We only cover some of the common permutations though. + + parse("class Foo { set setter(x = 5) {} };"); + parse("class Foo { set setter(x = a) {} };"); + parse("class Foo { set setter(x = a + 5) {} };"); + + parse("class Foo { set setter([x, y, z]) {} };"); + parse("class Foo { set setter([x, y, ...z]) {}};"); + parse("class Foo { set setter([x, y, z] = [1, 2, 3]) {} };"); + parse("class Foo { set setter([x = 1, y = 2, z = 3]) {} };"); + + parse("class Foo { set setter({x, y, z}) {}};"); + parse("class Foo { set setter({x, y, z} = {x: 1, y: 2, z: 3}) {} };"); + parse("class Foo { set setter({x = 1, y = 2, z = 3}) {} };"); + } + + public void testSetterInvalid_ClassSyntax_EsNext() { + expectFeatures(); + mode = LanguageMode.ES_NEXT; + + parseError("class Foo { set setter() {} };", "Setter must have exactly 1 parameter, found 0"); + parseError( + "class Foo { set setter(x, y) {} };", "Setter must have exactly 1 parameter, found 2"); + parseError( + "class Foo { set setter(...x, y) {} };", "Setter must have exactly 1 parameter, found 2"); + parseError("class Foo { set setter(...x) {} };", "Setter must not have a rest parameter"); + } + + // We only cover some of the common permutations though. + public void testSetter_Computed_ClassSyntax() { + expectFeatures(Feature.CLASSES, Feature.SETTER, Feature.COMPUTED_PROPERTIES); + mode = LanguageMode.ECMASCRIPT6; + + parse("class Foo { set [setter](x = 5) {} };"); + parse("class Foo { set [setter](x = a) {} };"); + parse("class Foo { set [setter](x = a + 5) {} };"); + + parse("class Foo { set [setter]([x, y, z]) {} };"); + parse("class Foo { set [setter]([x, y, ...z]) {}};"); + parse("class Foo { set [setter]([x, y, z] = [1, 2, 3]) {} };"); + parse("class Foo { set [setter]([x = 1, y = 2, z = 3]) {} };"); + + parse("class Foo { set [setter]({x, y, z}) {}};"); + parse("class Foo { set [setter]({x, y, z} = {x: 1, y: 2, z: 3}) {} };"); + parse("class Foo { set [setter]({x = 1, y = 2, z = 3}) {} };"); + } + + public void testSetterInvalid_Computed_ClassSyntax_EsNext() { + expectFeatures(); + mode = LanguageMode.ES_NEXT; + + parseError("class Foo { set [setter]() {} };", "Setter must have exactly 1 parameter, found 0"); + parseError( + "class Foo { set [setter](x, y) {} };", "Setter must have exactly 1 parameter, found 2"); + parseError( + "class Foo { set [setter](...x, y) {} };", "Setter must have exactly 1 parameter, found 2"); + parseError("class Foo { set [setter](...x) {} };", "Setter must not have a rest parameter"); + } + public void testLamestWarningEver() { // This used to be a warning. parse("var x = /** @type {undefined} */ (y);"); @@ -3001,25 +3143,6 @@ public void testTypeScriptKeywords() { parse("while (i--) { module = module[i]; }"); } - public void testGettersES3() { - mode = LanguageMode.ECMASCRIPT3; - strictMode = SLOPPY; - - parseError("var x = {get x(){} };", IRFactory.GETTER_ERROR_MESSAGE); - parseError("var x = {get function(){} };", IRFactory.GETTER_ERROR_MESSAGE); - parseError("var x = {get 'function'(){} };", IRFactory.GETTER_ERROR_MESSAGE); - parseError("var x = {get 1(){} };", IRFactory.GETTER_ERROR_MESSAGE); - } - - public void testSettersES3() { - mode = LanguageMode.ECMASCRIPT3; - strictMode = SLOPPY; - - parseError("var x = {set function(a){} };", IRFactory.SETTER_ERROR_MESSAGE); - parseError("var x = {set 'function'(a){} };", IRFactory.SETTER_ERROR_MESSAGE); - parseError("var x = {set 1(a){} };", IRFactory.SETTER_ERROR_MESSAGE); - } - public void testKeywordsAsProperties1() { expectFeatures(Feature.KEYWORDS_AS_PROPERTIES); mode = LanguageMode.ECMASCRIPT3;