/* * Copyright 2014 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.parsing; import static com.google.common.truth.Truth.assertThat; import static com.google.javascript.jscomp.parsing.Config.StrictMode.SLOPPY; import static com.google.javascript.jscomp.parsing.Config.StrictMode.STRICT; import static com.google.javascript.jscomp.parsing.JsDocInfoParser.BAD_TYPE_WIKI_LINK; import static com.google.javascript.jscomp.testing.NodeSubject.assertNode; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.debugging.sourcemap.SourceMapConsumerV3; import com.google.javascript.jscomp.SourceFile; import com.google.javascript.jscomp.SourceMapInput; import com.google.javascript.jscomp.parsing.Config.LanguageMode; import com.google.javascript.jscomp.parsing.ParserRunner.ParseResult; import com.google.javascript.jscomp.parsing.parser.FeatureSet; import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.SimpleSourceFile; import com.google.javascript.rhino.StaticSourceFile; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.testing.BaseJSTypeTestCase; import com.google.javascript.rhino.testing.TestErrorReporter; import java.util.List; public final class ParserTest extends BaseJSTypeTestCase { private static final String SUSPICIOUS_COMMENT_WARNING = IRFactory.SUSPICIOUS_COMMENT_WARNING; private static final String TRAILING_COMMA_MESSAGE = "Trailing comma is not legal in an ECMA-262 object initializer"; private static final String MISSING_GT_MESSAGE = "Bad type annotation. missing closing >" + BAD_TYPE_WIKI_LINK; private static final String UNLABELED_BREAK = "unlabelled break must be inside loop or switch"; private static final String UNEXPECTED_CONTINUE = "continue must be inside loop"; private static final String UNEXPECTED_RETURN = "return must be inside function"; private static final String UNEXPECTED_LABELED_CONTINUE = "continue can only use labeles of iteration statements"; private static final String UNDEFINED_LABEL = "undefined label"; private static final String HTML_COMMENT_WARNING = "In some cases, '<!--' " + "and '-->' are treated as a '//' " + "for legacy reasons. Removing this from your code is " + "safe for all browsers currently in use."; private static final String INVALID_ASSIGNMENT_TARGET = "invalid assignment target"; private static final String SEMICOLON_EXPECTED = "Semi-colon expected"; private Config.LanguageMode mode; private Config.StrictMode strictMode; private boolean isIdeMode = false; private FeatureSet expectedFeatures; @Override protected void setUp() throws Exception { super.setUp(); mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; isIdeMode = false; expectedFeatures = FeatureSet.ES3; } public void testExponentOperator() { mode = LanguageMode.ECMASCRIPT7; strictMode = STRICT; expectFeatures(Feature.EXPONENT_OP); parse("x**y"); // Parentheses are required for disambiguation when a unary expression is desired as // the left operand. parse("-(x**y)"); parse("(-x)**y"); parseError("-x**y", "Unary operator '-' requires parentheses before '**'"); // Parens are not required for unary operator on the right operand parse("x**-y"); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.EXPONENT_OP); parseWarning( "x**y", requiresLanguageModeMessage(LanguageMode.ECMASCRIPT7, Feature.EXPONENT_OP)); } public void testExponentAssignmentOperator() { mode = LanguageMode.ECMASCRIPT7; strictMode = STRICT; expectFeatures(Feature.EXPONENT_OP); parse("x**=y;"); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseWarning( "x**=y;", requiresLanguageModeMessage(LanguageMode.ECMASCRIPT7, Feature.EXPONENT_OP)); } public void testFunction() { parse("var f = function(x,y,z) { return 0; }"); parse("function f(x,y,z) { return 0; }"); parseError("var f = function(x,y,z,) {}", "Invalid trailing comma in formal parameter list"); parseError("function f(x,y,z,) {}", "Invalid trailing comma in formal parameter list"); } public void testWhile() { parse("while(1) { break; }"); } public void testNestedWhile() { parse("while(1) { while(1) { break; } }"); } public void testBreak() { parseError("break;", UNLABELED_BREAK); } public void testContinue() { parseError("continue;", UNEXPECTED_CONTINUE); } public void testBreakCrossFunction() { parseError("while(1) { function f() { break; } }", UNLABELED_BREAK); } public void testBreakCrossFunctionInFor() { parseError("while(1) {for(var f = function () { break; };;) {}}", UNLABELED_BREAK); } public void testBreakInForOf() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.FOR_OF); parse("" + "for (var x of [1, 2, 3]) {\n" + " if (x == 2) break;\n" + "}"); } public void testContinueToSwitch() { parseError("switch(1) {case(1): continue; }", UNEXPECTED_CONTINUE); } public void testContinueToSwitchWithNoCases() { parse("switch(1){}"); } public void testContinueToSwitchWithTwoCases() { parseError("switch(1){case(1):break;case(2):continue;}", UNEXPECTED_CONTINUE); } public void testContinueToSwitchWithDefault() { parseError("switch(1){case(1):break;case(2):default:continue;}", UNEXPECTED_CONTINUE); } public void testContinueToLabelSwitch() { parseError( "while(1) {a: switch(1) {case(1): continue a; }}", UNEXPECTED_LABELED_CONTINUE); } public void testContinueOutsideSwitch() { parse("b: while(1) { a: switch(1) { case(1): continue b; } }"); } public void testContinueNotCrossFunction1() { parse("a:switch(1){case(1):function f(){a:while(1){continue a;}}}"); } public void testContinueNotCrossFunction2() { parseError( "a:switch(1){case(1):function f(){while(1){continue a;}}}", UNDEFINED_LABEL + " \"a\""); } public void testContinueInForOf() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.FOR_OF); parse("" + "for (var x of [1, 2, 3]) {\n" + " if (x == 2) continue;\n" + "}"); } /** @bug 19100575 */ public void testVarSourceLocations() { isIdeMode = true; Node n = parse("var x, y = 1;"); Node var = n.getFirstChild(); assertNode(var).hasType(Token.VAR); Node x = var.getFirstChild(); assertNode(x).hasType(Token.NAME); assertNode(x).hasCharno("var ".length()); Node y = x.getNext(); assertNode(y).hasType(Token.NAME); assertNode(y).hasCharno("var x, ".length()); assertNode(y).hasLength("y = 1".length()); } public void testSourceLocationsNonAscii() { Node n = parse("'안녕세계!'"); Node exprResult = n.getFirstChild(); Node string = exprResult.getFirstChild(); assertNode(string).hasType(Token.STRING); assertNode(string).hasLength(7); // 2 quotes, plus 5 characters } public void testReturn() { parse("function foo() { return 1; }"); parseError("return;", UNEXPECTED_RETURN); parseError("return 1;", UNEXPECTED_RETURN); } public void testThrow() { parse("throw Error();"); parse("throw new Error();"); parse("throw '';"); parseError("throw;", "semicolon/newline not allowed after 'throw'"); parseError("throw\nError();", "semicolon/newline not allowed after 'throw'"); } public void testLabel1() { parse("foo:bar"); } public void testLabel2() { parse("{foo:bar}"); } public void testLabel3() { parse("foo:bar:baz"); } public void testDuplicateLabelWithoutBraces() { parseError("foo:foo:bar", "Duplicate label \"foo\""); } public void testDuplicateLabelWithBraces() { parseError("foo:{bar;foo:baz}", "Duplicate label \"foo\""); } public void testDuplicateLabelWithFor() { parseError("foo:for(;;){foo:bar}", "Duplicate label \"foo\""); } public void testNonDuplicateLabelSiblings() { parse("foo:1;foo:2"); } public void testNonDuplicateLabelCrossFunction() { parse("foo:(function(){foo:2})"); } public void testLinenoCharnoAssign1() throws Exception { Node assign = parse("a = b").getFirstFirstChild(); assertNode(assign).hasType(Token.ASSIGN); assertNode(assign).hasLineno(1); assertNode(assign).hasCharno(0); } public void testLinenoCharnoAssign2() throws Exception { Node assign = parse("\n a.g.h.k = 45").getFirstFirstChild(); assertNode(assign).hasType(Token.ASSIGN); assertNode(assign).hasLineno(2); assertNode(assign).hasCharno(1); } public void testLinenoCharnoCall() throws Exception { Node call = parse("\n foo(123);").getFirstFirstChild(); assertNode(call).hasType(Token.CALL); assertNode(call).hasLineno(2); assertNode(call).hasCharno(1); } public void testLinenoCharnoGetProp1() throws Exception { Node getprop = parse("\n foo.bar").getFirstFirstChild(); assertNode(getprop).hasType(Token.GETPROP); assertNode(getprop).hasLineno(2); assertNode(getprop).hasCharno(1); Node name = getprop.getSecondChild(); assertNode(name).hasType(Token.STRING); assertNode(name).hasLineno(2); assertNode(name).hasCharno(5); } public void testLinenoCharnoGetProp2() throws Exception { Node getprop = parse("\n foo.\nbar").getFirstFirstChild(); assertNode(getprop).hasType(Token.GETPROP); assertNode(getprop).hasLineno(2); assertNode(getprop).hasCharno(1); Node name = getprop.getSecondChild(); assertNode(name).hasType(Token.STRING); assertNode(name).hasLineno(3); assertNode(name).hasCharno(0); } public void testLinenoCharnoGetelem1() throws Exception { Node call = parse("\n foo[123]").getFirstFirstChild(); assertNode(call).hasType(Token.GETELEM); assertNode(call).hasLineno(2); assertNode(call).hasCharno(1); } public void testLinenoCharnoGetelem2() throws Exception { Node call = parse("\n \n foo()[123]").getFirstFirstChild(); assertNode(call).hasType(Token.GETELEM); assertNode(call).hasLineno(3); assertNode(call).hasCharno(1); } public void testLinenoCharnoGetelem3() throws Exception { Node call = parse("\n \n (8 + kl)[123]").getFirstFirstChild(); assertNode(call).hasType(Token.GETELEM); assertNode(call).hasLineno(3); assertNode(call).hasCharno(1); } public void testLinenoCharnoForComparison() throws Exception { Node lt = parse("for (; i < j;){}").getFirstChild().getSecondChild(); assertNode(lt).hasType(Token.LT); assertNode(lt).hasLineno(1); assertNode(lt).hasCharno(7); } public void testLinenoCharnoHook() throws Exception { Node n = parse("\n a ? 9 : 0").getFirstFirstChild(); assertNode(n).hasType(Token.HOOK); assertNode(n).hasLineno(2); assertNode(n).hasCharno(1); } public void testLinenoCharnoArrayLiteral() throws Exception { Node n = parse("\n [8, 9]").getFirstFirstChild(); assertNode(n).hasType(Token.ARRAYLIT); assertNode(n).hasLineno(2); assertNode(n).hasCharno(2); n = n.getFirstChild(); assertNode(n).hasType(Token.NUMBER); assertNode(n).hasLineno(2); assertNode(n).hasCharno(3); n = n.getNext(); assertNode(n).hasType(Token.NUMBER); assertNode(n).hasLineno(2); assertNode(n).hasCharno(6); } public void testLinenoCharnoObjectLiteral() throws Exception { Node n = parse("\n\n var a = {a:0\n,b :1};") .getFirstFirstChild().getFirstChild(); assertNode(n).hasType(Token.OBJECTLIT); assertNode(n).hasLineno(3); assertNode(n).hasCharno(9); Node key = n.getFirstChild(); assertNode(key).hasType(Token.STRING_KEY); assertNode(key).hasLineno(3); assertNode(key).hasCharno(10); Node value = key.getFirstChild(); assertNode(value).hasType(Token.NUMBER); assertNode(value).hasLineno(3); assertNode(value).hasCharno(12); key = key.getNext(); assertNode(key).hasType(Token.STRING_KEY); assertNode(key).hasLineno(4); assertNode(key).hasCharno(1); value = key.getFirstChild(); assertNode(value).hasType(Token.NUMBER); assertNode(value).hasLineno(4); assertNode(value).hasCharno(4); } public void testLinenoCharnoAdd() throws Exception { testLinenoCharnoBinop("+"); } public void testLinenoCharnoSub() throws Exception { testLinenoCharnoBinop("-"); } public void testLinenoCharnoMul() throws Exception { testLinenoCharnoBinop("*"); } public void testLinenoCharnoDiv() throws Exception { testLinenoCharnoBinop("/"); } public void testLinenoCharnoMod() throws Exception { testLinenoCharnoBinop("%"); } public void testLinenoCharnoShift() throws Exception { testLinenoCharnoBinop("<<"); } public void testLinenoCharnoBinaryAnd() throws Exception { testLinenoCharnoBinop("&"); } public void testLinenoCharnoAnd() throws Exception { testLinenoCharnoBinop("&&"); } public void testLinenoCharnoBinaryOr() throws Exception { testLinenoCharnoBinop("|"); } public void testLinenoCharnoOr() throws Exception { testLinenoCharnoBinop("||"); } public void testLinenoCharnoLt() throws Exception { testLinenoCharnoBinop("<"); } public void testLinenoCharnoLe() throws Exception { testLinenoCharnoBinop("<="); } public void testLinenoCharnoGt() throws Exception { testLinenoCharnoBinop(">"); } public void testLinenoCharnoGe() throws Exception { testLinenoCharnoBinop(">="); } private void testLinenoCharnoBinop(String binop) { Node op = parse("var a = 89 " + binop + " 76;").getFirstChild(). getFirstFirstChild(); assertNode(op).hasLineno(1); assertNode(op).hasCharno(8); } public void testJSDocAttachment1() { Node varNode = parse("/** @type {number} */var a;").getFirstChild(); // VAR assertNode(varNode).hasType(Token.VAR); JSDocInfo varInfo = varNode.getJSDocInfo(); assertThat(varInfo).isNotNull(); assertTypeEquals(NUMBER_TYPE, varInfo.getType()); // VAR NAME Node varNameNode = varNode.getFirstChild(); assertNode(varNameNode).hasType(Token.NAME); assertThat(varNameNode.getJSDocInfo()).isNull(); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.LET_DECLARATIONS); Node letNode = parse("/** @type {number} */let a;").getFirstChild(); // LET assertNode(letNode).hasType(Token.LET); JSDocInfo letInfo = letNode.getJSDocInfo(); assertThat(letInfo).isNotNull(); assertTypeEquals(NUMBER_TYPE, letInfo.getType()); // LET NAME Node letNameNode = letNode.getFirstChild(); assertNode(letNameNode).hasType(Token.NAME); assertThat(letNameNode.getJSDocInfo()).isNull(); expectFeatures(Feature.CONST_DECLARATIONS); Node constNode = parse("/** @type {number} */const a = 0;").getFirstChild(); // CONST assertNode(constNode).hasType(Token.CONST); JSDocInfo constInfo = constNode.getJSDocInfo(); assertThat(constInfo).isNotNull(); assertTypeEquals(NUMBER_TYPE, constInfo.getType()); // CONST NAME Node constNameNode = constNode.getFirstChild(); assertNode(constNameNode).hasType(Token.NAME); assertThat(constNameNode.getJSDocInfo()).isNull(); } public void testJSDocAttachment2() { Node varNode = parse("/** @type {number} */var a,b;").getFirstChild(); // VAR assertNode(varNode).hasType(Token.VAR); JSDocInfo info = varNode.getJSDocInfo(); assertThat(info).isNotNull(); assertTypeEquals(NUMBER_TYPE, info.getType()); // First NAME Node nameNode1 = varNode.getFirstChild(); assertNode(nameNode1).hasType(Token.NAME); assertThat(nameNode1.getJSDocInfo()).isNull(); // Second NAME Node nameNode2 = nameNode1.getNext(); assertNode(nameNode2).hasType(Token.NAME); assertThat(nameNode2.getJSDocInfo()).isNull(); } public void testJSDocAttachment3() { Node assignNode = parse("/** @type {number} */goog.FOO = 5;").getFirstFirstChild(); assertNode(assignNode).hasType(Token.ASSIGN); JSDocInfo info = assignNode.getJSDocInfo(); assertThat(info).isNotNull(); assertTypeEquals(NUMBER_TYPE, info.getType()); } public void testJSDocAttachment4() { Node varNode = parse( "var a, /** @define {number} */ b = 5;").getFirstChild(); // ASSIGN assertNode(varNode).hasType(Token.VAR); assertThat(varNode.getJSDocInfo()).isNull(); // a Node a = varNode.getFirstChild(); assertThat(a.getJSDocInfo()).isNull(); // b Node b = a.getNext(); JSDocInfo info = b.getJSDocInfo(); assertThat(info).isNotNull(); assertThat(info.isDefine()).isTrue(); assertTypeEquals(NUMBER_TYPE, info.getType()); } public void testJSDocAttachment5() { Node varNode = parse("var /** @type {number} */a, /** @define {number} */b = 5;").getFirstChild(); // ASSIGN assertNode(varNode).hasType(Token.VAR); assertThat(varNode.getJSDocInfo()).isNull(); // a Node a = varNode.getFirstChild(); assertThat(a.getJSDocInfo()).isNotNull(); JSDocInfo info = a.getJSDocInfo(); assertThat(info).isNotNull(); assertThat(info.isDefine()).isFalse(); assertTypeEquals(NUMBER_TYPE, info.getType()); // b Node b = a.getNext(); info = b.getJSDocInfo(); assertThat(info).isNotNull(); assertThat(info.isDefine()).isTrue(); assertTypeEquals(NUMBER_TYPE, info.getType()); } /** * Tests that a JSDoc comment in an unexpected place of the code does not * propagate to following code due to {@link JSDocInfo} aggregation. */ public void testJSDocAttachment6() throws Exception { Node functionNode = parse( "var a = /** @param {number} index */5;" + "/** @return {boolean} */function f(index){}") .getSecondChild(); assertNode(functionNode).hasType(Token.FUNCTION); JSDocInfo info = functionNode.getJSDocInfo(); assertThat(info).isNotNull(); assertThat(info.hasParameter("index")).isFalse(); assertThat(info.hasReturnType()).isTrue(); assertTypeEquals(BOOLEAN_TYPE, info.getReturnType()); } public void testJSDocAttachment7() { Node varNode = parse("/** */var a;").getFirstChild(); // VAR assertNode(varNode).hasType(Token.VAR); // NAME Node nameNode = varNode.getFirstChild(); assertNode(nameNode).hasType(Token.NAME); assertThat(nameNode.getJSDocInfo()).isNull(); } public void testJSDocAttachment8() { Node varNode = parse("/** x */var a;").getFirstChild(); // VAR assertNode(varNode).hasType(Token.VAR); // NAME Node nameNode = varNode.getFirstChild(); assertNode(nameNode).hasType(Token.NAME); assertThat(nameNode.getJSDocInfo()).isNull(); } public void testJSDocAttachment9() { Node varNode = parse("/** \n x */var a;").getFirstChild(); // VAR assertNode(varNode).hasType(Token.VAR); // NAME Node nameNode = varNode.getFirstChild(); assertNode(nameNode).hasType(Token.NAME); assertThat(nameNode.getJSDocInfo()).isNull(); } public void testJSDocAttachment10() { Node varNode = parse("/** x\n */var a;").getFirstChild(); // VAR assertNode(varNode).hasType(Token.VAR); // NAME Node nameNode = varNode.getFirstChild(); assertNode(nameNode).hasType(Token.NAME); assertThat(nameNode.getJSDocInfo()).isNull(); } public void testJSDocAttachment11() { Node varNode = parse("/** @type {{x : number, 'y' : string, z}} */var a;") .getFirstChild(); // VAR assertNode(varNode).hasType(Token.VAR); JSDocInfo info = varNode.getJSDocInfo(); assertThat(info).isNotNull(); assertTypeEquals( createRecordTypeBuilder() .addProperty("x", NUMBER_TYPE, null) .addProperty("y", STRING_TYPE, null) .addProperty("z", UNKNOWN_TYPE, null) .build(), info.getType()); // NAME Node nameNode = varNode.getFirstChild(); assertNode(nameNode).hasType(Token.NAME); assertThat(nameNode.getJSDocInfo()).isNull(); } public void testJSDocAttachment12() { Node varNode = parse("var a = {/** @type {Object} */ b: c};") .getFirstChild(); Node objectLitNode = varNode.getFirstFirstChild(); assertNode(objectLitNode).hasType(Token.OBJECTLIT); assertThat(objectLitNode.getFirstChild().getJSDocInfo()).isNotNull(); } public void testJSDocAttachment13() { Node varNode = parse("/** foo */ var a;").getFirstChild(); assertThat(varNode.getJSDocInfo()).isNotNull(); } public void testJSDocAttachment14() { Node varNode = parse("/** */ var a;").getFirstChild(); assertThat(varNode.getJSDocInfo()).isNull(); } public void testJSDocAttachment15() { Node varNode = parse("/** \n * \n */ var a;").getFirstChild(); assertThat(varNode.getJSDocInfo()).isNull(); } public void testJSDocAttachment16() { Node exprCall = parse("/** @private */ x(); function f() {};").getFirstChild(); assertNode(exprCall).hasType(Token.EXPR_RESULT); assertThat(exprCall.getNext().getJSDocInfo()).isNull(); assertThat(exprCall.getFirstChild().getJSDocInfo()).isNotNull(); } public void testJSDocAttachment17() { Node fn = parse( "function f() { " + " return /** @type {string} */ (g(1 /** @desc x */));" + "};").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); Node cast = fn.getLastChild().getFirstFirstChild(); assertNode(cast).hasType(Token.CAST); } public void testJSDocAttachment18() { Node fn = parse( "function f() { " + " var x = /** @type {string} */ (y);" + "};").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); Node cast = fn.getLastChild().getFirstFirstChild().getFirstChild(); assertNode(cast).hasType(Token.CAST); } public void testJSDocAttachment19() { Node fn = parse( "function f() { " + " /** @type {string} */" + " return;" + "};").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); Node ret = fn.getLastChild().getFirstChild(); assertNode(ret).hasType(Token.RETURN); assertThat(ret.getJSDocInfo()).isNotNull(); } public void testJSDocAttachment20() { Node fn = parse( "function f() { " + " /** @type {string} */" + " if (true) return;" + "};").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); Node ret = fn.getLastChild().getFirstChild(); assertNode(ret).hasType(Token.IF); assertThat(ret.getJSDocInfo()).isNotNull(); } public void testJSDocAttachment21() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.CONST_DECLARATIONS); parse("/** @param {string} x */ const f = function() {};"); expectFeatures(Feature.LET_DECLARATIONS); parse("/** @param {string} x */ let f = function() {};"); } // Tests that JSDoc gets attached to the children of export nodes, and there are no warnings. // See https://github.com/google/closure-compiler/issues/781 public void testJSDocAttachment22() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.MODULES); Node n = parse("/** @param {string} x */ export function f(x) {};"); Node export = n.getFirstChild(); assertNode(export).hasType(Token.EXPORT); assertThat(export.getJSDocInfo()).isNull(); assertThat(export.getFirstChild().getJSDocInfo()).isNotNull(); assertThat(export.getFirstChild().getJSDocInfo().hasParameter("x")).isTrue(); } public void testInlineJSDocAttachment1() { Node fn = parse("function f(/** string */ x) {}").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); JSDocInfo info = fn.getSecondChild().getFirstChild().getJSDocInfo(); assertThat(info).isNotNull(); assertTypeEquals(STRING_TYPE, info.getType()); } public void testInlineJSDocAttachment2() { Node fn = parse( "function f(/** ? */ x) {}").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); JSDocInfo info = fn.getSecondChild().getFirstChild().getJSDocInfo(); assertThat(info).isNotNull(); assertTypeEquals(UNKNOWN_TYPE, info.getType()); } public void testInlineJSDocAttachment3() { parse("function f(/** @type {string} */ x) {}"); } public void testInlineJSDocAttachment4() { parse( "function f(/**\n" + " * @type {string}\n" + " */ x) {}"); } public void testInlineJSDocAttachment5() { Node vardecl = parse("var /** string */ x = 'asdf';").getFirstChild(); JSDocInfo info = vardecl.getFirstChild().getJSDocInfo(); assertThat(info).isNotNull(); assertThat(info.hasType()).isTrue(); assertTypeEquals(STRING_TYPE, info.getType()); } public void testInlineJSDocAttachment6() { Node fn = parse("function f(/** {attr: number} */ x) {}").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); JSDocInfo info = fn.getSecondChild().getFirstChild().getJSDocInfo(); assertThat(info).isNotNull(); assertTypeEquals( createRecordTypeBuilder().addProperty("attr", NUMBER_TYPE, null).build(), info.getType()); } public void testInlineJSDocWithOptionalType() { Node fn = parse("function f(/** string= */ x) {}").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); JSDocInfo info = fn.getSecondChild().getFirstChild().getJSDocInfo(); assertThat(info.getType().isOptionalArg()).isTrue(); } public void testInlineJSDocWithVarArgs() { Node fn = parse("function f(/** ...string */ x) {}").getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); JSDocInfo info = fn.getSecondChild().getFirstChild().getJSDocInfo(); assertThat(info.getType().isVarArgs()).isTrue(); } public void testIncorrectJSDocDoesNotAlterJSParsing1() throws Exception { assertNodeEquality( parse("var a = [1,2]"), parseWarning("/** @type {Array<number} */var a = [1,2]", MISSING_GT_MESSAGE)); } public void testIncorrectJSDocDoesNotAlterJSParsing2() throws Exception { assertNodeEquality( parse("var a = [1,2]"), parseWarning("/** @type {Array.<number}*/var a = [1,2]", MISSING_GT_MESSAGE)); } public void testIncorrectJSDocDoesNotAlterJSParsing3() throws Exception { assertNodeEquality( parse("C.prototype.say=function(nums) {alert(nums.join(','));};"), parseWarning("/** @param {Array.<number} nums */" + "C.prototype.say=function(nums) {alert(nums.join(','));};", MISSING_GT_MESSAGE)); } public void testIncorrectJSDocDoesNotAlterJSParsing4() throws Exception { assertNodeEquality( parse("C.prototype.say=function(nums) {alert(nums.join(','));};"), parse("/** @return {boolean} */" + "C.prototype.say=function(nums) {alert(nums.join(','));};")); } public void testIncorrectJSDocDoesNotAlterJSParsing5() throws Exception { assertNodeEquality( parse("C.prototype.say=function(nums) {alert(nums.join(','));};"), parse("/** @param {boolean} this is some string*/" + "C.prototype.say=function(nums) {alert(nums.join(','));};")); } public void testIncorrectJSDocDoesNotAlterJSParsing6() throws Exception { assertNodeEquality( parse("C.prototype.say=function(nums) {alert(nums.join(','));};"), parseWarning( "/** @param {bool!*%E$} */" + "C.prototype.say=function(nums) {alert(nums.join(','));};", "Bad type annotation. expected closing }" + BAD_TYPE_WIKI_LINK, "Bad type annotation. expecting a variable name in a @param tag." + BAD_TYPE_WIKI_LINK)); } public void testIncorrectJSDocDoesNotAlterJSParsing7() throws Exception { isIdeMode = true; assertNodeEquality( parse("C.prototype.say=function(nums) {alert(nums.join(','));};"), parseWarning("/** @see */" + "C.prototype.say=function(nums) {alert(nums.join(','));};", "@see tag missing description")); } public void testIncorrectJSDocDoesNotAlterJSParsing8() throws Exception { isIdeMode = true; assertNodeEquality( parse("C.prototype.say=function(nums) {alert(nums.join(','));};"), parseWarning("/** @author */" + "C.prototype.say=function(nums) {alert(nums.join(','));};", "@author tag missing author")); } public void testIncorrectJSDocDoesNotAlterJSParsing9() throws Exception { assertNodeEquality( parse("C.prototype.say=function(nums) {alert(nums.join(','));};"), parseWarning("/** @someillegaltag */" + "C.prototype.say=function(nums) {alert(nums.join(','));};", "illegal use of unknown JSDoc tag \"someillegaltag\";" + " ignoring it")); } public void testMisplacedDescAnnotation_noWarning() { parse("/** @desc Foo. */ var MSG_BAR = goog.getMsg('hello');"); parse("/** @desc Foo. */ x.y.z.MSG_BAR = goog.getMsg('hello');"); parse("/** @desc Foo. */ MSG_BAR = goog.getMsg('hello');"); parse("var msgs = {/** @desc x */ MSG_X: goog.getMsg('x')}"); } public void testUnescapedSlashInRegexpCharClass() { parse("var foo = /[/]/;"); parse("var foo = /[hi there/]/;"); parse("var foo = /[/yo dude]/;"); parse("var foo = /\\/[@#$/watashi/wa/suteevu/desu]/;"); } /** * Test for https://github.com/google/closure-compiler/issues/389. */ public void testMalformedRegexp() { // Simple repro case String js = "var x = com\\"; parseError(js, "Invalid escape sequence"); // The original repro case as reported. js = Joiner.on('\n').join( "(function() {", " var url=\"\";", " switch(true)", " {", " case /a.com\\/g|l.i/N/.test(url):", " return \"\";", " case /b.com\\/T/.test(url):", " return \"\";", " }", "}", ")();"); parseError(js, "primary expression expected"); } private static void assertNodeEquality(Node expected, Node found) { String message = expected.checkTreeEquals(found); if (message != null) { fail(message); } } @SuppressWarnings("unchecked") public void testParse() { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; Node a = Node.newString(Token.NAME, "a"); a.addChildToFront(Node.newString(Token.NAME, "b")); List<ParserResult> testCases = ImmutableList.of( new ParserResult( "3;", createScript(new Node(Token.EXPR_RESULT, Node.newNumber(3.0)))), new ParserResult( "var a = b;", createScript(new Node(Token.VAR, a))) ); for (ParserResult testCase : testCases) { assertNodeEquality(testCase.node, parse(testCase.code)); } } public void testPostfixExpression() { parse("a++"); parse("a.b--"); parse("a[0]++"); parseError("a()++", "Invalid postfix increment operand."); parseError("(new C)--", "Invalid postfix decrement operand."); parseError("this++", "Invalid postfix increment operand."); parseError("(a--)++", "Invalid postfix increment operand."); parseError("(+a)++", "Invalid postfix increment operand."); parseError("[1,2]++", "Invalid postfix increment operand."); parseError("'literal'++", "Invalid postfix increment operand."); } public void testUnaryExpression() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("delete a.b"); parse("delete a[0]"); parse("void f()"); parse("typeof new C"); parse("++a[0]"); parse("--a.b"); parse("+{a: 1}"); parse("-[1,2]"); parse("~'42'"); expectFeatures(Feature.SUPER); parse("!super.a"); expectFeatures(); parseError("delete f()", "Invalid delete operand. Only properties can be deleted."); parseError("++a++", "Invalid prefix increment operand."); parseError("--{a: 1}", "Invalid prefix decrement operand."); parseError("++this", "Invalid prefix increment operand."); parseError("++(-a)", "Invalid prefix increment operand."); parseError("++{a: 1}", "Invalid prefix increment operand."); parseError("++'literal'", "Invalid prefix increment operand."); parseError("++delete a.b", "Invalid prefix increment operand."); } public void testAutomaticSemicolonInsertion() { // var statements assertNodeEquality( parse("var x = 1\nvar y = 2"), parse("var x = 1; var y = 2;")); assertNodeEquality( parse("var x = 1\n, y = 2"), parse("var x = 1, y = 2;")); // assign statements assertNodeEquality( parse("x = 1\ny = 2"), parse("x = 1; y = 2;")); // This fails because an EMPTY statement // is inserted after the 'x=1'. // TODO(tbreisacher): Fix and re-enable. //assertNodeEquality( // parse("x = 1\n;y = 2"), // parse("x = 1; y = 2;")); // if/else statements assertNodeEquality( parse("if (x)\n;else{}"), parse("if (x) {} else {}")); } /** * Test all the ASI examples from * http://www.ecma-international.org/ecma-262/5.1/#sec-7.9.2 */ public void testAutomaticSemicolonInsertionExamplesFromSpec() { parseError("{ 1 2 } 3", SEMICOLON_EXPECTED); assertNodeEquality( parse("{ 1\n2 } 3"), parse("{ 1; 2; } 3;")); parseError("for (a; b\n)", "';' expected"); assertNodeEquality( parse("function f() { return\na + b }"), parse("function f() { return; a + b; }")); assertNodeEquality( parse("a = b\n++c"), parse("a = b; ++c;")); parseError("if (a > b)\nelse c = d", "primary expression expected"); assertNodeEquality( parse("a = b + c\n(d + e).print()"), parse("a = b + c(d + e).print()")); } private static Node createScript(Node n) { Node script = new Node(Token.SCRIPT); script.addChildToBack(n); return script; } public void testMethodInObjectLiteral() { expectFeatures(Feature.EXTENDED_OBJECT_LITERALS); testMethodInObjectLiteral("var a = {b() {}};"); testMethodInObjectLiteral("var a = {b() { alert('b'); }};"); // Static methods not allowed in object literals. expectFeatures(); parseError("var a = {static b() { alert('b'); }};", "Cannot use keyword in short object literal"); } private void testMethodInObjectLiteral(String js) { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse(js); mode = LanguageMode.ECMASCRIPT5; parseWarning(js, getRequiresEs6Message(Feature.MEMBER_DECLARATIONS)); } public void testExtendedObjectLiteral() { expectFeatures(Feature.EXTENDED_OBJECT_LITERALS); testExtendedObjectLiteral("var a = {b};"); testExtendedObjectLiteral("var a = {b, c};"); testExtendedObjectLiteral("var a = {b, c: d, e};"); testExtendedObjectLiteral("var a = {type};"); testExtendedObjectLiteral("var a = {declare};"); testExtendedObjectLiteral("var a = {namespace};"); testExtendedObjectLiteral("var a = {module};"); expectFeatures(); parseError("var a = { '!@#$%' };", "':' expected"); parseError("var a = { 123 };", "':' expected"); parseError("var a = { let };", "Cannot use keyword in short object literal"); parseError("var a = { else };", "Cannot use keyword in short object literal"); } private void testExtendedObjectLiteral(String js) { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse(js); mode = LanguageMode.ECMASCRIPT5; parseWarning(js, getRequiresEs6Message(Feature.EXTENDED_OBJECT_LITERALS)); } public void testComputedPropertiesObjLit() { expectFeatures(Feature.COMPUTED_PROPERTIES); // Method testComputedProperty(Joiner.on('\n').join( "var x = {", " [prop + '_']() {}", "}")); // Getter testComputedProperty(Joiner.on('\n').join( "var x = {", " get [prop + '_']() {}", "}")); // Setter testComputedProperty(Joiner.on('\n').join( "var x = {", " set [prop + '_'](val) {}", "}")); // Generator method mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse(Joiner.on('\n').join( "var x = {", " *[prop + '_']() {}", "}")); } public void testComputedMethodClass() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.CLASSES, Feature.COMPUTED_PROPERTIES); parse(Joiner.on('\n').join( "class X {", " [prop + '_']() {}", "}")); parse(Joiner.on('\n').join( "class X {", " static [prop + '_']() {}", "}")); } public void testComputedProperty() { expectFeatures(Feature.COMPUTED_PROPERTIES); testComputedProperty(Joiner.on('\n').join( "var prop = 'some complex expression';", "", "var x = {", " [prop]: 'foo'", "}")); testComputedProperty(Joiner.on('\n').join( "var prop = 'some complex expression';", "", "var x = {", " [prop + '!']: 'foo'", "}")); testComputedProperty(Joiner.on('\n').join( "var prop;", "", "var x = {", " [prop = 'some expr']: 'foo'", "}")); testComputedProperty(Joiner.on('\n').join( "var x = {", " [1 << 8]: 'foo'", "}")); String js = Joiner.on('\n').join( "var x = {", " [1 << 8]: 'foo',", " [1 << 7]: 'bar'", "}"); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse(js); mode = LanguageMode.ECMASCRIPT5; String warning = getRequiresEs6Message(Feature.COMPUTED_PROPERTIES); parseWarning(js, warning, warning); } private void testComputedProperty(String js) { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse(js); mode = LanguageMode.ECMASCRIPT5; parseWarning(js, getRequiresEs6Message(Feature.COMPUTED_PROPERTIES)); } public void testTrailingCommaWarning1() { parse("var a = ['foo', 'bar'];"); } public void testTrailingCommaWarning2() { parse("var a = ['foo',,'bar'];"); } public void testTrailingCommaWarning3() { expectFeatures(Feature.TRAILING_COMMA); parseWarning("var a = ['foo', 'bar',];", TRAILING_COMMA_MESSAGE); mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; parse("var a = ['foo', 'bar',];"); } public void testTrailingCommaWarning4() { expectFeatures(Feature.TRAILING_COMMA); parseWarning("var a = [,];", TRAILING_COMMA_MESSAGE); mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; parse("var a = [,];"); } public void testTrailingCommaWarning5() { parse("var a = {'foo': 'bar'};"); } public void testTrailingCommaWarning6() { expectFeatures(Feature.TRAILING_COMMA); parseWarning("var a = {'foo': 'bar',};", TRAILING_COMMA_MESSAGE); mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; parse("var a = {'foo': 'bar',};"); } public void testTrailingCommaWarning7() { parseError("var a = {,};", "'}' expected"); } public void testSuspiciousBlockCommentWarning1() { parseWarning("/* @type {number} */ var x = 3;", SUSPICIOUS_COMMENT_WARNING); } public void testSuspiciousBlockCommentWarning2() { parseWarning("/* \n * @type {number} */ var x = 3;", SUSPICIOUS_COMMENT_WARNING); } public void testSuspiciousBlockCommentWarning3() { parseWarning("/* \n *@type {number} */ var x = 3;", SUSPICIOUS_COMMENT_WARNING); } public void testSuspiciousBlockCommentWarning4() { parseWarning( " /*\n" + " * @type {number}\n" + " */\n" + " var x = 3;", SUSPICIOUS_COMMENT_WARNING); } public void testSuspiciousBlockCommentWarning5() { parseWarning( " /*\n" + " * some random text here\n" + " * @type {number}\n" + " */\n" + " var x = 3;", SUSPICIOUS_COMMENT_WARNING); } public void testSuspiciousBlockCommentWarning6() { parseWarning("/* @type{number} */ var x = 3;", SUSPICIOUS_COMMENT_WARNING); } public void testSuspiciousBlockCommentWarning7() { // jsdoc tags contain letters only, no underscores etc. parse("/* @cc_on */ var x = 3;"); } public void testSuspiciousBlockCommentWarning8() { // a jsdoc tag can't be immediately followed by a paren parse("/* @TODO(username) */ var x = 3;"); } public void testCatchClauseForbidden() { parseError("try { } catch (e if true) {}", "')' expected"); } public void testConstForbidden() { expectFeatures(Feature.CONST_DECLARATIONS); parseWarning("const x = 3;", getRequiresEs6Message(Feature.CONST_DECLARATIONS)); } public void testAnonymousFunctionExpression() { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; parseError("function () {}", "'identifier' expected"); mode = LanguageMode.ECMASCRIPT6; parseError("function () {}", "'identifier' expected"); } public void testArrayDestructuringVar() { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parseWarning("var [x,y] = foo();", getRequiresEs6Message(Feature.DESTRUCTURING)); mode = LanguageMode.ECMASCRIPT6; parse("var [x,y] = foo();"); // arbitrary LHS assignment target not allowed parseError( "var [x,y[15]] = foo();", "Only an identifier or destructuring pattern is allowed here."); } public void testArrayDestructuringAssign() { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parseWarning("[x,y] = foo();", getRequiresEs6Message(Feature.DESTRUCTURING)); mode = LanguageMode.ECMASCRIPT6; parse("[x,y] = foo();"); // arbitrary LHS assignment target is allowed parse("[x,y[15]] = foo();"); } public void testArrayDestructuringInitializer() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("var [x=1,y] = foo();"); parse("[x=1,y] = foo();"); parse("var [x,y=2] = foo();"); parse("[x,y=2] = foo();"); parse("var [[a] = ['b']] = [];"); parse("[[a] = ['b']] = [];"); // arbitrary LHS target allowed in assignment, but not declaration parse("[[a.x] = ['b']] = [];"); parseError( "var [[a.x] = ['b']] = [];", "Only an identifier or destructuring pattern is allowed here."); } public void testArrayDestructuringDeclarationRest() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING, Feature.REST_PARAMETERS); parse("var [first, ...rest] = foo();"); expectFeatures(Feature.DESTRUCTURING, Feature.REST_PARAMETERS, Feature.LET_DECLARATIONS); parse("let [first, ...rest] = foo();"); expectFeatures(Feature.DESTRUCTURING, Feature.REST_PARAMETERS, Feature.CONST_DECLARATIONS); parse("const [first, ...rest] = foo();"); // nested destructuring in regular parameters and rest parameters expectFeatures(Feature.DESTRUCTURING, Feature.REST_PARAMETERS); parse("var [first, {a, b}, ...[re, st, ...{length}]] = foo();"); parseError( "var [first, ...more = 'default'] = foo();", "A default value cannot be specified after '...'"); parseError("var [first, ...more, last] = foo();", "']' expected"); mode = LanguageMode.ECMASCRIPT5; parseWarning( "var [first, ...rest] = foo();", getRequiresEs6Message(Feature.DESTRUCTURING)); } public void testArrayDestructuringAssignRest() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING, Feature.REST_PARAMETERS); 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; parseWarning("var [first, ...rest] = foo();", getRequiresEs6Message(Feature.DESTRUCTURING)); } public void testArrayDestructuringFnDeclaration() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("function f([x, y]) { use(x); use(y); }"); parse("function f([x, [y, z]]) {}"); parse("function f([x, {y, foo: z}]) {}"); parse("function f([x, y] = [1, 2]) { use(x); use(y); }"); parse("function f([x, x]) {}"); // arbitrary LHS expression not allowed as a formal parameter parseError( "function f([a[0], x]) {}", "Only an identifier or destructuring pattern is allowed here."); // restriction applies to sub-patterns parseError( "function f([a, [x.foo]]) {}", "Only an identifier or destructuring pattern is allowed here."); parseError( "function f([a, {foo: x.foo}]) {}", "Only an identifier or destructuring pattern is allowed here."); } public void testObjectDestructuringVar() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("var {x, y} = foo();"); parse("var {x: x, y: y} = foo();"); parse("var {x: {y, z}} = foo();"); parse("var {x: {y: {z}}} = foo();"); // Useless, but legal. parse("var {} = foo();"); // Arbitrary LHS target not allowed in declaration parseError("var {x.a, y} = foo();", "'}' expected"); parseError( "var {a: x.a, y} = foo();", "Only an identifier or destructuring pattern is allowed here."); } public void testObjectDestructuringVarWithInitializer() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("var {x = 1} = foo();"); parse("var {x: {y = 1}} = foo();"); parse("var {x: y = 1} = foo();"); parse("var {x: v1 = 5, y: v2 = 'str'} = foo();"); parse("var {k1: {k2 : x} = bar(), k3: y} = foo();"); } public void testObjectDestructuringAssign() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("({x, y}) = foo();", "invalid assignment target"); expectFeatures(Feature.DESTRUCTURING); parse("({x, y} = foo());"); parse("({x: x, y: y} = foo());"); parse("({x: {y, z}} = foo());"); parse("({k1: {k2 : x} = bar(), k3: y} = foo());"); // Useless, but legal. parse("({} = foo());"); } public void testObjectDestructuringAssignWithInitializer() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("({x = 1}) = foo();", "invalid assignment target"); expectFeatures(Feature.DESTRUCTURING); parse("({x = 1} = foo());"); parse("({x: {y = 1}} = foo());"); parse("({x: y = 1} = foo());"); parse("({x: v1 = 5, y: v2 = 'str'} = foo());"); parse("({k1: {k2 : x} = bar(), k3: y} = foo());"); } public void testObjectDestructuringWithInitializerInvalid() { expectFeatures(Feature.DESTRUCTURING); parseError("var {{x}} = foo();", "'}' expected"); expectFeatures(); parseError("({{x}}) = foo();", "'}' expected"); parseError("({{a} = {a: 'b'}}) = foo();", "'}' expected"); parseError("({{a : b} = {a: 'b'}}) = foo();", "'}' expected"); } public void testObjectDestructuringFnDeclaration() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("function f({x, y}) { use(x); use(y); }"); parse("function f({w, x: {y, z}}) {}"); parse("function f({x, y} = {x:1, y:2}) {}"); parse("function f({x, x}) {}"); // arbitrary LHS expression not allowed as a formal parameter parseError("function f({a[0], x}) {}", "'}' expected"); parseError( "function f({foo: a[0], x}) {}", "Only an identifier or destructuring pattern is allowed here."); // restriction applies to sub-patterns parseError( "function f({a, foo: [x.foo]}) {}", "Only an identifier or destructuring pattern is allowed here."); parseError( "function f({a, x: {foo: x.foo}}) {}", "Only an identifier or destructuring pattern is allowed here."); } public void testObjectDestructuringComputedProp() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("var {[x]: y} = z;"); parse("var { [foo()] : [x,y,z] = bar() } = baz();"); parseError("var {[x]} = z;", "':' expected"); } public void testObjectDestructuringStringAndNumberKeys() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("var {'s': x} = foo();"); parse("var {3: x} = foo();"); parseError("var { 'hello world' } = foo();", "':' expected"); parseError("var { 4 } = foo();", "':' expected"); parseError("var { 'hello' = 'world' } = foo();", "':' expected"); parseError("var { 2 = 5 } = foo();", "':' expected"); } /** * See https://github.com/google/closure-compiler/issues/1262 */ public void testObjectNumberKeysSpecial() { Node n = parse("var a = {12345678901234567890: 2}"); Node objectLit = n.getFirstChild().getFirstChild().getFirstChild(); assertThat(objectLit.getToken()).isEqualTo(Token.OBJECTLIT); Node number = objectLit.getFirstChild(); assertThat(number.getToken()).isEqualTo(Token.STRING_KEY); assertThat(number.getString()).isEqualTo("12345678901234567000"); } public void testObjectDestructuringKeywordKeys() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("var {if: x, else: y} = foo();"); parse("var {while: x=1, for: y} = foo();"); parse("var {type} = foo();"); parse("var {declare} = foo();"); parse("var {module} = foo();"); parse("var {namespace} = foo();"); parseError("var {while} = foo();", "cannot use keyword 'while' here."); parseError("var {implements} = foo();", "cannot use keyword 'implements' here."); } public void testObjectDestructuringComplexTarget() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parseError( "var {foo: bar.x} = baz();", "Only an identifier or destructuring pattern is allowed here."); parse("({foo: bar.x} = baz());"); parse("for ({foo: bar.x} in baz());"); parseError( "var {foo: bar[x]} = baz();", "Only an identifier or destructuring pattern is allowed here."); parse("({foo: bar[x]} = baz());"); parse("for ({foo: bar[x]} in baz());"); } public void testObjectDestructuringExtraParens() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("({x: y} = z);"); parse("({x: (y)} = z);"); parse("({x: ((y))} = z);"); parse("([x] = y);"); parse("[(x), y] = z;"); parse("[x, (y)] = z;"); parseError("[x, ([y])] = z;", INVALID_ASSIGNMENT_TARGET); parseError("[x, (([y]))] = z;", INVALID_ASSIGNMENT_TARGET); } public void testObjectLiteralCannotUseDestructuring() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("var o = {x = 5}", "Default value cannot appear at top level of an object literal."); } public void testMixedDestructuring() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("var {x: [y, z]} = foo();"); parse("var [x, {y, z}] = foo();"); parse("({x: [y, z]} = foo());"); parse("[x, {y, z}] = foo();"); parse("function f({x: [y, z]}) {}"); parse("function f([x, {y, z}]) {}"); } public void testMixedDestructuringWithInitializer() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("var {x: [y, z] = [1, 2]} = foo();"); parse("var [x, {y, z} = {y: 3, z: 4}] = foo();"); parse("({x: [y, z] = [1, 2]} = foo());"); parse("[x, {y, z} = {y: 3, z: 4}] = foo();"); parse("function f({x: [y, z] = [1, 2]}) {}"); parse("function f([x, {y, z} = {y: 3, z: 4}]) {}"); } public void testDestructuringNoRHS() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parseError("var {x: y};", "destructuring must have an initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parseError("let {x: y};", "destructuring must have an initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parseError("const {x: y};", "const variables must have an initializer"); expectFeatures(Feature.DESTRUCTURING); parseError("var {x};", "destructuring must have an initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parseError("let {x};", "destructuring must have an initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parseError("const {x};", "const variables must have an initializer"); expectFeatures(Feature.DESTRUCTURING); parseError("var [x, y];", "destructuring must have an initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parseError("let [x, y];", "destructuring must have an initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parseError("const [x, y];", "const variables must have an initializer"); } public void testComprehensions() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; String error = "unsupported language feature:" + " array/generator comprehensions"; // array comprehensions parseError("[for (x of y) z];", error); expectFeatures(Feature.DESTRUCTURING); // Note: the object pattern triggers this parseError("[for ({x,y} of z) x+y];", error); expectFeatures(); parseError("[for (x of y) if (x<10) z];", error); parseError("[for (a = 5 of v) a];", "'identifier' expected"); // generator comprehensions parseError("(for (x of y) z);", error); expectFeatures(Feature.DESTRUCTURING); // Note: the object pattern triggers this parseError("(for ({x,y} of z) x+y);", error); expectFeatures(); parseError("(for (x of y) if (x<10) z);", error); parseError("(for (a = 5 of v) a);", "'identifier' expected"); } public void testLetForbidden1() { expectFeatures(Feature.LET_DECLARATIONS); parseWarning("let x = 3;", getRequiresEs6Message(Feature.LET_DECLARATIONS)); } public void testLetForbidden2() { expectFeatures(Feature.LET_DECLARATIONS); parseWarning("function f() { let x = 3; };", getRequiresEs6Message(Feature.LET_DECLARATIONS)); } public void testLetForbidden3() { mode = LanguageMode.ECMASCRIPT5; strictMode = STRICT; parseError("function f() { var let = 3; }", "'identifier' expected"); mode = LanguageMode.ECMASCRIPT6; parseError("function f() { var let = 3; }", "'identifier' expected"); } public void testYieldForbidden() { parseError("function f() { yield 3; }", "primary expression expected"); } public void testGenerator() { expectFeatures(Feature.GENERATORS); mode = LanguageMode.ECMASCRIPT6; strictMode = STRICT; parse("var obj = { *f() { yield 3; } };"); parse("function* f() { yield 3; }"); parse("function f() { return function* g() {} }"); mode = LanguageMode.ECMASCRIPT5; parseWarning("function* f() { yield 3; }", getRequiresEs6Message(Feature.GENERATORS)); parseWarning("var obj = { * f() { yield 3; } };", getRequiresEs6Message(Feature.GENERATORS), getRequiresEs6Message(Feature.MEMBER_DECLARATIONS)); } public void testBracelessFunctionForbidden() { parseError("var sq = function(x) x * x;", "'{' expected"); } public void testGeneratorsForbidden() { parseError("var i = (x for (x in obj));", "')' expected"); } public void testGettersForbidden1() { expectFeatures(Feature.GETTER); parseError("var x = {get foo() { return 3; }};", IRFactory.GETTER_ERROR_MESSAGE); } public void testGettersForbidden2() { parseError("var x = {get foo bar() { return 3; }};", "'(' expected"); } public void testGettersForbidden3() { parseError("var x = {a getter:function b() { return 3; }};", "'}' expected"); } public void testGettersForbidden4() { parseError("var x = {\"a\" getter:function b() { return 3; }};", "':' expected"); } public void testGettersForbidden5() { expectFeatures(Feature.GETTER); parseError("var x = {a: 2, get foo() { return 3; }};", IRFactory.GETTER_ERROR_MESSAGE); } public void testGettersForbidden6() { expectFeatures(Feature.GETTER); parseError("var x = {get 'foo'() { return 3; }};", IRFactory.GETTER_ERROR_MESSAGE); } public void testSettersForbidden() { expectFeatures(Feature.SETTER); parseError("var x = {set foo(a) { y = 3; }};", IRFactory.SETTER_ERROR_MESSAGE); } public void testSettersForbidden2() { // TODO(johnlenz): maybe just report the first error, when not in IDE mode? parseError("var x = {a setter:function b() { return 3; }};", "'}' expected"); } public void testFileOverviewJSDoc1() { isIdeMode = true; Node n = parse("/** @fileoverview Hi mom! */ function Foo() {}"); assertNode(n.getFirstChild()).hasType(Token.FUNCTION); assertThat(n.getJSDocInfo()).isNotNull(); assertThat(n.getFirstChild().getJSDocInfo()).isNull(); assertThat(n.getJSDocInfo().getFileOverview()).isEqualTo("Hi mom!"); } public void testFileOverviewJSDocDoesNotHoseParsing() { assertNode( parse("/** @fileoverview Hi mom! \n */ function Foo() {}").getFirstChild()) .hasType(Token.FUNCTION); assertNode( parse("/** @fileoverview Hi mom! \n * * * */ function Foo() {}").getFirstChild()) .hasType(Token.FUNCTION); assertNode(parse("/** @fileoverview \n * x */ function Foo() {}").getFirstChild()) .hasType(Token.FUNCTION); assertNode( parse("/** @fileoverview \n * x \n */ function Foo() {}").getFirstChild()) .hasType(Token.FUNCTION); } public void testFileOverviewJSDoc2() { isIdeMode = true; Node n = parse("/** @fileoverview Hi mom! */" + " /** @constructor */ function Foo() {}"); assertThat(n.getJSDocInfo()).isNotNull(); assertThat(n.getJSDocInfo().getFileOverview()).isEqualTo("Hi mom!"); assertThat(n.getFirstChild().getJSDocInfo()).isNotNull(); assertThat(n.getFirstChild().getJSDocInfo().hasFileOverview()).isFalse(); assertThat(n.getFirstChild().getJSDocInfo().isConstructor()).isTrue(); } public void testImportantComment() { isIdeMode = true; Node n = parse("/*! Hi mom! */ function Foo() {}"); assertNode(n.getFirstChild()).hasType(Token.FUNCTION); assertThat(n.getJSDocInfo()).isNotNull(); assertThat(n.getFirstChild().getJSDocInfo()).isNull(); assertThat(n.getJSDocInfo().getLicense()).isEqualTo(" Hi mom! "); } public void testObjectLiteralDoc1() { Node n = parse("var x = {/** @type {number} */ 1: 2};"); Node objectLit = n.getFirstFirstChild().getFirstChild(); assertNode(objectLit).hasType(Token.OBJECTLIT); Node number = objectLit.getFirstChild(); assertNode(number).hasType(Token.STRING_KEY); assertThat(number.getJSDocInfo()).isNotNull(); } public void testDuplicatedParam() { parseWarning("function foo(x, x) {}", "Duplicate parameter name \"x\""); } public void testLetAsIdentifier() { mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parse("var let"); mode = LanguageMode.ECMASCRIPT5; parse("var let"); mode = LanguageMode.ECMASCRIPT5; strictMode = STRICT; parseError("var let", "'identifier' expected"); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("var let"); mode = LanguageMode.ECMASCRIPT6; strictMode = STRICT; parseError("var let", "'identifier' expected"); } public void testLet() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.LET_DECLARATIONS); parse("let x;"); parse("let x = 1;"); parse("let x, y = 2;"); parse("let x = 1, y = 2;"); } public void testConst() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.CONST_DECLARATIONS); parseError("const x;", "const variables must have an initializer"); parse("const x = 1;"); parseError("const x, y = 2;", "const variables must have an initializer"); parse("const x = 1, y = 2;"); } public void testYield1() { mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parse("var yield"); mode = LanguageMode.ECMASCRIPT5; parse("var yield"); mode = LanguageMode.ECMASCRIPT5; strictMode = STRICT; parseError("var yield", "'identifier' expected"); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("var yield"); mode = LanguageMode.ECMASCRIPT6; strictMode = STRICT; parseError("var yield", "'identifier' expected"); } public void testYield2() { mode = LanguageMode.ECMASCRIPT6; strictMode = STRICT; expectFeatures(Feature.GENERATORS); parse("function * f() { yield; }"); parse("function * f() { yield /a/i; }"); expectFeatures(); parseError("function * f() { 1 + yield; }", "primary expression expected"); parseError("function * f() { 1 + yield 2; }", "primary expression expected"); parseError("function * f() { yield 1 + yield 2; }", "primary expression expected"); parseError("function * f() { yield(1) + yield(2); }", "primary expression expected"); expectFeatures(Feature.GENERATORS); parse("function * f() { (yield 1) + (yield 2); }"); // OK parse("function * f() { yield * yield; }"); // OK (yield * (yield)) expectFeatures(); parseError("function * f() { yield + yield; }", "primary expression expected"); expectFeatures(Feature.GENERATORS); parse("function * f() { (yield) + (yield); }"); // OK parse("function * f() { return yield; }"); // OK parse("function * f() { return yield 1; }"); // OK parse(LINE_JOINER.join( "function * f() {", " yield *", // line break allowed here " [1, 2, 3];", "}")); expectFeatures(); parseError(LINE_JOINER.join( "function * f() {", " yield", // line break not allowed here " *[1, 2, 3];", "}"), "'}' expected"); parseError("function * f() { yield *; }", "yield* requires an expression"); } public void testYield3() { mode = LanguageMode.ECMASCRIPT6; strictMode = STRICT; expectFeatures(Feature.GENERATORS); // TODO(johnlenz): validate "yield" parsing. Firefox rejects this // use of "yield". parseError("function * f() { yield , yield; }"); } public void testStringLineContinuation() { expectFeatures(Feature.STRING_CONTINUATION); mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; Node n = parseError("'one\\\ntwo';", "String continuations are not supported in this language mode."); assertThat(n.getFirstFirstChild().getString()).isEqualTo("onetwo"); mode = LanguageMode.ECMASCRIPT5; parseWarning("'one\\\ntwo';", "String continuations are not recommended. See" + " https://google.github.io/styleguide/javascriptguide.xml?showone=Multiline_string_literals#Multiline_string_literals"); assertThat(n.getFirstFirstChild().getString()).isEqualTo("onetwo"); mode = LanguageMode.ECMASCRIPT6; parseWarning("'one\\\ntwo';", "String continuations are not recommended. See" + " https://google.github.io/styleguide/javascriptguide.xml?showone=Multiline_string_literals#Multiline_string_literals"); assertThat(n.getFirstFirstChild().getString()).isEqualTo("onetwo"); } public void testStringLiteral() { Node n = parse("'foo'"); Node stringNode = n.getFirstFirstChild(); assertNode(stringNode).hasType(Token.STRING); assertThat(stringNode.getString()).isEqualTo("foo"); } private Node testTemplateLiteral(String s) { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; parseWarning(s, getRequiresEs6Message(Feature.TEMPLATE_LITERALS)); mode = LanguageMode.ECMASCRIPT6; return parse(s); } private void assertSimpleTemplateLiteral(String expectedContents, String literal) { Node node = testTemplateLiteral(literal).getFirstFirstChild(); assertNode(node).hasType(Token.TEMPLATELIT); assertThat(node.getChildCount()).isEqualTo(1); assertNode(node.getFirstChild()).hasType(Token.STRING); assertThat(node.getFirstChild().getString()).isEqualTo(expectedContents); } public void testUseTemplateLiteral() { expectFeatures(Feature.TEMPLATE_LITERALS); testTemplateLiteral("f`hello world`;"); testTemplateLiteral("`hello ${name} ${world}`.length;"); } public void testTemplateLiteral() { expectFeatures(Feature.TEMPLATE_LITERALS); testTemplateLiteral("``"); testTemplateLiteral("`\"`"); testTemplateLiteral("`\\\"`"); testTemplateLiteral("`\\``"); testTemplateLiteral("`hello world`;"); testTemplateLiteral("`hello\nworld`;"); testTemplateLiteral("`string containing \\`escaped\\` backticks`;"); testTemplateLiteral("{ `in block` }"); testTemplateLiteral("{ `in ${block}` }"); } public void testTemplateLiteralWithNewline() { expectFeatures(Feature.TEMPLATE_LITERALS); assertSimpleTemplateLiteral("hello\nworld", "`hello\nworld`"); assertSimpleTemplateLiteral("\n", "`\r`"); assertSimpleTemplateLiteral("\n", "`\r\n`"); assertSimpleTemplateLiteral("\\\n", "`\\\\\n`"); assertSimpleTemplateLiteral("\\\n", "`\\\\\r\n`"); assertSimpleTemplateLiteral("\r\n", "`\\r\\n`"); // template literals support explicit escapes assertSimpleTemplateLiteral("\\r\\n", "`\\\\r\\\\n`"); // note: no actual newlines here } public void testTemplateLiteralWithLineContinuation() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.TEMPLATE_LITERALS); Node n = parseWarning("`string \\\ncontinuation`", "String continuations are not recommended. See" + " https://google.github.io/styleguide/javascriptguide.xml?showone=Multiline_string_literals#Multiline_string_literals"); Node templateLiteral = n.getFirstFirstChild(); Node stringNode = templateLiteral.getFirstChild(); assertNode(stringNode).hasType(Token.STRING); assertThat(stringNode.getString()).isEqualTo("string continuation"); } public void testTemplateLiteralSubstitution() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.TEMPLATE_LITERALS); parse("`hello ${name}`;"); parse("`hello ${name} ${world}`;"); parse("`hello ${name }`"); expectFeatures(); parseError("`hello ${name", "Expected '}' after expression in template literal"); parseError("`hello ${name tail}", "Expected '}' after expression in template literal"); } public void testUnterminatedTemplateLiteral() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("`hello", "Unterminated template literal"); parseError("`hello\\`", "Unterminated template literal"); } public void testIncorrectEscapeSequenceInTemplateLiteral() { parseError("`hello\\x", "Hex digit expected"); parseError("`hello\\x`", "Hex digit expected"); } public void testExponentialLiterals() { parse("0e0"); parse("0E0"); parse("0E1"); parse("1E0"); parse("1E-0"); parse("10E10"); parse("10E-10"); parse("1.0E1"); parseError("01E0", SEMICOLON_EXPECTED); parseError("0E", "Exponent part must contain at least one digit"); parseError("1E-", "Exponent part must contain at least one digit"); parseError("1E1.1", SEMICOLON_EXPECTED); } public void testBinaryLiterals() { expectFeatures(Feature.BINARY_LITERALS); mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parseWarning("0b0001;", "Binary integer literals are not supported in this language mode."); mode = LanguageMode.ECMASCRIPT5; parseWarning("0b0001;", "Binary integer literals are not supported in this language mode."); mode = LanguageMode.ECMASCRIPT6; parse("0b0001;"); } public void testOctalLiterals() { expectFeatures(Feature.OCTAL_LITERALS); mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parseWarning("0o0001;", "Octal integer literals are not supported in this language mode."); mode = LanguageMode.ECMASCRIPT5; parseWarning("0o0001;", "Octal integer literals are not supported in this language mode."); mode = LanguageMode.ECMASCRIPT6; parse("0o0001;"); } public void testOldStyleOctalLiterals() { mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parseWarning("0001;", "Octal integer literals are not supported in Ecmascript 5 strict mode."); mode = LanguageMode.ECMASCRIPT5; parseWarning("0001;", "Octal integer literals are not supported in Ecmascript 5 strict mode."); mode = LanguageMode.ECMASCRIPT6; parseWarning("0001;", "Octal integer literals are not supported in Ecmascript 5 strict mode."); } public void testInvalidOctalLiterals() { mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parseError("0o08;", "Invalid octal digit in octal literal."); mode = LanguageMode.ECMASCRIPT5; parseError("0o08;", "Invalid octal digit in octal literal."); mode = LanguageMode.ECMASCRIPT6; parseError("0o08;", "Invalid octal digit in octal literal."); } public void testInvalidOldStyleOctalLiterals() { mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parseError("08;", "Invalid octal digit in octal literal."); parseError("01238;", "Invalid octal digit in octal literal."); mode = LanguageMode.ECMASCRIPT5; parseError("08;", "Invalid octal digit in octal literal."); parseError("01238;", "Invalid octal digit in octal literal."); mode = LanguageMode.ECMASCRIPT6; parseError("08;", "Invalid octal digit in octal literal."); parseError("01238;", "Invalid octal digit in octal literal."); } public void testGetter() { 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); mode = LanguageMode.ECMASCRIPT5; parse("var x = {get 1(){}};"); parse("var x = {get 'a'(){}};"); parse("var x = {get a(){}};"); expectFeatures(); parseError("var x = {get a(b){}};", "')' expected"); } public void testSetter() { 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); mode = LanguageMode.ECMASCRIPT5; parse("var x = {set 1(x){}};"); parse("var x = {set 'a'(x){}};"); parse("var x = {set a(x){}};"); expectFeatures(); parseError("var x = {set a(){}};", "'identifier' expected"); } public void testLamestWarningEver() { // This used to be a warning. parse("var x = /** @type {undefined} */ (y);"); parse("var x = /** @type {void} */ (y);"); } public void testUnfinishedComment() { parseError("/** this is a comment ", "unterminated comment"); } public void testHtmlStartCommentAtStartOfLine() { parseWarning("<!-- This text is ignored.\nalert(1)", HTML_COMMENT_WARNING); } public void testHtmlStartComment() { parseWarning("alert(1) <!-- This text is ignored.\nalert(2)", HTML_COMMENT_WARNING); } public void testHtmlEndCommentAtStartOfLine() { parseWarning("alert(1)\n --> This text is ignored.", HTML_COMMENT_WARNING); } // "-->" is not the start of a comment, when it is not at the beginning // of a line. public void testHtmlEndComment() { parse("while (x --> 0) {\n alert(1)\n}"); } public void testParseBlockDescription() { isIdeMode = true; Node n = parse("/** This is a variable. */ var x;"); Node var = n.getFirstChild(); assertThat(var.getJSDocInfo()).isNotNull(); assertThat(var.getJSDocInfo().getBlockDescription()).isEqualTo("This is a variable."); } public void testUnnamedFunctionStatement() { // Statements parseError("function() {};", "'identifier' expected"); parseError("if (true) { function() {}; }", "'identifier' expected"); parse("function f() {};"); // Expressions parse("(function f() {});"); parse("(function () {});"); } public void testReservedKeywords() { expectFeatures(Feature.ES3_KEYWORDS_AS_IDENTIFIERS); mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parseError("var boolean;", "identifier is a reserved word"); parseError("function boolean() {};", "identifier is a reserved word"); parseError("boolean = 1;", "identifier is a reserved word"); expectFeatures(); parseError("class = 1;", "'identifier' expected"); parseError("public = 2;", "primary expression expected"); mode = LanguageMode.ECMASCRIPT5; expectFeatures(Feature.ES3_KEYWORDS_AS_IDENTIFIERS); parse("var boolean;"); parse("function boolean() {};"); parse("boolean = 1;"); expectFeatures(); parseError("class = 1;", "'identifier' expected"); parseError("var import = 0;", "'identifier' expected"); // TODO(johnlenz): reenable //parse("public = 2;"); mode = LanguageMode.ECMASCRIPT5; strictMode = STRICT; expectFeatures(Feature.ES3_KEYWORDS_AS_IDENTIFIERS); parse("var boolean;"); parse("function boolean() {};"); parse("boolean = 1;"); expectFeatures(); parseError("public = 2;", "primary expression expected"); parseError("class = 1;", "'identifier' expected"); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.CONST_DECLARATIONS); parseError("const else = 1;", "'identifier' expected"); } public void testTypeScriptKeywords() { parse("type = 2;"); parse("var type = 3;"); parse("type\nx = 5"); parse("while (i--) { type = types[i]; }"); parse("declare = 2;"); parse("var declare = 3;"); parse("declare\nx = 5"); parse("while (i--) { declare = declares[i]; }"); parse("module = 2;"); parse("var module = 3;"); parse("module\nx = 5"); parse("while (i--) { module = module[i]; }"); } public void testKeywordsAsProperties() { expectFeatures(Feature.KEYWORDS_AS_PROPERTIES); mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parseWarning("var x = {function: 1};", IRFactory.INVALID_ES3_PROP_NAME); parseWarning("x.function;", IRFactory.INVALID_ES3_PROP_NAME); 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); 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); parseWarning("var x = {class: 1};", IRFactory.INVALID_ES3_PROP_NAME); expectFeatures(); parse("var x = {'class': 1};"); expectFeatures(Feature.KEYWORDS_AS_PROPERTIES); parseWarning("x.class;", IRFactory.INVALID_ES3_PROP_NAME); expectFeatures(); parse("x['class'];"); parse("var x = {let: 1};"); // 'let' is not reserved in ES3 parse("x.let;"); parse("var x = {yield: 1};"); // 'yield' is not reserved in ES3 parse("x.yield;"); expectFeatures(Feature.KEYWORDS_AS_PROPERTIES); parseWarning("x.prototype.catch = function() {};", IRFactory.INVALID_ES3_PROP_NAME); parseWarning("x().catch();", IRFactory.INVALID_ES3_PROP_NAME); mode = LanguageMode.ECMASCRIPT5; parse("var x = {function: 1};"); parse("x.function;"); parse("var x = {get function(){} };"); parse("var x = {get 'function'(){} };"); parse("var x = {get 1(){} };"); parse("var x = {set function(a){} };"); parse("var x = {set 'function'(a){} };"); parse("var x = {set 1(a){} };"); parse("var x = {class: 1};"); parse("x.class;"); expectFeatures(); parse("var x = {let: 1};"); parse("x.let;"); parse("var x = {yield: 1};"); parse("x.yield;"); expectFeatures(Feature.KEYWORDS_AS_PROPERTIES); parse("x.prototype.catch = function() {};"); parse("x().catch();"); mode = LanguageMode.ECMASCRIPT5; strictMode = STRICT; parse("var x = {function: 1};"); parse("x.function;"); parse("var x = {get function(){} };"); parse("var x = {get 'function'(){} };"); parse("var x = {get 1(){} };"); parse("var x = {set function(a){} };"); parse("var x = {set 'function'(a){} };"); parse("var x = {set 1(a){} };"); parse("var x = {class: 1};"); parse("x.class;"); expectFeatures(); parse("var x = {let: 1};"); parse("x.let;"); parse("var x = {yield: 1};"); parse("x.yield;"); expectFeatures(Feature.KEYWORDS_AS_PROPERTIES); parse("x.prototype.catch = function() {};"); parse("x().catch();"); } public void testKeywordsAsPropertiesInExterns1() { mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parse("/** @fileoverview\n@externs\n*/\n var x = {function: 1};"); } public void testKeywordsAsPropertiesInExterns2() { mode = LanguageMode.ECMASCRIPT3; strictMode = SLOPPY; parse("/** @fileoverview\n@externs\n*/\n var x = {}; x.function + 1;"); } public void testUnicodeInIdentifiers() { parse("var \\u00fb"); parse("var \\u00fbtest\\u00fb"); parse("Js\\u00C7ompiler"); parse("Js\\u0043ompiler"); parse("if(true){foo=\\u03b5}"); parse("if(true){foo=\\u03b5}else bar()"); } public void testUnicodePointEscapeInIdentifiers() { parse("var \\u{0043}"); parse("var \\u{0043}test\\u{0043}"); parse("var \\u0043test\\u{0043}"); parse("var \\u{0043}test\\u0043"); parse("Js\\u{0043}ompiler"); parse("Js\\u{765}ompiler"); parse("var \\u0043;{43}"); } public void testUnicodePointEscapeStringLiterals() { parse("var i = \'\\u0043ompiler\'"); parse("var i = \'\\u{43}ompiler\'"); parse("var i = \'\\u{1f42a}ompiler\'"); parse("var i = \'\\u{2603}ompiler\'"); parse("var i = \'\\u{1}ompiler\'"); } public void testInvalidUnicodePointEscapeInIdentifiers() { parseError("var \\u{defg", "Invalid escape sequence"); parseError("var \\u{03b5", "Invalid escape sequence"); parseError("var \\u43{43}", "Invalid escape sequence"); parseError("var \\u{defgRestOfIdentifier", "Invalid escape sequence"); parseError("var \\u03b5}", "primary expression expected"); parseError("var \\u{03b5}}}", "primary expression expected"); parseError("var \\u{03b5}{}", SEMICOLON_EXPECTED); parseError("var \\u0043{43}", SEMICOLON_EXPECTED); parseError("var \\u{DEFG}", "Invalid escape sequence"); parseError("Js\\u{}ompiler", "Invalid escape sequence"); // Legal unicode but invalid in identifier parseError("Js\\u{99}ompiler", "Invalid escape sequence"); parseError("Js\\u{10000}ompiler", "Invalid escape sequence"); } public void testInvalidUnicodePointEscapeStringLiterals() { parseError("var i = \'\\u{defg\'", "Hex digit expected"); parseError("var i = \'\\u{defgRestOfIdentifier\'", "Hex digit expected"); parseError("var i = \'\\u{DEFG}\'", "Hex digit expected"); parseError("var i = \'Js\\u{}ompiler\'", "Empty unicode escape"); parseError("var i = \'\\u{345", "Hex digit expected"); } public void testInvalidEscape() { parseError("var \\x39abc", "Invalid escape sequence"); parseError("var abc\\t", "Invalid escape sequence"); } public void testUnnecessaryEscape() { parseWarning("var str = '\\a'", "Unnecessary escape: '\\a' is equivalent to just 'a'"); parse("var str = '\\b'"); parseWarning("var str = '\\c'", "Unnecessary escape: '\\c' is equivalent to just 'c'"); parseWarning("var str = '\\d'", "Unnecessary escape: '\\d' is equivalent to just 'd'"); parseWarning("var str = '\\e'", "Unnecessary escape: '\\e' is equivalent to just 'e'"); parse("var str = '\\f'"); parse("var str = '\\/'"); parse("var str = '\\0'"); parseWarning("var str = '\\1'", "Unnecessary escape: '\\1' is equivalent to just '1'"); parseWarning("var str = '\\2'", "Unnecessary escape: '\\2' is equivalent to just '2'"); parseWarning("var str = '\\3'", "Unnecessary escape: '\\3' is equivalent to just '3'"); parseWarning("var str = '\\%'", "Unnecessary escape: '\\%' is equivalent to just '%'"); parse("var str = '\\$'"); // TODO(tbreisacher): We should warn for this case. mode = LanguageMode.ECMASCRIPT6; expectFeatures(Feature.TEMPLATE_LITERALS); parse("var str = `\\$`"); } public void testEOFInUnicodeEscape() { parseError("var \\u1", "Invalid escape sequence"); parseError("var \\u12", "Invalid escape sequence"); parseError("var \\u123", "Invalid escape sequence"); } public void testEndOfIdentifierInUnicodeEscape() { parseError("var \\u1 = 1;", "Invalid escape sequence"); parseError("var \\u12 = 2;", "Invalid escape sequence"); parseError("var \\u123 = 3;", "Invalid escape sequence"); } public void testInvalidUnicodeEscape() { parseError("var \\uDEFG", "Invalid escape sequence"); } public void testUnicodeEscapeInvalidIdentifierStart() { parseError("var \\u0037yler", "Character '7' (U+0037) is not a valid identifier start char"); parseError("var \\u{37}yler", "Character '7' (U+0037) is not a valid identifier start char"); parseError("var \\u0020space", "Invalid escape sequence"); } public void testUnicodeEscapeInvalidIdentifierChar() { parseError("var sp\\u0020ce", "Invalid escape sequence"); } /** * It is illegal to use a keyword as an identifier, even if you use * unicode escapes to obscure the fact that you are trying do that. */ public void testKeywordAsIdentifier() { parseError("var while;", "'identifier' expected"); parseError("var wh\\u0069le;", "'identifier' expected"); } public void testGetPropFunctionName() { parseError("function a.b() {}", "'(' expected"); parseError("var x = function a.b() {}", "'(' expected"); } public void testGetPropFunctionNameIdeMode() { // In IDE mode, we try to fix up the tree, but sometimes // this leads to even more errors. isIdeMode = true; parseError("function a.b() {}", "'(' expected", "',' expected", "Invalid trailing comma in formal parameter list"); parseError("var x = function a.b() {}", "'(' expected", "',' expected", "Invalid trailing comma in formal parameter list"); } public void testIdeModePartialTree() { Node partialTree = parseError("function Foo() {} f.", "'identifier' expected"); assertThat(partialTree).isNull(); isIdeMode = true; partialTree = parseError("function Foo() {} f.", "'identifier' expected"); assertThat(partialTree).isNotNull(); } public void testForEach() { parseError( "function f(stamp, status) {\n" + " for each ( var curTiming in this.timeLog.timings ) {\n" + " if ( curTiming.callId == stamp ) {\n" + " curTiming.flag = status;\n" + " break;\n" + " }\n" + " }\n" + "};", "'(' expected"); } public void testValidTypeAnnotation1() { parse("/** @type {string} */ var o = 'str';"); parse("var /** @type {string} */ o = 'str', /** @type {number} */ p = 0;"); parse("/** @type {function():string} */ function o() { return 'str'; }"); parse("var o = {}; /** @type {string} */ o.prop = 'str';"); parse("var o = {}; /** @type {string} */ o['prop'] = 'str';"); parse("var o = { /** @type {string} */ prop : 'str' };"); parse("var o = { /** @type {string} */ 'prop' : 'str' };"); parse("var o = { /** @type {string} */ 1 : 'str' };"); } public void testValidTypeAnnotation2() { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; expectFeatures(Feature.GETTER); parse("var o = { /** @type {string} */ get prop() { return 'str' }};"); expectFeatures(Feature.SETTER); parse("var o = { /** @type {string} */ set prop(s) {}};"); } public void testValidTypeAnnotation3() { // This one we don't currently support in the type checker but // we would like to. parse("try {} catch (/** @type {Error} */ e) {}"); } public void testValidTypeAnnotation4() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.MODULES); parse("/** @type {number} */ export var x = 3;"); } public void testParsingAssociativity() { assertNodeEquality(parse("x * y * z"), parse("(x * y) * z")); assertNodeEquality(parse("x + y + z"), parse("(x + y) + z")); assertNodeEquality(parse("x | y | z"), parse("(x | y) | z")); assertNodeEquality(parse("x & y & z"), parse("(x & y) & z")); assertNodeEquality(parse("x ^ y ^ z"), parse("(x ^ y) ^ z")); assertNodeEquality(parse("x || y || z"), parse("(x || y) || z")); assertNodeEquality(parse("x && y && z"), parse("(x && y) && z")); } public void testIssue1116() { parse("/**/"); } public void testUnterminatedStringLiteral() { parseError("var unterm = 'forgot closing quote", "Unterminated string literal"); parseError("var unterm = 'forgot closing quote\n" + "alert(unterm);", "Unterminated string literal"); } /** * @bug 14231379 */ public void testUnterminatedRegExp() { parseError("var unterm = /forgot trailing slash", "Expected '/' in regular expression literal"); parseError("var unterm = /forgot trailing slash\n" + "alert(unterm);", "Expected '/' in regular expression literal"); } public void testRegExp() { assertNodeEquality(parse("/a/"), script(expr(regex("a")))); assertNodeEquality(parse("/\\\\/"), script(expr(regex("\\\\")))); assertNodeEquality(parse("/\\s/"), script(expr(regex("\\s")))); assertNodeEquality(parse("/\\u000A/"), script(expr(regex("\\u000A")))); assertNodeEquality(parse("/[\\]]/"), script(expr(regex("[\\]]")))); } public void testRegExpFlags() { // Various valid combinations. parse("/a/"); parse("/a/i"); parse("/a/g"); parse("/a/m"); parse("/a/ig"); parse("/a/gm"); parse("/a/mgi"); // Invalid combinations parseError("/a/a", "Invalid RegExp flag 'a'"); parseError("/a/b", "Invalid RegExp flag 'b'"); parseError("/a/abc", "Invalid RegExp flag 'a'", "Invalid RegExp flag 'b'", "Invalid RegExp flag 'c'"); } /** * New RegExp flags added in ES6. */ public void testES6RegExpFlags() { expectFeatures(Feature.REGEXP_FLAG_Y); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("/a/y"); expectFeatures(Feature.REGEXP_FLAG_U); parse("/a/u"); mode = LanguageMode.ECMASCRIPT5; expectFeatures(Feature.REGEXP_FLAG_Y); parseWarning("/a/y", getRequiresEs6Message(Feature.REGEXP_FLAG_Y)); expectFeatures(Feature.REGEXP_FLAG_U); parseWarning("/a/u", getRequiresEs6Message(Feature.REGEXP_FLAG_U)); parseWarning("/a/yu", getRequiresEs6Message(Feature.REGEXP_FLAG_Y), getRequiresEs6Message(Feature.REGEXP_FLAG_U)); } public void testDefaultParameters() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DEFAULT_PARAMETERS); parse("function f(a, b=0) {}"); parse("function f(a, b=0, c) {}"); mode = LanguageMode.ECMASCRIPT5; parseWarning("function f(a, b=0) {}", getRequiresEs6Message(Feature.DEFAULT_PARAMETERS)); } public void testRestParameters() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.REST_PARAMETERS); parse("function f(...b) {}"); parse("(...xs) => xs"); parse("(x, ...xs) => xs"); parse("(x, y, ...xs) => xs"); parseError("(...xs, x) => xs", "')' expected"); parseError( "function f(...a[0]) {}", "Only an identifier or destructuring pattern is allowed here."); } public void testDestructuredRestParameters() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.REST_PARAMETERS, Feature.DESTRUCTURING); parse("(...[x]) => xs"); parse("(...[x, y]) => xs"); parse("(a, b, c, ...[x, y, z]) => x"); parseError( "function f(...[a[0]]) {}", "Only an identifier or destructuring pattern is allowed here."); } public void testRestParameters_ES5() { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; expectFeatures(Feature.REST_PARAMETERS); parseWarning("function f(...b) {}", getRequiresEs6Message(Feature.REST_PARAMETERS)); } public void testExpressionsThatLookLikeParameters1() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("();", "invalid parenthesized expression"); expectFeatures(Feature.REST_PARAMETERS); parseError("(...xs);", "invalid parenthesized expression"); parseError("(x, ...xs);", "A rest parameter must be in a parameter list."); parseError("(a, b, c, ...xs);", "A rest parameter must be in a parameter list."); } public void testExpressionsThatLookLikeParameters2() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("!()", "invalid parenthesized expression"); parseError("().method", "invalid parenthesized expression"); parseError("() || a", "invalid parenthesized expression"); parseError("() && a", "invalid parenthesized expression"); parseError("x = ()", "invalid parenthesized expression"); } public void testExpressionsThatLookLikeParameters3() { expectFeatures(Feature.REST_PARAMETERS); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("!(...x)", "invalid parenthesized expression"); parseError("(...x).method", "invalid parenthesized expression"); parseError("(...x) || a", "invalid parenthesized expression"); parseError("(...x) && a", "invalid parenthesized expression"); parseError("x = (...x)", "invalid parenthesized expression"); } public void testDefaultParametersWithRestParameters() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DEFAULT_PARAMETERS, Feature.REST_PARAMETERS); parse("function f(a=0, ...b) {}"); parse("function f(a, b=0, ...c) {}"); parse("function f(a, b=0, c=1, ...d) {}"); parseError("function f(a=1, ...b=3) {}", "A default value cannot be specified after '...'"); } public void testClass1() { expectFeatures(Feature.CLASSES); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("class C {}"); mode = LanguageMode.ECMASCRIPT5; parseWarning("class C {}", getRequiresEs6Message(Feature.CLASSES)); mode = LanguageMode.ECMASCRIPT3; parseWarning("class C {}", getRequiresEs6Message(Feature.CLASSES)); } public void testClass2() { expectFeatures(Feature.CLASSES); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("class C {}"); parse("class C {\n" + " member() {}\n" + " get field() {}\n" + " set field(a) {}\n" + "}\n"); parse("class C {\n" + " static member() {}\n" + " static get field() {}\n" + " static set field(a) {}\n" + "}\n"); } public void testClass3() { expectFeatures(Feature.CLASSES); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("class C {\n" + " member() {};\n" + " get field() {};\n" + " set field(a) {};\n" + "}\n"); parse("class C {\n" + " static member() {};\n" + " static get field() {};\n" + " static set field(a) {};\n" + "}\n"); } public void testClassKeywordsAsMethodNames() { expectFeatures(Feature.CLASSES, Feature.KEYWORDS_AS_PROPERTIES); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse(Joiner.on('\n').join( "class KeywordMethods {", " continue() {}", " throw() {}", " else() {}", "}")); } public void testClassReservedWordsAsMethodNames() { expectFeatures(Feature.CLASSES, Feature.KEYWORDS_AS_PROPERTIES); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse( "class C {\n" + " import() {};\n" + " get break() {};\n" + " set break(a) {};\n" + "}\n"); parse( "class C {\n" + " static import() {};\n" + " static get break() {};\n" + " static set break(a) {};\n" + "}\n"); } public void testSuper1() { expectFeatures(Feature.SUPER); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; // TODO(johnlenz): super in global scope should be a syntax error parse("super;"); parse("function f() {super;};"); mode = LanguageMode.ECMASCRIPT5; parseWarning("super;", getRequiresEs6Message(Feature.SUPER)); mode = LanguageMode.ECMASCRIPT3; parseWarning("super;", getRequiresEs6Message(Feature.SUPER)); } public void testNewTarget() { expectFeatures(Feature.NEW_TARGET); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; // TODO(bradfordcsmith): new.target in global scope should be a syntax error parse("new.target;"); parse("function f() { new.target; };"); mode = LanguageMode.ECMASCRIPT5; parseWarning( "function f() { new.target; }", getRequiresEs6Message(Feature.NEW_TARGET)); mode = LanguageMode.ECMASCRIPT3; parseWarning( "function f() { new.target; }", getRequiresEs6Message(Feature.NEW_TARGET)); } public void testNewDotSomethingInvalid() { expectFeatures(Feature.NEW_TARGET); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("function f(){new.something}", "'target' expected"); } public void testArrow1() { expectFeatures(Feature.ARROW_FUNCTIONS); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("()=>1;"); parse("()=>{}"); parse("(a,b) => a + b;"); parse("a => b"); parse("a => { return b }"); parse("a => b"); parse("var x = (a => b);"); mode = LanguageMode.ECMASCRIPT5; parseWarning("a => b", getRequiresEs6Message(Feature.ARROW_FUNCTIONS)); mode = LanguageMode.ECMASCRIPT3; parseWarning("a => b;", getRequiresEs6Message(Feature.ARROW_FUNCTIONS)); } public void testArrowInvalid1() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("*()=>1;", "primary expression expected"); expectFeatures(Feature.ARROW_FUNCTIONS); parseError("var f = x\n=>2", "No newline allowed before '=>'"); parseError("f = (x,y)\n=>2;", "No newline allowed before '=>'"); parseError("f( (x,y)\n=>2)", "No newline allowed before '=>'"); } public void testInvalidAwait() { parseError("await 15;", "'await' used in a non-async function context"); parseError( "function f() { return await 5; }", "'await' used in a non-async function context"); } public void testAsyncFunction() { String asyncFunctionExpressionSource = "f = async function() {};"; String asyncFunctionDeclarationSource = "async function f() {}"; expectFeatures(Feature.ASYNC_FUNCTIONS); for (LanguageMode m : LanguageMode.values()) { mode = m; strictMode = (m == LanguageMode.ECMASCRIPT3) ? SLOPPY : STRICT; if (m.featureSet.contains(Feature.ASYNC_FUNCTIONS)) { parse(asyncFunctionExpressionSource); parse(asyncFunctionDeclarationSource); } else { parseWarning( asyncFunctionExpressionSource, requiresLanguageModeMessage(LanguageMode.ECMASCRIPT8, Feature.ASYNC_FUNCTIONS)); parseWarning( asyncFunctionDeclarationSource, requiresLanguageModeMessage(LanguageMode.ECMASCRIPT8, Feature.ASYNC_FUNCTIONS)); } } } public void testAsyncNamedFunction() { mode = LanguageMode.ECMASCRIPT6; expectFeatures(Feature.CLASSES, Feature.CONST_DECLARATIONS); parse(LINE_JOINER.join( "class C {", " async(x) { return x; }", "}", "const c = new C();", "c.async(1);", "let foo = async(5);")); } public void testInvalidAsyncFunction() { mode = LanguageMode.ECMASCRIPT8; strictMode = STRICT; expectFeatures(Feature.ASYNC_FUNCTIONS); parseError("async function *f(){}", "async functions cannot be generators"); parseError("f = async function *(){}", "async functions cannot be generators"); } public void testAsyncArrowFunction() { doAsyncArrowFunctionTest("f = async (x) => x + 1"); doAsyncArrowFunctionTest("f = async x => x + 1"); } private void doAsyncArrowFunctionTest(String arrowFunctionSource) { expectFeatures(Feature.ASYNC_FUNCTIONS, Feature.ARROW_FUNCTIONS); for (LanguageMode m : LanguageMode.values()) { mode = m; strictMode = (m == LanguageMode.ECMASCRIPT3) ? SLOPPY : STRICT; if (m.featureSet.contains(Feature.ASYNC_FUNCTIONS)) { parse(arrowFunctionSource); } else if (m.featureSet.contains(Feature.ARROW_FUNCTIONS)) { parseWarning( arrowFunctionSource, requiresLanguageModeMessage(LanguageMode.ECMASCRIPT8, Feature.ASYNC_FUNCTIONS)); } else { parseWarning( arrowFunctionSource, requiresLanguageModeMessage(LanguageMode.ECMASCRIPT6, Feature.ARROW_FUNCTIONS), requiresLanguageModeMessage(LanguageMode.ECMASCRIPT8, Feature.ASYNC_FUNCTIONS)); } } } public void testAsyncArrowInvalid() { mode = LanguageMode.ECMASCRIPT8; strictMode = STRICT; parseError("f = not_async (x) => x + 1;", "'=>' unexpected"); } public void testAsyncMethod() { mode = LanguageMode.ECMASCRIPT8; strictMode = STRICT; expectFeatures(Feature.ASYNC_FUNCTIONS); parse("o={async m(){}}"); parse("o={async [a+b](){}}"); parse("class C{async m(){}}"); parse("class C{static async m(){}}"); parse("class C{async [a+b](){}}"); parse("class C{static async [a+b](){}}"); } public void testInvalidAsyncMethod() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.MEMBER_DECLARATIONS); // 'async' allowed as a name parse("o={async(){}}"); parse("class C{async(){}}"); parse("class C{static async(){}}"); expectFeatures(); parse("o={async:false}"); parseError("class C{async};", "'(' expected"); // newline after 'async' forces it to be the property name mode = LanguageMode.ECMASCRIPT8; strictMode = STRICT; parseError("o={async\nm(){}}", "'}' expected"); parseError("o={static async\nm(){}}", "Cannot use keyword in short object literal"); parseError("class C{async\nm(){}}", "'(' expected"); parseError("class C{static async\nm(){}}", "'(' expected"); } public void testAwaitExpression() { mode = LanguageMode.ECMASCRIPT8; strictMode = STRICT; expectFeatures(Feature.ASYNC_FUNCTIONS); parse("async function f(p){await p}"); parse("f = async function(p){await p}"); parse("f = async(p)=>await p"); parse("class C{async m(p){await p}}"); parse("class C{static async m(p){await p}}"); // await must have an operand parseError("async function f() { await; }", "primary expression expected"); } public void testFor_ES5() { parse("for (var x; x != 10; x = next()) {}"); parse("for (var x; x != 10; x = next());"); parse("for (var x = 0; x != 10; x++) {}"); parse("for (var x = 0; x != 10; x++);"); parse("var x; for (x; x != 10; x = next()) {}"); parse("var x; for (x; x != 10; x = next());"); parseError("for (x in {};;) {}", "')' expected"); } public void testFor_ES6() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.LET_DECLARATIONS); parse("for (let x; x != 10; x = next()) {}"); parse("for (let x; x != 10; x = next());"); parse("for (let x = 0; x != 10; x++) {}"); parse("for (let x = 0; x != 10; x++);"); expectFeatures(Feature.CONST_DECLARATIONS); parseError("for (const x; x != 10; x = next()) {}", "const variables must have an initializer"); parseError("for (const x; x != 10; x = next());", "const variables must have an initializer"); parse("for (const x = 0; x != 10; x++) {}"); parse("for (const x = 0; x != 10; x++);"); } public void testForIn_ES6() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("for (a in b) c;"); parse("for (var a in b) c;"); expectFeatures(Feature.LET_DECLARATIONS); parse("for (let a in b) c;"); expectFeatures(Feature.CONST_DECLARATIONS); parse("for (const a in b) c;"); expectFeatures(); parseError("for (a,b in c) d;", INVALID_ASSIGNMENT_TARGET); parseError( "for (var a,b in c) d;", "for-in statement may not have more than one variable declaration"); parseError( "for (let a,b in c) d;", "for-in statement may not have more than one variable declaration"); parseError( "for (const a,b in c) d;", "for-in statement may not have more than one variable declaration"); parseError("for (a=1 in b) c;", INVALID_ASSIGNMENT_TARGET); parseError("for (let a=1 in b) c;", "for-in statement may not have initializer"); parseError("for (const a=1 in b) c;", "for-in statement may not have initializer"); parseError("for (var a=1 in b) c;", "for-in statement may not have initializer"); parseError("for (\"a\" in b) c;", INVALID_ASSIGNMENT_TARGET); } public void testForIn_ES5() { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; parse("for (a in b) c;"); parse("for (var a in b) c;"); parseError("for (a=1 in b) c;", INVALID_ASSIGNMENT_TARGET); parseWarning("for (var a=1 in b) c;", "for-in statement should not have initializer"); } public void testForInDestructuring() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.DESTRUCTURING); parse("for ({a} in b) c;"); parse("for (var {a} in b) c;"); expectFeatures(Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parse("for (let {a} in b) c;"); expectFeatures(Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parse("for (const {a} in b) c;"); expectFeatures(Feature.DESTRUCTURING); parse("for ({a: b} in c) d;"); parse("for (var {a: b} in c) d;"); expectFeatures(Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parse("for (let {a: b} in c) d;"); expectFeatures(Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parse("for (const {a: b} in c) d;"); expectFeatures(Feature.DESTRUCTURING); parse("for ([a] in b) c;"); parse("for (var [a] in b) c;"); expectFeatures(Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parse("for (let [a] in b) c;"); expectFeatures(Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parse("for (const [a] in b) c;"); expectFeatures(Feature.DESTRUCTURING); parseError("for ({a: b} = foo() in c) d;", INVALID_ASSIGNMENT_TARGET); parseError("for (var {a: b} = foo() in c) d;", "for-in statement may not have initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parseError("for (let {a: b} = foo() in c) d;", "for-in statement may not have initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parseError("for (const {a: b} = foo() in c) d;", "for-in statement may not have initializer"); expectFeatures(Feature.DESTRUCTURING); parseError("for ([a] = foo() in b) c;", INVALID_ASSIGNMENT_TARGET); parseError("for (var [a] = foo() in b) c;", "for-in statement may not have initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parseError("for (let [a] = foo() in b) c;", "for-in statement may not have initializer"); expectFeatures(Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parseError("for (const [a] = foo() in b) c;", "for-in statement may not have initializer"); } public void testForOf1() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.FOR_OF); parse("for(a of b) c;"); parse("for(var a of b) c;"); expectFeatures(Feature.FOR_OF, Feature.LET_DECLARATIONS); parse("for(let a of b) c;"); expectFeatures(Feature.FOR_OF, Feature.CONST_DECLARATIONS); parse("for(const a of b) c;"); } public void testForOf2() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("for(a=1 of b) c;", INVALID_ASSIGNMENT_TARGET); parseError("for(var a=1 of b) c;", "for-of statement may not have initializer"); parseError("for(let a=1 of b) c;", "for-of statement may not have initializer"); parseError("for(const a=1 of b) c;", "for-of statement may not have initializer"); } public void testForOf3() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("for(var a, b of c) d;", "for-of statement may not have more than one variable declaration"); parseError("for(let a, b of c) d;", "for-of statement may not have more than one variable declaration"); parseError("for(const a, b of c) d;", "for-of statement may not have more than one variable declaration"); } public void testForOf4() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parseError("for(a, b of c) d;", INVALID_ASSIGNMENT_TARGET); } public void testDestructuringInForLoops() { expectFeatures(Feature.DESTRUCTURING); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; // Destructuring forbids an initializer in for-in/for-of parseError("for (var {x: y} = foo() in bar()) {}", "for-in statement may not have initializer"); parseError("for (let {x: y} = foo() in bar()) {}", "for-in statement may not have initializer"); parseError("for (const {x: y} = foo() in bar()) {}", "for-in statement may not have initializer"); parseError("for (var {x: y} = foo() of bar()) {}", "for-of statement may not have initializer"); parseError("for (let {x: y} = foo() of bar()) {}", "for-of statement may not have initializer"); parseError("for (const {x: y} = foo() of bar()) {}", "for-of statement may not have initializer"); // but requires it in a vanilla for loop parseError("for (var {x: y};;) {}", "destructuring must have an initializer"); parseError("for (let {x: y};;) {}", "destructuring must have an initializer"); parseError("for (const {x: y};;) {}", "const variables must have an initializer"); } public void testInvalidDestructuring() { expectFeatures(Feature.DESTRUCTURING); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; // {x: 5} and {x: 'str'} are valid object literals but not valid patterns. parseError("for ({x: 5} in foo()) {}", INVALID_ASSIGNMENT_TARGET); parseError("for ({x: 'str'} in foo()) {}", INVALID_ASSIGNMENT_TARGET); parseError("var {x: 5} = foo();", INVALID_ASSIGNMENT_TARGET); parseError("var {x: 'str'} = foo();", INVALID_ASSIGNMENT_TARGET); parseError("({x: 5} = foo());", INVALID_ASSIGNMENT_TARGET); parseError("({x: 'str'} = foo());", INVALID_ASSIGNMENT_TARGET); // {method(){}} is a valid object literal but not a valid object pattern. parseError("function f({method(){}}) {}", "'}' expected"); parseError("function f({method(){}} = foo()) {}", "'}' expected"); } public void testForOfPatterns() { expectFeatures(Feature.FOR_OF, Feature.DESTRUCTURING); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("for({x} of b) c;"); parse("for({x: y} of b) c;"); parse("for([x, y] of b) c;"); parse("for([x, ...y] of b) c;"); expectFeatures(Feature.FOR_OF, Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parse("for(let {x} of b) c;"); parse("for(let {x: y} of b) c;"); parse("for(let [x, y] of b) c;"); parse("for(let [x, ...y] of b) c;"); expectFeatures(Feature.FOR_OF, Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parse("for(const {x} of b) c;"); parse("for(const {x: y} of b) c;"); parse("for(const [x, y] of b) c;"); parse("for(const [x, ...y] of b) c;"); expectFeatures(Feature.FOR_OF, Feature.DESTRUCTURING); parse("for(var {x} of b) c;"); parse("for(var {x: y} of b) c;"); parse("for(var [x, y] of b) c;"); parse("for(var [x, ...y] of b) c;"); } public void testForOfPatternsWithInitializer() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.FOR_OF, Feature.DESTRUCTURING); parseError("for({x}=a of b) c;", INVALID_ASSIGNMENT_TARGET); parseError("for({x: y}=a of b) c;", INVALID_ASSIGNMENT_TARGET); parseError("for([x, y]=a of b) c;", INVALID_ASSIGNMENT_TARGET); parseError("for([x, ...y]=a of b) c;", INVALID_ASSIGNMENT_TARGET); expectFeatures(Feature.FOR_OF, Feature.DESTRUCTURING, Feature.LET_DECLARATIONS); parseError("for(let {x}=a of b) c;", "for-of statement may not have initializer"); parseError("for(let {x: y}=a of b) c;", "for-of statement may not have initializer"); parseError("for(let [x, y]=a of b) c;", "for-of statement may not have initializer"); parseError("for(let [x, ...y]=a of b) c;", "for-of statement may not have initializer"); expectFeatures(Feature.FOR_OF, Feature.DESTRUCTURING, Feature.CONST_DECLARATIONS); parseError("for(const {x}=a of b) c;", "for-of statement may not have initializer"); parseError("for(const {x: y}=a of b) c;", "for-of statement may not have initializer"); parseError("for(const [x, y]=a of b) c;", "for-of statement may not have initializer"); parseError("for(const [x, ...y]=a of b) c;", "for-of statement may not have initializer"); } public void testImport() { expectFeatures(Feature.MODULES); mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; parse("import 'someModule'"); parse("import d from './someModule'"); parse("import {} from './someModule'"); parse("import {x, y} from './someModule'"); parse("import {x as x1, y as y1} from './someModule'"); parse("import {x as x1, y as y1, } from './someModule'"); parse("import {default as d, class as c} from './someModule'"); parse("import d, {x as x1, y as y1} from './someModule'"); parse("import * as sm from './someModule'"); expectFeatures(); parseError("import class from './someModule'", "cannot use keyword 'class' here."); parseError("import * as class from './someModule'", "'identifier' expected"); parseError("import {a as class} from './someModule'", "'identifier' expected"); parseError("import {class} from './someModule'", "'as' expected"); } public void testExport() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY; expectFeatures(Feature.MODULES); parse("export const x = 1"); parse("export var x = 1"); parse("export function f() {}"); parse("export class c {}"); parse("export {x, y}"); parse("export {x as x1}"); parse("export {x as x1, y as x2}"); parse("export {x as default, y as class}"); expectFeatures(); parseError("export {default as x}", "cannot use keyword 'default' here."); parseError("export {package as x}", "cannot use keyword 'package' here."); parseError("export {package}", "cannot use keyword 'package' here."); expectFeatures(Feature.MODULES); parse("export {x as x1, y as y1} from './someModule'"); parse("export {x as x1, y as y1, } from './someModule'"); parse("export {default as d} from './someModule'"); parse("export {d as default, c as class} from './someModule'"); parse("export {default as default, class as class} from './someModule'"); parse("export {class} from './someModule'"); parse("export * from './someModule'"); expectFeatures(); parseError("export * as s from './someModule';", "'from' expected"); } public void testGoogModule() { Node tree = parse("goog.module('example');"); assertNode(tree).hasType(Token.SCRIPT); assertThat(tree.getStaticSourceFile()).isNotNull(); assertNode(tree.getFirstChild()).hasType(Token.MODULE_BODY); assertThat(tree.getFirstChild().getStaticSourceFile()).isNotNull(); } public void testShebang() { parse("#!/usr/bin/node\n var x = 1;"); parseError("var x = 1; \n #!/usr/bin/node", "primary expression expected"); } public void testLookaheadGithubIssue699() { long start = System.currentTimeMillis(); parse( "[1,[1,[1,[1,[1,[1,\n" + "[1,[1,[1,[1,[1,[1,\n" + "[1,[1,[1,[1,[1,[1,\n" + "[1,[1,[1,[1,[1,[1,\n" + "[1,[1,[1,[1,[1,[1,\n" + "[1,[1,\n" + "[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] "); long stop = System.currentTimeMillis(); assertThat(stop - start).named("runtime").isLessThan(5000L); } public void testInvalidHandling1() { parse("" + "/**\n" + " * @fileoverview Definition.\n" + " * @mods {ns.bar}\n" + " * @modName mod\n" + " *\n" + " * @extends {ns.bar}\n" + " * @author someone\n" + " */\n" + "\n" + "goog.provide('ns.foo');\n" + ""); } public void testUtf8() { mode = LanguageMode.ECMASCRIPT5; strictMode = SLOPPY; Node n = parse("\uFEFFfunction f() {}\n"); Node fn = n.getFirstChild(); assertNode(fn).hasType(Token.FUNCTION); } public void testParseDeep1() { String code = "var x; x = \n"; for (int i = 1; i < 15000; i++) { code += " \'" + i + "\' +\n"; } code += "\'end\';n"; parse(code); } public void testParseDeep2() { String code = "var x; x = \n"; for (int i = 1; i < 15000; i++) { code += " \'" + i + "\' +\n"; } code += "\'end\'; /** a comment */\n"; parse(code); } public void testParseDeep3() { String code = "var x; x = \n"; for (int i = 1; i < 15000; i++) { code += " \'" + i + "\' +\n"; } code += " /** @type {string} */ (x);\n"; parse(code); } public void testParseDeep4() { // Currently, we back off if there is any JSDoc in the tree of binary expressions String code = "var x; x = \n"; for (int i = 1; i < 15000; i++) { if (i == 5) { code += " /** @type {string} */ (x) +\n"; } code += " \'" + i + "\' +\n"; } code += "\'end\';n"; try { parse(code); fail(); } catch (java.lang.StackOverflowError e) { // expected exception } } public void testParseInlineSourceMap() { String code = "var X = (function () {\n" + " function X(input) {\n" + " this.y = input;\n" + " }\n" + " return X;\n" + "}());\n" + "console.log(new X(1));\n" + "//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9vLmpz" + "Iiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZm9vLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQU" + "FBO0lBR0UsV0FBWSxLQUFhO1FBQ3ZCLElBQUksQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ2pCLENBQUM7" + "SUFDSCxRQUFDO0FBQUQsQ0FBQyxBQU5ELElBTUM7QUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQy" + "xDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMifQ=="; ParseResult result = doParse(code); assertThat(result.sourceMap).named("inline source map").isNotNull(); SourceMapInput input = new SourceMapInput(SourceFile.fromCode("test.js.map", result.sourceMap)); SourceMapConsumerV3 sourceMap = input.getSourceMap(); assertThat(sourceMap.getOriginalSources()).containsExactly("foo.ts"); } private static String getRequiresEs6Message(Feature feature) { return requiresLanguageModeMessage(LanguageMode.ECMASCRIPT6, feature); } private static String requiresLanguageModeMessage(LanguageMode languageMode, Feature feature) { return String.format( "this language feature is only supported for %s mode or better: %s", languageMode, feature); } private static Node script(Node stmt) { Node n = new Node(Token.SCRIPT, stmt); return n; } private static Node expr(Node n) { return new Node(Token.EXPR_RESULT, n); } private static Node regex(String regex) { return new Node(Token.REGEXP, Node.newString(regex)); } /** * Verify that the given code has the given parse errors. * @return If in IDE mode, returns a partial tree. */ private Node parseError(String source, String... errors) { TestErrorReporter testErrorReporter = new TestErrorReporter(errors, null); ParseResult result = ParserRunner.parse( new SimpleSourceFile("input", false), source, createConfig(), testErrorReporter); Node script = result.ast; // check expected features if specified if (expectedFeatures != null) { assertThat(result.features).isEqualTo(expectedFeatures); } // verifying that all errors were seen testErrorReporter.assertHasEncounteredAllErrors(); testErrorReporter.assertHasEncounteredAllWarnings(); return script; } /** * Verify that the given code has the given parse warnings. * @return The parse tree. */ private Node parseWarning(String string, String... warnings) { return doParse(string, warnings).ast; } private ParserRunner.ParseResult doParse(String string, String... warnings) { TestErrorReporter testErrorReporter = new TestErrorReporter(null, warnings); StaticSourceFile file = new SimpleSourceFile("input", false); ParserRunner.ParseResult result = ParserRunner.parse( file, string, createConfig(), testErrorReporter); // check expected features if specified if (expectedFeatures != null) { assertThat(result.features).isEqualTo(expectedFeatures); } // verifying that all warnings were seen testErrorReporter.assertHasEncounteredAllErrors(); testErrorReporter.assertHasEncounteredAllWarnings(); return result; } /** * Verify that the given code has no parse warnings or errors. * @return The parse tree. */ private Node parse(String string) { return parseWarning(string); } private Config createConfig() { if (isIdeMode) { return ParserRunner.createConfig( mode, Config.JsDocParsing.INCLUDE_DESCRIPTIONS_NO_WHITESPACE, Config.RunMode.KEEP_GOING, null, true, strictMode); } else { return ParserRunner.createConfig(mode, null, strictMode); } } /** Sets expectedFeatures based on the list of features. */ private void expectFeatures(Feature... features) { expectedFeatures = FeatureSet.ES3; for (Feature feature : features) { expectedFeatures = expectedFeatures.require(feature); } } private static class ParserResult { private final String code; private final Node node; private ParserResult(String code, Node node) { this.code = code; this.node = node; } } }