/*
 * Copyright 2008 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 static com.google.javascript.jscomp.CheckAccessControls.BAD_PRIVATE_GLOBAL_ACCESS;
import static com.google.javascript.jscomp.CheckAccessControls.BAD_PRIVATE_PROPERTY_ACCESS;
import static com.google.javascript.jscomp.CheckAccessControls.BAD_PROTECTED_PROPERTY_ACCESS;
import static com.google.javascript.jscomp.CheckAccessControls.CONST_PROPERTY_DELETED;
import static com.google.javascript.jscomp.CheckAccessControls.CONST_PROPERTY_REASSIGNED_VALUE;
import static com.google.javascript.jscomp.CheckAccessControls.DEPRECATED_CLASS;
import static com.google.javascript.jscomp.CheckAccessControls.DEPRECATED_CLASS_REASON;
import static com.google.javascript.jscomp.CheckAccessControls.DEPRECATED_NAME;
import static com.google.javascript.jscomp.CheckAccessControls.DEPRECATED_NAME_REASON;
import static com.google.javascript.jscomp.CheckAccessControls.DEPRECATED_PROP;
import static com.google.javascript.jscomp.CheckAccessControls.DEPRECATED_PROP_REASON;
import static com.google.javascript.jscomp.CheckAccessControls.EXTEND_FINAL_CLASS;
import static com.google.javascript.jscomp.CheckAccessControls.PRIVATE_OVERRIDE;
import static com.google.javascript.jscomp.CheckAccessControls.VISIBILITY_MISMATCH;

/**
 * Tests for {@link CheckAccessControls}.
 *
 * @author nicksantos@google.com (Nick Santos)
 */
public class CheckAccessControlsTest extends CompilerTestCase {

  public CheckAccessControlsTest() {
    super(CompilerTypeTestCase.DEFAULT_EXTERNS);
    parseTypeInfo = true;
    enableTypeCheck(CheckLevel.WARNING);
  }

  @Override
  protected CompilerPass getProcessor(final Compiler compiler) {
    return new CheckAccessControls(compiler);
  }

  @Override
  protected CompilerOptions getOptions() {
    CompilerOptions options = super.getOptions();
    options.setWarningLevel(DiagnosticGroups.ACCESS_CONTROLS, CheckLevel.ERROR);
    options.setWarningLevel(DiagnosticGroups.CONSTANT_PROPERTY,
        CheckLevel.ERROR);
    return options;
  }

  /**
   * Tests that the given JavaScript code has a @deprecated marker
   * somewhere in it which raises an error. Also tests that the
   * deprecated marker works with a message. The JavaScript should
   * have a JsDoc of the form "@deprecated %s\n".
   *
   * @param js The JavaScript code to parse and test.
   * @param reason A simple deprecation reason string, used for testing
   *    the addition of a deprecation reason to the @deprecated tag.
   * @param error The deprecation error expected when no reason is given.
   * @param errorWithMessage The deprecation error expected when a reason
   *    message is given.
   */
  private void testDep(String js, String reason,
                       DiagnosticType error,
                       DiagnosticType errorWithMessage) {

    // Test without a reason.
    test(String.format(js, ""), null, error);

    // Test with a reason.
    test(String.format(js, reason), null, errorWithMessage, null, reason);
  }

  public void testDeprecatedFunction() {
    testDep("/** @deprecated %s */ function f() {} function g() { f(); }",
            "Some Reason",
            DEPRECATED_NAME, DEPRECATED_NAME_REASON);
  }

  public void testWarningOnDeprecatedConstVariable() {
    testDep("/** @deprecated %s */ var f = 4; function g() { alert(f); }",
            "Another reason",
            DEPRECATED_NAME, DEPRECATED_NAME_REASON);
  }

  public void testThatNumbersArentDeprecated() {
    testSame("/** @deprecated */ var f = 4; var h = 3; " +
             "function g() { alert(h); }");
  }

  public void testDeprecatedFunctionVariable() {
    testDep("/** @deprecated %s */ var f = function() {}; " +
            "function g() { f(); }", "I like g...",
            DEPRECATED_NAME, DEPRECATED_NAME_REASON);
  }

