/*
 * 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;

import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;

import junit.framework.TestCase;

/**
 * Tests for {@link SyntacticScopeCreator}.
 *
 */
public class SyntacticScopeCreatorTest extends TestCase {

  /**
   * Helper to create a top-level scope from a JavaScript string
   */
  private static Scope getScope(String js) {
    Compiler compiler = new Compiler();
    Node root = compiler.parseTestCode(js);
    assertEquals(0, compiler.getErrorCount());
    Scope scope =
        new SyntacticScopeCreator(compiler).createScope(root, null);
    return scope;
  }

  /**
   * Helper to traverse the tree creating the Scope object everywhere.
   */
  private static void testScopes(String js, int errorCount) {
    Compiler compiler = new Compiler();
    Node root = compiler.parseTestCode(js);
    NodeTraversal.traverse(
        compiler, root, new NodeTraversal.AbstractPostOrderCallback() {
          @Override
          public
          void visit(NodeTraversal t, Node n, Node parent) {
            t.getScope();
          }
        });
    assertEquals(errorCount, compiler.getErrorCount());
  }

  public void testFunctionScope() {
    Scope scope = getScope("function foo() {}\n" +
                           "var x = function bar(a1) {};" +
                           "[function bar2() { var y; }];" +
                           "if (true) { function z() {} }"
                           );
    assertTrue(scope.isDeclared("foo", false));
    assertTrue(scope.isDeclared("x", false));
    assertTrue(scope.isDeclared("z", false));

    // The following should not be declared in this scope
    assertFalse(scope.isDeclared("a1", false));
    assertFalse(scope.isDeclared("bar", false));
    assertFalse(scope.isDeclared("bar2", false));
    assertFalse(scope.isDeclared("y", false));
    assertFalse(scope.isDeclared("", false));
  }

  public void testScopeRootNode() {
    String js = "function foo() {\n" +
        " var x = 10;" +
        "}";
    Compiler compiler = new Compiler();
    Node root = compiler.parseTestCode(js);
    assertEquals(0, compiler.getErrorCount());

    Scope globalScope =
        new SyntacticScopeCreator(compiler).createScope(root, null);
    assertEquals(root, globalScope.getRootNode());

    Node fooNode = root.getFirstChild();
    assertEquals(Token.FUNCTION, fooNode.getType());
    Scope fooScope =
        new SyntacticScopeCreator(compiler).createScope(fooNode, null);
    assertEquals(fooNode, fooScope.getRootNode());
    assertTrue(fooScope.isDeclared("x", false));
  }

  public void testRedeclaration1() {
     String js = "var a; var a;";
     int errors = createGlobalScopeHelper(js);
     assertEquals(1, errors);
  }

  public void testRedeclaration2() {
    String js = "var a; /** @suppress {duplicate} */ var a;";
    int errors = createGlobalScopeHelper(js);
    assertEquals(0, errors);
  }

  public void testRedeclaration3() {
    String js = " /** @suppress {duplicate} */ var a; var a; ";
    int errors = createGlobalScopeHelper(js);
    assertEquals(0, errors);
  }


  public void testFunctionScopeArguments() {
    // A var declaration doesn't mask arguments
    testScopes("function f() {var arguments}", 0);

    testScopes("var f = function arguments() {}", 1);
    testScopes("var f = function (arguments) {}", 1);
    testScopes("function f() {try {} catch(arguments) {}}", 1);
  }

  /**
   * Parse the supplied JS and create the global SyntaticScope object.
   * @return The error count.
   */
  private int createGlobalScopeHelper(String js) {
    Compiler compiler = new Compiler();
    CompilerOptions options = new CompilerOptions();
    options.checkSymbols = true;
    compiler.initOptions(options);

    Node root = compiler.parseTestCode(js);
    assertEquals(0, compiler.getErrorCount());
    Scope globalScope =
      new SyntacticScopeCreator(compiler).createScope(root, null);
    assertEquals(root, globalScope.getRootNode());
    return compiler.getErrorCount();
  }
}
