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

public class RenamePrototypesTest extends CompilerTestCase {

  private static final String EXTERNS = "var js_version;js_version.toString;";
  private VariableMap prevUsedRenameMap;
  private RenamePrototypes renamePrototypes;

  public RenamePrototypesTest() {
    super(EXTERNS);
    enableNormalize();
  }

  @Override
  public CompilerPass getProcessor(Compiler compiler) {
    return renamePrototypes =
        new RenamePrototypes(compiler, true, null, prevUsedRenameMap);
  }

  @Override
  protected void tearDown() throws Exception {
    super.tearDown();

    prevUsedRenameMap = null;
  }

  @Override
  protected int getNumRepetitions() {
    // The RenamePrototypes pass should only be run once over a parse tree.
    return 1;
  }

  public void testRenamePrototypes1() {
    test("Bar.prototype={'getFoo':function(){},2:function(){}}",
         "Bar.prototype={'a':function(){},2:function(){}}");
  }

  public void testRenamePrototypes2() {
    // Simple
    test("Bar.prototype.getFoo=function(){};Bar.getFoo(b);" +
         "Bar.prototype.getBaz=function(){}",
         "Bar.prototype.a=function(){};Bar.a(b);" +
         "Bar.prototype.b=function(){}");
    test("Bar.prototype['getFoo']=function(){};Bar.getFoo(b);" +
         "Bar.prototype['getBaz']=function(){}",
         "Bar.prototype['a']=function(){};Bar.a(b);" +
         "Bar.prototype['b']=function(){}");
    test("Bar.prototype={'getFoo':function(){},2:function(){}}",
         "Bar.prototype={'a':function(){},2:function(){}}");
    test("Bar.prototype={'getFoo':function(){}," +
         "'getBar':function(){}};b.getFoo()",
         "Bar.prototype={'a':function(){}," +
         "'b':function(){}};b.a()");

    test("Bar.prototype={'B':function(){}," +
         "'getBar':function(){}};b.getBar()",
         "Bar.prototype={'b':function(){}," +
         "'a':function(){}};b.a()");

    // overlap
    test("Bar.prototype={'a':function(){}," +
         "'b':function(){}};b.b()",
         "Bar.prototype={'b':function(){}," +
         "'a':function(){}};b.a()");

    // don't rename anything with a leading underscore
    test("Bar.prototype={'_getFoo':function(){}," +
         "'getBar':function(){}};b._getFoo()",
         "Bar.prototype={'_getFoo':function(){}," +
         "'a':function(){}};b._getFoo()");

    // Externed methods
    test("Bar.prototype={'toString':function(){}," +
         "'getBar':function(){}};b.toString()",
         "Bar.prototype={'toString':function(){}," +
         "'a':function(){}};b.toString()");

    // don't rename a method to an existing (unrenamed) property
    test("Bar.prototype.foo=function(){}" +
         ";bar.foo();bar.a",
         "Bar.prototype.b=function(){}" +
         ";bar.b();bar.a");
  }

  public void testRenamePrototypesWithGetOrSet() {
    // Simple
    // TODO(johnlenz): Enable these for after Rhino support is added.
    // test("Bar.prototype={get 'getFoo'(){}}",
    //      "Bar.prototype={get a(){}}");
    // test("Bar.prototype={get 2(){}}",
    //      "Bar.prototype={get 2(){}}");
    test("Bar.prototype={get getFoo(){}}",
         "Bar.prototype={get a(){}}");
    test("Bar.prototype={get getFoo(){}}; a.getFoo;",
         "Bar.prototype={get a(){}}; a.a;");

    // TODO(johnlenz): Enable these for after Rhino support is added.
    // test("Bar.prototype={set 'getFoo'(x){}}",
    //      "Bar.prototype={set a(x){}}");
    // test("Bar.prototype={set 2(x){}}",
    //      "Bar.prototype={set 2(x){}}");
    test("Bar.prototype={set getFoo(x){}}",
         "Bar.prototype={set a(x){}}");
    test("Bar.prototype={set getFoo(x){}}; a.getFoo;",
         "Bar.prototype={set a(x){}}; a.a;");

    // overlap
    test("Bar.prototype={get a(){}," +
         "get b(){}};b.b()",
         "Bar.prototype={get b(){}," +
         "get a(){}};b.a()");
  }

  /**
   * Test renaming private properties (end with underscores) and test to make
   * sure we don't rename other properties.
   */
  public void testRenameProperties() {
    test("var foo; foo.prop_='bar'", "var foo;foo.a='bar'");
    test("this.prop_='bar'", "this.a='bar'");
    test("this.prop='bar'", "this.prop='bar'");
    test("this['prop_']='bar'", "this['a']='bar'");
    test("this['prop']='bar'", "this['prop']='bar'");
    test("var foo={prop1_: 'bar',prop2_: 'baz'};",
         "var foo={a:'bar',b:'baz'}");
  }