  public void testNoWarningInGlobalScope() {
    testSame("var goog = {}; goog.makeSingleton = function(x) {};" +
        "/** @deprecated */ function f() {} goog.makeSingleton(f);");
  }

  public void testNoWarningInGlobalScopeForCall() {
    testDep("/** @deprecated %s */ function f() {} f();",
            "Some global scope", DEPRECATED_NAME, DEPRECATED_NAME_REASON);
  }

  public void testNoWarningInDeprecatedFunction() {
    testSame("/** @deprecated */ function f() {} " +
             "/** @deprecated */ function g() { f(); }");
  }

  public void testWarningInNormalClass() {
    testDep("/** @deprecated %s */ function f() {}" +
            "/** @constructor */  var Foo = function() {}; " +
            "Foo.prototype.bar = function() { f(); }",
            "FooBar", DEPRECATED_NAME, DEPRECATED_NAME_REASON);
  }

  public void testWarningForProperty1() {
    testDep("/** @constructor */ function Foo() {}" +
            "/** @deprecated %s */ Foo.prototype.bar = 3;" +
            "Foo.prototype.baz = function() { alert((new Foo()).bar); };",
            "A property is bad",
            DEPRECATED_PROP, DEPRECATED_PROP_REASON);
  }

  public void testWarningForProperty2() {
    testDep("/** @constructor */ function Foo() {}" +
            "/** @deprecated %s */ Foo.prototype.bar = 3;" +
            "Foo.prototype.baz = function() { alert(this.bar); };",
            "Zee prop, it is deprecated!",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testWarningForDeprecatedClass() {
    testDep("/** @constructor \n* @deprecated %s */ function Foo() {} " +
            "function f() { new Foo(); }",
            "Use the class 'Bar'",
            DEPRECATED_CLASS,
            DEPRECATED_CLASS_REASON);
  }

  public void testNoWarningForDeprecatedClassInstance() {
    testSame("/** @constructor \n * @deprecated */ function Foo() {} " +
             "/** @param {Foo} x */ function f(x) { return x; }");
  }

  public void testWarningForDeprecatedSuperClass() {
    testDep("/** @constructor \n * @deprecated %s */ function Foo() {} " +
            "/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
            "function f() { new SubFoo(); }",
            "Superclass to the rescue!",
            DEPRECATED_CLASS,
            DEPRECATED_CLASS_REASON);
  }

  public void testWarningForDeprecatedSuperClass2() {
    testDep("/** @constructor \n * @deprecated %s */ function Foo() {} " +
            "var namespace = {}; " +
            "/** @constructor \n * @extends {Foo} */ " +
            "namespace.SubFoo = function() {}; " +
            "function f() { new namespace.SubFoo(); }",
            "Its only weakness is Kryptoclass",
            DEPRECATED_CLASS,
            DEPRECATED_CLASS_REASON);
  }

  public void testWarningForPrototypeProperty() {
    testDep("/** @constructor */ function Foo() {}" +
            "/** @deprecated %s */ Foo.prototype.bar = 3;" +
            "Foo.prototype.baz = function() { alert(Foo.prototype.bar); };",
            "It is now in production, use that model...",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testNoWarningForNumbers() {
    testSame("/** @constructor */ function Foo() {}" +
             "/** @deprecated */ Foo.prototype.bar = 3;" +
             "Foo.prototype.baz = function() { alert(3); };");
  }

  public void testWarningForMethod1() {
    testDep("/** @constructor */ function Foo() {}" +
            "/** @deprecated %s */ Foo.prototype.bar = function() {};" +
            "Foo.prototype.baz = function() { this.bar(); };",
            "There is a madness to this method",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testWarningForMethod2() {
    testDep("/** @constructor */ function Foo() {} " +
            "/** @deprecated %s */ Foo.prototype.bar; " +
            "Foo.prototype.baz = function() { this.bar(); };",
            "Stop the ringing!",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testNoWarningInDeprecatedClass() {
    testSame("/** @deprecated */ function f() {} " +
             "/** @constructor \n * @deprecated */ " +
             "var Foo = function() {}; " +
             "Foo.prototype.bar = function() { f(); }");
  }

  public void testNoWarningInDeprecatedClass2() {
    testSame("/** @deprecated */ function f() {} " +
             "/** @constructor \n * @deprecated */ " +
             "var Foo = function() {}; " +
             "Foo.bar = function() { f(); }");
  }

  public void testNoWarningInDeprecatedStaticMethod() {
    testSame("/** @deprecated */ function f() {} " +
             "/** @constructor */ " +
             "var Foo = function() {}; " +
             "/** @deprecated */ Foo.bar = function() { f(); }");
  }

  public void testWarningInStaticMethod() {
    testDep("/** @deprecated %s */ function f() {} " +
            "/** @constructor */ " +
            "var Foo = function() {}; " +
            "Foo.bar = function() { f(); }",
            "crazy!",
            DEPRECATED_NAME,
            DEPRECATED_NAME_REASON);
  }

  public void testDeprecatedObjLitKey() {
    testDep("var f = {}; /** @deprecated %s */ f.foo = 3; " +
            "function g() { return f.foo; }",
            "It is literally not used anymore",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testWarningForSubclassMethod() {
    testDep("/** @constructor */ function Foo() {}" +
            "Foo.prototype.bar = function() {};" +
            "/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
            "/** @deprecated %s */ SubFoo.prototype.bar = function() {};" +
            "function f() { (new SubFoo()).bar(); };",
            "I have a parent class!",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testWarningForSuperClassWithDeprecatedSubclassMethod() {
    testSame("/** @constructor */ function Foo() {}" +
             "Foo.prototype.bar = function() {};" +
             "/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
             "/** @deprecated \n * @override */ SubFoo.prototype.bar = " +
             "function() {};" +
             "function f() { (new Foo()).bar(); };");
  }

  public void testWarningForSuperclassMethod() {
    testDep("/** @constructor */ function Foo() {}" +
            "/** @deprecated %s */ Foo.prototype.bar = function() {};" +
            "/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
            "SubFoo.prototype.bar = function() {};" +
            "function f() { (new SubFoo()).bar(); };",
            "I have a child class!",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testWarningForSuperclassMethod2() {
    testDep("/** @constructor */ function Foo() {}" +
            "/** @deprecated %s \n* @protected */" +
            "Foo.prototype.bar = function() {};" +
            "/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
            "/** @protected */SubFoo.prototype.bar = function() {};" +
            "function f() { (new SubFoo()).bar(); };",
            "I have another child class...",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testWarningForBind() {
    testDep("/** @deprecated %s */ Function.prototype.bind = function() {};" +
            "(function() {}).bind();",
            "I'm bound to this method...",
            DEPRECATED_PROP,
            DEPRECATED_PROP_REASON);
  }

  public void testWarningForDeprecatedClassInGlobalScope() {
    testDep("/** @constructor \n * @deprecated %s */ var Foo = function() {};" +
            "new Foo();",
            "I'm a very worldly object!",
            DEPRECATED_CLASS,
            DEPRECATED_CLASS_REASON);
  }

  public void testNoWarningForPrototypeCopying() {
    testSame("/** @constructor */ var Foo = function() {};" +
             "Foo.prototype.bar = function() {};" +
             "/** @deprecated */ Foo.prototype.baz = Foo.prototype.bar;" +
             "(new Foo()).bar();");
  }

  public void testNoWarningOnDeprecatedPrototype() {
    // This used to cause an NPE.
    testSame("/** @constructor */ var Foo = function() {};" +
        "/** @deprecated */ Foo.prototype = {};" +
        "Foo.prototype.bar = function() {};");
  }

  public void testPrivateAccessForNames() {
    testSame("/** @private */ function foo_() {}; foo_();");
    test(new String[] {
      "/** @private */ function foo_() {};",
      "foo_();"
    }, null, BAD_PRIVATE_GLOBAL_ACCESS);
  }

  public void testPrivateAccessForProperties1() {
    testSame("/** @constructor */ function Foo() {}" +
        "/** @private */ Foo.prototype.bar_ = function() {};" +
        "Foo.prototype.baz = function() { this.bar_(); }; (new Foo).bar_();");
  }

  public void testPrivateAccessForProperties2() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {}",
      "/** @private */ Foo.prototype.bar_ = function() {};" +
      "Foo.prototype.baz = function() { this.bar_(); }; (new Foo).bar_();"
    });
  }

  public void testPrivateAccessForProperties3() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {}" +
      "/** @private */ Foo.prototype.bar_ = function() {}; (new Foo).bar_();",
      "Foo.prototype.baz = function() { this.bar_(); };"
    });
  }

  public void testPrivateAccessForProperties4() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {}" +
      "/** @private */ Foo.prototype.bar_ = function() {};",
      "Foo.prototype['baz'] = function() { (new Foo()).bar_(); };"
    });
  }

  public void testNoPrivateAccessForProperties1() {
    test(new String[] {
      "/** @constructor */ function Foo() {} (new Foo).bar_();",
      "/** @private */ Foo.prototype.bar_ = function() {};" +
      "Foo.prototype.baz = function() { this.bar_(); };"
    }, null, BAD_PRIVATE_PROPERTY_ACCESS);
  }

  public void testNoPrivateAccessForProperties2() {
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @private */ Foo.prototype.bar_ = function() {};" +
      "Foo.prototype.baz = function() { this.bar_(); };",
      "(new Foo).bar_();"
    }, null, BAD_PRIVATE_PROPERTY_ACCESS);
  }

  public void testNoPrivateAccessForProperties3() {
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @private */ Foo.prototype.bar_ = function() {};",
      "/** @constructor */ function OtherFoo() { (new Foo).bar_(); }"
    }, null, BAD_PRIVATE_PROPERTY_ACCESS);
  }

  public void testNoPrivateAccessForProperties4() {
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @private */ Foo.prototype.bar_ = function() {};",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() { this.bar_(); }"
    }, null, BAD_PRIVATE_PROPERTY_ACCESS);
  }

  public void testNoPrivateAccessForProperties5() {
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @private */ Foo.prototype.bar_ = function() {};",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {};" +
      "SubFoo.prototype.baz = function() { this.bar_(); }"
    }, null, BAD_PRIVATE_PROPERTY_ACCESS);
  }

  public void testNoPrivateAccessForProperties6() {
    // Overriding a private property with a non-private property
    // in a different file causes problems.
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @private */ Foo.prototype.bar_ = function() {};",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {};" +
      "SubFoo.prototype.bar_ = function() {};"
    }, null, BAD_PRIVATE_PROPERTY_ACCESS);
  }

  public void testNoPrivateAccessForProperties7() {
    // It's OK to override a private property with a non-private property
    // in the same file, but you'll get yelled at when you try to use it.
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @private */ Foo.prototype.bar_ = function() {};" +
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {};" +
      "SubFoo.prototype.bar_ = function() {};",
      "SubFoo.prototype.baz = function() { this.bar_(); }"
    }, null, BAD_PRIVATE_PROPERTY_ACCESS);
  }

  public void testNoPrivateAccessForProperties8() {
    test(new String[] {
      "/** @constructor */ function Foo() { /** @private */ this.bar_ = 3; }",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() { /** @private */ this.bar_ = 3; };"
    }, null, PRIVATE_OVERRIDE);
  }

  public void testProtectedAccessForProperties1() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {}" +
      "/** @protected */ Foo.prototype.bar = function() {};" +
      "(new Foo).bar();",
      "Foo.prototype.baz = function() { this.bar(); };"
    });
  }

  public void testProtectedAccessForProperties2() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {}" +
      "/** @protected */ Foo.prototype.bar = function() {};" +
      "(new Foo).bar();",
      "/** @constructor \n * @extends {Foo} */" +
      "function SubFoo() { this.bar(); }"
    });
  }

  public void testProtectedAccessForProperties3() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {}" +
      "/** @protected */ Foo.prototype.bar = function() {};" +
      "(new Foo).bar();",
      "/** @constructor \n * @extends {Foo} */" +
      "function SubFoo() { }" +
      "SubFoo.baz = function() { (new Foo).bar(); }"
    });
  }

  public void testProtectedAccessForProperties4() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {}" +
      "/** @protected */ Foo.bar = function() {};",
      "/** @constructor \n * @extends {Foo} */" +
      "function SubFoo() { Foo.bar(); }"
    });
  }

  public void testProtectedAccessForProperties5() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {}" +
      "/** @protected */ Foo.prototype.bar = function() {};" +
      "(new Foo).bar();",
      "/** @constructor \n * @extends {Foo} */" +
      "var SubFoo = function() { this.bar(); }"
    });
  }

  public void testProtectedAccessForProperties6() {
    testSame(new String[] {
      "var goog = {};" +
      "/** @constructor */ goog.Foo = function() {};" +
      "/** @protected */ goog.Foo.prototype.bar = function() {};",
      "/** @constructor \n * @extends {goog.Foo} */" +
      "goog.SubFoo = function() { this.bar(); };"
    });
  }

  public void testNoProtectedAccessForProperties1() {
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @protected */ Foo.prototype.bar = function() {};",
      "(new Foo).bar();"
    }, null, BAD_PROTECTED_PROPERTY_ACCESS);
  }

  public void testNoProtectedAccessForProperties2() {
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @protected */ Foo.prototype.bar = function() {};",
      "/** @constructor */ function OtherFoo() { (new Foo).bar(); }"
    }, null, BAD_PROTECTED_PROPERTY_ACCESS);
  }

  public void testNoProtectedAccessForProperties3() {
    test(new String[] {
      "/** @constructor */ function Foo() {} " +
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {}" +
      "/** @protected */ SubFoo.prototype.bar = function() {};",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubberFoo() { (new SubFoo).bar(); }"
    }, null, BAD_PROTECTED_PROPERTY_ACCESS);
  }

  public void testNoProtectedAccessForProperties4() {
    test(new String[] {
      "/** @constructor */ function Foo() { (new SubFoo).bar(); } ",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {}" +
      "/** @protected */ SubFoo.prototype.bar = function() {};",
    }, null, BAD_PROTECTED_PROPERTY_ACCESS);
  }

  public void testNoProtectedAccessForProperties5() {
    test(new String[] {
      "var goog = {};" +
      "/** @constructor */ goog.Foo = function() {};" +
      "/** @protected */ goog.Foo.prototype.bar = function() {};",
      "/** @constructor */" +
      "goog.NotASubFoo = function() { (new goog.Foo).bar(); };"
    }, null, BAD_PROTECTED_PROPERTY_ACCESS);
  }

  public void testNoExceptionsWithBadConstructors1() {
    testSame(new String[] {
      "function Foo() { (new SubFoo).bar(); } " +
      "/** @constructor */ function SubFoo() {}" +
      "/** @protected */ SubFoo.prototype.bar = function() {};"
    });
  }

  public void testNoExceptionsWithBadConstructors2() {
    testSame(new String[] {
      "/** @constructor */ function Foo() {} " +
      "Foo.prototype.bar = function() {};" +
      "/** @constructor */" +
      "function SubFoo() {}" +
      "/** @protected */ " +
      "SubFoo.prototype.bar = function() { (new Foo).bar(); };"
    });
  }

  public void testGoodOverrideOfProtectedProperty() {
    testSame(new String[] {
      "/** @constructor */ function Foo() { } " +
      "/** @protected */ Foo.prototype.bar = function() {};",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {}" +
      "/** @inheritDoc */ SubFoo.prototype.bar = function() {};",
    });
  }

  public void testBadOverrideOfProtectedProperty() {
    test(new String[] {
      "/** @constructor */ function Foo() { } " +
      "/** @protected */ Foo.prototype.bar = function() {};",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {}" +
      "/** @private */ SubFoo.prototype.bar = function() {};",
    }, null, VISIBILITY_MISMATCH);
  }

  public void testBadOverrideOfPrivateProperty() {
    test(new String[] {
      "/** @constructor */ function Foo() { } " +
      "/** @private */ Foo.prototype.bar = function() {};",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {}" +
      "/** @protected */ SubFoo.prototype.bar = function() {};",
    }, null, PRIVATE_OVERRIDE);

    testSame(new String[] {
      "/** @constructor */ function Foo() { } " +
      "/** @private */ Foo.prototype.bar = function() {};",
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {}" +
      "/** @override \n *@suppress{visibility} */\n" +
      " SubFoo.prototype.bar = function() {};",
    });
  }

  public void testAccessOfStaticMethodOnPrivateConstructor() {
    testSame(new String[] {
      "/** @constructor \n * @private */ function Foo() { } " +
      "Foo.create = function() { return new Foo(); };",
      "Foo.create()",
    });
  }

  public void testAccessOfStaticMethodOnPrivateQualifiedConstructor() {
    testSame(new String[] {
      "var goog = {};" +
      "/** @constructor \n * @private */ goog.Foo = function() { }; " +
      "goog.Foo.create = function() { return new goog.Foo(); };",
      "goog.Foo.create()",
    });
  }

  public void testInstanceofOfPrivateConstructor() {
    testSame(new String[] {
      "var goog = {};" +
      "/** @constructor \n * @private */ goog.Foo = function() { }; " +
      "goog.Foo.create = function() { return new goog.Foo(); };",
      "goog instanceof goog.Foo",
    });
  }

  public void testOkAssignmentOfDeprecatedProperty() {
    testSame(
        "/** @constructor */ function Foo() {" +
        " /** @deprecated */ this.bar = 3;" +
        "}");
  }

  public void testBadReadOfDeprecatedProperty() {
    testDep(
        "/** @constructor */ function Foo() {" +
        " /** @deprecated %s */ this.bar = 3;" +
        "  this.baz = this.bar;" +
        "}",
        "GRR",
        DEPRECATED_PROP,
        DEPRECATED_PROP_REASON);
  }

  public void testAutoboxedDeprecatedProperty() {
    test(
        "", // no externs
        "/** @constructor */ function String() {}" +
        "/** @deprecated %s */ String.prototype.length;" +
        "function f() { return 'x'.length; }",
        "GRR",
        DEPRECATED_PROP_REASON,
        null);
  }

  public void testAutoboxedPrivateProperty() {
    test(
        "/** @constructor */ function String() {}" +
        "/** @private */ String.prototype.length;", // externs
        "function f() { return 'x'.length; }",
        "", // output
        BAD_PRIVATE_PROPERTY_ACCESS,
        null);
  }

  public void testNullableDeprecatedProperty() {
    testDep(
        "/** @constructor */ function Foo() {}" +
        "/** @deprecated %s */ Foo.prototype.length;" +
        "/** @param {?Foo} x */ function f(x) { return x.length; }",
        "GRR",
        DEPRECATED_PROP,
        DEPRECATED_PROP_REASON);
  }

  public void testNullablePrivateProperty() {
    test(new String[] {
        "/** @constructor */ function Foo() {}" +
        "/** @private */ Foo.prototype.length;",
        "/** @param {?Foo} x */ function f(x) { return x.length; }"
    }, null, BAD_PRIVATE_PROPERTY_ACCESS);
  }

  public void testConstantProperty1() {
    test("/** @constructor */ function A() {" +
        "/** @const */ this.bar = 3;}" +
        "/** @constructor */ function B() {" +
        "/** @const */ this.bar = 3;this.bar += 4;}",
        null, CONST_PROPERTY_REASSIGNED_VALUE);
  }

  public void testConstantProperty2() {
    test("/** @constructor */ function Foo() {}" +
        "/** @const */ Foo.prototype.prop = 2;" +
        "var foo = new Foo();" +
        "foo.prop = 3;",
        null , CONST_PROPERTY_REASSIGNED_VALUE);
  }

  public void testConstantProperty3() {
    testSame("var o = { /** @const */ x: 1 };" +
        "o.x = 2;");
  }

  public void testConstantProperty4() {
    test("/** @constructor */ function cat(name) {}" +
        "/** @const */ cat.test = 1;" +
        "cat.test *= 2;",
        null, CONST_PROPERTY_REASSIGNED_VALUE);
  }

  public void testConstantProperty5() {
    test("/** @constructor */ function Foo() { this.prop = 1;}" +
        "/** @const */ Foo.prototype.prop;" +
        "Foo.prototype.prop = 2",
        null , CONST_PROPERTY_REASSIGNED_VALUE);
  }

  public void testConstantProperty6() {
    test("/** @constructor */ function Foo() { this.prop = 1;}" +
        "/** @const */ Foo.prototype.prop = 2;",
        null , CONST_PROPERTY_REASSIGNED_VALUE);
  }

  public void testConstantProperty7() {
    testSame("/** @constructor */ function Foo() {} " +
      "Foo.prototype.bar_ = function() {};" +
      "/** @constructor \n * @extends {Foo} */ " +
      "function SubFoo() {};" +
      "/** @const */ /** @override */ SubFoo.prototype.bar_ = function() {};" +
      "SubFoo.prototype.baz = function() { this.bar_(); }");
  }

  public void testConstantProperty8() {
    testSame("var o = { /** @const */ x: 1 };" +
        "var y = o.x;");
  }

  public void testConstantProperty9() {
    testSame("/** @constructor */ function A() {" +
        "/** @const */ this.bar = 3;}" +
        "/** @constructor */ function B() {" +
        "this.bar = 4;}");
  }

  public void testConstantProperty10() {
    testSame("/** @constructor */ function Foo() { this.prop = 1;}" +
        "/** @const */ Foo.prototype.prop;");
  }

  public void testConstantProperty11() {
    test("/** @constructor */ function Foo() {}" +
        "/** @const */ Foo.prototype.bar;" +
        "/**\n" +
        " * @constructor\n" +
        " * @extends {Foo}\n" +
        " */ function SubFoo() { this.bar = 5; this.bar = 6; }",
        null , CONST_PROPERTY_REASSIGNED_VALUE);
  }

  public void testConstantProperty12() {
    testSame("/** @constructor */ function Foo() {}" +
        "/** @const */ Foo.prototype.bar;" +
        "/**\n" +
        " * @constructor\n" +
        " * @extends {Foo}\n" +
        " */ function SubFoo() { this.bar = 5; }" +
        "/**\n" +
        " * @constructor\n" +
        " * @extends {Foo}\n" +
        " */ function SubFoo2() { this.bar = 5; }");
  }

  public void testConstantProperty13() {
    test("/** @constructor */ function Foo() {}" +
        "/** @const */ Foo.prototype.bar;" +
        "/**\n" +
        " * @constructor\n" +
        " * @extends {Foo}\n" +
        " */ function SubFoo() { this.bar = 5; }" +
        "/**\n" +
        " * @constructor\n" +
        " * @extends {SubFoo}\n" +
        " */ function SubSubFoo() { this.bar = 5; }",
        null , CONST_PROPERTY_REASSIGNED_VALUE);
  }

  public void testConstantProperty14() {
    test("/** @constructor */ function Foo() {" +
        "/** @const */ this.bar = 3; delete this.bar; }",
        null, CONST_PROPERTY_DELETED);
  }

  public void testSuppressConstantProperty() {
    testSame("/** @constructor */ function A() {" +
        "/** @const */ this.bar = 3;}" +
        "/**\n" +
        " * @suppress {constantProperty}\n" +
        " * @constructor\n" +
        " */ function B() {" +
        "/** @const */ this.bar = 3;this.bar += 4;}");
  }

  public void testSuppressConstantProperty2() {
    testSame("/** @constructor */ function A() {" +
        "/** @const */ this.bar = 3;}" +
        "/**\n" +
        " * @suppress {const}\n" +
        " * @constructor\n" +
        " */ function B() {" +
        "/** @const */ this.bar = 3;this.bar += 4;}");
  }

  public void testFinalClassCannotBeSubclassed() {
    test(
        "/**\n"
        + " * @constructor\n"
        + " * @const\n"
        + " */ Foo = function() {};\n"
        + "/**\n"
        + " * @constructor\n"
        + " * @extends {Foo}\n*"
        + " */ Bar = function() {};",
        null, EXTEND_FINAL_CLASS);
    test(
        "/**\n"
        + " * @constructor\n"
        + " * @const\n"
        + " */ function Foo() {};\n"
        + "/**\n"
        + " * @constructor\n"
        + " * @extends {Foo}\n*"
        + " */ function Bar() {};",
        null, EXTEND_FINAL_CLASS);
  }
}
