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