  /**
   * Tests a potential tricky interaction between prototype renaming and
   * property renaming.
   */
  public void testBoth() {
    test("Bar.prototype.getFoo_=function(){};Bar.getFoo_(b);" +
         "Bar.prototype.getBaz_=function(){}",
         "Bar.prototype.a=function(){};Bar.a(b);" +
         "Bar.prototype.b=function(){}");
  }

  public void testPropertyNameThatIsBothObjLitKeyAndPrototypeProperty() {
    // This test protects against regression of a bug where non-private object
    // literal keys were getting renamed if they clashed with custom prototype
    // methods. Now we don't simply don't rename in this situation, since
    // references like z.myprop are ambiguous.
    test("x.prototype.myprop=function(){};y={myprop:0};z.myprop",
         "x.prototype.myprop=function(){};y={myprop:0};z.myprop");

    // This test shows that a property can be renamed if both the prototype
    // property renaming policy and the objlit key renaming policy agree that
    // it can be renamed.
    test("x.prototype.myprop_=function(){};y={myprop_:0};z.myprop_",
         "x.prototype.a=function(){};y={a:0};z.a");
  }

  public void testModule() {
    JSModule[] modules = createModules(
        "function Bar(){} var foo; Bar.prototype.getFoo_=function(x){};" +
        "foo.getFoo_(foo);foo.doo_=foo;foo.bloo_=foo;",
        "function Far(){} var too; Far.prototype.getGoo_=function(x){};" +
        "too.getGoo_(too);too.troo_=too;too.bloo_=too;");

    test(modules, new String[] {
        "function Bar(){}var foo; Bar.prototype.a=function(x){};" +
        "foo.a(foo);foo.d=foo;foo.c=foo;",
        "function Far(){}var too; Far.prototype.b=function(x){};" +
        "too.b(too);too.e=too;too.c=too;"
    });
  }

  public void testStableSimple1() {
    testStable(
        "Bar.prototype.getFoo=function(){};Bar.getFoo(b);" +
        "Bar.prototype.getBaz=function(){}",
        "Bar.prototype.a=function(){};Bar.a(b);" +
        "Bar.prototype.b=function(){}",
        "Bar.prototype.getBar=function(){};Bar.getBar(b);" +
        "Bar.prototype.getFoo=function(){};Bar.getFoo(b);" +
        "Bar.prototype.getBaz=function(){}",
        "Bar.prototype.c=function(){};Bar.c(b);" +
        "Bar.prototype.a=function(){};Bar.a(b);" +
        "Bar.prototype.b=function(){}");
  }

  public void testStableSimple2() {
    testStable(
        "Bar.prototype['getFoo']=function(){};Bar.getFoo(b);" +
        "Bar.prototype['getBaz']=function(){}",
        "Bar.prototype['a']=function(){};Bar.a(b);" +
        "Bar.prototype['b']=function(){}",
        "Bar.prototype['getFoo']=function(){};Bar.getFoo(b);" +
        "Bar.prototype['getBar']=function(){};" +
        "Bar.prototype['getBaz']=function(){}",
        "Bar.prototype['a']=function(){};Bar.a(b);" +
        "Bar.prototype['c']=function(){};" +
        "Bar.prototype['b']=function(){}");
  }

  public void testStableSimple3() {
    testStable(
        "Bar.prototype={'getFoo':function(){}," +
        "'getBar':function(){}};b.getFoo()",
        "Bar.prototype={'a':function(){}, 'b':function(){}};b.a()",
        "Bar.prototype={'getFoo':function(){}," +
        "'getBaz':function(){},'getBar':function(){}};b.getFoo()",
        "Bar.prototype={'a':function(){}, " +
        "'c':function(){}, 'b':function(){}};b.a()");
  }

  public void testStableOverlap() {
    testStable(
        "Bar.prototype={'a':function(){},'b':function(){}};b.b()",
        "Bar.prototype={'b':function(){},'a':function(){}};b.a()",
        "Bar.prototype={'a':function(){},'b':function(){}};b.b()",
        "Bar.prototype={'b':function(){},'a':function(){}};b.a()");
  }

  public void testStableTrickyExternedMethods() {
    test("Bar.prototype={'toString':function(){}," +
         "'getBar':function(){}};b.toString()",
         "Bar.prototype={'toString':function(){}," +
         "'a':function(){}};b.toString()");
    prevUsedRenameMap = renamePrototypes.getPropertyMap();
    String externs = EXTERNS + "prop.a;";
    test(externs,
         "Bar.prototype={'toString':function(){}," +
         "'getBar':function(){}};b.toString()",
         "Bar.prototype={'toString':function(){}," +
         "'b':function(){}};b.toString()", null, null);
  }

  public void testStable(String input1, String expected1,
                         String input2, String expected2) {
    test(input1, expected1);
    prevUsedRenameMap = renamePrototypes.getPropertyMap();
    test(input2, expected2);
  }
}
