/*
 * Copyright 2009 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.Sets;
import com.google.javascript.jscomp.SourceFile;
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.head.CompilerEnvirons;
import com.google.javascript.rhino.head.Parser;
import com.google.javascript.rhino.head.ast.AstRoot;
import com.google.javascript.rhino.testing.BaseJSTypeTestCase;

/**
 * Tests {@link IRFactory}.
 */
public class IRFactoryTest extends BaseJSTypeTestCase {

  private LanguageMode mode = LanguageMode.ECMASCRIPT3;

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    mode = LanguageMode.ECMASCRIPT3;
  }

  public void testStrictScript() throws Exception {
    assertNull(newParse("").getDirectives());
    assertEquals(
        Sets.newHashSet("use strict"),
        newParse("'use strict'").getDirectives());
  }

  public void testArrayLiteral2() throws Exception {
    testNewParser("[a, , b]",
      "SCRIPT 1 [source_file: FileName.js] [length: 8]\n" +
      "    EXPR_RESULT 1 [source_file: FileName.js] [length: 8]\n" +
      "        ARRAYLIT 1 [source_file: FileName.js] [length: 8]\n" +
      "            NAME a 1 [source_file: FileName.js] [length: 1]\n" +
      "            EMPTY 1 [source_file: FileName.js] [length: 1]\n" +
      "            NAME b 1 [source_file: FileName.js] [length: 1]\n");
  }

  public void testArrayLiteral4() throws Exception {
    testNewParser("[,,,a,,b]",
      "SCRIPT 1 [source_file: FileName.js] [length: 9]\n" +
      "    EXPR_RESULT 1 [source_file: FileName.js] [length: 9]\n" +
      "        ARRAYLIT 1 [source_file: FileName.js] [length: 9]\n" +
      "            EMPTY 1 [source_file: FileName.js] [length: 1]\n" +
      "            EMPTY 1 [source_file: FileName.js] [length: 1]\n" +
      "            EMPTY 1 [source_file: FileName.js] [length: 1]\n" +
      "            NAME a 1 [source_file: FileName.js] [length: 1]\n" +
      "            EMPTY 1 [source_file: FileName.js] [length: 1]\n" +
      "            NAME b 1 [source_file: FileName.js] [length: 1]\n");
  }

  public void testObjectLiteral() {
    newParse("var o = {}");
  }

  public void testObjectLiteral2() {
    newParse("var o = {a: 1}");
  }

  public void testObjectLiteral3() {
    newParse("var o = {a: 1, b: 2}");
  }

  public void testObjectLiteral4() {
    newParse("var o = {1: 'a'}");
  }

  public void testObjectLiteral5() {
    newParse("var o = {'a': 'a'}");
  }

  public void testObjectLiteral6() {
    testNewParser("({1: true})",
      "SCRIPT 1 [source_file: FileName.js] [length: 11]\n" +
      "    EXPR_RESULT 1 [source_file: FileName.js] [length: 10]\n" +
      "        OBJECTLIT 1 [source_file: FileName.js] [length: 9]\n" +
      "            STRING_KEY 1 1 [quoted: 1] [source_file: FileName.js] [length: 1]\n" +
      "                TRUE 1 [source_file: FileName.js] [length: 4]\n");
  }

  public void testObjectLiteral7() {
    mode = LanguageMode.ECMASCRIPT5;

    testNewParser("({get 1() {}})",
        "SCRIPT 1 [source_file: FileName.js] [length: 14]\n" +
        "    EXPR_RESULT 1 [source_file: FileName.js] [length: 13]\n" +
        "        OBJECTLIT 1 [source_file: FileName.js] [length: 12]\n" +
        "            GETTER_DEF 1 1 [quoted: 1] [source_file: FileName.js] [length: 1]\n" +
        "                FUNCTION  1 [source_file: FileName.js] [length: 6]\n" +
        "                    NAME  1 [source_file: FileName.js]\n" +
        "                    PARAM_LIST 1 [source_file: FileName.js]\n" +
        "                    BLOCK 1 [source_file: FileName.js] [length: 2]\n");
  }

  public void testObjectLiteral8() {
    mode = LanguageMode.ECMASCRIPT5;

    testNewParser("({set 1(a) {}})",
        "SCRIPT 1 [source_file: FileName.js] [length: 15]\n" +
        "    EXPR_RESULT 1 [source_file: FileName.js] [length: 14]\n" +
        "        OBJECTLIT 1 [source_file: FileName.js] [length: 13]\n" +
        "            SETTER_DEF 1 1 [quoted: 1] [source_file: FileName.js] [length: 1]\n" +
        "                FUNCTION  1 [source_file: FileName.js] [length: 7]\n" +
        "                    NAME  1 [source_file: FileName.js]\n" +
        "                    PARAM_LIST 1 [source_file: FileName.js]\n" +
        "                        NAME a 1 [source_file: FileName.js] [length: 1]\n" +
        "                    BLOCK 1 [source_file: FileName.js] [length: 2]\n");
  }

  // The old and new parser produce different results now with labels, and
  // named breaks and continues, so disable these tests.
  public void testLabel() {
    testNewParser("foo: bar",
        "SCRIPT 1 [source_file: FileName.js] [length: 8]\n" +
        "    LABEL 1 [source_file: FileName.js] [length: 4]\n" +
        "        LABEL_NAME foo 1 [source_file: FileName.js] [length: 4]\n" +
        "        EXPR_RESULT 1 [source_file: FileName.js] [length: 3]\n" +
        "            NAME bar 1 [source_file: FileName.js] [length: 3]\n");
  }

  public void testLabel2() {
    testNewParser("l: while (f()) { if (g()) { continue l; } }",
        "SCRIPT 1 [source_file: FileName.js] [length: 43]\n" +
        "    LABEL 1 [source_file: FileName.js] [length: 2]\n" +
        "        LABEL_NAME l 1 [source_file: FileName.js] [length: 2]\n" +
        "        WHILE 1 [source_file: FileName.js] [length: 40]\n" +
        "            CALL 1 [source_file: FileName.js] [length: 3]\n" +
        "                NAME f 1 [source_file: FileName.js] [length: 1]\n" +
        "            BLOCK 1 [source_file: FileName.js] [length: 28]\n" +
        "                IF 1 [source_file: FileName.js] [length: 24]\n" +
        "                    CALL 1 [source_file: FileName.js] [length: 3]\n" +
        "                        NAME g 1 [source_file: FileName.js] [length: 1]\n" +
        "                    BLOCK 1 [source_file: FileName.js] [length: 15]\n" +
        "                        CONTINUE 1 [source_file: FileName.js] [length: 11]\n" +
        "                            LABEL_NAME l 1 [source_file: FileName.js] [length: 1]\n");
  }

  public void testLabel3() {
    testNewParser("Foo:Bar:X:{ break Bar; }",
        "SCRIPT 1 [source_file: FileName.js] [length: 24]\n" +
        "    LABEL 1 [source_file: FileName.js] [length: 4]\n" +
        "        LABEL_NAME Foo 1 [source_file: FileName.js] [length: 4]\n" +
        "        LABEL 1 [source_file: FileName.js] [length: 4]\n" +
        "            LABEL_NAME Bar 1 [source_file: FileName.js] [length: 4]\n" +
        "            LABEL 1 [source_file: FileName.js] [length: 2]\n" +
        "                LABEL_NAME X 1 [source_file: FileName.js] [length: 2]\n" +
        "                BLOCK 1 [source_file: FileName.js] [length: 14]\n" +
        "                    BREAK 1 [source_file: FileName.js] [length: 10]\n" +
        "                        LABEL_NAME Bar 1 [source_file: FileName.js] [length: 3]\n");
  }

  public void testNegation1() {
    testNewParser("-a",
        "SCRIPT 1 [source_file: FileName.js] [length: 2]\n" +
        "    EXPR_RESULT 1 [source_file: FileName.js] [length: 2]\n" +
        "        NEG 1 [source_file: FileName.js] [length: 2]\n" +
        "            NAME a 1 [source_file: FileName.js] [length: 1]\n");
  }

  public void testNegation2() {
    testNewParser("-2",
        "SCRIPT 1 [source_file: FileName.js] [length: 2]\n" +
        "    EXPR_RESULT 1 [source_file: FileName.js] [length: 2]\n" +
        "        NUMBER -2.0 1 [source_file: FileName.js] [length: 1]\n");
  }

  public void testNegation3() {
    testNewParser("1 - -2",
        "SCRIPT 1 [source_file: FileName.js] [length: 6]\n" +
        "    EXPR_RESULT 1 [source_file: FileName.js] [length: 6]\n" +
        "        SUB 1 [source_file: FileName.js] [length: 6]\n" +
        "            NUMBER 1.0 1 [source_file: FileName.js] [length: 1]\n" +
        "            NUMBER -2.0 1 [source_file: FileName.js] [length: 1]\n");
  }

  public void testGetter() {
    mode = LanguageMode.ECMASCRIPT5;
    testNewParser("({get a() {}})",
        "SCRIPT 1 [source_file: FileName.js] [length: 14]\n" +
        "    EXPR_RESULT 1 [source_file: FileName.js] [length: 13]\n" +
        "        OBJECTLIT 1 [source_file: FileName.js] [length: 12]\n" +
        "            GETTER_DEF a 1 [source_file: FileName.js] [length: 1]\n" +
        "                FUNCTION  1 [source_file: FileName.js] [length: 6]\n" +
        "                    NAME  1 [source_file: FileName.js]\n" +
        "                    PARAM_LIST 1 [source_file: FileName.js]\n" +
        "                    BLOCK 1 [source_file: FileName.js] [length: 2]\n");
  }

  public void testSetter() {
    mode = LanguageMode.ECMASCRIPT5;
    testNewParser("({set a(x) {}})",
        "SCRIPT 1 [source_file: FileName.js] [length: 15]\n" +
        "    EXPR_RESULT 1 [source_file: FileName.js] [length: 14]\n" +
        "        OBJECTLIT 1 [source_file: FileName.js] [length: 13]\n" +
        "            SETTER_DEF a 1 [source_file: FileName.js] [length: 1]\n" +
        "                FUNCTION  1 [source_file: FileName.js] [length: 7]\n" +
        "                    NAME  1 [source_file: FileName.js]\n" +
        "                    PARAM_LIST 1 [source_file: FileName.js]\n" +
        "                        NAME x 1 [source_file: FileName.js] [length: 1]\n" +
        "                    BLOCK 1 [source_file: FileName.js] [length: 2]\n");
  }

  public void testDelete1() {
    testNoParseError("delete a.b;");
  }

  public void testDelete2() {
    testNoParseError("delete a['b'];");
  }

  public void testDelete3() {
    // This is allowed in ES3 and ES5, but not in ES5/strict. There
    // is a strict mode check for this.
    testNoParseError("delete a;");
  }

  public void testDelete4() {
    testParseError("delete 'x';",
        "Invalid delete operand. Only properties can be deleted.");
  }

  public void testCommentPositions1() {
    Node root = newParse("/** @param {string} x */function a(x) {};" +
        "/** @param {string} x */function b(x) {}");
    Node a = root.getFirstChild();
    Node b = root.getLastChild();
    assertMarkerPosition(a, 1, 4);
    assertMarkerPosition(b, 1, 45);
  }

  public void testCommentPositions2() {
    Node root = newParse(
        "/* foo \n" +
        "   bar \n" +
        "*/\n" +
        "/** @param {string} x */\n" +
        "function a(x) {};\n" +
        "\n" +
        "/* bar \n" +
        "   foo \n" +
        "   foo */\n" +
        "\n" +
        "/**   @param {string} x */\n" +
        "function b(x) {};");
    assertMarkerPosition(root.getFirstChild(), 4, 4);
    assertMarkerPosition(root.getFirstChild().getNext().getNext(), 11, 6);
  }

   public void testLiteralLocation() {
    Node root = newParse(
        "var d =\n" +
        "    \"foo\";\n" +
        "var e =\n" +
        "    1;\n" +
        "var f = \n" +
        "    1.2;\n" +
        "var g = \n" +
        "    2e5;\n" +
        "var h = \n" +
        "    'bar';\n");

    Node firstStmt = root.getFirstChild();
    Node firstLiteral = firstStmt.getFirstChild().getFirstChild();
    Node secondStmt = firstStmt.getNext();
    Node secondLiteral = secondStmt.getFirstChild().getFirstChild();
    Node thirdStmt = secondStmt.getNext();
    Node thirdLiteral = thirdStmt.getFirstChild().getFirstChild();
    Node fourthStmt = thirdStmt.getNext();
    Node fourthLiteral = fourthStmt.getFirstChild().getFirstChild();
    Node fifthStmt = fourthStmt.getNext();
    Node fifthLiteral = fifthStmt.getFirstChild().getFirstChild();

    assertNodePosition(2, 4, firstLiteral);
    assertNodePosition(4, 4, secondLiteral);
    assertNodePosition(6, 4, thirdLiteral);
    assertNodePosition(8, 4, fourthLiteral);
    assertNodePosition(10, 4, fifthLiteral);
  }

  public void testSwitchLocation() {
    Node root = newParse(
        "switch (a) {\n" +
        "  //{\n" +
        "   case 1:\n" +
        "     b++;\n" +
        "   case 2:\n" +
        "   default:\n" +
        "     b--;\n" +
        "  }\n");

    Node switchStmt = root.getFirstChild();
    Node switchVar = switchStmt.getFirstChild();
    Node firstCase = switchVar.getNext();
    Node caseArg = firstCase.getFirstChild();
    Node caseBody = caseArg.getNext();
    Node caseExprStmt = caseBody.getFirstChild();
    Node incrExpr = caseExprStmt.getFirstChild();
    Node incrVar = incrExpr.getFirstChild();
    Node secondCase = firstCase.getNext();
    Node defaultCase = secondCase.getNext();

    assertNodePosition(1, 0, switchStmt);
    assertNodePosition(1, 8, switchVar);
    assertNodePosition(3, 3, firstCase);
    assertNodePosition(3, 8, caseArg);
    assertNodePosition(3, 3, caseBody);
    assertNodePosition(4, 5, caseExprStmt);
    assertNodePosition(4, 5, incrExpr);
    assertNodePosition(4, 5, incrVar);
    assertNodePosition(5, 3, secondCase);
    assertNodePosition(6, 3, defaultCase);
  }

  public void testFunctionParamLocation() {
    Node root = newParse(
        "function\n" +
        "     foo(a,\n" +
        "     b,\n" +
        "     c)\n" +
        "{}\n");

    Node function = root.getFirstChild();
    Node functionName = function.getFirstChild();
    Node params = functionName.getNext();
    Node param1 = params.getFirstChild();
    Node param2 = param1.getNext();
    Node param3 = param2.getNext();
    Node body = params.getNext();

    assertNodePosition(1, 0, function);
    assertNodePosition(2, 5, functionName);
    // params corresponds to the LP token.
    // Can't be on a separate line because of inferred
    // semicolons.
    assertNodePosition(2, 8, params);
    assertNodePosition(2, 9, param1);
    assertNodePosition(3, 5, param2);
    assertNodePosition(4, 5, param3);
    assertNodePosition(5, 0, body);
  }

  public void testVarDeclLocation() {
    Node root = newParse(
        "var\n" +
        "    a =\n" +
        "    3\n");
    Node varDecl = root.getFirstChild();
    Node varName = varDecl.getFirstChild();
    Node varExpr = varName.getFirstChild();

    assertNodePosition(1, 0, varDecl);
    assertNodePosition(2, 4, 1, varName);
    assertNodePosition(3, 4, 1, varExpr);
  }

  public void testReturnLocation() {
    Node root = newParse(
        "function\n" +
        "    foo(\n" +
        "    a,\n" +
        "    b,\n" +
        "    c) {\n" +
        "    return\n" +
        "    4;\n" +
        "}\n");

    Node function = root.getFirstChild();
    Node functionName = function.getFirstChild();
    Node params = functionName.getNext();
    Node body = params.getNext();
    Node returnStmt = body.getFirstChild();
    Node exprStmt = returnStmt.getNext();
    Node returnVal = exprStmt.getFirstChild();

    assertNodePosition(6, 4, returnStmt);
    assertNodePosition(7, 4, exprStmt);
    assertNodePosition(7, 4, returnVal);
  }

  public void testLinenoFor() {
    Node root = newParse(
        "for(\n" +
        ";\n" +
        ";\n" +
        ") {\n" +
        "}\n");

    Node forNode = root.getFirstChild();
    Node initClause= forNode.getFirstChild();
    Node condClause = initClause.getNext();
    Node incrClause = condClause.getNext();

    assertNodePosition(1, 0, forNode);
    assertNodePosition(2, 0, initClause);
    assertNodePosition(3, 0, condClause);
    // TODO(bowdidge) Incorrectly gets charno position when EmptyExpression
    // has its absolute position on the carriage return.  For now, the
    // line number gets reported correctly (on the next line) but the
    // character position is -1, so the overall line/char pair in our tree
    // is -1.
    assertNodePosition(-1, -1, incrClause); // should be 4
  }

  public void testBinaryExprLocation() {
    Node root = newParse(
        "var d = a\n" +
        "    + \n" +
        "    b;\n" +
        "var\n" +
        "    e =\n" +
        "    a +\n" +
        "    c;\n" +
        "var f = b\n" +
        "    / c;\n");

    Node firstVarDecl = root.getFirstChild();
    Node firstVar = firstVarDecl.getFirstChild();
    Node firstVarAdd = firstVar.getFirstChild();

    Node secondVarDecl = firstVarDecl.getNext();
    Node secondVar = secondVarDecl.getFirstChild();
    Node secondVarAdd = secondVar.getFirstChild();

    Node thirdVarDecl = secondVarDecl.getNext();
    Node thirdVar = thirdVarDecl.getFirstChild();
    Node thirdVarAdd = thirdVar.getFirstChild();

    assertNodePosition(1, 0, firstVarDecl);
    assertNodePosition(1, 4, firstVar);
    assertNodePosition(1, 8, firstVarAdd);
    assertNodePosition(1, 8, firstVarAdd.getFirstChild());
    assertNodePosition(3, 4, firstVarAdd.getLastChild());

    assertNodePosition(4, 0, secondVarDecl);
    assertNodePosition(5, 4, secondVar);
    assertNodePosition(6, 4, secondVarAdd);
    assertNodePosition(6, 4, secondVarAdd.getFirstChild());
    assertNodePosition(7, 4, secondVarAdd.getLastChild());

    assertNodePosition(8, 0, thirdVarDecl);
    assertNodePosition(8, 4, thirdVar);
    assertNodePosition(8, 8, thirdVarAdd);
    assertNodePosition(8, 8, thirdVarAdd.getFirstChild());
    assertNodePosition(9, 6, thirdVarAdd.getLastChild());
  }

  public void testPrefixLocation() {
    Node root = newParse(
         "a++;\n" +
         "--\n" +
         "b;\n");

    Node firstStmt = root.getFirstChild();
    Node secondStmt = firstStmt.getNext();
    Node firstOp = firstStmt.getFirstChild();
    Node secondOp = secondStmt.getFirstChild();

    assertNodePosition(1, 0, firstOp);
    assertNodePosition(2, 0, secondOp);
  }

  public void testIfLocation() {
    Node root = newParse(
        "if\n" +
        "  (a == 3)\n" +
        "{\n" +
        "  b = 0;\n" +
        "}\n" +
        "  else\n" +
        "{\n" +
        "  c = 1;\n" +
        "}\n");

    Node ifStmt = root.getFirstChild();
    Node eqClause = ifStmt.getFirstChild();
    Node thenClause = eqClause.getNext();
    Node elseClause = thenClause.getNext();

    assertNodePosition(1, 0, ifStmt);
    assertNodePosition(2, 3, eqClause);
    assertNodePosition(3, 0, thenClause);
    assertNodePosition(7, 0, elseClause);
  }

  public void testTryLocation() {
     Node root = newParse(
         "try {\n" +
         "  var x = 1;\n" +
         "} catch\n" +
         "   (err)\n" +
         "{\n" +
         "} finally {\n" +
         "  var y = 2;\n" +
         "}\n");

    Node tryStmt = root.getFirstChild();
    Node tryBlock = tryStmt.getFirstChild();
    Node catchBlock = tryBlock.getNext();
    Node catchVarBlock = catchBlock.getFirstChild();
    Node catchVar = catchVarBlock.getFirstChild();
    Node finallyBlock = catchBlock.getNext();
    Node finallyStmt = finallyBlock.getFirstChild();

    assertNodePosition(1, 0, tryStmt);
    assertNodePosition(1, 4, tryBlock);
    assertNodePosition(3, 2, catchVarBlock);
    assertNodePosition(4, 4, catchVar);
    assertNodePosition(3, 0, catchBlock);
    assertNodePosition(6, 10, finallyBlock);
    assertNodePosition(7, 2, finallyStmt);
  }

  public void testHookLocation() {
    Node root = newParse(
        "a\n" +
        "?\n" +
        "b\n" +
        ":\n" +
        "c\n" +
        ";\n");

    Node hookExpr = root.getFirstChild().getFirstChild();
    Node condExpr = hookExpr.getFirstChild();
    Node thenExpr = condExpr.getNext();
    Node elseExpr = thenExpr.getNext();

    assertNodePosition(2, 0, hookExpr);
    assertNodePosition(1, 0, condExpr);
    assertNodePosition(3, 0, thenExpr);
    assertNodePosition(5, 0, elseExpr);
  }

  public void testLabelLocation() {
    Node root = newParse(
        "foo:\n" +
        "a = 1;\n" +
        "bar:\n" +
        "b = 2;\n");

    Node firstStmt = root.getFirstChild();
    Node secondStmt = firstStmt.getNext();

    assertNodePosition(1, 0, firstStmt);
    assertNodePosition(3, 0, secondStmt);
  }

  public void testCompareLocation() {
    Node root = newParse(
        "a\n" +
        "<\n" +
        "b\n");

    Node condClause = root.getFirstChild().getFirstChild();
    Node lhs = condClause.getFirstChild();
    Node rhs = lhs.getNext();

    assertNodePosition(1, 0, condClause);
    assertNodePosition(1, 0, lhs);
    assertNodePosition(3, 0, rhs);
   }

  public void testEqualityLocation() {
    Node root = newParse(
        "a\n" +
        "==\n" +
        "b\n");

    Node condClause = root.getFirstChild().getFirstChild();
    Node lhs = condClause.getFirstChild();
    Node rhs = lhs.getNext();

    assertNodePosition(1, 0, condClause);
    assertNodePosition(1, 0, lhs);
    assertNodePosition(3, 0, rhs);
  }

  public void testPlusEqLocation() {
    Node root = newParse(
        "a\n" +
        "+=\n" +
        "b\n");

    Node condClause = root.getFirstChild().getFirstChild();
    Node lhs = condClause.getFirstChild();
    Node rhs = lhs.getNext();

    assertNodePosition(1, 0, condClause);
    assertNodePosition(1, 0, lhs);
    assertNodePosition(3, 0, rhs);
  }

  public void testCommaLocation() {
    Node root = newParse(
        "a,\n" +
        "b,\n" +
        "c;\n");

    Node statement = root.getFirstChild();
    Node comma1 = statement.getFirstChild();
    Node comma2 = comma1.getFirstChild();
    Node cRef = comma2.getNext();
    Node aRef = comma2.getFirstChild();
    Node bRef = aRef.getNext();

    assertNodePosition(1, 0, comma2);
    assertNodePosition(1, 0, aRef);
    assertNodePosition(2, 0, bRef);
    assertNodePosition(3, 0, cRef);
  }

  public void testRegexpLocation() {
    Node root = newParse(
        "var path =\n" +
        "replace(\n" +
        "/a/g," +
        "'/');\n");

    Node firstVarDecl = root.getFirstChild();
    Node firstVar = firstVarDecl.getFirstChild();
    Node callNode = firstVar.getFirstChild();
    Node fnName = callNode.getFirstChild();
    Node regexObject = fnName.getNext();
    Node aString = regexObject.getFirstChild();
    Node endRegexString = regexObject.getNext();

    assertNodePosition(1, 0, firstVarDecl);
    assertNodePosition(1, 4, 4, firstVar);
    assertNodePosition(2, 0, 18, callNode);
    assertNodePosition(2, 0, 7, fnName);
    assertNodePosition(3, 0, regexObject);
    assertNodePosition(3, 0, aString);
    assertNodePosition(3, 5, endRegexString);
  }

  public void testNestedOr() {
    Node root = newParse(
        "if (a && \n" +
        "    b() || \n" +
        "    /* comment */\n" +
        "    c) {\n" +
        "}\n"
    );

    Node ifStmt = root.getFirstChild();
    Node orClause = ifStmt.getFirstChild();
    Node andClause = orClause.getFirstChild();
    Node cName = andClause.getNext();

    assertNodePosition(1, 0, ifStmt);
    assertNodePosition(1, 4, orClause);
    assertNodePosition(1, 4, andClause);
    assertNodePosition(4, 4, cName);

  }

  public void testBitwiseOps() {
      Node root = newParse(
        "if (a & \n" +
        "    b() | \n" +
        "    /* comment */\n" +
        "    c) {\n" +
        "}\n"
    );

    Node ifStmt = root.getFirstChild();
    Node bitOr = ifStmt.getFirstChild();
    Node bitAnd = bitOr.getFirstChild();
    Node cName = bitAnd.getNext();

    assertNodePosition(1, 0, ifStmt);
    assertNodePosition(1, 4, bitOr);
    assertNodePosition(1, 4, bitAnd);
    assertNodePosition(4, 4, cName);

  }

  public void testObjectLitLocation() {
    Node root = newParse(
        "var foo =\n" +
        "{ \n" +
        "'A' : 'A', \n" +
        "'B' : 'B', \n" +
        "'C' :\n" +
        "    'C' \n" +
        "};\n");

    Node firstVarDecl = root.getFirstChild();
    Node firstVar = firstVarDecl.getFirstChild();
    Node firstObjectLit = firstVar.getFirstChild();
    Node firstKey = firstObjectLit.getFirstChild();
    Node firstValue = firstKey.getFirstChild();

    Node secondKey = firstKey.getNext();
    Node secondValue = secondKey.getFirstChild();

    Node thirdKey = secondKey.getNext();
    Node thirdValue = thirdKey.getFirstChild();

    assertNodePosition(1, 4, firstVar);
    assertNodePosition(2, 0, firstObjectLit);

    assertNodePosition(3, 0, firstKey);
    assertNodePosition(3, 6, firstValue);

    assertNodePosition(4, 0, secondKey);
    assertNodePosition(4, 6, secondValue);

    assertNodePosition(5, 0, thirdKey);
    assertNodePosition(6, 4, thirdValue);
  }

  public void testTryWithoutCatchLocation() {
     Node root = newParse(
         "try {\n" +
         "  var x = 1;\n" +
         "} finally {\n" +
         "  var y = 2;\n" +
         "}\n");

    Node tryStmt = root.getFirstChild();
    Node tryBlock = tryStmt.getFirstChild();
    Node catchBlock = tryBlock.getNext();
    Node finallyBlock = catchBlock.getNext();
    Node finallyStmt = finallyBlock.getFirstChild();

    assertNodePosition(1, 0, tryStmt);
    assertNodePosition(1, 4, tryBlock);
    assertNodePosition(3, 0, catchBlock);
    assertNodePosition(3, 10, finallyBlock);
    assertNodePosition(4, 2, finallyStmt);
  }

  public void testTryWithoutFinallyLocation() {
     Node root = newParse(
         "try {\n" +
         "  var x = 1;\n" +
         "} catch (ex) {\n" +
         "  var y = 2;\n" +
         "}\n");

    Node tryStmt = root.getFirstChild();
    Node tryBlock = tryStmt.getFirstChild();
    Node catchBlock = tryBlock.getNext();
    Node catchStmt = catchBlock.getFirstChild();
    Node exceptionVar = catchStmt.getFirstChild();
    Node exceptionBlock = exceptionVar.getNext();
    Node varDecl = exceptionBlock.getFirstChild();

    assertNodePosition(1, 0, tryStmt);
    assertNodePosition(1, 4, tryBlock);
    assertNodePosition(3, 0, catchBlock);
    assertNodePosition(3, 2, catchStmt);
    assertNodePosition(3, 9, exceptionVar);
    assertNodePosition(3, 13, exceptionBlock);
    assertNodePosition(4, 2, varDecl);
  }

  public void testMultilineEqLocation() {
    Node  root = newParse(
        "if\n" +
        "    (((a == \n" +
        "  3) && \n" +
        "  (b == 2)) || \n" +
        " (c == 1)) {\n" +
        "}\n");
    Node ifStmt = root.getFirstChild();
    Node orTest = ifStmt.getFirstChild();
    Node andTest = orTest.getFirstChild();
    Node cTest = andTest.getNext();
    Node aTest = andTest.getFirstChild();
    Node bTest = aTest.getNext();

    assertNodePosition(1, 0, ifStmt);
    assertNodePosition(2, 7, orTest);
    assertNodePosition(2, 7, andTest);
    assertNodePosition(2, 7, aTest);
    assertNodePosition(4, 3, bTest);
    assertNodePosition(5, 2, cTest);
  }

  public void testMultilineBitTestLocation() {
    Node root = newParse(
        "if (\n" +
        "      ((a \n" +
        "        | 3 \n" +
        "       ) == \n" +
        "       (b \n" +
        "        & 2)) && \n" +
        "      ((a \n" +
        "         ^ 0xffff) \n" +
        "       != \n" +
        "       (c \n" +
        "        << 1))) {\n" +
        "}\n");

    Node ifStmt = root.getFirstChild();
    Node andTest = ifStmt.getFirstChild();
    Node eqTest = andTest.getFirstChild();
    Node notEqTest = eqTest.getNext();

    Node bitOrTest = eqTest.getFirstChild();
    Node bitAndTest = bitOrTest.getNext();

    Node bitXorTest = notEqTest.getFirstChild();
    Node bitShiftTest = bitXorTest.getNext();

    assertNodePosition(1, 0, ifStmt);

    assertNodePosition(2, 8, eqTest);
    assertNodePosition(7, 8, notEqTest);

    assertNodePosition(2, 8, bitOrTest);
    assertNodePosition(5, 8, bitAndTest);
    assertNodePosition(7, 8, bitXorTest);
    assertNodePosition(10, 8, bitShiftTest);
  }

  public void testCallLocation() {
    Node root = newParse(
        "a.\n" +
        "b.\n" +
        "cccc(1);\n");

    Node exprStmt = root.getFirstChild();
    Node functionCall = exprStmt.getFirstChild();
    Node functionProp = functionCall.getFirstChild();
    Node firstNameComponent = functionProp.getFirstChild();
    Node lastNameComponent = firstNameComponent.getNext();
    Node aNameComponent = firstNameComponent.getFirstChild();
    Node bNameComponent = aNameComponent.getNext();

    assertNodePosition(1, 0, 13, functionCall);
    assertNodePosition(1, 0, 10, functionProp);
    // TODO(bowdidge) New Rhino doesn't keep the position of the dot handy.
    // New Rhino treats the location of the qualified name as the beginning of
    // the whole name.
    assertNodePosition(1, 0, 4, firstNameComponent);
    assertNodePosition(3, 0, 4, lastNameComponent);

    assertNodePosition(1, 0, 1, aNameComponent);
    assertNodePosition(2, 0, 1, bNameComponent);
  }

  public void testNewLocation() {
    Node root = newParse(
        "new c();\n");

    Node exprStmt = root.getFirstChild();
    Node newExpr = exprStmt.getFirstChild();
    assertNodePosition(1, 0, 7, newExpr);
  }

  public void testNewLocationMultiLine() {
    Node root = newParse(
        "new   \n" +
        "c();\n");

    Node exprStmt = root.getFirstChild();
    Node newExpr = exprStmt.getFirstChild();
    assertNodePosition(1, 0, 10, newExpr);
  }

  public void testLinenoDeclaration() {
    Node root = newParse(
        "a.\n" +
        "b=\n" +
        "function() {};\n");

    Node exprStmt = root.getFirstChild();
    Node fnAssignment =  exprStmt.getFirstChild();
    Node aDotbName = fnAssignment.getFirstChild();
    Node aName = aDotbName.getFirstChild();
    Node bName = aName.getNext();
    Node fnNode = aDotbName.getNext();
    Node fnName = fnNode.getFirstChild();

    assertNodePosition(1, 0, fnAssignment);
    // TODO(bowdidge) New Rhino doesn't keep track of the position of the dot.
    //assertNodePosition(1, 1, aDotbName);
    assertNodePosition(1, 0, aName);
    assertNodePosition(2, 0, bName);
    assertNodePosition(3, 0, fnNode);
    assertNodePosition(3, 8, fnName);
  }

  final String INVALID_ASSIGNMENT_TARGET = "invalid assignment target";
  final String INVALID_INCREMENT_TARGET = "invalid increment target";
  final String INVALID_DECREMENT_TARGET = "invalid decrement target";

  final String INVALID_INC_OPERAND = "Invalid increment operand";
  final String INVALID_DEC_OPERAND = "Invalid decrement operand";

  public void testAssignmentValidation() {
    testNoParseError("x=1");
    testNoParseError("x.y=1");
    testNoParseError("f().y=1");
    testParseError("(x||y)=1", INVALID_ASSIGNMENT_TARGET);
    testParseError("(x?y:z)=1", INVALID_ASSIGNMENT_TARGET);
    testParseError("f()=1", INVALID_ASSIGNMENT_TARGET);

    testNoParseError("x+=1");
    testNoParseError("x.y+=1");
    testNoParseError("f().y+=1");
    testParseError("(x||y)+=1", INVALID_ASSIGNMENT_TARGET);
    testParseError("(x?y:z)+=1", INVALID_ASSIGNMENT_TARGET);
    testParseError("f()+=1", INVALID_ASSIGNMENT_TARGET);

    testParseError("f()++", INVALID_INCREMENT_TARGET);
    testParseError("f()--", INVALID_DECREMENT_TARGET);
    testParseError("++f()", INVALID_INCREMENT_TARGET);
    testParseError("--f()", INVALID_DECREMENT_TARGET);
  }

  private void testNoParseError(String string) {
    testParseError(string, (String)null);
  }

  private void testParseError(String string, String error) {
    testParseError(string, error == null ? null : new String[] { error });
  }

  private void testParseError(String string, String[] errors) {
    newParse(string, new TestErrorReporter(errors, null));
    assertTrue("unexpected warnings reported",
        errorReporter.hasEncounteredAllWarnings());
    assertTrue("expected error were not reported",
        errorReporter.hasEncounteredAllErrors());
  }

  private void assertMarkerPosition(Node n, int lineno, int charno) {
    int count = 0;
    for (JSDocInfo.Marker marker : n.getJSDocInfo().getMarkers()) {
      assertEquals(lineno, marker.getAnnotation().getStartLine());
      assertEquals(charno, marker.getAnnotation().getPositionOnStartLine());
      count++;
    }
    assertEquals(1, count);
  }

  private void assertNodePosition(int lineno, int charno, Node n) {
    assertEquals("Line number", lineno, n.getLineno());
    assertEquals("Column position", charno, n.getCharno());
  }

  private void assertNodePosition(int lineno, int charno, int length, Node n) {
    assertEquals("Line number", lineno, n.getLineno());
    assertEquals("Column position", charno, n.getCharno());
    assertEquals("Length", length, n.getLength());
  }


  private void testNewParser(String code, String expected) {
    String actual = newParse(code).toStringTree();
    assertEquals(expected, actual);
  }

  private Node newParse(String string) {
    return newParse(string, new TestErrorReporter(null, null));
  }

  private Node newParse(String string, TestErrorReporter errorReporter) {
    CompilerEnvirons environment = new CompilerEnvirons();

    environment.setRecordingComments(true);
    environment.setRecordingLocalJsDocComments(true);

    Parser p = new Parser(environment);
    AstRoot script = p.parse(string, null, 1);

    Config config = ParserRunner.createConfig(true, mode, false);
    Node root = IRFactory.transformTree(
        script, SourceFile.fromCode("FileName.js", string), string, config, errorReporter);

    return root;
  }
}
