/*
* 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_PACKAGE_PROPERTY_ACCESS;
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_PROPERTY_OVERRIDE_IN_FILE_WITH_FILEOVERVIEW_VISIBILITY;
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.CONVENTION_MISMATCH;
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;
import com.google.common.collect.ImmutableList;
/**
* Tests for {@link CheckAccessControls}.
*
* @author nicksantos@google.com (Nick Santos)
*/
public final class CheckAccessControlsTest extends TypeICompilerTestCase {
private static final DiagnosticGroup NTI_CONST =
new DiagnosticGroup(
GlobalTypeInfo.CONST_WITHOUT_INITIALIZER,
GlobalTypeInfo.COULD_NOT_INFER_CONST_TYPE,
GlobalTypeInfo.MISPLACED_CONST_ANNOTATION,
NewTypeInference.CONST_REASSIGNED,
NewTypeInference.CONST_PROPERTY_REASSIGNED,
NewTypeInference.CONST_PROPERTY_DELETED);
public CheckAccessControlsTest() {
super(CompilerTypeTestCase.DEFAULT_EXTERNS);
parseTypeInfo = true;
enableClosurePass();
enableRewriteClosureCode();
}
@Override
protected int getNumRepetitions() {
return 1;
}
@Override
protected CompilerPass getProcessor(final Compiler compiler) {
return new CheckAccessControls(compiler, true);
}
@Override
protected CompilerOptions getOptions() {
CompilerOptions options = super.getOptions();
options.setWarningLevel(DiagnosticGroups.ACCESS_CONTROLS, CheckLevel.ERROR);
options.setWarningLevel(DiagnosticGroups.DEPRECATED, CheckLevel.ERROR);
options.setWarningLevel(DiagnosticGroups.CONSTANT_PROPERTY, CheckLevel.ERROR);
// Disable NTI's native const checks so as to suppress duplicate warnings that
// prevent us from testing the const checks of CheckAccessControls itself.
options.setWarningLevel(NTI_CONST, CheckLevel.OFF);
return options;
}
private void testDepName(String js, String errorMessage) {
test(js, null, DEPRECATED_NAME_REASON, null, errorMessage);
}
private void testDepProp(String js, String errorMessage) {
test(js, null, DEPRECATED_PROP_REASON, null, errorMessage);
}
private void testDepClass(String js, String errorMessage) {
test(js, null, DEPRECATED_CLASS_REASON, null, errorMessage);
}
public void testDeprecatedFunctionNoReason() {
testError("/** @deprecated */ function f() {} function g() { f(); }", DEPRECATED_NAME);
}
public void testDeprecatedFunction() {
testDepName(
"/** @deprecated Some Reason */ function f() {} function g() { f(); }",
"Variable f has been deprecated: Some Reason");
}
public void testWarningOnDeprecatedConstVariable() {
testDepName(
"/** @deprecated Another reason */ var f = 4; function g() { alert(f); }",
"Variable f has been deprecated: Another reason");
}
public void testThatNumbersArentDeprecated() {
testSame("/** @deprecated */ var f = 4; var h = 3; function g() { alert(h); }");
}
public void testDeprecatedFunctionVariable() {
testDepName(
"/** @deprecated I like g... */ var f = function() {}; function g() { f(); }",
"Variable f has been deprecated: I like g...");
}
public void testNoWarningInGlobalScope() {
testSame("var goog = {}; goog.makeSingleton = function(x) {};"
+ "/** @deprecated */ function f() {} goog.makeSingleton(f);");
}
public void testNoWarningInGlobalScopeForCall() {
testDepName(
"/** @deprecated Some global scope */ function f() {} f();",
"Variable f has been deprecated: Some global scope");
}
public void testNoWarningInDeprecatedFunction() {
testSame("/** @deprecated */ function f() {} /** @deprecated */ function g() { f(); }");
}
public void testWarningInNormalClass() {
testDepName(
"/** @deprecated FooBar */ function f() {}"
+ "/** @constructor */ var Foo = function() {}; "
+ "Foo.prototype.bar = function() { f(); }",
"Variable f has been deprecated: FooBar");
}
public void testWarningForProperty1() {
testDepProp(
"/** @constructor */ function Foo() {}"
+ "/** @deprecated A property is bad */ Foo.prototype.bar = 3;"
+ "Foo.prototype.baz = function() { alert((new Foo()).bar); };",
"Property bar of type Foo has been deprecated: A property is bad");
}
public void testWarningForProperty2() {
testDepProp(
"/** @constructor */ function Foo() {}"
+ "/** @deprecated Zee prop, it is deprecated! */ Foo.prototype.bar = 3;"
+ "Foo.prototype.baz = function() { alert(this.bar); };",
"Property bar of type Foo has been deprecated: Zee prop, it is deprecated!");
}
public void testWarningForDeprecatedClass() {
testDepClass(
"/** @constructor \n* @deprecated Use the class 'Bar' */ function Foo() {} "
+ "function f() { new Foo(); }",
"Class Foo has been deprecated: Use the class 'Bar'");
}
public void testWarningForDeprecatedClassNoReason() {
testError(
"/** @constructor \n* @deprecated */ function Foo() {} " + "function f() { new Foo(); }",
DEPRECATED_CLASS);
}
public void testNoWarningForDeprecatedClassInstance() {
testSame("/** @constructor \n * @deprecated */ function Foo() {} "
+ "/** @param {Foo} x */ function f(x) { return x; }");
}
public void testWarningForDeprecatedSuperClass() {
testDepClass(
"/** @constructor \n * @deprecated Superclass to the rescue! */ function Foo() {} "
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "function f() { new SubFoo(); }",
"Class SubFoo has been deprecated: Superclass to the rescue!");
}
public void testWarningForDeprecatedSuperClass2() {
testDepClass(
"/** @constructor \n * @deprecated Its only weakness is Kryptoclass */ function Foo() {} "
+ "/** @const */ var namespace = {}; "
+ "/** @constructor \n * @extends {Foo} */ "
+ "namespace.SubFoo = function() {}; "
+ "function f() { new namespace.SubFoo(); }",
"Class namespace.SubFoo has been deprecated: Its only weakness is Kryptoclass");
}
public void testWarningForPrototypeProperty() {
String js =
"/** @constructor */ function Foo() {}"
+ "/** @deprecated It is now in production, use that model... */ Foo.prototype.bar = 3;"
+ "Foo.prototype.baz = function() { alert(Foo.prototype.bar); };";
testDepProp(
js,
"Property bar of type Foo.prototype has been deprecated:"
+ " It is now in production, use that model...");
}
public void testNoWarningForNumbers() {
testSame("/** @constructor */ function Foo() {}"
+ "/** @deprecated */ Foo.prototype.bar = 3;"
+ "Foo.prototype.baz = function() { alert(3); };");
}
public void testWarningForMethod1() {
testDepProp(
"/** @constructor */ function Foo() {}"
+ "/** @deprecated There is a madness to this method */"
+ "Foo.prototype.bar = function() {};"
+ "Foo.prototype.baz = function() { this.bar(); };",
"Property bar of type Foo has been deprecated: There is a madness to this method");
}
public void testWarningForMethod2() {
testDepProp(
"/** @constructor */ function Foo() {}"
+ "/** @deprecated Stop the ringing! */ Foo.prototype.bar;"
+ "Foo.prototype.baz = function() { this.bar(); };",
"Property bar of type Foo has been deprecated: Stop the ringing!");
}
public void testNoWarningInDeprecatedClass() {
testSame("/** @deprecated */ function f() {} "
+ "/** @constructor \n * @deprecated */ "
+ "var Foo = function() {}; "
+ "Foo.prototype.bar = function() { f(); }");
}
public void testNoWarningOnDeclaration() {
testSame("/** @constructor */ function F() {\n"
+ " /**\n"
+ " * @type {number}\n"
+ " * @deprecated Use something else.\n"
+ " */\n"
+ " this.code;\n"
+ "}");
}
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() {
testDepName(
"/** @deprecated crazy! */ function f() {} "
+ "/** @constructor */ "
+ "var Foo = function() {}; "
+ "Foo.bar = function() { f(); }",
"Variable f has been deprecated: crazy!");
}
public void testDeprecatedObjLitKey() {
testDepProp(
"/** @const */ var f = {};"
+ "/** @deprecated It is literally not used anymore */ f.foo = 3;"
+ "function g() { return f.foo; }",
"Property foo of type f has been deprecated: It is literally not used anymore");
}
public void testWarningForSubclassMethod() {
testDepProp(
"/** @constructor */ function Foo() {}"
+ "Foo.prototype.bar = function() {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "/** @deprecated I have a parent class! */ SubFoo.prototype.bar = function() {};"
+ "function f() { (new SubFoo()).bar(); };",
"Property bar of type SubFoo has been deprecated: I have a parent class!");
}
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() {
testDepProp(
"/** @constructor */ function Foo() {}"
+ "/** @deprecated I have a child class! */ Foo.prototype.bar = function() {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "SubFoo.prototype.bar = function() {};"
+ "function f() { (new SubFoo()).bar(); };",
"Property bar of type SubFoo has been deprecated: I have a child class!");
}
public void testWarningForSuperclassMethod2() {
testDepProp(
"/** @constructor */ function Foo() {}"
+ "/** @deprecated I have another child class... \n* @protected */"
+ "Foo.prototype.bar = function() {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "/** @protected */SubFoo.prototype.bar = function() {};"
+ "function f() { (new SubFoo()).bar(); };",
"Property bar of type SubFoo has been deprecated: I have another child class...");
}
public void testWarningForBind() {
// NTI reports NTI_REDCLARED_PROPERTY here, which is as intended. If this were a new
// property and not the existing `bind`, then we'd report the deprecation warning as expected
// (see testAutoboxedDeprecatedProperty and testAutoboxedPrivateProperty).
this.mode = TypeInferenceMode.OTI_ONLY;
testDepProp(
"/** @deprecated I'm bound to this method... */ Function.prototype.bind = function() {};"
+ "(function() {}).bind();",
"Property bind of type function has been deprecated: I'm bound to this method...");
}
public void testWarningForDeprecatedClassInGlobalScope() {
testDepClass(
"/** @constructor \n * @deprecated I'm a very worldly object! */ var Foo = function() {};"
+ "new Foo();",
"Class Foo has been deprecated: I'm a very worldly object!");
}
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_();");
testError(new String[] {"/** @private */ function foo_() {};", "foo_();"},
BAD_PRIVATE_GLOBAL_ACCESS);
}
public void testPrivateAccessForNames2() {
// Private by convention
testSame("function foo_() {}; foo_();");
testError(new String[] {"function foo_() {};", "foo_();"}, 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() {
// Even though baz is "part of the Foo class" the access is disallowed since it's
// not in the same file.
testError(new String[] {
"/** @constructor */ function Foo() {}"
+ "/** @private */ Foo.prototype.bar_ = function() {}; (new Foo).bar_();",
"Foo.prototype.baz = function() { this.bar_(); };"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testPrivateAccessForProperties4() {
testSame(
"/** @constructor */ function Foo() {}"
+ "/** @private */ Foo.prototype.bar_ = function() {};"
+ "Foo.prototype['baz'] = function() { (new Foo()).bar_(); };");
}
public void testPrivateAccessForProperties5() {
test(
new String[] {
LINE_JOINER.join(
"/** @constructor */",
"function Parent () {",
" /** @private */",
" this.prop = 'foo';",
"};"),
LINE_JOINER.join(
"/**",
" * @constructor",
" * @extends {Parent}",
" */",
"function Child() {",
" this.prop = 'asdf';",
"}",
"Child.prototype = new Parent();")
},
null,
BAD_PRIVATE_PROPERTY_ACCESS,
null,
"Access to private property prop of Parent not allowed here.");
}
public void testPrivateAccessForProperties6() {
test(
new String[] {
LINE_JOINER.join(
"goog.provide('x.y.z.Parent');",
"",
"/** @constructor */",
"x.y.z.Parent = function() {",
" /** @private */",
" this.prop = 'foo';",
"};"),
LINE_JOINER.join(
"goog.require('x.y.z.Parent');",
"",
"/**",
" * @constructor",
" * @extends {x.y.z.Parent}",
" */",
"function Child() {",
" this.prop = 'asdf';",
"}",
"Child.prototype = new x.y.z.Parent();")
},
null,
BAD_PRIVATE_PROPERTY_ACCESS,
null,
"Access to private property prop of x.y.z.Parent not allowed here.");
}
public void testPrivateAccess_googModule() {
String[] js = new String[] {
LINE_JOINER.join(
"goog.module('example.One');",
"/** @constructor */ function One() {}",
"/** @private */ One.prototype.m = function() {};",
"exports = One;"),
LINE_JOINER.join(
"goog.module('example.two');",
"var One = goog.require('example.One');",
"(new One()).m();"),
};
this.mode = TypeInferenceMode.OTI_ONLY;
test(
js,
null,
BAD_PRIVATE_PROPERTY_ACCESS,
null,
"Access to private property m of One not allowed here.");
this.mode = TypeInferenceMode.NTI_ONLY;
test(
js,
null,
BAD_PRIVATE_PROPERTY_ACCESS,
null,
"Access to private property m of module$exports$example$One not allowed here.");
}
public void testNoPrivateAccessForProperties1() {
testError(new String[] {
"/** @constructor */ function Foo() {} (new Foo).bar_();",
"/** @private */ Foo.prototype.bar_ = function() {};"
+ "Foo.prototype.baz = function() { this.bar_(); };"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties2() {
testError(new String[] {
"/** @constructor */ function Foo() {} "
+ "/** @private */ Foo.prototype.bar_ = function() {};"
+ "Foo.prototype.baz = function() { this.bar_(); };",
"(new Foo).bar_();"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties3() {
testError(new String[] {
"/** @constructor */ function Foo() {} "
+ "/** @private */ Foo.prototype.bar_ = function() {};",
"/** @constructor */ function OtherFoo() { (new Foo).bar_(); }"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties4() {
testError(new String[] {
"/** @constructor */ function Foo() {} "
+ "/** @private */ Foo.prototype.bar_ = function() {};",
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() { this.bar_(); }"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties5() {
testError(new String[] {
"/** @constructor */ function Foo() {} "
+ "/** @private */ Foo.prototype.bar_ = function() {};",
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() {};"
+ "SubFoo.prototype.baz = function() { this.bar_(); }"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties6() {
// Overriding a private property with a non-private property
// in a different file causes problems.
testError(new String[] {
"/** @constructor */ function Foo() {} "
+ "/** @private */ Foo.prototype.bar_ = function() {};",
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() {};"
+ "SubFoo.prototype.bar_ = function() {};"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties6a() {
// Same as above, except with namespaced constructors
testError(new String[] {
"/** @const */ var ns = {};"
+ "/** @constructor */ ns.Foo = function() {}; "
+ "/** @private */ ns.Foo.prototype.bar_ = function() {};",
"/** @constructor \n * @extends {ns.Foo} */ "
+ "ns.SubFoo = function() {};"
+ "ns.SubFoo.prototype.bar_ = function() {};"},
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.
testError(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_(); }"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties8() {
testError(new String[] {
"/** @constructor */ function Foo() { /** @private */ this.bar_ = 3; }",
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() { /** @private */ this.bar_ = 3; };"},
PRIVATE_OVERRIDE);
}
public void testNoPrivateAccessForProperties9() {
testError(new String[] {
"/** @constructor */ function Foo() {}"
+ "Foo.prototype = {"
+ "/** @private */ bar_: 3"
+ "}",
"new Foo().bar_;"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties10() {
testError(new String[] {
"/** @constructor */ function Foo() {}"
+ "Foo.prototype = {"
+ "/** @private */ bar_: function() {}"
+ "}",
"new Foo().bar_();"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties11() {
testError(new String[] {
"/** @constructor */ function Foo() {}"
+ "Foo.prototype = {"
+ "/** @private */ get bar_() { return 1; }"
+ "}",
"var a = new Foo().bar_;"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForProperties12() {
testError(new String[] {
"/** @constructor */ function Foo() {}"
+ "Foo.prototype = {"
+ "/** @private */ set bar_(x) { this.barValue = x; }"
+ "}",
"new Foo().bar_ = 1;"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testNoPrivateAccessForNamespaces() {
testError(new String[] {
"/** @const */ var foo = {};\n"
+ "/** @private */ foo.bar_ = function() {};",
"foo.bar_();"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
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[] {
"/** @const */ var goog = {};"
+ "/** @constructor */ goog.Foo = function() {};"
+ "/** @protected */ goog.Foo.prototype.bar = function() {};",
"/** @constructor \n * @extends {goog.Foo} */"
+ "goog.SubFoo = function() { this.bar(); };"});
}
public void testProtectedAccessForProperties7() {
testSame(new String[] {
"/** @constructor */ var Foo = function() {};"
+ "Foo.prototype = { /** @protected */ bar: function() {} }",
"/** @constructor \n * @extends {Foo} */"
+ "var SubFoo = function() { this.bar(); };"
+ "SubFoo.prototype = { moo: function() { this.bar(); }};"});
}
public void testProtectedAccessForProperties8() {
testSame(new String[] {
"/** @constructor */ var Foo = function() {};"
+ "Foo.prototype = { /** @protected */ bar: function() {} }",
"/** @constructor \n * @extends {Foo} */"
+ "var SubFoo = function() {};"
+ "SubFoo.prototype = { get moo() { this.bar(); }};"});
}
public void testProtectedAccessForProperties9() {
testSame(new String[] {
"/** @constructor */ var Foo = function() {};"
+ "Foo.prototype = { /** @protected */ bar: function() {} }",
"/** @constructor \n * @extends {Foo} */"
+ "var SubFoo = function() {};"
+ "SubFoo.prototype = { set moo(val) { this.x = this.bar(); }};"});
}
public void testProtectedAccessForProperties10() {
// NTI throws NTI_CTOR_IN_DIFFERENT_SCOPE
testSame(ImmutableList.of(
SourceFile.fromCode(
"foo.js",
"/** @constructor */ var Foo = function() {};"
+ "/** @protected */ Foo.prototype.bar = function() {};"),
SourceFile.fromCode(
"sub_foo.js",
"/** @constructor @extends {Foo} */"
+ "var SubFoo = function() {};"
+ "(/** @suppress {newCheckTypes} */ function() {"
+ "SubFoo.prototype.baz = function() { this.bar(); }"
+ "})();")));
}
public void testProtectedAccessForProperties11() {
test(
ImmutableList.of(
SourceFile.fromCode(
"foo.js",
LINE_JOINER.join(
"goog.provide('Foo');",
"/** @interface */ Foo = function() {};",
"/** @protected */ Foo.prop = {};")),
SourceFile.fromCode(
"bar.js",
LINE_JOINER.join(
"goog.require('Foo');",
"/** @constructor @implements {Foo} */",
"function Bar() { Foo.prop; };"))),
null,
null);
}
public void testProtectedAccessForProperties12() {
test(
ImmutableList.of(
SourceFile.fromCode(
"a.js",
LINE_JOINER.join(
"goog.provide('A');",
"/** @constructor */",
"var A = function() {",
" /**",
" * @type {?String}",
" * @protected",
" */",
" this.prop;",
"}")),
SourceFile.fromCode(
"b.js",
LINE_JOINER.join(
"goog.require('A');",
"/**",
" * @constructor",
" * @extends {A}",
" */",
"var B = function() {",
" this.prop.length;",
" this.prop.length;",
"};"))),
null,
null);
}
// FYI: Java warns for the b1.method access in c.js.
// Instead of following that in NTI, we chose to follow the behavior of
// the old JSCompiler type checker, to make migration easier.
public void testProtectedAccessForProperties13() {
test(
ImmutableList.of(
SourceFile.fromCode(
"a.js",
LINE_JOINER.join(
"goog.provide('A');",
"/** @constructor */",
"var A = function() {}",
"/** @protected */",
"A.prototype.method = function() {};")),
SourceFile.fromCode(
"b1.js",
LINE_JOINER.join(
"goog.require('A');",
"goog.provide('B1');",
"/** @constructor @extends {A} */",
"var B1 = function() {};",
"/** @override */",
"B1.prototype.method = function() {};")),
SourceFile.fromCode(
"b2.js",
LINE_JOINER.join(
"goog.require('A');",
"goog.provide('B2');",
"/** @constructor @extends {A} */",
"var B2 = function() {};",
"/** @override */",
"B2.prototype.method = function() {};")),
SourceFile.fromCode(
"c.js",
LINE_JOINER.join(
"goog.require('B1');",
"goog.require('B2');",
"/**",
" * @param {!B1} b1",
" * @constructor",
" * @extends {B2}",
" */",
"var C = function(b1) {",
" var x = b1.method();",
"};"))),
null,
null);
}
public void testNoProtectedAccessForProperties1() {
testError(new String[] {
"/** @constructor */ function Foo() {} "
+ "/** @protected */ Foo.prototype.bar = function() {};",
"(new Foo).bar();"},
BAD_PROTECTED_PROPERTY_ACCESS);
}
public void testNoProtectedAccessForProperties2() {
testError(new String[] {
"/** @constructor */ function Foo() {} "
+ "/** @protected */ Foo.prototype.bar = function() {};",
"/** @constructor */ function OtherFoo() { (new Foo).bar(); }"},
BAD_PROTECTED_PROPERTY_ACCESS);
}
public void testNoProtectedAccessForProperties3() {
testError(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(); }"},
BAD_PROTECTED_PROPERTY_ACCESS);
}
public void testNoProtectedAccessForProperties4() {
testError(new String[] {
"/** @constructor */ function Foo() { (new SubFoo).bar(); } ",
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() {}"
+ "/** @protected */ SubFoo.prototype.bar = function() {};",
},
BAD_PROTECTED_PROPERTY_ACCESS);
}
public void testNoProtectedAccessForProperties5() {
testError(new String[] {
"/** @const */ var goog = {};"
+ "/** @constructor */ goog.Foo = function() {};"
+ "/** @protected */ goog.Foo.prototype.bar = function() {};",
"/** @constructor */"
+ "goog.NotASubFoo = function() { (new goog.Foo).bar(); };"},
BAD_PROTECTED_PROPERTY_ACCESS);
}
public void testNoProtectedAccessForProperties6() {
testError(new String[] {
"/** @constructor */ function Foo() {}"
+ "Foo.prototype = {"
+ "/** @protected */ bar: 3"
+ "}",
"new Foo().bar;"},
BAD_PROTECTED_PROPERTY_ACCESS);
}
public void testNoProtectedAccessForProperties7() {
testError(new String[] {
"/** @constructor */ function Foo() {}"
+ "Foo.prototype = {"
+ "/** @protected */ bar: function() {}"
+ "}",
"new Foo().bar();"},
BAD_PROTECTED_PROPERTY_ACCESS);
}
public void testPackagePrivateAccessForNames() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */\n"
+ "function Parent() {\n"
+ "/** @package */\n"
+ "this.prop = 'foo';\n"
+ "}\n;"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/**"
+ " * @constructor\n"
+ " * @extends {Parent}\n"
+ " */\n"
+ "function Child() {\n"
+ " this.prop = 'asdf';\n"
+ "}\n"
+ "Child.prototype = new Parent();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testPackagePrivateAccessForProperties1() {
testSame("/** @constructor */ function Foo() {}"
+ "/** @package */ Foo.prototype.bar = function() {};"
+ "Foo.prototype.baz = function() { this.bar(); }; (new Foo).bar();");
}
public void testPackagePrivateAccessForProperties2() {
testSame(ImmutableList.of(
SourceFile.fromCode(Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {}"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/** @package */ Foo.prototype.bar = function() {};"
+ "Foo.prototype.baz = function() { this.bar(); }; (new Foo).bar();")));
}
public void testPackagePrivateAccessForProperties3() {
testSame(ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {}"
+ "/** @package */ Foo.prototype.bar = function() {}; (new Foo).bar();"),
SourceFile.fromCode(Compiler.joinPathParts("foo", "baz.js"),
"Foo.prototype.baz = function() { this.bar(); };")));
}
public void testPackagePrivateAccessForProperties4() {
testSame(ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {}"
+ "/** @package */ Foo.prototype.bar = function() {};"),
SourceFile.fromCode(
Compiler.joinPathParts("foo", "baz.js"),
"Foo.prototype['baz'] = function() { (new Foo()).bar(); };")));
}
public void testPackagePrivateAccessForProperties5() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */\n"
+ "function Parent () {\n"
+ " /** @package */\n"
+ " this.prop = 'foo';\n"
+ "};"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/**\n"
+ " * @constructor\n"
+ " * @extends {Parent}\n"
+ " */\n"
+ "function Child() {\n"
+ " this.prop = 'asdf';\n"
+ "}\n"
+ "Child.prototype = new Parent();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testNoPackagePrivateAccessForProperties1() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {} (new Foo).bar();"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/** @package */ Foo.prototype.bar = function() {};"
+ "Foo.prototype.baz = function() { this.bar(); };")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testNoPackagePrivateAccessForProperties2() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {} "
+ "/** @package */ Foo.prototype.bar = function() {};"
+ "Foo.prototype.baz = function() { this.bar(); };"),
SourceFile.fromCode(Compiler.joinPathParts("baz", "quux.js"), "(new Foo).bar();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testNoPackagePrivateAccessForProperties3() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {} "
+ "/** @package */ Foo.prototype.bar = function() {};"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/** @constructor */ function OtherFoo() { (new Foo).bar(); }")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testNoPackagePrivateAccessForProperties4() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {} "
+ "/** @package */ Foo.prototype.bar = function() {};"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() { this.bar(); }")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testNoPackagePrivateAccessForNamespaces() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @const */ var foo = {};\n"
+ "/** @package */ foo.bar = function() {};"),
SourceFile.fromCode(Compiler.joinPathParts("baz", "quux.js"), "foo.bar();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testNoPackagePrivateAccessForProperties5() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {} "
+ "/** @package */ Foo.prototype.bar = function() {};"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() {};"
+ "SubFoo.prototype.baz = function() { this.bar(); }")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testNoPackagePrivateAccessForProperties6() {
// Overriding a private property with a non-package-private property
// in a different file causes problems.
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {} "
+ "/** @package */ Foo.prototype.bar = function() {};"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() {};"
+ "SubFoo.prototype.bar = function() {};")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testNoPackagePrivateAccessForProperties7() {
// It's OK to override a package-private property with a
// non-package-private property in the same file, but you'll get
// yelled at when you try to use it.
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/** @constructor */ function Foo() {} "
+ "/** @package */ Foo.prototype.bar = function() {};"
+ "/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() {};"
+ "SubFoo.prototype.bar = function() {};"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"SubFoo.prototype.baz = function() { this.bar(); }")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void
testOverrideWithoutVisibilityRedeclInFileWithFileOverviewVisibilityNotAllowed_OneFile() {
testError(
"/**\n"
+ "* @fileoverview\n"
+ "* @package\n"
+ "*/\n"
+ "/** @struct @constructor */\n"
+ "Foo = function() {};\n"
+ "/** @private */\n"
+ "Foo.prototype.privateMethod_ = function() {};\n"
+ "/** @struct @constructor @extends {Foo} */\n"
+ "Bar = function() {};\n"
+ "/** @override */\n"
+ "Bar.prototype.privateMethod_ = function() {};\n",
BAD_PROPERTY_OVERRIDE_IN_FILE_WITH_FILEOVERVIEW_VISIBILITY);
}
public void testNamespacedFunctionDoesNotNeedVisibilityRedeclInFileWithFileOverviewVisibility() {
testSame("/**\n"
+ " * @fileoverview\n"
+ " * @package\n"
+ " */\n"
+ "/** @return {string} */\n"
+ "foo.bar = function() { return 'asdf'; };");
}
public void
testOverrideWithoutVisibilityRedeclInFileWithFileOverviewVisibilityNotAllowed_TwoFiles() {
testError(new String[] {
"/** @struct @constructor */\n"
+ "Foo = function() {};\n"
+ "/** @protected */\n"
+ "Foo.prototype.protectedMethod = function() {};\n",
" /**\n"
+ "* @fileoverview \n"
+ "* @package\n"
+ "*/\n"
+ "/** @struct @constructor @extends {Foo} */\n"
+ "Bar = function() {};\n"
+ "/** @override */\n"
+ "Bar.prototype.protectedMethod = function() {};\n"},
BAD_PROPERTY_OVERRIDE_IN_FILE_WITH_FILEOVERVIEW_VISIBILITY);
}
public void testOverrideWithoutVisibilityRedeclInFileWithNoFileOverviewOk() {
testSame("/** @struct @constructor */\n"
+ "Foo = function() {};\n"
+ "/** @private */\n"
+ "Foo.prototype.privateMethod_ = function() {};\n"
+ "/** @struct @constructor @extends {Foo} */\n"
+ "Bar = function() {};\n"
+ "/** @override */\n"
+ "Bar.prototype.privateMethod_ = function() {};\n");
}
public void testOverrideWithoutVisibilityRedeclInFileWithNoFileOverviewVisibilityOk() {
testSame("/**\n"
+ " * @fileoverview\n"
+ " */\n"
+ "/** @struct @constructor */\n"
+ "Foo = function() {};\n"
+ "/** @private */\n"
+ "Foo.prototype.privateMethod_ = function() {};\n"
+ "/** @struct @constructor @extends {Foo} */\n"
+ "Bar = function() {};\n"
+ "/** @override */\n"
+ "Bar.prototype.privateMethod_ = function() {};\n");
}
public void testOverrideWithVisibilityRedeclInFileWithFileOverviewVisibilityOk_OneFile() {
testSame("/**\n"
+ " * @fileoverview\n"
+ " * @package\n"
+ " */\n"
+ "/** @struct @constructor */\n"
+ "Foo = function() {};\n"
+ "/** @private */\n"
+ "Foo.prototype.privateMethod_ = function() {};\n"
+ "/** @struct @constructor @extends {Foo} */\n"
+ "Bar = function() {};\n"
+ "/** @override @private */\n"
+ "Bar.prototype.privateMethod_ = function() {};\n");
}
public void testOverrideWithVisibilityRedeclInFileWithFileOverviewVisibilityOk_TwoFiles() {
testSame(new String[] {
"/** @struct @constructor */\n"
+ "Foo = function() {};\n"
+ "/** @protected */\n"
+ "Foo.prototype.protectedMethod = function() {};\n",
" /**\n"
+ "* @fileoverview\n"
+ "* @package\n"
+ "*/\n"
+ "/** @struct @constructor @extends {Foo} */\n"
+ "Bar = function() {};\n"
+ "/** @override @protected */\n"
+ "Bar.prototype.protectedMethod = function() {};\n"});
}
public void testPublicFileOverviewVisibilityDoesNotApplyToNameWithExplicitPackageVisibility() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @public\n"
+ " */\n"
+ "/** @constructor @package */ function Foo() {};"),
SourceFile.fromCode(Compiler.joinPathParts("baz", "quux.js"), "new Foo();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testPackageFileOverviewVisibilityDoesNotApplyToNameWithExplicitPublicVisibility() {
testSame(ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @package\n"
+ " */\n"
+ "/** @constructor @public */ function Foo() {};"),
SourceFile.fromCode(Compiler.joinPathParts("baz", "quux.js"), "new Foo();")));
}
public void testPackageFileOverviewVisibilityAppliesToNameWithoutExplicitVisibility() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @package\n"
+ " */\n"
+ "/** @constructor */\n"
+ "var Foo = function() {};\n"),
SourceFile.fromCode(Compiler.joinPathParts("baz", "quux.js"), "new Foo();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void
testPackageFileOverviewVisibilityDoesNotApplyToPropertyWithExplicitPublicVisibility() {
testSame(ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @package\n"
+ " */\n"
+ "/** @constructor */\n"
+ "Foo = function() {};\n"
+ "/** @public */\n"
+ "Foo.prototype.bar = function() {};\n"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"var foo = new Foo();\n"
+ "foo.bar();")));
}
public void testFileoverviewVisibilityDoesNotApplyToGoogProvidedNamespace1() {
test(
ImmutableList.of(
SourceFile.fromCode("foo.js", "goog.provide('foo');"),
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
LINE_JOINER.join(
"/**\n",
" * @fileoverview\n",
" * @package\n",
" */\n",
"goog.provide('foo.bar');")),
SourceFile.fromCode("bar.js", "goog.require('foo')")),
ImmutableList.of(
SourceFile.fromCode("foo.js", "/** @const */ var foo={};"),
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
LINE_JOINER.join(
"/**\n",
" * @fileoverview\n",
" * @package\n",
" */\n",
"/** @const */ foo.bar={};")),
SourceFile.fromCode("bar.js", "")),
null,
null);
}
public void testFileoverviewVisibilityDoesNotApplyToGoogProvidedNamespace2() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
LINE_JOINER.join(
"/**",
" * @fileoverview",
" * @package",
" */",
"goog.provide('foo.bar');")),
SourceFile.fromCode("foo.js", "goog.provide('foo');"),
SourceFile.fromCode(
"bar.js",
LINE_JOINER.join(
"goog.require('foo');",
"var x = foo;"))),
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
LINE_JOINER.join(
"/** @const */var foo={};",
"/**",
" * @fileoverview",
" * @package",
" */",
"/** @const */foo.bar={};")),
SourceFile.fromCode("foo.js", ""),
SourceFile.fromCode("bar.js", "var x=foo")),
null,
null);
}
public void testFileoverviewVisibilityDoesNotApplyToGoogProvidedNamespace3() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
LINE_JOINER.join(
"/**",
" * @fileoverview",
" * @package",
" */",
"goog.provide('one.two');",
"one.two.three = function(){};")),
SourceFile.fromCode(
"baz.js",
LINE_JOINER.join(
"goog.require('one.two');",
"var x = one.two;"))),
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
LINE_JOINER.join(
"/** @const */ var one={};",
"/**",
" * @fileoverview",
" * @package",
" */",
"/** @const */ one.two={};",
"one.two.three=function(){};")),
SourceFile.fromCode("baz.js", "var x=one.two")),
null,
null);
}
public void testFileoverviewVisibilityDoesNotApplyToGoogProvidedNamespace4() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @package\n"
+ " */\n"
+ "goog.provide('one.two');\n"
+ "one.two.three = function(){};"),
SourceFile.fromCode(
"baz.js",
"goog.require('one.two');\n"
+ "var x = one.two.three();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void
testPublicFileOverviewVisibilityDoesNotApplyToPropertyWithExplicitPackageVisibility() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @public\n"
+ " */\n"
+ "/** @constructor */\n"
+ "Foo = function() {};\n"
+ "/** @package */\n"
+ "Foo.prototype.bar = function() {};\n"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"var foo = new Foo();\n"
+ "foo.bar();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testPublicFileOverviewVisibilityAppliesToPropertyWithoutExplicitVisibility() {
testSame(ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @public\n"
+ " */\n"
+ "/** @constructor */\n"
+ "Foo = function() {};\n"
+ "Foo.prototype.bar = function() {};\n"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"var foo = new Foo();\n"
+ "foo.bar();")));
}
public void testPackageFileOverviewVisibilityAppliesToPropertyWithoutExplicitVisibility() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @package\n"
+ " */\n"
+ "/** @constructor */\n"
+ "Foo = function() {};\n"
+ "Foo.prototype.bar = function() {};\n"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"var foo = new Foo();\n"
+ "foo.bar();")),
null, BAD_PACKAGE_PROPERTY_ACCESS);
}
public void testFileOverviewVisibilityComesFromDeclarationFileNotUseFile() {
test(
ImmutableList.of(
SourceFile.fromCode(
Compiler.joinPathParts("foo", "bar.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @package\n"
+ " */\n"
+ "/** @constructor */\n"
+ "Foo = function() {};\n"
+ "Foo.prototype.bar = function() {};\n"),
SourceFile.fromCode(
Compiler.joinPathParts("baz", "quux.js"),
"/**\n"
+ " * @fileoverview\n"
+ " * @public\n"
+ " */\n"
+ "var foo = new Foo();\n"
+ "foo.bar();")),
null, BAD_PACKAGE_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() {
testError(new String[] {
"/** @constructor */ function Foo() { } "
+ "/** @protected */ Foo.prototype.bar = function() {};",
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() {}"
+ "/** @private */ SubFoo.prototype.bar = function() {};",
},
VISIBILITY_MISMATCH);
}
public void testBadOverrideOfPrivateProperty() {
testError(new String[] {
"/** @constructor */ function Foo() { } "
+ "/** @private */ Foo.prototype.bar = function() {};",
"/** @constructor \n * @extends {Foo} */ "
+ "function SubFoo() {}"
+ "/** @protected */ SubFoo.prototype.bar = function() {};",
},
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[] {
"/** @const */ 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[] {
"/** @const */ 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() {
testDepProp(
"/** @constructor */ function Foo() {"
+ " /** @deprecated GRR */ this.bar = 3;"
+ " this.baz = this.bar;"
+ "}",
"Property bar of type Foo has been deprecated: GRR");
}
public void testAutoboxedDeprecatedProperty() {
test(DEFAULT_EXTERNS,
"/** @deprecated %s */ String.prototype.prop;"
+ "function f() { return 'x'.prop; }",
(String) null, DEPRECATED_PROP_REASON, null);
}
public void testAutoboxedPrivateProperty() {
test(
// externs
DEFAULT_EXTERNS + "/** @private */ String.prototype.prop;",
"function f() { return 'x'.prop; }",
(String) null, // no output
BAD_PRIVATE_PROPERTY_ACCESS, null);
}
public void testNullableDeprecatedProperty() {
testError(
"/** @constructor */ function Foo() {}"
+ "/** @deprecated */ Foo.prototype.length;"
+ "/** @param {?Foo} x */ function f(x) { return x.length; }",
DEPRECATED_PROP);
}
public void testNullablePrivateProperty() {
testError(new String[] {
"/** @constructor */ function Foo() {}"
+ "/** @private */ Foo.prototype.length;",
"/** @param {?Foo} x */ function f(x) { return x.length; }"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testPrivatePropertyByConvention1() {
testError(new String[] {
"/** @constructor */ function Foo() {}\n"
+ "/** @type {number} */ Foo.prototype.length_;\n",
"/** @param {?Foo} x */ function f(x) { return x.length_; }\n"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testPrivatePropertyByConvention2() {
testError(new String[] {
"/** @constructor */ function Foo() {\n"
+ " /** @type {number} */ this.length_ = 1;\n"
+ "}\n"
+ "/** @type {number} */ Foo.prototype.length_;\n",
"/** @param {Foo} x */ function f(x) { return x.length_; }\n"},
BAD_PRIVATE_PROPERTY_ACCESS);
}
public void testDeclarationAndConventionConflict1() {
testError(
"/** @constructor */ function Foo() {} /** @protected */ Foo.prototype.length_;",
CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict2() {
testError(
"/** @constructor */ function Foo() {}\n"
+ "/** @public {number} */ Foo.prototype.length_;\n",
CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict3() {
testError(
"/** @constructor */ function Foo() { /** @protected */ this.length_ = 1;\n}\n",
CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict4a() {
testError(
"/** @constructor */ function Foo() {}"
+ "Foo.prototype = { /** @protected */ length_: 1 }\n"
+ "new Foo().length_",
CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict4b() {
testError(
"/** @const */ var NS = {}; /** @constructor */ NS.Foo = function() {};"
+ "NS.Foo.prototype = { /** @protected */ length_: 1 };\n"
+ "(new NS.Foo()).length_;",
CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict5() {
testError(
"/** @constructor */ function Foo() {}\n"
+ "Foo.prototype = { /** @protected */ get length_() { return 1; } }\n",
CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict6() {
testError(
"/** @constructor */ function Foo() {}\n"
+ "Foo.prototype = { /** @protected */ set length_(x) { } }\n",
CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict7() {
testError("/** @public */ var Foo_;", CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict8() {
testError("/** @package */ var Foo_;", CONVENTION_MISMATCH);
}
public void testDeclarationAndConventionConflict9() {
testError("/** @protected */ var Foo_;", CONVENTION_MISMATCH);
}
public void testConstantProperty1a() {
testError(
"/** @constructor */ function A() {"
+ "/** @const */ this.bar = 3;}"
+ "/** @constructor */ function B() {"
+ "/** @const */ this.bar = 3;this.bar += 4;}",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty1b() {
testError(
"/** @constructor */ function A() {"
+ "this.BAR = 3;}"
+ "/** @constructor */ function B() {"
+ "this.BAR = 3;this.BAR += 4;}",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty2a() {
testError(
"/** @constructor */ function Foo() {}"
+ "/** @const */ Foo.prototype.prop = 2;"
+ "var foo = new Foo();"
+ "foo.prop = 3;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty2b() {
testError(
"/** @constructor */ function Foo() {}"
+ "Foo.prototype.PROP = 2;"
+ "var foo = new Foo();"
+ "foo.PROP = 3;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty3a() {
testSame("/** @constructor */ function Foo() {}\n"
+ "/** @type {number} */ Foo.prototype.PROP = 2;\n"
+ "/** @suppress {duplicate|const} */ Foo.prototype.PROP = 3;\n");
}
public void testConstantProperty3b() {
testSame("/** @constructor */ function Foo() {}\n"
+ "/** @const */ Foo.prototype.prop = 2;\n"
+ "/** @suppress {const} */ Foo.prototype.prop = 3;\n");
}
public void testNamespaceConstantProperty1() {
testError(
""
+ "/** @const */ var o = {};\n"
+ "/** @const */ o.x = 1;"
+ "o.x = 2;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testNamespaceConstantProperty2() {
// NTI requires an @const annotation on namespaces, as in testNamespaceConstantProperty1.
// This is the only difference between the two tests.
this.mode = TypeInferenceMode.OTI_ONLY;
testError(
"var o = {};\n"
+ "/** @const */ o.x = 1;\n"
+ "o.x = 2;\n",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testNamespaceConstantProperty2a() {
testSame("/** @const */ var o = {};\n"
+ "/** @const */ o.x = 1;\n"
+ "/** @const */ var o2 = {};\n"
+ "/** @const */ o2.x = 1;\n");
}
public void testNamespaceConstantProperty3() {
testError(
"/** @const */ var o = {};\n"
+ "/** @const */ o.x = 1;"
+ "o.x = 2;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty3a1() {
testSame("var o = { /** @const */ x: 1 };"
+ "o.x = 2;");
}
public void testConstantProperty3a2() {
// The old type checker should report this but it doesn't.
// NTI reports CONST_PROPERTY_REASSIGNED.
testSame("/** @const */ var o = { /** @const */ x: 1 };"
+ "o.x = 2;");
}
public void testConstantProperty3b1() {
// We should report this but we don't.
testSame("var o = { XYZ: 1 };"
+ "o.XYZ = 2;");
}
public void testConstantProperty3b2() {
// NTI reports NTI_REDECLARED_PROPERTY
this.mode = TypeInferenceMode.OTI_ONLY;
// The old type checker should report this but it doesn't.
testSame("/** @const */ var o = { XYZ: 1 };"
+ "o.XYZ = 2;");
}
public void testConstantProperty4() {
testError(
"/** @constructor */ function cat(name) {}"
+ "/** @const */ cat.test = 1;"
+ "cat.test *= 2;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty4b() {
testError(
"/** @constructor */ function cat(name) {}"
+ "cat.TEST = 1;"
+ "cat.TEST *= 2;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty5() {
testError(
"/** @constructor */ function Foo() { this.prop = 1;}"
+ "/** @const */ Foo.prototype.prop;"
+ "Foo.prototype.prop = 2",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty6() {
testError(
"/** @constructor */ function Foo() { this.prop = 1;}"
+ "/** @const */ Foo.prototype.prop = 2;",
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("/** @const */ 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 testConstantProperty10a() {
testSame("/** @constructor */ function Foo() { this.prop = 1;}"
+ "/** @const */ Foo.prototype.prop;");
}
public void testConstantProperty10b() {
// NTI reports NTI_REDECLARED_PROPERTY
this.mode = TypeInferenceMode.OTI_ONLY;
testSame("/** @constructor */ function Foo() { this.PROP = 1;}"
+ "Foo.prototype.PROP;");
}
public void testConstantProperty11() {
testError(
"/** @constructor */ function Foo() {}"
+ "/** @const */ Foo.prototype.bar;"
+ "/**\n"
+ " * @constructor\n"
+ " * @extends {Foo}\n"
+ " */ function SubFoo() { this.bar = 5; this.bar = 6; }",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty12() {
// NTI deliberately disallows this pattern (separate declaration and initialization
// of const properties). (b/30205953)
this.mode = TypeInferenceMode.OTI_ONLY;
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() {
testError(
"/** @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; }",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty14() {
testError(
"/** @constructor */ function Foo() {"
+ "/** @const */ this.bar = 3; delete this.bar; }",
CONST_PROPERTY_DELETED);
}
public void testConstantPropertyInExterns() {
String externs =
DEFAULT_EXTERNS
+ "/** @constructor */ function Foo() {};\n"
+ "/** @const */ Foo.prototype.PROP;";
String js = "var f = new Foo(); f.PROP = 1; f.PROP = 2;";
test(externs, js, (String) null, CONST_PROPERTY_REASSIGNED_VALUE, null);
}
public void testConstantProperty15() {
testSame("/** @constructor */ function Foo() {};\n"
+ "Foo.CONST = 100;\n"
+ "/** @type {Foo} */\n"
+ "var foo = new Foo();\n"
+ "/** @type {number} */\n"
+ "foo.CONST = Foo.CONST;");
}
public void testConstantProperty15a() {
testError(
"/** @constructor */ function Foo() { this.CONST = 100; };\n"
+ "/** @type {Foo} */\n"
+ "var foo = new Foo();\n"
+ "/** @type {number} */\n"
+ "foo.CONST = 0;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty15b() {
testError(
"/** @constructor */ function Foo() {};\n"
+ "Foo.prototype.CONST = 100;\n"
+ "/** @type {Foo} */\n"
+ "var foo = new Foo();\n"
+ "/** @type {number} */\n"
+ "foo.CONST = 0;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty15c() {
testError(
""
+ "/** @constructor */ function Bar() {this.CONST = 100;};\n"
+ "/** @constructor \n @extends {Bar} */ function Foo() {};\n"
+ "/** @type {Foo} */\n"
+ "var foo = new Foo();\n"
+ "/** @type {number} */\n"
+ "foo.CONST = 0;",
CONST_PROPERTY_REASSIGNED_VALUE);
}
public void testConstantProperty16() {
testSame("/** @constructor */ function Foo() {};\n"
+ "Foo.CONST = 100;\n"
+ "/** @constructor */ function Bar() {};\n"
+ "Bar.CONST = 100;\n");
}
public void testConstantProperty17() {
testSame("function Foo() {};\n"
+ "Foo.CONST = 100;\n"
+ "function Bar() {};\n"
+ "Bar.CONST = 100;\n");
}
public void testConstantProperty18() {
testSame("/** @param {string} a */\n"
+ "function Foo(a) {};\n"
+ "Foo.CONST = 100;\n"
+ "/** @param {string} a */\n"
+ "function Bar(a) {};\n"
+ "Bar.CONST = 100;\n");
}
public void testConstantProperty19() {
testSame("/** @param {string} a */\n"
+ "function Foo(a) {};\n"
+ "Foo.CONST = 100;\n"
+ "/** @param {number} a */\n"
+ "function Bar(a) {};\n"
+ "Bar.CONST = 100;\n");
}
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() {
testError(
LINE_JOINER.join(
"/**",
" * @constructor",
" * @final",
" */ var Foo = function() {};",
"/**",
" * @constructor",
" * @extends {Foo}*",
" */ var Bar = function() {};"),
EXTEND_FINAL_CLASS);
testError(
LINE_JOINER.join(
"/**",
" * @constructor",
" * @final",
" */ function Foo() {};",
"/**",
" * @constructor",
" * @extends {Foo}*",
" */ function Bar() {};"),
EXTEND_FINAL_CLASS);
testSame(
LINE_JOINER.join(
"/**",
" * @constructor",
" * @const",
" */ var Foo = function() {};",
"/**",
" * @constructor",
" * @extends {Foo}",
" */ var Bar = function() {};"));
}
public void testCircularPrototypeLink() {
// NOTE: this does yield a useful warning, except we don't check for it in this test:
// WARNING - Cycle detected in inheritance chain of type Foo
// This warning already has a test: TypeCheckTest::testPrototypeLoop.
testError(
LINE_JOINER.join(
"/** @constructor @extends {Foo} */ function Foo() {}",
"/** @const */ Foo.prop = 1;",
"Foo.prop = 2;"),
CONST_PROPERTY_REASSIGNED_VALUE);
// In OTI this next test causes a stack overflow.
this.mode = TypeInferenceMode.NTI_ONLY;
testError(
LINE_JOINER.join(
"/** @constructor */ function Foo() {}",
"/** @type {!Foo} */ Foo.prototype = new Foo();",
"/** @const */ Foo.prop = 1;",
"Foo.prop = 2;"),
CONST_PROPERTY_REASSIGNED_VALUE);
}
}