/* * Copyright 2007 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 com.google.common.collect.ImmutableList; import com.google.javascript.jscomp.parsing.Config.LanguageMode; import com.google.javascript.jscomp.testing.TestErrorReporter; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.head.ScriptRuntime; import com.google.javascript.rhino.jstype.SimpleSourceFile; import com.google.javascript.rhino.jstype.StaticSourceFile; import com.google.javascript.rhino.testing.BaseJSTypeTestCase; import java.io.IOException; import java.util.List; import java.util.logging.Logger; public class ParserTest extends BaseJSTypeTestCase { private static final String SUSPICIOUS_COMMENT_WARNING = IRFactory.SUSPICIOUS_COMMENT_WARNING; private static final String TRAILING_COMMA_MESSAGE = ScriptRuntime.getMessage0("msg.extra.trailing.comma"); private static final String BAD_PROPERTY_MESSAGE = ScriptRuntime.getMessage0("msg.bad.prop"); private static final String MISSING_GT_MESSAGE = "Bad type annotation. " + com.google.javascript.rhino.ScriptRuntime.getMessage0( "msg.jsdoc.missing.gt"); private static final String MISPLACED_TYPE_ANNOTATION = IRFactory.MISPLACED_TYPE_ANNOTATION; private Config.LanguageMode mode; private boolean isIdeMode = false; @Override protected void setUp() throws Exception { super.setUp(); mode = LanguageMode.ECMASCRIPT3; isIdeMode = false; } public void testLinenoCharnoAssign1() throws Exception { Node assign = parse("a = b").getFirstChild().getFirstChild(); assertEquals(Token.ASSIGN, assign.getType()); assertEquals(1, assign.getLineno()); assertEquals(0, assign.getCharno()); } public void testLinenoCharnoAssign2() throws Exception { Node assign = parse("\n a.g.h.k = 45").getFirstChild().getFirstChild(); assertEquals(Token.ASSIGN, assign.getType()); assertEquals(2, assign.getLineno()); assertEquals(1, assign.getCharno()); } public void testLinenoCharnoCall() throws Exception { Node call = parse("\n foo(123);").getFirstChild().getFirstChild(); assertEquals(Token.CALL, call.getType()); assertEquals(2, call.getLineno()); assertEquals(1, call.getCharno()); } public void testLinenoCharnoGetProp1() throws Exception { Node getprop = parse("\n foo.bar").getFirstChild().getFirstChild(); assertEquals(Token.GETPROP, getprop.getType()); assertEquals(2, getprop.getLineno()); assertEquals(1, getprop.getCharno()); Node name = getprop.getFirstChild().getNext(); assertEquals(Token.STRING, name.getType()); assertEquals(2, name.getLineno()); assertEquals(5, name.getCharno()); } public void testLinenoCharnoGetProp2() throws Exception { Node getprop = parse("\n foo.\nbar").getFirstChild().getFirstChild(); assertEquals(Token.GETPROP, getprop.getType()); assertEquals(2, getprop.getLineno()); assertEquals(1, getprop.getCharno()); Node name = getprop.getFirstChild().getNext(); assertEquals(Token.STRING, name.getType()); assertEquals(3, name.getLineno()); assertEquals(0, name.getCharno()); } public void testLinenoCharnoGetelem1() throws Exception { Node call = parse("\n foo[123]").getFirstChild().getFirstChild(); assertEquals(Token.GETELEM, call.getType()); assertEquals(2, call.getLineno()); assertEquals(1, call.getCharno()); } public void testLinenoCharnoGetelem2() throws Exception { Node call = parse("\n \n foo()[123]").getFirstChild().getFirstChild(); assertEquals(Token.GETELEM, call.getType()); assertEquals(3, call.getLineno()); assertEquals(1, call.getCharno()); } public void testLinenoCharnoGetelem3() throws Exception { Node call = parse("\n \n (8 + kl)[123]").getFirstChild().getFirstChild(); assertEquals(Token.GETELEM, call.getType()); assertEquals(3, call.getLineno()); assertEquals(2, call.getCharno()); } public void testLinenoCharnoForComparison() throws Exception { Node lt = parse("for (; i < j;){}").getFirstChild().getFirstChild().getNext(); assertEquals(Token.LT, lt.getType()); assertEquals(1, lt.getLineno()); assertEquals(7, lt.getCharno()); } public void testLinenoCharnoHook() throws Exception { Node n = parse("\n a ? 9 : 0").getFirstChild().getFirstChild(); assertEquals(Token.HOOK, n.getType()); assertEquals(2, n.getLineno()); assertEquals(1, n.getCharno()); } public void testLinenoCharnoArrayLiteral() throws Exception { Node n = parse("\n [8, 9]").getFirstChild().getFirstChild(); assertEquals(Token.ARRAYLIT, n.getType()); assertEquals(2, n.getLineno()); assertEquals(2, n.getCharno()); n = n.getFirstChild(); assertEquals(Token.NUMBER, n.getType()); assertEquals(2, n.getLineno()); assertEquals(3, n.getCharno()); n = n.getNext(); assertEquals(Token.NUMBER, n.getType()); assertEquals(2, n.getLineno()); assertEquals(6, n.getCharno()); } public void testLinenoCharnoObjectLiteral() throws Exception { Node n = parse("\n\n var a = {a:0\n,b :1};") .getFirstChild().getFirstChild().getFirstChild(); assertEquals(Token.OBJECTLIT, n.getType()); assertEquals(3, n.getLineno()); assertEquals(9, n.getCharno()); Node key = n.getFirstChild(); assertEquals(Token.STRING_KEY, key.getType()); assertEquals(3, key.getLineno()); assertEquals(10, key.getCharno()); Node value = key.getFirstChild(); assertEquals(Token.NUMBER, value.getType()); assertEquals(3, value.getLineno()); assertEquals(12, value.getCharno()); key = key.getNext(); assertEquals(Token.STRING_KEY, key.getType()); assertEquals(4, key.getLineno()); assertEquals(1, key.getCharno()); value = key.getFirstChild(); assertEquals(Token.NUMBER, value.getType()); assertEquals(4, value.getLineno()); assertEquals(4, value.getCharno()); } 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(). getFirstChild().getFirstChild(); assertEquals(1, op.getLineno()); assertEquals(8, op.getCharno()); } public void testJSDocAttachment1() { Node varNode = parse("/** @type number */var a;").getFirstChild(); // VAR assertEquals(Token.VAR, varNode.getType()); JSDocInfo info = varNode.getJSDocInfo(); assertNotNull(info); assertTypeEquals(NUMBER_TYPE, info.getType()); // NAME Node nameNode = varNode.getFirstChild(); assertEquals(Token.NAME, nameNode.getType()); assertNull(nameNode.getJSDocInfo()); } public void testJSDocAttachment2() { Node varNode = parse("/** @type number */var a,b;").getFirstChild(); // VAR assertEquals(Token.VAR, varNode.getType()); JSDocInfo info = varNode.getJSDocInfo(); assertNotNull(info); assertTypeEquals(NUMBER_TYPE, info.getType()); // First NAME Node nameNode1 = varNode.getFirstChild(); assertEquals(Token.NAME, nameNode1.getType()); assertNull(nameNode1.getJSDocInfo()); // Second NAME Node nameNode2 = nameNode1.getNext(); assertEquals(Token.NAME, nameNode2.getType()); assertNull(nameNode2.getJSDocInfo()); } public void testJSDocAttachment3() { Node assignNode = parse( "/** @type number */goog.FOO = 5;").getFirstChild().getFirstChild(); // ASSIGN assertEquals(Token.ASSIGN, assignNode.getType()); JSDocInfo info = assignNode.getJSDocInfo(); assertNotNull(info); assertTypeEquals(NUMBER_TYPE, info.getType()); } public void testJSDocAttachment4() { Node varNode = parse( "var a, /** @define {number} */b = 5;").getFirstChild(); // ASSIGN assertEquals(Token.VAR, varNode.getType()); assertNull(varNode.getJSDocInfo()); // a Node a = varNode.getFirstChild(); assertNull(a.getJSDocInfo()); // b Node b = a.getNext(); JSDocInfo info = b.getJSDocInfo(); assertNotNull(info); assertTrue(info.isDefine()); assertTypeEquals(NUMBER_TYPE, info.getType()); } public void testJSDocAttachment5() { Node varNode = parse( "var /** @type number */a, /** @define {number} */b = 5;") .getFirstChild(); // ASSIGN assertEquals(Token.VAR, varNode.getType()); assertNull(varNode.getJSDocInfo()); // a Node a = varNode.getFirstChild(); assertNotNull(a.getJSDocInfo()); JSDocInfo info = a.getJSDocInfo(); assertNotNull(info); assertFalse(info.isDefine()); assertTypeEquals(NUMBER_TYPE, info.getType()); // b Node b = a.getNext(); info = b.getJSDocInfo(); assertNotNull(info); assertTrue(info.isDefine()); 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){}") .getFirstChild().getNext(); assertEquals(Token.FUNCTION, functionNode.getType()); JSDocInfo info = functionNode.getJSDocInfo(); assertNotNull(info); assertFalse(info.hasParameter("index")); assertTrue(info.hasReturnType()); assertTypeEquals(UNKNOWN_TYPE, info.getReturnType()); } public void testJSDocAttachment7() { Node varNode = parse("/** */var a;").getFirstChild(); // VAR assertEquals(Token.VAR, varNode.getType()); // NAME Node nameNode = varNode.getFirstChild(); assertEquals(Token.NAME, nameNode.getType()); assertNull(nameNode.getJSDocInfo()); } public void testJSDocAttachment8() { Node varNode = parse("/** x */var a;").getFirstChild(); // VAR assertEquals(Token.VAR, varNode.getType()); // NAME Node nameNode = varNode.getFirstChild(); assertEquals(Token.NAME, nameNode.getType()); assertNull(nameNode.getJSDocInfo()); } public void testJSDocAttachment9() { Node varNode = parse("/** \n x */var a;").getFirstChild(); // VAR assertEquals(Token.VAR, varNode.getType()); // NAME Node nameNode = varNode.getFirstChild(); assertEquals(Token.NAME, nameNode.getType()); assertNull(nameNode.getJSDocInfo()); } public void testJSDocAttachment10() { Node varNode = parse("/** x\n */var a;").getFirstChild(); // VAR assertEquals(Token.VAR, varNode.getType()); // NAME Node nameNode = varNode.getFirstChild(); assertEquals(Token.NAME, nameNode.getType()); assertNull(nameNode.getJSDocInfo()); } public void testJSDocAttachment11() { Node varNode = parse("/** @type {{x : number, 'y' : string, z}} */var a;") .getFirstChild(); // VAR assertEquals(Token.VAR, varNode.getType()); JSDocInfo info = varNode.getJSDocInfo(); assertNotNull(info); 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(); assertEquals(Token.NAME, nameNode.getType()); assertNull(nameNode.getJSDocInfo()); } public void testJSDocAttachment12() { Node varNode = parse("var a = {/** @type {Object} */ b: c};") .getFirstChild(); Node objectLitNode = varNode.getFirstChild().getFirstChild(); assertEquals(Token.OBJECTLIT, objectLitNode.getType()); assertNotNull(objectLitNode.getFirstChild().getJSDocInfo()); } public void testJSDocAttachment13() { Node varNode = parse("/** foo */ var a;").getFirstChild(); assertNotNull(varNode.getJSDocInfo()); } public void testJSDocAttachment14() { Node varNode = parse("/** */ var a;").getFirstChild(); assertNull(varNode.getJSDocInfo()); } public void testJSDocAttachment15() { Node varNode = parse("/** \n * \n */ var a;").getFirstChild(); assertNull(varNode.getJSDocInfo()); } public void testJSDocAttachment16() { Node exprCall = parse("/** @private */ x(); function f() {};").getFirstChild(); assertEquals(Token.EXPR_RESULT, exprCall.getType()); assertNull(exprCall.getNext().getJSDocInfo()); assertNotNull(exprCall.getFirstChild().getJSDocInfo()); } public void testIncorrectJSDocDoesNotAlterJSParsing1() throws Exception { assertNodeEquality( parse("var a = [1,2]"), parse("/** @type Array. 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))), new ParserResult( "\"hell\\\no\\ world\\\n\\\n!\"", createScript(new Node(Token.EXPR_RESULT, Node.newString(Token.STRING, "hello world!"))))); for (ParserResult testCase : testCases) { assertNodeEquality(testCase.node, parse(testCase.code)); } } private Node createScript(Node n) { Node script = new Node(Token.SCRIPT); script.addChildToBack(n); return script; } public void testTrailingCommaWarning1() { parse("var a = ['foo', 'bar'];"); } public void testTrailingCommaWarning2() { parse("var a = ['foo',,'bar'];"); } public void testTrailingCommaWarning3() { parse("var a = ['foo', 'bar',];", TRAILING_COMMA_MESSAGE); mode = LanguageMode.ECMASCRIPT5; parse("var a = ['foo', 'bar',];"); } public void testTrailingCommaWarning4() { parse("var a = [,];", TRAILING_COMMA_MESSAGE); mode = LanguageMode.ECMASCRIPT5; parse("var a = [,];"); } public void testTrailingCommaWarning5() { parse("var a = {'foo': 'bar'};"); } public void testTrailingCommaWarning6() { parse("var a = {'foo': 'bar',};", TRAILING_COMMA_MESSAGE); mode = LanguageMode.ECMASCRIPT5; parse("var a = {'foo': 'bar',};"); } public void testTrailingCommaWarning7() { parseError("var a = {,};", BAD_PROPERTY_MESSAGE); } public void testSuspiciousBlockCommentWarning1() { parse("/* @type {number} */ var x = 3;", SUSPICIOUS_COMMENT_WARNING); } public void testSuspiciousBlockCommentWarning2() { parse("/* \n * @type {number} */ var x = 3;", SUSPICIOUS_COMMENT_WARNING); } public void testCatchClauseForbidden() { parseError("try { } catch (e if true) {}", "Catch clauses are not supported"); } public void testConstForbidden() { parseError("const x = 3;", "Unsupported syntax: CONST"); } public void testDestructuringAssignForbidden() { parseError("var [x, y] = foo();", "destructuring assignment forbidden"); } public void testDestructuringAssignForbidden2() { parseError("var {x, y} = foo();", "missing : after property id"); } public void testDestructuringAssignForbidden3() { parseError("var {x: x, y: y} = foo();", "destructuring assignment forbidden"); } public void testDestructuringAssignForbidden4() { parseError("[x, y] = foo();", "destructuring assignment forbidden", "invalid assignment target"); } public void testLetForbidden() { parseError("function f() { let (x = 3) { alert(x); }; }", "missing ; before statement", "syntax error"); } public void testYieldForbidden() { parseError("function f() { yield 3; }", "missing ; before statement"); } public void testBracelessFunctionForbidden() { parseError("var sq = function(x) x * x;", "missing { before function body"); } public void testGeneratorsForbidden() { parseError("var i = (x for (x in obj));", "Unsupported syntax: GENEXPR"); } public void testGettersForbidden1() { parseError("var x = {get foo() { return 3; }};", IRFactory.GETTER_ERROR_MESSAGE); } public void testGettersForbidden2() { parseError("var x = {get foo bar() { return 3; }};", "invalid property id"); } public void testGettersForbidden3() { parseError("var x = {a getter:function b() { return 3; }};", "missing : after property id", "syntax error"); } public void testGettersForbidden4() { parseError("var x = {\"a\" getter:function b() { return 3; }};", "missing : after property id", "syntax error"); } public void testGettersForbidden5() { parseError("var x = {a: 2, get foo() { return 3; }};", IRFactory.GETTER_ERROR_MESSAGE); } public void testSettersForbidden() { parseError("var x = {set foo() { return 3; }};", IRFactory.SETTER_ERROR_MESSAGE); } public void testSettersForbidden2() { parseError("var x = {a setter:function b() { return 3; }};", "missing : after property id", "syntax error"); } public void testFileOverviewJSDoc1() { Node n = parse("/** @fileoverview Hi mom! */ function Foo() {}"); assertEquals(Token.FUNCTION, n.getFirstChild().getType()); assertTrue(n.getJSDocInfo() != null); assertNull(n.getFirstChild().getJSDocInfo()); assertEquals("Hi mom!", n.getJSDocInfo().getFileOverview()); } public void testFileOverviewJSDocDoesNotHoseParsing() { assertEquals( Token.FUNCTION, parse("/** @fileoverview Hi mom! \n */ function Foo() {}") .getFirstChild().getType()); assertEquals( Token.FUNCTION, parse("/** @fileoverview Hi mom! \n * * * */ function Foo() {}") .getFirstChild().getType()); assertEquals( Token.FUNCTION, parse("/** @fileoverview \n * x */ function Foo() {}") .getFirstChild().getType()); assertEquals( Token.FUNCTION, parse("/** @fileoverview \n * x \n */ function Foo() {}") .getFirstChild().getType()); } public void testFileOverviewJSDoc2() { Node n = parse("/** @fileoverview Hi mom! */ " + "/** @constructor */ function Foo() {}"); assertTrue(n.getJSDocInfo() != null); assertEquals("Hi mom!", n.getJSDocInfo().getFileOverview()); assertTrue(n.getFirstChild().getJSDocInfo() != null); assertFalse(n.getFirstChild().getJSDocInfo().hasFileOverview()); assertTrue(n.getFirstChild().getJSDocInfo().isConstructor()); } public void testObjectLiteralDoc1() { Node n = parse("var x = {/** @type {number} */ 1: 2};"); Node objectLit = n.getFirstChild().getFirstChild().getFirstChild(); assertEquals(Token.OBJECTLIT, objectLit.getType()); Node number = objectLit.getFirstChild(); assertEquals(Token.STRING_KEY, number.getType()); assertNotNull(number.getJSDocInfo()); } public void testDuplicatedParam() { parse("function foo(x, x) {}", "Duplicate parameter name \"x\"."); } public void testGetter() { mode = LanguageMode.ECMASCRIPT3; 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(){}};"); parseError("var x = {get a(b){}};", "getters may not have parameters"); } public void testSetter() { mode = LanguageMode.ECMASCRIPT3; 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){}};"); parseError("var x = {set a(){}};", "setters must have exactly one parameter"); } 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 testParseBlockDescription() { Node n = parse("/** This is a variable. */ var x;"); Node var = n.getFirstChild(); assertNotNull(var.getJSDocInfo()); assertEquals("This is a variable.", var.getJSDocInfo().getBlockDescription()); } public void testUnnamedFunctionStatement() { // Statements parseError("function() {};", "unnamed function statement"); parseError("if (true) { function() {}; }", "unnamed function statement"); parse("function f() {};"); // Expressions parse("(function f() {});"); parse("(function () {});"); } public void testReservedKeywords() { boolean isIdeMode = false; mode = LanguageMode.ECMASCRIPT3; parseError("var boolean;", "missing variable name"); parseError("function boolean() {};", "missing ( before function parameters."); parseError("boolean = 1;", "identifier is a reserved word"); parseError("class = 1;", "identifier is a reserved word"); parseError("public = 2;", "identifier is a reserved word"); mode = LanguageMode.ECMASCRIPT5; parse("var boolean;"); parse("function boolean() {};"); parse("boolean = 1;"); parseError("class = 1;", "identifier is a reserved word"); parse("public = 2;"); mode = LanguageMode.ECMASCRIPT5_STRICT; parse("var boolean;"); parse("function boolean() {};"); parse("boolean = 1;"); parseError("class = 1;", "identifier is a reserved word"); parseError("public = 2;", "identifier is a reserved word"); } public void testKeywordsAsProperties() { boolean isIdeMode = false; mode = LanguageMode.ECMASCRIPT3; parseError("var x = {function: 1};", "invalid property id"); parseError("x.function;", "missing name after . operator"); parseError("var x = {get x(){} };", IRFactory.GETTER_ERROR_MESSAGE); parseError("var x = {get function(){} };", "invalid property id"); parseError("var x = {get 'function'(){} };", IRFactory.GETTER_ERROR_MESSAGE); parseError("var x = {get 1(){} };", IRFactory.GETTER_ERROR_MESSAGE); parseError("var x = {set function(a){} };", "invalid property id"); parseError("var x = {set 'function'(a){} };", IRFactory.SETTER_ERROR_MESSAGE); parseError("var x = {set 1(a){} };", IRFactory.SETTER_ERROR_MESSAGE); parseError("var x = {class: 1};", "invalid property id"); parseError("x.class;", "missing name after . operator"); parse("var x = {let: 1};"); parse("x.let;"); parse("var x = {yield: 1};"); parse("x.yield;"); 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;"); parse("var x = {let: 1};"); parse("x.let;"); parse("var x = {yield: 1};"); parse("x.yield;"); mode = LanguageMode.ECMASCRIPT5_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;"); parse("var x = {let: 1};"); parse("x.let;"); parse("var x = {yield: 1};"); parse("x.yield;"); } public void testGetPropFunctionName() { parseError("function a.b() {}", "missing ( before function parameters."); parseError("var x = function a.b() {}", "missing ( before function parameters."); } 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() {}", "missing ( before function parameters.", "missing formal parameter", "missing ) after formal parameters", "missing { before function body", "syntax error", "missing ; before statement", "missing ; before statement", "missing } after function body", "Unsupported syntax: ERROR", "Unsupported syntax: ERROR"); parseError("var x = function a.b() {}", "missing ( before function parameters.", "missing formal parameter", "missing ) after formal parameters", "missing { before function body", "syntax error", "missing ; before statement", "missing ; before statement", "missing } after function body", "Unsupported syntax: ERROR", "Unsupported syntax: ERROR"); } public void testIdeModePartialTree() { Node partialTree = parseError("function Foo() {} f.", "missing name after . operator"); assertNull(partialTree); isIdeMode = true; partialTree = parseError("function Foo() {} f.", "missing name after . operator"); assertNotNull(partialTree); } 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" + "};", "unsupported language extension: for each"); } public void testMisplacedTypeAnnotation1() { // misuse with COMMA parse( "var o = {};" + "/** @type {string} */ o.prop1 = 1, o.prop2 = 2;", MISPLACED_TYPE_ANNOTATION); } public void testMisplacedTypeAnnotation2() { // missing parenthese for the cast. parse( "var o = /** @type {string} */ getValue();", MISPLACED_TYPE_ANNOTATION); } public void testMisplacedTypeAnnotation3() { // missing parenthese for the cast. parse( "var o = 1 + /** @type {string} */ value;", MISPLACED_TYPE_ANNOTATION); } public void testMisplacedTypeAnnotation4() { // missing parenthese for the cast. parse( "var o = /** @type {!Array.} */ ['hello', 'you'];", MISPLACED_TYPE_ANNOTATION); } public void testMisplacedTypeAnnotation5() { // missing parenthese for the cast. parse( "var o = (/** @type {!Foo} */ {});", MISPLACED_TYPE_ANNOTATION); } public void testMisplacedTypeAnnotation6() { parse("var o = /** @type {function():string} */ function() {return 'str';}", MISPLACED_TYPE_ANNOTATION); } 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; parse("var o = { /** @type {string} */ get prop() { return 'str' }};"); parse("var o = { /** @type {string} */ set prop(s) {}};"); } public void testValidTypeAnnotation3() { // These two we don't currently support in the type checker but // we would like to. parse("try {} catch (/** @type {Error} */ e) {}"); parse("function f(/** @type {string} */ a) {}"); } /** * Verify that the given code has the given parse errors. * @return If in IDE mode, returns a partial tree. */ private Node parseError(String string, String... errors) { TestErrorReporter testErrorReporter = new TestErrorReporter(errors, null); Node script = null; try { StaticSourceFile file = new SimpleSourceFile("input", false); script = ParserRunner.parse( file, string, ParserRunner.createConfig(isIdeMode, mode, false), testErrorReporter, Logger.getAnonymousLogger()).ast; } catch (IOException e) { throw new RuntimeException(e); } // verifying that all warnings were seen assertTrue(testErrorReporter.hasEncounteredAllErrors()); assertTrue(testErrorReporter.hasEncounteredAllWarnings()); return script; } private Node parse(String string, String... warnings) { TestErrorReporter testErrorReporter = new TestErrorReporter(null, warnings); Node script = null; try { StaticSourceFile file = new SimpleSourceFile("input", false); script = ParserRunner.parse( file, string, ParserRunner.createConfig(true, mode, false), testErrorReporter, Logger.getAnonymousLogger()).ast; } catch (IOException e) { throw new RuntimeException(e); } // verifying that all warnings were seen assertTrue(testErrorReporter.hasEncounteredAllErrors()); assertTrue(testErrorReporter.hasEncounteredAllWarnings()); return script; } private static class ParserResult { private final String code; private final Node node; private ParserResult(String code, Node node) { this.code = code; this.node = node; } } }