/*
* Copyright 2006 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.common.truth.Truth.assertThat;
import static com.google.javascript.jscomp.TypeCheck.INSTANTIATE_ABSTRACT_CLASS;
import static com.google.javascript.jscomp.parsing.JsDocInfoParser.BAD_TYPE_WIKI_LINK;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.type.ClosureReverseAbstractInterpreter;
import com.google.javascript.jscomp.type.SemanticReverseAbstractInterpreter;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Tests {@link TypeCheck}.
*
*/
public final class TypeCheckTest extends CompilerTypeTestCase {
private static final String SUGGESTION_CLASS =
"/** @constructor\n */\n"
+ "function Suggest() {}\n"
+ "Suggest.prototype.a = 1;\n"
+ "Suggest.prototype.veryPossible = 1;\n"
+ "Suggest.prototype.veryPossible2 = 1;\n";
private static final String ILLEGAL_PROPERTY_CREATION_MESSAGE = "Cannot add a property"
+ " to a struct instance after it is constructed. (If you already declared the property,"
+ " make sure to give it a type.)";
@Override
public void setUp() throws Exception {
super.setUp();
// Enable missing override checks that are disabled by default.
compiler.getOptions().setWarningLevel(DiagnosticGroups.MISSING_OVERRIDE, CheckLevel.WARNING);
}
public void testInitialTypingScope() throws Exception {
TypedScope s = new TypedScopeCreator(compiler,
CodingConventions.getDefault()).createInitialScope(
new Node(Token.ROOT));
assertTypeEquals(ARRAY_FUNCTION_TYPE, s.getVar("Array").getType());
assertTypeEquals(BOOLEAN_OBJECT_FUNCTION_TYPE,
s.getVar("Boolean").getType());
assertTypeEquals(DATE_FUNCTION_TYPE, s.getVar("Date").getType());
assertTypeEquals(ERROR_FUNCTION_TYPE, s.getVar("Error").getType());
assertTypeEquals(EVAL_ERROR_FUNCTION_TYPE,
s.getVar("EvalError").getType());
assertTypeEquals(NUMBER_OBJECT_FUNCTION_TYPE,
s.getVar("Number").getType());
assertTypeEquals(OBJECT_FUNCTION_TYPE, s.getVar("Object").getType());
assertTypeEquals(RANGE_ERROR_FUNCTION_TYPE,
s.getVar("RangeError").getType());
assertTypeEquals(REFERENCE_ERROR_FUNCTION_TYPE,
s.getVar("ReferenceError").getType());
assertTypeEquals(REGEXP_FUNCTION_TYPE, s.getVar("RegExp").getType());
assertTypeEquals(STRING_OBJECT_FUNCTION_TYPE,
s.getVar("String").getType());
assertTypeEquals(SYNTAX_ERROR_FUNCTION_TYPE,
s.getVar("SyntaxError").getType());
assertTypeEquals(TYPE_ERROR_FUNCTION_TYPE,
s.getVar("TypeError").getType());
assertTypeEquals(URI_ERROR_FUNCTION_TYPE,
s.getVar("URIError").getType());
}
public void testPrivateType() throws Exception {
testTypes(
"/** @private {number} */ var x = false;",
"initializing variable\n"
+ "found : boolean\n"
+ "required: number");
}
public void testTypeCheck1() throws Exception {
testTypes("/**@return {void}*/function foo(){ if (foo()) return; }");
}
public void testTypeCheck2() throws Exception {
testTypes(
"/**@return {void}*/function foo(){ var x=foo(); x--; }",
"increment/decrement\n"
+ "found : undefined\n"
+ "required: number");
}
public void testTypeCheck4() throws Exception {
testTypes("/**@return {void}*/function foo(){ !foo(); }");
}
public void testTypeCheck5() throws Exception {
testTypes(
"/**@return {void}*/function foo(){ var a = +foo(); }",
"sign operator\n"
+ "found : undefined\n"
+ "required: number");
}
public void testTypeCheck6() throws Exception {
testTypes(
"/**@return {void}*/function foo(){"
+ "/** @type {undefined|number} */var a;if (a == foo())return;}");
}
public void testTypeCheck8() throws Exception {
testTypes("/**@return {void}*/function foo(){do {} while (foo());}");
}
public void testTypeCheck9() throws Exception {
testTypes("/**@return {void}*/function foo(){while (foo());}");
}
public void testTypeCheck10() throws Exception {
testTypes("/**@return {void}*/function foo(){for (;foo(););}");
}
public void testTypeCheck11() throws Exception {
testTypes("/**@type {!Number} */var a;"
+ "/**@type {!String} */var b;"
+ "a = b;",
"assignment\n"
+ "found : String\n"
+ "required: Number");
}
public void testTypeCheck12() throws Exception {
testTypes("/**@return {!Object}*/function foo(){var a = 3^foo();}",
"bad right operand to bitwise operator\n" +
"found : Object\n" +
"required: (boolean|null|number|string|undefined)");
}
public void testTypeCheck13() throws Exception {
testTypes("/**@type {!Number|!String}*/var i; i=/xx/;",
"assignment\n" +
"found : RegExp\n" +
"required: (Number|String)");
}
public void testTypeCheck14() throws Exception {
testTypes("/**@param {?} opt_a*/function foo(opt_a){}");
}
public void testTypeCheck15() throws Exception {
testTypes("/**@type {Number|null} */var x;x=null;x=10;",
"assignment\n" +
"found : number\n" +
"required: (Number|null)");
}
public void testTypeCheck16() throws Exception {
testTypes("/**@type {Number|null} */var x='';",
"initializing variable\n" +
"found : string\n" +
"required: (Number|null)");
}
public void testTypeCheck17() throws Exception {
testTypes("/**@return {Number}\n@param {Number} opt_foo */\n" +
"function a(opt_foo){\nreturn /**@type {Number}*/(opt_foo);\n}");
}
public void testTypeCheck18() throws Exception {
testTypes("/**@return {RegExp}\n*/\n function a(){return new RegExp();}");
}
public void testTypeCheck19() throws Exception {
testTypes("/**@return {Array}\n*/\n function a(){return new Array();}");
}
public void testTypeCheck20() throws Exception {
testTypes("/**@return {Date}\n*/\n function a(){return new Date();}");
}
public void testTypeCheckBasicDowncast() throws Exception {
testTypes("/** @constructor */function foo() {}\n" +
"/** @type {Object} */ var bar = new foo();\n");
}
public void testTypeCheckNoDowncastToNumber() throws Exception {
testTypes("/** @constructor */function foo() {}\n" +
"/** @type {!Number} */ var bar = new foo();\n",
"initializing variable\n" +
"found : foo\n" +
"required: Number");
}
public void testTypeCheck21() throws Exception {
testTypes("/** @type {Array<String>} */var foo;");
}
public void testTypeCheck22() throws Exception {
testTypes("/** @param {Element|Object} p */\nfunction foo(p){}\n" +
"/** @constructor */function Element(){}\n" +
"/** @type {Element|Object} */var v;\n" +
"foo(v);\n");
}
public void testTypeCheck23() throws Exception {
testTypes("/** @type {(Object|Null)} */var foo; foo = null;");
}
public void testTypeCheck24() throws Exception {
testTypes("/** @constructor */function MyType(){}\n" +
"/** @type {(MyType|Null)} */var foo; foo = null;");
}
public void testTypeCheck25() throws Exception {
testTypes("function foo(/** {a: number} */ obj) {};"
+ "foo({b: 'abc'});",
"actual parameter 1 of foo does not match formal parameter\n" +
"found : {a: (number|undefined), b: string}\n" +
"required: {a: number}");
}
public void testTypeCheck26() throws Exception {
testTypes("function foo(/** {a: number} */ obj) {};"
+ "foo({a: 'abc'});",
"actual parameter 1 of foo does not match formal parameter\n"
+ "found : {a: (number|string)}\n"
+ "required: {a: number}");
}
public void testTypeCheck27() throws Exception {
testTypes("function foo(/** {a: number} */ obj) {};"
+ "foo({a: 123});");
}
public void testTypeCheck28() throws Exception {
testTypes("function foo(/** ? */ obj) {};"
+ "foo({a: 123});");
}
public void testTypeCheckInlineReturns() throws Exception {
testTypes(
"function /** string */ foo(x) { return x; }" +
"var /** number */ a = foo('abc');",
"initializing variable\n"
+ "found : string\n"
+ "required: number");
}
public void testTypeCheckDefaultExterns() throws Exception {
testTypes("/** @param {string} x */ function f(x) {}" +
"f([].length);" ,
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testTypeCheckCustomExterns() throws Exception {
testTypes(
DEFAULT_EXTERNS + "/** @type {boolean} */ Array.prototype.oogabooga;",
"/** @param {string} x */ function f(x) {}" +
"f([].oogabooga);" ,
"actual parameter 1 of f does not match formal parameter\n" +
"found : boolean\n" +
"required: string", false);
}
public void testTypeCheckCustomExterns2() throws Exception {
testTypes(
DEFAULT_EXTERNS + "/** @enum {string} */ var Enum = {FOO: 1, BAR: 1};",
"/** @param {Enum} x */ function f(x) {} f(Enum.FOO); f(true);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : boolean\n" +
"required: Enum<string>",
false);
}
public void testDontCrashOnRecursiveTemplateReference() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/**",
" * @constructor @struct",
" * @implements {Iterable<!Array<KEY|VAL>>}",
" * @template KEY, VAL",
" */",
"function Map(opt_iterable) {}"),
LINE_JOINER.join(
"/** @constructor @implements {Iterable<VALUE>} @template VALUE */",
"function Foo() {",
" /** @type {!Map<VALUE, VALUE>} */ this.map = new Map;",
"}"));
}
public void testTemplatizedArray1() throws Exception {
testTypes("/** @param {!Array<number>} a\n" +
"* @return {string}\n" +
"*/ var f = function(a) { return a[0]; };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testTemplatizedArray2() throws Exception {
testTypes("/** @param {!Array<!Array<number>>} a\n" +
"* @return {number}\n" +
"*/ var f = function(a) { return a[0]; };",
"inconsistent return type\n" +
"found : Array<number>\n" +
"required: number");
}
public void testTemplatizedArray3() throws Exception {
testTypes("/** @param {!Array<number>} a\n" +
"* @return {number}\n" +
"*/ var f = function(a) { a[1] = 0; return a[0]; };");
}
public void testTemplatizedArray4() throws Exception {
testTypes("/** @param {!Array<number>} a\n" +
"*/ var f = function(a) { a[0] = 'a'; };",
"assignment\n" +
"found : string\n" +
"required: number");
}
public void testTemplatizedArray5() throws Exception {
testTypes("/** @param {!Array<*>} a\n" +
"*/ var f = function(a) { a[0] = 'a'; };");
}
public void testTemplatizedArray6() throws Exception {
testTypes("/** @param {!Array<*>} a\n" +
"* @return {string}\n" +
"*/ var f = function(a) { return a[0]; };",
"inconsistent return type\n" +
"found : *\n" +
"required: string");
}
public void testTemplatizedArray7() throws Exception {
testTypes("/** @param {?Array<number>} a\n" +
"* @return {string}\n" +
"*/ var f = function(a) { return a[0]; };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testTemplatizedObject1() throws Exception {
testTypes("/** @param {!Object<number>} a\n" +
"* @return {string}\n" +
"*/ var f = function(a) { return a[0]; };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testTemplatizedObjectOnWindow() throws Exception {
testTypesWithExtraExterns(
"/** @constructor */ window.Object = Object;",
LINE_JOINER.join(
"/** @param {!window.Object<number>} a",
" * @return {string}",
" */ var f = function(a) { return a[0]; };"),
LINE_JOINER.join(
"inconsistent return type",
"found : number",
"required: string"));
}
public void testTemplatizedObject2() throws Exception {
testTypes("/** @param {!Object<string,number>} a\n" +
"* @return {string}\n" +
"*/ var f = function(a) { return a['x']; };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testTemplatizedObject3() throws Exception {
testTypes("/** @param {!Object<number,string>} a\n" +
"* @return {string}\n" +
"*/ var f = function(a) { return a['x']; };",
"restricted index type\n" +
"found : string\n" +
"required: number");
}
public void testTemplatizedObject4() throws Exception {
testTypes("/** @enum {string} */ var E = {A: 'a', B: 'b'};\n" +
"/** @param {!Object<E,string>} a\n" +
"* @return {string}\n" +
"*/ var f = function(a) { return a['x']; };",
"restricted index type\n" +
"found : string\n" +
"required: E<string>");
}
public void testTemplatizedObject5() throws Exception {
testTypes("/** @constructor */ function F() {" +
" /** @type {Object<number, string>} */ this.numbers = {};" +
"}" +
"(new F()).numbers['ten'] = '10';",
"restricted index type\n" +
"found : string\n" +
"required: number");
}
public void testUnionOfFunctionAndType() throws Exception {
testTypes("/** @type {null|(function(Number):void)} */ var a;" +
"/** @type {(function(Number):void)|null} */ var b = null; a = b;");
}
public void testOptionalParameterComparedToUndefined() throws Exception {
testTypes("/** @param {Number} opt_a */function foo(opt_a)" +
"{if (opt_a==undefined) var b = 3;}");
}
public void testOptionalAllType() throws Exception {
testTypes("/** @param {*} opt_x */function f(opt_x) { return opt_x }\n" +
"/** @type {*} */var y;\n" +
"f(y);");
}
public void testOptionalUnknownNamedType() throws Exception {
testTypes("/** @param {!T} opt_x\n@return {undefined} */\n" +
"function f(opt_x) { return opt_x; }\n" +
"/** @constructor */var T = function() {};",
"inconsistent return type\n" +
"found : (T|undefined)\n" +
"required: undefined");
}
public void testOptionalArgFunctionParam() throws Exception {
testTypes("/** @param {function(number=)} a */" +
"function f(a) {a()};");
}
public void testOptionalArgFunctionParam2() throws Exception {
testTypes("/** @param {function(number=)} a */" +
"function f(a) {a(3)};");
}
public void testOptionalArgFunctionParam3() throws Exception {
testTypes("/** @param {function(number=)} a */" +
"function f(a) {a(undefined)};");
}
public void testOptionalArgFunctionParam4() throws Exception {
String expectedWarning = "Function a: called with 2 argument(s). " +
"Function requires at least 0 argument(s) and no more than 1 " +
"argument(s).";
testTypes("/** @param {function(number=)} a */function f(a) {a(3,4)};",
expectedWarning, false);
}
public void testOptionalArgFunctionParamError() throws Exception {
String expectedWarning =
"Bad type annotation. variable length argument must be last." + BAD_TYPE_WIKI_LINK;
testTypes("/** @param {function(...number, number=)} a */" +
"function f(a) {};", expectedWarning, false);
}
public void testOptionalNullableArgFunctionParam() throws Exception {
testTypes("/** @param {function(?number=)} a */" +
"function f(a) {a()};");
}
public void testOptionalNullableArgFunctionParam2() throws Exception {
testTypes("/** @param {function(?number=)} a */" +
"function f(a) {a(null)};");
}
public void testOptionalNullableArgFunctionParam3() throws Exception {
testTypes("/** @param {function(?number=)} a */" +
"function f(a) {a(3)};");
}
public void testOptionalArgFunctionReturn() throws Exception {
testTypes("/** @return {function(number=)} */" +
"function f() { return function(opt_x) { }; };" +
"f()()");
}
public void testOptionalArgFunctionReturn2() throws Exception {
testTypes("/** @return {function(Object=)} */" +
"function f() { return function(opt_x) { }; };" +
"f()({})");
}
public void testBooleanType() throws Exception {
testTypes("/**@type {boolean} */var x = 1 < 2;");
}
public void testBooleanReduction1() throws Exception {
testTypes("/**@type {string} */var x; x = null || \"a\";");
}
public void testBooleanReduction2() throws Exception {
// It's important for the type system to recognize that in no case
// can the boolean expression evaluate to a boolean value.
testTypes("/** @param {string} s\n @return {string} */" +
"(function(s) { return ((s == 'a') && s) || 'b'; })");
}
public void testBooleanReduction3() throws Exception {
testTypes("/** @param {string} s\n @return {string?} */" +
"(function(s) { return s && null && 3; })");
}
public void testBooleanReduction4() throws Exception {
testTypes("/** @param {Object} x\n @return {Object} */" +
"(function(x) { return null || x || null ; })");
}
public void testBooleanReduction5() throws Exception {
testTypes("/**\n" +
"* @param {Array|string} x\n" +
"* @return {string?}\n" +
"*/\n" +
"var f = function(x) {\n" +
"if (!x || typeof x == 'string') {\n" +
"return x;\n" +
"}\n" +
"return null;\n" +
"};");
}
public void testBooleanReduction6() throws Exception {
testTypes("/**\n" +
"* @param {Array|string|null} x\n" +
"* @return {string?}\n" +
"*/\n" +
"var f = function(x) {\n" +
"if (!(x && typeof x != 'string')) {\n" +
"return x;\n" +
"}\n" +
"return null;\n" +
"};");
}
public void testBooleanReduction7() throws Exception {
testTypes("/** @constructor */var T = function() {};\n" +
"/**\n" +
"* @param {Array|T} x\n" +
"* @return {null}\n" +
"*/\n" +
"var f = function(x) {\n" +
"if (!x) {\n" +
"return x;\n" +
"}\n" +
"return null;\n" +
"};");
}
public void testNullAnd() throws Exception {
testTypes("/** @type {null} */var x;\n" +
"/** @type {number} */var r = x && x;",
"initializing variable\n" +
"found : null\n" +
"required: number");
}
public void testNullOr() throws Exception {
testTypes("/** @type {null} */var x;\n" +
"/** @type {number} */var r = x || x;",
"initializing variable\n" +
"found : null\n" +
"required: number");
}
public void testBooleanPreservation1() throws Exception {
testTypes("/**@type {string} */var x = \"a\";" +
"x = ((x == \"a\") && x) || x == \"b\";",
"assignment\n" +
"found : (boolean|string)\n" +
"required: string");
}
public void testBooleanPreservation2() throws Exception {
testTypes("/**@type {string} */var x = \"a\"; x = (x == \"a\") || x;",
"assignment\n" +
"found : (boolean|string)\n" +
"required: string");
}
public void testBooleanPreservation3() throws Exception {
testTypes("/** @param {Function?} x\n @return {boolean?} */" +
"function f(x) { return x && x == \"a\"; }",
"condition always evaluates to false\n" +
"left : Function\n" +
"right: string");
}
public void testBooleanPreservation4() throws Exception {
testTypes("/** @param {Function?|boolean} x\n @return {boolean} */" +
"function f(x) { return x && x == \"a\"; }",
"inconsistent return type\n" +
"found : (boolean|null)\n" +
"required: boolean");
}
public void testTypeOfReduction1() throws Exception {
testTypes("/** @param {string|number} x\n @return {string} */ " +
"function f(x) { return typeof x == 'number' ? String(x) : x; }");
}
public void testTypeOfReduction2() throws Exception {
testTypes("/** @param {string|number} x\n @return {string} */ " +
"function f(x) { return typeof x != 'string' ? String(x) : x; }");
}
public void testTypeOfReduction3() throws Exception {
testTypes("/** @param {number|null} x\n @return {number} */ " +
"function f(x) { return typeof x == 'object' ? 1 : x; }");
}
public void testTypeOfReduction4() throws Exception {
testTypes("/** @param {Object|undefined} x\n @return {Object} */ " +
"function f(x) { return typeof x == 'undefined' ? {} : x; }");
}
public void testTypeOfReduction5() throws Exception {
testTypes("/** @enum {string} */ var E = {A: 'a', B: 'b'};\n" +
"/** @param {!E|number} x\n @return {string} */ " +
"function f(x) { return typeof x != 'number' ? x : 'a'; }");
}
public void testTypeOfReduction6() throws Exception {
testTypes("/** @param {number|string} x\n@return {string} */\n" +
"function f(x) {\n" +
"return typeof x == 'string' && x.length == 3 ? x : 'a';\n" +
"}");
}
public void testTypeOfReduction7() throws Exception {
testTypes("/** @return {string} */var f = function(x) { " +
"return typeof x == 'number' ? x : 'a'; }",
"inconsistent return type\n" +
"found : (number|string)\n" +
"required: string");
}
public void testTypeOfReduction8() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @param {number|string} x\n@return {string} */\n" +
"function f(x) {\n" +
"return goog.isString(x) && x.length == 3 ? x : 'a';\n" +
"}", null);
}
public void testTypeOfReduction9() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @param {!Array|string} x\n@return {string} */\n" +
"function f(x) {\n" +
"return goog.isArray(x) ? 'a' : x;\n" +
"}", null);
}
public void testTypeOfReduction10() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @param {Array|string} x\n@return {Array} */\n" +
"function f(x) {\n" +
"return goog.isArray(x) ? x : [];\n" +
"}", null);
}
public void testTypeOfReduction11() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @param {Array|string} x\n@return {Array} */\n" +
"function f(x) {\n" +
"return goog.isObject(x) ? x : [];\n" +
"}", null);
}
public void testTypeOfReduction12() throws Exception {
testTypes("/** @enum {string} */ var E = {A: 'a', B: 'b'};\n" +
"/** @param {E|Array} x\n @return {Array} */ " +
"function f(x) { return typeof x == 'object' ? x : []; }");
}
public void testTypeOfReduction13() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @enum {string} */ var E = {A: 'a', B: 'b'};\n" +
"/** @param {E|Array} x\n@return {Array} */ " +
"function f(x) { return goog.isObject(x) ? x : []; }", null);
}
public void testTypeOfReduction14() throws Exception {
// Don't do type inference on GETELEMs.
testClosureTypes(
CLOSURE_DEFS +
"function f(x) { " +
" return goog.isString(arguments[0]) ? arguments[0] : 0;" +
"}", null);
}
public void testTypeOfReduction15() throws Exception {
// Don't do type inference on GETELEMs.
testClosureTypes(
CLOSURE_DEFS +
"function f(x) { " +
" return typeof arguments[0] == 'string' ? arguments[0] : 0;" +
"}", null);
}
public void testTypeOfReduction16() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @interface */ function I() {}\n" +
"/**\n" +
" * @param {*} x\n" +
" * @return {I}\n" +
" */\n" +
"function f(x) { " +
" if(goog.isObject(x)) {" +
" return /** @type {I} */(x);" +
" }" +
" return null;" +
"}", null);
}
public void testQualifiedNameReduction1() throws Exception {
testTypes("var x = {}; /** @type {string?} */ x.a = 'a';\n" +
"/** @return {string} */ var f = function() {\n" +
"return x.a ? x.a : 'a'; }");
}
public void testQualifiedNameReduction2() throws Exception {
testTypes("/** @param {string?} a\n@constructor */ var T = " +
"function(a) {this.a = a};\n" +
"/** @return {string} */ T.prototype.f = function() {\n" +
"return this.a ? this.a : 'a'; }");
}
public void testQualifiedNameReduction3() throws Exception {
testTypes("/** @param {string|Array} a\n@constructor */ var T = " +
"function(a) {this.a = a};\n" +
"/** @return {string} */ T.prototype.f = function() {\n" +
"return typeof this.a == 'string' ? this.a : 'a'; }");
}
public void testQualifiedNameReduction4() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @param {string|Array} a\n@constructor */ var T = " +
"function(a) {this.a = a};\n" +
"/** @return {string} */ T.prototype.f = function() {\n" +
"return goog.isString(this.a) ? this.a : 'a'; }", null);
}
public void testQualifiedNameReduction5a() throws Exception {
testTypes("var x = {/** @type {string} */ a:'b' };\n" +
"/** @return {string} */ var f = function() {\n" +
"return x.a; }");
}
public void testQualifiedNameReduction5b() throws Exception {
testTypes(
"var x = {/** @type {number} */ a:12 };\n" +
"/** @return {string} */\n" +
"var f = function() {\n" +
" return x.a;\n" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testQualifiedNameReduction5c() throws Exception {
testTypes(
"/** @return {string} */ var f = function() {\n" +
"var x = {/** @type {number} */ a:0 };\n" +
"return (x.a) ? (x.a) : 'a'; }",
"inconsistent return type\n" +
"found : (number|string)\n" +
"required: string");
}
public void testQualifiedNameReduction6() throws Exception {
testTypes(
"/** @return {string} */ var f = function() {\n" +
"var x = {/** @return {string?} */ get a() {return 'a'}};\n" +
"return x.a ? x.a : 'a'; }");
}
public void testQualifiedNameReduction7() throws Exception {
testTypes(
"/** @return {string} */ var f = function() {\n" +
"var x = {/** @return {number} */ get a() {return 12}};\n" +
"return x.a; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testQualifiedNameReduction7a() throws Exception {
// It would be nice to find a way to make this an error.
testTypes(
"/** @return {string} */ var f = function() {\n" +
"var x = {get a() {return 12}};\n" +
"return x.a; }");
}
public void testQualifiedNameReduction8() throws Exception {
testTypes(
"/** @return {string} */ var f = function() {\n" +
"var x = {get a() {return 'a'}};\n" +
"return x.a ? x.a : 'a'; }");
}
public void testQualifiedNameReduction9() throws Exception {
testTypes(
"/** @return {string} */ var f = function() {\n" +
"var x = { /** @param {string} b */ set a(b) {}};\n" +
"return x.a ? x.a : 'a'; }");
}
public void testQualifiedNameReduction10() throws Exception {
// TODO(johnlenz): separate setter property types from getter property
// types.
testTypes(
"/** @return {string} */ var f = function() {\n" +
"var x = { /** @param {number} b */ set a(b) {}};\n" +
"return x.a ? x.a : 'a'; }",
"inconsistent return type\n" +
"found : (number|string)\n" +
"required: string");
}
public void testUnknownsDontOverrideDeclaredTypesInLocalScope1() throws Exception {
testTypes(
"/** @constructor */ var C = function() {\n"
+ " /** @type {string} */ this.a = 'str'};\n"
+ "/** @param {?} a\n @return {number} */\n"
+ "C.prototype.f = function(a) {\n"
+ " this.a = a;\n"
+ " return this.a;\n"
+ "}\n",
"inconsistent return type\n"
+ "found : string\n"
+ "required: number");
}
public void testUnknownsDontOverrideDeclaredTypesInLocalScope2() throws Exception {
testTypes(
"/** @constructor */ var C = function() {\n"
+ " /** @type {string} */ this.a = 'str';\n"
+ "};\n"
+ "/** @type {C} */ var x = new C();"
+ "/** @param {?} a\n @return {number} */\n"
+ "C.prototype.f = function(a) {\n"
+ " x.a = a;\n"
+ " return x.a;\n"
+ "}\n",
"inconsistent return type\n"
+ "found : string\n"
+ "required: number");
}
public void testObjLitDef1a() throws Exception {
testTypes(
"var x = {/** @type {number} */ a:12 };\n" +
"x.a = 'a';",
"assignment to property a of x\n" +
"found : string\n" +
"required: number");
}
public void testObjLitDef1b() throws Exception {
testTypes(
"function f(){" +
"var x = {/** @type {number} */ a:12 };\n" +
"x.a = 'a';" +
"};\n" +
"f();",
"assignment to property a of x\n" +
"found : string\n" +
"required: number");
}
public void testObjLitDef2a() throws Exception {
testTypes(
"var x = {/** @param {number} b */ set a(b){} };\n" +
"x.a = 'a';",
"assignment to property a of x\n" +
"found : string\n" +
"required: number");
}
public void testObjLitDef2b() throws Exception {
testTypes(
"function f(){" +
"var x = {/** @param {number} b */ set a(b){} };\n" +
"x.a = 'a';" +
"};\n" +
"f();",
"assignment to property a of x\n" +
"found : string\n" +
"required: number");
}
public void testObjLitDef3a() throws Exception {
testTypes(
"/** @type {string} */ var y;\n" +
"var x = {/** @return {number} */ get a(){} };\n" +
"y = x.a;",
"assignment\n" +
"found : number\n" +
"required: string");
}
public void testObjLitDef3b() throws Exception {
testTypes(
"/** @type {string} */ var y;\n" +
"function f(){" +
"var x = {/** @return {number} */ get a(){} };\n" +
"y = x.a;" +
"};\n" +
"f();",
"assignment\n" +
"found : number\n" +
"required: string");
}
public void testObjLitDef4() throws Exception {
testTypes(
"var x = {" +
"/** @return {number} */ a:12 };\n",
"assignment to property a of {a: function (): number}\n" +
"found : number\n" +
"required: function (): number");
}
public void testObjLitDef5() throws Exception {
testTypes(
"var x = {};\n" +
"/** @return {number} */ x.a = 12;\n",
"assignment to property a of x\n" +
"found : number\n" +
"required: function (): number");
}
public void testObjLitDef6() throws Exception {
testTypes("var lit = /** @struct */ { 'x': 1 };",
"Illegal key, the object literal is a struct");
}
public void testObjLitDef7() throws Exception {
testTypes("var lit = /** @dict */ { x: 1 };",
"Illegal key, the object literal is a dict");
}
public void testInstanceOfReduction1() throws Exception {
testTypes("/** @constructor */ var T = function() {};\n" +
"/** @param {T|string} x\n@return {T} */\n" +
"var f = function(x) {\n" +
"if (x instanceof T) { return x; } else { return new T(); }\n" +
"};");
}
public void testInstanceOfReduction2() throws Exception {
testTypes("/** @constructor */ var T = function() {};\n" +
"/** @param {!T|string} x\n@return {string} */\n" +
"var f = function(x) {\n" +
"if (x instanceof T) { return ''; } else { return x; }\n" +
"};");
}
public void testUndeclaredGlobalProperty1() throws Exception {
testTypes("/** @const */ var x = {}; x.y = null;" +
"function f(a) { x.y = a; }" +
"/** @param {string} a */ function g(a) { }" +
"function h() { g(x.y); }");
}
public void testUndeclaredGlobalProperty2() throws Exception {
testTypes("/** @const */ var x = {}; x.y = null;" +
"function f() { x.y = 3; }" +
"/** @param {string} a */ function g(a) { }" +
"function h() { g(x.y); }",
"actual parameter 1 of g does not match formal parameter\n" +
"found : (null|number)\n" +
"required: string");
}
public void testLocallyInferredGlobalProperty1() throws Exception {
// We used to have a bug where x.y.z leaked from f into h.
testTypes(
"/** @constructor */ function F() {}" +
"/** @type {number} */ F.prototype.z;" +
"/** @const */ var x = {}; /** @type {F} */ x.y;" +
"function f() { x.y.z = 'abc'; }" +
"/** @param {number} x */ function g(x) {}" +
"function h() { g(x.y.z); }",
"assignment to property z of F\n" +
"found : string\n" +
"required: number");
}
public void testPropertyInferredPropagation() throws Exception {
testTypes("/** @return {Object} */function f() { return {}; }\n" +
"function g() { var x = f(); if (x.p) x.a = 'a'; else x.a = 'b'; }\n" +
"function h() { var x = f(); x.a = false; }");
}
public void testPropertyInference1() throws Exception {
testTypes(
"/** @constructor */ function F() { this.x_ = true; }" +
"/** @return {string} */" +
"F.prototype.bar = function() { if (this.x_) return this.x_; };",
"inconsistent return type\n" +
"found : boolean\n" +
"required: string");
}
public void testPropertyInference2() throws Exception {
testTypes(
"/** @constructor */ function F() { this.x_ = true; }" +
"F.prototype.baz = function() { this.x_ = null; };" +
"/** @return {string} */" +
"F.prototype.bar = function() { if (this.x_) return this.x_; };",
"inconsistent return type\n" +
"found : boolean\n" +
"required: string");
}
public void testPropertyInference3() throws Exception {
testTypes(
"/** @constructor */ function F() { this.x_ = true; }" +
"F.prototype.baz = function() { this.x_ = 3; };" +
"/** @return {string} */" +
"F.prototype.bar = function() { if (this.x_) return this.x_; };",
"inconsistent return type\n" +
"found : (boolean|number)\n" +
"required: string");
}
public void testPropertyInference4() throws Exception {
testTypes(
"/** @constructor */ function F() { }" +
"F.prototype.x_ = 3;" +
"/** @return {string} */" +
"F.prototype.bar = function() { if (this.x_) return this.x_; };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testPropertyInference5() throws Exception {
testTypes(
"/** @constructor */ function F() { }" +
"F.prototype.baz = function() { this.x_ = 3; };" +
"/** @return {string} */" +
"F.prototype.bar = function() { if (this.x_) return this.x_; };");
}
public void testPropertyInference6() throws Exception {
testTypes(
"/** @constructor */ function F() { }" +
"(new F).x_ = 3;" +
"/** @return {string} */" +
"F.prototype.bar = function() { return this.x_; };");
}
public void testPropertyInference7() throws Exception {
testTypes(
"/** @constructor */ function F() { this.x_ = true; }" +
"(new F).x_ = 3;" +
"/** @return {string} */" +
"F.prototype.bar = function() { return this.x_; };",
"inconsistent return type\n" +
"found : boolean\n" +
"required: string");
}
public void testPropertyInference8() throws Exception {
testTypes(
"/** @constructor */ function F() { " +
" /** @type {string} */ this.x_ = 'x';" +
"}" +
"(new F).x_ = 3;" +
"/** @return {string} */" +
"F.prototype.bar = function() { return this.x_; };",
"assignment to property x_ of F\n" +
"found : number\n" +
"required: string");
}
public void testPropertyInference9() throws Exception {
testTypes(
"/** @constructor */ function A() {}" +
"/** @return {function(): ?} */ function f() { " +
" return function() {};" +
"}" +
"var g = f();" +
"/** @type {number} */ g.prototype.bar_ = null;",
"assignment\n" +
"found : null\n" +
"required: number");
}
public void testPropertyInference10() throws Exception {
// NOTE(nicksantos): There used to be a bug where a property
// on the prototype of one structural function would leak onto
// the prototype of other variables with the same structural
// function type.
testTypes(
"/** @constructor */ function A() {}" +
"/** @return {function(): ?} */ function f() { " +
" return function() {};" +
"}" +
"var g = f();" +
"/** @type {number} */ g.prototype.bar_ = 1;" +
"var h = f();" +
"/** @type {string} */ h.prototype.bar_ = 1;",
"assignment\n" +
"found : number\n" +
"required: string");
}
public void testNoPersistentTypeInferenceForObjectProperties() throws Exception {
testTypes("/** @param {Object} o\n@param {string} x */\n" +
"function s1(o,x) { o.x = x; }\n" +
"/** @param {Object} o\n@return {string} */\n" +
"function g1(o) { return typeof o.x == 'undefined' ? '' : o.x; }\n" +
"/** @param {Object} o\n@param {number} x */\n" +
"function s2(o,x) { o.x = x; }\n" +
"/** @param {Object} o\n@return {number} */\n" +
"function g2(o) { return typeof o.x == 'undefined' ? 0 : o.x; }");
}
public void testNoPersistentTypeInferenceForFunctionProperties() throws Exception {
testTypes("/** @param {Function} o\n@param {string} x */\n" +
"function s1(o,x) { o.x = x; }\n" +
"/** @param {Function} o\n@return {string} */\n" +
"function g1(o) { return typeof o.x == 'undefined' ? '' : o.x; }\n" +
"/** @param {Function} o\n@param {number} x */\n" +
"function s2(o,x) { o.x = x; }\n" +
"/** @param {Function} o\n@return {number} */\n" +
"function g2(o) { return typeof o.x == 'undefined' ? 0 : o.x; }");
}
public void testObjectPropertyTypeInferredInLocalScope1() throws Exception {
testTypes("/** @param {!Object} o\n@return {string} */\n" +
"function f(o) { o.x = 1; return o.x; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testObjectPropertyTypeInferredInLocalScope2() throws Exception {
testTypes("/**@param {!Object} o\n@param {number?} x\n@return {string}*/" +
"function f(o, x) { o.x = 'a';\nif (x) {o.x = x;}\nreturn o.x; }",
"inconsistent return type\n" +
"found : (number|string)\n" +
"required: string");
}
public void testObjectPropertyTypeInferredInLocalScope3() throws Exception {
testTypes("/**@param {!Object} o\n@param {number?} x\n@return {string}*/" +
"function f(o, x) { if (x) {o.x = x;} else {o.x = 'a';}\nreturn o.x; }",
"inconsistent return type\n" +
"found : (number|string)\n" +
"required: string");
}
public void testMismatchingOverridingInferredPropertyBeforeDeclaredProperty1() throws Exception {
testTypes("/** @constructor */var T = function() { this.x = ''; };\n" +
"/** @type {number} */ T.prototype.x = 0;",
"assignment to property x of T\n" +
"found : string\n" +
"required: number");
}
public void testMismatchingOverridingInferredPropertyBeforeDeclaredProperty2() throws Exception {
testTypes("/** @constructor */var T = function() { this.x = ''; };\n" +
"/** @type {number} */ T.prototype.x;",
"assignment to property x of T\n" +
"found : string\n" +
"required: number");
}
public void testMismatchingOverridingInferredPropertyBeforeDeclaredProperty3() throws Exception {
testTypes("/** @type {Object} */ var n = {};\n" +
"/** @constructor */ n.T = function() { this.x = ''; };\n" +
"/** @type {number} */ n.T.prototype.x = 0;",
"assignment to property x of n.T\n" +
"found : string\n" +
"required: number");
}
public void testMismatchingOverridingInferredPropertyBeforeDeclaredProperty4() throws Exception {
testTypes("var n = {};\n" +
"/** @constructor */ n.T = function() { this.x = ''; };\n" +
"/** @type {number} */ n.T.prototype.x = 0;",
"assignment to property x of n.T\n" +
"found : string\n" +
"required: number");
}
public void testAbstractMethodInAbstractClass() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var C = function() {};",
"/** @abstract */ C.prototype.foo = function() {};"));
}
public void testAbstractMethodInConcreteClass() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor */ var C = function() {};",
"/** @abstract */ C.prototype.foo = function() {};"),
"Abstract methods can only appear in abstract classes. Please declare the class as "
+ "@abstract");
}
public void testAbstractMethodInConcreteClassExtendingAbstractClass() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @abstract */ B.prototype.foo = function() {};"),
"Abstract methods can only appear in abstract classes. Please declare the class as "
+ "@abstract");
}
public void testConcreteMethodOverridingAbstractMethod() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() {};"));
}
public void testConcreteMethodInAbstractClass1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};"));
}
public void testConcreteMethodInAbstractClass2() throws Exception {
// Currently goog.abstractMethod are not considered abstract, so no warning is given when a
// concrete subclass fails to implement it.
testTypes(
LINE_JOINER.join(
CLOSURE_DEFS,
"/** @abstract @constructor */ var A = function() {};",
"A.prototype.foo = goog.abstractMethod;",
"/** @constructor @extends {A} */ var B = function() {};"));
}
public void testAbstractMethodInInterface() throws Exception {
// TODO(moz): There's no need to tag methods with @abstract in interfaces, maybe give a warning
// on this.
testTypes(
LINE_JOINER.join(
"/** @interface */ var I = function() {};",
"/** @abstract */ I.prototype.foo = function() {};"));
}
public void testAbstractMethodNotImplemented1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};"),
"property foo on abstract class A is not implemented by type B");
}
public void testAbstractMethodNotImplemented2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @abstract */ A.prototype.bar = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() {};"),
"property bar on abstract class A is not implemented by type B");
}
public void testAbstractMethodNotImplemented3() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @abstract @constructor @extends {A} */ var B = function() {};",
"/** @abstract @override */ B.prototype.foo = function() {};",
"/** @constructor @extends {B} */ var C = function() {};"),
"property foo on abstract class B is not implemented by type C");
}
public void testAbstractMethodNotImplemented4() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @abstract @constructor @extends {A} */ var B = function() {};",
"/** @constructor @extends {B} */ var C = function() {};"),
"property foo on abstract class A is not implemented by type C");
}
public void testAbstractMethodNotImplemented5() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @interface */ var I = function() {};",
"I.prototype.foo = function() {};",
"/** @abstract @constructor @implements {I} */ var A = function() {};",
"/** @abstract @override */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};"),
"property foo on abstract class A is not implemented by type B");
}
public void testAbstractMethodNotImplemented6() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @override @type {number} */ B.prototype.foo;"),
"property foo on abstract class A is not implemented by type B");
}
public void testAbstractMethodImplemented1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @abstract */ A.prototype.bar = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() {};",
"/** @override */ B.prototype.bar = function() {};",
"/** @constructor @extends {B} */ var C = function() {};"));
}
public void testAbstractMethodImplemented2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @abstract @constructor */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @abstract */ A.prototype.bar = function() {};",
"/** @abstract @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() {};",
"/** @constructor @extends {B} */ var C = function() {};",
"/** @override */ C.prototype.bar = function() {};"));
}
public void testPropertyUsedBeforeDefinition1() throws Exception {
testTypes("/** @constructor */ var T = function() {};\n" +
"/** @return {string} */" +
"T.prototype.f = function() { return this.g(); };\n" +
"/** @return {number} */ T.prototype.g = function() { return 1; };\n",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testPropertyUsedBeforeDefinition2() throws Exception {
testTypes("var n = {};\n" +
"/** @constructor */ n.T = function() {};\n" +
"/** @return {string} */" +
"n.T.prototype.f = function() { return this.g(); };\n" +
"/** @return {number} */ n.T.prototype.g = function() { return 1; };\n",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testAdd1() throws Exception {
testTypes("/**@return {void}*/function foo(){var a = 'abc'+foo();}");
}
public void testAdd2() throws Exception {
testTypes("/**@return {void}*/function foo(){var a = foo()+4;}");
}
public void testAdd3() throws Exception {
testTypes("/** @type {string} */ var a = 'a';" +
"/** @type {string} */ var b = 'b';" +
"/** @type {string} */ var c = a + b;");
}
public void testAdd4() throws Exception {
testTypes("/** @type {number} */ var a = 5;" +
"/** @type {string} */ var b = 'b';" +
"/** @type {string} */ var c = a + b;");
}
public void testAdd5() throws Exception {
testTypes("/** @type {string} */ var a = 'a';" +
"/** @type {number} */ var b = 5;" +
"/** @type {string} */ var c = a + b;");
}
public void testAdd6() throws Exception {
testTypes("/** @type {number} */ var a = 5;" +
"/** @type {number} */ var b = 5;" +
"/** @type {number} */ var c = a + b;");
}
public void testAdd7() throws Exception {
testTypes("/** @type {number} */ var a = 5;" +
"/** @type {string} */ var b = 'b';" +
"/** @type {number} */ var c = a + b;",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testAdd8() throws Exception {
testTypes("/** @type {string} */ var a = 'a';" +
"/** @type {number} */ var b = 5;" +
"/** @type {number} */ var c = a + b;",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testAdd9() throws Exception {
testTypes("/** @type {number} */ var a = 5;" +
"/** @type {number} */ var b = 5;" +
"/** @type {string} */ var c = a + b;",
"initializing variable\n" +
"found : number\n" +
"required: string");
}
public void testAdd10() throws Exception {
// d.e.f will have unknown type.
testTypes(
suppressMissingProperty("e", "f") +
"/** @type {number} */ var a = 5;" +
"/** @type {string} */ var c = a + d.e.f;");
}
public void testAdd11() throws Exception {
// d.e.f will have unknown type.
testTypes(
suppressMissingProperty("e", "f") +
"/** @type {number} */ var a = 5;" +
"/** @type {number} */ var c = a + d.e.f;");
}
public void testAdd12() throws Exception {
testTypes("/** @return {(number|string)} */ function a() { return 5; }" +
"/** @type {number} */ var b = 5;" +
"/** @type {boolean} */ var c = a() + b;",
"initializing variable\n" +
"found : (number|string)\n" +
"required: boolean");
}
public void testAdd13() throws Exception {
testTypes("/** @type {number} */ var a = 5;" +
"/** @return {(number|string)} */ function b() { return 5; }" +
"/** @type {boolean} */ var c = a + b();",
"initializing variable\n" +
"found : (number|string)\n" +
"required: boolean");
}
public void testAdd14() throws Exception {
testTypes("/** @type {(null|string)} */ var a = unknown;" +
"/** @type {number} */ var b = 5;" +
"/** @type {boolean} */ var c = a + b;",
"initializing variable\n" +
"found : (number|string)\n" +
"required: boolean");
}
public void testAdd15() throws Exception {
testTypes("/** @type {number} */ var a = 5;" +
"/** @return {(number|string)} */ function b() { return 5; }" +
"/** @type {boolean} */ var c = a + b();",
"initializing variable\n" +
"found : (number|string)\n" +
"required: boolean");
}
public void testAdd16() throws Exception {
testTypes("/** @type {(undefined|string)} */ var a = unknown;" +
"/** @type {number} */ var b = 5;" +
"/** @type {boolean} */ var c = a + b;",
"initializing variable\n" +
"found : (number|string)\n" +
"required: boolean");
}
public void testAdd17() throws Exception {
testTypes("/** @type {number} */ var a = 5;" +
"/** @type {(undefined|string)} */ var b = unknown;" +
"/** @type {boolean} */ var c = a + b;",
"initializing variable\n" +
"found : (number|string)\n" +
"required: boolean");
}
public void testAdd18() throws Exception {
testTypes("function f() {};" +
"/** @type {string} */ var a = 'a';" +
"/** @type {number} */ var c = a + f();",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testAdd19() throws Exception {
testTypes("/** @param {number} opt_x\n@param {number} opt_y\n" +
"@return {number} */ function f(opt_x, opt_y) {" +
"return opt_x + opt_y;}");
}
public void testAdd20() throws Exception {
testTypes("/** @param {!Number} opt_x\n@param {!Number} opt_y\n" +
"@return {number} */ function f(opt_x, opt_y) {" +
"return opt_x + opt_y;}");
}
public void testAdd21() throws Exception {
testTypes("/** @param {Number|Boolean} opt_x\n" +
"@param {number|boolean} opt_y\n" +
"@return {number} */ function f(opt_x, opt_y) {" +
"return opt_x + opt_y;}");
}
public void testNumericComparison1() throws Exception {
testTypes("/**@param {number} a*/ function f(a) {return a < 3;}");
}
public void testNumericComparison2() throws Exception {
testTypes("/**@param {!Object} a*/ function f(a) {return a < 3;}",
"left side of numeric comparison\n" +
"found : Object\n" +
"required: number");
}
public void testNumericComparison3() throws Exception {
testTypes("/**@param {string} a*/ function f(a) {return a < 3;}");
}
public void testNumericComparison4() throws Exception {
testTypes("/**@param {(number|undefined)} a*/ " +
"function f(a) {return a < 3;}");
}
public void testNumericComparison5() throws Exception {
testTypes("/**@param {*} a*/ function f(a) {return a < 3;}",
"left side of numeric comparison\n" +
"found : *\n" +
"required: number");
}
public void testNumericComparison6() throws Exception {
testTypes("/**@return {void} */ function foo() { if (3 >= foo()) return; }",
"right side of numeric comparison\n" +
"found : undefined\n" +
"required: number");
}
public void testStringComparison1() throws Exception {
testTypes("/**@param {string} a*/ function f(a) {return a < 'x';}");
}
public void testStringComparison2() throws Exception {
testTypes("/**@param {Object} a*/ function f(a) {return a < 'x';}");
}
public void testStringComparison3() throws Exception {
testTypes("/**@param {number} a*/ function f(a) {return a < 'x';}");
}
public void testStringComparison4() throws Exception {
testTypes("/**@param {string|undefined} a*/ " +
"function f(a) {return a < 'x';}");
}
public void testStringComparison5() throws Exception {
testTypes("/**@param {*} a*/ " +
"function f(a) {return a < 'x';}");
}
public void testStringComparison6() throws Exception {
testTypes("/**@return {void} */ " +
"function foo() { if ('a' >= foo()) return; }",
"right side of comparison\n" +
"found : undefined\n" +
"required: string");
}
public void testValueOfComparison1() throws Exception {
testTypes("/** @constructor */function O() {};" +
"/**@override*/O.prototype.valueOf = function() { return 1; };" +
"/**@param {!O} a\n@param {!O} b*/ function f(a,b) { return a < b; }");
}
public void testValueOfComparison2() throws Exception {
testTypes("/** @constructor */function O() {};" +
"/**@override*/O.prototype.valueOf = function() { return 1; };" +
"/**@param {!O} a\n@param {number} b*/" +
"function f(a,b) { return a < b; }");
}
public void testValueOfComparison3() throws Exception {
testTypes("/** @constructor */function O() {};" +
"/**@override*/O.prototype.toString = function() { return 'o'; };" +
"/**@param {!O} a\n@param {string} b*/" +
"function f(a,b) { return a < b; }");
}
public void testGenericRelationalExpression() throws Exception {
testTypes("/**@param {*} a\n@param {*} b*/ " +
"function f(a,b) {return a < b;}");
}
public void testInstanceof1() throws Exception {
testTypes("function foo(){" +
"if (bar instanceof 3)return;}",
"instanceof requires an object\n" +
"found : number\n" +
"required: Object");
}
public void testInstanceof2() throws Exception {
testTypes("/**@return {void}*/function foo(){" +
"if (foo() instanceof Object)return;}",
"deterministic instanceof yields false\n" +
"found : undefined\n" +
"required: NoObject");
}
public void testInstanceof3() throws Exception {
testTypes("/**@return {*} */function foo(){" +
"if (foo() instanceof Object)return;}");
}
public void testInstanceof4() throws Exception {
testTypes("/**@return {(Object|number)} */function foo(){" +
"if (foo() instanceof Object)return 3;}");
}
public void testInstanceof5() throws Exception {
// No warning for unknown types.
testTypes("/** @return {?} */ function foo(){" +
"if (foo() instanceof Object)return;}");
}
public void testInstanceof6() throws Exception {
testTypes("/**@return {(Array|number)} */function foo(){" +
"if (foo() instanceof Object)return 3;}");
}
public void testInstanceOfReduction3() throws Exception {
testTypes(
"/** \n" +
" * @param {Object} x \n" +
" * @param {Function} y \n" +
" * @return {boolean} \n" +
" */\n" +
"var f = function(x, y) {\n" +
" return x instanceof y;\n" +
"};");
}
public void testScoping1() throws Exception {
testTypes(
"/**@param {string} a*/function foo(a){" +
" /**@param {Array|string} a*/function bar(a){" +
" if (a instanceof Array)return;" +
" }" +
"}");
}
public void testScoping2() throws Exception {
testTypes(
"/** @type {number} */ var a;" +
"function Foo() {" +
" /** @type {string} */ var a;" +
"}");
}
public void testScoping3() throws Exception {
testTypes("\n\n/** @type{Number}*/var b;\n/** @type{!String} */var b;",
"variable b redefined with type String, original " +
"definition at [testcode]:3 with type (Number|null)");
}
public void testScoping4() throws Exception {
testTypes("/** @type{Number}*/var b; if (true) /** @type{!String} */var b;",
"variable b redefined with type String, original " +
"definition at [testcode]:1 with type (Number|null)");
}
public void testScoping5() throws Exception {
// multiple definitions are not checked by the type checker but by a
// subsequent pass
testTypes("if (true) var b; var b;");
}
public void testScoping6() throws Exception {
// multiple definitions are not checked by the type checker but by a
// subsequent pass
testTypes("if (true) var b; if (true) var b;");
}
public void testScoping7() throws Exception {
testTypes("/** @constructor */function A() {" +
" /** @type {!A} */this.a = null;" +
"}",
"assignment to property a of A\n" +
"found : null\n" +
"required: A");
}
public void testScoping8() throws Exception {
testTypes("/** @constructor */function A() {}" +
"/** @constructor */function B() {" +
" /** @type {!A} */this.a = null;" +
"}",
"assignment to property a of B\n" +
"found : null\n" +
"required: A");
}
public void testScoping9() throws Exception {
testTypes("/** @constructor */function B() {" +
" /** @type {!A} */this.a = null;" +
"}" +
"/** @constructor */function A() {}",
"assignment to property a of B\n" +
"found : null\n" +
"required: A");
}
public void testScoping10() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = function b(){};");
// a declared, b is not
assertTrue(p.scope.isDeclared("a", false));
assertFalse(p.scope.isDeclared("b", false));
// checking that a has the correct assigned type
assertEquals("function (): undefined",
p.scope.getVar("a").getType().toString());
}
public void testDontDropPropertiesInUnion1() throws Exception {
testTypes(LINE_JOINER.join(
"/** @param {{a: number}|{a:number, b:string}} x */",
"function f(x) {",
" var /** null */ n = x.b;",
"}"));
}
public void testDontDropPropertiesInUnion2() throws Exception {
testTypes(LINE_JOINER.join(
"/** @param {{a:number, b:string}|{a: number}} x */",
"function f(x) {",
" var /** null */ n = x.b;",
"}"));
}
public void testDontDropPropertiesInUnion3() throws Exception {
testTypes(LINE_JOINER.join(
"/** @param {{a: number}|{a:number, b:string}} x */",
"function f(x) {}",
"/** @param {{a: number}} x */",
"function g(x) { return x.b; }"));
}
public void testDontDropPropertiesInUnion4() throws Exception {
testTypes(LINE_JOINER.join(
"/** @param {{a: number}|{a:number, b:string}} x */",
"function f(x) {}",
"/** @param {{c: number}} x */",
"function g(x) { return x.b; }"),
"Property b never defined on x");
}
public void testDontDropPropertiesInUnion5() throws Exception {
testTypes(LINE_JOINER.join(
"/** @param {{a: number}|{a: number, b: string}} x */",
"function f(x) {}",
"f({a: 123});"));
}
public void testDontDropPropertiesInUnion6() throws Exception {
testTypes(LINE_JOINER.join(
"/** @param {{a: number}|{a: number, b: string}} x */",
"function f(x) {",
" var /** null */ n = x;",
"}"),
LINE_JOINER.join(
"initializing variable",
"found : {a: number}",
"required: null"));
}
public void testDontDropPropertiesInUnion7() throws Exception {
// Missed warning because in the registry we map {a, c} to {b, d}
testTypes(LINE_JOINER.join(
"/** @param {{a: number}|{a:number, b:string}} x */",
"function f(x) {}",
"/** @param {{c: number}|{c:number, d:string}} x */",
"function g(x) { return x.b; }"));
}
public void testScoping11() throws Exception {
// named function expressions create a binding in their body only
// the return is wrong but the assignment is OK since the type of b is ?
testTypes(
"/** @return {number} */var a = function b(){ return b };",
"inconsistent return type\n" +
"found : function (): number\n" +
"required: number");
}
public void testScoping12() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @type {number} */ F.prototype.bar = 3;" +
"/** @param {!F} f */ function g(f) {" +
" /** @return {string} */" +
" function h() {" +
" return f.bar;" +
" }" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testFunctionArguments1() throws Exception {
testFunctionType(
"/** @param {number} a\n@return {string} */" +
"function f(a) {}",
"function (number): string");
}
public void testFunctionArguments2() throws Exception {
testFunctionType(
"/** @param {number} opt_a\n@return {string} */" +
"function f(opt_a) {}",
"function (number=): string");
}
public void testFunctionArguments3() throws Exception {
testFunctionType(
"/** @param {number} b\n@return {string} */" +
"function f(a,b) {}",
"function (?, number): string");
}
public void testFunctionArguments4() throws Exception {
testFunctionType(
"/** @param {number} opt_a\n@return {string} */" +
"function f(a,opt_a) {}",
"function (?, number=): string");
}
public void testFunctionArguments5() throws Exception {
testTypes(
"function a(opt_a,a) {}",
"optional arguments must be at the end");
}
public void testFunctionArguments6() throws Exception {
testTypes(
"function a(var_args,a) {}",
"variable length argument must be last");
}
public void testFunctionArguments7() throws Exception {
testTypes(
"/** @param {number} opt_a\n@return {string} */" +
"function a(a,opt_a,var_args) {}");
}
public void testFunctionArguments8() throws Exception {
testTypes(
"function a(a,opt_a,var_args,b) {}",
"variable length argument must be last");
}
public void testFunctionArguments9() throws Exception {
// testing that only one error is reported
testTypes(
"function a(a,opt_a,var_args,b,c) {}",
"variable length argument must be last");
}
public void testFunctionArguments10() throws Exception {
// testing that only one error is reported
testTypes(
"function a(a,opt_a,b,c) {}",
"optional arguments must be at the end");
}
public void testFunctionArguments11() throws Exception {
testTypes(
"function a(a,opt_a,b,c,var_args,d) {}",
"optional arguments must be at the end");
}
public void testFunctionArguments12() throws Exception {
testTypes("/** @param {String} foo */function bar(baz){}",
"parameter foo does not appear in bar's parameter list");
}
public void testFunctionArguments13() throws Exception {
// verifying that the argument type have non-inferable types
testTypes(
"/** @return {boolean} */ function u() { return true; }" +
"/** @param {boolean} b\n@return {?boolean} */" +
"function f(b) { if (u()) { b = null; } return b; }",
"assignment\n" +
"found : null\n" +
"required: boolean");
}
public void testFunctionArguments14() throws Exception {
testTypes(
"/**\n" +
" * @param {string} x\n" +
" * @param {number} opt_y\n" +
" * @param {boolean} var_args\n" +
" */ function f(x, opt_y, var_args) {}" +
"f('3'); f('3', 2); f('3', 2, true); f('3', 2, true, false);");
}
public void testFunctionArguments15() throws Exception {
testTypes(
"/** @param {?function(*)} f */" +
"function g(f) { f(1, 2); }",
"Function f: called with 2 argument(s). " +
"Function requires at least 1 argument(s) " +
"and no more than 1 argument(s).");
}
public void testFunctionArguments16() throws Exception {
testTypes(
"/** @param {...number} var_args */" +
"function g(var_args) {} g(1, true);",
"actual parameter 2 of g does not match formal parameter\n" +
"found : boolean\n" +
"required: (number|undefined)");
}
public void testFunctionArguments17() throws Exception {
testTypes(
"/** @param {booool|string} x */" +
"function f(x) { g(x) }" +
"/** @param {number} x */" +
"function g(x) {}",
"Bad type annotation. Unknown type booool");
}
public void testFunctionArguments18() throws Exception {
testTypes(
"function f(x) {}" +
"f(/** @param {number} y */ (function() {}));",
"parameter y does not appear in <anonymous>'s parameter list");
}
public void testPrintFunctionName1() throws Exception {
// Ensures that the function name is pretty.
testTypes(
"var goog = {}; goog.run = function(f) {};" +
"goog.run();",
"Function goog.run: called with 0 argument(s). " +
"Function requires at least 1 argument(s) " +
"and no more than 1 argument(s).");
}
public void testPrintFunctionName2() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {}; " +
"Foo.prototype.run = function(f) {};" +
"(new Foo).run();",
"Function Foo.prototype.run: called with 0 argument(s). " +
"Function requires at least 1 argument(s) " +
"and no more than 1 argument(s).");
}
public void testFunctionInference1() throws Exception {
testFunctionType(
"function f(a) {}",
"function (?): undefined");
}
public void testFunctionInference2() throws Exception {
testFunctionType(
"function f(a,b) {}",
"function (?, ?): undefined");
}
public void testFunctionInference3() throws Exception {
testFunctionType(
"function f(var_args) {}",
"function (...?): undefined");
}
public void testFunctionInference4() throws Exception {
testFunctionType(
"function f(a,b,c,var_args) {}",
"function (?, ?, ?, ...?): undefined");
}
public void testFunctionInference5() throws Exception {
testFunctionType(
"/** @this {Date}\n@return {string} */function f(a) {}", "function (this:Date, ?): string");
}
public void testFunctionInference6() throws Exception {
testFunctionType(
"/** @this {Date}\n@return {string} */function f(opt_a) {}",
"function (this:Date, ?=): string");
}
public void testFunctionInference7() throws Exception {
testFunctionType(
"/** @this {Date} */function f(a,b,c,var_args) {}",
"function (this:Date, ?, ?, ?, ...?): undefined");
}
public void testFunctionInference8() throws Exception {
testFunctionType(
"function f() {}",
"function (): undefined");
}
public void testFunctionInference9() throws Exception {
testFunctionType(
"var f = function() {};",
"function (): undefined");
}
public void testFunctionInference10() throws Exception {
testFunctionType(
"/** @this {Date}\n@param {boolean} b\n@return {string} */" +
"var f = function(a,b) {};",
"function (this:Date, ?, boolean): string");
}
public void testFunctionInference11() throws Exception {
testFunctionType(
"var goog = {};" +
"/** @return {number}*/goog.f = function(){};",
"goog.f",
"function (): number");
}
public void testFunctionInference12() throws Exception {
testFunctionType(
"var goog = {};" +
"goog.f = function(){};",
"goog.f",
"function (): undefined");
}
public void testFunctionInference13() throws Exception {
testFunctionType(
"var goog = {};" +
"/** @constructor */ goog.Foo = function(){};" +
"/** @param {!goog.Foo} f */function eatFoo(f){};",
"eatFoo",
"function (goog.Foo): undefined");
}
public void testFunctionInference14() throws Exception {
testFunctionType(
"var goog = {};" +
"/** @constructor */ goog.Foo = function(){};" +
"/** @return {!goog.Foo} */function eatFoo(){ return new goog.Foo; };",
"eatFoo",
"function (): goog.Foo");
}
public void testFunctionInference15() throws Exception {
testFunctionType(
"/** @constructor */ function f() {};" +
"f.prototype.foo = function(){};",
"f.prototype.foo",
"function (this:f): undefined");
}
public void testFunctionInference16() throws Exception {
testFunctionType(
"/** @constructor */ function f() {};" +
"f.prototype.foo = function(){};",
"(new f).foo",
"function (this:f): undefined");
}
public void testFunctionInference17() throws Exception {
testFunctionType(
"/** @constructor */ function f() {}" +
"function abstractMethod() {}" +
"/** @param {number} x */ f.prototype.foo = abstractMethod;",
"(new f).foo",
"function (this:f, number): ?");
}
public void testFunctionInference18() throws Exception {
testFunctionType(
"var goog = {};" +
"/** @this {Date} */ goog.eatWithDate;",
"goog.eatWithDate",
"function (this:Date): ?");
}
public void testFunctionInference19() throws Exception {
testFunctionType(
"/** @param {string} x */ var f;",
"f",
"function (string): ?");
}
public void testFunctionInference20() throws Exception {
testFunctionType(
"/** @this {Date} */ var f;",
"f",
"function (this:Date): ?");
}
public void testFunctionInference21() throws Exception {
testTypes(
"var f = function() { throw 'x' };" +
"/** @return {boolean} */ var g = f;");
testFunctionType(
"var f = function() { throw 'x' };",
"f",
"function (): ?");
}
public void testFunctionInference22() throws Exception {
testTypes(
"/** @type {!Function} */ var f = function() { g(this); };" +
"/** @param {boolean} x */ var g = function(x) {};");
}
public void testFunctionInference23() throws Exception {
// We want to make sure that 'prop' isn't declared on all objects.
testTypes(
"/** @type {!Function} */ var f = function() {\n" +
" /** @type {number} */ this.prop = 3;\n" +
"};" +
"/**\n" +
" * @param {Object} x\n" +
" * @return {string}\n" +
" */ var g = function(x) { return x.prop; };");
}
public void testInnerFunction1() throws Exception {
testTypes(
"function f() {" +
" /** @type {number} */ var x = 3;\n" +
" function g() { x = null; }" +
" return x;" +
"}",
"assignment\n" +
"found : null\n" +
"required: number");
}
public void testInnerFunction2() throws Exception {
testTypes(
"/** @return {number} */\n" +
"function f() {" +
" var x = null;\n" +
" function g() { x = 3; }" +
" g();" +
" return x;" +
"}",
"inconsistent return type\n" +
"found : (null|number)\n" +
"required: number");
}
public void testInnerFunction3() throws Exception {
testTypes(
"var x = null;" +
"/** @return {number} */\n" +
"function f() {" +
" x = 3;\n" +
" /** @return {number} */\n" +
" function g() { x = true; return x; }" +
" return x;" +
"}",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testInnerFunction4() throws Exception {
testTypes(
"var x = null;" +
"/** @return {number} */\n" +
"function f() {" +
" x = '3';\n" +
" /** @return {number} */\n" +
" function g() { x = 3; return x; }" +
" return x;" +
"}",
"inconsistent return type\n" +
"found : string\n" +
"required: number");
}
public void testInnerFunction5() throws Exception {
testTypes(
"/** @return {number} */\n" +
"function f() {" +
" var x = 3;\n" +
" /** @return {number} */" +
" function g() { var x = 3;x = true; return x; }" +
" return x;" +
"}",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testInnerFunction6() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"function f() {" +
" var x = 0 || function() {};\n" +
" function g() { if (goog.isFunction(x)) { x(1); } }" +
" g();" +
"}",
"Function x: called with 1 argument(s). " +
"Function requires at least 0 argument(s) " +
"and no more than 0 argument(s).");
}
public void testInnerFunction7() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"function f() {" +
" /** @type {number|function()} */" +
" var x = 0 || function() {};\n" +
" function g() { if (goog.isFunction(x)) { x(1); } }" +
" g();" +
"}",
"Function x: called with 1 argument(s). " +
"Function requires at least 0 argument(s) " +
"and no more than 0 argument(s).");
}
public void testInnerFunction8() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"function f() {" +
" function x() {};\n" +
" function g() { if (goog.isFunction(x)) { x(1); } }" +
" g();" +
"}",
"Function x: called with 1 argument(s). " +
"Function requires at least 0 argument(s) " +
"and no more than 0 argument(s).");
}
public void testInnerFunction9() throws Exception {
testTypes(
"function f() {" +
" var x = 3;\n" +
" function g() { x = null; };\n" +
" function h() { return x == null; }" +
" return h();" +
"}");
}
public void testInnerFunction10() throws Exception {
testTypes(
"function f() {" +
" /** @type {?number} */ var x = null;" +
" /** @return {string} */" +
" function g() {" +
" if (!x) {" +
" x = 1;" +
" }" +
" return x;" +
" }" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testInnerFunction11() throws Exception {
// TODO(nicksantos): This is actually bad inference, because
// h sets x to null. We should fix this, but for now we do it
// this way so that we don't break existing binaries. We will
// need to change TypeInference#isUnflowable to fix this.
testTypes(
"function f() {" +
" /** @type {?number} */ var x = null;" +
" /** @return {number} */" +
" function g() {" +
" x = 1;" +
" h();" +
" return x;" +
" }" +
" function h() {" +
" x = null;" +
" }" +
"}");
}
public void testAbstractMethodHandling1() throws Exception {
testTypes(
"/** @type {Function} */ var abstractFn = function() {};" +
"abstractFn(1);");
}
public void testAbstractMethodHandling2() throws Exception {
testTypes(
"var abstractFn = function() {};" +
"abstractFn(1);",
"Function abstractFn: called with 1 argument(s). " +
"Function requires at least 0 argument(s) " +
"and no more than 0 argument(s).");
}
public void testAbstractMethodHandling3() throws Exception {
testTypes(
"var goog = {};" +
"/** @type {Function} */ goog.abstractFn = function() {};" +
"goog.abstractFn(1);");
}
public void testAbstractMethodHandling4() throws Exception {
testTypes(
"var goog = {};" +
"goog.abstractFn = function() {};" +
"goog.abstractFn(1);",
"Function goog.abstractFn: called with 1 argument(s). " +
"Function requires at least 0 argument(s) " +
"and no more than 0 argument(s).");
}
public void testAbstractMethodHandling5() throws Exception {
testTypes(
"/** @type {!Function} */ var abstractFn = function() {};" +
"/** @param {number} x */ var f = abstractFn;" +
"f('x');",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testAbstractMethodHandling6() throws Exception {
testTypes(
"var goog = {};" +
"/** @type {Function} */ goog.abstractFn = function() {};" +
"/** @param {number} x */ goog.f = abstractFn;" +
"goog.f('x');",
"actual parameter 1 of goog.f does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testMethodInference1() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @return {number} */ F.prototype.foo = function() { return 3; };" +
"/** @constructor \n * @extends {F} */ " +
"function G() {}" +
"/** @override */ G.prototype.foo = function() { return true; };",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testMethodInference2() throws Exception {
testTypes(
"var goog = {};" +
"/** @constructor */ goog.F = function() {};" +
"/** @return {number} */ goog.F.prototype.foo = " +
" function() { return 3; };" +
"/** @constructor \n * @extends {goog.F} */ " +
"goog.G = function() {};" +
"/** @override */ goog.G.prototype.foo = function() { return true; };",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testMethodInference3() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @param {boolean} x \n * @return {number} */ " +
"F.prototype.foo = function(x) { return 3; };" +
"/** @constructor \n * @extends {F} */ " +
"function G() {}" +
"/** @override */ " +
"G.prototype.foo = function(x) { return x; };",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testMethodInference4() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @param {boolean} x \n * @return {number} */ " +
"F.prototype.foo = function(x) { return 3; };" +
"/** @constructor \n * @extends {F} */ " +
"function G() {}" +
"/** @override */ " +
"G.prototype.foo = function(y) { return y; };",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testMethodInference5() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @param {number} x \n * @return {string} */ " +
"F.prototype.foo = function(x) { return 'x'; };" +
"/** @constructor \n * @extends {F} */ " +
"function G() {}" +
"/** @type {number} */ G.prototype.num = 3;" +
"/** @override */ " +
"G.prototype.foo = function(y) { return this.num + y; };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testMethodInference6() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @param {number} x */ F.prototype.foo = function(x) { };" +
"/** @constructor \n * @extends {F} */ " +
"function G() {}" +
"/** @override */ G.prototype.foo = function() { };" +
"(new G()).foo(1);");
}
public void testMethodInference7() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"F.prototype.foo = function() { };" +
"/** @constructor \n * @extends {F} */ " +
"function G() {}" +
"/** @override */ G.prototype.foo = function(x, y) { };",
"mismatch of the foo property type and the type of the property " +
"it overrides from superclass F\n" +
"original: function (this:F): undefined\n" +
"override: function (this:G, ?, ?): undefined");
}
public void testMethodInference8() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"F.prototype.foo = function() { };" +
"/** @constructor \n * @extends {F} */ " +
"function G() {}" +
"/** @override */ " +
"G.prototype.foo = function(opt_b, var_args) { };" +
"(new G()).foo(1, 2, 3);");
}
public void testMethodInference9() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"F.prototype.foo = function() { };" +
"/** @constructor \n * @extends {F} */ " +
"function G() {}" +
"/** @override */ " +
"G.prototype.foo = function(var_args, opt_b) { };",
"variable length argument must be last");
}
public void testStaticMethodDeclaration1() throws Exception {
testTypes(
"/** @constructor */ function F() { F.foo(true); }" +
"/** @param {number} x */ F.foo = function(x) {};",
"actual parameter 1 of F.foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testStaticMethodDeclaration2() throws Exception {
testTypes(
"var goog = goog || {}; function f() { goog.foo(true); }" +
"/** @param {number} x */ goog.foo = function(x) {};",
"actual parameter 1 of goog.foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testStaticMethodDeclaration3() throws Exception {
testTypes(
"var goog = goog || {}; function f() { goog.foo(true); }" +
"goog.foo = function() {};",
"Function goog.foo: called with 1 argument(s). Function requires " +
"at least 0 argument(s) and no more than 0 argument(s).");
}
public void testDuplicateStaticMethodDecl1() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @param {number} x */ goog.foo = function(x) {};" +
"/** @param {number} x */ goog.foo = function(x) {};",
"variable goog.foo redefined, original definition at [testcode]:1");
}
public void testDuplicateStaticMethodDecl2() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @param {number} x */ goog.foo = function(x) {};" +
"/** @param {number} x \n * @suppress {duplicate} */ " +
"goog.foo = function(x) {};");
}
public void testDuplicateStaticMethodDecl3() throws Exception {
testTypes(
"var goog = goog || {};" +
"goog.foo = function(x) {};" +
"goog.foo = function(x) {};");
}
public void testDuplicateStaticMethodDecl4() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @type {Function} */ goog.foo = function(x) {};" +
"goog.foo = function(x) {};");
}
public void testDuplicateStaticMethodDecl5() throws Exception {
testTypes(
"var goog = goog || {};" +
"goog.foo = function(x) {};" +
"/** @return {undefined} */ goog.foo = function(x) {};",
"variable goog.foo redefined, " +
"original definition at [testcode]:1");
}
public void testDuplicateStaticMethodDecl6() throws Exception {
// Make sure the CAST node doesn't interfere with the @suppress
// annotation.
testTypes(
"var goog = goog || {};" +
"goog.foo = function(x) {};" +
"/**\n" +
" * @suppress {duplicate}\n" +
" * @return {undefined}\n" +
" */\n" +
"goog.foo = " +
" /** @type {!Function} */ (function(x) {});");
}
public void testDuplicateStaticPropertyDecl1() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @type {Foo} */ goog.foo;" +
"/** @type {Foo} */ goog.foo;" +
"/** @constructor */ function Foo() {}");
}
public void testDuplicateStaticPropertyDecl2() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @type {Foo} */ goog.foo;" +
"/** @type {Foo} \n * @suppress {duplicate} */ goog.foo;" +
"/** @constructor */ function Foo() {}");
}
public void testDuplicateStaticPropertyDecl3() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @type {!Foo} */ goog.foo;" +
"/** @type {string} */ goog.foo;" +
"/** @constructor */ function Foo() {}",
"variable goog.foo redefined with type string, " +
"original definition at [testcode]:1 with type Foo");
}
public void testDuplicateStaticPropertyDecl4() throws Exception {
testClosureTypesMultipleWarnings(
"var goog = goog || {};" +
"/** @type {!Foo} */ goog.foo;" +
"/** @type {string} */ goog.foo = 'x';" +
"/** @constructor */ function Foo() {}",
ImmutableList.of(
"assignment to property foo of goog\n" +
"found : string\n" +
"required: Foo",
"variable goog.foo redefined with type string, " +
"original definition at [testcode]:1 with type Foo"));
}
public void testDuplicateStaticPropertyDecl5() throws Exception {
testClosureTypesMultipleWarnings(
"var goog = goog || {};" +
"/** @type {!Foo} */ goog.foo;" +
"/** @type {string}\n * @suppress {duplicate} */ goog.foo = 'x';" +
"/** @constructor */ function Foo() {}",
ImmutableList.of(
"assignment to property foo of goog\n" +
"found : string\n" +
"required: Foo",
"variable goog.foo redefined with type string, " +
"original definition at [testcode]:1 with type Foo"));
}
public void testDuplicateStaticPropertyDecl6() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @type {string} */ goog.foo = 'y';" +
"/** @type {string}\n * @suppress {duplicate} */ goog.foo = 'x';");
}
public void testDuplicateStaticPropertyDecl7() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @param {string} x */ goog.foo;" +
"/** @type {function(string)} */ goog.foo;");
}
public void testDuplicateStaticPropertyDecl8() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @return {EventCopy} */ goog.foo;" +
"/** @constructor */ function EventCopy() {}" +
"/** @return {EventCopy} */ goog.foo;");
}
public void testDuplicateStaticPropertyDecl9() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @return {EventCopy} */ goog.foo;" +
"/** @return {EventCopy} */ goog.foo;" +
"/** @constructor */ function EventCopy() {}");
}
public void testDuplicateStaticPropertyDec20() throws Exception {
testTypes(
"/**\n" +
" * @fileoverview\n" +
" * @suppress {duplicate}\n" +
" */" +
"var goog = goog || {};" +
"/** @type {string} */ goog.foo = 'y';" +
"/** @type {string} */ goog.foo = 'x';");
}
public void testDuplicateLocalVarDecl() throws Exception {
testClosureTypesMultipleWarnings(
"/** @param {number} x */\n" +
"function f(x) { /** @type {string} */ var x = ''; }",
ImmutableList.of(
"variable x redefined with type string, original definition" +
" at [testcode]:2 with type number",
"initializing variable\n" +
"found : string\n" +
"required: number"));
}
public void testDuplicateInstanceMethod1() throws Exception {
// If there's no jsdoc on the methods, then we treat them like
// any other inferred properties.
testTypes(
"/** @constructor */ function F() {}" +
"F.prototype.bar = function() {};" +
"F.prototype.bar = function() {};");
}
public void testDuplicateInstanceMethod2() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** jsdoc */ F.prototype.bar = function() {};" +
"/** jsdoc */ F.prototype.bar = function() {};",
"variable F.prototype.bar redefined, " +
"original definition at [testcode]:1");
}
public void testDuplicateInstanceMethod3() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"F.prototype.bar = function() {};" +
"/** jsdoc */ F.prototype.bar = function() {};",
"variable F.prototype.bar redefined, " +
"original definition at [testcode]:1");
}
public void testDuplicateInstanceMethod4() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** jsdoc */ F.prototype.bar = function() {};" +
"F.prototype.bar = function() {};");
}
public void testDuplicateInstanceMethod5() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** jsdoc \n * @return {number} */ F.prototype.bar = function() {" +
" return 3;" +
"};" +
"/** jsdoc \n * @suppress {duplicate} */ " +
"F.prototype.bar = function() { return ''; };",
"inconsistent return type\n" +
"found : string\n" +
"required: number");
}
public void testDuplicateInstanceMethod6() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** jsdoc \n * @return {number} */ F.prototype.bar = function() {" +
" return 3;" +
"};" +
"/** jsdoc \n * @return {string} * \n @suppress {duplicate} */ " +
"F.prototype.bar = function() { return ''; };",
"assignment to property bar of F.prototype\n" +
"found : function (this:F): string\n" +
"required: function (this:F): number");
}
public void testStubFunctionDeclaration1() throws Exception {
testFunctionType(
"/** @constructor */ function f() {};" +
"/** @param {number} x \n * @param {string} y \n" +
" * @return {number} */ f.prototype.foo;",
"(new f).foo",
"function (this:f, number, string): number");
}
public void testStubFunctionDeclaration2() throws Exception {
testExternFunctionType(
// externs
"/** @constructor */ function f() {};" +
"/** @constructor \n * @extends {f} */ f.subclass;",
"f.subclass",
"function (new:f.subclass): ?");
}
public void testStubFunctionDeclaration3() throws Exception {
testFunctionType(
"/** @constructor */ function f() {};" +
"/** @return {undefined} */ f.foo;",
"f.foo",
"function (): undefined");
}
public void testStubFunctionDeclaration4() throws Exception {
testFunctionType(
"/** @constructor */ function f() { " +
" /** @return {number} */ this.foo;" +
"}",
"(new f).foo",
"function (this:f): number");
}
public void testStubFunctionDeclaration5() throws Exception {
testFunctionType(
"/** @constructor */ function f() { " +
" /** @type {Function} */ this.foo;" +
"}",
"(new f).foo",
createNullableType(U2U_CONSTRUCTOR_TYPE).toString());
}
public void testStubFunctionDeclaration6() throws Exception {
testFunctionType(
"/** @constructor */ function f() {} " +
"/** @type {Function} */ f.prototype.foo;",
"(new f).foo",
createNullableType(U2U_CONSTRUCTOR_TYPE).toString());
}
public void testStubFunctionDeclaration7() throws Exception {
testFunctionType(
"/** @constructor */ function f() {} " +
"/** @type {Function} */ f.prototype.foo = function() {};",
"(new f).foo",
createNullableType(U2U_CONSTRUCTOR_TYPE).toString());
}
public void testStubFunctionDeclaration8() throws Exception {
testFunctionType(
"/** @type {Function} */ var f = function() {}; ",
"f",
createNullableType(U2U_CONSTRUCTOR_TYPE).toString());
}
public void testStubFunctionDeclaration9() throws Exception {
testFunctionType(
"/** @type {function():number} */ var f; ",
"f",
"function (): number");
}
public void testStubFunctionDeclaration10() throws Exception {
testFunctionType(
"/** @type {function(number):number} */ var f = function(x) {};",
"f",
"function (number): number");
}
public void testNestedFunctionInference1() throws Exception {
String nestedAssignOfFooAndBar =
"/** @constructor */ function f() {};" +
"f.prototype.foo = f.prototype.bar = function(){};";
testFunctionType(nestedAssignOfFooAndBar, "(new f).bar",
"function (this:f): undefined");
}
/**
* Tests the type of a function definition. The function defined by
* {@code functionDef} should be named {@code "f"}.
*/
private void testFunctionType(String functionDef, String functionType) {
testFunctionType(functionDef, "f", functionType);
}
/**
* Tests the type of a function definition. The function defined by
* {@code functionDef} should be named {@code functionName}.
*/
private void testFunctionType(String functionDef, String functionName,
String functionType) {
// using the variable initialization check to verify the function's type
testTypes(
functionDef +
"/** @type {number} */var a=" + functionName + ";",
"initializing variable\n" +
"found : " + functionType + "\n" +
"required: number");
}
/**
* Tests the type of a function definition in externs.
* The function defined by {@code functionDef} should be
* named {@code functionName}.
*/
private void testExternFunctionType(String functionDef, String functionName,
String functionType) {
testTypes(
functionDef,
"/** @type {number} */var a=" + functionName + ";",
"initializing variable\n" +
"found : " + functionType + "\n" +
"required: number", false);
}
public void testTypeRedefinition() throws Exception {
testClosureTypesMultipleWarnings("a={};/**@enum {string}*/ a.A = {ZOR:'b'};"
+ "/** @constructor */ a.A = function() {}",
ImmutableList.of(
"variable a.A redefined with type function (new:a.A): undefined, " +
"original definition at [testcode]:1 with type enum{a.A}",
"assignment to property A of a\n" +
"found : function (new:a.A): undefined\n" +
"required: enum{a.A}"));
}
public void testIn1() throws Exception {
testTypes("'foo' in Object");
}
public void testIn2() throws Exception {
testTypes("3 in Object");
}
public void testIn3() throws Exception {
testTypes("undefined in Object");
}
public void testIn4() throws Exception {
testTypes("Date in Object",
"left side of 'in'\n" +
"found : function (new:Date, ?=, ?=, ?=, ?=, ?=, ?=, ?=): string\n" +
"required: string");
}
public void testIn5() throws Exception {
testTypes("'x' in null",
"'in' requires an object\n" +
"found : null\n" +
"required: Object");
}
public void testIn6() throws Exception {
testTypes(
"/** @param {number} x */" +
"function g(x) {}" +
"g(1 in {});",
"actual parameter 1 of g does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testIn7() throws Exception {
// Make sure we do inference in the 'in' expression.
testTypes(
"/**\n" +
" * @param {number} x\n" +
" * @return {number}\n" +
" */\n" +
"function g(x) { return 5; }" +
"function f() {" +
" var x = {};" +
" x.foo = '3';" +
" return g(x.foo) in {};" +
"}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testForIn1() throws Exception {
testTypes(
"/** @param {boolean} x */ function f(x) {}" +
"for (var k in {}) {" +
" f(k);" +
"}",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: boolean");
}
public void testForIn2() throws Exception {
testTypes(
"/** @param {boolean} x */ function f(x) {}" +
"/** @enum {string} */ var E = {FOO: 'bar'};" +
"/** @type {Object<E, string>} */ var obj = {};" +
"var k = null;" +
"for (k in obj) {" +
" f(k);" +
"}",
"actual parameter 1 of f does not match formal parameter\n" +
"found : E<string>\n" +
"required: boolean");
}
public void testForIn3() throws Exception {
testTypes(
"/** @param {boolean} x */ function f(x) {}" +
"/** @type {Object<number>} */ var obj = {};" +
"for (var k in obj) {" +
" f(obj[k]);" +
"}",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: boolean");
}
public void testForIn4() throws Exception {
testTypes(
"/** @param {boolean} x */ function f(x) {}" +
"/** @enum {string} */ var E = {FOO: 'bar'};" +
"/** @type {Object<E, Array>} */ var obj = {};" +
"for (var k in obj) {" +
" f(obj[k]);" +
"}",
"actual parameter 1 of f does not match formal parameter\n" +
"found : (Array|null)\n" +
"required: boolean");
}
public void testForIn5() throws Exception {
testTypes(
"/** @param {boolean} x */ function f(x) {}" +
"/** @constructor */ var E = function(){};" +
"/** @override */ E.prototype.toString = function() { return ''; };" +
"/** @type {Object<!E, number>} */ var obj = {};" +
"for (var k in obj) {" +
" f(k);" +
"}",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: boolean");
}
// TODO(nicksantos): change this to something that makes sense.
// public void testComparison1() throws Exception {
// testTypes("/**@type null */var a;" +
// "/**@type !Date */var b;" +
// "if (a==b) {}",
// "condition always evaluates to false\n" +
// "left : null\n" +
// "right: Date");
// }
public void testComparison2() throws Exception {
testTypes("/**@type {number}*/var a;" +
"/**@type {!Date} */var b;" +
"if (a!==b) {}",
"condition always evaluates to true\n" +
"left : number\n" +
"right: Date");
}
public void testComparison3() throws Exception {
// Since null == undefined in JavaScript, this code is reasonable.
testTypes("/** @type {(Object|undefined)} */var a;" +
"var b = a == null");
}
public void testComparison4() throws Exception {
testTypes("/** @type {(!Object|undefined)} */var a;" +
"/** @type {!Object} */var b;" +
"var c = a == b");
}
public void testComparison5() throws Exception {
testTypes("/** @type {null} */var a;" +
"/** @type {null} */var b;" +
"a == b",
"condition always evaluates to true\n" +
"left : null\n" +
"right: null");
}
public void testComparison6() throws Exception {
testTypes("/** @type {null} */var a;" +
"/** @type {null} */var b;" +
"a != b",
"condition always evaluates to false\n" +
"left : null\n" +
"right: null");
}
public void testComparison7() throws Exception {
testTypes("var a;" +
"var b;" +
"a == b",
"condition always evaluates to true\n" +
"left : undefined\n" +
"right: undefined");
}
public void testComparison8() throws Exception {
testTypes("/** @type {Array<string>} */ var a = [];" +
"a[0] == null || a[1] == undefined");
}
public void testComparison9() throws Exception {
testTypes("/** @type {Array<undefined>} */ var a = [];" +
"a[0] == null",
"condition always evaluates to true\n" +
"left : undefined\n" +
"right: null");
}
public void testComparison10() throws Exception {
testTypes("/** @type {Array<undefined>} */ var a = [];" +
"a[0] === null");
}
public void testComparison11() throws Exception {
testTypes(
"(function(){}) == 'x'",
"condition always evaluates to false\n" +
"left : function (): undefined\n" +
"right: string");
}
public void testComparison12() throws Exception {
testTypes(
"(function(){}) == 3",
"condition always evaluates to false\n" +
"left : function (): undefined\n" +
"right: number");
}
public void testComparison13() throws Exception {
testTypes(
"(function(){}) == false",
"condition always evaluates to false\n" +
"left : function (): undefined\n" +
"right: boolean");
}
public void testComparison14() throws Exception {
testTypes("/** @type {function((Array|string), Object): number} */" +
"function f(x, y) { return x === y; }",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testComparison15() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @constructor */ function F() {}" +
"/**\n" +
" * @param {number} x\n" +
" * @constructor\n" +
" * @extends {F}\n" +
" */\n" +
"function G(x) {}\n" +
"goog.inherits(G, F);\n" +
"/**\n" +
" * @param {number} x\n" +
" * @constructor\n" +
" * @extends {G}\n" +
" */\n" +
"function H(x) {}\n" +
"goog.inherits(H, G);\n" +
"/** @param {G} x */" +
"function f(x) { return x.constructor === H; }",
null);
}
public void testDeleteOperator1() throws Exception {
testTypes(
"var x = {};" +
"/** @return {string} */ function f() { return delete x['a']; }",
"inconsistent return type\n" +
"found : boolean\n" +
"required: string");
}
public void testDeleteOperator2() throws Exception {
testTypes(
"var obj = {};" +
"/** \n" +
" * @param {string} x\n" +
" * @return {Object} */ function f(x) { return obj; }" +
"/** @param {?number} x */ function g(x) {" +
" if (x) { delete f(x)['a']; }" +
"}",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testEnumStaticMethod1() throws Exception {
testTypes(
"/** @enum */ var Foo = {AAA: 1};" +
"/** @param {number} x */ Foo.method = function(x) {};" +
"Foo.method(true);",
"actual parameter 1 of Foo.method does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testEnumStaticMethod2() throws Exception {
testTypes(
"/** @enum */ var Foo = {AAA: 1};" +
"/** @param {number} x */ Foo.method = function(x) {};" +
"function f() { Foo.method(true); }",
"actual parameter 1 of Foo.method does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testEnum1() throws Exception {
testTypes("/**@enum*/var a={BB:1,CC:2};\n" +
"/**@type {a}*/var d;d=a.BB;");
}
public void testEnum3() throws Exception {
testTypes("/**@enum*/var a={BB:1,BB:2}",
"variable a.BB redefined, original definition at [testcode]:1");
}
public void testEnum4() throws Exception {
testTypes("/**@enum*/var a={BB:'string'}",
"assignment to property BB of enum{a}\n" +
"found : string\n" +
"required: number");
}
public void testEnum5() throws Exception {
testTypes("/**@enum {?String}*/var a={BB:'string'}",
"assignment to property BB of enum{a}\n" +
"found : string\n" +
"required: (String|null)");
}
public void testEnum6() throws Exception {
testTypes("/**@enum*/var a={BB:1,CC:2};\n/**@type {!Array}*/var d;d=a.BB;",
"assignment\n" +
"found : a<number>\n" +
"required: Array");
}
public void testEnum7() throws Exception {
testTypes("/** @enum */var a={AA:1,BB:2,CC:3};" +
"/** @type {a} */var b=a.D;",
"element D does not exist on this enum");
}
public void testEnum8() throws Exception {
testClosureTypesMultipleWarnings("/** @enum */var a=8;",
ImmutableList.of(
"enum initializer must be an object literal or an enum",
"initializing variable\n" +
"found : number\n" +
"required: enum{a}"));
}
public void testEnum9() throws Exception {
testClosureTypesMultipleWarnings(
"var goog = {};" +
"/** @enum */goog.a=8;",
ImmutableList.of(
"assignment to property a of goog\n" +
"found : number\n" +
"required: enum{goog.a}",
"enum initializer must be an object literal or an enum"));
}
public void testEnum10() throws Exception {
testTypes(
"/** @enum {number} */" +
"goog.K = { A : 3 };");
}
public void testEnum11() throws Exception {
testTypes(
"/** @enum {number} */" +
"goog.K = { 502 : 3 };");
}
public void testEnum12() throws Exception {
testTypes(
"/** @enum {number} */ var a = {};" +
"/** @enum */ var b = a;");
}
public void testEnum13() throws Exception {
testTypes(
"/** @enum {number} */ var a = {};" +
"/** @enum {string} */ var b = a;",
"incompatible enum element types\n" +
"found : number\n" +
"required: string");
}
public void testEnum14() throws Exception {
testTypes(
"/** @enum {number} */ var a = {FOO:5};" +
"/** @enum */ var b = a;" +
"var c = b.FOO;");
}
public void testEnum15() throws Exception {
testTypes(
"/** @enum {number} */ var a = {FOO:5};" +
"/** @enum */ var b = a;" +
"var c = b.BAR;",
"element BAR does not exist on this enum");
}
public void testEnum16() throws Exception {
testTypes("var goog = {};" +
"/**@enum*/goog .a={BB:1,BB:2}",
"variable goog.a.BB redefined, original definition at [testcode]:1");
}
public void testEnum17() throws Exception {
testTypes("var goog = {};" +
"/**@enum*/goog.a={BB:'string'}",
"assignment to property BB of enum{goog.a}\n" +
"found : string\n" +
"required: number");
}
public void testEnum18() throws Exception {
testTypes("/**@enum*/ var E = {A: 1, B: 2};" +
"/** @param {!E} x\n@return {number} */\n" +
"var f = function(x) { return x; };");
}
public void testEnum19() throws Exception {
testTypes("/**@enum*/ var E = {A: 1, B: 2};" +
"/** @param {number} x\n@return {!E} */\n" +
"var f = function(x) { return x; };",
"inconsistent return type\n" +
"found : number\n" +
"required: E<number>");
}
public void testEnum20() throws Exception {
testTypes("/**@enum*/ var E = {A: 1, B: 2}; var x = []; x[E.A] = 0;");
}
public void testEnum21() throws Exception {
Node n = parseAndTypeCheck(
"/** @enum {string} */ var E = {A : 'a', B : 'b'};\n" +
"/** @param {!E} x\n@return {!E} */ function f(x) { return x; }");
Node nodeX = n.getLastChild().getLastChild().getLastChild().getLastChild();
JSType typeE = nodeX.getJSType();
assertFalse(typeE.isObject());
assertFalse(typeE.isNullable());
}
public void testEnum22() throws Exception {
testTypes("/**@enum*/ var E = {A: 1, B: 2};" +
"/** @param {E} x \n* @return {number} */ function f(x) {return x}");
}
public void testEnum23() throws Exception {
testTypes("/**@enum*/ var E = {A: 1, B: 2};" +
"/** @param {E} x \n* @return {string} */ function f(x) {return x}",
"inconsistent return type\n" +
"found : E<number>\n" +
"required: string");
}
public void testEnum24() throws Exception {
testTypes("/**@enum {?Object} */ var E = {A: {}};" +
"/** @param {E} x \n* @return {!Object} */ function f(x) {return x}",
"inconsistent return type\n" +
"found : E<(Object|null)>\n" +
"required: Object");
}
public void testEnum25() throws Exception {
testTypes("/**@enum {!Object} */ var E = {A: {}};" +
"/** @param {E} x \n* @return {!Object} */ function f(x) {return x}");
}
public void testEnum26() throws Exception {
testTypes("var a = {}; /**@enum*/ a.B = {A: 1, B: 2};" +
"/** @param {a.B} x \n* @return {number} */ function f(x) {return x}");
}
public void testEnum27() throws Exception {
// x is unknown
testTypes("/** @enum */ var A = {B: 1, C: 2}; " +
"function f(x) { return A == x; }");
}
public void testEnum28() throws Exception {
// x is unknown
testTypes("/** @enum */ var A = {B: 1, C: 2}; " +
"function f(x) { return A.B == x; }");
}
public void testEnum29() throws Exception {
testTypes("/** @enum */ var A = {B: 1, C: 2}; " +
"/** @return {number} */ function f() { return A; }",
"inconsistent return type\n" +
"found : enum{A}\n" +
"required: number");
}
public void testEnum30() throws Exception {
testTypes("/** @enum */ var A = {B: 1, C: 2}; " +
"/** @return {number} */ function f() { return A.B; }");
}
public void testEnum31() throws Exception {
testTypes("/** @enum */ var A = {B: 1, C: 2}; " +
"/** @return {A} */ function f() { return A; }",
"inconsistent return type\n" +
"found : enum{A}\n" +
"required: A<number>");
}
public void testEnum32() throws Exception {
testTypes("/** @enum */ var A = {B: 1, C: 2}; " +
"/** @return {A} */ function f() { return A.B; }");
}
public void testEnum34() throws Exception {
testTypes("/** @enum */ var A = {B: 1, C: 2}; " +
"/** @param {number} x */ function f(x) { return x == A.B; }");
}
public void testEnum35() throws Exception {
testTypes("var a = a || {}; /** @enum */ a.b = {C: 1, D: 2};" +
"/** @return {a.b} */ function f() { return a.b.C; }");
}
public void testEnum36() throws Exception {
testTypes("var a = a || {}; /** @enum */ a.b = {C: 1, D: 2};" +
"/** @return {!a.b} */ function f() { return 1; }",
"inconsistent return type\n" +
"found : number\n" +
"required: a.b<number>");
}
public void testEnum37() throws Exception {
testTypes(
"var goog = goog || {};" +
"/** @enum {number} */ goog.a = {};" +
"/** @enum */ var b = goog.a;");
}
public void testEnum38() throws Exception {
testTypes(
"/** @enum {MyEnum} */ var MyEnum = {};" +
"/** @param {MyEnum} x */ function f(x) {}",
"Parse error. Cycle detected in inheritance chain " +
"of type MyEnum");
}
public void testEnum39() throws Exception {
testTypes(
"/** @enum {Number} */ var MyEnum = {FOO: new Number(1)};" +
"/** @param {MyEnum} x \n * @return {number} */" +
"function f(x) { return x == MyEnum.FOO && MyEnum.FOO == x; }",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testEnum40() throws Exception {
testTypes(
"/** @enum {Number} */ var MyEnum = {FOO: new Number(1)};" +
"/** @param {number} x \n * @return {number} */" +
"function f(x) { return x == MyEnum.FOO && MyEnum.FOO == x; }",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testEnum41() throws Exception {
testTypes(
"/** @enum {number} */ var MyEnum = {/** @const */ FOO: 1};" +
"/** @return {string} */" +
"function f() { return MyEnum.FOO; }",
"inconsistent return type\n" +
"found : MyEnum<number>\n" +
"required: string");
}
public void testEnum42() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"/** @enum {Object} */ var MyEnum = {FOO: {newProperty: 1, b: 2}};" +
"f(MyEnum.FOO.newProperty);");
}
public void testAliasedEnum1() throws Exception {
testTypes(
"/** @enum */ var YourEnum = {FOO: 3};" +
"/** @enum */ var MyEnum = YourEnum;" +
"/** @param {MyEnum} x */ function f(x) {} f(MyEnum.FOO);");
}
public void testAliasedEnum2() throws Exception {
testTypes(
"/** @enum */ var YourEnum = {FOO: 3};" +
"/** @enum */ var MyEnum = YourEnum;" +
"/** @param {YourEnum} x */ function f(x) {} f(MyEnum.FOO);");
}
public void testAliasedEnum3() throws Exception {
testTypes(
"/** @enum */ var YourEnum = {FOO: 3};" +
"/** @enum */ var MyEnum = YourEnum;" +
"/** @param {MyEnum} x */ function f(x) {} f(YourEnum.FOO);");
}
public void testAliasedEnum4() throws Exception {
testTypes(
"/** @enum */ var YourEnum = {FOO: 3};" +
"/** @enum */ var MyEnum = YourEnum;" +
"/** @param {YourEnum} x */ function f(x) {} f(YourEnum.FOO);");
}
public void testAliasedEnum5() throws Exception {
testTypes(
"/** @enum */ var YourEnum = {FOO: 3};" +
"/** @enum */ var MyEnum = YourEnum;" +
"/** @param {string} x */ function f(x) {} f(MyEnum.FOO);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : YourEnum<number>\n" +
"required: string");
}
public void testBackwardsEnumUse1() throws Exception {
testTypes(
"/** @return {string} */ function f() { return MyEnum.FOO; }" +
"/** @enum {string} */ var MyEnum = {FOO: 'x'};");
}
public void testBackwardsEnumUse2() throws Exception {
testTypes(
"/** @return {number} */ function f() { return MyEnum.FOO; }" +
"/** @enum {string} */ var MyEnum = {FOO: 'x'};",
"inconsistent return type\n" +
"found : MyEnum<string>\n" +
"required: number");
}
public void testBackwardsEnumUse3() throws Exception {
testTypes(
"/** @return {string} */ function f() { return MyEnum.FOO; }" +
"/** @enum {string} */ var YourEnum = {FOO: 'x'};" +
"/** @enum {string} */ var MyEnum = YourEnum;");
}
public void testBackwardsEnumUse4() throws Exception {
testTypes(
"/** @return {number} */ function f() { return MyEnum.FOO; }" +
"/** @enum {string} */ var YourEnum = {FOO: 'x'};" +
"/** @enum {string} */ var MyEnum = YourEnum;",
"inconsistent return type\n" +
"found : YourEnum<string>\n" +
"required: number");
}
public void testBackwardsEnumUse5() throws Exception {
testTypes(
"/** @return {string} */ function f() { return MyEnum.BAR; }" +
"/** @enum {string} */ var YourEnum = {FOO: 'x'};" +
"/** @enum {string} */ var MyEnum = YourEnum;",
"element BAR does not exist on this enum");
}
public void testBackwardsTypedefUse2() throws Exception {
testTypes(
"/** @this {MyTypedef} */ function f() {}" +
"/** @typedef {!(Date|Array)} */ var MyTypedef;");
}
public void testBackwardsTypedefUse4() throws Exception {
testTypes(
"/** @return {MyTypedef} */ function f() { return null; }" +
"/** @typedef {string} */ var MyTypedef;",
"inconsistent return type\n" +
"found : null\n" +
"required: string");
}
public void testBackwardsTypedefUse6() throws Exception {
testTypes(
"/** @return {goog.MyTypedef} */ function f() { return null; }" +
"var goog = {};" +
"/** @typedef {string} */ goog.MyTypedef;",
"inconsistent return type\n" +
"found : null\n" +
"required: string");
}
public void testBackwardsTypedefUse7() throws Exception {
testTypes(
"/** @return {goog.MyTypedef} */ function f() { return null; }" +
"var goog = {};" +
"/** @typedef {Object} */ goog.MyTypedef;");
}
public void testBackwardsTypedefUse8() throws Exception {
// Technically, this isn't quite right, because the JS runtime
// will coerce null -> the global object. But we'll punt on that for now.
testTypes(
"/** @param {!Array} x */ function g(x) {}" +
"/** @this {goog.MyTypedef} */ function f() { g(this); }" +
"var goog = {};" +
"/** @typedef {(Array|null|undefined)} */ goog.MyTypedef;");
}
public void testBackwardsTypedefUse9() throws Exception {
testTypes(
"/** @param {!Array} x */ function g(x) {}" +
"/** @this {goog.MyTypedef} */ function f() { g(this); }" +
"var goog = {};" +
"/** @typedef {(Error|null|undefined)} */ goog.MyTypedef;",
"actual parameter 1 of g does not match formal parameter\n" +
"found : Error\n" +
"required: Array");
}
public void testBackwardsTypedefUse10() throws Exception {
testTypes(
"/** @param {goog.MyEnum} x */ function g(x) {}" +
"var goog = {};" +
"/** @enum {goog.MyTypedef} */ goog.MyEnum = {FOO: 1};" +
"/** @typedef {number} */ goog.MyTypedef;" +
"g(1);",
"actual parameter 1 of g does not match formal parameter\n" +
"found : number\n" +
"required: goog.MyEnum<number>");
}
public void testBackwardsConstructor1() throws Exception {
testTypes(
"function f() { (new Foo(true)); }" +
"/** \n * @constructor \n * @param {number} x */" +
"var Foo = function(x) {};",
"actual parameter 1 of Foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testBackwardsConstructor2() throws Exception {
testTypes(
"function f() { (new Foo(true)); }" +
"/** \n * @constructor \n * @param {number} x */" +
"var YourFoo = function(x) {};" +
"/** \n * @constructor \n * @param {number} x */" +
"var Foo = YourFoo;",
"actual parameter 1 of Foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testMinimalConstructorAnnotation() throws Exception {
testTypes("/** @constructor */function Foo(){}");
}
public void testGoodExtends1() throws Exception {
// A minimal @extends example
testTypes("/** @constructor */function base() {}\n" +
"/** @constructor\n * @extends {base} */function derived() {}\n");
}
public void testGoodExtends2() throws Exception {
testTypes("/** @constructor\n * @extends base */function derived() {}\n" +
"/** @constructor */function base() {}\n");
}
public void testGoodExtends3() throws Exception {
testTypes("/** @constructor\n * @extends {Object} */function base() {}\n" +
"/** @constructor\n * @extends {base} */function derived() {}\n");
}
public void testGoodExtends4() throws Exception {
// Ensure that @extends actually sets the base type of a constructor
// correctly. Because this isn't part of the human-readable Function
// definition, we need to crawl the prototype chain (eww).
Node n = parseAndTypeCheck(
"var goog = {};\n" +
"/** @constructor */goog.Base = function(){};\n" +
"/** @constructor\n" +
" * @extends {goog.Base} */goog.Derived = function(){};\n");
Node subTypeName = n.getLastChild().getLastChild().getFirstChild();
assertEquals("goog.Derived", subTypeName.getQualifiedName());
FunctionType subCtorType =
(FunctionType) subTypeName.getNext().getJSType();
assertEquals("goog.Derived", subCtorType.getInstanceType().toString());
JSType superType = subCtorType.getPrototype().getImplicitPrototype();
assertEquals("goog.Base", superType.toString());
}
public void testGoodExtends5() throws Exception {
// we allow for the extends annotation to be placed first
testTypes("/** @constructor */function base() {}\n" +
"/** @extends {base}\n * @constructor */function derived() {}\n");
}
public void testGoodExtends6() throws Exception {
testFunctionType(
CLOSURE_DEFS +
"/** @constructor */function base() {}\n" +
"/** @return {number} */ " +
" base.prototype.foo = function() { return 1; };\n" +
"/** @extends {base}\n * @constructor */function derived() {}\n" +
"goog.inherits(derived, base);",
"derived.superClass_.foo",
"function (this:base): number");
}
public void testGoodExtends7() throws Exception {
testFunctionType(
"Function.prototype.inherits = function(x) {};" +
"/** @constructor */function base() {}\n" +
"/** @extends {base}\n * @constructor */function derived() {}\n" +
"derived.inherits(base);",
"(new derived).constructor",
"function (new:derived, ...?): ?");
}
public void testGoodExtends8() throws Exception {
testTypes("/** @constructor \n @extends {Base} */ function Sub() {}" +
"/** @return {number} */ function f() { return (new Sub()).foo; }" +
"/** @constructor */ function Base() {}" +
"/** @type {boolean} */ Base.prototype.foo = true;",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testGoodExtends9() throws Exception {
testTypes(
"/** @constructor */ function Super() {}" +
"Super.prototype.foo = function() {};" +
"/** @constructor \n * @extends {Super} */ function Sub() {}" +
"Sub.prototype = new Super();" +
"/** @override */ Sub.prototype.foo = function() {};");
}
public void testGoodExtends10() throws Exception {
testTypes(
"/** @constructor */ function Super() {}" +
"/** @constructor \n * @extends {Super} */ function Sub() {}" +
"Sub.prototype = new Super();" +
"/** @return {Super} */ function foo() { return new Sub(); }");
}
public void testGoodExtends11() throws Exception {
testTypes(
"/** @constructor */ function Super() {}" +
"/** @param {boolean} x */ Super.prototype.foo = function(x) {};" +
"/** @constructor \n * @extends {Super} */ function Sub() {}" +
"Sub.prototype = new Super();" +
"(new Sub()).foo(0);",
"actual parameter 1 of Super.prototype.foo " +
"does not match formal parameter\n" +
"found : number\n" +
"required: boolean");
}
public void testGoodExtends12() throws Exception {
testTypes(
"/** @constructor \n * @extends {Super} */ function Sub() {}" +
"/** @constructor \n * @extends {Sub} */ function Sub2() {}" +
"/** @constructor */ function Super() {}" +
"/** @param {Super} x */ function foo(x) {}" +
"foo(new Sub2());");
}
public void testGoodExtends13() throws Exception {
testTypes(
"/** @constructor \n * @extends {B} */ function C() {}" +
"/** @constructor \n * @extends {D} */ function E() {}" +
"/** @constructor \n * @extends {C} */ function D() {}" +
"/** @constructor \n * @extends {A} */ function B() {}" +
"/** @constructor */ function A() {}" +
"/** @param {number} x */ function f(x) {} f(new E());",
"actual parameter 1 of f does not match formal parameter\n" +
"found : E\n" +
"required: number");
}
public void testGoodExtends14() throws Exception {
testTypes(
CLOSURE_DEFS +
"/** @param {Function} f */ function g(f) {" +
" /** @constructor */ function NewType() {};" +
" goog.inherits(NewType, f);" +
" (new NewType());" +
"}");
}
public void testGoodExtends15() throws Exception {
testTypes(
CLOSURE_DEFS +
"/** @constructor */ function OldType() {}" +
"/** @param {?function(new:OldType)} f */ function g(f) {" +
" /**\n" +
" * @constructor\n" +
" * @extends {OldType}\n" +
" */\n" +
" function NewType() {};" +
" goog.inherits(NewType, f);" +
" NewType.prototype.method = function() {" +
" NewType.superClass_.foo.call(this);" +
" };" +
"}",
"Property foo never defined on OldType.prototype");
}
public void testGoodExtends16() throws Exception {
testTypes(
CLOSURE_DEFS +
"/** @param {Function} f */ function g(f) {" +
" /** @constructor */ function NewType() {};" +
" goog.inherits(f, NewType);" +
" (new NewType());" +
"}");
}
public void testGoodExtends17() throws Exception {
testFunctionType(
"Function.prototype.inherits = function(x) {};" +
"/** @constructor */function base() {}\n" +
"/** @param {number} x */ base.prototype.bar = function(x) {};\n" +
"/** @extends {base}\n * @constructor */function derived() {}\n" +
"derived.inherits(base);",
"(new derived).constructor.prototype.bar",
"function (this:base, number): undefined");
}
public void testGoodExtends18() throws Exception {
testTypes(
CLOSURE_DEFS +
"/** @constructor\n" +
" * @template T */\n" +
"function C() {}\n" +
"/** @constructor\n" +
" * @extends {C<string>} */\n" +
"function D() {};\n" +
"goog.inherits(D, C);\n" +
"(new D())");
}
public void testGoodExtends19() throws Exception {
testTypes(
CLOSURE_DEFS +
"/** @constructor */\n" +
"function C() {}\n" +
"" +
"/** @interface\n" +
" * @template T */\n" +
"function D() {}\n" +
"/** @param {T} t */\n" +
"D.prototype.method;\n" +
"" +
"/** @constructor\n" +
" * @template T\n" +
" * @extends {C}\n" +
" * @implements {D<T>} */\n" +
"function E() {};\n" +
"goog.inherits(E, C);\n" +
"/** @override */\n" +
"E.prototype.method = function(t) {};\n" +
"" +
"var e = /** @type {E<string>} */ (new E());\n" +
"e.method(3);",
"actual parameter 1 of E.prototype.method does not match formal " +
"parameter\n" +
"found : number\n" +
"required: string");
}
public void testGoodExtends20() throws Exception {
testTypes(""
+ "/** @interface */\n"
+ "var MyInterface = function() {};\n"
+ "MyInterface.prototype = {\n"
+ " /** @return {number} */\n"
+ " method: function() {}\n"
+ "}\n"
+ "/** @extends {MyInterface}\n * @interface */\n"
+ "var MyOtherInterface = function() {};\n"
+ "MyOtherInterface.prototype = {\n"
+ " /** @return {number} \n @override */\n"
+ " method: function() {}\n"
+ "}");
}
public void testGoodExtends21() throws Exception {
testTypes(""
+ "/** @constructor */\n"
+ "var MyType = function() {};\n"
+ "MyType.prototype = {\n"
+ " /** @return {number} */\n"
+ " method: function() {}\n"
+ "}\n"
+ "/** @constructor \n"
+ " * @extends {MyType}\n"
+ " */\n"
+ "var MyOtherType = function() {};\n"
+ "MyOtherType.prototype = {\n"
+ " /** @return {number}\n"
+ " * @override */\n"
+ " method: function() {}\n"
+ "}");
}
public void testBadExtends1() throws Exception {
testTypes("/** @constructor */function base() {}\n" +
"/** @constructor\n * @extends {not_base} */function derived() {}\n",
"Bad type annotation. Unknown type not_base");
}
public void testBadExtends2() throws Exception {
testTypes("/** @constructor */function base() {\n" +
"/** @type {!Number}*/\n" +
"this.baseMember = new Number(4);\n" +
"}\n" +
"/** @constructor\n" +
" * @extends {base} */function derived() {}\n" +
"/** @param {!String} x*/\n" +
"function foo(x){ }\n" +
"/** @type {!derived}*/var y;\n" +
"foo(y.baseMember);\n",
"actual parameter 1 of foo does not match formal parameter\n" +
"found : Number\n" +
"required: String");
}
public void testBadExtends3() throws Exception {
testTypes("/** @extends {Object} */function base() {}",
"@extends used without @constructor or @interface for base");
}
public void testBadExtends4() throws Exception {
// If there's a subclass of a class with a bad extends,
// we only want to warn about the first one.
testTypes(
"/** @constructor \n * @extends {bad} */ function Sub() {}" +
"/** @constructor \n * @extends {Sub} */ function Sub2() {}" +
"/** @param {Sub} x */ function foo(x) {}" +
"foo(new Sub2());",
"Bad type annotation. Unknown type bad");
}
public void testBadExtends5() throws Exception {
testTypes(""
+ "/** @interface */\n"
+ "var MyInterface = function() {};\n"
+ "MyInterface.prototype = {\n"
+ " /** @return {number} */\n"
+ " method: function() {}\n"
+ "}\n"
+ "/** @extends {MyInterface}\n * @interface */\n"
+ "var MyOtherInterface = function() {};\n"
+ "MyOtherInterface.prototype = {\n"
+ " /** @return {string} \n @override */\n"
+ " method: function() {}\n"
+ "}",
""
+ "mismatch of the method property type and the type of the property "
+ "it overrides from superclass MyInterface\n"
+ "original: function (this:MyInterface): number\n"
+ "override: function (this:MyOtherInterface): string");
}
public void testBadExtends6() throws Exception {
testTypes(""
+ "/** @constructor */\n"
+ "var MyType = function() {};\n"
+ "MyType.prototype = {\n"
+ " /** @return {number} */\n"
+ " method: function() {}\n"
+ "}\n"
+ "/** @constructor \n"
+ " * @extends {MyType}\n"
+ " */\n"
+ "var MyOtherType = function() {};\n"
+ "MyOtherType.prototype = {\n"
+ " /** @return {string}\n"
+ " * @override */\n"
+ " method: function() { return ''; }\n"
+ "}",
""
+ "mismatch of the method property type and the type of the property "
+ "it overrides from superclass MyType\n"
+ "original: function (this:MyType): number\n"
+ "override: function (this:MyOtherType): string");
}
public void testLateExtends() throws Exception {
testTypes(
CLOSURE_DEFS +
"/** @constructor */ function Foo() {}\n" +
"Foo.prototype.foo = function() {};\n" +
"/** @constructor */function Bar() {}\n" +
"goog.inherits(Foo, Bar);\n",
"Missing @extends tag on type Foo");
}
public void testSuperclassMatch() throws Exception {
compiler.getOptions().setCodingConvention(new GoogleCodingConvention());
testTypes("/** @constructor */ var Foo = function() {};\n" +
"/** @constructor \n @extends Foo */ var Bar = function() {};\n" +
"Bar.inherits = function(x){};" +
"Bar.inherits(Foo);\n");
}
public void testSuperclassMatchWithMixin() throws Exception {
compiler.getOptions().setCodingConvention(new GoogleCodingConvention());
testTypes("/** @constructor */ var Foo = function() {};\n" +
"/** @constructor */ var Baz = function() {};\n" +
"/** @constructor \n @extends Foo */ var Bar = function() {};\n" +
"Bar.inherits = function(x){};" +
"Bar.mixin = function(y){};" +
"Bar.inherits(Foo);\n" +
"Bar.mixin(Baz);\n");
}
public void testSuperclassMismatch1() throws Exception {
compiler.getOptions().setCodingConvention(new GoogleCodingConvention());
testTypes("/** @constructor */ var Foo = function() {};\n" +
"/** @constructor \n @extends Object */ var Bar = function() {};\n" +
"Bar.inherits = function(x){};" +
"Bar.inherits(Foo);\n",
"Missing @extends tag on type Bar");
}
public void testSuperclassMismatch2() throws Exception {
compiler.getOptions().setCodingConvention(new GoogleCodingConvention());
testTypes("/** @constructor */ var Foo = function(){};\n" +
"/** @constructor */ var Bar = function(){};\n" +
"Bar.inherits = function(x){};" +
"Bar.inherits(Foo);",
"Missing @extends tag on type Bar");
}
public void testSuperClassDefinedAfterSubClass1() throws Exception {
testTypes(
"/** @constructor \n * @extends {Base} */ function A() {}" +
"/** @constructor \n * @extends {Base} */ function B() {}" +
"/** @constructor */ function Base() {}" +
"/** @param {A|B} x \n * @return {B|A} */ " +
"function foo(x) { return x; }");
}
public void testSuperClassDefinedAfterSubClass2() throws Exception {
testTypes(
"/** @constructor \n * @extends {Base} */ function A() {}" +
"/** @constructor \n * @extends {Base} */ function B() {}" +
"/** @param {A|B} x \n * @return {B|A} */ " +
"function foo(x) { return x; }" +
"/** @constructor */ function Base() {}");
}
public void testGoodSuperCall() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"class A {",
" /**",
" * @param {string} a",
" */",
" constructor(a) {",
" this.a = a;",
" }",
"}",
"class B extends A {",
" constructor() {",
" super('b');",
" }",
"}",
""));
}
public void testBadSuperCall() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"class A {",
" /**",
" * @param {string} a",
" */",
" constructor(a) {",
" this.a = a;",
" }",
"}",
"class B extends A {",
" constructor() {",
" super(5);",
" }",
"}"),
LINE_JOINER.join(
"actual parameter 1 of super does not match formal parameter",
"found : number",
"required: string"));
}
private void setLanguageInAndOut(LanguageMode languageIn, LanguageMode languageOut) {
CompilerOptions options = compiler.getOptions();
options.setLanguageIn(languageIn);
options.setLanguageOut(languageOut);
}
public void testDirectPrototypeAssignment1() throws Exception {
testTypes(
"/** @constructor */ function Base() {}" +
"Base.prototype.foo = 3;" +
"/** @constructor \n * @extends {Base} */ function A() {}" +
"A.prototype = new Base();" +
"/** @return {string} */ function foo() { return (new A).foo; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testDirectPrototypeAssignment2() throws Exception {
// This ensures that we don't attach property 'foo' onto the Base
// instance object.
testTypes(
"/** @constructor */ function Base() {}" +
"/** @constructor \n * @extends {Base} */ function A() {}" +
"A.prototype = new Base();" +
"A.prototype.foo = 3;" +
"/** @return {string} */ function foo() { return (new Base).foo; }");
}
public void testDirectPrototypeAssignment3() throws Exception {
// This verifies that the compiler doesn't crash if the user
// overwrites the prototype of a global variable in a local scope.
testTypes(
"/** @constructor */ var MainWidgetCreator = function() {};" +
"/** @param {Function} ctor */" +
"function createMainWidget(ctor) {" +
" /** @constructor */ function tempCtor() {};" +
" tempCtor.prototype = ctor.prototype;" +
" MainWidgetCreator.superClass_ = ctor.prototype;" +
" MainWidgetCreator.prototype = new tempCtor();" +
"}");
}
public void testGoodImplements1() throws Exception {
testTypes("/** @interface */function Disposable() {}\n" +
"/** @implements {Disposable}\n * @constructor */function f() {}");
}
public void testGoodImplements2() throws Exception {
testTypes("/** @interface */function Base1() {}\n" +
"/** @interface */function Base2() {}\n" +
"/** @constructor\n" +
" * @implements {Base1}\n" +
" * @implements {Base2}\n" +
" */ function derived() {}");
}
public void testGoodImplements3() throws Exception {
testTypes("/** @interface */function Disposable() {}\n" +
"/** @constructor \n @implements {Disposable} */function f() {}");
}
public void testGoodImplements4() throws Exception {
testTypes("var goog = {};" +
"/** @type {!Function} */" +
"goog.abstractMethod = function() {};" +
"/** @interface */\n" +
"goog.Disposable = goog.abstractMethod;" +
"goog.Disposable.prototype.dispose = goog.abstractMethod;" +
"/** @implements {goog.Disposable}\n * @constructor */" +
"goog.SubDisposable = function() {};" +
"/** @inheritDoc */ " +
"goog.SubDisposable.prototype.dispose = function() {};");
}
public void testGoodImplements5() throws Exception {
testTypes(
"/** @interface */\n" +
"goog.Disposable = function() {};" +
"/** @type {Function} */" +
"goog.Disposable.prototype.dispose = function() {};" +
"/** @implements {goog.Disposable}\n * @constructor */" +
"goog.SubDisposable = function() {};" +
"/** @param {number} key \n @override */ " +
"goog.SubDisposable.prototype.dispose = function(key) {};");
}
public void testGoodImplements6() throws Exception {
testTypes(
"var myNullFunction = function() {};" +
"/** @interface */\n" +
"goog.Disposable = function() {};" +
"/** @return {number} */" +
"goog.Disposable.prototype.dispose = myNullFunction;" +
"/** @implements {goog.Disposable}\n * @constructor */" +
"goog.SubDisposable = function() {};" +
"/** @return {number} \n @override */ " +
"goog.SubDisposable.prototype.dispose = function() { return 0; };");
}
public void testGoodImplements7() throws Exception {
testTypes(
"var myNullFunction = function() {};" +
"/** @interface */\n" +
"goog.Disposable = function() {};" +
"/** @return {number} */" +
"goog.Disposable.prototype.dispose = function() {};" +
"/** @implements {goog.Disposable}\n * @constructor */" +
"goog.SubDisposable = function() {};" +
"/** @return {number} \n @override */ " +
"goog.SubDisposable.prototype.dispose = function() { return 0; };");
}
public void testGoodImplements8() throws Exception {
testTypes(""
+ "/** @interface */\n"
+ "MyInterface = function() {};\n"
+ "MyInterface.prototype = {\n"
+ " /** @return {number} */\n"
+ " method: function() {}\n"
+ "}\n"
+ "/** @implements {MyInterface}\n * @constructor */\n"
+ "MyClass = function() {};\n"
+ "MyClass.prototype = {\n"
+ " /** @return {number} \n @override */\n"
+ " method: function() { return 0; }\n"
+ "}");
}
public void testBadImplements1() throws Exception {
testTypes("/** @interface */function Base1() {}\n" +
"/** @interface */function Base2() {}\n" +
"/** @constructor\n" +
" * @implements {nonExistent}\n" +
" * @implements {Base2}\n" +
" */ function derived() {}",
"Bad type annotation. Unknown type nonExistent");
}
public void testBadImplements2() throws Exception {
testTypes("/** @interface */function Disposable() {}\n" +
"/** @implements {Disposable}\n */function f() {}",
"@implements used without @constructor for f");
}
public void testBadImplements3() throws Exception {
testTypes(
"var goog = {};" +
"/** @type {!Function} */ goog.abstractMethod = function(){};" +
"/** @interface */ var Disposable = goog.abstractMethod;" +
"Disposable.prototype.method = goog.abstractMethod;" +
"/** @implements {Disposable}\n * @constructor */function f() {}",
"property method on interface Disposable is not implemented by type f");
}
public void testBadImplements4() throws Exception {
testTypes("/** @interface */function Disposable() {}\n" +
"/** @implements {Disposable}\n * @interface */function f() {}",
"f cannot implement this type; an interface can only extend, " +
"but not implement interfaces");
}
public void testBadImplements5() throws Exception {
testTypes("/** @interface */function Disposable() {}\n" +
"/** @type {number} */ Disposable.prototype.bar = function() {};",
"assignment to property bar of Disposable.prototype\n" +
"found : function (): undefined\n" +
"required: number");
}
public void testBadImplements6() throws Exception {
testClosureTypesMultipleWarnings(
"/** @interface */function Disposable() {}\n" +
"/** @type {function()} */ Disposable.prototype.bar = 3;",
ImmutableList.of(
"assignment to property bar of Disposable.prototype\n" +
"found : number\n" +
"required: function (): ?",
"interface members can only be empty property declarations, " +
"empty functions, or goog.abstractMethod"));
}
public void testBadImplements7() throws Exception {
testTypes(""
+ "/** @interface */\n"
+ "MyInterface = function() {};\n"
+ "MyInterface.prototype = {\n"
+ " /** @return {number} */\n"
+ " method: function() {}\n"
+ "}\n"
+ "/** @implements {MyInterface}\n * @constructor */\n"
+ "MyClass = function() {};\n"
+ "MyClass.prototype = {\n"
+ " /** @return {string} \n @override */\n"
+ " method: function() { return ''; }\n"
+ "}",
""
+ "mismatch of the method property on type MyClass and the type of the property "
+ "it overrides from interface MyInterface\n"
+ "original: function (): number\n"
+ "override: function (): string");
}
public void testBadImplements8() throws Exception {
testTypes(""
+ "/** @interface */\n"
+ "MyInterface = function() {};\n"
+ "MyInterface.prototype = {\n"
+ " /** @return {number} */\n"
+ " method: function() {}\n"
+ "}\n"
+ "/** @implements {MyInterface}\n * @constructor */\n"
+ "MyClass = function() {};\n"
+ "MyClass.prototype = {\n"
+ " /** @return {number} */\n"
+ " method: function() { return 0; }\n"
+ "}",
""
+ "property method already defined on interface MyInterface; "
+ "use @override to override it");
}
public void testProtoDoesNotRequireOverrideFromInterface() throws Exception {
testTypes(DEFAULT_EXTERNS + "/** @type {Object} */ Object.prototype.__proto__;",
"/** @interface */\n"
+ "var MyInterface = function() {};\n"
+ "/** @constructor\n @implements {MyInterface} */\n"
+ "var MySuper = function() {};\n"
+ "/** @constructor\n @extends {MySuper} */\n"
+ "var MyClass = function() {};\n"
+ "MyClass.prototype = {\n"
+ " __proto__: MySuper.prototype\n"
+ "}",
(String) null,
false);
}
public void testConstructorClassTemplate() throws Exception {
testTypes("/** @constructor \n @template S,T */ function A() {}\n");
}
public void testInterfaceExtends() throws Exception {
testTypes("/** @interface */function A() {}\n" +
"/** @interface \n * @extends {A} */function B() {}\n" +
"/** @constructor\n" +
" * @implements {B}\n" +
" */ function derived() {}");
}
public void testDontCrashOnDupPropDefinition() throws Exception {
testTypes(LINE_JOINER.join(
"/** @const */",
"var ns = {};",
"/** @interface */",
"ns.I = function() {};",
"/** @interface */",
"ns.A = function() {};",
"/**",
" * @constructor",
" * @implements {ns.I}",
" */",
"ns.A = function() {};"),
"variable ns.A redefined, original definition at [testcode]:6");
}
public void testBadInterfaceExtends1() throws Exception {
testTypes("/** @interface \n * @extends {nonExistent} */function A() {}",
"Bad type annotation. Unknown type nonExistent");
}
public void testBadInterfaceExtendsNonExistentInterfaces() throws Exception {
String js = "/** @interface \n" +
" * @extends {nonExistent1} \n" +
" * @extends {nonExistent2} \n" +
" */function A() {}";
String[] expectedWarnings = {
"Bad type annotation. Unknown type nonExistent1",
"Bad type annotation. Unknown type nonExistent2"
};
testTypes(js, expectedWarnings);
}
public void testBadInterfaceExtends2() throws Exception {
testTypes("/** @constructor */function A() {}\n" +
"/** @interface \n * @extends {A} */function B() {}",
"B cannot extend this type; interfaces can only extend interfaces");
}
public void testBadInterfaceExtends3() throws Exception {
testTypes("/** @interface */function A() {}\n" +
"/** @constructor \n * @extends {A} */function B() {}",
"B cannot extend this type; constructors can only extend constructors");
}
public void testBadInterfaceExtends4() throws Exception {
// TODO(user): This should be detected as an error. Even if we enforce
// that A cannot be used in the assignment, we should still detect the
// inheritance chain as invalid.
testTypes("/** @interface */function A() {}\n" +
"/** @constructor */function B() {}\n" +
"B.prototype = A;");
}
public void testBadInterfaceExtends5() throws Exception {
// TODO(user): This should be detected as an error. Even if we enforce
// that A cannot be used in the assignment, we should still detect the
// inheritance chain as invalid.
testTypes("/** @constructor */function A() {}\n" +
"/** @interface */function B() {}\n" +
"B.prototype = A;");
}
public void testBadImplementsAConstructor() throws Exception {
testTypes("/** @constructor */function A() {}\n" +
"/** @constructor \n * @implements {A} */function B() {}",
"can only implement interfaces");
}
public void testBadImplementsNonInterfaceType() throws Exception {
testTypes("/** @constructor \n * @implements {Boolean} */function B() {}",
"can only implement interfaces");
}
public void testBadImplementsNonObjectType() throws Exception {
testTypes("/** @constructor \n * @implements {string} */function S() {}",
"can only implement interfaces");
}
public void testBadImplementsDuplicateInterface1() throws Exception {
// verify that the same base (not templatized) interface cannot be
// @implemented more than once.
testTypes(
"/** @interface \n" +
" * @template T\n" +
" */\n" +
"function Foo() {}\n" +
"/** @constructor \n" +
" * @implements {Foo<?>}\n" +
" * @implements {Foo}\n" +
" */\n" +
"function A() {}\n",
"Cannot @implement the same interface more than once\n" +
"Repeated interface: Foo");
}
public void testBadImplementsDuplicateInterface2() throws Exception {
// verify that the same base (not templatized) interface cannot be
// @implemented more than once.
testTypes(
"/** @interface \n" +
" * @template T\n" +
" */\n" +
"function Foo() {}\n" +
"/** @constructor \n" +
" * @implements {Foo<string>}\n" +
" * @implements {Foo<number>}\n" +
" */\n" +
"function A() {}\n",
"Cannot @implement the same interface more than once\n" +
"Repeated interface: Foo");
}
public void testInterfaceAssignment1() throws Exception {
testTypes("/** @interface */var I = function() {};\n" +
"/** @constructor\n@implements {I} */var T = function() {};\n" +
"var t = new T();\n" +
"/** @type {!I} */var i = t;");
}
public void testInterfaceAssignment2() throws Exception {
testTypes("/** @interface */var I = function() {};\n" +
"/** @constructor */var T = function() {};\n" +
"var t = new T();\n" +
"/** @type {!I} */var i = t;",
"initializing variable\n" +
"found : T\n" +
"required: I");
}
public void testInterfaceAssignment3() throws Exception {
testTypes("/** @interface */var I = function() {};\n" +
"/** @constructor\n@implements {I} */var T = function() {};\n" +
"var t = new T();\n" +
"/** @type {I|number} */var i = t;");
}
public void testInterfaceAssignment4() throws Exception {
testTypes("/** @interface */var I1 = function() {};\n" +
"/** @interface */var I2 = function() {};\n" +
"/** @constructor\n@implements {I1} */var T = function() {};\n" +
"var t = new T();\n" +
"/** @type {I1|I2} */var i = t;");
}
public void testInterfaceAssignment5() throws Exception {
testTypes("/** @interface */var I1 = function() {};\n" +
"/** @interface */var I2 = function() {};\n" +
"/** @constructor\n@implements {I1}\n@implements {I2}*/" +
"var T = function() {};\n" +
"var t = new T();\n" +
"/** @type {I1} */var i1 = t;\n" +
"/** @type {I2} */var i2 = t;\n");
}
public void testInterfaceAssignment6() throws Exception {
testTypes("/** @interface */var I1 = function() {};\n" +
"/** @interface */var I2 = function() {};\n" +
"/** @constructor\n@implements {I1} */var T = function() {};\n" +
"/** @type {!I1} */var i1 = new T();\n" +
"/** @type {!I2} */var i2 = i1;\n",
"initializing variable\n" +
"found : I1\n" +
"required: I2");
}
public void testInterfaceAssignment7() throws Exception {
testTypes("/** @interface */var I1 = function() {};\n" +
"/** @interface\n@extends {I1}*/var I2 = function() {};\n" +
"/** @constructor\n@implements {I2}*/var T = function() {};\n" +
"var t = new T();\n" +
"/** @type {I1} */var i1 = t;\n" +
"/** @type {I2} */var i2 = t;\n" +
"i1 = i2;\n");
}
public void testInterfaceAssignment8() throws Exception {
testTypes("/** @interface */var I = function() {};\n" +
"/** @type {I} */var i;\n" +
"/** @type {Object} */var o = i;\n" +
"new Object().prototype = i.prototype;");
}
public void testInterfaceAssignment9() throws Exception {
testTypes("/** @interface */var I = function() {};\n" +
"/** @return {I?} */function f() { return null; }\n" +
"/** @type {!I} */var i = f();\n",
"initializing variable\n" +
"found : (I|null)\n" +
"required: I");
}
public void testInterfaceAssignment10() throws Exception {
testTypes("/** @interface */var I1 = function() {};\n" +
"/** @interface */var I2 = function() {};\n" +
"/** @constructor\n@implements {I2} */var T = function() {};\n" +
"/** @return {!I1|!I2} */function f() { return new T(); }\n" +
"/** @type {!I1} */var i1 = f();\n",
"initializing variable\n" +
"found : (I1|I2)\n" +
"required: I1");
}
public void testInterfaceAssignment11() throws Exception {
testTypes("/** @interface */var I1 = function() {};\n" +
"/** @interface */var I2 = function() {};\n" +
"/** @constructor */var T = function() {};\n" +
"/** @return {!I1|!I2|!T} */function f() { return new T(); }\n" +
"/** @type {!I1} */var i1 = f();\n",
"initializing variable\n" +
"found : (I1|I2|T)\n" +
"required: I1");
}
public void testInterfaceAssignment12() throws Exception {
testTypes("/** @interface */var I = function() {};\n" +
"/** @constructor\n@implements{I}*/var T1 = function() {};\n" +
"/** @constructor\n@extends {T1}*/var T2 = function() {};\n" +
"/** @return {I} */function f() { return new T2(); }");
}
public void testInterfaceAssignment13() throws Exception {
testTypes("/** @interface */var I = function() {};\n" +
"/** @constructor\n@implements {I}*/var T = function() {};\n" +
"/** @constructor */function Super() {};\n" +
"/** @return {I} */Super.prototype.foo = " +
"function() { return new T(); };\n" +
"/** @constructor\n@extends {Super} */function Sub() {}\n" +
"/** @override\n@return {T} */Sub.prototype.foo = " +
"function() { return new T(); };\n");
}
public void testGetprop1() throws Exception {
testTypes("/** @return {void}*/function foo(){foo().bar;}",
"No properties on this expression\n" +
"found : undefined\n" +
"required: Object");
}
public void testGetprop2() throws Exception {
testTypes("var x = null; x.alert();",
"No properties on this expression\n" +
"found : null\n" +
"required: Object");
}
public void testGetprop3() throws Exception {
testTypes(
"/** @constructor */ " +
"function Foo() { /** @type {?Object} */ this.x = null; }" +
"Foo.prototype.initX = function() { this.x = {foo: 1}; };" +
"Foo.prototype.bar = function() {" +
" if (this.x == null) { this.initX(); alert(this.x.foo); }" +
"};");
}
public void testGetprop4() throws Exception {
testTypes("var x = null; x.prop = 3;",
"No properties on this expression\n" +
"found : null\n" +
"required: Object");
}
public void testSetprop1() throws Exception {
// Create property on struct in the constructor
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() { this.x = 123; }");
}
public void testSetprop2() throws Exception {
// Create property on struct outside the constructor
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"(new Foo()).x = 123;",
ILLEGAL_PROPERTY_CREATION_MESSAGE);
}
public void testSetprop3() throws Exception {
// Create property on struct outside the constructor
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"(function() { (new Foo()).x = 123; })();",
ILLEGAL_PROPERTY_CREATION_MESSAGE);
}
public void testSetprop4() throws Exception {
// Assign to existing property of struct outside the constructor
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() { this.x = 123; }\n" +
"(new Foo()).x = \"asdf\";");
}
public void testSetprop5() throws Exception {
// Create a property on union that includes a struct
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"(true ? new Foo() : {}).x = 123;",
ILLEGAL_PROPERTY_CREATION_MESSAGE);
}
public void testSetprop6() throws Exception {
// Create property on struct in another constructor
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"/**\n" +
" * @constructor\n" +
" * @param{Foo} f\n" +
" */\n" +
"function Bar(f) { f.x = 123; }",
ILLEGAL_PROPERTY_CREATION_MESSAGE);
}
public void testSetprop7() throws Exception {
//Bug b/c we require THIS when creating properties on structs for simplicity
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {\n" +
" var t = this;\n" +
" t.x = 123;\n" +
"}",
ILLEGAL_PROPERTY_CREATION_MESSAGE);
}
public void testSetprop8() throws Exception {
// Create property on struct using DEC
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"(new Foo()).x--;",
new String[] {
ILLEGAL_PROPERTY_CREATION_MESSAGE,
"Property x never defined on Foo"
});
}
public void testSetprop9() throws Exception {
// Create property on struct using ASSIGN_ADD
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"(new Foo()).x += 123;",
new String[] {
ILLEGAL_PROPERTY_CREATION_MESSAGE,
"Property x never defined on Foo"
});
}
public void testSetprop10() throws Exception {
// Create property on object literal that is a struct
testTypes("/** \n" +
" * @constructor \n" +
" * @struct \n" +
" */ \n" +
"function Square(side) { \n" +
" this.side = side; \n" +
"} \n" +
"Square.prototype = /** @struct */ {\n" +
" area: function() { return this.side * this.side; }\n" +
"};\n" +
"Square.prototype.id = function(x) { return x; };");
}
public void testSetprop11() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"/** @constructor */\n" +
"function Bar() {}\n" +
"Bar.prototype = new Foo();\n" +
"Bar.prototype.someprop = 123;");
}
public void testSetprop12() throws Exception {
// Create property on a constructor of structs (which isn't itself a struct)
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"Foo.someprop = 123;");
}
public void testSetprop13() throws Exception {
// Create static property on struct
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Parent() {}\n" +
"/**\n" +
" * @constructor\n" +
" * @extends {Parent}\n" +
" */\n" +
"function Kid() {}\n" +
"Kid.prototype.foo = 123;\n" +
"var x = (new Kid()).foo;");
}
public void testSetprop14() throws Exception {
// Create static property on struct
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Top() {}\n" +
"/**\n" +
" * @constructor\n" +
" * @extends {Top}\n" +
" */\n" +
"function Mid() {}\n" +
"/** blah blah */\n" +
"Mid.prototype.foo = function() { return 1; };\n" +
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" * @extends {Mid}\n" +
" */\n" +
"function Bottom() {}\n" +
"/** @override */\n" +
"Bottom.prototype.foo = function() { return 3; };");
}
public void testSetprop15() throws Exception {
// Create static property on struct
testTypes(
"/** @interface */\n" +
"function Peelable() {};\n" +
"/** @return {undefined} */\n" +
"Peelable.prototype.peel;\n" +
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Fruit() {};\n" +
"/**\n" +
" * @constructor\n" +
" * @extends {Fruit}\n" +
" * @implements {Peelable}\n" +
" */\n" +
"function Banana() { };\n" +
"function f() {};\n" +
"/** @override */\n" +
"Banana.prototype.peel = f;");
}
public void testGetpropDict1() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @dict\n" +
" */" +
"function Dict1(){ this['prop'] = 123; }" +
"/** @param{Dict1} x */" +
"function takesDict(x) { return x.prop; }",
"Cannot do '.' access on a dict");
}
public void testGetpropDict2() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @dict\n" +
" */" +
"function Dict1(){ this['prop'] = 123; }" +
"/**\n" +
" * @constructor\n" +
" * @extends {Dict1}\n" +
" */" +
"function Dict1kid(){ this['prop'] = 123; }" +
"/** @param{Dict1kid} x */" +
"function takesDict(x) { return x.prop; }",
"Cannot do '.' access on a dict");
}
public void testGetpropDict3() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @dict\n" +
" */" +
"function Dict1() { this['prop'] = 123; }" +
"/** @constructor */" +
"function NonDict() { this.prop = 321; }" +
"/** @param{(NonDict|Dict1)} x */" +
"function takesDict(x) { return x.prop; }",
"Cannot do '.' access on a dict");
}
public void testGetpropDict4() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @dict\n" +
" */" +
"function Dict1() { this['prop'] = 123; }" +
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */" +
"function Struct1() { this.prop = 123; }" +
"/** @param{(Struct1|Dict1)} x */" +
"function takesNothing(x) { return x.prop; }",
"Cannot do '.' access on a dict");
}
public void testGetpropDict5() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @dict\n" +
" */" +
"function Dict1(){ this.prop = 123; }",
"Cannot do '.' access on a dict");
}
public void testGetpropDict6() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @dict\n" +
" */\n" +
"function Foo() {}\n" +
"function Bar() {}\n" +
"Bar.prototype = new Foo();\n" +
"Bar.prototype.someprop = 123;\n",
"Cannot do '.' access on a dict");
}
public void testGetpropDict7() throws Exception {
testTypes("(/** @dict */ {'x': 123}).x = 321;",
"Cannot do '.' access on a dict");
}
public void testGetelemStruct1() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */" +
"function Struct1(){ this.prop = 123; }" +
"/** @param{Struct1} x */" +
"function takesStruct(x) {" +
" var z = x;" +
" return z['prop'];" +
"}",
"Cannot do '[]' access on a struct");
}
public void testGetelemStruct2() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */" +
"function Struct1(){ this.prop = 123; }" +
"/**\n" +
" * @constructor\n" +
" * @extends {Struct1}" +
" */" +
"function Struct1kid(){ this.prop = 123; }" +
"/** @param{Struct1kid} x */" +
"function takesStruct2(x) { return x['prop']; }",
"Cannot do '[]' access on a struct");
}
public void testGetelemStruct3() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */" +
"function Struct1(){ this.prop = 123; }" +
"/**\n" +
" * @constructor\n" +
" * @extends {Struct1}\n" +
" */" +
"function Struct1kid(){ this.prop = 123; }" +
"var x = (new Struct1kid())['prop'];",
"Cannot do '[]' access on a struct");
}
public void testGetelemStruct4() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */" +
"function Struct1() { this.prop = 123; }" +
"/** @constructor */" +
"function NonStruct() { this.prop = 321; }" +
"/** @param{(NonStruct|Struct1)} x */" +
"function takesStruct(x) { return x['prop']; }",
"Cannot do '[]' access on a struct");
}
public void testGetelemStruct5() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */" +
"function Struct1() { this.prop = 123; }" +
"/**\n" +
" * @constructor\n" +
" * @dict\n" +
" */" +
"function Dict1() { this['prop'] = 123; }" +
"/** @param{(Struct1|Dict1)} x */" +
"function takesNothing(x) { return x['prop']; }",
"Cannot do '[]' access on a struct");
}
public void testGetelemStruct6() throws Exception {
// By casting Bar to Foo, the illegal bracket access is not detected
testTypes("/** @interface */ function Foo(){}\n" +
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" * @implements {Foo}\n" +
" */" +
"function Bar(){ this.x = 123; }\n" +
"var z = /** @type {Foo} */(new Bar())['x'];");
}
public void testGetelemStruct7() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"/** @constructor */\n" +
"function Bar() {}\n" +
"Bar.prototype = new Foo();\n" +
"Bar.prototype['someprop'] = 123;\n",
"Cannot do '[]' access on a struct");
}
public void testGetelemStruct_noErrorForSettingWellKnownSymbol() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"Foo.prototype[Symbol.iterator] = 123;\n");
}
public void testGetelemStruct_noErrorForGettingWellKnownSymbol() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {}\n" +
"/** @param {!Foo} foo */\n" +
"function getIterator(foo) { return foo[Symbol.iterator](); }\n");
}
public void testInOnStruct() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */" +
"function Foo() {}\n" +
"if ('prop' in (new Foo())) {}",
"Cannot use the IN operator with structs");
}
public void testForinOnStruct() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */" +
"function Foo() {}\n" +
"for (var prop in (new Foo())) {}",
"Cannot use the IN operator with structs");
}
public void testArrayLegacyAccess1() throws Exception {
String externs = DEFAULT_EXTERNS.replace(
" * @implements {IArrayLike<T>}",
LINE_JOINER.join(
" * @implements {IObject<?, T>} ",
" * @implements {IArrayLike<T>} "));
Preconditions.checkState(DEFAULT_EXTERNS.length() != externs.length());
testTypesWithExterns(externs, "var a = []; var b = a['hi'];");
}
public void testIArrayLikeAccess1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** ",
" * @param {!IArrayLike<T>} x",
" * @return {T}",
" * @template T",
"*/",
"function f(x) { return x[0]; }",
"function g(/** !Array<string> */ x) {",
" var /** null */ y = f(x);",
"}"),
"initializing variable\n"
+ "found : string\n"
+ "required: null");
}
public void testIArrayLikeAccess2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** ",
" * @param {!IArrayLike<T>} x",
" * @return {T}",
" * @template T",
"*/",
"function f(x) { return x[0]; }",
"function g(/** !IArrayLike<string> */ x) {",
" var /** null */ y = f(x);",
"}"),
"initializing variable\n"
+ "found : string\n"
+ "required: null");
}
public void testArrayAccess1() throws Exception {
testTypes("var a = []; var b = a['hi'];",
"restricted index type\n" +
"found : string\n" +
"required: number");
}
public void testArrayAccess2() throws Exception {
testTypes("var a = []; var b = a[[1,2]];",
"restricted index type\n" +
"found : Array\n" +
"required: number");
}
public void testArrayAccess3() throws Exception {
testTypes("var bar = [];" +
"/** @return {void} */function baz(){};" +
"var foo = bar[baz()];",
"restricted index type\n" +
"found : undefined\n" +
"required: number");
}
public void testArrayAccess4() throws Exception {
testTypes("/**@return {!Array}*/function foo(){};var bar = foo()[foo()];",
"restricted index type\n" +
"found : Array\n" +
"required: number");
}
public void testArrayAccess6() throws Exception {
testTypes("var bar = null[1];",
"only arrays or objects can be accessed\n" +
"found : null\n" +
"required: Object");
}
public void testArrayAccess7() throws Exception {
testTypes("var bar = void 0; bar[0];",
"only arrays or objects can be accessed\n" +
"found : undefined\n" +
"required: Object");
}
public void testArrayAccess8() throws Exception {
// Verifies that we don't emit two warnings, because
// the var has been dereferenced after the first one.
testTypes("var bar = void 0; bar[0]; bar[1];",
"only arrays or objects can be accessed\n" +
"found : undefined\n" +
"required: Object");
}
public void testArrayAccess9() throws Exception {
testTypes("/** @return {?Array} */ function f() { return []; }" +
"f()[{}]",
"restricted index type\n" +
"found : {}\n" +
"required: number");
}
public void testPropAccess() throws Exception {
testTypes("/** @param {*} x */var f = function(x) {\n" +
"var o = String(x);\n" +
"if (typeof o['a'] != 'undefined') { return o['a']; }\n" +
"return null;\n" +
"};");
}
public void testPropAccess2() throws Exception {
testTypes("var bar = void 0; bar.baz;",
"No properties on this expression\n" +
"found : undefined\n" +
"required: Object");
}
public void testPropAccess3() throws Exception {
// Verifies that we don't emit two warnings, because
// the var has been dereferenced after the first one.
testTypes("var bar = void 0; bar.baz; bar.bax;",
"No properties on this expression\n" +
"found : undefined\n" +
"required: Object");
}
public void testPropAccess4() throws Exception {
testTypes("/** @param {*} x */ function f(x) { return x['hi']; }");
}
public void testSwitchCase1() throws Exception {
testTypes(
"/**@type {number}*/var a;" + "/**@type {string}*/var b;" + "switch(a){case b:;}",
"case expression doesn't match switch\n" + "found : string\n" + "required: number");
}
public void testSwitchCase2() throws Exception {
testTypes("var a = null; switch (typeof a) { case 'foo': }");
}
public void testVar1() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @type {(string|null)} */var a = null");
assertTypeEquals(createUnionType(STRING_TYPE, NULL_TYPE),
p.scope.getVar("a").getType());
}
public void testVar2() throws Exception {
testTypes("/** @type {Function} */ var a = function(){}");
}
public void testVar3() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = 3;");
assertTypeEquals(NUMBER_TYPE, p.scope.getVar("a").getType());
}
public void testVar4() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var a = 3; a = 'string';");
assertTypeEquals(createUnionType(STRING_TYPE, NUMBER_TYPE),
p.scope.getVar("a").getType());
}
public void testVar5() throws Exception {
testTypes("var goog = {};" +
"/** @type {string} */goog.foo = 'hello';" +
"/** @type {number} */var a = goog.foo;",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testVar6() throws Exception {
testTypes(
"function f() {" +
" return function() {" +
" /** @type {!Date} */" +
" var a = 7;" +
" };" +
"}",
"initializing variable\n" +
"found : number\n" +
"required: Date");
}
public void testVar7() throws Exception {
testTypes("/** @type {number} */var a, b;",
"declaration of multiple variables with shared type information");
}
public void testVar8() throws Exception {
testTypes("var a, b;");
}
public void testVar9() throws Exception {
testTypes("/** @enum */var a;",
"enum initializer must be an object literal or an enum");
}
public void testVar10() throws Exception {
testTypes("/** @type {!Number} */var foo = 'abc';",
"initializing variable\n" +
"found : string\n" +
"required: Number");
}
public void testVar11() throws Exception {
testTypes("var /** @type {!Date} */foo = 'abc';",
"initializing variable\n" +
"found : string\n" +
"required: Date");
}
public void testVar12() throws Exception {
testTypes("var /** @type {!Date} */foo = 'abc', " +
"/** @type {!RegExp} */bar = 5;",
new String[] {
"initializing variable\n" +
"found : string\n" +
"required: Date",
"initializing variable\n" +
"found : number\n" +
"required: RegExp"});
}
public void testVar13() throws Exception {
// this caused an NPE
testTypes("var /** @type {number} */a,a;");
}
public void testVar14() throws Exception {
testTypes("/** @return {number} */ function f() { var x; return x; }",
"inconsistent return type\n" +
"found : undefined\n" +
"required: number");
}
public void testVar15() throws Exception {
testTypes("/** @return {number} */" +
"function f() { var x = x || {}; return x; }",
"inconsistent return type\n" +
"found : {}\n" +
"required: number");
}
public void testAssign1() throws Exception {
testTypes("var goog = {};" +
"/** @type {number} */goog.foo = 'hello';",
"assignment to property foo of goog\n" +
"found : string\n" +
"required: number");
}
public void testAssign2() throws Exception {
testTypes("var goog = {};" +
"/** @type {number} */goog.foo = 3;" +
"goog.foo = 'hello';",
"assignment to property foo of goog\n" +
"found : string\n" +
"required: number");
}
public void testAssign3() throws Exception {
testTypes("var goog = {};" +
"/** @type {number} */goog.foo = 3;" +
"goog.foo = 4;");
}
public void testAssign4() throws Exception {
testTypes("var goog = {};" +
"goog.foo = 3;" +
"goog.foo = 'hello';");
}
public void testAssignInference() throws Exception {
testTypes(
"/**" +
" * @param {Array} x" +
" * @return {number}" +
" */" +
"function f(x) {" +
" var y = null;" +
" y = x[0];" +
" if (y == null) { return 4; } else { return 6; }" +
"}");
}
public void testOr1() throws Exception {
testTypes("/** @type {number} */var a;" +
"/** @type {number} */var b;" +
"a + b || undefined;");
}
public void testOr2() throws Exception {
testTypes("/** @type {number} */var a;" +
"/** @type {number} */var b;" +
"/** @type {number} */var c = a + b || undefined;",
"initializing variable\n" +
"found : (number|undefined)\n" +
"required: number");
}
public void testOr3() throws Exception {
testTypes("/** @type {(number|undefined)} */var a;" + "/** @type {number} */var c = a || 3;");
}
/**
* Test that type inference continues with the right side,
* when no short-circuiting is possible.
* See bugid 1205387 for more details.
*/
public void testOr4() throws Exception {
testTypes("/**@type {number} */var x;x=null || \"a\";",
"assignment\n" +
"found : string\n" +
"required: number");
}
/**
* @see #testOr4()
*/
public void testOr5() throws Exception {
testTypes("/**@type {number} */var x;x=undefined || \"a\";",
"assignment\n" +
"found : string\n" +
"required: number");
}
public void testAnd1() throws Exception {
testTypes(
"/** @type {number} */var a;" + "/** @type {number} */var b;" + "a + b && undefined;");
}
public void testAnd2() throws Exception {
testTypes(
"/** @type {number} */var a;"
+ "/** @type {number} */var b;"
+ "/** @type {number} */var c = a + b && undefined;",
"initializing variable\n" + "found : (number|undefined)\n" + "required: number");
}
public void testAnd3() throws Exception {
testTypes(
"/** @type {(!Array|undefined)} */var a;"
+ "/** @type {number} */var c = a && undefined;",
"initializing variable\n" + "found : undefined\n" + "required: number");
}
public void testAnd4() throws Exception {
testTypes(
"/** @param {number} x */function f(x){};\n"
+ "/** @type {null} */var x; /** @type {number?} */var y;\n"
+ "if (x && y) { f(y) }");
}
public void testAnd5() throws Exception {
testTypes("/** @param {number} x\n@param {string} y*/function f(x,y){};\n" +
"/** @type {number?} */var x; /** @type {string?} */var y;\n" +
"if (x && y) { f(x, y) }");
}
public void testAnd6() throws Exception {
testTypes("/** @param {number} x */function f(x){};\n" +
"/** @type {number|undefined} */var x;\n" +
"if (x && f(x)) { f(x) }");
}
public void testAnd7() throws Exception {
// TODO(user): a deterministic warning should be generated for this
// case since x && x is always false. The implementation of this requires
// a more precise handling of a null value within a variable's type.
// Currently, a null value defaults to ? which passes every check.
testTypes("/** @type {null} */var x; if (x && x) {}");
}
public void testAnd8() throws Exception {
testTypes(
"function f(/** (null | number | string) */ x) {\n" +
" (x && (typeof x === 'number')) && takesNum(x);\n" +
"}\n" +
"function takesNum(/** number */ n) {}");
}
public void testAnd9() throws Exception {
testTypes(
"function f(/** (number|string|null) */ x) {\n" +
" if (x && typeof x === 'number') {\n" +
" takesNum(x);\n" +
" }\n" +
"}\n" +
"function takesNum(/** number */ x) {}");
}
public void testAnd10() throws Exception {
testTypes(
"function f(/** (null | number | string) */ x) {\n" +
" (x && (typeof x === 'string')) && takesNum(x);\n" +
"}\n" +
"function takesNum(/** number */ n) {}",
"actual parameter 1 of takesNum does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testHook() throws Exception {
testTypes("/**@return {void}*/function foo(){ var x=foo()?a:b; }");
}
public void testHookRestrictsType1() throws Exception {
testTypes("/** @return {(string|null)} */" +
"function f() { return null;}" +
"/** @type {(string|null)} */ var a = f();" +
"/** @type {string} */" +
"var b = a ? a : 'default';");
}
public void testHookRestrictsType2() throws Exception {
testTypes("/** @type {String} */" +
"var a = null;" +
"/** @type {null} */" +
"var b = a ? null : a;");
}
public void testHookRestrictsType3() throws Exception {
testTypes("/** @type {String} */" +
"var a;" +
"/** @type {null} */" +
"var b = (!a) ? a : null;");
}
public void testHookRestrictsType4() throws Exception {
testTypes("/** @type {(boolean|undefined)} */" +
"var a;" +
"/** @type {boolean} */" +
"var b = a != null ? a : true;");
}
public void testHookRestrictsType5() throws Exception {
testTypes("/** @type {(boolean|undefined)} */" +
"var a;" +
"/** @type {(undefined)} */" +
"var b = a == null ? a : undefined;");
}
public void testHookRestrictsType6() throws Exception {
testTypes("/** @type {(number|null|undefined)} */" +
"var a;" +
"/** @type {number} */" +
"var b = a == null ? 5 : a;");
}
public void testHookRestrictsType7() throws Exception {
testTypes("/** @type {(number|null|undefined)} */" +
"var a;" +
"/** @type {number} */" +
"var b = a == undefined ? 5 : a;");
}
public void testWhileRestrictsType1() throws Exception {
testTypes("/** @param {null} x */ function g(x) {}" +
"/** @param {number?} x */\n" +
"function f(x) {\n" +
"while (x) {\n" +
"if (g(x)) { x = 1; }\n" +
"x = x-1;\n}\n}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : number\n" +
"required: null");
}
public void testWhileRestrictsType2() throws Exception {
testTypes("/** @param {number?} x\n@return {number}*/\n" +
"function f(x) {\n/** @type {number} */var y = 0;" +
"while (x) {\n" +
"y = x;\n" +
"x = x-1;\n}\n" +
"return y;}");
}
public void testHigherOrderFunctions1() throws Exception {
testTypes(
"/** @type {function(number)} */var f;" +
"f(true);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testHigherOrderFunctions2() throws Exception {
testTypes(
"/** @type {function():!Date} */var f;" + "/** @type {boolean} */var a = f();",
"initializing variable\n" + "found : Date\n" + "required: boolean");
}
public void testHigherOrderFunctions3() throws Exception {
testTypes(
"/** @type {function(this:Error):Date} */var f; new f",
"cannot instantiate non-constructor");
}
public void testHigherOrderFunctions4() throws Exception {
testTypes(
"/** @type {function(this:Error, ...number):Date} */var f; new f",
"cannot instantiate non-constructor");
}
public void testHigherOrderFunctions5() throws Exception {
testTypes(
"/** @param {number} x */ function g(x) {}" +
"/** @type {function(new:Error, ...number):Date} */ var f;" +
"g(new f());",
"actual parameter 1 of g does not match formal parameter\n" +
"found : Error\n" +
"required: number");
}
public void testConstructorAlias1() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {};" +
"/** @type {number} */ Foo.prototype.bar = 3;" +
"/** @constructor */ var FooAlias = Foo;" +
"/** @return {string} */ function foo() { " +
" return (new FooAlias()).bar; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testConstructorAlias2() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {};" +
"/** @constructor */ var FooAlias = Foo;" +
"/** @type {number} */ FooAlias.prototype.bar = 3;" +
"/** @return {string} */ function foo() { " +
" return (new Foo()).bar; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testConstructorAlias3() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {};" +
"/** @type {number} */ Foo.prototype.bar = 3;" +
"/** @constructor */ var FooAlias = Foo;" +
"/** @return {string} */ function foo() { " +
" return (new FooAlias()).bar; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testConstructorAlias4() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {};" +
"var FooAlias = Foo;" +
"/** @type {number} */ FooAlias.prototype.bar = 3;" +
"/** @return {string} */ function foo() { " +
" return (new Foo()).bar; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testConstructorAlias5() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {};" +
"/** @constructor */ var FooAlias = Foo;" +
"/** @return {FooAlias} */ function foo() { " +
" return new Foo(); }");
}
public void testConstructorAlias6() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {};" +
"/** @constructor */ var FooAlias = Foo;" +
"/** @return {Foo} */ function foo() { " +
" return new FooAlias(); }");
}
public void testConstructorAlias7() throws Exception {
testTypes(
"var goog = {};" +
"/** @constructor */ goog.Foo = function() {};" +
"/** @constructor */ goog.FooAlias = goog.Foo;" +
"/** @return {number} */ function foo() { " +
" return new goog.FooAlias(); }",
"inconsistent return type\n" +
"found : goog.Foo\n" +
"required: number");
}
public void testConstructorAlias8() throws Exception {
testTypes(
"var goog = {};" +
"/**\n * @param {number} x \n * @constructor */ " +
"goog.Foo = function(x) {};" +
"/**\n * @param {number} x \n * @constructor */ " +
"goog.FooAlias = goog.Foo;" +
"/** @return {number} */ function foo() { " +
" return new goog.FooAlias(1); }",
"inconsistent return type\n" +
"found : goog.Foo\n" +
"required: number");
}
public void testConstructorAlias9() throws Exception {
testTypes(
"var goog = {};" +
"/**\n * @param {number} x \n * @constructor */ " +
"goog.Foo = function(x) {};" +
"/** @constructor */ goog.FooAlias = goog.Foo;" +
"/** @return {number} */ function foo() { " +
" return new goog.FooAlias(1); }",
"inconsistent return type\n" +
"found : goog.Foo\n" +
"required: number");
}
public void testConstructorAlias10() throws Exception {
testTypes(
"/**\n * @param {number} x \n * @constructor */ " +
"var Foo = function(x) {};" +
"/** @constructor */ var FooAlias = Foo;" +
"/** @return {number} */ function foo() { " +
" return new FooAlias(1); }",
"inconsistent return type\n" +
"found : Foo\n" +
"required: number");
}
public void testConstructorAlias11() throws Exception {
testTypes(
"/**\n * @param {number} x \n * @constructor */ " +
"var Foo = function(x) {};" +
"/** @const */ var FooAlias = Foo;" +
"/** @const */ var FooAlias2 = FooAlias;" +
"/** @return {FooAlias2} */ function foo() { " +
" return 1; }",
"inconsistent return type\n" +
"found : number\n" +
"required: (FooAlias2|null)");
}
public void testClosure1() throws Exception {
testClosureTypes(
CLOSURE_DEFS
+ "/** @type {string|undefined} */var a;"
+ "/** @type {string} */"
+ "var b = goog.isDef(a) ? a : 'default';",
null);
}
public void testClosure2() throws Exception {
testClosureTypes(
CLOSURE_DEFS
+ "/** @type {string?} */var a;"
+ "/** @type {string} */"
+ "var b = goog.isNull(a) ? 'default' : a;",
null);
}
public void testClosure3() throws Exception {
testClosureTypes(
CLOSURE_DEFS
+ "/** @type {string|null|undefined} */var a;"
+ "/** @type {string} */"
+ "var b = goog.isDefAndNotNull(a) ? a : 'default';",
null);
}
public void testClosure4() throws Exception {
testClosureTypes(
CLOSURE_DEFS
+ "/** @type {string|undefined} */var a;"
+ "/** @type {string} */"
+ "var b = !goog.isDef(a) ? 'default' : a;",
null);
}
public void testClosure5() throws Exception {
testClosureTypes(
CLOSURE_DEFS
+ "/** @type {string?} */var a;"
+ "/** @type {string} */"
+ "var b = !goog.isNull(a) ? a : 'default';",
null);
}
public void testClosure6() throws Exception {
testClosureTypes(
CLOSURE_DEFS
+ "/** @type {string|null|undefined} */var a;"
+ "/** @type {string} */"
+ "var b = !goog.isDefAndNotNull(a) ? 'default' : a;",
null);
}
public void testClosure7() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @type {string|null|undefined} */ var a = foo();" +
"/** @type {number} */" +
"var b = goog.asserts.assert(a);",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testReturn1() throws Exception {
testTypes("/**@return {void}*/function foo(){ return 3; }",
"inconsistent return type\n" +
"found : number\n" +
"required: undefined");
}
public void testReturn2() throws Exception {
testTypes("/**@return {!Number}*/function foo(){ return; }",
"inconsistent return type\n" +
"found : undefined\n" +
"required: Number");
}
public void testReturn3() throws Exception {
testTypes("/**@return {!Number}*/function foo(){ return 'abc'; }",
"inconsistent return type\n" +
"found : string\n" +
"required: Number");
}
public void testReturn4() throws Exception {
testTypes("/**@return {!Number}\n*/\n function a(){return new Array();}",
"inconsistent return type\n" +
"found : Array\n" +
"required: Number");
}
public void testReturn5() throws Exception {
testTypes("/** @param {number} n\n" +
"@constructor */function n(n){return};");
}
public void testReturn6() throws Exception {
testTypes(
"/** @param {number} opt_a\n@return {string} */" +
"function a(opt_a) { return opt_a }",
"inconsistent return type\n" +
"found : (number|undefined)\n" +
"required: string");
}
public void testReturn7() throws Exception {
testTypes("/** @constructor */var A = function() {};\n" +
"/** @constructor */var B = function() {};\n" +
"/** @return {!B} */A.f = function() { return 1; };",
"inconsistent return type\n" +
"found : number\n" +
"required: B");
}
public void testReturn8() throws Exception {
testTypes("/** @constructor */var A = function() {};\n" +
"/** @constructor */var B = function() {};\n" +
"/** @return {!B} */A.prototype.f = function() { return 1; };",
"inconsistent return type\n" +
"found : number\n" +
"required: B");
}
public void testInferredReturn1() throws Exception {
testTypes(
"function f() {} /** @param {number} x */ function g(x) {}" +
"g(f());",
"actual parameter 1 of g does not match formal parameter\n" +
"found : undefined\n" +
"required: number");
}
public void testInferredReturn2() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() {}; " +
"/** @param {number} x */ function g(x) {}" +
"g((new Foo()).bar());",
"actual parameter 1 of g does not match formal parameter\n" +
"found : undefined\n" +
"required: number");
}
public void testInferredReturn3() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() {}; " +
"/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
"/** @return {number} \n * @override */ " +
"SubFoo.prototype.bar = function() { return 3; }; ",
"mismatch of the bar property type and the type of the property " +
"it overrides from superclass Foo\n" +
"original: function (this:Foo): undefined\n" +
"override: function (this:SubFoo): number");
}
public void testInferredReturn4() throws Exception {
// By design, this throws a warning. if you want global x to be
// defined to some other type of function, then you need to declare it
// as a greater type.
testTypes(
"var x = function() {};" +
"x = /** @type {function(): number} */ (function() { return 3; });",
"assignment\n" +
"found : function (): number\n" +
"required: function (): undefined");
}
public void testInferredReturn5() throws Exception {
// If x is local, then the function type is not declared.
testTypes(
"/** @return {string} */" +
"function f() {" +
" var x = function() {};" +
" x = /** @type {function(): number} */ (function() { return 3; });" +
" return x();" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testInferredReturn6() throws Exception {
testTypes(
"/** @return {string} */" +
"function f() {" +
" var x = function() {};" +
" if (f()) " +
" x = /** @type {function(): number} */ " +
" (function() { return 3; });" +
" return x();" +
"}",
"inconsistent return type\n" +
"found : (number|undefined)\n" +
"required: string");
}
public void testInferredReturn7() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @param {number} x */ Foo.prototype.bar = function(x) {};" +
"Foo.prototype.bar = function(x) { return 3; };",
"inconsistent return type\n" +
"found : number\n" +
"required: undefined");
}
public void testInferredReturn8() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}"
+ "/** @param {number} x */ Foo.prototype.bar = function(x) {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "/** @override @param {number} x */ SubFoo.prototype.bar = "
+ " function(x) { return 3; }",
"inconsistent return type\n" + "found : number\n" + "required: undefined");
}
public void testInferredParam1() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @param {number} x */ Foo.prototype.bar = function(x) {};" +
"/** @param {string} x */ function f(x) {}" +
"Foo.prototype.bar = function(y) { f(y); };",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testInferredParam2() throws Exception {
testTypes(
"/** @param {string} x */ function f(x) {}"
+ "/** @constructor */ function Foo() {}"
+ "/** @param {number} x */ Foo.prototype.bar = function(x) {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "/** @override @return {void} */ SubFoo.prototype.bar = "
+ " function(x) { f(x); }",
"actual parameter 1 of f does not match formal parameter\n"
+ "found : number\n"
+ "required: string");
}
public void testInferredParam3() throws Exception {
testTypes(
"/** @param {string} x */ function f(x) {}"
+ "/** @constructor */ function Foo() {}"
+ "/** @param {number=} x */ Foo.prototype.bar = function(x) {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "/** @override @return {void} */ SubFoo.prototype.bar = "
+ " function(x) { f(x); }; (new SubFoo()).bar();",
"actual parameter 1 of f does not match formal parameter\n"
+ "found : (number|undefined)\n"
+ "required: string");
}
public void testInferredParam4() throws Exception {
testTypes(
"/** @param {string} x */ function f(x) {}"
+ "/** @constructor */ function Foo() {}"
+ "/** @param {...number} x */ Foo.prototype.bar = function(x) {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "/** @override @return {void} */ SubFoo.prototype.bar = "
+ " function(x) { f(x); }; (new SubFoo()).bar();",
"actual parameter 1 of f does not match formal parameter\n"
+ "found : (number|undefined)\n"
+ "required: string");
}
public void testInferredParam5() throws Exception {
testTypes(
"/** @param {string} x */ function f(x) {}"
+ "/** @constructor */ function Foo() {}"
+ "/** @param {...number} x */ Foo.prototype.bar = function(x) {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "/** @override @param {number=} x \n * @param {...number} y */ "
+ "SubFoo.prototype.bar = "
+ " function(x, y) { f(x); }; (new SubFoo()).bar();",
"actual parameter 1 of f does not match formal parameter\n"
+ "found : (number|undefined)\n"
+ "required: string");
}
public void testInferredParam6() throws Exception {
testTypes(
"/** @param {string} x */ function f(x) {}"
+ "/** @constructor */ function Foo() {}"
+ "/** @param {number=} x */ Foo.prototype.bar = function(x) {};"
+ "/** @constructor \n * @extends {Foo} */ function SubFoo() {}"
+ "/** @override @param {number=} x \n * @param {number=} y */ "
+ "SubFoo.prototype.bar = "
+ " function(x, y) { f(y); };",
"actual parameter 1 of f does not match formal parameter\n"
+ "found : (number|undefined)\n"
+ "required: string");
}
public void testInferredParam7() throws Exception {
testTypes(
"/** @param {string} x */ function f(x) {}" +
"/** @type {function(number=,number=)} */" +
"var bar = function(x, y) { f(y); };",
"actual parameter 1 of f does not match formal parameter\n" +
"found : (number|undefined)\n" +
"required: string");
}
public void testOverriddenParams1() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @param {...?} var_args */" +
"Foo.prototype.bar = function(var_args) {};" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @param {number} x\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = function(x) {};");
}
public void testOverriddenParams2() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {function(...?)} */" +
"Foo.prototype.bar = function(var_args) {};" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @type {function(number)}\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = function(x) {};");
}
public void testOverriddenParams3() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @param {...number} var_args */" +
"Foo.prototype.bar = function(var_args) { };" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @param {number} x\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = function(x) {};",
"mismatch of the bar property type and the type of the " +
"property it overrides from superclass Foo\n" +
"original: function (this:Foo, ...number): undefined\n" +
"override: function (this:SubFoo, number): undefined");
}
public void testOverriddenParams4() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {function(...number)} */" +
"Foo.prototype.bar = function(var_args) {};" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @type {function(number)}\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = function(x) {};",
"mismatch of the bar property type and the type of the " +
"property it overrides from superclass Foo\n" +
"original: function (...number): ?\n" +
"override: function (number): ?");
}
public void testOverriddenParams5() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @param {number} x */" +
"Foo.prototype.bar = function(x) { };" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = function() {};" +
"(new SubFoo()).bar();");
}
public void testOverriddenParams6() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @param {number} x */" +
"Foo.prototype.bar = function(x) { };" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = function() {};" +
"(new SubFoo()).bar(true);",
"actual parameter 1 of SubFoo.prototype.bar " +
"does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testOverriddenParams7() throws Exception {
testTypes(
"/** @constructor\n * @template T */ function Foo() {}" +
"/** @param {T} x */" +
"Foo.prototype.bar = function(x) { };" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo<string>}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @param {number} x\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = function(x) {};",
"mismatch of the bar property type and the type of the " +
"property it overrides from superclass Foo\n" +
"original: function (this:Foo, string): undefined\n" +
"override: function (this:SubFoo, number): undefined");
}
public void testOverriddenReturn1() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @return {Object} */ Foo.prototype.bar = " +
" function() { return {}; };" +
"/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
"/** @return {SubFoo}\n * @override */ SubFoo.prototype.bar = " +
" function() { return new Foo(); }",
"inconsistent return type\n" +
"found : Foo\n" +
"required: (SubFoo|null)");
}
public void testOverriddenReturn2() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @return {SubFoo} */ Foo.prototype.bar = " +
" function() { return new SubFoo(); };" +
"/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
"/** @return {Foo} x\n * @override */ SubFoo.prototype.bar = " +
" function() { return new SubFoo(); }",
"mismatch of the bar property type and the type of the " +
"property it overrides from superclass Foo\n" +
"original: function (this:Foo): (SubFoo|null)\n" +
"override: function (this:SubFoo): (Foo|null)");
}
public void testOverriddenReturn3() throws Exception {
testTypes(
"/** @constructor \n * @template T */ function Foo() {}" +
"/** @return {T} */ Foo.prototype.bar = " +
" function() { return null; };" +
"/** @constructor \n * @extends {Foo<string>} */ function SubFoo() {}" +
"/** @override */ SubFoo.prototype.bar = " +
" function() { return 3; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testOverriddenReturn4() throws Exception {
testTypes(
"/** @constructor \n * @template T */ function Foo() {}" +
"/** @return {T} */ Foo.prototype.bar = " +
" function() { return null; };" +
"/** @constructor \n * @extends {Foo<string>} */ function SubFoo() {}" +
"/** @return {number}\n * @override */ SubFoo.prototype.bar = " +
" function() { return 3; }",
"mismatch of the bar property type and the type of the " +
"property it overrides from superclass Foo\n" +
"original: function (this:Foo): string\n" +
"override: function (this:SubFoo): number");
}
public void testThis1() throws Exception {
testTypes("var goog = {};" +
"/** @constructor */goog.A = function(){};" +
"/** @return {number} */" +
"goog.A.prototype.n = function() { return this };",
"inconsistent return type\n" +
"found : goog.A\n" +
"required: number");
}
public void testOverriddenProperty1() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {Object} */" +
"Foo.prototype.bar = {};" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @type {Array}\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = [];");
}
public void testOverriddenProperty2() throws Exception {
testTypes(
"/** @constructor */ function Foo() {" +
" /** @type {Object} */" +
" this.bar = {};" +
"}" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/**\n" +
" * @type {Array}\n" +
" * @override\n" +
" */" +
"SubFoo.prototype.bar = [];");
}
public void testOverriddenProperty3() throws Exception {
testTypes(
"/** @constructor */ function Foo() {" +
"}" +
"/** @type {string} */ Foo.prototype.data;" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/** @type {string|Object} \n @override */ " +
"SubFoo.prototype.data = null;",
"mismatch of the data property type and the type " +
"of the property it overrides from superclass Foo\n" +
"original: string\n" +
"override: (Object|null|string)");
}
public void testOverriddenProperty4() throws Exception {
// These properties aren't declared, so there should be no warning.
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = null;" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"SubFoo.prototype.bar = 3;");
}
public void testOverriddenProperty5() throws Exception {
// An override should be OK if the superclass property wasn't declared.
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = null;" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/** @override */ SubFoo.prototype.bar = 3;");
}
public void testOverriddenProperty6() throws Exception {
// The override keyword shouldn't be necessary if the subclass property
// is inferred.
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {?number} */ Foo.prototype.bar = null;" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"SubFoo.prototype.bar = 3;");
}
public void testThis2() throws Exception {
testTypes("var goog = {};" +
"/** @constructor */goog.A = function(){" +
" this.foo = null;" +
"};" +
"/** @return {number} */" +
"goog.A.prototype.n = function() { return this.foo };",
"inconsistent return type\n" +
"found : null\n" +
"required: number");
}
public void testThis3() throws Exception {
testTypes("var goog = {};" +
"/** @constructor */goog.A = function(){" +
" this.foo = null;" +
" this.foo = 5;" +
"};");
}
public void testThis4() throws Exception {
testTypes("var goog = {};" +
"/** @constructor */goog.A = function(){" +
" /** @type {string?} */this.foo = null;" +
"};" +
"/** @return {number} */goog.A.prototype.n = function() {" +
" return this.foo };",
"inconsistent return type\n" +
"found : (null|string)\n" +
"required: number");
}
public void testThis5() throws Exception {
testTypes(
"/** @this {Date}\n@return {number}*/function h() { return this }",
"inconsistent return type\n" + "found : Date\n" + "required: number");
}
public void testThis6() throws Exception {
testTypes("var goog = {};" +
"/** @constructor\n@return {!Date} */" +
"goog.A = function(){ return this };",
"inconsistent return type\n" +
"found : goog.A\n" +
"required: Date");
}
public void testThis7() throws Exception {
testTypes("/** @constructor */function A(){};" +
"/** @return {number} */A.prototype.n = function() { return this };",
"inconsistent return type\n" +
"found : A\n" +
"required: number");
}
public void testThis8() throws Exception {
testTypes("/** @constructor */function A(){" +
" /** @type {string?} */this.foo = null;" +
"};" +
"/** @return {number} */A.prototype.n = function() {" +
" return this.foo };",
"inconsistent return type\n" +
"found : (null|string)\n" +
"required: number");
}
public void testThis9() throws Exception {
// In A.bar, the type of {@code this} is unknown.
testTypes("/** @constructor */function A(){};" +
"A.prototype.foo = 3;" +
"/** @return {string} */ A.bar = function() { return this.foo; };");
}
public void testThis10() throws Exception {
// In A.bar, the type of {@code this} is inferred from the @this tag.
testTypes("/** @constructor */function A(){};" +
"A.prototype.foo = 3;" +
"/** @this {A}\n@return {string} */" +
"A.bar = function() { return this.foo; };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testThis11() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"/** @constructor */ function Ctor() {" +
" /** @this {Date} */" +
" this.method = function() {" +
" f(this);" +
" };" +
"}",
"actual parameter 1 of f does not match formal parameter\n" +
"found : Date\n" +
"required: number");
}
public void testThis12() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"/** @constructor */ function Ctor() {}" +
"Ctor.prototype['method'] = function() {" +
" f(this);" +
"}",
"actual parameter 1 of f does not match formal parameter\n" +
"found : Ctor\n" +
"required: number");
}
public void testThis13() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"/** @constructor */ function Ctor() {}" +
"Ctor.prototype = {" +
" method: function() {" +
" f(this);" +
" }" +
"};",
"actual parameter 1 of f does not match formal parameter\n" +
"found : Ctor\n" +
"required: number");
}
public void testThis14() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"f(this.Object);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : function (new:Object, *=): Object\n" +
"required: number");
}
public void testThisTypeOfFunction1() throws Exception {
testTypes(
"/** @type {function(this:Object)} */ function f() {}" +
"f();");
}
public void testThisTypeOfFunction2() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @type {function(this:F)} */ function f() {}" +
"f();",
"\"function (this:F): ?\" must be called with a \"this\" type");
}
public void testThisTypeOfFunction3() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"F.prototype.bar = function() {};" +
"var f = (new F()).bar; f();",
"\"function (this:F): undefined\" must be called with a \"this\" type");
}
public void testThisTypeOfFunction4() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"F.prototype.moveTo = function(x, y) {};" +
"F.prototype.lineTo = function(x, y) {};" +
"function demo() {" +
" var path = new F();" +
" var points = [[1,1], [2,2]];" +
" for (var i = 0; i < points.length; i++) {" +
" (i == 0 ? path.moveTo : path.lineTo)(" +
" points[i][0], points[i][1]);" +
" }" +
"}",
"\"function (this:F, ?, ?): undefined\" " +
"must be called with a \"this\" type");
}
public void testThisTypeOfFunction5() throws Exception {
testTypes(LINE_JOINER.join(
"/** @type {function(this:number)} */",
"function f() {",
" var /** number */ n = this;",
"}"));
}
public void testGlobalThis1() throws Exception {
testTypes("/** @constructor */ function Window() {}" +
"/** @param {string} msg */ " +
"Window.prototype.alert = function(msg) {};" +
"this.alert(3);",
"actual parameter 1 of Window.prototype.alert " +
"does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testGlobalThis2() throws Exception {
// this.alert = 3 doesn't count as a declaration, so this isn't a warning.
testTypes("/** @constructor */ function Bindow() {}" +
"/** @param {string} msg */ " +
"Bindow.prototype.alert = function(msg) {};" +
"this.alert = 3;" +
"(new Bindow()).alert(this.alert)");
}
public void testGlobalThis2b() throws Exception {
testTypes("/** @constructor */ function Bindow() {}" +
"/** @param {string} msg */ " +
"Bindow.prototype.alert = function(msg) {};" +
"/** @return {number} */ this.alert = function() { return 3; };" +
"(new Bindow()).alert(this.alert())",
"actual parameter 1 of Bindow.prototype.alert " +
"does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testGlobalThis3() throws Exception {
testTypes(
"/** @param {string} msg */ " +
"function alert(msg) {};" +
"this.alert(3);",
"actual parameter 1 of global this.alert " +
"does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testGlobalThis4() throws Exception {
testTypes(
"/** @param {string} msg */ " +
"var alert = function(msg) {};" +
"this.alert(3);",
"actual parameter 1 of global this.alert " +
"does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testGlobalThis5() throws Exception {
testTypes(
"function f() {" +
" /** @param {string} msg */ " +
" var alert = function(msg) {};" +
"}" +
"this.alert(3);",
"Property alert never defined on global this");
}
public void testGlobalThis6() throws Exception {
testTypes(
"/** @param {string} msg */ " +
"var alert = function(msg) {};" +
"var x = 3;" +
"x = 'msg';" +
"this.alert(this.x);");
}
public void testGlobalThis7() throws Exception {
testTypes(
"/** @constructor */ function Window() {}" +
"/** @param {Window} msg */ " +
"var foo = function(msg) {};" +
"foo(this);");
}
public void testGlobalThis8() throws Exception {
testTypes(
"/** @constructor */ function Window() {}" +
"/** @param {number} msg */ " +
"var foo = function(msg) {};" +
"foo(this);",
"actual parameter 1 of foo does not match formal parameter\n" +
"found : global this\n" +
"required: number");
}
public void testGlobalThis9() throws Exception {
testTypes(
// Window is not marked as a constructor, so the
// inheritance doesn't happen.
"function Window() {}" +
"Window.prototype.alert = function() {};" +
"this.alert();",
"Property alert never defined on global this");
}
public void testControlFlowRestrictsType1() throws Exception {
testTypes(
"/** @return {String?} */ function f() { return null; }"
+ "/** @type {String?} */ var a = f();"
+ "/** @type {String} */ var b = new String('foo');"
+ "/** @type {null} */ var c = null;"
+ "if (a) {"
+ " b = a;"
+ "} else {"
+ " c = a;"
+ "}");
}
public void testControlFlowRestrictsType2() throws Exception {
testTypes(
"/** @return {(string|null)} */ function f() { return null; }"
+ "/** @type {(string|null)} */ var a = f();"
+ "/** @type {string} */ var b = 'foo';"
+ "/** @type {null} */ var c = null;"
+ "if (a) {"
+ " b = a;"
+ "} else {"
+ " c = a;"
+ "}",
"assignment\n" + "found : (null|string)\n" + "required: null");
}
public void testControlFlowRestrictsType3() throws Exception {
testTypes(
"/** @type {(string|void)} */"
+ "var a;"
+ "/** @type {string} */"
+ "var b = 'foo';"
+ "if (a) {"
+ " b = a;"
+ "}");
}
public void testControlFlowRestrictsType4() throws Exception {
testTypes("/** @param {string} a */ function f(a){}" +
"/** @type {(string|undefined)} */ var a;" +
"a && f(a);");
}
public void testControlFlowRestrictsType5() throws Exception {
testTypes("/** @param {undefined} a */ function f(a){}" +
"/** @type {(!Array|undefined)} */ var a;" +
"a || f(a);");
}
public void testControlFlowRestrictsType6() throws Exception {
testTypes("/** @param {undefined} x */ function f(x) {}" +
"/** @type {(string|undefined)} */ var a;" +
"a && f(a);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: undefined");
}
public void testControlFlowRestrictsType7() throws Exception {
testTypes("/** @param {undefined} x */ function f(x) {}" +
"/** @type {(string|undefined)} */ var a;" +
"a && f(a);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: undefined");
}
public void testControlFlowRestrictsType8() throws Exception {
testTypes("/** @param {undefined} a */ function f(a){}" +
"/** @type {(!Array|undefined)} */ var a;" +
"if (a || f(a)) {}");
}
public void testControlFlowRestrictsType9() throws Exception {
testTypes("/** @param {number?} x\n * @return {number}*/\n" +
"var f = function(x) {\n" +
"if (!x || x == 1) { return 1; } else { return x; }\n" +
"};");
}
public void testControlFlowRestrictsType10() throws Exception {
// We should correctly infer that y will be (null|{}) because
// the loop wraps around.
testTypes("/** @param {number} x */ function f(x) {}" +
"function g() {" +
" var y = null;" +
" for (var i = 0; i < 10; i++) {" +
" f(y);" +
" if (y != null) {" +
" // y is None the first time it goes through this branch\n" +
" } else {" +
" y = {};" +
" }" +
" }" +
"};",
"actual parameter 1 of f does not match formal parameter\n" +
"found : (null|{})\n" +
"required: number");
}
public void testControlFlowRestrictsType11() throws Exception {
testTypes("/** @param {boolean} x */ function f(x) {}" +
"function g() {" +
" var y = null;" +
" if (y != null) {" +
" for (var i = 0; i < 10; i++) {" +
" f(y);" +
" }" +
" }" +
"};",
"condition always evaluates to false\n" +
"left : null\n" +
"right: null");
}
public void testSwitchCase3() throws Exception {
testTypes("/** @type {String} */" +
"var a = new String('foo');" +
"switch (a) { case 'A': }");
}
public void testSwitchCase4() throws Exception {
testTypes("/** @type {(string|Null)} */" +
"var a = unknown;" +
"switch (a) { case 'A':break; case null:break; }");
}
public void testSwitchCase5() throws Exception {
testTypes("/** @type {(String|Null)} */" +
"var a = unknown;" +
"switch (a) { case 'A':break; case null:break; }");
}
public void testSwitchCase6() throws Exception {
testTypes("/** @type {(Number|Null)} */" +
"var a = unknown;" +
"switch (a) { case 5:break; case null:break; }");
}
public void testSwitchCase7() throws Exception {
// This really tests the inference inside the case.
testTypes(
"/**\n" +
" * @param {number} x\n" +
" * @return {number}\n" +
" */\n" +
"function g(x) { return 5; }" +
"function f() {" +
" var x = {};" +
" x.foo = '3';" +
" switch (3) { case g(x.foo): return 3; }" +
"}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testSwitchCase8() throws Exception {
// This really tests the inference inside the switch clause.
testTypes(
"/**\n" +
" * @param {number} x\n" +
" * @return {number}\n" +
" */\n" +
"function g(x) { return 5; }" +
"function f() {" +
" var x = {};" +
" x.foo = '3';" +
" switch (g(x.foo)) { case 3: return 3; }" +
"}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testImplicitCast1() throws Exception {
testTypesWithExterns("/** @constructor */ function Element() {};\n" +
"/** @type {string}\n" +
" * @implicitCast */" +
"Element.prototype.innerHTML;",
"(new Element).innerHTML = new Array();");
}
public void testImplicitCast2() throws Exception {
testTypesWithExterns(
"/** @constructor */ function Element() {};\n" +
"/**\n" +
" * @type {string}\n" +
" * @implicitCast\n" +
" */\n" +
"Element.prototype.innerHTML;\n",
"/** @constructor */ function C(e) {\n" +
" /** @type {Element} */ this.el = e;\n" +
"}\n" +
"C.prototype.method = function() {\n" +
" this.el.innerHTML = new Array();\n" +
"};\n");
}
public void testImplicitCastSubclassAccess() throws Exception {
testTypesWithExterns("/** @constructor */ function Element() {};\n" +
"/** @type {string}\n" +
" * @implicitCast */" +
"Element.prototype.innerHTML;" +
"/** @constructor \n @extends Element */" +
"function DIVElement() {};",
"(new DIVElement).innerHTML = new Array();");
}
public void testImplicitCastNotInExterns() throws Exception {
testTypes("/** @constructor */ function Element() {};\n" +
"/** @type {string}\n" +
" * @implicitCast */" +
"Element.prototype.innerHTML;" +
"(new Element).innerHTML = new Array();",
new String[] {
"Illegal annotation on innerHTML. @implicitCast may only be " +
"used in externs.",
"assignment to property innerHTML of Element\n" +
"found : Array\n" +
"required: string"});
}
public void testNumberNode() throws Exception {
Node n = typeCheck(Node.newNumber(0));
assertTypeEquals(NUMBER_TYPE, n.getJSType());
}
public void testStringNode() throws Exception {
Node n = typeCheck(Node.newString("hello"));
assertTypeEquals(STRING_TYPE, n.getJSType());
}
public void testBooleanNodeTrue() throws Exception {
Node trueNode = typeCheck(new Node(Token.TRUE));
assertTypeEquals(BOOLEAN_TYPE, trueNode.getJSType());
}
public void testBooleanNodeFalse() throws Exception {
Node falseNode = typeCheck(new Node(Token.FALSE));
assertTypeEquals(BOOLEAN_TYPE, falseNode.getJSType());
}
public void testUndefinedNode() throws Exception {
Node p = new Node(Token.ADD);
Node n = Node.newString(Token.NAME, "undefined");
p.addChildToBack(n);
p.addChildToBack(Node.newNumber(5));
typeCheck(p);
assertTypeEquals(VOID_TYPE, n.getJSType());
}
public void testNumberAutoboxing() throws Exception {
testTypes("/** @type {Number} */var a = 4;",
"initializing variable\n" +
"found : number\n" +
"required: (Number|null)");
}
public void testNumberUnboxing() throws Exception {
testTypes("/** @type {number} */var a = new Number(4);",
"initializing variable\n" +
"found : Number\n" +
"required: number");
}
public void testStringAutoboxing() throws Exception {
testTypes("/** @type {String} */var a = 'hello';",
"initializing variable\n" +
"found : string\n" +
"required: (String|null)");
}
public void testStringUnboxing() throws Exception {
testTypes("/** @type {string} */var a = new String('hello');",
"initializing variable\n" +
"found : String\n" +
"required: string");
}
public void testBooleanAutoboxing() throws Exception {
testTypes("/** @type {Boolean} */var a = true;",
"initializing variable\n" +
"found : boolean\n" +
"required: (Boolean|null)");
}
public void testBooleanUnboxing() throws Exception {
testTypes("/** @type {boolean} */var a = new Boolean(false);",
"initializing variable\n" +
"found : Boolean\n" +
"required: boolean");
}
public void testIIFE1() throws Exception {
testTypes(
"var namespace = {};" +
"/** @type {number} */ namespace.prop = 3;" +
"(function(ns) {" +
" ns.prop = true;" +
"})(namespace);",
"assignment to property prop of ns\n" +
"found : boolean\n" +
"required: number");
}
public void testIIFE2() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"(function(ctor) {" +
" /** @type {boolean} */ ctor.prop = true;" +
"})(Foo);" +
"/** @return {number} */ function f() { return Foo.prop; }",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testIIFE3() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"(function(ctor) {" +
" /** @type {boolean} */ ctor.prop = true;" +
"})(Foo);" +
"/** @param {number} x */ function f(x) {}" +
"f(Foo.prop);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testIIFE4() throws Exception {
testTypes(
"/** @const */ var namespace = {};" +
"(function(ns) {" +
" /**\n" +
" * @constructor\n" +
" * @param {number} x\n" +
" */\n" +
" ns.Ctor = function(x) {};" +
"})(namespace);" +
"new namespace.Ctor(true);",
"actual parameter 1 of namespace.Ctor " +
"does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testIIFE5() throws Exception {
// TODO(nicksantos): This behavior is currently incorrect.
// To handle this case properly, we'll need to change how we handle
// type resolution.
testTypes(
"/** @const */ var namespace = {};" +
"(function(ns) {" +
" /**\n" +
" * @constructor\n" +
" */\n" +
" ns.Ctor = function() {};" +
" /** @type {boolean} */ ns.Ctor.prototype.bar = true;" +
"})(namespace);" +
"/** @param {namespace.Ctor} x\n" +
" * @return {number} */ function f(x) { return x.bar; }",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testNotIIFE1() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"/** @param {...?} x */ function g(x) {}" +
"g(function(y) { f(y); }, true);");
}
public void testNamespaceType1() throws Exception {
testTypes(
"/** @namespace */ var x = {};" +
"/** @param {x.} y */ function f(y) {};",
"Parse error. Namespaces not supported yet (x.)");
}
public void testNamespaceType2() throws Exception {
testTypes(
"/** @namespace */ var x = {};" +
"/** @namespace */ x.y = {};" +
"/** @param {x.y.} y */ function f(y) {}",
"Parse error. Namespaces not supported yet (x.y.)");
}
public void testIssue61() throws Exception {
testTypes(
"var ns = {};" +
"(function() {" +
" /** @param {string} b */" +
" ns.a = function(b) {};" +
"})();" +
"function d() {" +
" ns.a(123);" +
"}",
"actual parameter 1 of ns.a does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testIssue61b() throws Exception {
testTypes(
"var ns = {};" +
"(function() {" +
" /** @param {string} b */" +
" ns.a = function(b) {};" +
"})();" +
"ns.a(123);",
"actual parameter 1 of ns.a does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testIssue86() throws Exception {
testTypes(
"/** @interface */ function I() {}" +
"/** @return {number} */ I.prototype.get = function(){};" +
"/** @constructor \n * @implements {I} */ function F() {}" +
"/** @override */ F.prototype.get = function() { return true; };",
"inconsistent return type\n" +
"found : boolean\n" +
"required: number");
}
public void testIssue124() throws Exception {
testTypes(
"var t = null;" +
"function test() {" +
" if (t != null) { t = null; }" +
" t = 1;" +
"}");
}
public void testIssue124b() throws Exception {
testTypes(
"var t = null;" +
"function test() {" +
" if (t != null) { t = null; }" +
" t = undefined;" +
"}",
"condition always evaluates to false\n" +
"left : (null|undefined)\n" +
"right: null");
}
public void testIssue259() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"/** @constructor */" +
"var Clock = function() {" +
" /** @constructor */" +
" this.Date = function() {};" +
" f(new this.Date());" +
"};",
"actual parameter 1 of f does not match formal parameter\n" +
"found : this.Date\n" +
"required: number");
}
public void testIssue301() throws Exception {
testTypes(
"Array.indexOf = function() {};" +
"var s = 'hello';" +
"alert(s.toLowerCase.indexOf('1'));",
"Property indexOf never defined on String.prototype.toLowerCase");
}
public void testIssue368() throws Exception {
testTypes(
"/** @constructor */ function Foo(){}" +
"/**\n" +
" * @param {number} one\n" +
" * @param {string} two\n" +
" */\n" +
"Foo.prototype.add = function(one, two) {};" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */\n" +
"function Bar(){}" +
"/** @override */\n" +
"Bar.prototype.add = function(ignored) {};" +
"(new Bar()).add(1, 2);",
"actual parameter 2 of Bar.prototype.add does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testIssue380() throws Exception {
testTypes(
"/** @type { function(string): {innerHTML: string} } */\n" +
"document.getElementById;\n" +
"var list = /** @type {!Array<string>} */ ['hello', 'you'];\n" +
"list.push('?');\n" +
"document.getElementById('node').innerHTML = list.toString();");
}
public void testIssue483() throws Exception {
testTypes(
"/** @constructor */ function C() {" +
" /** @type {?Array} */ this.a = [];" +
"}" +
"C.prototype.f = function() {" +
" if (this.a.length > 0) {" +
" g(this.a);" +
" }" +
"};" +
"/** @param {number} a */ function g(a) {}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : Array\n" +
"required: number");
}
public void testIssue537a() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype = {method: function() {}};" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */\n" +
"function Bar() {" +
" Foo.call(this);" +
" if (this.baz()) this.method(1);" +
"}" +
"Bar.prototype = {" +
" baz: function() {" +
" return true;" +
" }" +
"};" +
"Bar.prototype.__proto__ = Foo.prototype;",
"Function Foo.prototype.method: called with 1 argument(s). " +
"Function requires at least 0 argument(s) " +
"and no more than 0 argument(s).");
}
public void testIssue537b() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype = {method: function() {}};" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */\n" +
"function Bar() {" +
" Foo.call(this);" +
" if (this.baz(1)) this.method();" +
"}" +
"Bar.prototype = {" +
" baz: function() {" +
" return true;" +
" }" +
"};" +
"Bar.prototype.__proto__ = Foo.prototype;",
"Function Bar.prototype.baz: called with 1 argument(s). " +
"Function requires at least 0 argument(s) " +
"and no more than 0 argument(s).");
}
public void testIssue537c() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */\n" +
"function Bar() {" +
" Foo.call(this);" +
" if (this.baz2()) alert(1);" +
"}" +
"Bar.prototype = {" +
" baz: function() {" +
" return true;" +
" }" +
"};" +
"Bar.prototype.__proto__ = Foo.prototype;",
"Property baz2 never defined on Bar");
}
public void testIssue537d() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}"
+ "Foo.prototype = {"
+ " /** @return {Bar} */ x: function() { new Bar(); },"
+ " /** @return {Foo} */ y: function() { new Bar(); }"
+ "};"
+ "/**\n"
+ " * @constructor\n"
+ " * @extends {Foo}\n"
+ " */\n"
+ "function Bar() {"
+ " this.xy = 3;"
+ "}"
+ "/** @return {Bar} */ function f() { return new Bar(); }"
+ "/** @return {Foo} */ function g() { return new Bar(); }"
+ "Bar.prototype = {"
+ " /** @override @return {Bar} */ x: function() { new Bar(); },"
+ " /** @override @return {Foo} */ y: function() { new Bar(); }"
+ "};"
+ "Bar.prototype.__proto__ = Foo.prototype;");
}
public void testIssue586() throws Exception {
testTypes(
"/** @constructor */" +
"var MyClass = function() {};" +
"/** @param {boolean} success */" +
"MyClass.prototype.fn = function(success) {};" +
"MyClass.prototype.test = function() {" +
" this.fn();" +
" this.fn = function() {};" +
"};",
"Function MyClass.prototype.fn: called with 0 argument(s). " +
"Function requires at least 1 argument(s) " +
"and no more than 1 argument(s).");
}
public void testIssue635() throws Exception {
// TODO(nicksantos): Make this emit a warning, because of the 'this' type.
testTypes(
"/** @constructor */" +
"function F() {}" +
"F.prototype.bar = function() { this.baz(); };" +
"F.prototype.baz = function() {};" +
"/** @constructor */" +
"function G() {}" +
"G.prototype.bar = F.prototype.bar;");
}
public void testIssue635b() throws Exception {
testTypes(
"/** @constructor */" +
"function F() {}" +
"/** @constructor */" +
"function G() {}" +
"/** @type {function(new:G)} */ var x = F;",
"initializing variable\n" +
"found : function (new:F): undefined\n" +
"required: function (new:G): ?");
}
public void testIssue669() throws Exception {
testTypes(
"/** @return {{prop1: (Object|undefined)}} */" +
"function f(a) {" +
" var results;" +
" if (a) {" +
" results = {};" +
" results.prop1 = {a: 3};" +
" } else {" +
" results = {prop2: 3};" +
" }" +
" return results;" +
"}");
}
public void testIssue688() throws Exception {
testTypes(
"/** @const */ var SOME_DEFAULT =\n" +
" /** @type {TwoNumbers} */ ({first: 1, second: 2});\n" +
"/**\n" +
"* Class defining an interface with two numbers.\n" +
"* @interface\n" +
"*/\n" +
"function TwoNumbers() {}\n" +
"/** @type {number} */\n" +
"TwoNumbers.prototype.first;\n" +
"/** @type {number} */\n" +
"TwoNumbers.prototype.second;\n" +
"/** @return {number} */ function f() { return SOME_DEFAULT; }",
"inconsistent return type\n" +
"found : (TwoNumbers|null)\n" +
"required: number");
}
public void testIssue700() throws Exception {
testTypes(
"/**\n" +
" * @param {{text: string}} opt_data\n" +
" * @return {string}\n" +
" */\n" +
"function temp1(opt_data) {\n" +
" return opt_data.text;\n" +
"}\n" +
"\n" +
"/**\n" +
" * @param {{activity: (boolean|number|string|null|Object)}} opt_data\n" +
" * @return {string}\n" +
" */\n" +
"function temp2(opt_data) {\n" +
" /** @suppress {checkTypes} */\n" +
" function __inner() {\n" +
" return temp1(opt_data.activity);\n" +
" }\n" +
" return __inner();\n" +
"}\n" +
"\n" +
"/**\n" +
" * @param {{n: number, text: string, b: boolean}} opt_data\n" +
" * @return {string}\n" +
" */\n" +
"function temp3(opt_data) {\n" +
" return 'n: ' + opt_data.n + ', t: ' + opt_data.text + '.';\n" +
"}\n" +
"\n" +
"function callee() {\n" +
" var output = temp3({\n" +
" n: 0,\n" +
" text: 'a string',\n" +
" b: true\n" +
" })\n" +
" alert(output);\n" +
"}\n" +
"\n" +
"callee();");
}
public void testIssue725() throws Exception {
testTypes(
"/** @typedef {{name: string}} */ var RecordType1;" +
"/** @typedef {{name2222: string}} */ var RecordType2;" +
"/** @param {RecordType1} rec */ function f(rec) {" +
" alert(rec.name2222);" +
"}",
"Property name2222 never defined on rec");
}
public void testIssue726() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @param {number} x */ Foo.prototype.bar = function(x) {};" +
"/** @return {!Function} */ " +
"Foo.prototype.getDeferredBar = function() { " +
" var self = this;" +
" return function() {" +
" self.bar(true);" +
" };" +
"};",
"actual parameter 1 of Foo.prototype.bar does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testIssue765() throws Exception {
testTypes(
"/** @constructor */" +
"var AnotherType = function (parent) {" +
" /** @param {string} stringParameter Description... */" +
" this.doSomething = function (stringParameter) {};" +
"};" +
"/** @constructor */" +
"var YetAnotherType = function () {" +
" this.field = new AnotherType(self);" +
" this.testfun=function(stringdata) {" +
" this.field.doSomething(null);" +
" };" +
"};",
"actual parameter 1 of AnotherType.doSomething " +
"does not match formal parameter\n" +
"found : null\n" +
"required: string");
}
public void testIssue783() throws Exception {
testTypes(
"/** @constructor */" +
"var Type = function () {" +
" /** @type {Type} */" +
" this.me_ = this;" +
"};" +
"Type.prototype.doIt = function() {" +
" var me = this.me_;" +
" for (var i = 0; i < me.unknownProp; i++) {}" +
"};",
"Property unknownProp never defined on Type");
}
public void testIssue791() throws Exception {
testTypes(
"/** @param {{func: function()}} obj */" +
"function test1(obj) {}" +
"var fnStruc1 = {};" +
"fnStruc1.func = function() {};" +
"test1(fnStruc1);");
}
public void testIssue810() throws Exception {
testTypes(
"/** @constructor */" +
"var Type = function () {" +
"};" +
"Type.prototype.doIt = function(obj) {" +
" this.prop = obj.unknownProp;" +
"};",
"Property unknownProp never defined on obj");
}
public void testIssue1002() throws Exception {
testTypes(
"/** @interface */" +
"var I = function() {};" +
"/** @constructor @implements {I} */" +
"var A = function() {};" +
"/** @constructor @implements {I} */" +
"var B = function() {};" +
"var f = function() {" +
" if (A === B) {" +
" new B();" +
" }" +
"};");
}
public void testIssue1023() throws Exception {
testTypes(
"/** @constructor */" +
"function F() {}" +
"(function () {" +
" F.prototype = {" +
" /** @param {string} x */" +
" bar: function (x) { }" +
" };" +
"})();" +
"(new F()).bar(true)",
"actual parameter 1 of F.prototype.bar does not match formal parameter\n" +
"found : boolean\n" +
"required: string");
}
public void testIssue1047() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" */\n" +
"function C2() {}\n" +
"\n" +
"/**\n" +
" * @constructor\n" +
" */\n" +
"function C3(c2) {\n" +
" /**\n" +
" * @type {C2} \n" +
" * @private\n" +
" */\n" +
" this.c2_;\n" +
"\n" +
" var x = this.c2_.prop;\n" +
"}",
"Property prop never defined on C2");
}
public void testIssue1056() throws Exception {
testTypes(
"/** @type {Array} */ var x = null;" +
"x.push('hi');",
"No properties on this expression\n" +
"found : null\n" +
"required: Object");
}
public void testIssue1072() throws Exception {
testTypes(
"/**\n" +
" * @param {string} x\n" +
" * @return {number}\n" +
" */\n" +
"var f1 = function (x) {\n" +
" return 3;\n" +
"};\n" +
"\n" +
"/** Function */\n" +
"var f2 = function (x) {\n" +
" if (!x) throw new Error()\n" +
" return /** @type {number} */ (f1('x'))\n" +
"}\n" +
"\n" +
"/**\n" +
" * @param {string} x\n" +
" */\n" +
"var f3 = function (x) {};\n" +
"\n" +
"f1(f3);",
"actual parameter 1 of f1 does not match formal parameter\n" +
"found : function (string): undefined\n" +
"required: string");
}
public void testIssue1123() throws Exception {
testTypes(
"/** @param {function(number)} g */ function f(g) {}" +
"f(function(a, b) {})",
"actual parameter 1 of f does not match formal parameter\n" +
"found : function (?, ?): undefined\n" +
"required: function (number): ?");
}
public void testIssue1201() throws Exception {
testTypes(
"/** @param {function(this:void)} f */ function g(f) {}" +
"/** @constructor */ function F() {}" +
"/** desc */ F.prototype.bar = function() {};" +
"g(new F().bar);",
"actual parameter 1 of g does not match formal parameter\n" +
"found : function (this:F): undefined\n" +
"required: function (this:undefined): ?");
}
public void testIssue1201b() throws Exception {
testTypes(
"/** @param {function(this:void)} f */ function g(f) {}" +
"/** @constructor */ function F() {}" +
"/** desc */ F.prototype.bar = function() {};" +
"var f = new F();" +
"g(f.bar.bind(f));");
}
public void testIssue1201c() throws Exception {
testTypes(
"/** @param {function(this:void)} f */ function g(f) {}" +
"g(function() { this.alert() })",
"No properties on this expression\n" +
"found : undefined\n" +
"required: Object");
}
public void testIssue926a() throws Exception {
testTypes("/** x */ function error() {}" +
"/**\n" +
" * @constructor\n" +
" * @param {string} error\n" +
" */\n" +
"function C(error) {\n" +
" /** @const */ this.e = error;\n" +
"}" +
"/** @type {number} */ var x = (new C('x')).e;",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testIssue926b() throws Exception {
testTypes("/** @constructor */\n" +
"function A() {\n" +
" /** @constructor */\n" +
" function B() {}\n" +
" /** @type {!B} */ this.foo = new B();" +
" /** @type {!B} */ var C = new B();" +
"}" +
"/** @type {number} */ var x = (new A()).foo;",
"initializing variable\n" +
"found : B\n" +
"required: number");
}
public void testEnums() throws Exception {
testTypes(
"var outer = function() {" +
" /** @enum {number} */" +
" var Level = {" +
" NONE: 0," +
" };" +
" /** @type {!Level} */" +
" var l = Level.NONE;" +
"}");
}
/**
* Tests that the || operator is type checked correctly, that is of
* the type of the first argument or of the second argument. See
* bugid 592170 for more details.
*/
public void testBug592170() throws Exception {
testTypes(
"/** @param {Function} opt_f ... */" +
"function foo(opt_f) {" +
" /** @type {Function} */" +
" return opt_f || function () {};" +
"}");
}
/**
* Tests that undefined can be compared shallowly to a value of type
* (number,undefined) regardless of the side on which the undefined
* value is.
*/
public void testBug901455() throws Exception {
testTypes("/** @return {(number|undefined)} */ function a() { return 3; }" +
"var b = undefined === a()");
testTypes("/** @return {(number|undefined)} */ function a() { return 3; }" +
"var b = a() === undefined");
}
/**
* Tests that the match method of strings returns nullable arrays.
*/
public void testBug908701() throws Exception {
testTypes("/** @type {String} */var s = new String('foo');" +
"var b = s.match(/a/) != null;");
}
/**
* Tests that named types play nicely with subtyping.
*/
public void testBug908625() throws Exception {
testTypes("/** @constructor */function A(){}" +
"/** @constructor\n * @extends A */function B(){}" +
"/** @param {B} b" +
"\n @return {(A|undefined)} */function foo(b){return b}");
}
/**
* Tests that assigning two untyped functions to a variable whose type is
* inferred and calling this variable is legal.
*/
public void testBug911118() throws Exception {
// verifying the type assigned to function expressions assigned variables
TypedScope s = parseAndTypeCheckWithScope("var a = function(){};").scope;
JSType type = s.getVar("a").getType();
assertEquals("function (): undefined", type.toString());
// verifying the bug example
testTypes("function nullFunction() {};" +
"var foo = nullFunction;" +
"foo = function() {};" +
"foo();");
}
public void testBug909000() throws Exception {
testTypes("/** @constructor */function A(){}\n" +
"/** @param {!A} a\n" +
"@return {boolean}*/\n" +
"function y(a) { return a }",
"inconsistent return type\n" +
"found : A\n" +
"required: boolean");
}
public void testBug930117() throws Exception {
testTypes(
"/** @param {boolean} x */function f(x){}" +
"f(null);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : null\n" +
"required: boolean");
}
public void testBug1484445() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {number?} */ Foo.prototype.bar = null;" +
"/** @type {number?} */ Foo.prototype.baz = null;" +
"/** @param {Foo} foo */" +
"function f(foo) {" +
" while (true) {" +
" if (foo.bar == null && foo.baz == null) {" +
" foo.bar;" +
" }" +
" }" +
"}");
}
public void testBug1859535() throws Exception {
testTypes(
"/**\n" +
" * @param {Function} childCtor Child class.\n" +
" * @param {Function} parentCtor Parent class.\n" +
" */" +
"var inherits = function(childCtor, parentCtor) {" +
" /** @constructor */" +
" function tempCtor() {};" +
" tempCtor.prototype = parentCtor.prototype;" +
" childCtor.superClass_ = parentCtor.prototype;" +
" childCtor.prototype = new tempCtor();" +
" /** @override */ childCtor.prototype.constructor = childCtor;" +
"};" +
"/**" +
" * @param {Function} constructor\n" +
" * @param {Object} var_args\n" +
" * @return {Object}\n" +
" */" +
"var factory = function(constructor, var_args) {" +
" /** @constructor */" +
" var tempCtor = function() {};" +
" tempCtor.prototype = constructor.prototype;" +
" var obj = new tempCtor();" +
" constructor.apply(obj, arguments);" +
" return obj;" +
"};");
}
public void testBug1940591() throws Exception {
testTypes(
"/** @type {Object} */" +
"var a = {};\n" +
"/** @type {number} */\n" +
"a.name = 0;\n" +
"/**\n" +
" * @param {Function} x anything.\n" +
" */\n" +
"a.g = function(x) { x.name = 'a'; }");
}
public void testBug1942972() throws Exception {
testTypes(
"var google = {\n" +
" gears: {\n" +
" factory: {},\n" +
" workerPool: {}\n" +
" }\n" +
"};\n" +
"\n" +
"google.gears = {factory: {}};\n");
}
public void testBug1943776() throws Exception {
testTypes(
"/** @return {{foo: Array}} */" +
"function bar() {" +
" return {foo: []};" +
"}");
}
public void testBug1987544() throws Exception {
testTypes(
"/** @param {string} x */ function foo(x) {}" +
"var duration;" +
"if (true && !(duration = 3)) {" +
" foo(duration);" +
"}",
"actual parameter 1 of foo does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testBug1940769() throws Exception {
testTypes(
"/** @return {!Object} */ " +
"function proto(obj) { return obj.prototype; }" +
"/** @constructor */ function Map() {}" +
"/**\n" +
" * @constructor\n" +
" * @extends {Map}\n" +
" */" +
"function Map2() { Map.call(this); };" +
"Map2.prototype = proto(Map);");
}
public void testBug2335992() throws Exception {
testTypes(
"/** @return {*} */ function f() { return 3; }" +
"var x = f();" +
"/** @type {string} */" +
"x.y = 3;",
"assignment\n" +
"found : number\n" +
"required: string");
}
public void testBug2341812() throws Exception {
testTypes(
"/** @interface */" +
"function EventTarget() {}" +
"/** @constructor \n * @implements {EventTarget} */" +
"function Node() {}" +
"/** @type {number} */ Node.prototype.index;" +
"/** @param {EventTarget} x \n * @return {string} */" +
"function foo(x) { return x.index; }");
}
public void testBug7701884() throws Exception {
testTypes(
"/**\n" +
" * @param {Array<T>} x\n" +
" * @param {function(T)} y\n" +
" * @template T\n" +
" */\n" +
"var forEach = function(x, y) {\n" +
" for (var i = 0; i < x.length; i++) y(x[i]);\n" +
"};" +
"/** @param {number} x */" +
"function f(x) {}" +
"/** @param {?} x */" +
"function h(x) {" +
" var top = null;" +
" forEach(x, function(z) { top = z; });" +
" if (top) f(top);" +
"}");
}
public void testBug8017789() throws Exception {
testTypes(
"/** @param {(map|function())} isResult */" +
"var f = function(isResult) {" +
" while (true)" +
" isResult['t'];" +
"};" +
"/** @typedef {Object<string, number>} */" +
"var map;");
}
public void testBug12441160() throws Exception {
testTypes(
"/** @param {string} a */ \n" +
"function use(a) {};\n" +
"/**\n" +
" * @param {function(this:THIS)} fn\n" +
" * @param {THIS} context \n" +
" * @constructor\n" +
" * @template THIS\n" +
" */\n" +
"var P = function(fn, context) {}\n" +
"\n" +
"/** @constructor */\n" +
"function C() { /** @type {number} */ this.a = 1; }\n" +
"\n" +
"/** @return {P} */ \n" +
"C.prototype.method = function() {\n" +
" return new P(function() { use(this.a); }, this);\n" +
"};\n" +
"\n",
"actual parameter 1 of use does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testBug13641083a() throws Exception {
testTypes(
"/** @constructor @struct */ function C() {};" +
"new C().bar;",
TypeCheck.INEXISTENT_PROPERTY);
}
public void testBug13641083b() throws Exception {
testTypes(
"/** @type {?} */ var C;" +
"C.bar + 1;",
TypeCheck.POSSIBLE_INEXISTENT_PROPERTY);
}
public void testTypedefBeforeUse() throws Exception {
testTypes(
"/** @typedef {Object<string, number>} */" +
"var map;" +
"/** @param {(map|function())} isResult */" +
"var f = function(isResult) {" +
" while (true)" +
" isResult['t'];" +
"};");
}
public void testScopedConstructors1() throws Exception {
testTypes(
"function foo1() { " +
" /** @constructor */ function Bar() { " +
" /** @type {number} */ this.x = 3;" +
" }" +
"}" +
"function foo2() { " +
" /** @constructor */ function Bar() { " +
" /** @type {string} */ this.x = 'y';" +
" }" +
" /** " +
" * @param {Bar} b\n" +
" * @return {number}\n" +
" */" +
" function baz(b) { return b.x; }" +
"}",
"inconsistent return type\n" +
"found : string\n" +
"required: number");
}
public void testScopedConstructors2() throws Exception {
testTypes(
"/** @param {Function} f */" +
"function foo1(f) {" +
" /** @param {Function} g */" +
" f.prototype.bar = function(g) {};" +
"}");
}
public void testQualifiedNameInference1() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {number?} */ Foo.prototype.bar = null;" +
"/** @type {number?} */ Foo.prototype.baz = null;" +
"/** @param {Foo} foo */" +
"function f(foo) {" +
" while (true) {" +
" if (!foo.baz) break; " +
" foo.bar = null;" +
" }" +
// Tests a bug where this condition always evaluated to true.
" return foo.bar == null;" +
"}");
}
public void testQualifiedNameInference2() throws Exception {
testTypes(
"var x = {};" +
"x.y = c;" +
"function f(a, b) {" +
" if (a) {" +
" if (b) " +
" x.y = 2;" +
" else " +
" x.y = 1;" +
" }" +
" return x.y == null;" +
"}");
}
public void testQualifiedNameInference3() throws Exception {
testTypes(
"var x = {};" +
"x.y = c;" +
"function f(a, b) {" +
" if (a) {" +
" if (b) " +
" x.y = 2;" +
" else " +
" x.y = 1;" +
" }" +
" return x.y == null;" +
"} function g() { x.y = null; }");
}
public void testQualifiedNameInference4() throws Exception {
testTypes(
"/** @param {string} x */ function f(x) {}\n" +
"/**\n" +
" * @param {?string} x \n" +
" * @constructor\n" +
" */" +
"function Foo(x) { this.x_ = x; }\n" +
"Foo.prototype.bar = function() {" +
" if (this.x_) { f(this.x_); }" +
"};");
}
public void testQualifiedNameInference5() throws Exception {
testTypes(
"var ns = {}; " +
"(function() { " +
" /** @param {number} x */ ns.foo = function(x) {}; })();" +
"(function() { ns.foo(true); })();",
"actual parameter 1 of ns.foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testQualifiedNameInference6() throws Exception {
testTypes(
"/** @const */ var ns = {}; " +
"/** @param {number} x */ ns.foo = function(x) {};" +
"(function() { " +
" ns.foo = function(x) {};" +
" ns.foo(true); " +
"})();",
"actual parameter 1 of ns.foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testQualifiedNameInference7() throws Exception {
testTypes(
"var ns = {}; " +
"(function() { " +
" /** @constructor \n * @param {number} x */ " +
" ns.Foo = function(x) {};" +
" /** @param {ns.Foo} x */ function f(x) {}" +
" f(new ns.Foo(true));" +
"})();",
"actual parameter 1 of ns.Foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testQualifiedNameInference8() throws Exception {
testClosureTypesMultipleWarnings(
"var ns = {}; " +
"(function() { " +
" /** @constructor \n * @param {number} x */ " +
" ns.Foo = function(x) {};" +
"})();" +
"/** @param {ns.Foo} x */ function f(x) {}" +
"f(new ns.Foo(true));",
ImmutableList.of(
"actual parameter 1 of ns.Foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number"));
}
public void testQualifiedNameInference9() throws Exception {
testTypes(
"var ns = {}; " +
"ns.ns2 = {}; " +
"(function() { " +
" /** @constructor \n * @param {number} x */ " +
" ns.ns2.Foo = function(x) {};" +
" /** @param {ns.ns2.Foo} x */ function f(x) {}" +
" f(new ns.ns2.Foo(true));" +
"})();",
"actual parameter 1 of ns.ns2.Foo does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testQualifiedNameInference10() throws Exception {
testTypes(
"var ns = {}; " +
"ns.ns2 = {}; " +
"(function() { " +
" /** @interface */ " +
" ns.ns2.Foo = function() {};" +
" /** @constructor \n * @implements {ns.ns2.Foo} */ " +
" function F() {}" +
" (new F());" +
"})();");
}
public void testQualifiedNameInference11() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"function f() {" +
" var x = new Foo();" +
" x.onload = function() {" +
" x.onload = null;" +
" };" +
"}");
}
public void testQualifiedNameInference12() throws Exception {
// We should be able to tell that the two 'this' properties
// are different.
testTypes(
"/** @param {function(this:Object)} x */ function f(x) {}" +
"/** @constructor */ function Foo() {" +
" /** @type {number} */ this.bar = 3;" +
" f(function() { this.bar = true; });" +
"}");
}
public void testQualifiedNameInference13() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"function f(z) {" +
" var x = new Foo();" +
" if (z) {" +
" x.onload = function() {};" +
" } else {" +
" x.onload = null;" +
" };" +
"}");
}
public void testSheqRefinedScope() throws Exception {
Node n = parseAndTypeCheck(
"/** @constructor */function A() {}\n" +
"/** @constructor \n @extends A */ function B() {}\n" +
"/** @return {number} */\n" +
"B.prototype.p = function() { return 1; }\n" +
"/** @param {A} a\n @param {B} b */\n" +
"function f(a, b) {\n" +
" b.p();\n" +
" if (a === b) {\n" +
" b.p();\n" +
" }\n" +
"}");
Node nodeC = n.getLastChild().getLastChild().getLastChild().getLastChild()
.getLastChild().getLastChild();
JSType typeC = nodeC.getJSType();
assertTrue(typeC.isNumber());
Node nodeB = nodeC.getFirstFirstChild();
JSType typeB = nodeB.getJSType();
assertEquals("B", typeB.toString());
}
public void testAssignToUntypedVariable() throws Exception {
Node n = parseAndTypeCheck("var z; z = 1;");
Node assign = n.getLastChild().getFirstChild();
Node node = assign.getFirstChild();
assertFalse(node.getJSType().isUnknownType());
assertEquals("number", node.getJSType().toString());
}
public void testAssignToUntypedProperty() throws Exception {
Node n = parseAndTypeCheck(
"/** @constructor */ function Foo() {}\n" +
"Foo.prototype.a = 1;" +
"(new Foo).a;");
Node node = n.getLastChild().getFirstChild();
assertFalse(node.getJSType().isUnknownType());
assertTrue(node.getJSType().isNumber());
}
public void testNew1() throws Exception {
testTypes("new 4", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew2() throws Exception {
testTypes("var Math = {}; new Math()", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew3() throws Exception {
testTypes("new Date()");
}
public void testNew4() throws Exception {
testTypes("/** @constructor */function A(){}; new A();");
}
public void testNew5() throws Exception {
testTypes("function A(){}; new A();", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew6() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @constructor */function A(){};" +
"var a = new A();");
JSType aType = p.scope.getVar("a").getType();
assertThat(aType).isInstanceOf(ObjectType.class);
ObjectType aObjectType = (ObjectType) aType;
assertEquals("A", aObjectType.getConstructor().getReferenceName());
}
public void testNew7() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"if (opt_constructor) { new opt_constructor; }" +
"}");
}
public void testNew8() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"new opt_constructor;" +
"}");
}
public void testNew9() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"new (opt_constructor || Array);" +
"}");
}
public void testNew10() throws Exception {
testTypes("var goog = {};" +
"/** @param {Function} opt_constructor */" +
"goog.Foo = function (opt_constructor) {" +
"new (opt_constructor || Array);" +
"}");
}
public void testNew11() throws Exception {
testTypes("/** @param {Function} c1 */" +
"function f(c1) {" +
" var c2 = function(){};" +
" c1.prototype = new c2;" +
"}", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew12() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = new Array();");
TypedVar a = p.scope.getVar("a");
assertTypeEquals(ARRAY_TYPE, a.getType());
}
public void testNew13() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"/** @constructor */function FooBar(){};" +
"var a = new FooBar();");
TypedVar a = p.scope.getVar("a");
assertThat(a.getType()).isInstanceOf(ObjectType.class);
assertEquals("FooBar", a.getType().toString());
}
public void testNew14() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"/** @constructor */var FooBar = function(){};" +
"var a = new FooBar();");
TypedVar a = p.scope.getVar("a");
assertThat(a.getType()).isInstanceOf(ObjectType.class);
assertEquals("FooBar", a.getType().toString());
}
public void testNew15() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"/** @constructor */goog.A = function(){};" +
"var a = new goog.A();");
TypedVar a = p.scope.getVar("a");
assertThat(a.getType()).isInstanceOf(ObjectType.class);
assertEquals("goog.A", a.getType().toString());
}
public void testNew16() throws Exception {
testTypes(
"/** \n" +
" * @param {string} x \n" +
" * @constructor \n" +
" */" +
"function Foo(x) {}" +
"function g() { new Foo(1); }",
"actual parameter 1 of Foo does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testNew17() throws Exception {
testTypes("var goog = {}; goog.x = 3; new goog.x",
"cannot instantiate non-constructor");
}
public void testNew18() throws Exception {
testTypes("var goog = {};" +
"/** @constructor */ goog.F = function() {};" +
"/** @constructor */ goog.G = goog.F;");
}
public void testNew19() throws Exception {
testTypes(
"/** @constructor @abstract */ var Foo = function() {}; var foo = new Foo();",
INSTANTIATE_ABSTRACT_CLASS);
}
public void testNew20() throws Exception {
testTypes(LINE_JOINER.join(
"/** @constructor @abstract */",
"function Bar() {};",
"/** @return {function(new:Bar)} */",
"function foo() {}",
"var Foo = foo();",
"var f = new Foo;"),
INSTANTIATE_ABSTRACT_CLASS);
}
public void testName1() throws Exception {
assertTypeEquals(VOID_TYPE, testNameNode("undefined"));
}
public void testName2() throws Exception {
assertTypeEquals(OBJECT_FUNCTION_TYPE, testNameNode("Object"));
}
public void testName3() throws Exception {
assertTypeEquals(ARRAY_FUNCTION_TYPE, testNameNode("Array"));
}
public void testName4() throws Exception {
assertTypeEquals(DATE_FUNCTION_TYPE, testNameNode("Date"));
}
public void testName5() throws Exception {
assertTypeEquals(REGEXP_FUNCTION_TYPE, testNameNode("RegExp"));
}
/**
* Type checks a NAME node and retrieve its type.
*/
private JSType testNameNode(String name) {
Node node = Node.newString(Token.NAME, name);
Node parent = new Node(Token.SCRIPT, node);
parent.setInputId(new InputId("code"));
Node externs = new Node(Token.SCRIPT);
externs.setInputId(new InputId("externs"));
Node externAndJsRoot = IR.root(externs, parent);
makeTypeCheck().processForTesting(null, parent);
return node.getJSType();
}
public void testBitOperation1() throws Exception {
testTypes("/**@return {void}*/function foo(){ ~foo(); }",
"operator ~ cannot be applied to undefined");
}
public void testBitOperation2() throws Exception {
testTypes("/**@return {void}*/function foo(){var a = foo()<<3;}",
"operator << cannot be applied to undefined");
}
public void testBitOperation3() throws Exception {
testTypes("/**@return {void}*/function foo(){var a = 3<<foo();}",
"operator << cannot be applied to undefined");
}
public void testBitOperation4() throws Exception {
testTypes("/**@return {void}*/function foo(){var a = foo()>>>3;}",
"operator >>> cannot be applied to undefined");
}
public void testBitOperation5() throws Exception {
testTypes("/**@return {void}*/function foo(){var a = 3>>>foo();}",
"operator >>> cannot be applied to undefined");
}
public void testBitOperation6() throws Exception {
testTypes("/**@return {!Object}*/function foo(){var a = foo()&3;}",
"bad left operand to bitwise operator\n" +
"found : Object\n" +
"required: (boolean|null|number|string|undefined)");
}
public void testBitOperation7() throws Exception {
testTypes("var x = null; x |= undefined; x &= 3; x ^= '3'; x |= true;");
}
public void testBitOperation8() throws Exception {
testTypes("var x = void 0; x |= new Number(3);");
}
public void testBitOperation9() throws Exception {
testTypes("var x = void 0; x |= {};",
"bad right operand to bitwise operator\n" +
"found : {}\n" +
"required: (boolean|null|number|string|undefined)");
}
public void testCall1() throws Exception {
testTypes("3();", "number expressions are not callable");
}
public void testCall2() throws Exception {
testTypes("/** @param {!Number} foo*/function bar(foo){ bar('abc'); }",
"actual parameter 1 of bar does not match formal parameter\n" +
"found : string\n" +
"required: Number");
}
public void testCall3() throws Exception {
// We are checking that an unresolved named type can successfully
// meet with a functional type to produce a callable type.
testTypes("/** @type {Function|undefined} */var opt_f;" +
"/** @type {some.unknown.type} */var f1;" +
"var f2 = opt_f || f1;" +
"f2();",
"Bad type annotation. Unknown type some.unknown.type");
}
public void testCall4() throws Exception {
testTypes("/**@param {!RegExp} a*/var foo = function bar(a){ bar('abc'); }",
"actual parameter 1 of bar does not match formal parameter\n" +
"found : string\n" +
"required: RegExp");
}
public void testCall5() throws Exception {
testTypes("/**@param {!RegExp} a*/var foo = function bar(a){ foo('abc'); }",
"actual parameter 1 of foo does not match formal parameter\n" +
"found : string\n" +
"required: RegExp");
}
public void testCall6() throws Exception {
testTypes("/** @param {!Number} foo*/function bar(foo){}" +
"bar('abc');",
"actual parameter 1 of bar does not match formal parameter\n" +
"found : string\n" +
"required: Number");
}
public void testCall7() throws Exception {
testTypes("/** @param {!RegExp} a*/var foo = function bar(a){};" +
"foo('abc');",
"actual parameter 1 of foo does not match formal parameter\n" +
"found : string\n" +
"required: RegExp");
}
public void testCall8() throws Exception {
testTypes("/** @type {Function|number} */var f;f();",
"(Function|number) expressions are " +
"not callable");
}
public void testCall9() throws Exception {
testTypes(
"var goog = {};" +
"/** @constructor */ goog.Foo = function() {};" +
"/** @param {!goog.Foo} a */ var bar = function(a){};" +
"bar('abc');",
"actual parameter 1 of bar does not match formal parameter\n" +
"found : string\n" +
"required: goog.Foo");
}
public void testCall10() throws Exception {
testTypes("/** @type {Function} */var f;f();");
}
public void testCall11() throws Exception {
testTypes("var f = new Function(); f();");
}
public void testAbstractMethodCall1() throws Exception {
// Converted from Closure style "goog.base" super call
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"B.superClass_ = A.prototype",
"/** @override */ B.prototype.foo = function() { B.superClass_.foo.call(this); };"),
"Abstract super method A.prototype.foo cannot be called");
}
public void testAbstractMethodCall2() throws Exception {
// Converted from Closure style "goog.base" super call, with namespace
testTypes(
LINE_JOINER.join(
"/** @const */ var ns = {};",
"/** @constructor @abstract */ ns.A = function() {};",
"/** @abstract */ ns.A.prototype.foo = function() {};",
"/** @constructor @extends {ns.A} */ ns.B = function() {};",
"ns.B.superClass_ = ns.A.prototype",
"/** @override */ ns.B.prototype.foo = function() {",
" ns.B.superClass_.foo.call(this);",
"};"),
"Abstract super method ns.A.prototype.foo cannot be called");
}
public void testAbstractMethodCall3() throws Exception {
// Converted from ES6 super call
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() { A.prototype.foo.call(this); };"),
"Abstract super method A.prototype.foo cannot be called");
}
public void testAbstractMethodCall4() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @const */ var ns = {};",
"/** @constructor @abstract */ ns.A = function() {};",
"ns.A.prototype.foo = function() {};",
"/** @constructor @extends {ns.A} */ ns.B = function() {};",
"ns.B.superClass_ = ns.A.prototype",
"/** @override */ ns.B.prototype.foo = function() {",
" ns.B.superClass_.foo.call(this);",
"};"));
}
public void testAbstractMethodCall5() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ var A = function() {};",
"A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() { A.prototype.foo.call(this); };"));
}
public void testAbstractMethodCall6() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @const */ var ns = {};",
"/** @constructor @abstract */ ns.A = function() {};",
"ns.A.prototype.foo = function() {};",
"ns.A.prototype.foo.bar = function() {};",
"/** @constructor @extends {ns.A} */ ns.B = function() {};",
"ns.B.superClass_ = ns.A.prototype",
"/** @override */ ns.B.prototype.foo = function() {",
" ns.B.superClass_.foo.bar.call(this);",
"};"));
}
public void testAbstractMethodCall7() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ var A = function() {};",
"A.prototype.foo = function() {};",
"A.prototype.foo.bar = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() { A.prototype.foo.bar.call(this); };"));
}
public void testAbstractMethodCall8() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ var A = function() {};",
"A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() { A.prototype.foo['call'](this); };"));
}
public void testAbstractMethodCall9() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @struct @constructor */ var A = function() {};",
"A.prototype.foo = function() {};",
"/** @struct @constructor @extends {A} */ var B = function() {};",
"/** @override */ B.prototype.foo = function() {",
" (function() {",
" return A.prototype.foo.call($jscomp$this);",
" })();",
"};"));
}
public void testAbstractMethodCall10() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"A.prototype.foo.call(new Subtype);"),
"Abstract super method A.prototype.foo cannot be called");
}
public void testAbstractMethodCall11() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ function A() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ function B() {};",
"/** @override */ B.prototype.foo = function() {};",
"var abstractMethod = A.prototype.foo;",
"abstractMethod.call(new B);"),
"Abstract super method A.prototype.foo cannot be called");
}
public void testAbstractMethodCall12() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ var A = function() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ var B = function() {};",
"B.superClass_ = A.prototype",
"/** @override */ B.prototype.foo = function() { B.superClass_.foo.apply(this); };"),
"Abstract super method A.prototype.foo cannot be called");
}
public void testAbstractMethodCall13() throws Exception {
// Calling abstract @constructor is allowed
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ var A = function() {};",
"/** @constructor @extends {A} */ var B = function() { A.call(this); };"));
}
public void testAbstractMethodCall_Indirect1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ function A() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ function B() {};",
"/** @override */ B.prototype.foo = function() {};",
"var abstractMethod = A.prototype.foo;",
"(0, abstractMethod).call(new B);"),
"Abstract super method A.prototype.foo cannot be called");
}
public void testAbstractMethodCall_Indirect2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor @abstract */ function A() {};",
"/** @abstract */ A.prototype.foo = function() {};",
"/** @constructor @extends {A} */ function B() {};",
"/** @override */ B.prototype.foo = function() {};",
"var abstractMethod = A.prototype.foo;",
"(abstractMethod = abstractMethod).call(new B);"),
"Abstract super method A.prototype.foo cannot be called");
}
public void testAbstractMethodCall_Es6Class() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"/** @abstract */",
"class Base {",
" /** @abstract */",
" foo() {}",
" bar() {",
" this.foo();",
" }",
"}",
"class Sub extends Base {",
" /** @override */",
" foo() {}",
" /** @override */",
" bar() {",
" this.foo();",
" }",
"}"));
}
public void testAbstractMethodCall_Es6Class_prototype() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"/** @abstract */",
"class Base {",
" /** @abstract */",
" foo() {}",
"}",
"class Sub extends Base {",
" /** @override */",
" foo() {}",
" bar() {",
" Sub.prototype.foo();",
" }",
"}"));
}
public void testAbstractMethodCall_Es6Class_prototype_warning() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"/** @abstract */",
"class Base {",
" /** @abstract */",
" foo() {}",
"}",
"class Sub extends Base {",
" /** @override */",
" foo() {}",
" bar() {",
" Base.prototype.foo();",
" }",
"}"),
"Abstract super method Base.prototype.foo cannot be called");
}
public void testNonAbstractMethodCall_Es6Class_prototype() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"/** @abstract */",
"class Base {",
" /** @abstract */",
" foo() {}",
" bar() {}",
"}",
"class Sub extends Base {",
" /** @override */",
" foo() {}",
" /** @override */",
" bar() {",
" Base.prototype.bar();",
" }",
"}"));
}
// GitHub issue #2262: https://github.com/google/closure-compiler/issues/2262
public void testAbstractMethodCall_Es6ClassWithSpread() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"/** @abstract */",
"class Base {",
" /** @abstract */",
" foo() {}",
"}",
"class Sub extends Base {",
" /** @override */",
" foo() {}",
" /** @param {!Array} arr */",
" bar(arr) {",
" this.foo.apply(this, [].concat(arr));",
" }",
"}"));
}
public void testFunctionCall1() throws Exception {
testTypes(
"/** @param {number} x */ var foo = function(x) {};" +
"foo.call(null, 3);");
}
public void testFunctionCall2() throws Exception {
testTypes(
"/** @param {number} x */ var foo = function(x) {};" +
"foo.call(null, 'bar');",
"actual parameter 2 of foo.call does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testFunctionCall3() throws Exception {
testTypes(
"/** @param {number} x \n * @constructor */ " +
"var Foo = function(x) { this.bar.call(null, x); };" +
"/** @type {function(number)} */ Foo.prototype.bar;");
}
public void testFunctionCall4() throws Exception {
testTypes(
"/** @param {string} x \n * @constructor */ " +
"var Foo = function(x) { this.bar.call(null, x); };" +
"/** @type {function(number)} */ Foo.prototype.bar;",
"actual parameter 2 of this.bar.call " +
"does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testFunctionCall5() throws Exception {
testTypes(
"/** @param {Function} handler \n * @constructor */ " +
"var Foo = function(handler) { handler.call(this, x); };");
}
public void testFunctionCall6() throws Exception {
testTypes(
"/** @param {Function} handler \n * @constructor */ " +
"var Foo = function(handler) { handler.apply(this, x); };");
}
public void testFunctionCall7() throws Exception {
testTypes(
"/** @param {Function} handler \n * @param {Object} opt_context */ " +
"var Foo = function(handler, opt_context) { " +
" handler.call(opt_context, x);" +
"};");
}
public void testFunctionCall8() throws Exception {
testTypes(
"/** @param {Function} handler \n * @param {Object} opt_context */ " +
"var Foo = function(handler, opt_context) { " +
" handler.apply(opt_context, x);" +
"};");
}
public void testFunctionCall9() throws Exception {
testTypes(
"/** @constructor\n * @template T\n **/ function Foo() {}\n" +
"/** @param {T} x */ Foo.prototype.bar = function(x) {}\n" +
"var foo = /** @type {Foo<string>} */ (new Foo());\n" +
"foo.bar(3);",
"actual parameter 1 of Foo.prototype.bar does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testFunctionBind1() throws Exception {
testTypes(
"/** @type {function(string, number): boolean} */" +
"function f(x, y) { return true; }" +
"f.bind(null, 3);",
"actual parameter 2 of f.bind does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testFunctionBind2() throws Exception {
testTypes(
"/** @type {function(number): boolean} */" +
"function f(x) { return true; }" +
"f(f.bind(null, 3)());",
"actual parameter 1 of f does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testFunctionBind3() throws Exception {
testTypes(
"/** @type {function(number, string): boolean} */" +
"function f(x, y) { return true; }" +
"f.bind(null, 3)(true);",
"actual parameter 1 of function does not match formal parameter\n" +
"found : boolean\n" +
"required: string");
}
public void testFunctionBind4() throws Exception {
testTypes(
"/** @param {...number} x */" +
"function f(x) {}" +
"f.bind(null, 3, 3, 3)(true);",
"actual parameter 1 of function does not match formal parameter\n" +
"found : boolean\n" +
"required: (number|undefined)");
}
public void testFunctionBind5() throws Exception {
testTypes(
"/** @param {...number} x */" +
"function f(x) {}" +
"f.bind(null, true)(3, 3, 3);",
"actual parameter 2 of f.bind does not match formal parameter\n" +
"found : boolean\n" +
"required: (number|undefined)");
}
public void testFunctionBind6() throws Exception {
testTypes(LINE_JOINER.join(
"/** @constructor */",
"function MyType() {",
" /** @type {number} */",
" this.x = 0;",
" var f = function() {",
" this.x = 'str';",
" }.bind(this);",
"}"), LINE_JOINER.join(
"assignment to property x of MyType",
"found : string",
"required: number"));
}
public void testFunctionBind7() throws Exception {
testTypes(LINE_JOINER.join(
"/** @constructor */",
"function MyType() {",
" /** @type {number} */",
" this.x = 0;",
"}",
"var m = new MyType;",
"(function f() {this.x = 'str';}).bind(m);"),
LINE_JOINER.join(
"assignment to property x of MyType",
"found : string",
"required: number"));
}
public void testFunctionBind8() throws Exception {
testTypes(LINE_JOINER.join(
"/** @constructor */",
"function MyType() {}",
"",
"/** @constructor */",
"function AnotherType() {}",
"AnotherType.prototype.foo = function() {};",
"",
"/** @type {?} */",
"var m = new MyType;",
"(function f() {this.foo();}).bind(m);"),
(DiagnosticType) null);
}
public void testFunctionBind9() throws Exception {
testTypes(LINE_JOINER.join(
"/** @constructor */",
"function MyType() {}",
"",
"/** @constructor */",
"function AnotherType() {}",
"AnotherType.prototype.foo = function() {};",
"",
"var m = new MyType;",
"(function f() {this.foo();}).bind(m);"),
TypeCheck.INEXISTENT_PROPERTY);
}
public void testGoogBind1() throws Exception {
testClosureTypes(
"var goog = {}; goog.bind = function(var_args) {};" +
"/** @type {function(number): boolean} */" +
"function f(x, y) { return true; }" +
"f(goog.bind(f, null, 'x')());",
"actual parameter 1 of f does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testGoogBind2() throws Exception {
// TODO(nicksantos): We do not currently type-check the arguments
// of the goog.bind.
testClosureTypes(
"var goog = {}; goog.bind = function(var_args) {};" +
"/** @type {function(boolean): boolean} */" +
"function f(x, y) { return true; }" +
"f(goog.bind(f, null, 'x')());",
null);
}
public void testCast2() throws Exception {
// can upcast to a base type.
testTypes("/** @constructor */function base() {}\n" +
"/** @constructor\n @extends {base} */function derived() {}\n" +
"/** @type {base} */ var baz = new derived();\n");
}
public void testCast3() throws Exception {
// cannot downcast
testTypes("/** @constructor */function base() {}\n" +
"/** @constructor @extends {base} */function derived() {}\n" +
"/** @type {!derived} */ var baz = new base();\n",
"initializing variable\n" +
"found : base\n" +
"required: derived");
}
public void testCast3a() throws Exception {
// cannot downcast
testTypes("/** @constructor */function Base() {}\n" +
"/** @constructor @extends {Base} */function Derived() {}\n" +
"var baseInstance = new Base();" +
"/** @type {!Derived} */ var baz = baseInstance;\n",
"initializing variable\n" +
"found : Base\n" +
"required: Derived");
}
public void testCast4() throws Exception {
// downcast must be explicit
testTypes("/** @constructor */function base() {}\n" +
"/** @constructor\n * @extends {base} */function derived() {}\n" +
"/** @type {!derived} */ var baz = " +
"/** @type {!derived} */(new base());\n");
}
public void testCast4Types() throws Exception {
// downcast must be explicit
Node root = parseAndTypeCheck(
"/** @constructor */function base() {}\n" +
"/** @constructor\n * @extends {base} */function derived() {}\n" +
"/** @type {!derived} */ var baz = " +
"/** @type {!derived} */(new base());\n");
Node castedExprNode = root.getLastChild().getFirstFirstChild().getFirstChild();
assertEquals("derived", castedExprNode.getJSType().toString());
assertEquals("base", castedExprNode.getJSTypeBeforeCast().toString());
}
public void testCast5() throws Exception {
// cannot explicitly cast to an unrelated type
testTypes("/** @constructor */function foo() {}\n" +
"/** @constructor */function bar() {}\n" +
"var baz = /** @type {!foo} */(new bar);\n",
"invalid cast - must be a subtype or supertype\n" +
"from: bar\n" +
"to : foo");
}
public void testCast5a() throws Exception {
// cannot explicitly cast to an unrelated type
testTypes("/** @constructor */function foo() {}\n" +
"/** @constructor */function bar() {}\n" +
"var barInstance = new bar;\n" +
"var baz = /** @type {!foo} */(barInstance);\n",
"invalid cast - must be a subtype or supertype\n" +
"from: bar\n" +
"to : foo");
}
public void testCast6() throws Exception {
// can explicitly cast to a subtype or supertype
testTypes("/** @constructor */function foo() {}\n" +
"/** @constructor \n @extends foo */function bar() {}\n" +
"var baz = /** @type {!bar} */(new bar);\n" +
"var baz = /** @type {!foo} */(new foo);\n" +
"var baz = /** @type {bar} */(new bar);\n" +
"var baz = /** @type {foo} */(new foo);\n" +
"var baz = /** @type {!foo} */(new bar);\n" +
"var baz = /** @type {!bar} */(new foo);\n" +
"var baz = /** @type {foo} */(new bar);\n" +
"var baz = /** @type {bar} */(new foo);\n");
}
public void testCast7() throws Exception {
testTypes("var x = /** @type {foo} */ (new Object());",
"Bad type annotation. Unknown type foo");
}
public void testCast8() throws Exception {
testTypes("function f() { return /** @type {foo} */ (new Object()); }",
"Bad type annotation. Unknown type foo");
}
public void testCast9() throws Exception {
testTypes("var foo = {};" +
"function f() { return /** @type {foo} */ (new Object()); }",
"Bad type annotation. Unknown type foo");
}
public void testCast10() throws Exception {
testTypes("var foo = function() {};" +
"function f() { return /** @type {foo} */ (new Object()); }",
"Bad type annotation. Unknown type foo");
}
public void testCast11() throws Exception {
testTypes("var goog = {}; goog.foo = {};" +
"function f() { return /** @type {goog.foo} */ (new Object()); }",
"Bad type annotation. Unknown type goog.foo");
}
public void testCast12() throws Exception {
testTypes("var goog = {}; goog.foo = function() {};" +
"function f() { return /** @type {goog.foo} */ (new Object()); }",
"Bad type annotation. Unknown type goog.foo");
}
public void testCast13() throws Exception {
// Test to make sure that the forward-declaration still allows for
// a warning.
testClosureTypes("var goog = {}; " +
"goog.addDependency('zzz.js', ['goog.foo'], []);" +
"goog.foo = function() {};" +
"function f() { return /** @type {goog.foo} */ (new Object()); }",
"Bad type annotation. Unknown type goog.foo");
}
public void testCast14() throws Exception {
// Test to make sure that the forward-declaration still prevents
// some warnings.
testClosureTypes("var goog = {}; " +
"goog.addDependency('zzz.js', ['goog.bar'], []);" +
"function f() { return /** @type {goog.bar} */ (new Object()); }",
null);
}
public void testCast15() throws Exception {
// This fixes a bug where a type cast on an object literal
// would cause a run-time cast exception if the node was visited
// more than once.
//
// Some code assumes that an object literal must have a object type,
// while because of the cast, it could have any type (including
// a union).
compiler.getOptions().setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT_2015);
testTypes(
"for (var i = 0; i < 10; i++) {" +
"var x = /** @type {Object|number} */ ({foo: 3});" +
"/** @param {number} x */ function f(x) {}" +
"f(x.foo);" +
"f([].foo);" +
"}",
"Property foo never defined on Array");
}
public void testCast16() throws Exception {
// A type cast should not invalidate the checks on the members
testTypes(
"for (var i = 0; i < 10; i++) {" +
"var x = /** @type {Object|number} */ (" +
" {/** @type {string} */ foo: 3});" +
"}",
"assignment to property foo of {foo: string}\n" +
"found : number\n" +
"required: string");
}
public void testCast17a() throws Exception {
// Mostly verifying that rhino actually understands these JsDocs.
testTypes("/** @constructor */ function Foo() {} \n" +
"/** @type {Foo} */ var x = /** @type {Foo} */ (y)");
testTypes("/** @constructor */ function Foo() {} \n" +
"/** @type {Foo} */ var x = /** @type {Foo} */ (y)");
}
public void testCast17b() throws Exception {
// Mostly verifying that rhino actually understands these JsDocs.
testTypes("/** @constructor */ function Foo() {} \n" +
"/** @type {Foo} */ var x = /** @type {Foo} */ ({})");
}
public void testCast19() throws Exception {
testTypes(
"var x = 'string';\n" +
"/** @type {number} */\n" +
"var y = /** @type {number} */(x);",
"invalid cast - must be a subtype or supertype\n" +
"from: string\n" +
"to : number");
}
public void testCast20() throws Exception {
testTypes(
"/** @enum {boolean|null} */\n" +
"var X = {" +
" AA: true," +
" BB: false," +
" CC: null" +
"};\n" +
"var y = /** @type {X} */(true);");
}
public void testCast21() throws Exception {
testTypes(
"/** @enum {boolean|null} */\n" +
"var X = {" +
" AA: true," +
" BB: false," +
" CC: null" +
"};\n" +
"var value = true;\n" +
"var y = /** @type {X} */(value);");
}
public void testCast22() throws Exception {
testTypes(
"var x = null;\n" +
"var y = /** @type {number} */(x);",
"invalid cast - must be a subtype or supertype\n" +
"from: null\n" +
"to : number");
}
public void testCast23() throws Exception {
testTypes(
"var x = null;\n" +
"var y = /** @type {Number} */(x);");
}
public void testCast24() throws Exception {
testTypes(
"var x = undefined;\n" +
"var y = /** @type {number} */(x);",
"invalid cast - must be a subtype or supertype\n" +
"from: undefined\n" +
"to : number");
}
public void testCast25() throws Exception {
testTypes(
"var x = undefined;\n" +
"var y = /** @type {number|undefined} */(x);");
}
public void testCast26() throws Exception {
testTypes(
"function fn(dir) {\n" +
" var node = dir ? 1 : 2;\n" +
" fn(/** @type {number} */ (node));\n" +
"}");
}
public void testCast27() throws Exception {
// C doesn't implement I but a subtype might.
testTypes(
"/** @interface */ function I() {}\n" +
"/** @constructor */ function C() {}\n" +
"var x = new C();\n" +
"var y = /** @type {I} */(x);");
}
public void testCast27a() throws Exception {
// C doesn't implement I but a subtype might.
testTypes(
"/** @interface */ function I() {}\n" +
"/** @constructor */ function C() {}\n" +
"/** @type {C} */ var x ;\n" +
"var y = /** @type {I} */(x);");
}
public void testCast28() throws Exception {
// C doesn't implement I but a subtype might.
testTypes(
"/** @interface */ function I() {}\n" +
"/** @constructor */ function C() {}\n" +
"/** @type {!I} */ var x;\n" +
"var y = /** @type {C} */(x);");
}
public void testCast28a() throws Exception {
// C doesn't implement I but a subtype might.
testTypes(
"/** @interface */ function I() {}\n" +
"/** @constructor */ function C() {}\n" +
"/** @type {I} */ var x;\n" +
"var y = /** @type {C} */(x);");
}
public void testCast29a() throws Exception {
// C doesn't implement the record type but a subtype might.
testTypes(
"/** @constructor */ function C() {}\n" +
"var x = new C();\n" +
"var y = /** @type {{remoteJids: Array, sessionId: string}} */(x);");
}
public void testCast29b() throws Exception {
// C doesn't implement the record type but a subtype might.
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {C} */ var x;\n" +
"var y = /** @type {{prop1: Array, prop2: string}} */(x);");
}
public void testCast29c() throws Exception {
// C doesn't implement the record type but a subtype might.
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {{remoteJids: Array, sessionId: string}} */ var x ;\n" +
"var y = /** @type {C} */(x);");
}
public void testCast30() throws Exception {
// Should be able to cast to a looser return type
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {function():string} */ var x ;\n" +
"var y = /** @type {function():?} */(x);");
}
public void testCast31() throws Exception {
// Should be able to cast to a tighter parameter type
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {function(*)} */ var x ;\n" +
"var y = /** @type {function(string)} */(x);");
}
public void testCast32() throws Exception {
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {Object} */ var x ;\n" +
"var y = /** @type {null|{length:number}} */(x);");
}
public void testCast33() throws Exception {
// null and void should be assignable to any type that accepts one or the
// other or both.
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {null|undefined} */ var x ;\n" +
"var y = /** @type {string?|undefined} */(x);");
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {null|undefined} */ var x ;\n" +
"var y = /** @type {string|undefined} */(x);");
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {null|undefined} */ var x ;\n" +
"var y = /** @type {string?} */(x);");
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {null|undefined} */ var x ;\n" +
"var y = /** @type {null} */(x);");
}
public void testCast34a() throws Exception {
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {Object} */ var x ;\n" +
"var y = /** @type {Function} */(x);");
}
public void testCast34b() throws Exception {
testTypes(
"/** @constructor */ function C() {}\n" +
"/** @type {Function} */ var x ;\n" +
"var y = /** @type {Object} */(x);");
}
public void testNestedCasts() throws Exception {
testTypes("/** @constructor */var T = function() {};\n" +
"/** @constructor */var V = function() {};\n" +
"/**\n" +
"* @param {boolean} b\n" +
"* @return {T|V}\n" +
"*/\n" +
"function f(b) { return b ? new T() : new V(); }\n" +
"/**\n" +
"* @param {boolean} b\n" +
"* @return {boolean|undefined}\n" +
"*/\n" +
"function g(b) { return b ? true : undefined; }\n" +
"/** @return {T} */\n" +
"function h() {\n" +
"return /** @type {T} */ (f(/** @type {boolean} */ (g(true))));\n" +
"}");
}
public void testNativeCast1() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"f(String(true));",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testNativeCast2() throws Exception {
testTypes(
"/** @param {string} x */ function f(x) {}" +
"f(Number(true));",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testNativeCast3() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"f(Boolean(''));",
"actual parameter 1 of f does not match formal parameter\n" +
"found : boolean\n" +
"required: number");
}
public void testNativeCast4() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"f(Error(''));",
"actual parameter 1 of f does not match formal parameter\n" +
"found : Error\n" +
"required: number");
}
public void testBadConstructorCall() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo();",
"Constructor function (new:Foo): undefined should be called " +
"with the \"new\" keyword");
}
public void testTypeof() throws Exception {
testTypes("/**@return {void}*/function foo(){ var a = typeof foo(); }");
}
public void testTypeof2() throws Exception {
testTypes("function f(){ if (typeof 123 == 'numbr') return 321; }",
"unknown type: numbr");
}
public void testTypeof3() throws Exception {
testTypes("function f() {" +
"return (typeof 123 == 'number' ||" +
"typeof 123 == 'string' ||" +
"typeof 123 == 'boolean' ||" +
"typeof 123 == 'undefined' ||" +
"typeof 123 == 'function' ||" +
"typeof 123 == 'object' ||" +
"typeof 123 == 'unknown'); }");
}
public void testConstDecl1() throws Exception {
testTypes(
"/** @param {?number} x \n @return {boolean} */" +
"function f(x) { " +
" if (x) { /** @const */ var y = x; return y } return true; " +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: boolean");
}
public void testConstDecl2() throws Exception {
compiler.getOptions().setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT_2015);
testTypes(
"/** @param {?number} x */" +
"function f(x) { " +
" if (x) {" +
" /** @const */ var y = x; " +
" /** @return {boolean} */ function g() { return y; } " +
" }" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: boolean");
}
public void testConstructorType1() throws Exception {
testTypes("/**@constructor*/function Foo(){}" +
"/**@type{!Foo}*/var f = new Date();",
"initializing variable\n" +
"found : Date\n" +
"required: Foo");
}
public void testConstructorType2() throws Exception {
testTypes("/**@constructor*/function Foo(){\n" +
"/**@type{Number}*/this.bar = new Number(5);\n" +
"}\n" +
"/**@type{Foo}*/var f = new Foo();\n" +
"/**@type{Number}*/var n = f.bar;");
}
public void testConstructorType3() throws Exception {
// Reverse the declaration order so that we know that Foo is getting set
// even on an out-of-order declaration sequence.
testTypes("/**@type{Foo}*/var f = new Foo();\n" +
"/**@type{Number}*/var n = f.bar;" +
"/**@constructor*/function Foo(){\n" +
"/**@type{Number}*/this.bar = new Number(5);\n" +
"}\n");
}
public void testConstructorType4() throws Exception {
testTypes("/**@constructor*/function Foo(){\n" +
"/**@type{!Number}*/this.bar = new Number(5);\n" +
"}\n" +
"/**@type{!Foo}*/var f = new Foo();\n" +
"/**@type{!String}*/var n = f.bar;",
"initializing variable\n" +
"found : Number\n" +
"required: String");
}
public void testConstructorType5() throws Exception {
testTypes("/**@constructor*/function Foo(){}\n" +
"if (Foo){}\n");
}
public void testConstructorType6() throws Exception {
testTypes("/** @constructor */\n" +
"function bar() {}\n" +
"function _foo() {\n" +
" /** @param {bar} x */\n" +
" function f(x) {}\n" +
"}");
}
public void testConstructorType7() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @constructor */function A(){};");
JSType type = p.scope.getVar("A").getType();
assertThat(type).isInstanceOf(FunctionType.class);
FunctionType fType = (FunctionType) type;
assertEquals("A", fType.getReferenceName());
}
public void testConstructorType8() throws Exception {
testTypes(
"var ns = {};" +
"ns.create = function() { return function() {}; };" +
"/** @constructor */ ns.Foo = ns.create();" +
"ns.Foo.prototype = {x: 0, y: 0};" +
"/**\n" +
" * @param {ns.Foo} foo\n" +
" * @return {string}\n" +
" */\n" +
"function f(foo) {" +
" return foo.x;" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testConstructorType9() throws Exception {
testTypes(
"var ns = {};" +
"ns.create = function() { return function() {}; };" +
"ns.extend = function(x) { return x; };" +
"/** @constructor */ ns.Foo = ns.create();" +
"ns.Foo.prototype = ns.extend({x: 0, y: 0});" +
"/**\n" +
" * @param {ns.Foo} foo\n" +
" * @return {string}\n" +
" */\n" +
"function f(foo) {" +
" return foo.x;" +
"}");
}
public void testConstructorType10() throws Exception {
testTypes("/** @constructor */" +
"function NonStr() {}" +
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" * @extends{NonStr}\n" +
" */" +
"function NonStrKid() {}");
}
public void testConstructorType11() throws Exception {
testTypes("/** @constructor */" +
"function NonDict() {}" +
"/**\n" +
" * @constructor\n" +
" * @dict\n" +
" * @extends{NonDict}\n" +
" */" +
"function NonDictKid() {}");
}
public void testConstructorType12() throws Exception {
testTypes("/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Bar() {}\n" +
"Bar.prototype = {};\n");
}
public void testBadStruct() throws Exception {
testTypes("/** @struct */function Struct1() {}",
"@struct used without @constructor for Struct1");
}
public void testBadDict() throws Exception {
testTypes("/** @dict */function Dict1() {}",
"@dict used without @constructor for Dict1");
}
public void testAnonymousPrototype1() throws Exception {
testTypes(
"var ns = {};" +
"/** @constructor */ ns.Foo = function() {" +
" this.bar(3, 5);" +
"};" +
"ns.Foo.prototype = {" +
" bar: function(x) {}" +
"};",
"Function ns.Foo.prototype.bar: called with 2 argument(s). " +
"Function requires at least 1 argument(s) and no more " +
"than 1 argument(s).");
}
public void testAnonymousPrototype2() throws Exception {
testTypes(
"/** @interface */ var Foo = function() {};" +
"Foo.prototype = {" +
" foo: function(x) {}" +
"};" +
"/**\n" +
" * @constructor\n" +
" * @implements {Foo}\n" +
" */ var Bar = function() {};",
"property foo on interface Foo is not implemented by type Bar");
}
public void testAnonymousType1() throws Exception {
testTypes("function f() { return {}; }" +
"/** @constructor */\n" +
"f().bar = function() {};");
}
public void testAnonymousType2() throws Exception {
testTypes("function f() { return {}; }" +
"/** @interface */\n" +
"f().bar = function() {};");
}
public void testAnonymousType3() throws Exception {
testTypes("function f() { return {}; }" +
"/** @enum */\n" +
"f().bar = {FOO: 1};");
}
public void testBang1() throws Exception {
testTypes("/** @param {Object} x\n@return {!Object} */\n" +
"function f(x) { return x; }",
"inconsistent return type\n" +
"found : (Object|null)\n" +
"required: Object");
}
public void testBang2() throws Exception {
testTypes("/** @param {Object} x\n@return {!Object} */\n" +
"function f(x) { return x ? x : new Object(); }");
}
public void testBang3() throws Exception {
testTypes("/** @param {Object} x\n@return {!Object} */\n" +
"function f(x) { return /** @type {!Object} */ (x); }");
}
public void testBang4() throws Exception {
testTypes("/**@param {Object} x\n@param {Object} y\n@return {boolean}*/\n" +
"function f(x, y) {\n" +
"if (typeof x != 'undefined') { return x == y; }\n" +
"else { return x != y; }\n}");
}
public void testBang5() throws Exception {
testTypes("/**@param {Object} x\n@param {Object} y\n@return {boolean}*/\n" +
"function f(x, y) { return !!x && x == y; }");
}
public void testBang6() throws Exception {
testTypes("/** @param {Object?} x\n@return {Object} */\n" +
"function f(x) { return x; }");
}
public void testBang7() throws Exception {
testTypes("/**@param {(Object|string|null)} x\n" +
"@return {(Object|string)}*/function f(x) { return x; }");
}
public void testDefinePropertyOnNullableObject1() throws Exception {
testTypes("/** @type {Object} */ var n = {};\n" +
"/** @type {number} */ n.x = 1;\n" +
"/** @return {boolean} */function f() { return n.x; }",
"inconsistent return type\n" +
"found : number\n" +
"required: boolean");
}
public void testDefinePropertyOnNullableObject2() throws Exception {
testTypes("/** @constructor */ var T = function() {};\n" +
"/** @param {T} t\n@return {boolean} */function f(t) {\n" +
"t.x = 1; return t.x; }",
"inconsistent return type\n" +
"found : number\n" +
"required: boolean");
}
public void testUnknownConstructorInstanceType1() throws Exception {
testTypes("/** @return {Array} */ function g(f) { return new f(); }");
}
public void testUnknownConstructorInstanceType2() throws Exception {
testTypes("function g(f) { return /** @type {Array} */(new f()); }");
}
public void testUnknownConstructorInstanceType3() throws Exception {
testTypes("function g(f) { var x = new f(); x.a = 1; return x; }");
}
public void testUnknownPrototypeChain() throws Exception {
testTypes("/**\n" +
"* @param {Object} co\n" +
" * @return {Object}\n" +
" */\n" +
"function inst(co) {\n" +
" /** @constructor */\n" +
" var c = function() {};\n" +
" c.prototype = co.prototype;\n" +
" return new c;\n" +
"}");
}
public void testNamespacedConstructor() throws Exception {
Node root = parseAndTypeCheck(
"var goog = {};" +
"/** @constructor */ goog.MyClass = function() {};" +
"/** @return {!goog.MyClass} */ " +
"function foo() { return new goog.MyClass(); }");
JSType typeOfFoo = root.getLastChild().getJSType();
assert(typeOfFoo instanceof FunctionType);
JSType retType = ((FunctionType) typeOfFoo).getReturnType();
assert(retType instanceof ObjectType);
assertEquals("goog.MyClass", ((ObjectType) retType).getReferenceName());
}
public void testComplexNamespace() throws Exception {
String js =
"var goog = {};" +
"goog.foo = {};" +
"goog.foo.bar = 5;";
TypeCheckResult p = parseAndTypeCheckWithScope(js);
// goog type in the scope
JSType googScopeType = p.scope.getVar("goog").getType();
assertThat(googScopeType).isInstanceOf(ObjectType.class);
assertTrue("foo property not present on goog type",
googScopeType.hasProperty("foo"));
assertFalse("bar property present on goog type",
googScopeType.hasProperty("bar"));
// goog type on the VAR node
Node varNode = p.root.getFirstChild();
assertEquals(Token.VAR, varNode.getToken());
JSType googNodeType = varNode.getFirstChild().getJSType();
assertThat(googNodeType).isInstanceOf(ObjectType.class);
// goog scope type and goog type on VAR node must be the same
assertSame(googNodeType, googScopeType);
// goog type on the left of the GETPROP node (under fist ASSIGN)
Node getpropFoo1 = varNode.getNext().getFirstFirstChild();
assertEquals(Token.GETPROP, getpropFoo1.getToken());
assertEquals("goog", getpropFoo1.getFirstChild().getString());
JSType googGetpropFoo1Type = getpropFoo1.getFirstChild().getJSType();
assertThat(googGetpropFoo1Type).isInstanceOf(ObjectType.class);
// still the same type as the one on the variable
assertSame(googScopeType, googGetpropFoo1Type);
// the foo property should be defined on goog
JSType googFooType = ((ObjectType) googScopeType).getPropertyType("foo");
assertThat(googFooType).isInstanceOf(ObjectType.class);
// goog type on the left of the GETPROP lower level node
// (under second ASSIGN)
Node getpropFoo2 = varNode.getNext().getNext()
.getFirstFirstChild().getFirstChild();
assertEquals(Token.GETPROP, getpropFoo2.getToken());
assertEquals("goog", getpropFoo2.getFirstChild().getString());
JSType googGetpropFoo2Type = getpropFoo2.getFirstChild().getJSType();
assertThat(googGetpropFoo2Type).isInstanceOf(ObjectType.class);
// still the same type as the one on the variable
assertSame(googScopeType, googGetpropFoo2Type);
// goog.foo type on the left of the top-level GETPROP node
// (under second ASSIGN)
JSType googFooGetprop2Type = getpropFoo2.getJSType();
assertTrue("goog.foo incorrectly annotated in goog.foo.bar selection",
googFooGetprop2Type instanceof ObjectType);
ObjectType googFooGetprop2ObjectType = (ObjectType) googFooGetprop2Type;
assertFalse("foo property present on goog.foo type",
googFooGetprop2ObjectType.hasProperty("foo"));
assertTrue("bar property not present on goog.foo type",
googFooGetprop2ObjectType.hasProperty("bar"));
assertTypeEquals("bar property on goog.foo type incorrectly inferred",
NUMBER_TYPE, googFooGetprop2ObjectType.getPropertyType("bar"));
}
public void testAddingMethodsUsingPrototypeIdiomSimpleNamespace() throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype.m1 = 5");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals(NATIVE_PROPERTIES_COUNT + 1,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
}
public void testAddingMethodsUsingPrototypeIdiomComplexNamespace1() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"goog.A = /** @constructor */function() {};" +
"/** @type {number} */goog.A.prototype.m1 = 5");
testAddingMethodsUsingPrototypeIdiomComplexNamespace(p);
}
public void testAddingMethodsUsingPrototypeIdiomComplexNamespace2() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"/** @constructor */goog.A = function() {};" +
"/** @type {number} */goog.A.prototype.m1 = 5");
testAddingMethodsUsingPrototypeIdiomComplexNamespace(p);
}
private void testAddingMethodsUsingPrototypeIdiomComplexNamespace(
TypeCheckResult p) {
ObjectType goog = (ObjectType) p.scope.getVar("goog").getType();
assertEquals(NATIVE_PROPERTIES_COUNT + 1, goog.getPropertiesCount());
JSType googA = goog.getPropertyType("A");
assertNotNull(googA);
assertThat(googA).isInstanceOf(FunctionType.class);
FunctionType googAFunction = (FunctionType) googA;
ObjectType classA = googAFunction.getInstanceType();
assertEquals(NATIVE_PROPERTIES_COUNT + 1, classA.getPropertiesCount());
checkObjectType(classA, "m1", NUMBER_TYPE);
}
public void testAddingMethodsPrototypeIdiomAndObjectLiteralSimpleNamespace() throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype = {m1: 5, m2: true}");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals(NATIVE_PROPERTIES_COUNT + 2,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
checkObjectType(instanceType, "m2", BOOLEAN_TYPE);
}
public void testDontAddMethodsIfNoConstructor() throws Exception {
Node js1Node = parseAndTypeCheck(
"function A() {}" +
"A.prototype = {m1: 5, m2: true}");
JSType functionAType = js1Node.getFirstChild().getJSType();
assertEquals("function (): undefined", functionAType.toString());
assertTypeEquals(UNKNOWN_TYPE,
U2U_FUNCTION_TYPE.getPropertyType("m1"));
assertTypeEquals(UNKNOWN_TYPE,
U2U_FUNCTION_TYPE.getPropertyType("m2"));
}
public void testFunctionAssignement() throws Exception {
testTypes("/**" +
"* @param {string} ph0" +
"* @param {string} ph1" +
"* @return {string}" +
"*/" +
"function MSG_CALENDAR_ACCESS_ERROR(ph0, ph1) {return ''}" +
"/** @type {Function} */" +
"var MSG_CALENDAR_ADD_ERROR = MSG_CALENDAR_ACCESS_ERROR;");
}
public void testAddMethodsPrototypeTwoWays() throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype = {m1: 5, m2: true};" +
"A.prototype.m3 = 'third property!';");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals("A", instanceType.toString());
assertEquals(NATIVE_PROPERTIES_COUNT + 3,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
checkObjectType(instanceType, "m2", BOOLEAN_TYPE);
checkObjectType(instanceType, "m3", STRING_TYPE);
}
public void testPrototypePropertyTypes() throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {\n" +
" /** @type {string} */ this.m1;\n" +
" /** @type {Object?} */ this.m2 = {};\n" +
" /** @type {boolean} */ this.m3;\n" +
"}\n" +
"/** @type {string} */ A.prototype.m4;\n" +
"/** @type {number} */ A.prototype.m5 = 0;\n" +
"/** @type {boolean} */ A.prototype.m6;\n");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals(NATIVE_PROPERTIES_COUNT + 6,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", STRING_TYPE);
checkObjectType(instanceType, "m2",
createUnionType(OBJECT_TYPE, NULL_TYPE));
checkObjectType(instanceType, "m3", BOOLEAN_TYPE);
checkObjectType(instanceType, "m4", STRING_TYPE);
checkObjectType(instanceType, "m5", NUMBER_TYPE);
checkObjectType(instanceType, "m6", BOOLEAN_TYPE);
}
public void testValueTypeBuiltInPrototypePropertyType() throws Exception {
Node node = parseAndTypeCheck("\"x\".charAt(0)");
assertTypeEquals(STRING_TYPE, node.getFirstFirstChild().getJSType());
}
public void testDeclareBuiltInConstructor() throws Exception {
// Built-in prototype properties should be accessible
// even if the built-in constructor is declared.
Node node = parseAndTypeCheck(
"/** @constructor */ var String = function(opt_str) {};\n" +
"(new String(\"x\")).charAt(0)");
assertTypeEquals(STRING_TYPE, node.getLastChild().getFirstChild().getJSType());
}
public void testExtendBuiltInType1() throws Exception {
String externs =
"/** @constructor */ var String = function(opt_str) {};\n" +
"/**\n" +
"* @param {number} start\n" +
"* @param {number} opt_length\n" +
"* @return {string}\n" +
"*/\n" +
"String.prototype.substr = function(start, opt_length) {};\n";
Node n1 = parseAndTypeCheck(externs + "(new String(\"x\")).substr(0,1);");
assertTypeEquals(STRING_TYPE, n1.getLastChild().getFirstChild().getJSType());
}
public void testExtendBuiltInType2() throws Exception {
String externs =
"/** @constructor */ var String = function(opt_str) {};\n" +
"/**\n" +
"* @param {number} start\n" +
"* @param {number} opt_length\n" +
"* @return {string}\n" +
"*/\n" +
"String.prototype.substr = function(start, opt_length) {};\n";
Node n2 = parseAndTypeCheck(externs + "\"x\".substr(0,1);");
assertTypeEquals(STRING_TYPE, n2.getLastChild().getFirstChild().getJSType());
}
public void testExtendFunction1() throws Exception {
Node n = parseAndTypeCheck("/**@return {number}*/Function.prototype.f = " +
"function() { return 1; };\n" +
"(new Function()).f();");
JSType type = n.getLastChild().getLastChild().getJSType();
assertTypeEquals(NUMBER_TYPE, type);
}
public void testExtendFunction2() throws Exception {
Node n = parseAndTypeCheck("/**@return {number}*/Function.prototype.f = " +
"function() { return 1; };\n" +
"(function() {}).f();");
JSType type = n.getLastChild().getLastChild().getJSType();
assertTypeEquals(NUMBER_TYPE, type);
}
public void testInheritanceCheck1() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"Sub.prototype.foo = function() {};");
}
public void testInheritanceCheck2() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};",
"property foo not defined on any superclass of Sub");
}
public void testInheritanceCheck3() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"Super.prototype.foo = function() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"Sub.prototype.foo = function() {};",
"property foo already defined on superclass Super; " +
"use @override to override it");
}
public void testInheritanceCheck4() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"Super.prototype.foo = function() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};");
}
public void testInheritanceCheck5() throws Exception {
testTypes(
"/** @constructor */function Root() {};" +
"Root.prototype.foo = function() {};" +
"/** @constructor\n @extends {Root} */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"Sub.prototype.foo = function() {};",
"property foo already defined on superclass Root; " +
"use @override to override it");
}
public void testInheritanceCheck6() throws Exception {
testTypes(
"/** @constructor */function Root() {};" +
"Root.prototype.foo = function() {};" +
"/** @constructor\n @extends {Root} */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};");
}
public void testInheritanceCheck7() throws Exception {
testTypes(
"var goog = {};" +
"/** @constructor */goog.Super = function() {};" +
"goog.Super.prototype.foo = 3;" +
"/** @constructor\n @extends {goog.Super} */goog.Sub = function() {};" +
"goog.Sub.prototype.foo = 5;");
}
public void testInheritanceCheck8() throws Exception {
testTypes(
"var goog = {};" +
"/** @constructor */goog.Super = function() {};" +
"goog.Super.prototype.foo = 3;" +
"/** @constructor\n @extends {goog.Super} */goog.Sub = function() {};" +
"/** @override */goog.Sub.prototype.foo = 5;");
}
public void testInheritanceCheck9_1() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"Super.prototype.foo = function() { return 3; };" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override\n @return {number} */Sub.prototype.foo =\n" +
"function() { return 1; };");
}
public void testInheritanceCheck9_2() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"/** @return {number} */" +
"Super.prototype.foo = function() { return 1; };" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo =\n" +
"function() {};");
}
public void testInheritanceCheck9_3() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"/** @return {number} */" +
"Super.prototype.foo = function() { return 1; };" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override\n @return {string} */Sub.prototype.foo =\n" +
"function() { return \"some string\" };",
"mismatch of the foo property type and the type of the property it " +
"overrides from superclass Super\n" +
"original: function (this:Super): number\n" +
"override: function (this:Sub): string");
}
public void testInheritanceCheck10_1() throws Exception {
testTypes(
"/** @constructor */function Root() {};" +
"Root.prototype.foo = function() { return 3; };" +
"/** @constructor\n @extends {Root} */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override\n @return {number} */Sub.prototype.foo =\n" +
"function() { return 1; };");
}
public void testInheritanceCheck10_2() throws Exception {
testTypes(
"/** @constructor */function Root() {};" +
"/** @return {number} */" +
"Root.prototype.foo = function() { return 1; };" +
"/** @constructor\n @extends {Root} */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo =\n" +
"function() {};");
}
public void testInheritanceCheck10_3() throws Exception {
testTypes(
"/** @constructor */function Root() {};" +
"/** @return {number} */" +
"Root.prototype.foo = function() { return 1; };" +
"/** @constructor\n @extends {Root} */function Super() {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override\n @return {string} */Sub.prototype.foo =\n" +
"function() { return \"some string\" };",
"mismatch of the foo property type and the type of the property it " +
"overrides from superclass Root\n" +
"original: function (this:Root): number\n" +
"override: function (this:Sub): string");
}
public void testInterfaceInheritanceCheck11() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"/** @param {number} bar */Super.prototype.foo = function(bar) {};" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override\n @param {string} bar */Sub.prototype.foo =\n" +
"function(bar) {};",
"mismatch of the foo property type and the type of the property it " +
"overrides from superclass Super\n" +
"original: function (this:Super, number): undefined\n" +
"override: function (this:Sub, string): undefined");
}
public void testInheritanceCheck12() throws Exception {
testTypes(
"var goog = {};" +
"/** @constructor */goog.Super = function() {};" +
"goog.Super.prototype.foo = 3;" +
"/** @constructor\n @extends {goog.Super} */goog.Sub = function() {};" +
"/** @override */goog.Sub.prototype.foo = \"some string\";");
}
public void testInheritanceCheck13() throws Exception {
testTypes(
"var goog = {};\n" +
"/** @constructor\n @extends {goog.Missing} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};",
"Bad type annotation. Unknown type goog.Missing");
}
public void testInheritanceCheck14() throws Exception {
testClosureTypes(
"var goog = {};\n" +
"/** @constructor\n @extends {goog.Missing} */\n" +
"goog.Super = function() {};\n" +
"/** @constructor\n @extends {goog.Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};",
"Bad type annotation. Unknown type goog.Missing");
}
public void testInheritanceCheck15() throws Exception {
testTypes(
"/** @constructor */function Super() {};" +
"/** @param {number} bar */Super.prototype.foo;" +
"/** @constructor\n @extends {Super} */function Sub() {};" +
"/** @override\n @param {number} bar */Sub.prototype.foo =\n" +
"function(bar) {};");
}
public void testInheritanceCheck16() throws Exception {
testTypes(
"var goog = {};" +
"/** @constructor */goog.Super = function() {};" +
"/** @type {number} */ goog.Super.prototype.foo = 3;" +
"/** @constructor\n @extends {goog.Super} */goog.Sub = function() {};" +
"/** @type {number} */ goog.Sub.prototype.foo = 5;",
"property foo already defined on superclass goog.Super; " +
"use @override to override it");
}
public void testInheritanceCheck17() throws Exception {
// Make sure this warning still works, even when there's no
// @override tag.
testTypes(
"var goog = {};"
+ "/** @constructor */goog.Super = function() {};"
+ "/** @param {number} x */ goog.Super.prototype.foo = function(x) {};"
+ "/** @constructor\n @extends {goog.Super} */goog.Sub = function() {};"
+ "/** @override @param {string} x */ goog.Sub.prototype.foo = function(x) {};",
"mismatch of the foo property type and the type of the property it "
+ "overrides from superclass goog.Super\n"
+ "original: function (this:goog.Super, number): undefined\n"
+ "override: function (this:goog.Sub, string): undefined");
}
public void testInterfacePropertyOverride1() throws Exception {
testTypes(
"/** @interface */function Super() {};" +
"Super.prototype.foo = function() {};" +
"/** @interface\n @extends {Super} */function Sub() {};" +
"Sub.prototype.foo = function() {};");
}
public void testInterfacePropertyOverride2() throws Exception {
testTypes(
"/** @interface */function Root() {};" +
"Root.prototype.foo = function() {};" +
"/** @interface\n @extends {Root} */function Super() {};" +
"/** @interface\n @extends {Super} */function Sub() {};" +
"Sub.prototype.foo = function() {};");
}
public void testInterfaceInheritanceCheck1() throws Exception {
testTypes(
"/** @interface */function Super() {};" +
"Super.prototype.foo = function() {};" +
"/** @constructor\n @implements {Super} */function Sub() {};" +
"Sub.prototype.foo = function() {};",
"property foo already defined on interface Super; use @override to " +
"override it");
}
public void testInterfaceInheritanceCheck2() throws Exception {
testTypes(
"/** @interface */function Super() {};" +
"Super.prototype.foo = function() {};" +
"/** @constructor\n @implements {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};");
}
public void testInterfaceInheritanceCheck3() throws Exception {
testTypes(
"/** @interface */function Root() {};" +
"/** @return {number} */Root.prototype.foo = function() {};" +
"/** @interface\n @extends {Root} */function Super() {};" +
"/** @constructor\n @implements {Super} */function Sub() {};" +
"/** @return {number} */Sub.prototype.foo = function() { return 1;};",
"property foo already defined on interface Root; use @override to " +
"override it");
}
public void testInterfaceInheritanceCheck4() throws Exception {
testTypes(
"/** @interface */function Root() {};" +
"/** @return {number} */Root.prototype.foo = function() {};" +
"/** @interface\n @extends {Root} */function Super() {};" +
"/** @constructor\n @implements {Super} */function Sub() {};" +
"/** @override\n * @return {number} */Sub.prototype.foo =\n" +
"function() { return 1;};");
}
public void testInterfaceInheritanceCheck5() throws Exception {
testTypes(
"/** @interface */function Super() {};"
+ "/** @return {string} */Super.prototype.foo = function() {};"
+ "/** @constructor\n @implements {Super} */function Sub() {};"
+ "/** @override\n @return {number} */Sub.prototype.foo = function() { return 1; };",
"mismatch of the foo property on type Sub and the type of the property it "
+ "overrides from interface Super\n"
+ "original: function (this:Super): string\n"
+ "override: function (this:Sub): number");
}
public void testInterfaceInheritanceCheck6() throws Exception {
testTypes(
"/** @interface */function Root() {};"
+ "/** @return {string} */Root.prototype.foo = function() {};"
+ "/** @interface\n @extends {Root} */function Super() {};"
+ "/** @constructor\n @implements {Super} */function Sub() {};"
+ "/** @override\n @return {number} */Sub.prototype.foo = function() { return 1; };",
"mismatch of the foo property on type Sub and the type of the property it "
+ "overrides from interface Root\n"
+ "original: function (this:Root): string\n"
+ "override: function (this:Sub): number");
}
public void testInterfaceInheritanceCheck7() throws Exception {
testTypes(
"/** @interface */function Super() {};"
+ "/** @param {number} bar */Super.prototype.foo = function(bar) {};"
+ "/** @constructor\n @implements {Super} */function Sub() {};"
+ "/** @override\n @param {string} bar */Sub.prototype.foo =\n"
+ "function(bar) {};",
"mismatch of the foo property on type Sub and the type of the property it "
+ "overrides from interface Super\n"
+ "original: function (this:Super, number): undefined\n"
+ "override: function (this:Sub, string): undefined");
}
public void testInterfaceInheritanceCheck8() throws Exception {
testTypes(
"/** @constructor\n @implements {Super} */function Sub() {};" +
"/** @override */Sub.prototype.foo = function() {};",
new String[] {
"Bad type annotation. Unknown type Super",
"property foo not defined on any superclass of Sub"
});
}
public void testInterfaceInheritanceCheck9() throws Exception {
testTypes(
"/** @interface */ function I() {}" +
"/** @return {number} */ I.prototype.bar = function() {};" +
"/** @constructor */ function F() {}" +
"/** @return {number} */ F.prototype.bar = function() {return 3; };" +
"/** @return {number} */ F.prototype.foo = function() {return 3; };" +
"/** @constructor \n * @extends {F} \n * @implements {I} */ " +
"function G() {}" +
"/** @return {string} */ function f() { return new G().bar(); }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testInterfaceInheritanceCheck10() throws Exception {
testTypes(
"/** @interface */ function I() {}" +
"/** @return {number} */ I.prototype.bar = function() {};" +
"/** @constructor */ function F() {}" +
"/** @return {number} */ F.prototype.foo = function() {return 3; };" +
"/** @constructor \n * @extends {F} \n * @implements {I} */ " +
"function G() {}" +
"/** @return {number} \n * @override */ " +
"G.prototype.bar = G.prototype.foo;" +
"/** @return {string} */ function f() { return new G().bar(); }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testInterfaceInheritanceCheck12() throws Exception {
testTypes(
"/** @interface */ function I() {};\n"
+ "/** @type {string} */ I.prototype.foobar;\n"
+ "/** \n * @constructor \n * @implements {I} */\n"
+ "function C() {\n"
+ "/** \n * @type {number} */ this.foobar = 2;};\n"
+ "/** @type {I} */ \n var test = new C(); alert(test.foobar);",
"mismatch of the foobar property on type C and the type of the property"
+ " it overrides from interface I\n"
+ "original: string\n"
+ "override: number");
}
public void testInterfaceInheritanceCheck13() throws Exception {
testTypes(
"function abstractMethod() {};\n" +
"/** @interface */var base = function() {};\n" +
"/** @extends {base} \n @interface */ var Int = function() {}\n" +
"/** @type {{bar : !Function}} */ var x; \n" +
"/** @type {!Function} */ base.prototype.bar = abstractMethod; \n" +
"/** @type {Int} */ var foo;\n" +
"foo.bar();");
}
/**
* Verify that templatized interfaces can extend one another and share
* template values.
*/
public void testInterfaceInheritanceCheck14() throws Exception {
testTypes(
"/** @interface\n @template T */function A() {};" +
"/** @return {T} */A.prototype.foo = function() {};" +
"/** @interface\n @template U\n @extends {A<U>} */function B() {};" +
"/** @return {U} */B.prototype.bar = function() {};" +
"/** @constructor\n @implements {B<string>} */function C() {};" +
"/** @return {string}\n @override */C.prototype.foo = function() {};" +
"/** @return {string}\n @override */C.prototype.bar = function() {};");
}
/**
* Verify that templatized instances can correctly implement templatized
* interfaces.
*/
public void testInterfaceInheritanceCheck15() throws Exception {
testTypes(
"/** @interface\n @template T */function A() {};" +
"/** @return {T} */A.prototype.foo = function() {};" +
"/** @interface\n @template U\n @extends {A<U>} */function B() {};" +
"/** @return {U} */B.prototype.bar = function() {};" +
"/** @constructor\n @template V\n @implements {B<V>}\n */function C() {};" +
"/** @return {V}\n @override */C.prototype.foo = function() {};" +
"/** @return {V}\n @override */C.prototype.bar = function() {};");
}
/**
* Verify that using @override to declare the signature for an implementing
* class works correctly when the interface is generic.
*/
public void testInterfaceInheritanceCheck16() throws Exception {
testTypes(
"/** @interface\n @template T */function A() {};" +
"/** @return {T} */A.prototype.foo = function() {};" +
"/** @return {T} */A.prototype.bar = function() {};" +
"/** @constructor\n @implements {A<string>} */function B() {};" +
"/** @override */B.prototype.foo = function() { return 'string'};" +
"/** @override */B.prototype.bar = function() { return 3 };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testInterfacePropertyNotImplemented() throws Exception {
testTypes(
"/** @interface */function Int() {};" +
"Int.prototype.foo = function() {};" +
"/** @constructor\n @implements {Int} */function Foo() {};",
"property foo on interface Int is not implemented by type Foo");
}
public void testInterfacePropertyNotImplemented2() throws Exception {
testTypes(
"/** @interface */function Int() {};" +
"Int.prototype.foo = function() {};" +
"/** @interface \n @extends {Int} */function Int2() {};" +
"/** @constructor\n @implements {Int2} */function Foo() {};",
"property foo on interface Int is not implemented by type Foo");
}
/**
* Verify that templatized interfaces enforce their template type values.
*/
public void testInterfacePropertyNotImplemented3() throws Exception {
testTypes(
"/** @interface\n @template T */function Int() {};"
+ "/** @return {T} */Int.prototype.foo = function() {};"
+ "/** @constructor\n @implements {Int<string>} */function Foo() {};"
+ "/** @return {number}\n @override */Foo.prototype.foo = function() {};",
"mismatch of the foo property on type Foo and the type of the property it "
+ "overrides from interface Int\n"
+ "original: function (this:Int): string\n"
+ "override: function (this:Foo): number");
}
public void testStubConstructorImplementingInterface() throws Exception {
// This does not throw a warning for unimplemented property because Foo is
// just a stub.
testTypesWithExterns(
// externs
"/** @interface */ function Int() {}\n" +
"Int.prototype.foo = function() {};" +
"/** @constructor \n @implements {Int} */ var Foo;\n",
"");
}
public void testObjectLiteral() throws Exception {
Node n = parseAndTypeCheck("var a = {m1: 7, m2: 'hello'}");
Node nameNode = n.getFirstFirstChild();
Node objectNode = nameNode.getFirstChild();
// node extraction
assertEquals(Token.NAME, nameNode.getToken());
assertEquals(Token.OBJECTLIT, objectNode.getToken());
// value's type
ObjectType objectType =
(ObjectType) objectNode.getJSType();
assertTypeEquals(NUMBER_TYPE, objectType.getPropertyType("m1"));
assertTypeEquals(STRING_TYPE, objectType.getPropertyType("m2"));
// variable's type
assertTypeEquals(objectType, nameNode.getJSType());
}
public void testObjectLiteralDeclaration1() throws Exception {
testTypes(
"var x = {" +
"/** @type {boolean} */ abc: true," +
"/** @type {number} */ 'def': 0," +
"/** @type {string} */ 3: 'fgh'" +
"};");
}
public void testObjectLiteralDeclaration2() throws Exception {
testTypes(
"var x = {" +
" /** @type {boolean} */ abc: true" +
"};" +
"x.abc = 0;",
"assignment to property abc of x\n" +
"found : number\n" +
"required: boolean");
}
public void testObjectLiteralDeclaration3() throws Exception {
testTypes(
"/** @param {{foo: !Function}} x */ function f(x) {}" +
"f({foo: function() {}});");
}
public void testObjectLiteralDeclaration4() throws Exception {
testClosureTypes(
"var x = {" +
" /** @param {boolean} x */ abc: function(x) {}" +
"};" +
"/**\n" +
" * @param {string} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};",
"assignment to property abc of x\n" +
"found : function (string): undefined\n" +
"required: function (boolean): undefined");
// TODO(user): suppress {duplicate} currently also silence the
// redefining type error in the TypeValidator. Maybe it needs
// a new suppress name instead?
}
public void testObjectLiteralDeclaration5() throws Exception {
testTypes(
"var x = {" +
" /** @param {boolean} x */ abc: function(x) {}" +
"};" +
"/**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};");
}
public void testObjectLiteralDeclaration6() throws Exception {
testTypes(
"var x = {};" +
"/**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};" +
"x = {" +
" /**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */" +
" abc: function(x) {}" +
"};");
}
public void testObjectLiteralDeclaration7() throws Exception {
testTypes(
"var x = {};" +
"/**\n" +
" * @type {function(boolean): undefined}\n" +
" */ x.abc = function(x) {};" +
"x = {" +
" /**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */" +
" abc: function(x) {}" +
"};");
}
public void testCallDateConstructorAsFunction() throws Exception {
// ECMA-262 15.9.2: When Date is called as a function rather than as a
// constructor, it returns a string.
Node n = parseAndTypeCheck("Date()");
assertTypeEquals(STRING_TYPE, n.getFirstFirstChild().getJSType());
}
// According to ECMA-262, Error & Array function calls are equivalent to
// constructor calls.
public void testCallErrorConstructorAsFunction() throws Exception {
Node n = parseAndTypeCheck("Error('x')");
assertTypeEquals(ERROR_TYPE,
n.getFirstFirstChild().getJSType());
}
public void testCallArrayConstructorAsFunction() throws Exception {
Node n = parseAndTypeCheck("Array()");
assertTypeEquals(ARRAY_TYPE,
n.getFirstFirstChild().getJSType());
}
public void testPropertyTypeOfUnionType() throws Exception {
testTypes("var a = {};" +
"/** @constructor */ a.N = function() {};\n" +
"a.N.prototype.p = 1;\n" +
"/** @constructor */ a.S = function() {};\n" +
"a.S.prototype.p = 'a';\n" +
"/** @param {!a.N|!a.S} x\n@return {string} */\n" +
"var f = function(x) { return x.p; };",
"inconsistent return type\n" +
"found : (number|string)\n" +
"required: string");
}
// TODO(user): We should flag these as invalid. This will probably happen
// when we make sure the interface is never referenced outside of its
// definition. We might want more specific and helpful error messages.
//public void testWarningOnInterfacePrototype() throws Exception {
// testTypes("/** @interface */ u.T = function() {};\n" +
// "/** @return {number} */ u.T.prototype = function() { };",
// "e of its definition");
//}
//
//public void testBadPropertyOnInterface1() throws Exception {
// testTypes("/** @interface */ u.T = function() {};\n" +
// "/** @return {number} */ u.T.f = function() { return 1;};",
// "cannot reference an interface outside of its definition");
//}
//
//public void testBadPropertyOnInterface2() throws Exception {
// testTypes("/** @interface */ function T() {};\n" +
// "/** @return {number} */ T.f = function() { return 1;};",
// "cannot reference an interface outside of its definition");
//}
//
//public void testBadPropertyOnInterface3() throws Exception {
// testTypes("/** @interface */ u.T = function() {}; u.T.x",
// "cannot reference an interface outside of its definition");
//}
//
//public void testBadPropertyOnInterface4() throws Exception {
// testTypes("/** @interface */ function T() {}; T.x;",
// "cannot reference an interface outside of its definition");
//}
public void testAnnotatedPropertyOnInterface1() throws Exception {
// For interfaces we must allow function definitions that don't have a
// return statement, even though they declare a returned type.
testTypes("/** @interface */ u.T = function() {};\n" +
"/** @return {number} */ u.T.prototype.f = function() {};");
}
public void testAnnotatedPropertyOnInterface2() throws Exception {
testTypes("/** @interface */ u.T = function() {};\n" +
"/** @return {number} */ u.T.prototype.f = function() { };");
}
public void testAnnotatedPropertyOnInterface3() throws Exception {
testTypes("/** @interface */ function T() {};\n" +
"/** @return {number} */ T.prototype.f = function() { };");
}
public void testAnnotatedPropertyOnInterface4() throws Exception {
testTypes(
CLOSURE_DEFS +
"/** @interface */ function T() {};\n" +
"/** @return {number} */ T.prototype.f = goog.abstractMethod;");
}
// TODO(user): If we want to support this syntax we have to warn about
// missing annotations.
//public void testWarnUnannotatedPropertyOnInterface1() throws Exception {
// testTypes("/** @interface */ u.T = function () {}; u.T.prototype.x;",
// "interface property x is not annotated");
//}
//
//public void testWarnUnannotatedPropertyOnInterface2() throws Exception {
// testTypes("/** @interface */ function T() {}; T.prototype.x;",
// "interface property x is not annotated");
//}
public void testWarnUnannotatedPropertyOnInterface5() throws Exception {
testTypes("/** @interface */ u.T = function () {};\n" +
"u.T.prototype.x = function() {};");
}
public void testWarnUnannotatedPropertyOnInterface6() throws Exception {
testTypes("/** @interface */ function T() {};\n" +
"T.prototype.x = function() {};");
}
// TODO(user): If we want to support this syntax we have to warn about
// the invalid type of the interface member.
//public void testWarnDataPropertyOnInterface1() throws Exception {
// testTypes("/** @interface */ u.T = function () {};\n" +
// "/** @type {number} */u.T.prototype.x;",
// "interface members can only be plain functions");
//}
public void testDataPropertyOnInterface1() throws Exception {
testTypes("/** @interface */ function T() {};\n" +
"/** @type {number} */T.prototype.x;");
}
public void testDataPropertyOnInterface2() throws Exception {
testTypes(
"/** @interface */ function T() {};\n"
+ "/** @type {number} */T.prototype.x;\n"
+ "/** @constructor \n"
+ " * @implements {T} \n"
+ " */\n"
+ "function C() {}\n"
+ "/** @override */\n"
+ "C.prototype.x = 'foo';",
"mismatch of the x property on type C and the type of the property it "
+ "overrides from interface T\n"
+ "original: number\n"
+ "override: string");
}
public void testDataPropertyOnInterface3() throws Exception {
testTypes(
"/** @interface */ function T() {};\n"
+ "/** @type {number} */T.prototype.x;\n"
+ "/** @constructor \n"
+ " * @implements {T} \n"
+ " */\n"
+ "function C() {}\n"
+ "/** @override */\n"
+ "C.prototype.x = 'foo';",
"mismatch of the x property on type C and the type of the property it "
+ "overrides from interface T\n"
+ "original: number\n"
+ "override: string");
}
public void testDataPropertyOnInterface4() throws Exception {
testTypes(
"/** @interface */ function T() {};\n"
+ "/** @type {number} */T.prototype.x;\n"
+ "/** @constructor \n"
+ " * @implements {T} \n"
+ " */\n"
+ "function C() { /** @type {string} */ \n this.x = 'foo'; }\n",
"mismatch of the x property on type C and the type of the property it "
+ "overrides from interface T\n"
+ "original: number\n"
+ "override: string");
}
public void testWarnDataPropertyOnInterface3() throws Exception {
testTypes("/** @interface */ u.T = function () {};\n" +
"/** @type {number} */u.T.prototype.x = 1;",
"interface members can only be empty property declarations, "
+ "empty functions, or goog.abstractMethod");
}
public void testWarnDataPropertyOnInterface4() throws Exception {
testTypes("/** @interface */ function T() {};\n" +
"/** @type {number} */T.prototype.x = 1;",
"interface members can only be empty property declarations, "
+ "empty functions, or goog.abstractMethod");
}
// TODO(user): If we want to support this syntax we should warn about the
// mismatching types in the two tests below.
//public void testErrorMismatchingPropertyOnInterface1() throws Exception {
// testTypes("/** @interface */ u.T = function () {};\n" +
// "/** @param {Number} foo */u.T.prototype.x =\n" +
// "/** @param {String} foo */function(foo) {};",
// "found : \n" +
// "required: ");
//}
//
//public void testErrorMismatchingPropertyOnInterface2() throws Exception {
// testTypes("/** @interface */ function T() {};\n" +
// "/** @return {number} */T.prototype.x =\n" +
// "/** @return {string} */function() {};",
// "found : \n" +
// "required: ");
//}
// TODO(user): We should warn about this (bar is missing an annotation). We
// probably don't want to warn about all missing parameter annotations, but
// we should be as strict as possible regarding interfaces.
//public void testErrorMismatchingPropertyOnInterface3() throws Exception {
// testTypes("/** @interface */ u.T = function () {};\n" +
// "/** @param {Number} foo */u.T.prototype.x =\n" +
// "function(foo, bar) {};",
// "found : \n" +
// "required: ");
//}
public void testErrorMismatchingPropertyOnInterface4() throws Exception {
testTypes("/** @interface */ u.T = function () {};\n" +
"/** @param {Number} foo */u.T.prototype.x =\n" +
"function() {};",
"parameter foo does not appear in u.T.prototype.x's parameter list");
}
public void testErrorMismatchingPropertyOnInterface5() throws Exception {
testTypes("/** @interface */ function T() {};\n" +
"/** @type {number} */T.prototype.x = function() { };",
"assignment to property x of T.prototype\n" +
"found : function (): undefined\n" +
"required: number");
}
public void testErrorMismatchingPropertyOnInterface6() throws Exception {
testClosureTypesMultipleWarnings(
"/** @interface */ function T() {};\n" +
"/** @return {number} */T.prototype.x = 1",
ImmutableList.of(
"assignment to property x of T.prototype\n" +
"found : number\n" +
"required: function (this:T): number",
"interface members can only be empty property declarations, " +
"empty functions, or goog.abstractMethod"));
}
public void testInterfaceNonEmptyFunction() throws Exception {
testTypes("/** @interface */ function T() {};\n" +
"T.prototype.x = function() { return 'foo'; }",
"interface member functions must have an empty body"
);
}
public void testDoubleNestedInterface() throws Exception {
testTypes("/** @interface */ var I1 = function() {};\n" +
"/** @interface */ I1.I2 = function() {};\n" +
"/** @interface */ I1.I2.I3 = function() {};\n");
}
public void testStaticDataPropertyOnNestedInterface() throws Exception {
testTypes("/** @interface */ var I1 = function() {};\n" +
"/** @interface */ I1.I2 = function() {};\n" +
"/** @type {number} */ I1.I2.x = 1;\n");
}
public void testInterfaceInstantiation() throws Exception {
testTypes("/** @interface */var f = function(){}; new f",
"cannot instantiate non-constructor");
}
public void testPrototypeLoop() throws Exception {
testClosureTypesMultipleWarnings(
suppressMissingProperty("foo") +
"/** @constructor \n * @extends {T} */var T = function() {};" +
"alert((new T).foo);",
ImmutableList.of(
"Parse error. Cycle detected in inheritance chain of type T",
"Could not resolve type in @extends tag of T"));
}
public void testImplementsLoop() throws Exception {
testClosureTypesMultipleWarnings(
suppressMissingProperty("foo") +
"/** @constructor \n * @implements {T} */var T = function() {};" +
"alert((new T).foo);",
ImmutableList.of(
"Parse error. Cycle detected in inheritance chain of type T"));
}
public void testImplementsExtendsLoop() throws Exception {
testClosureTypesMultipleWarnings(
suppressMissingProperty("foo") +
"/** @constructor \n * @implements {F} */var G = function() {};" +
"/** @constructor \n * @extends {G} */var F = function() {};" +
"alert((new F).foo);",
ImmutableList.of(
"Parse error. Cycle detected in inheritance chain of type F"));
}
public void testInterfaceExtendsLoop() throws Exception {
testClosureTypesMultipleWarnings(
suppressMissingProperty("foo") +
"/** @interface \n * @extends {F} */var G = function() {};" +
"/** @interface \n * @extends {G} */var F = function() {};" +
"/** @constructor \n * @implements {F} */var H = function() {};" +
"alert((new H).foo);",
ImmutableList.of(
"extends loop involving F, "
+ "loop: F -> G -> F",
"extends loop involving G, "
+ "loop: G -> F -> G"));
}
public void testInterfaceExtendsLoop2() throws Exception {
testClosureTypes(
suppressMissingProperty("foo") +
"/** @record \n * @extends {F} */var G = function() {};" +
"/** @record \n * @extends {G} */var F = function() {};" +
"/** @constructor \n * @implements {F} */var H = function() {};" +
"alert((new H).foo);",
"Parse error. Cycle detected in inheritance chain of type F");
}
public void testInheritPropFromMultipleInterfaces1() throws Exception {
// Low#prop gets the type of whichever property is declared last,
// even if that type is not the most specific.
testTypes(
LINE_JOINER.join(
"/** @interface */",
"function High1() {}",
"/** @type {number|string} */",
"High1.prototype.prop;",
"/** @interface */",
"function High2() {}",
"/** @type {number} */",
"High2.prototype.prop;",
"/**",
" * @interface",
" * @extends {High1}",
" * @extends {High2}",
" */",
"function Low() {}",
"function f(/** !Low */ x) { var /** null */ n = x.prop; }"),
LINE_JOINER.join(
"initializing variable",
"found : (number|string)",
"required: null"));
}
public void testInheritPropFromMultipleInterfaces2() throws Exception {
// Low#prop gets the type of whichever property is declared last,
// even if that type is not the most specific.
testTypes(
LINE_JOINER.join(
"/** @interface */",
"function High1() {}",
"/** @type {number} */",
"High1.prototype.prop;",
"/** @interface */",
"function High2() {}",
"/** @type {number|string} */",
"High2.prototype.prop;",
"/**",
" * @interface",
" * @extends {High1}",
" * @extends {High2}",
" */",
"function Low() {}",
"function f(/** !Low */ x) { var /** null */ n = x.prop; }"),
LINE_JOINER.join(
"initializing variable",
"found : number",
"required: null"));
}
public void testInheritPropFromMultipleInterfaces3() throws Exception {
testTypes(
LINE_JOINER.join(
"/**",
" * @interface",
" * @template T1",
" */",
"function MyCollection() {}",
"/**",
" * @interface",
" * @template T2",
" * @extends {MyCollection<T2>}",
" */",
"function MySet() {}",
"/**",
" * @interface",
" * @template T3,T4",
" */",
"function MyMapEntry() {}",
"/**",
" * @interface",
" * @template T5,T6",
" */",
"function MyMultimap() {}",
"/** @return {MyCollection<MyMapEntry<T5, T6>>} */",
"MyMultimap.prototype.entries = function() {};",
"/**",
" * @interface",
" * @template T7,T8",
" * @extends {MyMultimap<T7, T8>}",
" */",
"function MySetMultimap() {}",
"/** @return {MySet<MyMapEntry<T7, T8>>} */",
"MySetMultimap.prototype.entries = function() {};",
"/**",
" * @interface",
" * @template T9,T10",
" * @extends {MyMultimap<T9, T10>}",
" */",
"function MyFilteredMultimap() {}",
"/**",
" * @interface",
" * @template T11,T12",
" * @extends {MyFilteredMultimap<T11, T12>}",
" * @extends {MySetMultimap<T11, T12>}",
" */",
"function MyFilteredSetMultimap() {}"));
}
public void testInheritSameGenericInterfaceFromDifferentPaths() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @const */ var ns = {};",
"/**",
" * @constructor",
" * @template T1",
" */",
"ns.Foo = function() {};",
"/**",
" * @interface",
" * @template T2",
" */",
"ns.High = function() {};",
"/** @type {!ns.Foo<T2>} */",
"ns.High.prototype.myprop;",
"/**",
" * @interface",
" * @template T3",
" * @extends {ns.High<T3>}",
" */",
"ns.Med1 = function() {};",
"/**",
" * @interface",
" * @template T4",
" * @extends {ns.High<T4>}",
" */",
"ns.Med2 = function() {};",
"/**",
" * @interface",
" * @template T5",
" * @extends {ns.Med1<T5>}",
" * @extends {ns.Med2<T5>}",
" */",
"ns.Low = function() {};"));
}
public void testConversionFromInterfaceToRecursiveConstructor() throws Exception {
testClosureTypesMultipleWarnings(
suppressMissingProperty("foo") +
"/** @interface */ var OtherType = function() {}\n" +
"/** @implements {MyType} \n * @constructor */\n" +
"var MyType = function() {}\n" +
"/** @type {MyType} */\n" +
"var x = /** @type {!OtherType} */ (new Object());",
ImmutableList.of(
"Parse error. Cycle detected in inheritance chain of type MyType",
"initializing variable\n" +
"found : OtherType\n" +
"required: (MyType|null)"));
}
public void testDirectPrototypeAssign() throws Exception {
// For now, we just ignore @type annotations on the prototype.
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @constructor */ function Bar() {}" +
"/** @type {Array} */ Bar.prototype = new Foo()");
}
// In all testResolutionViaRegistry* tests, since u is unknown, u.T can only
// be resolved via the registry and not via properties.
public void testResolutionViaRegistry1() throws Exception {
testTypes("/** @constructor */ u.T = function() {};\n" +
"/** @type {(number|string)} */ u.T.prototype.a;\n" +
"/**\n" +
"* @param {u.T} t\n" +
"* @return {string}\n" +
"*/\n" +
"var f = function(t) { return t.a; };",
"inconsistent return type\n" +
"found : (number|string)\n" +
"required: string");
}
public void testResolutionViaRegistry2() throws Exception {
testTypes(
"/** @constructor */ u.T = function() {" +
" this.a = 0; };\n" +
"/**\n" +
"* @param {u.T} t\n" +
"* @return {string}\n" +
"*/\n" +
"var f = function(t) { return t.a; };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testResolutionViaRegistry3() throws Exception {
testTypes("/** @constructor */ u.T = function() {};\n" +
"/** @type {(number|string)} */ u.T.prototype.a = 0;\n" +
"/**\n" +
"* @param {u.T} t\n" +
"* @return {string}\n" +
"*/\n" +
"var f = function(t) { return t.a; };",
"inconsistent return type\n" +
"found : (number|string)\n" +
"required: string");
}
public void testResolutionViaRegistry4() throws Exception {
testTypes("/** @constructor */ u.A = function() {};\n" +
"/**\n* @constructor\n* @extends {u.A}\n*/\nu.A.A = function() {}\n;" +
"/**\n* @constructor\n* @extends {u.A}\n*/\nu.A.B = function() {};\n" +
"var ab = new u.A.B();\n" +
"/** @type {!u.A} */ var a = ab;\n" +
"/** @type {!u.A.A} */ var aa = ab;\n",
"initializing variable\n" +
"found : u.A.B\n" +
"required: u.A.A");
}
public void testResolutionViaRegistry5() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ u.T = function() {}; u.T");
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertThat(type).isInstanceOf(FunctionType.class);
assertEquals("u.T",
((FunctionType) type).getInstanceType().getReferenceName());
}
public void testGatherProperyWithoutAnnotation1() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ var T = function() {};" +
"/** @type {!T} */var t; t.x; t;");
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertThat(type).isInstanceOf(ObjectType.class);
ObjectType objectType = (ObjectType) type;
assertFalse(objectType.hasProperty("x"));
}
public void testGatherProperyWithoutAnnotation2() throws Exception {
TypeCheckResult ns =
parseAndTypeCheckWithScope("/** @type {!Object} */var t; t.x; t;");
Node n = ns.root;
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertTypeEquals(type, OBJECT_TYPE);
assertThat(type).isInstanceOf(ObjectType.class);
ObjectType objectType = (ObjectType) type;
assertFalse(objectType.hasProperty("x"));
}
public void testFunctionMasksVariableBug() throws Exception {
testTypes("var x = 4; var f = function x(b) { return b ? 1 : x(true); };",
"function x masks variable (IE bug)");
}
public void testDfa1() throws Exception {
testTypes("var x = null;\n x = 1;\n /** @type {number} */ var y = x;");
}
public void testDfa2() throws Exception {
testTypes("function u() {}\n" +
"/** @return {number} */ function f() {\nvar x = 'todo';\n" +
"if (u()) { x = 1; } else { x = 2; } return x;\n}");
}
public void testDfa3() throws Exception {
testTypes("function u() {}\n" +
"/** @return {number} */ function f() {\n" +
"/** @type {number|string} */ var x = 'todo';\n" +
"if (u()) { x = 1; } else { x = 2; } return x;\n}");
}
public void testDfa4() throws Exception {
testTypes("/** @param {Date?} d */ function f(d) {\n" +
"if (!d) { return; }\n" +
"/** @type {!Date} */ var e = d;\n}");
}
public void testDfa5() throws Exception {
testTypes("/** @return {string?} */ function u() {return 'a';}\n" +
"/** @param {string?} x\n@return {string} */ function f(x) {\n" +
"while (!x) { x = u(); }\nreturn x;\n}");
}
public void testDfa6() throws Exception {
testTypes("/** @return {Object?} */ function u() {return {};}\n" +
"/** @param {Object?} x */ function f(x) {\n" +
"while (x) { x = u(); if (!x) { x = u(); } }\n}");
}
public void testDfa7() throws Exception {
testTypes("/** @constructor */ var T = function() {};\n" +
"/** @type {Date?} */ T.prototype.x = null;\n" +
"/** @param {!T} t */ function f(t) {\n" +
"if (!t.x) { return; }\n" +
"/** @type {!Date} */ var e = t.x;\n}");
}
public void testDfa8() throws Exception {
testTypes("/** @constructor */ var T = function() {};\n" +
"/** @type {number|string} */ T.prototype.x = '';\n" +
"function u() {}\n" +
"/** @param {!T} t\n@return {number} */ function f(t) {\n" +
"if (u()) { t.x = 1; } else { t.x = 2; } return t.x;\n}");
}
public void testDfa9() throws Exception {
testTypes("function f() {\n/** @type {string?} */var x;\nx = null;\n" +
"if (x == null) { return 0; } else { return 1; } }",
"condition always evaluates to true\n" +
"left : null\n" +
"right: null");
}
public void testDfa10() throws Exception {
testTypes("/** @param {null} x */ function g(x) {}" +
"/** @param {string?} x */function f(x) {\n" +
"if (!x) { x = ''; }\n" +
"if (g(x)) { return 0; } else { return 1; } }",
"actual parameter 1 of g does not match formal parameter\n" +
"found : string\n" +
"required: null");
}
public void testDfa11() throws Exception {
testTypes("/** @param {string} opt_x\n@return {string} */\n" +
"function f(opt_x) { if (!opt_x) { " +
"throw new Error('x cannot be empty'); } return opt_x; }");
}
public void testDfa12() throws Exception {
testTypes("/** @param {string} x \n * @constructor \n */" +
"var Bar = function(x) {};" +
"/** @param {string} x */ function g(x) { return true; }" +
"/** @param {string|number} opt_x */ " +
"function f(opt_x) { " +
" if (opt_x) { new Bar(g(opt_x) && 'x'); }" +
"}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : (number|string)\n" +
"required: string");
}
public void testDfa13() throws Exception {
testTypes(
"/**\n" +
" * @param {string} x \n" +
" * @param {number} y \n" +
" * @param {number} z \n" +
" */" +
"function g(x, y, z) {}" +
"function f() { " +
" var x = 'a'; g(x, x = 3, x);" +
"}");
}
public void testTypeInferenceWithCast1() throws Exception {
testTypes(
"/**@return {(number|null|undefined)}*/function u(x) {return null;}" +
"/**@param {number?} x\n@return {number?}*/function f(x) {return x;}" +
"/**@return {number?}*/function g(x) {" +
"var y = /**@type {number?}*/(u(x)); return f(y);}");
}
public void testTypeInferenceWithCast2() throws Exception {
testTypes(
"/**@return {(number|null|undefined)}*/function u(x) {return null;}" +
"/**@param {number?} x\n@return {number?}*/function f(x) {return x;}" +
"/**@return {number?}*/function g(x) {" +
"var y; y = /**@type {number?}*/(u(x)); return f(y);}");
}
public void testTypeInferenceWithCast3() throws Exception {
testTypes(
"/**@return {(number|null|undefined)}*/function u(x) {return 1;}" +
"/**@return {number}*/function g(x) {" +
"return /**@type {number}*/(u(x));}");
}
public void testTypeInferenceWithCast4() throws Exception {
testTypes(
"/**@return {(number|null|undefined)}*/function u(x) {return 1;}" +
"/**@return {number}*/function g(x) {" +
"return /**@type {number}*/(u(x)) && 1;}");
}
public void testTypeInferenceWithCast5() throws Exception {
testTypes(
"/** @param {number} x */ function foo(x) {}" +
"/** @param {{length:*}} y */ function bar(y) {" +
" /** @type {string} */ y.length;" +
" foo(y.length);" +
"}",
"actual parameter 1 of foo does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testTypeInferenceWithClosure1() throws Exception {
testTypes(
"/** @return {boolean} */" +
"function f() {" +
" /** @type {?string} */ var x = null;" +
" function g() { x = 'y'; } g(); " +
" return x == null;" +
"}");
}
public void testTypeInferenceWithClosure2() throws Exception {
testTypes(
"/** @return {boolean} */" +
"function f() {" +
" /** @type {?string} */ var x = null;" +
" function g() { x = 'y'; } g(); " +
" return x === 3;" +
"}",
"condition always evaluates to false\n" +
"left : (null|string)\n" +
"right: number");
}
public void testTypeInferenceWithNoEntry1() throws Exception {
testTypes(
"/** @param {number} x */ function f(x) {}" +
"/** @constructor */ function Foo() {}" +
"Foo.prototype.init = function() {" +
" /** @type {?{baz: number}} */ this.bar = {baz: 3};" +
"};" +
"/**\n" +
" * @extends {Foo}\n" +
" * @constructor\n" +
" */" +
"function SubFoo() {}" +
"/** Method */" +
"SubFoo.prototype.method = function() {" +
" for (var i = 0; i < 10; i++) {" +
" f(this.bar);" +
" f(this.bar.baz);" +
" }" +
"};",
"actual parameter 1 of f does not match formal parameter\n" +
"found : (null|{baz: number})\n" +
"required: number");
}
public void testTypeInferenceWithNoEntry2() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @param {number} x */ function f(x) {}" +
"/** @param {!Object} x */ function g(x) {}" +
"/** @constructor */ function Foo() {}" +
"Foo.prototype.init = function() {" +
" /** @type {?{baz: number}} */ this.bar = {baz: 3};" +
"};" +
"/**\n" +
" * @extends {Foo}\n" +
" * @constructor\n" +
" */" +
"function SubFoo() {}" +
"/** Method */" +
"SubFoo.prototype.method = function() {" +
" for (var i = 0; i < 10; i++) {" +
" f(this.bar);" +
" goog.asserts.assert(this.bar);" +
" g(this.bar);" +
" }" +
"};",
"actual parameter 1 of f does not match formal parameter\n" +
"found : (null|{baz: number})\n" +
"required: number");
}
public void testForwardPropertyReference() throws Exception {
testTypes("/** @constructor */ var Foo = function() { this.init(); };" +
"/** @return {string} */" +
"Foo.prototype.getString = function() {" +
" return this.number_;" +
"};" +
"Foo.prototype.init = function() {" +
" /** @type {number} */" +
" this.number_ = 3;" +
"};",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testNoForwardTypeDeclaration() throws Exception {
testTypes(
"/** @param {MyType} x */ function f(x) {}",
"Bad type annotation. Unknown type MyType");
}
public void testNoForwardTypeDeclarationAndNoBraces() throws Exception {
testTypes("/** @return The result. */ function f() {}",
RhinoErrorReporter.JSDOC_MISSING_TYPE_WARNING);
}
public void testForwardTypeDeclaration1() throws Exception {
testClosureTypes(
// malformed addDependency calls shouldn't cause a crash
"goog.addDependency();" +
"goog.addDependency('y', [goog]);" +
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/** @param {MyType} x \n * @return {number} */" +
"function f(x) { return 3; }", null);
}
public void testForwardTypeDeclaration2() throws Exception {
String f = "goog.addDependency('zzz.js', ['MyType'], []);" +
"/** @param {MyType} x */ function f(x) { }";
testClosureTypes(f, null);
testClosureTypes(f + "f(3);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: (MyType|null)");
}
public void testForwardTypeDeclaration3() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/** @param {MyType} x */ function f(x) { return x; }" +
"/** @constructor */ var MyType = function() {};" +
"f(3);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: (MyType|null)");
}
public void testForwardTypeDeclaration4() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/** @param {MyType} x */ function f(x) { return x; }" +
"/** @constructor */ var MyType = function() {};" +
"f(new MyType());",
null);
}
public void testForwardTypeDeclaration5() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/**\n" +
" * @constructor\n" +
" * @extends {MyType}\n" +
" */ var YourType = function() {};" +
"/** @override */ YourType.prototype.method = function() {};",
"Could not resolve type in @extends tag of YourType");
}
public void testForwardTypeDeclaration6() throws Exception {
testClosureTypesMultipleWarnings(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/**\n" +
" * @constructor\n" +
" * @implements {MyType}\n" +
" */ var YourType = function() {};" +
"/** @override */ YourType.prototype.method = function() {};",
ImmutableList.of(
"Could not resolve type in @implements tag of YourType",
"property method not defined on any superclass of YourType"));
}
public void testForwardTypeDeclaration7() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/** @param {MyType=} x */" +
"function f(x) { return x == undefined; }", null);
}
public void testForwardTypeDeclaration8() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/** @param {MyType} x */" +
"function f(x) { return x.name == undefined; }", null);
}
public void testForwardTypeDeclaration9() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/** @param {MyType} x */" +
"function f(x) { x.name = 'Bob'; }", null);
}
public void testForwardTypeDeclaration10() throws Exception {
String f = "goog.addDependency('zzz.js', ['MyType'], []);" +
"/** @param {MyType|number} x */ function f(x) { }";
testClosureTypes(f, null);
testClosureTypes(f + "f(3);", null);
testClosureTypes(f + "f('3');",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: (MyType|null|number)");
}
public void testForwardTypeDeclaration12() throws Exception {
// We assume that {Function} types can produce anything, and don't
// want to type-check them.
testClosureTypes(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/**\n" +
" * @param {!Function} ctor\n" +
" * @return {MyType}\n" +
" */\n" +
"function f(ctor) { return new ctor(); }", null);
}
public void testForwardTypeDeclaration13() throws Exception {
// Some projects use {Function} registries to register constructors
// that aren't in their binaries. We want to make sure we can pass these
// around, but still do other checks on them.
testClosureTypes(
"goog.addDependency('zzz.js', ['MyType'], []);" +
"/**\n" +
" * @param {!Function} ctor\n" +
" * @return {MyType}\n" +
" */\n" +
"function f(ctor) { return (new ctor()).impossibleProp; }",
"Property impossibleProp never defined on ?");
}
public void testDuplicateTypeDef() throws Exception {
testTypes(
"var goog = {};" +
"/** @constructor */ goog.Bar = function() {};" +
"/** @typedef {number} */ goog.Bar;",
"variable goog.Bar redefined with type None, " +
"original definition at [testcode]:1 " +
"with type function (new:goog.Bar): undefined");
}
public void testTypeDef1() throws Exception {
testTypes(
"var goog = {};" +
"/** @typedef {number} */ goog.Bar;" +
"/** @param {goog.Bar} x */ function f(x) {}" +
"f(3);");
}
public void testTypeDef2() throws Exception {
testTypes(
"var goog = {};" +
"/** @typedef {number} */ goog.Bar;" +
"/** @param {goog.Bar} x */ function f(x) {}" +
"f('3');",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testTypeDef3() throws Exception {
testTypes(
"var goog = {};" +
"/** @typedef {number} */ var Bar;" +
"/** @param {Bar} x */ function f(x) {}" +
"f('3');",
"actual parameter 1 of f does not match formal parameter\n" +
"found : string\n" +
"required: number");
}
public void testTypeDef4() throws Exception {
testTypes(
"/** @constructor */ function A() {}" +
"/** @constructor */ function B() {}" +
"/** @typedef {(A|B)} */ var AB;" +
"/** @param {AB} x */ function f(x) {}" +
"f(new A()); f(new B()); f(1);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: (A|B|null)");
}
public void testTypeDef5() throws Exception {
// Notice that the error message is slightly different than
// the one for testTypeDef4, even though they should be the same.
// This is an implementation detail necessary for NamedTypes work out
// OK, and it should change if NamedTypes ever go away.
testTypes(
"/** @param {AB} x */ function f(x) {}" +
"/** @constructor */ function A() {}" +
"/** @constructor */ function B() {}" +
"/** @typedef {(A|B)} */ var AB;" +
"f(new A()); f(new B()); f(1);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: (A|B|null)");
}
public void testCircularTypeDef() throws Exception {
testTypes(
"var goog = {};" +
"/** @typedef {number|Array<goog.Bar>} */ goog.Bar;" +
"/** @param {goog.Bar} x */ function f(x) {}" +
"f(3); f([3]); f([[3]]);");
}
public void testGetTypedPercent1() throws Exception {
String js = "var id = function(x) { return x; }\n" +
"var id2 = function(x) { return id(x); }";
assertEquals(50.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent2() throws Exception {
String js = "var x = {}; x.y = 1;";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent3() throws Exception {
String js = "var f = function(x) { x.a = x.b; }";
assertEquals(50.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent4() throws Exception {
String js = "var n = {};\n /** @constructor */ n.T = function() {};\n" +
"/** @type {n.T} */ var x = new n.T();";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent5() throws Exception {
String js = "/** @enum {number} */ keys = {A: 1,B: 2,C: 3};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent6() throws Exception {
String js = "a = {TRUE: 1, FALSE: 0};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
private double getTypedPercent(String js) {
Node n = compiler.parseTestCode(js);
Node externs = IR.root();
Node externAndJsRoot = IR.root(externs, n);
TypeCheck t = makeTypeCheck();
t.processForTesting(null, n);
return t.getTypedPercent();
}
private static ObjectType getInstanceType(Node js1Node) {
JSType type = js1Node.getFirstChild().getJSType();
assertNotNull(type);
assertThat(type).isInstanceOf(FunctionType.class);
FunctionType functionType = (FunctionType) type;
assertTrue(functionType.isConstructor());
return functionType.getInstanceType();
}
public void testPrototypePropertyReference() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(""
+ "/** @constructor */\n"
+ "function Foo() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.bar = function(a){};\n"
+ "/** @param {Foo} f */\n"
+ "function baz(f) {\n"
+ " Foo.prototype.bar.call(f, 3);\n"
+ "}");
assertEquals(0, compiler.getErrorCount());
assertEquals(0, compiler.getWarningCount());
assertThat(p.scope.getVar("Foo").getType()).isInstanceOf(FunctionType.class);
FunctionType fooType = (FunctionType) p.scope.getVar("Foo").getType();
assertEquals("function (this:Foo, number): undefined",
fooType.getPrototype().getPropertyType("bar").toString());
}
public void testResolvingNamedTypes() throws Exception {
String js = ""
+ "/** @constructor */\n"
+ "var Foo = function() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.foo = function(a) {\n"
+ " return this.baz().toString();\n"
+ "};\n"
+ "/** @return {Baz} */\n"
+ "Foo.prototype.baz = function() { return new Baz(); };\n"
+ "/** @constructor\n"
+ " * @extends Foo */\n"
+ "var Bar = function() {};"
+ "/** @constructor */\n"
+ "var Baz = function() {};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testMissingProperty1() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"Foo.prototype.baz = function() { this.a = 3; };");
}
public void testMissingProperty2() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"Foo.prototype.baz = function() { this.b = 3; };",
"Property a never defined on Foo");
}
public void testMissingProperty3() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"(new Foo).a = 3;");
}
public void testMissingProperty4() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"(new Foo).b = 3;",
"Property a never defined on Foo");
}
public void testMissingProperty5() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"/** @constructor */ function Bar() { this.a = 3; };",
"Property a never defined on Foo");
}
public void testMissingProperty6() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"/** @constructor \n * @extends {Foo} */ " +
"function Bar() { this.a = 3; };");
}
public void testMissingProperty7() throws Exception {
testTypes(
"/** @param {Object} obj */" +
"function foo(obj) { return obj.impossible; }",
"Property impossible never defined on Object");
}
public void testMissingProperty8() throws Exception {
testTypes(
"/** @param {Object} obj */" +
"function foo(obj) { return typeof obj.impossible; }");
}
public void testMissingProperty9() throws Exception {
testTypes(
"/** @param {Object} obj */" +
"function foo(obj) { if (obj.impossible) { return true; } }");
}
public void testMissingProperty10() throws Exception {
testTypes(
"/** @param {Object} obj */" +
"function foo(obj) { while (obj.impossible) { return true; } }");
}
public void testMissingProperty11() throws Exception {
testTypes(
"/** @param {Object} obj */" +
"function foo(obj) { for (;obj.impossible;) { return true; } }");
}
public void testMissingProperty12() throws Exception {
testTypes(
"/** @param {Object} obj */" +
"function foo(obj) { do { } while (obj.impossible); }");
}
public void testMissingProperty13() throws Exception {
testTypes(
"var goog = {}; goog.isDef = function(x) { return false; };" +
"/** @param {Object} obj */" +
"function foo(obj) { return goog.isDef(obj.impossible); }");
}
public void testMissingProperty14() throws Exception {
testTypes(
"var goog = {}; goog.isDef = function(x) { return false; };" +
"/** @param {Object} obj */" +
"function foo(obj) { return goog.isNull(obj.impossible); }",
"Property isNull never defined on goog");
}
public void testMissingProperty15() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { if (x.foo) { x.foo(); } }");
}
public void testMissingProperty16() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { x.foo(); if (x.foo) {} }",
"Property foo never defined on Object");
}
public void testMissingProperty17() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { if (typeof x.foo == 'function') { x.foo(); } }");
}
public void testMissingProperty18() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { if (x.foo instanceof Function) { x.foo(); } }");
}
public void testMissingProperty19() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { if (x.bar) { if (x.foo) {} } else { x.foo(); } }",
"Property foo never defined on Object");
}
public void testMissingProperty20() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { if (x.foo) { } else { x.foo(); } }",
"Property foo never defined on Object");
}
public void testMissingProperty21() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { x.foo && x.foo(); }");
}
public void testMissingProperty22() throws Exception {
testTypes(
"/** @param {Object} x \n * @return {boolean} */" +
"function f(x) { return x.foo ? x.foo() : true; }");
}
public void testMissingProperty23() throws Exception {
testTypes(
"function f(x) { x.impossible(); }",
"Property impossible never defined on x");
}
public void testMissingProperty24() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MissingType'], []);" +
"/** @param {MissingType} x */" +
"function f(x) { x.impossible(); }", null);
}
public void testMissingProperty25() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {};" +
"Foo.prototype.bar = function() {};" +
"/** @constructor */ var FooAlias = Foo;" +
"(new FooAlias()).bar();");
}
public void testMissingProperty26() throws Exception {
testTypes(
"/** @constructor */ var Foo = function() {};" +
"/** @constructor */ var FooAlias = Foo;" +
"FooAlias.prototype.bar = function() {};" +
"(new Foo()).bar();");
}
public void testMissingProperty27() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MissingType'], []);" +
"/** @param {?MissingType} x */" +
"function f(x) {" +
" for (var parent = x; parent; parent = parent.getParent()) {}" +
"}", null);
}
public void testMissingProperty28() throws Exception {
testTypes(
"function f(obj) {" +
" /** @type {*} */ obj.foo;" +
" return obj.foo;" +
"}");
testTypes(
"function f(obj) {" +
" /** @type {*} */ obj.foo;" +
" return obj.foox;" +
"}",
"Property foox never defined on obj");
}
public void testMissingProperty29() throws Exception {
// This used to emit a warning.
testTypesWithExterns(
// externs
"/** @constructor */ var Foo;" +
"Foo.prototype.opera;" +
"Foo.prototype.opera.postError;",
"");
}
public void testMissingProperty30() throws Exception {
testTypes(
"/** @return {*} */" +
"function f() {" +
" return {};" +
"}" +
"f().a = 3;" +
"/** @param {Object} y */ function g(y) { return y.a; }");
}
public void testMissingProperty31() throws Exception {
testTypes(
"/** @return {Array|number} */" +
"function f() {" +
" return [];" +
"}" +
"f().a = 3;" +
"/** @param {Array} y */ function g(y) { return y.a; }");
}
public void testMissingProperty32() throws Exception {
testTypes(
"/** @return {Array|number} */" +
"function f() {" +
" return [];" +
"}" +
"f().a = 3;" +
"/** @param {Date} y */ function g(y) { return y.a; }",
"Property a never defined on Date");
}
public void testMissingProperty33() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { !x.foo || x.foo(); }");
}
public void testMissingProperty34() throws Exception {
testTypes(
"/** @fileoverview \n * @suppress {missingProperties} */" +
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"Foo.prototype.baz = function() { this.b = 3; };");
}
public void testMissingProperty35() throws Exception {
// Bar has specialProp defined, so Bar|Baz may have specialProp defined.
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @constructor */ function Bar() {}" +
"/** @constructor */ function Baz() {}" +
"/** @param {Foo|Bar} x */ function f(x) { x.specialProp = 1; }" +
"/** @param {Bar|Baz} x */ function g(x) { return x.specialProp; }");
}
public void testMissingProperty36() throws Exception {
// Foo has baz defined, and SubFoo has bar defined, so some objects with
// bar may have baz.
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.baz = 0;" +
"/** @constructor \n * @extends {Foo} */ function SubFoo() {}" +
"SubFoo.prototype.bar = 0;" +
"/** @param {{bar: number}} x */ function f(x) { return x.baz; }");
}
public void testMissingProperty37() throws Exception {
// This used to emit a missing property warning because we couldn't
// determine that the inf(Foo, {isVisible:boolean}) == SubFoo.
testTypes(
"/** @param {{isVisible: boolean}} x */ function f(x){" +
" x.isVisible = false;" +
"}" +
"/** @constructor */ function Foo() {}" +
"/**\n" +
" * @constructor \n" +
" * @extends {Foo}\n" +
" */ function SubFoo() {}" +
"/** @type {boolean} */ SubFoo.prototype.isVisible = true;" +
"/**\n" +
" * @param {Foo} x\n" +
" * @return {boolean}\n" +
" */\n" +
"function g(x) { return x.isVisible; }");
}
public void testMissingProperty38() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @constructor */ function Bar() {}" +
"/** @return {Foo|Bar} */ function f() { return new Foo(); }" +
"f().missing;",
"Property missing never defined on (Bar|Foo|null)");
}
public void testMissingProperty39() throws Exception {
testTypes(
"/** @return {string|number} */ function f() { return 3; }" +
"f().length;");
}
public void testMissingProperty40() throws Exception {
testClosureTypes(
"goog.addDependency('zzz.js', ['MissingType'], []);" +
"/** @param {(Array|MissingType)} x */" +
"function f(x) { x.impossible(); }", null);
}
public void testMissingProperty41() throws Exception {
testTypes(
"/** @param {(Array|Date)} x */" +
"function f(x) { if (x.impossible) x.impossible(); }");
}
public void testMissingProperty42() throws Exception {
testTypes(
"/** @param {Object} x */" +
"function f(x) { " +
" if (typeof x.impossible == 'undefined') throw Error();" +
" return x.impossible;" +
"}");
}
public void testMissingProperty43() throws Exception {
testTypes(
"function f(x) { " +
" return /** @type {number} */ (x.impossible) && 1;" +
"}");
}
public void testReflectObject1() throws Exception {
testClosureTypes(
"var goog = {}; goog.reflect = {}; " +
"goog.reflect.object = function(x, y){};" +
"/** @constructor */ function A() {}" +
"goog.reflect.object(A, {x: 3});",
null);
}
public void testReflectObject2() throws Exception {
testClosureTypes(
"var goog = {}; goog.reflect = {}; " +
"goog.reflect.object = function(x, y){};" +
"/** @param {string} x */ function f(x) {}" +
"/** @constructor */ function A() {}" +
"goog.reflect.object(A, {x: f(1 + 1)});",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testLends1() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, /** @lends */ ({bar: 1}));",
"Bad type annotation. missing object name in @lends tag." + BAD_TYPE_WIKI_LINK);
}
public void testLends2() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, /** @lends {Foob} */ ({bar: 1}));",
"Variable Foob not declared before @lends annotation.");
}
public void testLends3() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, {bar: 1});" +
"alert(Foo.bar);",
"Property bar never defined on Foo");
}
public void testLends4() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, /** @lends {Foo} */ ({bar: 1}));" +
"alert(Foo.bar);");
}
public void testLends5() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, {bar: 1});" +
"alert((new Foo()).bar);",
"Property bar never defined on Foo");
}
public void testLends6() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, /** @lends {Foo.prototype} */ ({bar: 1}));" +
"alert((new Foo()).bar);");
}
public void testLends7() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, /** @lends {Foo.prototype|Foo} */ ({bar: 1}));",
"Bad type annotation. expected closing }" + BAD_TYPE_WIKI_LINK);
}
public void testLends8() throws Exception {
testTypes(
"function extend(x, y) {}" +
"/** @type {number} */ var Foo = 3;" +
"extend(Foo, /** @lends {Foo} */ ({bar: 1}));",
"May only lend properties to object types. Foo has type number.");
}
public void testLends9() throws Exception {
testClosureTypesMultipleWarnings(
"function extend(x, y) {}" +
"/** @constructor */ function Foo() {}" +
"extend(Foo, /** @lends {!Foo} */ ({bar: 1}));",
ImmutableList.of(
"Bad type annotation. expected closing }" + BAD_TYPE_WIKI_LINK,
"Bad type annotation. missing object name in @lends tag." + BAD_TYPE_WIKI_LINK));
}
public void testLends10() throws Exception {
testTypes(
"function defineClass(x) { return function() {}; } " +
"/** @constructor */" +
"var Foo = defineClass(" +
" /** @lends {Foo.prototype} */ ({/** @type {number} */ bar: 1}));" +
"/** @return {string} */ function f() { return (new Foo()).bar; }",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testLends11() throws Exception {
testTypes(
"function defineClass(x, y) { return function() {}; } "
+ "/** @constructor */"
+ "var Foo = function() {};"
+ "/** @return {*} */ Foo.prototype.bar = function() { return 3; };"
+ "/**\n"
+ " * @constructor\n"
+ " * @extends {Foo}\n"
+ " */\n"
+ "var SubFoo = defineClass(Foo, "
+ " /** @lends {SubFoo.prototype} */ ({\n"
+ " /** @override @return {number} */ bar: function() { return 3; }}));"
+ "/** @return {string} */ function f() { return (new SubFoo()).bar(); }",
"inconsistent return type\n" + "found : number\n" + "required: string");
}
public void testDeclaredNativeTypeEquality() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ function Object() {};");
assertTypeEquals(registry.getNativeType(JSTypeNative.OBJECT_FUNCTION_TYPE),
n.getFirstChild().getJSType());
}
public void testUndefinedVar() throws Exception {
Node n = parseAndTypeCheck("var undefined;");
assertTypeEquals(registry.getNativeType(JSTypeNative.VOID_TYPE),
n.getFirstFirstChild().getJSType());
}
public void testFlowScopeBug1() throws Exception {
Node n = parseAndTypeCheck("/** @param {number} a \n"
+ "* @param {number} b */\n"
+ "function f(a, b) {\n"
+ "/** @type {number} */"
+ "var i = 0;"
+ "for (; (i + a) < b; ++i) {}}");
// check the type of the add node for i + f
assertTypeEquals(registry.getNativeType(JSTypeNative.NUMBER_TYPE),
n.getFirstChild().getLastChild().getLastChild().getFirstChild()
.getNext().getFirstChild().getJSType());
}
public void testFlowScopeBug2() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ function Foo() {};\n"
+ "Foo.prototype.hi = false;"
+ "function foo(a, b) {\n"
+ " /** @type {Array} */"
+ " var arr;"
+ " /** @type {number} */"
+ " var iter;"
+ " for (iter = 0; iter < arr.length; ++ iter) {"
+ " /** @type {Foo} */"
+ " var afoo = arr[iter];"
+ " afoo;"
+ " }"
+ "}");
// check the type of afoo when referenced
assertTypeEquals(registry.createNullableType(registry.getType("Foo")),
n.getLastChild().getLastChild().getLastChild().getLastChild()
.getLastChild().getLastChild().getJSType());
}
public void testAddSingletonGetter() throws Exception {
Node n = parseAndTypeCheck(
"/** @constructor */ function Foo() {};\n" +
"goog.addSingletonGetter(Foo);");
ObjectType o = (ObjectType) n.getFirstChild().getJSType();
assertEquals("function (): Foo",
o.getPropertyType("getInstance").toString());
assertEquals("Foo", o.getPropertyType("instance_").toString());
}
public void testTypeCheckStandaloneAST() throws Exception {
Node n = compiler.parseTestCode("function Foo() { }");
typeCheck(n);
MemoizedScopeCreator scopeCreator = new MemoizedScopeCreator(
new TypedScopeCreator(compiler));
TypedScope topScope = scopeCreator.createScope(n, null);
Node second = compiler.parseTestCode("new Foo");
Node externs = IR.root();
Node externAndJsRoot = IR.root(externs, second);
new TypeCheck(
compiler,
new SemanticReverseAbstractInterpreter(registry),
registry, topScope, scopeCreator)
.process(null, second);
assertEquals(1, compiler.getWarningCount());
assertEquals("cannot instantiate non-constructor",
compiler.getWarnings()[0].description);
}
public void testUpdateParameterTypeOnClosure() throws Exception {
testTypesWithExterns(
"/**\n" +
"* @constructor\n" +
"* @param {*=} opt_value\n" +
"* @return {!Object}\n" +
"*/\n" +
"function Object(opt_value) {}\n" +
"/**\n" +
"* @constructor\n" +
"* @param {...*} var_args\n" +
"*/\n" +
"function Function(var_args) {}\n" +
"/**\n" +
"* @type {Function}\n" +
"*/\n" +
// The line below sets JSDocInfo on Object so that the type of the
// argument to function f has JSDoc through its prototype chain.
"Object.prototype.constructor = function() {};\n",
"/**\n" +
"* @param {function(): boolean} fn\n" +
"*/\n" +
"function f(fn) {}\n" +
"f(function() { });\n");
}
public void testTemplatedThisType1() throws Exception {
testTypes(
"/** @constructor */\n" +
"function Foo() {}\n" +
"/**\n" +
" * @this {T}\n" +
" * @return {T}\n" +
" * @template T\n" +
" */\n" +
"Foo.prototype.method = function() {};\n" +
"/**\n" +
" * @constructor\n" +
" * @extends {Foo}\n" +
" */\n" +
"function Bar() {}\n" +
"var g = new Bar().method();\n" +
"/**\n" +
" * @param {number} a\n" +
" */\n" +
"function compute(a) {};\n" +
"compute(g);\n",
"actual parameter 1 of compute does not match formal parameter\n" +
"found : Bar\n" +
"required: number");
}
public void testTemplatedThisType2() throws Exception {
testTypes(
"/**\n" +
" * @this {Array<T>|{length:number}}\n" +
" * @return {T}\n" +
" * @template T\n" +
" */\n" +
"Array.prototype.method = function() {};\n" +
"(function(){\n" +
" Array.prototype.method.call(arguments);" +
"})();");
}
public void testTemplateType1() throws Exception {
testTypes(
"/**\n" +
"* @param {T} x\n" +
"* @param {T} y\n" +
"* @param {function(this:T, ...)} z\n" +
"* @template T\n" +
"*/\n" +
"function f(x, y, z) {}\n" +
"f(this, this, function() { this });");
}
public void testTemplateType2() throws Exception {
// "this" types need to be coerced for ES3 style function or left
// allow for ES5-strict methods.
testTypes(
"/**\n" +
"* @param {T} x\n" +
"* @param {function(this:T, ...)} y\n" +
"* @template T\n" +
"*/\n" +
"function f(x, y) {}\n" +
"f(0, function() {});");
}
public void testTemplateType3() throws Exception {
testTypes(
"/**" +
" * @param {T} v\n" +
" * @param {function(T)} f\n" +
" * @template T\n" +
" */\n" +
"function call(v, f) { f.call(null, v); }" +
"/** @type {string} */ var s;" +
"call(3, function(x) {" +
" x = true;" +
" s = x;" +
"});",
"assignment\n" +
"found : boolean\n" +
"required: string");
}
public void testTemplateType4() throws Exception {
testTypes(
"/**" +
" * @param {...T} p\n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(p) { return p; }\n" +
"/** @type {!Object} */ var x;" +
"x = fn(3, null);",
"assignment\n" +
"found : (null|number)\n" +
"required: Object");
}
public void testTemplateType5() throws Exception {
compiler.getOptions().setCodingConvention(new GoogleCodingConvention());
testTypes(
"var CGI_PARAM_RETRY_COUNT = 'rc';" +
"" +
"/**" +
" * @param {...T} p\n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(p) { return p; }\n" +
"/** @type {!Object} */ var x;" +
"" +
"/** @return {void} */\n" +
"function aScope() {\n" +
" x = fn(CGI_PARAM_RETRY_COUNT, 1);\n" +
"}",
"assignment\n" +
"found : (number|string)\n" +
"required: Object");
}
public void testTemplateType6() throws Exception {
testTypes(
"/**" +
" * @param {Array<T>} arr \n" +
" * @param {?function(T)} f \n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(arr, f) { return arr[0]; }\n" +
"/** @param {Array<number>} arr */ function g(arr) {" +
" /** @type {!Object} */ var x = fn.call(null, arr, null);" +
"}",
"initializing variable\n" +
"found : number\n" +
"required: Object");
}
public void testTemplateType7() throws Exception {
// TODO(johnlenz): As the @this type for Array.prototype.push includes
// "{length:number}" (and this includes "Array<number>") we don't
// get a type warning here. Consider special-casing array methods.
testTypes(
"/** @type {!Array<string>} */\n" +
"var query = [];\n" +
"query.push(1);\n");
}
public void testTemplateType8() throws Exception {
testTypes(
"/** @constructor \n" +
" * @template S,T\n" +
" */\n" +
"function Bar() {}\n" +
"/**" +
" * @param {Bar<T>} bar \n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(bar) {}\n" +
"/** @param {Bar<number>} bar */ function g(bar) {" +
" /** @type {!Object} */ var x = fn(bar);" +
"}",
"initializing variable\n" +
"found : number\n" +
"required: Object");
}
public void testTemplateType9() throws Exception {
// verify interface type parameters are recognized.
testTypes(
"/** @interface \n" +
" * @template S,T\n" +
" */\n" +
"function Bar() {}\n" +
"/**" +
" * @param {Bar<T>} bar \n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(bar) {}\n" +
"/** @param {Bar<number>} bar */ function g(bar) {" +
" /** @type {!Object} */ var x = fn(bar);" +
"}",
"initializing variable\n" +
"found : number\n" +
"required: Object");
}
public void testTemplateType10() throws Exception {
// verify a type parameterized with unknown can be assigned to
// the same type with any other type parameter.
testTypes(
"/** @constructor \n" +
" * @template T\n" +
" */\n" +
"function Bar() {}\n" +
"\n" +
"" +
"/** @type {!Bar<?>} */ var x;" +
"/** @type {!Bar<number>} */ var y;" +
"y = x;");
}
public void testTemplateType11() throws Exception {
// verify that assignment/subtype relationships work when extending
// templatized types.
testTypes(
"/** @constructor \n" +
" * @template T\n" +
" */\n" +
"function Foo() {}\n" +
"" +
"/** @constructor \n" +
" * @extends {Foo<string>}\n" +
" */\n" +
"function A() {}\n" +
"" +
"/** @constructor \n" +
" * @extends {Foo<number>}\n" +
" */\n" +
"function B() {}\n" +
"" +
"/** @type {!Foo<string>} */ var a = new A();\n" +
"/** @type {!Foo<string>} */ var b = new B();",
"initializing variable\n" +
"found : B\n" +
"required: Foo<string>");
}
public void testTemplateType12() throws Exception {
// verify that assignment/subtype relationships work when implementing
// templatized types.
testTypes(
"/** @interface \n" +
" * @template T\n" +
" */\n" +
"function Foo() {}\n" +
"" +
"/** @constructor \n" +
" * @implements {Foo<string>}\n" +
" */\n" +
"function A() {}\n" +
"" +
"/** @constructor \n" +
" * @implements {Foo<number>}\n" +
" */\n" +
"function B() {}\n" +
"" +
"/** @type {!Foo<string>} */ var a = new A();\n" +
"/** @type {!Foo<string>} */ var b = new B();",
"initializing variable\n" +
"found : B\n" +
"required: Foo<string>");
}
public void testTemplateType13() throws Exception {
// verify that assignment/subtype relationships work when extending
// templatized types.
testTypes(
"/** @constructor \n" +
" * @template T\n" +
" */\n" +
"function Foo() {}\n" +
"" +
"/** @constructor \n" +
" * @template T\n" +
" * @extends {Foo<T>}\n" +
" */\n" +
"function A() {}\n" +
"" +
"var a1 = new A();\n" +
"var a2 = /** @type {!A<string>} */ (new A());\n" +
"var a3 = /** @type {!A<number>} */ (new A());\n" +
"/** @type {!Foo<string>} */ var f1 = a1;\n" +
"/** @type {!Foo<string>} */ var f2 = a2;\n" +
"/** @type {!Foo<string>} */ var f3 = a3;",
"initializing variable\n" +
"found : A<number>\n" +
"required: Foo<string>");
}
public void testTemplateType14() throws Exception {
// verify that assignment/subtype relationships work when implementing
// templatized types.
testTypes(
"/** @interface \n" +
" * @template T\n" +
" */\n" +
"function Foo() {}\n" +
"" +
"/** @constructor \n" +
" * @template T\n" +
" * @implements {Foo<T>}\n" +
" */\n" +
"function A() {}\n" +
"" +
"var a1 = new A();\n" +
"var a2 = /** @type {!A<string>} */ (new A());\n" +
"var a3 = /** @type {!A<number>} */ (new A());\n" +
"/** @type {!Foo<string>} */ var f1 = a1;\n" +
"/** @type {!Foo<string>} */ var f2 = a2;\n" +
"/** @type {!Foo<string>} */ var f3 = a3;",
"initializing variable\n" +
"found : A<number>\n" +
"required: Foo<string>");
}
public void testTemplateType15() throws Exception {
testTypes(
"/**" +
" * @param {{foo:T}} p\n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(p) { return p.foo; }\n" +
"/** @type {!Object} */ var x;" +
"x = fn({foo:3});",
"assignment\n" +
"found : number\n" +
"required: Object");
}
public void testTemplateType16() throws Exception {
testTypes(
"/** @constructor */ function C() {\n" +
" /** @type {number} */ this.foo = 1\n" +
"}\n" +
"/**\n" +
" * @param {{foo:T}} p\n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(p) { return p.foo; }\n" +
"/** @type {!Object} */ var x;" +
"x = fn(new C());",
"assignment\n" +
"found : number\n" +
"required: Object");
}
public void testTemplateType17() throws Exception {
testTypes(
"/** @constructor */ function C() {}\n" +
"C.prototype.foo = 1;\n" +
"/**\n" +
" * @param {{foo:T}} p\n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(p) { return p.foo; }\n" +
"/** @type {!Object} */ var x;" +
"x = fn(new C());",
"assignment\n" +
"found : number\n" +
"required: Object");
}
public void testTemplateType18() throws Exception {
// Until template types can be restricted to exclude undefined, they
// are always optional.
testTypes(
"/** @constructor */ function C() {}\n" +
"C.prototype.foo = 1;\n" +
"/**\n" +
" * @param {{foo:T}} p\n" +
" * @return {T} \n" +
" * @template T\n" +
" */\n" +
"function fn(p) { return p.foo; }\n" +
"/** @type {!Object} */ var x;" +
"x = fn({});");
}
public void testTemplateType19() throws Exception {
testTypes(
"/**\n" +
" * @param {T} t\n" +
" * @param {U} u\n" +
" * @return {{t:T, u:U}} \n" +
" * @template T,U\n" +
" */\n" +
"function fn(t, u) { return {t:t, u:u}; }\n" +
"/** @type {null} */ var x = fn(1, 'str');",
"initializing variable\n" +
"found : {t: number, u: string}\n" +
"required: null");
}
public void testTemplateType20() throws Exception {
// "this" types is inferred when the parameters are declared.
testTypes(
"/** @constructor */ function C() {\n" +
" /** @type {void} */ this.x;\n" +
"}\n" +
"/**\n" +
"* @param {T} x\n" +
"* @param {function(this:T, ...)} y\n" +
"* @template T\n" +
"*/\n" +
"function f(x, y) {}\n" +
"f(new C, /** @param {number} a */ function(a) {this.x = a;});",
"assignment to property x of C\n" +
"found : number\n" +
"required: undefined");
}
public void testTemplateType21() throws Exception {
// "this" types is inferred when the parameters are declared.
testTypes(
"/** @interface @template T */ function A() {}\n" +
"/** @constructor @implements {A<Foo>} */\n" +
"function Foo() {}\n" +
"/** @constructor @implements {A<Bar>} */\n" +
"function Bar() {}\n" +
"/** @type {!Foo} */\n" +
"var x = new Bar();\n",
"initializing variable\n" +
"found : Bar\n" +
"required: Foo");
}
public void testTemplateType22() throws Exception {
// "this" types is inferred when the parameters are declared.
testTypes(
"/** @interface @template T */ function A() {}\n" +
"/** @interface @template T */ function B() {}\n" +
"/** @constructor @implements {A<Foo>} */\n" +
"function Foo() {}\n" +
"/** @constructor @implements {B<Foo>} */\n" +
"function Bar() {}\n" +
"/** @constructor @implements {B<Foo>} */\n" +
"function Qux() {}\n" +
"/** @type {!Qux} */\n" +
"var x = new Bar();\n",
"initializing variable\n" +
"found : Bar\n" +
"required: Qux");
}
public void testTemplateType23() throws Exception {
// "this" types is inferred when the parameters are declared.
testTypes(
"/** @interface @template T */ function A() {}\n" +
"/** @constructor @implements {A<Foo>} */\n" +
"function Foo() {}\n" +
"/** @type {!Foo} */\n" +
"var x = new Foo();\n");
}
public void testTemplateType24() throws Exception {
// Recursive templated type definition.
testTypes(LINE_JOINER.join(
"/**",
" * @constructor",
" * @template T",
" * @param {T} x",
" */",
"function Foo(x) {",
" /** @type {T} */",
" this.p = x;",
"}",
"/** @return {Foo<Foo<T>>} */",
"Foo.prototype.m = function() {",
" return null;",
"};",
"/** @return {T} */",
"Foo.prototype.get = function() {",
" return this.p;",
"};",
"var /** null */ n = new Foo(new Object).m().get();"),
"initializing variable\n"
+ "found : (Foo<Object>|null)\n"
+ "required: null");
}
public void testTemplateType25() throws Exception {
// Non-nullable recursive templated type definition.
testTypes(LINE_JOINER.join(
"/**",
" * @constructor",
" * @template T",
" * @param {T} x",
" */",
"function Foo(x) {",
" /** @type {T} */",
" this.p = x;",
"}",
"/** @return {!Foo<!Foo<T>>} */",
"Foo.prototype.m = function() {",
" return new Foo(new Foo(new Object));",
"};",
"/** @return {T} */",
"Foo.prototype.get = function() {",
" return this.p;",
"};",
"var /** null */ n = new Foo(new Object).m().get();"),
"initializing variable\n"
+ "found : Foo<Object>\n"
+ "required: null");
}
public void testTemplateType26() throws Exception {
// Class hierarchies which use the same template parameter name should not be treated as
// infinite recursion.
testTypes(
LINE_JOINER.join(
"/**",
" * @param {T} bar",
" * @constructor",
" * @template T",
" */",
"function Bar(bar) {",
" /** @type {T} */",
" this.bar = bar;",
"}",
"/** @return {T} */",
"Bar.prototype.getBar = function() {",
" return this.bar;",
"};",
"/**",
" * @param {T} foo",
" * @constructor",
" * @template T",
" * @extends {Bar<!Array<T>>}",
" */",
"function Foo(foo) {",
" /** @type {T} */",
" this.foo = foo;",
"}",
"var /** null */ n = new Foo(new Object).getBar();"),
"initializing variable\n" + "found : Array<Object>\n" + "required: null");
}
public void testTemplateTypeForwardReference() throws Exception {
// TODO(martinprobst): the test below asserts incorrect behavior for backwards compatibility.
testTypes(
LINE_JOINER.join(
"/** @param {!Foo<string>} x */",
"function f(x) {}",
"",
"/**",
" * @template T",
" * @constructor",
" */",
"function Foo() {}",
"",
"/** @param {!Foo<number>} x */",
"function g(x) {",
" f(x);",
"}"));
}
public void testTemplateTypeForwardReference_declared() throws Exception {
compiler.forwardDeclareType("Foo");
testTypes(
LINE_JOINER.join(
"/** @param {!Foo<string>} x */",
"function f(x) {}",
"",
"/**",
" * @template T",
" * @constructor",
" */",
"function Foo() {}",
"",
"/** @param {!Foo<number>} x */",
"function g(x) {",
" f(x);",
"}"));
}
public void testTemplateTypeForwardReference_declaredMissing() throws Exception {
compiler.forwardDeclareType("Foo");
testTypes(
LINE_JOINER.join(
"/** @param {!Foo<DoesNotExist>} x */",
"function f(x) {}"));
}
public void testTemplateTypeForwardReference_extends() throws Exception {
compiler.forwardDeclareType("Bar");
compiler.forwardDeclareType("Baz");
testTypes(
LINE_JOINER.join(
"/** @constructor @extends {Bar<Baz>} */",
"function Foo() {}",
"/** @constructor */",
"function Bar() {}"
));
}
public void testSubtypeNotTemplated1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @interface @template T */ function A() {}",
"/** @constructor @implements {A<U>} @template U */ function Foo() {}",
"function f(/** (!Object|!Foo<string>) */ x) {",
" var /** null */ n = x;",
"}"),
"initializing variable\n"
+ "found : Object\n"
+ "required: null");
}
public void testSubtypeNotTemplated2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @interface @template T */ function A() {}",
"/** @constructor @implements {A<U>} @template U */ function Foo() {}",
"function f(/** (!Object|!Foo) */ x) {",
" var /** null */ n = x;",
"}"),
"initializing variable\n"
+ "found : Object\n"
+ "required: null");
}
public void testTemplateTypeWithUnresolvedType() throws Exception {
testClosureTypes(
"var goog = {};\n" +
"goog.addDependency = function(a,b,c){};\n" +
"goog.addDependency('a.js', ['Color'], []);\n" +
"/** @interface @template T */ function C() {}\n" +
"/** @return {!Color} */ C.prototype.method;\n" +
"/** @constructor @implements {C} */ function D() {}\n" +
"/** @override */ D.prototype.method = function() {};", null); // no warning expected.
}
public void testTemplateTypeWithTypeDef1a() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template T\n" +
" * @param {T} x\n" +
" */\n" +
"function Generic(x) {}\n" +
"\n" +
"/** @constructor */\n" +
"function Foo() {}\n" +
"" +
"/** @typedef {!Foo} */\n" +
"var Bar;\n" +
"" +
"/** @type {Generic<!Foo>} */ var x;\n" +
"/** @type {Generic<!Bar>} */ var y;\n" +
"" +
"x = y;\n" + // no warning
"/** @type {null} */ var z1 = y;\n" +
"",
"initializing variable\n" +
"found : (Generic<Foo>|null)\n" +
"required: null");
}
public void testTemplateTypeWithTypeDef1b() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template T\n" +
" * @param {T} x\n" +
" */\n" +
"function Generic(x) {}\n" +
"\n" +
"/** @constructor */\n" +
"function Foo() {}\n" +
"" +
"/** @typedef {!Foo} */\n" +
"var Bar;\n" +
"" +
"/** @type {Generic<!Foo>} */ var x;\n" +
"/** @type {Generic<!Bar>} */ var y;\n" +
"" +
"y = x;\n" + // no warning.
"/** @type {null} */ var z1 = x;\n" +
"",
"initializing variable\n" +
"found : (Generic<Foo>|null)\n" +
"required: null");
}
public void testTemplateTypeWithTypeDef2a() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template T\n" +
" * @param {T} x\n" +
" */\n" +
"function Generic(x) {}\n" +
"\n" +
"/** @constructor */\n" +
"function Foo() {}\n" +
"\n" +
"/** @typedef {!Foo} */\n" +
"var Bar;\n" +
"\n" +
"function f(/** Generic<!Bar> */ x) {}\n" +
"/** @type {Generic<!Foo>} */ var x;\n" +
"f(x);\n"); // no warning expected.
}
public void testTemplateTypeWithTypeDef2b() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template T\n" +
" * @param {T} x\n" +
" */\n" +
"function Generic(x) {}\n" +
"\n" +
"/** @constructor */\n" +
"function Foo() {}\n" +
"\n" +
"/** @typedef {!Foo} */\n" +
"var Bar;\n" +
"\n" +
"function f(/** Generic<!Bar> */ x) {}\n" +
"/** @type {Generic<!Bar>} */ var x;\n" +
"f(x);\n"); // no warning expected.
}
public void testTemplateTypeWithTypeDef2c() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template T\n" +
" * @param {T} x\n" +
" */\n" +
"function Generic(x) {}\n" +
"\n" +
"/** @constructor */\n" +
"function Foo() {}\n" +
"\n" +
"/** @typedef {!Foo} */\n" +
"var Bar;\n" +
"\n" +
"function f(/** Generic<!Foo> */ x) {}\n" +
"/** @type {Generic<!Foo>} */ var x;\n" +
"f(x);\n"); // no warning expected.
}
public void testTemplateTypeWithTypeDef2d() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template T\n" +
" * @param {T} x\n" +
" */\n" +
"function Generic(x) {}\n" +
"\n" +
"/** @constructor */\n" +
"function Foo() {}\n" +
"\n" +
"/** @typedef {!Foo} */\n" +
"var Bar;\n" +
"\n" +
"function f(/** Generic<!Foo> */ x) {}\n" +
"/** @type {Generic<!Bar>} */ var x;\n" +
"f(x);\n"); // no warning expected.
}
public void testTemplatedFunctionInUnion1() throws Exception {
testTypes(
"/**\n" +
"* @param {T} x\n" +
"* @param {function(this:T, ...)|{fn:Function}} z\n" +
"* @template T\n" +
"*/\n" +
"function f(x, z) {}\n" +
"f([], function() { /** @type {string} */ var x = this });",
"initializing variable\n" +
"found : Array\n" +
"required: string");
}
public void testTemplateTypeRecursion1() throws Exception {
testTypes(
"/** @typedef {{a: D2}} */\n" +
"var D1;\n" +
"\n" +
"/** @typedef {{b: D1}} */\n" +
"var D2;\n" +
"\n" +
"fn(x);\n" +
"\n" +
"\n" +
"/**\n" +
" * @param {!D1} s\n" +
" * @template T\n" +
" */\n" +
"var fn = function(s) {};"
);
}
public void testTemplateTypeRecursion2() throws Exception {
testTypes(
"/** @typedef {{a: D2}} */\n" +
"var D1;\n" +
"\n" +
"/** @typedef {{b: D1}} */\n" +
"var D2;\n" +
"\n" +
"/** @type {D1} */ var x;" +
"fn(x);\n" +
"\n" +
"\n" +
"/**\n" +
" * @param {!D1} s\n" +
" * @template T\n" +
" */\n" +
"var fn = function(s) {};"
);
}
public void testTemplateTypeRecursion3() throws Exception {
testTypes(
"/** @typedef {{a: function(D2)}} */\n" +
"var D1;\n" +
"\n" +
"/** @typedef {{b: D1}} */\n" +
"var D2;\n" +
"\n" +
"/** @type {D1} */ var x;" +
"fn(x);\n" +
"\n" +
"\n" +
"/**\n" +
" * @param {!D1} s\n" +
" * @template T\n" +
" */\n" +
"var fn = function(s) {};"
);
}
public void disable_testBadTemplateType4() {
// TODO(johnlenz): Add a check for useless of template types.
// Unless there are at least two references to a Template type in
// a definition it isn't useful.
testTypes(
"/**\n" +
"* @template T\n" +
"*/\n" +
"function f() {}\n" +
"f();",
FunctionTypeBuilder.TEMPLATE_TYPE_EXPECTED.format());
}
public void disable_testBadTemplateType5() {
// TODO(johnlenz): Add a check for useless of template types.
// Unless there are at least two references to a Template type in
// a definition it isn't useful.
testTypes(
"/**\n" +
"* @template T\n" +
"* @return {T}\n" +
"*/\n" +
"function f() {}\n" +
"f();",
FunctionTypeBuilder.TEMPLATE_TYPE_EXPECTED.format());
}
public void disable_testFunctionLiteralUndefinedThisArgument() {
// TODO(johnlenz): this was a weird error. We should add a general
// restriction on what is accepted for T. Something like:
// "@template T of {Object|string}" or some such.
testTypes(""
+ "/**\n"
+ " * @param {function(this:T, ...)?} fn\n"
+ " * @param {?T} opt_obj\n"
+ " * @template T\n"
+ " */\n"
+ "function baz(fn, opt_obj) {}\n"
+ "baz(function() { this; });",
"Function literal argument refers to undefined this argument");
}
public void testFunctionLiteralDefinedThisArgument() throws Exception {
testTypes(""
+ "/**\n"
+ " * @param {function(this:T, ...)?} fn\n"
+ " * @param {?T} opt_obj\n"
+ " * @template T\n"
+ " */\n"
+ "function baz(fn, opt_obj) {}\n"
+ "baz(function() { this; }, {});");
}
public void testFunctionLiteralDefinedThisArgument2() throws Exception {
testTypes(""
+ "/** @param {string} x */ function f(x) {}"
+ "/**\n"
+ " * @param {?function(this:T, ...)} fn\n"
+ " * @param {T=} opt_obj\n"
+ " * @template T\n"
+ " */\n"
+ "function baz(fn, opt_obj) {}\n"
+ "function g() { baz(function() { f(this.length); }, []); }",
"actual parameter 1 of f does not match formal parameter\n"
+ "found : number\n"
+ "required: string");
}
public void testFunctionLiteralUnreadNullThisArgument() throws Exception {
testTypes(""
+ "/**\n"
+ " * @param {function(this:T, ...)?} fn\n"
+ " * @param {?T} opt_obj\n"
+ " * @template T\n"
+ " */\n"
+ "function baz(fn, opt_obj) {}\n"
+ "baz(function() {}, null);");
}
public void testUnionTemplateThisType() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @return {F|Array} */ function g() { return []; }" +
"/** @param {F} x */ function h(x) { }" +
"/**\n" +
"* @param {T} x\n" +
"* @param {function(this:T, ...)} y\n" +
"* @template T\n" +
"*/\n" +
"function f(x, y) {}\n" +
"f(g(), function() { h(this); });",
"actual parameter 1 of h does not match formal parameter\n" +
"found : (Array|F|null)\n" +
"required: (F|null)");
}
public void testActiveXObject() throws Exception {
testTypes(
"/** @type {Object} */ var x = new ActiveXObject();" +
"/** @type { {impossibleProperty} } */ var y = new ActiveXObject();");
}
public void testRecordType1() throws Exception {
testTypes(
"/** @param {{prop: number}} x */" +
"function f(x) {}" +
"f({});",
"actual parameter 1 of f does not match formal parameter\n" +
"found : {prop: (number|undefined)}\n" +
"required: {prop: number}");
}
public void testRecordType2() throws Exception {
testTypes(
"/** @param {{prop: (number|undefined)}} x */" +
"function f(x) {}" +
"f({});");
}
public void testRecordType3() throws Exception {
testTypes(
"/** @param {{prop: number}} x */" +
"function f(x) {}" +
"f({prop: 'x'});",
"actual parameter 1 of f does not match formal parameter\n" +
"found : {prop: (number|string)}\n" +
"required: {prop: number}");
}
public void testRecordType4() throws Exception {
// Notice that we do not do flow-based inference on the object type:
// We don't try to prove that x.prop may not be string until x
// gets passed to g.
testClosureTypesMultipleWarnings(
"/** @param {{prop: (number|undefined)}} x */" +
"function f(x) {}" +
"/** @param {{prop: (string|undefined)}} x */" +
"function g(x) {}" +
"var x = {}; f(x); g(x);",
ImmutableList.of(
"actual parameter 1 of f does not match formal parameter\n" +
"found : {prop: (number|string|undefined)}\n" +
"required: {prop: (number|undefined)}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : {prop: (number|string|undefined)}\n" +
"required: {prop: (string|undefined)}"));
}
public void testRecordType5() throws Exception {
testTypes(
"/** @param {{prop: (number|undefined)}} x */" +
"function f(x) {}" +
"/** @param {{otherProp: (string|undefined)}} x */" +
"function g(x) {}" +
"var x = {}; f(x); g(x);");
}
public void testRecordType6() throws Exception {
testTypes(
"/** @return {{prop: (number|undefined)}} x */" +
"function f() { return {}; }");
}
public void testRecordType7() throws Exception {
testTypes(
"/** @return {{prop: (number|undefined)}} x */" +
"function f() { var x = {}; g(x); return x; }" +
"/** @param {number} x */" +
"function g(x) {}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : {prop: (number|undefined)}\n" +
"required: number");
}
public void testRecordType8() throws Exception {
testTypes(
"/** @return {{prop: (number|string)}} x */" +
"function f() { var x = {prop: 3}; g(x.prop); return x; }" +
"/** @param {string} x */" +
"function g(x) {}",
"actual parameter 1 of g does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testDuplicateRecordFields1() throws Exception {
testTypes("/**"
+ "* @param {{x:string, x:number}} a"
+ "*/"
+ "function f(a) {};",
"Bad type annotation. Duplicate record field x." + BAD_TYPE_WIKI_LINK);
}
public void testDuplicateRecordFields2() throws Exception {
testTypes("/**"
+ "* @param {{name:string,number:x,number:y}} a"
+ " */"
+ "function f(a) {};",
new String[] {"Bad type annotation. Unknown type x",
"Bad type annotation. Duplicate record field number." + BAD_TYPE_WIKI_LINK});
}
public void testMultipleExtendsInterface1() throws Exception {
testTypes("/** @interface */ function base1() {}\n"
+ "/** @interface */ function base2() {}\n"
+ "/** @interface\n"
+ "* @extends {base1}\n"
+ "* @extends {base2}\n"
+ "*/\n"
+ "function derived() {}");
}
public void testMultipleExtendsInterface2() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"Int0.prototype.foo = function() {};" +
"/** @interface \n @extends {Int0} \n @extends {Int1} */" +
"function Int2() {};" +
"/** @constructor\n @implements {Int2} */function Foo() {};",
"property foo on interface Int0 is not implemented by type Foo");
}
public void testMultipleExtendsInterface3() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"Int1.prototype.foo = function() {};" +
"/** @interface \n @extends {Int0} \n @extends {Int1} */" +
"function Int2() {};" +
"/** @constructor\n @implements {Int2} */function Foo() {};",
"property foo on interface Int1 is not implemented by type Foo");
}
public void testMultipleExtendsInterface4() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"/** @interface \n @extends {Int0} \n @extends {Int1} \n" +
" @extends {number} */" +
"function Int2() {};" +
"/** @constructor\n @implements {Int2} */function Foo() {};",
"Int2 @extends non-object type number");
}
public void testMultipleExtendsInterface5() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @constructor */function Int1() {};" +
"/** @return {string} x */" +
"/** @interface \n @extends {Int0} \n @extends {Int1} */" +
"function Int2() {};",
"Int2 cannot extend this type; interfaces can only extend interfaces");
}
public void testMultipleExtendsInterface6() throws Exception {
testTypes(
"/** @interface */function Super1() {};" +
"/** @interface */function Super2() {};" +
"/** @param {number} bar */Super2.prototype.foo = function(bar) {};" +
"/** @interface\n @extends {Super1}\n " +
"@extends {Super2} */function Sub() {};" +
"/** @override\n @param {string} bar */Sub.prototype.foo =\n" +
"function(bar) {};",
"mismatch of the foo property type and the type of the property it " +
"overrides from superclass Super2\n" +
"original: function (this:Super2, number): undefined\n" +
"override: function (this:Sub, string): undefined");
}
public void testMultipleExtendsInterfaceAssignment() throws Exception {
testTypes("/** @interface */var I1 = function() {};\n" +
"/** @interface */ var I2 = function() {}\n" +
"/** @interface\n@extends {I1}\n@extends {I2}*/" +
"var I3 = function() {};\n" +
"/** @constructor\n@implements {I3}*/var T = function() {};\n" +
"var t = new T();\n" +
"/** @type {I1} */var i1 = t;\n" +
"/** @type {I2} */var i2 = t;\n" +
"/** @type {I3} */var i3 = t;\n" +
"i1 = i3;\n" +
"i2 = i3;\n");
}
public void testMultipleExtendsInterfaceParamPass() throws Exception {
testTypes(LINE_JOINER.join(
"/** @interface */",
"var I1 = function() {};",
"/** @interface */",
"var I2 = function() {}",
"/** @interface @extends {I1} @extends {I2} */",
"var I3 = function() {};",
"/** @constructor @implements {I3} */",
"var T = function() {};",
"var t = new T();",
"/**",
" * @param {I1} x",
" * @param {I2} y",
" * @param {I3} z",
" */",
"function foo(x,y,z){};",
"foo(t,t,t)"));
}
public void testBadMultipleExtendsClass() throws Exception {
testTypes("/** @constructor */ function base1() {}\n"
+ "/** @constructor */ function base2() {}\n"
+ "/** @constructor\n"
+ "* @extends {base1}\n"
+ "* @extends {base2}\n"
+ "*/\n"
+ "function derived() {}",
"Bad type annotation. type annotation incompatible with other annotations."
+ BAD_TYPE_WIKI_LINK);
}
public void testInterfaceExtendsResolution() throws Exception {
testTypes("/** @interface \n @extends {A} */ function B() {};\n" +
"/** @constructor \n @implements {B} */ function C() {};\n" +
"/** @interface */ function A() {};");
}
public void testPropertyCanBeDefinedInObject() throws Exception {
testTypes("/** @interface */ function I() {};" +
"I.prototype.bar = function() {};" +
"/** @type {Object} */ var foo;" +
"foo.bar();");
}
private void checkObjectType(ObjectType objectType, String propertyName,
JSType expectedType) {
assertTrue("Expected " + objectType.getReferenceName() +
" to have property " +
propertyName, objectType.hasProperty(propertyName));
assertTypeEquals("Expected " + objectType.getReferenceName() +
"'s property " +
propertyName + " to have type " + expectedType,
expectedType, objectType.getPropertyType(propertyName));
}
public void testExtendedInterfacePropertiesCompatibility1() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"/** @type {number} */" +
"Int0.prototype.foo;" +
"/** @type {string} */" +
"Int1.prototype.foo;" +
"/** @interface \n @extends {Int0} \n @extends {Int1} */" +
"function Int2() {};",
"Interface Int2 has a property foo with incompatible types in its " +
"super interfaces Int0 and Int1");
}
public void testExtendedInterfacePropertiesCompatibility2() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"/** @interface */function Int2() {};" +
"/** @type {number} */" +
"Int0.prototype.foo;" +
"/** @type {string} */" +
"Int1.prototype.foo;" +
"/** @type {Object} */" +
"Int2.prototype.foo;" +
"/** @interface \n @extends {Int0} \n @extends {Int1} \n" +
"@extends {Int2}*/" +
"function Int3() {};",
new String[] {
"Interface Int3 has a property foo with incompatible types in " +
"its super interfaces Int0 and Int1",
"Interface Int3 has a property foo with incompatible types in " +
"its super interfaces Int1 and Int2"
});
}
public void testExtendedInterfacePropertiesCompatibility3() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"/** @type {number} */" +
"Int0.prototype.foo;" +
"/** @type {string} */" +
"Int1.prototype.foo;" +
"/** @interface \n @extends {Int1} */ function Int2() {};" +
"/** @interface \n @extends {Int0} \n @extends {Int2} */" +
"function Int3() {};",
"Interface Int3 has a property foo with incompatible types in its " +
"super interfaces Int0 and Int1");
}
public void testExtendedInterfacePropertiesCompatibility4() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface \n @extends {Int0} */ function Int1() {};" +
"/** @type {number} */" +
"Int0.prototype.foo;" +
"/** @interface */function Int2() {};" +
"/** @interface \n @extends {Int2} */ function Int3() {};" +
"/** @type {string} */" +
"Int2.prototype.foo;" +
"/** @interface \n @extends {Int1} \n @extends {Int3} */" +
"function Int4() {};",
"Interface Int4 has a property foo with incompatible types in its " +
"super interfaces Int0 and Int2");
}
public void testExtendedInterfacePropertiesCompatibility5() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"/** @type {number} */" +
"Int0.prototype.foo;" +
"/** @type {string} */" +
"Int1.prototype.foo;" +
"/** @interface \n @extends {Int1} */ function Int2() {};" +
"/** @interface \n @extends {Int0} \n @extends {Int2} */" +
"function Int3() {};" +
"/** @interface */function Int4() {};" +
"/** @type {number} */" +
"Int4.prototype.foo;" +
"/** @interface \n @extends {Int3} \n @extends {Int4} */" +
"function Int5() {};",
new String[] {
"Interface Int3 has a property foo with incompatible types in its" +
" super interfaces Int0 and Int1",
"Interface Int5 has a property foo with incompatible types in its" +
" super interfaces Int1 and Int4"});
}
public void testExtendedInterfacePropertiesCompatibility6() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"/** @type {number} */" +
"Int0.prototype.foo;" +
"/** @type {string} */" +
"Int1.prototype.foo;" +
"/** @interface \n @extends {Int1} */ function Int2() {};" +
"/** @interface \n @extends {Int0} \n @extends {Int2} */" +
"function Int3() {};" +
"/** @interface */function Int4() {};" +
"/** @type {string} */" +
"Int4.prototype.foo;" +
"/** @interface \n @extends {Int3} \n @extends {Int4} */" +
"function Int5() {};",
"Interface Int3 has a property foo with incompatible types in its" +
" super interfaces Int0 and Int1");
}
public void testExtendedInterfacePropertiesCompatibility7() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"/** @type {number} */" +
"Int0.prototype.foo;" +
"/** @type {string} */" +
"Int1.prototype.foo;" +
"/** @interface \n @extends {Int1} */ function Int2() {};" +
"/** @interface \n @extends {Int0} \n @extends {Int2} */" +
"function Int3() {};" +
"/** @interface */function Int4() {};" +
"/** @type {Object} */" +
"Int4.prototype.foo;" +
"/** @interface \n @extends {Int3} \n @extends {Int4} */" +
"function Int5() {};",
new String[] {
"Interface Int3 has a property foo with incompatible types in its" +
" super interfaces Int0 and Int1",
"Interface Int5 has a property foo with incompatible types in its" +
" super interfaces Int1 and Int4"});
}
public void testExtendedInterfacePropertiesCompatibility8() throws Exception {
testTypes(
"/** @interface */function Int0() {};" +
"/** @interface */function Int1() {};" +
"/** @type {number} */" +
"Int0.prototype.foo;" +
"/** @type {string} */" +
"Int1.prototype.bar;" +
"/** @interface \n @extends {Int1} */ function Int2() {};" +
"/** @interface \n @extends {Int0} \n @extends {Int2} */" +
"function Int3() {};" +
"/** @interface */function Int4() {};" +
"/** @type {Object} */" +
"Int4.prototype.foo;" +
"/** @type {Null} */" +
"Int4.prototype.bar;" +
"/** @interface \n @extends {Int3} \n @extends {Int4} */" +
"function Int5() {};",
new String[] {
"Interface Int5 has a property bar with incompatible types in its" +
" super interfaces Int1 and Int4",
"Interface Int5 has a property foo with incompatible types in its" +
" super interfaces Int0 and Int4"});
}
public void testExtendedInterfacePropertiesCompatibility9() throws Exception {
testTypes(
"/** @interface\n * @template T */function Int0() {};" +
"/** @interface\n * @template T */function Int1() {};" +
"/** @type {T} */" +
"Int0.prototype.foo;" +
"/** @type {T} */" +
"Int1.prototype.foo;" +
"/** @interface \n @extends {Int0<number>} \n @extends {Int1<string>} */" +
"function Int2() {};",
"Interface Int2 has a property foo with incompatible types in its " +
"super interfaces Int0<number> and Int1<string>");
}
public void testExtendedInterfacePropertiesCompatibilityNoError() throws Exception {
testTypes(""
+ "/** @interface */function Int0() {};"
+ "/** @interface */function Int1() {};"
+ "/** @param {number} x */"
+ "Int0.prototype.foo;"
+ "/** @param {number} x */"
+ "Int1.prototype.foo;"
+ "/** @interface \n * @extends {Int0} \n * @extends {Int1} */"
+ "function Int2() {};");
}
public void testGenerics1() throws Exception {
String fnDecl = "/** \n" +
" * @param {T} x \n" +
" * @param {function(T):T} y \n" +
" * @template T\n" +
" */ \n" +
"function f(x,y) { return y(x); }\n";
testTypes(
fnDecl +
"/** @type {string} */" +
"var out;" +
"/** @type {string} */" +
"var result = f('hi', function(x){ out = x; return x; });");
testTypes(
fnDecl +
"/** @type {string} */" +
"var out;" +
"var result = f(0, function(x){ out = x; return x; });",
"assignment\n" +
"found : number\n" +
"required: string");
testTypes(
fnDecl +
"var out;" +
"/** @type {string} */" +
"var result = f(0, function(x){ out = x; return x; });",
"assignment\n" +
"found : number\n" +
"required: string");
}
public void testFilter0() throws Exception {
testTypes(
"/**\n" +
" * @param {T} arr\n" +
" * @return {T}\n" +
" * @template T\n" +
" */\n" +
"var filter = function(arr){};\n" +
"/** @type {!Array<string>} */" +
"var arr;\n" +
"/** @type {!Array<string>} */" +
"var result = filter(arr);");
}
public void testFilter1() throws Exception {
testTypes(
"/**\n" +
" * @param {!Array<T>} arr\n" +
" * @return {!Array<T>}\n" +
" * @template T\n" +
" */\n" +
"var filter = function(arr){};\n" +
"/** @type {!Array<string>} */" +
"var arr;\n" +
"/** @type {!Array<string>} */" +
"var result = filter(arr);");
}
public void testFilter2() throws Exception {
testTypes(
"/**\n" +
" * @param {!Array<T>} arr\n" +
" * @return {!Array<T>}\n" +
" * @template T\n" +
" */\n" +
"var filter = function(arr){};\n" +
"/** @type {!Array<string>} */" +
"var arr;\n" +
"/** @type {!Array<number>} */" +
"var result = filter(arr);",
"initializing variable\n" +
"found : Array<string>\n" +
"required: Array<number>");
}
public void testFilter3() throws Exception {
testTypes(
"/**\n" +
" * @param {Array<T>} arr\n" +
" * @return {Array<T>}\n" +
" * @template T\n" +
" */\n" +
"var filter = function(arr){};\n" +
"/** @type {Array<string>} */" +
"var arr;\n" +
"/** @type {Array<number>} */" +
"var result = filter(arr);",
"initializing variable\n" +
"found : (Array<string>|null)\n" +
"required: (Array<number>|null)");
}
public void testBackwardsInferenceGoogArrayFilter1() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @type {Array<string>} */" +
"var arr;\n" +
"/** @type {!Array<number>} */" +
"var result = goog.array.filter(" +
" arr," +
" function(item,index,src) {return false;});",
"initializing variable\n" +
"found : Array<string>\n" +
"required: Array<number>");
}
public void testBackwardsInferenceGoogArrayFilter2() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @type {number} */" +
"var out;" +
"/** @type {Array<string>} */" +
"var arr;\n" +
"var out4 = goog.array.filter(" +
" arr," +
" function(item,index,src) {out = item; return false});",
"assignment\n" +
"found : string\n" +
"required: number");
}
public void testBackwardsInferenceGoogArrayFilter3() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @type {string} */" +
"var out;" +
"/** @type {Array<string>} */ var arr;\n" +
"var result = goog.array.filter(" +
" arr," +
" function(item,index,src) {out = index;});",
"assignment\n" +
"found : number\n" +
"required: string");
}
public void testBackwardsInferenceGoogArrayFilter4() throws Exception {
testClosureTypes(
CLOSURE_DEFS +
"/** @type {string} */" +
"var out;" +
"/** @type {Array<string>} */ var arr;\n" +
"var out4 = goog.array.filter(" +
" arr," +
" function(item,index,srcArr) {out = srcArr;});",
"assignment\n" +
"found : (null|{length: number})\n" +
"required: string");
}
public void testCatchExpression1() throws Exception {
testTypes(
"function fn() {" +
" /** @type {number} */" +
" var out = 0;" +
" try {\n" +
" foo();\n" +
" } catch (/** @type {string} */ e) {\n" +
" out = e;" +
" }" +
"}\n",
"assignment\n" +
"found : string\n" +
"required: number");
}
public void testCatchExpression2() throws Exception {
testTypes(
"function fn() {" +
" /** @type {number} */" +
" var out = 0;" +
" /** @type {string} */" +
" var e;" +
" try {\n" +
" foo();\n" +
" } catch (e) {\n" +
" out = e;" +
" }" +
"}\n");
}
public void testTemplatized1() throws Exception {
testTypes(
"/** @type {!Array<string>} */" +
"var arr1 = [];\n" +
"/** @type {!Array<number>} */" +
"var arr2 = [];\n" +
"arr1 = arr2;",
"assignment\n" +
"found : Array<number>\n" +
"required: Array<string>");
}
public void testTemplatized2() throws Exception {
testTypes(
"/** @type {!Array<string>} */" +
"var arr1 = /** @type {!Array<number>} */([]);\n",
"initializing variable\n" +
"found : Array<number>\n" +
"required: Array<string>");
}
public void testTemplatized3() throws Exception {
testTypes(
"/** @type {Array<string>} */" +
"var arr1 = /** @type {!Array<number>} */([]);\n",
"initializing variable\n" +
"found : Array<number>\n" +
"required: (Array<string>|null)");
}
public void testTemplatized4() throws Exception {
testTypes(
"/** @type {Array<string>} */" +
"var arr1 = [];\n" +
"/** @type {Array<number>} */" +
"var arr2 = arr1;\n",
"initializing variable\n" +
"found : (Array<string>|null)\n" +
"required: (Array<number>|null)");
}
public void testTemplatized5() throws Exception {
testTypes(
"/**\n" +
" * @param {Object<T>} obj\n" +
" * @return {boolean|undefined}\n" +
" * @template T\n" +
" */\n" +
"var some = function(obj) {" +
" for (var key in obj) if (obj[key]) return true;" +
"};" +
"/** @return {!Array} */ function f() { return []; }" +
"/** @return {!Array<string>} */ function g() { return []; }" +
"some(f());\n" +
"some(g());\n");
}
public void testTemplatized6() throws Exception {
testTypes(
"/** @interface */ function I(){}\n" +
"/** @param {T} a\n" +
" * @return {T}\n" +
" * @template T\n" +
"*/\n" +
"I.prototype.method;\n" +
"" +
"/** @constructor \n" +
" * @implements {I}\n" +
" */ function C(){}\n" +
"/** @override*/ C.prototype.method = function(a) {}\n" +
"" +
"/** @type {null} */ var some = new C().method('str');",
"initializing variable\n" +
"found : string\n" +
"required: null");
}
public void testTemplatized7() throws Exception {
testTypes(
"/** @interface\n" +
" * @template Q\n " +
" */ function I(){}\n" +
"/** @param {T} a\n" +
" * @return {T|Q}\n" +
" * @template T\n" +
"*/\n" +
"I.prototype.method;\n" +
"/** @constructor \n" +
" * @implements {I<number>}\n" +
" */ function C(){}\n" +
"/** @override*/ C.prototype.method = function(a) {}\n" +
"/** @type {null} */ var some = new C().method('str');",
"initializing variable\n" +
"found : (number|string)\n" +
"required: null");
}
public void disable_testTemplatized8() {
// TODO(johnlenz): this should generate a warning but does not.
testTypes(
"/** @interface\n" +
" * @template Q\n " +
" */ function I(){}\n" +
"/** @param {T} a\n" +
" * @return {T|Q}\n" +
" * @template T\n" +
"*/\n" +
"I.prototype.method;\n" +
"/** @constructor \n" +
" * @implements {I<R>}\n" +
" * @template R\n " +
" */ function C(){}\n" +
"/** @override*/ C.prototype.method = function(a) {}\n" +
"/** @type {C<number>} var x = new C();" +
"/** @type {null} */ var some = x.method('str');",
"initializing variable\n" +
"found : (number|string)\n" +
"required: null");
}
public void testTemplatized9() throws Exception {
testTypes(
"/** @interface\n" +
" * @template Q\n " +
" */ function I(){}\n" +
"/** @param {T} a\n" +
" * @return {T|Q}\n" +
" * @template T\n" +
"*/\n" +
"I.prototype.method;\n" +
"/** @constructor \n" +
" * @param {R} a\n" +
" * @implements {I<R>}\n" +
" * @template R\n " +
" */ function C(a){}\n" +
"/** @override*/ C.prototype.method = function(a) {}\n" +
"/** @type {null} */ var some = new C(1).method('str');",
"initializing variable\n" +
"found : (number|string)\n" +
"required: null");
}
public void testTemplatized10() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template T\n" +
" */\n" +
"function Parent() {};\n" +
"\n" +
"/** @param {T} x */\n" +
"Parent.prototype.method = function(x) {};\n" +
"\n" +
"/**\n" +
" * @constructor\n" +
" * @extends {Parent<string>}\n" +
" */\n" +
"function Child() {};\n" +
"Child.prototype = new Parent();\n" +
"\n" +
"(new Child()).method(123); \n",
"actual parameter 1 of Parent.prototype.method does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testTemplatized11() throws Exception {
testTypes(
"/** \n" +
" * @template T\n" +
" * @constructor\n" +
" */\n" +
"function C() {}\n" +
"\n" +
"/**\n" +
" * @param {T|K} a\n" +
" * @return {T}\n" +
" * @template K\n" +
" */\n" +
"C.prototype.method = function (a) {};\n" +
"\n" +
// method returns "?"
"/** @type {void} */ var x = new C().method(1);");
}
public void testIssue1058() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template CLASS\n" +
" */\n" +
"var Class = function() {};\n" +
"\n" +
"/**\n" +
" * @param {function(CLASS):CLASS} a\n" +
" * @template T\n" +
" */\n" +
"Class.prototype.foo = function(a) {\n" +
" return 'string';\n" +
"};\n" +
"\n" +
"/** @param {number} a\n" +
" * @return {string} */\n" +
"var a = function(a) { return '' };\n" +
"\n" +
"new Class().foo(a);");
}
public void testDeterminacyIssue() throws Exception {
testTypes(
"(function() {\n" +
" /** @constructor */\n" +
" var ImageProxy = function() {};\n" +
" /** @constructor */\n" +
" var FeedReader = function() {};\n" +
" /** @type {ImageProxy} */\n" +
" FeedReader.x = new ImageProxy();\n" +
"})();");
}
public void testUnknownTypeReport() throws Exception {
compiler.getOptions().setWarningLevel(DiagnosticGroups.REPORT_UNKNOWN_TYPES,
CheckLevel.WARNING);
testTypes("function id(x) { return x; }",
"could not determine the type of this expression");
}
public void testUnknownForIn() throws Exception {
compiler.getOptions().setWarningLevel(DiagnosticGroups.REPORT_UNKNOWN_TYPES,
CheckLevel.WARNING);
testTypes("var x = {'a':1}; var y; \n for(\ny\n in x) {}");
}
public void testUnknownTypeDisabledByDefault() throws Exception {
testTypes("function id(x) { return x; }");
}
public void testTemplatizedTypeSubtypes2() throws Exception {
JSType arrayOfNumber = createTemplatizedType(
ARRAY_TYPE, NUMBER_TYPE);
JSType arrayOfString = createTemplatizedType(
ARRAY_TYPE, STRING_TYPE);
assertFalse(arrayOfString.isSubtype(createUnionType(arrayOfNumber, NULL_VOID)));
}
public void testNonexistentPropertyAccessOnStruct() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"var A = function() {};\n" +
"/** @param {A} a */\n" +
"function foo(a) {\n" +
" if (a.bar) { a.bar(); }\n" +
"}",
"Property bar never defined on A");
}
public void testNonexistentPropertyAccessOnStructOrObject() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"var A = function() {};\n" +
"/** @param {A|Object} a */\n" +
"function foo(a) {\n" +
" if (a.bar) { a.bar(); }\n" +
"}");
}
public void testNonexistentPropertyAccessOnExternStruct() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"var A = function() {};",
"/** @param {A} a */\n" +
"function foo(a) {\n" +
" if (a.bar) { a.bar(); }\n" +
"}",
"Property bar never defined on A", false);
}
public void testNonexistentPropertyAccessStructSubtype() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"var A = function() {};" +
"" +
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" * @extends {A}\n" +
" */\n" +
"var B = function() { this.bar = function(){}; };" +
"" +
"/** @param {A} a */\n" +
"function foo(a) {\n" +
" if (a.bar) { a.bar(); }\n" +
"}",
"Property bar never defined on A", false);
}
public void testNonexistentPropertyAccessStructInterfaceSubtype() throws Exception {
testTypes(LINE_JOINER.join(
"/**",
" * @interface",
" * @struct",
" */",
"var A = function() {};",
"",
"/**",
" * @interface",
" * @struct",
" * @extends {A}",
" */",
"var B = function() {};",
"/** @return {void} */ B.prototype.bar = function(){};",
"",
"/** @param {A} a */",
"function foo(a) {",
" if (a.bar) { a.bar(); }",
"}"),
"Property bar never defined on A", false);
}
public void testNonexistentPropertyAccessStructRecordSubtype() throws Exception {
testTypes(LINE_JOINER.join(
"/**",
" * @record",
" * @struct",
" */",
"var A = function() {};",
"",
"/**",
" * @record",
" * @struct",
" * @extends {A}",
" */",
"var B = function() {};",
"/** @return {void} */ B.prototype.bar = function(){};",
"",
"/** @param {A} a */",
"function foo(a) {",
" if (a.bar) { a.bar(); }",
"}"),
"Property bar never defined on A", false);
}
public void testNonexistentPropertyAccessStructSubtype2() throws Exception {
testTypes(
"/**\n" +
" * @constructor\n" +
" * @struct\n" +
" */\n" +
"function Foo() {\n" +
" this.x = 123;\n" +
"}\n" +
"var objlit = /** @struct */ { y: 234 };\n" +
"Foo.prototype = objlit;\n" +
"var n = objlit.x;\n",
"Property x never defined on Foo.prototype", false);
}
public void testIssue1024() throws Exception {
testTypes(
"/** @param {Object} a */\n" +
"function f(a) {\n" +
" a.prototype = '__proto'\n" +
"}\n" +
"/** @param {Object} b\n" +
" * @return {!Object}\n" +
" */\n" +
"function g(b) {\n" +
" return b.prototype\n" +
"}\n");
/* TODO(blickly): Make this warning go away.
* This is old behavior, but it doesn't make sense to warn about since
* both assignments are inferred.
*/
testTypes(
"/** @param {Object} a */\n" +
"function f(a) {\n" +
" a.prototype = {foo:3};\n" +
"}\n" +
"/** @param {Object} b\n" +
" */\n" +
"function g(b) {\n" +
" b.prototype = function(){};\n" +
"}\n",
"assignment to property prototype of Object\n" +
"found : {foo: number}\n" +
"required: function (): undefined");
}
public void testBug12722936() throws Exception {
// Verify we don't use a weaker type when a
// stronger type is known for a slot.
testTypes(
"/**\n" +
" * @constructor\n" +
" * @template T\n" +
" */\n" +
"function X() {}\n" +
"/** @constructor */ function C() {\n" +
" /** @type {!X<boolean>}*/\n" +
" this.a = new X();\n" +
" /** @type {null} */ var x = this.a;\n" +
"};\n" +
"\n",
"initializing variable\n" +
"found : X<boolean>\n" +
"required: null", false);
}
public void testModuleReferenceNotAllowed() throws Exception {
testTypes(
"/** @param {./Foo} z */ function f(z) {}",
"Bad type annotation. Unknown type ./Foo");
}
public void testCheckObjectKeysBadKey1() throws Exception {
testTypes("/** @type {!Object<!Object, number>} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey2() throws Exception {
testTypes("/** @type {!Object<function(), number>} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey3() throws Exception {
testTypes("/** @type {!Object<!Array<!Object>, number>} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey4() throws Exception {
testTypes("/** @type {!Object<*, number>} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey5() throws Exception {
testTypes("/** @type {(string|Object<Object, number>)} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey6() throws Exception {
testTypes("/** @type {!Object<number, !Object<Object, number>>} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey7() throws Exception {
testTypes(
"/** @constructor */\n" +
"var MyClass = function() {};\n" +
"/** @type {!Object<MyClass, number>} */\n" +
"var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey8() throws Exception {
testTypes(
"/** @enum{!Object} */\n" +
"var Enum = {};\n" +
"/** @type {!Object<Enum, number>} */\n" +
"var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey9() throws Exception {
testTypes("/** @type {function(!Object<!Object, number>)} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey10() throws Exception {
testTypes("/** @type {function(): !Object<!Object, number>} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysBadKey11() throws Exception {
testTypes(
"/** @constructor */\n" +
"function X() {}\n" +
"/** @constructor @extends {X} */\n" +
"function X2() {}\n" +
"/** @enum {!X} */\n" +
"var XE = {A:new X};\n" +
"/** @type {Object<(!XE|!X2), string>} */\n" +
"var Y = {};",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysVariousTags1() throws Exception {
testTypes("/** @type {!Object<!Object, number>} */ var k;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysVariousTags2() throws Exception {
testTypes("/** @param {!Object<!Object, number>} a */ var f = function(a) {};",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysVariousTags3() throws Exception {
testTypes("/** @return {!Object<!Object, number>} */ var f = function() {return {}};",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysVariousTags4() throws Exception {
testTypes("/** @typedef {!Object<!Object, number>} */ var MyType;",
TypeCheck.NON_STRINGIFIABLE_OBJECT_KEY);
}
public void testCheckObjectKeysGoodKey1() throws Exception {
testTypes("/** @type {!Object<number, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey2() throws Exception {
testTypes("/** @type {!Object<string, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey3() throws Exception {
testTypes("/** @type {!Object<boolean, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey4() throws Exception {
testTypes("/** @type {!Object<null, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey5() throws Exception {
testTypes("/** @type {!Object<undefined, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey6() throws Exception {
testTypes("/** @type {!Object<!Date, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey7() throws Exception {
testTypes("/** @type {!Object<!RegExp, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey8() throws Exception {
testTypes("/** @type {!Object<!Array, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey9() throws Exception {
testTypes("/** @type {!Object<!Array<number>, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey10() throws Exception {
testTypes("/** @type {!Object<?, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey11() throws Exception {
testTypes("/** @type {!Object<(string|number), number>} */ var k");
}
public void testCheckObjectKeysGoodKey12() throws Exception {
testTypes("/** @type {!Object<Object>} */ var k;");
}
public void testCheckObjectKeysGoodKey13() throws Exception {
testTypes(
"/** @interface */\n" +
"var MyInterface = function() {};\n" +
"/** @type {!Object<!MyInterface, number>} */\n" +
"var k;");
}
public void testCheckObjectKeysGoodKey14() throws Exception {
testTypes(
"/** @typedef {{a: number}} */ var MyRecord;\n" +
"/** @type {!Object<MyRecord, number>} */ var k;");
}
public void testCheckObjectKeysGoodKey15() throws Exception {
testTypes(
"/** @enum{number} */\n" +
"var Enum = {};\n" +
"/** @type {!Object<Enum, number>} */\n" +
"var k;");
}
public void testCheckObjectKeysClassWithToString() throws Exception {
testTypes(
"/** @constructor */\n" +
"var MyClass = function() {};\n" +
"/** @override*/\n" +
"MyClass.prototype.toString = function() { return ''; };\n" +
"/** @type {!Object<!MyClass, number>} */\n" +
"var k;");
}
public void testCheckObjectKeysClassInheritsToString() throws Exception {
testTypes(
"/** @constructor */\n" +
"var Parent = function() {};\n" +
"/** @override */\n" +
"Parent.prototype.toString = function() { return ''; };\n" +
"/** @constructor @extends {Parent} */\n" +
"var Child = function() {};\n" +
"/** @type {!Object<!Child, number>} */\n" +
"var k;");
}
public void testCheckObjectKeysForEnumUsingClassWithToString() throws Exception {
testTypes(
"/** @constructor */\n" +
"var MyClass = function() {};\n" +
"/** @override*/\n" +
"MyClass.prototype.toString = function() { return ''; };\n" +
"/** @enum{!MyClass} */\n" +
"var Enum = {};\n" +
"/** @type {!Object<Enum, number>} */\n" +
"var k;");
}
public void testCheckObjectKeysWithNamedType() throws Exception {
testTypes(
"/** @type {!Object<!PseudoId, number>} */\n" +
"var k;\n" +
"/** @typedef {number|string} */\n" +
"var PseudoId;");
}
public void testCheckObjectKeyRecursiveType() throws Exception {
testTypes(
"/** @typedef {!Object<string, !Predicate>} */ var Schema;\n" +
"/** @typedef {function(*): boolean|!Schema} */ var Predicate;\n" +
"/** @type {!Schema} */ var k;");
}
public void testDontOverrideNativeScalarTypes() throws Exception {
testTypes(
"string = 123;\n"
+ "var /** string */ s = 123;",
"initializing variable\n"
+ "found : number\n"
+ "required: string");
testTypes(
"var string = goog.require('goog.string');\n"
+ "var /** string */ s = 123;",
new String[] {
"Property require never defined on goog",
"initializing variable\n"
+ "found : number\n"
+ "required: string"
});
}
public void testTemplateMap1() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"function f() {\n"
+ " /** @type {Int8Array} */\n"
+ " var x = new Int8Array(10);\n"
+ " /** @type {IArrayLike<string>} */\n"
+ " var y;\n"
+ " y = x;\n"
+ "}",
"assignment\n"
+ "found : (Int8Array|null)\n"
+ "required: (IArrayLike<string>|null)");
}
public void testTemplateMap2() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"function f() {\n"
+ " /** @type {Int8Array} */\n"
+ " var x = new Int8Array(10);\n"
+ "\n"
+ " /** @type {IObject<number, string>} */\n"
+ " var z;\n"
+ " z = x;\n"
+ "}",
"assignment\n"
+ "found : (Int8Array|null)\n"
+ "required: (IObject<number,string>|null)");
}
public void testTemplateMap3() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"function f() {\n"
+ " var x = new Int8Array(10);\n"
+ "\n"
+ " /** @type {IArrayLike<string>} */\n"
+ " var y;\n"
+ " y = x;\n"
+ "}",
"assignment\n"
+ "found : Int8Array\n"
+ "required: (IArrayLike<string>|null)");
}
public void testTemplateMap4() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"function f() {\n"
+ " var x = new Int8Array(10);\n"
+ "\n"
+ " /** @type {IObject<number, string>} */\n"
+ " var z;\n"
+ " z = x;\n"
+ "}",
"assignment\n"
+ "found : Int8Array\n"
+ "required: (IObject<number,string>|null)");
}
public void testTemplateMap5() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"function f() {\n"
+ " var x = new Int8Array(10);\n"
+ " /** @type {IArrayLike<number>} */\n"
+ " var y;\n"
+ " y = x;\n"
+ "}");
}
public void testTemplateMap6() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"function f() {\n"
+ " var x = new Int8Array(10);\n"
+ " /** @type {IObject<number, number>} */\n"
+ " var z;\n"
+ " z = x;\n"
+ "}");
}
private static final String EXTERNS_WITH_IARRAYLIKE_DECLS =
"/**\n"
+ " * @constructor @implements IArrayLike<number>\n"
+ " */\n"
+ "function Int8Array(length, opt_byteOffset, opt_length) {}\n"
+ "/** @type {number} */\n"
+ "Int8Array.prototype.length;\n"
+ "/**\n"
+ "* @constructor\n"
+ "* @extends {Int8Array}\n"
+ "*/\n"
+ "function Int8Array2(len) {};\n"
+ "/**\n"
+ " * @interface\n"
+ " * @extends {IArrayLike<number>}\n"
+ " */\n"
+ "function IArrayLike2(){}\n"
+ "\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @implements {IArrayLike2}\n"
+ " */\n"
+ "function Int8Array3(len) {};\n"
+ "/** @type {number} */\n"
+ "Int8Array3.prototype.length;\n"
+ "/**\n" + " * @interface\n"
+ " * @extends {IArrayLike<VALUE3>}\n"
+ " * @template VALUE3\n"
+ " */\n"
+ "function IArrayLike3(){}\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @implements {IArrayLike3<number>}\n"
+ " */\n"
+ "function Int8Array4(length) {};\n"
+ "/** @type {number} */\n"
+ "Int8Array4.prototype.length;\n"
+ "/**\n"
+ " * @interface\n"
+ " * @extends {IArrayLike<VALUE2>}\n"
+ " * @template VALUE2\n"
+ " */\n"
+ "function IArrayLike4(){}\n"
+ "/**\n"
+ " * @interface\n"
+ " * @extends {IArrayLike4<boolean>}\n"
+ " */\n"
+ "function IArrayLike5(){}\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @implements {IArrayLike5}\n"
+ " */\n"
+ "function BooleanArray5(length) {};\n"
+ "/** @type {number} */\n"
+ "BooleanArray5.prototype.length;";
public void testArrayImplementsIArrayLike() throws Exception {
testTypes(
"/** @type {!Array<number>} */ var arr = [];\n"
+ "var /** null */ n = arr[0];\n",
"initializing variable\n"
+ "found : number\n"
+ "required: null");
}
public void testIArrayLike1() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"var arr = new Int8Array(7);\n"
+ "// no warning\n"
+ "arr[0] = 1;\n"
+ "arr[1] = 2;\n");
}
public void testIArrayLike2() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"var arr = new Int8Array(7);\n"
+ "// have warnings\n"
+ "arr[3] = false;\n",
"assignment\n"
+ "found : boolean\n"
+ "required: number");
}
public void testIArrayLike3() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"var arr = new Int8Array2(10);\n"
+ "// have warnings\n"
+ "arr[3] = false;\n",
"assignment\n"
+ "found : boolean\n"
+ "required: number");
}
public void testIArrayLike4() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"var arr = new Int8Array2(10);\n"
+ "// have warnings\n"
+ "arr[3] = false;\n",
"assignment\n"
+ "found : boolean\n"
+ "required: number");
}
public void testIArrayLike5() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"var arr = new Int8Array3(10);\n"
+ "// have warnings\n"
+ "arr[3] = false;\n",
"assignment\n"
+ "found : boolean\n"
+ "required: number");
}
public void testIArrayLike6() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"var arr = new Int8Array4(10);\n"
+ "// have warnings\n"
+ "arr[3] = false;\n",
"assignment\n"
+ "found : boolean\n"
+ "required: number");
}
public void testIArrayLike7() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
"var arr5 = new BooleanArray5(10);\n"
+ "arr5[2] = true;\n"
+ "arr5[3] = \"\";",
"assignment\n"
+ "found : string\n"
+ "required: boolean");
}
public void testIArrayLike8() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
LINE_JOINER.join(
"var arr2 = new Int8Array(10);",
"arr2[true] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : boolean",
"required: number"));
}
public void testIArrayLike9() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
LINE_JOINER.join(
"var arr2 = new Int8Array2(10);",
"arr2[true] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : boolean",
"required: number"));
}
public void testIArrayLike10() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
LINE_JOINER.join(
"var arr2 = new Int8Array3(10);",
"arr2[true] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : boolean",
"required: number"));
}
public void testIArrayLike11() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
LINE_JOINER.join(
"var arr2 = new Int8Array4(10);",
"arr2[true] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : boolean",
"required: number"));
}
public void testIArrayLike12() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
LINE_JOINER.join(
"var arr2 = new BooleanArray5(10);",
"arr2['prop'] = true;"),
LINE_JOINER.join(
"restricted index type",
"found : string",
"required: number"));
}
public void testIArrayLike13() throws Exception {
testTypesWithExtraExterns(EXTERNS_WITH_IARRAYLIKE_DECLS,
LINE_JOINER.join(
"var numOrStr = null ? 0 : 'prop';",
"var arr2 = new BooleanArray5(10);",
"arr2[numOrStr] = true;"),
LINE_JOINER.join(
"restricted index type",
"found : (number|string)",
"required: number"));
}
public void testIArrayLikeCovariant1() throws Exception {
testTypes(
LINE_JOINER.join(
"function f(/** !IArrayLike<(string|number)>*/ x){};",
"function g(/** !IArrayLike<number> */ arr) {",
" f(arr);",
"}"));
}
public void testIArrayLikeCovariant2() throws Exception {
testTypes(
LINE_JOINER.join(
"function f(/** !IArrayLike<(string|number)>*/ x){};",
"function g(/** !Array<number> */ arr) {",
" f(arr);",
"}"));
}
public void testIArrayLikeStructuralMatch1() throws Exception {
testTypes(
LINE_JOINER.join(
"function f(/** !IArrayLike */ x){};",
"/** @constructor */",
"function Foo() {}",
"/** @type {number} */ Foo.prototype.length",
"f(new Foo)"));
}
public void testIArrayLikeStructuralMatch2() throws Exception {
testTypes(
LINE_JOINER.join(
"function f(/** !IArrayLike */ x){};",
"/** @constructor */",
"function Foo() {",
" /** @type {number} */ this.length = 5;",
"}",
"f(new Foo)"));
}
public void testIArrayLikeStructuralMatch3() throws Exception {
testTypes(
LINE_JOINER.join(
"function f(/** !IArrayLike */ x){};",
"f({length: 5})"));
}
public void testIArrayLikeStructuralMatch4() throws Exception {
testTypes(
LINE_JOINER.join(
"function f(/** !IArrayLike */ x){};",
"/** @const */ var ns = {};",
"/** @type {number} */ ns.length",
"f(ns)"));
}
public void testIArrayLikeStructuralMatch5() throws Exception {
testTypes(
LINE_JOINER.join(
"function f(/** !IArrayLike */ x){};",
"var ns = function() {};",
"/** @type {number} */ ns.length",
"f(ns)"));
}
public void testIArrayLikeStructuralMatch6() throws Exception {
// Even though Foo's [] element type may not be string, we treat the lack
// of explicit type like ? and allow this.
testTypes(
LINE_JOINER.join(
"function f(/** !IArrayLike<string> */ x){};",
"/** @constructor */",
"function Foo() {}",
"/** @type {number} */ Foo.prototype.length",
"f(new Foo)"));
}
public void testTemplatizedStructuralMatch1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function WithPropT() {}",
"/** @type {T} */ WithPropT.prototype.prop;",
"function f(/** !WithPropT<number> */ x){}",
"/** @constructor */ function Foo() {}",
"/** @type {number} */ Foo.prototype.prop;",
"f(new Foo);"));
}
public void testTemplatizedStructuralMatch2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function WithPropT() {}",
"/** @type {T} */ WithPropT.prototype.prop",
"function f(/** !WithPropT<number> */ x){};",
"/** @constructor @template U */ function Foo() {}",
"/** @type {number} */ Foo.prototype.prop",
"f(new Foo)"));
}
public void testTemplatizedStructuralMatch3() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function WithPropT() {}",
"/** @type {T} */ WithPropT.prototype.prop",
"function f(/** !WithPropT<string> */ x){};",
"/** @constructor @template U */ function Foo() {}",
"/** @type {U} */ Foo.prototype.prop",
"f(new Foo)"));
}
public void testTemplatizedStructuralMismatch1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function WithPropT() {}",
"/** @type {T} */ WithPropT.prototype.prop",
"function f(/** !WithPropT<number> */ x){};",
"/** @constructor */ function Foo() {}",
"/** @type {string} */ Foo.prototype.prop = 'str'",
"f(new Foo)"),
LINE_JOINER.join(
"actual parameter 1 of f does not match formal parameter",
"found : Foo",
"required: WithPropT<number>"));
}
public void testTemplatizedStructuralMismatch2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function WithPropT() {}",
"/** @type {T} */ WithPropT.prototype.prop",
"function f(/** !WithPropT<number> */ x){};",
"/** @constructor @template U */ function Foo() {}",
"/** @type {string} */ Foo.prototype.prop = 'str'",
"f(new Foo)"),
LINE_JOINER.join(
"actual parameter 1 of f does not match formal parameter",
"found : Foo",
"required: WithPropT<number>"));
}
public void testTemplatizedStructuralMismatch3() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function WithPropT() {}",
"/** @type {T} */ WithPropT.prototype.prop",
"function f(/** !WithPropT<number> */ x){};",
"/**",
" * @constructor",
" * @template U",
" * @param {U} x",
" */",
"function Foo(x) {",
" /** @type {U} */ this.prop = x",
"}",
"f(new Foo('str'))"),
LINE_JOINER.join(
"actual parameter 1 of f does not match formal parameter",
"found : Foo<string>",
"required: WithPropT<number>"));
}
public void testTemplatizedStructuralMismatch4() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function WithProp() {}",
"/** @type {T} */ WithProp.prototype.prop;",
"/** @constructor */",
"function Foo() {",
" /** @type {number} */ this.prop = 4;",
"}",
"/**",
" * @template U",
" * @param {!WithProp<U>} x",
" * @param {U} y",
" */",
"function f(x, y){};",
"f(new Foo, 'str')"),
LINE_JOINER.join(
"actual parameter 1 of f does not match formal parameter",
"found : Foo",
"required: WithProp<string>"));
}
public void testTemplatizedStructuralMismatchNotFound() throws Exception {
// TODO(blickly): We would like to find the parameter mismatch here.
// Currently they match with type WithProp<?>, which is somewhat unsatisfying.
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function WithProp() {}",
"/** @type {T} */ WithProp.prototype.prop;",
"/** @constructor */",
"function Foo() {",
" /** @type {number} */ this.prop = 4;",
"}",
"/** @constructor */",
"function Bar() {",
" /** @type {string} */ this.prop = 'str';",
"}",
"/**",
" * @template U",
" * @param {!WithProp<U>} x",
" * @param {!WithProp<U>} y",
" */",
"function f(x, y){};",
"f(new Foo, new Bar)"));
}
private static final String EXTERNS_WITH_IOBJECT_DECLS = LINE_JOINER.join(
"/**",
" * @constructor",
" * @implements IObject<(string|number), number>",
" */",
"function Object2() {}",
"/**",
" * @constructor",
" * @implements IObject<number, number>",
" */",
"function Object3() {}");
public void testIObject1() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr2 = new Object2();",
"arr2[0] = 1;"));
}
public void testIObject2() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr2 = new Object2();",
"arr2['str'] = 1;"));
}
public void testIObject3() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr2 = new Object2();",
"arr2[true] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : boolean",
"required: (number|string)"));
}
public void testIObject4() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr2 = new Object2();",
"arr2[function (){}] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : function (): undefined",
"required: (number|string)"));
}
public void testIObject5() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr2 = new Object2();",
"arr2[{}] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : {}",
"required: (number|string)"));
}
public void testIObject6() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr2 = new Object2();",
"arr2[undefined] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : undefined",
"required: (number|string)"));
}
public void testIObject7() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr2 = new Object2();",
"arr2[null] = 1;"),
LINE_JOINER.join(
"restricted index type",
"found : null",
"required: (number|string)"));
}
public void testIObject8() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr = new Object2();",
"/** @type {boolean} */",
"var x = arr[3];"),
LINE_JOINER.join(
"initializing variable",
"found : number",
"required: boolean"));
}
public void testIObject9() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr = new Object2();",
"/** @type {(number|string)} */",
"var x = arr[3];"));
}
public void testIObject10() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr = new Object3();",
"/** @type {number} */",
"var x = arr[3];"));
}
public void testIObject11() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr = new Object3();",
"/** @type {boolean} */",
"var x = arr[3];"),
LINE_JOINER.join(
"initializing variable",
"found : number",
"required: boolean"));
}
public void testIObject12() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr = new Object3();",
"/** @type {string} */",
"var x = arr[3];"),
LINE_JOINER.join(
"initializing variable",
"found : number",
"required: string"));
}
public void testIObject13() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr = new Object3();",
"arr[3] = false;"),
LINE_JOINER.join(
"assignment",
"found : boolean",
"required: number"));
}
public void testIObject14() throws Exception {
testTypesWithExtraExterns(
EXTERNS_WITH_IOBJECT_DECLS,
LINE_JOINER.join(
"var arr = new Object3();",
"arr[3] = 'value';"),
LINE_JOINER.join(
"assignment",
"found : string",
"required: number"));
}
/**
* although C1 does not declare to extend Interface1,
* obj2 : C1 still structurally matches obj1 : Interface1
* because of the structural interface matching
* (Interface1 is declared with @record tag)
*/
public void testStructuralInterfaceMatching1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function Interface1() {}",
"/** @type {number} */",
"Interface1.prototype.length;",
"",
"/** @constructor */",
"function C1() {}",
"/** @type {number} */",
"C1.prototype.length;"),
LINE_JOINER.join(
"/** @type{Interface1} */",
"var obj1;",
"/** @type{C1} */",
"var obj2 = new C1();",
"obj1 = obj2;"));
}
public void testStructuralInterfaceMatching2() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function Interface1() {}",
"/** @type {number} */",
"Interface1.prototype.length;",
"",
"/** @constructor */",
"function C1() {}",
"/** @type {number} */",
"C1.prototype.length;"),
LINE_JOINER.join(
"/** @type{Interface1} */",
"var obj1;",
"var obj2 = new C1();",
"obj1 = obj2;"));
}
public void testStructuralInterfaceMatching3() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I1() {}",
"",
"/** @record */",
"function I2() {}"),
LINE_JOINER.join(
"/** @type {I1} */",
"var i1;",
"/** @type {I2} */",
"var i2;",
"i1 = i2;",
"i2 = i1;"));
}
public void testStructuralInterfaceMatching4_1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I1() {}",
"",
"/** @record */",
"function I2() {}"),
LINE_JOINER.join(
"/** @type {I1} */",
"var i1;",
"/** @type {I2} */",
"var i2;",
"i2 = i1;",
"i1 = i2;"));
}
public void testStructuralInterfaceMatching5_1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I1() {}",
"",
"/** @interface */",
"function I3() {}",
"/** @type {number} */",
"I3.prototype.length;"),
LINE_JOINER.join(
"/** @type {I1} */",
"var i1;",
"/** @type {I3} */",
"var i3;",
"i1 = i3;"));
}
public void testStructuralInterfaceMatching7_1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I1() {}",
"",
"/** @constructor */",
"function C1() {}",
"/** @type {number} */",
"C1.prototype.length;"),
LINE_JOINER.join(
"/** @type {I1} */",
"var i1;" +
"/** @type {C1} */",
"var c1;",
"i1 = c1; // no warning"));
}
public void testStructuralInterfaceMatching9() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C1() {}",
"/** @type {number} */",
"C1.prototype.length;",
"",
"/** @constructor */",
"function C2() {}",
"/** @type {number} */",
"C2.prototype.length;"),
LINE_JOINER.join(
"/** @type {C1} */",
"var c1;" +
"/** @type {C2} */",
"var c2;",
"c1 = c2;"),
LINE_JOINER.join(
"assignment",
"found : (C2|null)",
"required: (C1|null)"));
}
public void testStructuralInterfaceMatching11_1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function I3() {}",
"/** @type {number} */",
"I3.prototype.length;",
"",
"/** ",
" * @record",
" * @extends I3",
" */",
"function I4() {}",
"/** @type {boolean} */",
"I4.prototype.prop;",
"",
"/** @constructor */",
"function C4() {}",
"/** @type {number} */",
"C4.prototype.length;",
"/** @type {boolean} */",
"C4.prototype.prop;"),
LINE_JOINER.join(
"/** @type {I4} */",
"var i4;" +
"/** @type {C4} */",
"var c4;",
"i4 = c4;"));
}
public void testStructuralInterfaceMatching13() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/**",
" * @record",
" */",
" function I5() {}",
" /** @type {I5} */",
" I5.prototype.next;",
"",
" /**",
" * @interface",
" */",
" function C5() {}",
" /** @type {C5} */",
" C5.prototype.next;"),
LINE_JOINER.join(
"/** @type {I5} */",
"var i5;" +
"/** @type {C5} */",
"var c5;",
"i5 = c5;"));
}
public void testStructuralInterfaceMatching13_2() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/**",
" * @record",
" */",
" function I5() {}",
" /** @type {I5} */",
" I5.prototype.next;",
"",
" /**",
" * @record",
" */",
" function C5() {}",
" /** @type {C5} */",
" C5.prototype.next;"),
LINE_JOINER.join(
"/** @type {I5} */",
"var i5;" +
"/** @type {C5} */",
"var c5;",
"i5 = c5;"));
}
public void testStructuralInterfaceMatching13_3() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/**",
" * @interface",
" */",
" function I5() {}",
" /** @type {I5} */",
" I5.prototype.next;",
"",
" /**",
" * @record",
" */",
" function C5() {}",
" /** @type {C5} */",
" C5.prototype.next;"),
LINE_JOINER.join(
"/** @type {I5} */",
"var i5;" +
"/** @type {C5} */",
"var c5;",
"i5 = c5;"),
LINE_JOINER.join(
"assignment",
"found : (C5|null)",
"required: (I5|null)"));
}
public void testStructuralInterfaceMatching15() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I5() {}",
"/** @type {I5} */",
"I5.prototype.next;",
"",
"/** @constructor */",
"function C6() {}",
"/** @type {C6} */",
"C6.prototype.next;",
"",
"/** @constructor */",
"function C5() {}",
"/** @type {C6} */",
"C5.prototype.next;"),
LINE_JOINER.join(
"/** @type {I5} */",
"var i5;" +
"/** @type {C5} */",
"var c5;",
"i5 = c5;"));
}
/**
* a very long structural chain, all property types from I5 and C5
* are structurally the same, I5 is declared as @record
* so structural interface matching will be performed
*/
private static final String EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD =
LINE_JOINER.join(
"/** @record */",
"function I5() {}",
"/** @type {I5} */",
"I5.prototype.next;",
"",
"/** @constructor */",
"function C6() {}",
"/** @type {C6} */",
"C6.prototype.next;",
"",
"/** @constructor */",
"function C6_1() {}",
"/** @type {C6} */",
"C6_1.prototype.next;",
"",
"/** @constructor */",
"function C6_2() {}",
"/** @type {C6_1} */",
"C6_2.prototype.next;",
"",
"/** @constructor */",
"function C6_3() {}",
"/** @type {C6_2} */",
"C6_3.prototype.next;",
"",
"/** @constructor */",
"function C6_4() {}",
"/** @type {C6_3} */",
"C6_4.prototype.next;",
"",
"/** @constructor */",
"function C6_5() {}",
"/** @type {C6_4} */",
"C6_5.prototype.next;",
"",
"/** @constructor */",
"function C5() {}",
"/** @type {C6_5} */",
"C5.prototype.next;");
public void testStructuralInterfaceMatching16_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
LINE_JOINER.join(
"/** @type {I5} */",
"var i5;" +
"/** @type {C5} */",
"var c5;",
"i5 = c5;"));
}
public void testStructuralInterfaceMatching17_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
LINE_JOINER.join(
"/** @type {C5} */",
"var c5;",
"/**",
" * @param {I5} i5",
" */",
"function f(i5) {}",
"",
"f(c5);"));
}
public void testStructuralInterfaceMatching18_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
LINE_JOINER.join(
"/** @type {I5} */",
"var i5;" +
"/** @type {C5} */",
"var c5;",
"i5.next = c5;"));
}
/**
* a very long non-structural chain, there is a slight difference between
* the property type structural of I5 and that of C5:
* I5.next.next.next.next.next has type I5
* while
* C5.next.next.next.next.next has type number
*/
private static final String EXTERNS_FOR_LONG_NONMATCHING_CHAIN =
LINE_JOINER.join(
"/** @record */",
"function I5() {}",
"/** @type {I5} */",
"I5.prototype.next;",
"",
"/** @constructor */",
"function C6() {}",
"/** @type {number} */",
"C6.prototype.next;",
"",
"/** @constructor */",
"function C6_1() {}",
"/** @type {C6} */",
"C6_1.prototype.next;",
"",
"/** @constructor */",
"function C6_2() {}",
"/** @type {C6_1} */",
"C6_2.prototype.next;",
"",
"/** @constructor */",
"function C6_3() {}",
"/** @type {C6_2} */",
"C6_3.prototype.next;",
"",
"/** @constructor */",
"function C6_4() {}",
"/** @type {C6_3} */",
"C6_4.prototype.next;",
"",
"/** @constructor */",
"function C6_5() {}",
"/** @type {C6_4} */",
"C6_5.prototype.next;",
"",
"/** @interface */",
"function C5() {}",
"/** @type {C6_5} */",
"C5.prototype.next;");
public void testStructuralInterfaceMatching19() throws Exception {
testTypesWithExtraExterns(
// the type structure of I5 and C5 are different
EXTERNS_FOR_LONG_NONMATCHING_CHAIN,
LINE_JOINER.join(
"/** @type {I5} */",
"var i5;",
"/** @type {C5} */",
"var c5;",
"i5 = c5;"),
LINE_JOINER.join(
"assignment",
"found : (C5|null)",
"required: (I5|null)"));
}
public void testStructuralInterfaceMatching20() throws Exception {
testTypesWithExtraExterns(
// the type structure of I5 and C5 are different
EXTERNS_FOR_LONG_NONMATCHING_CHAIN,
LINE_JOINER.join(
"/** @type {C5} */",
"var c5;",
"/**",
" * @param {I5} i5",
" */",
"function f(i5) {}",
"",
"f(c5);"),
LINE_JOINER.join(
"actual parameter 1 of f does not match formal parameter",
"found : (C5|null)",
"required: (I5|null)"));
}
public void testStructuralInterfaceMatching21() throws Exception {
testTypesWithExtraExterns(
// the type structure of I5 and C5 are different
EXTERNS_FOR_LONG_NONMATCHING_CHAIN,
LINE_JOINER.join(
"/** @type {I5} */",
"var i5;",
"/** @type {C5} */",
"var c5;",
"i5.next = c5;"),
LINE_JOINER.join(
"assignment to property next of I5",
"found : (C5|null)",
"required: (I5|null)"));
}
/**
* structural interface matching will also be able to
* structurally match ordinary function types
* check if the return types of the ordinary function types match
* (should match, since declared with @record)
*/
public void testStructuralInterfaceMatching22_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* structural interface matching will also be able to
* structurally match ordinary function types
* check if the return types of the ordinary function types match
* (should not match)
*/
public void testStructuralInterfaceMatching23() throws Exception {
testTypesWithExtraExterns(
// the type structure of I5 and C5 are different
LINE_JOINER.join(
EXTERNS_FOR_LONG_NONMATCHING_CHAIN,
"/** @record */",
"function I7() {}",
"/** @type{function(): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"),
LINE_JOINER.join(
"assignment",
"found : (C7|null)",
"required: (I7|null)"));
}
/**
* structural interface matching will also be able to
* structurally match ordinary function types
* check if the parameter types of the ordinary function types match
* (should match, since declared with @record)
*/
public void testStructuralInterfaceMatching24_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(C5): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(I5): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* structural interface matching will also be able to
* structurally match ordinary function types
* check if the parameter types of the ordinary function types match
* (should match, since declared with @record)
*/
public void testStructuralInterfaceMatching26_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(C5, C5, I5): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(I5, C5): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* structural interface matching will also be able to
* structurally match ordinary function types
* check if the parameter types of the ordinary function types match
* (should match)
*/
public void testStructuralInterfaceMatching29_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* the "this" of I5 and C5 are covariants, so should match
*/
public void testStructuralInterfaceMatching30_1_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(this:I5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:C5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* the "this" of I5 and C5 are covariants, so should match
*/
public void testStructuralInterfaceMatching30_2_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* the "this" of I5 and C5 are covariants, so should match
*/
public void testStructuralInterfaceMatching30_3_1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */ function I5() {}",
"/** @constructor @implements {I5} */ function C5() {}",
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* I7 is declared with @record tag, so it will match
*/
public void testStructuralInterfaceMatching30_3_2() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */ function I5() {}",
"/** @constructor @implements {I5} */ function C5() {}",
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* Although I7 is declared with @record tag,
* note that I5 is declared with @interface and C5 does not
* extend I5, so it will not match
*/
public void testStructuralInterfaceMatching30_3_3() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */ function I5() {}",
"/** @constructor */ function C5() {}",
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"),
LINE_JOINER.join(
"assignment",
"found : (C7|null)",
"required: (I7|null)"));
}
public void testStructuralInterfaceMatching30_3_4() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
"/** @record */ function I5() {}",
"/** @constructor */ function C5() {}",
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* the "this" of I5 and C5 are covariants, so should match
*/
public void testStructuralInterfaceMatching30_4_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
"/** @record */ function I5() {}",
"/** @constructor @implements {I5} */ function C5() {}",
"/** @record */",
"function I7() {}",
"/** @type{function(this:I5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:C5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* although I7 is declared with @record tag
* I5 is declared with @interface tag, so no structural interface matching
*/
public void testStructuralInterfaceMatching30_4_2() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */ function I5() {}",
"/** @constructor */ function C5() {}",
"/** @record */",
"function I7() {}",
"/** @type{function(this:I5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:C5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"),
LINE_JOINER.join(
"assignment",
"found : (C7|null)",
"required: (I7|null)"));
}
/**
* structural interface matching will also be able to
* structurally match ordinary function types
* check if the this types of the ordinary function types match
* (should match)
*/
public void testStructuralInterfaceMatching31_1() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"));
}
/**
* test structural interface matching for record types
*/
public void testStructuralInterfaceMatching32_2() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {{prop: I7, prop2: C7}}*/",
"var r1;",
"/** @type {{prop: C7, prop2: C7}} */",
"var r2;",
"r1 = r2;"));
}
/**
* test structural interface matching for record types
*/
public void testStructuralInterfaceMatching33_3() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {{prop: I7, prop2: C7}}*/",
"var r1;",
"/** @type {{prop: C7, prop2: C7, prop3: C7}} */",
"var r2;",
"r1 = r2;"));
}
/**
* test structural interface matching for a combination of
* ordinary function types and record types
*/
public void testStructuralInterfaceMatching36_2() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {{fun: function(C7):I7, prop: {prop: I7}}} */",
" var com1;",
"/** @type {{fun: function(I7):C7, prop: {prop: C7}}} */",
"var com2;",
"",
"com1 = com2;"));
}
/**
* test structural interface matching for a combination of
* ordinary function types and record types
*/
public void testStructuralInterfaceMatching36_3() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {{fun: function(C7):I7, prop: {prop: I7}}} */",
" var com1;",
"/** @type {{fun: function(I7):C7, prop: {prop: C7}}} */",
"var com2;",
"",
"com1 = com2;"));
}
/**
* test structural interface matching for a combination of
* ordinary function types and record types
* here C7 does not structurally match I7
*/
public void testStructuralInterfaceMatching37() throws Exception {
testTypesWithExtraExterns(
// the type structure of I5 and C5 are different
LINE_JOINER.join(
EXTERNS_FOR_LONG_NONMATCHING_CHAIN,
"/** @record */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {{fun: function(C7):I7, prop: {prop: I7}}} */",
"var com1;",
"/** @type {{fun: function(I7):C7, prop: {prop: C7}}} */",
"var com2;",
"",
"com1 = com2;"),
LINE_JOINER.join(
"assignment",
"found : {fun: function ((I7|null)): (C7|null), prop: {prop: (C7|null)}}",
"required: {fun: function ((C7|null)): (I7|null), prop: {prop: (I7|null)}}"));
}
/**
* test structural interface matching for object literals
*/
public void testStructuralInterfaceMatching39() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I2() {}",
"/** @type {number} */",
"I2.prototype.length;"),
LINE_JOINER.join(
"/** @type {I2} */",
"var o1 = {length : 'test'};"),
LINE_JOINER.join(
"initializing variable",
"found : {length: string}",
"required: (I2|null)"));
}
/**
* test structural interface matching for object literals
*/
public void testStructuralInterfaceMatching40() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I2() {}",
"/** @type {number} */",
"I2.prototype.length;"),
LINE_JOINER.join(
"/** @type {I2} */",
"var o1 = {length : 123};"));
}
/**
* test structural interface matching for object literals
*/
public void testStructuralInterfaceMatching40_1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I2() {}",
"/** @type {number} */",
"I2.prototype.length;"),
LINE_JOINER.join(
"/** @type {I2} */",
"var o1 = {length : 123};"));
}
/**
* test structural interface matching for object literals
*/
public void testStructuralInterfaceMatching41() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I2() {}",
"/** @type {number} */",
"I2.prototype.length;"),
LINE_JOINER.join(
"/** @type {I2} */",
"var o1 = {length : 123};",
"/** @type {I2} */",
"var i;",
"i = o1;"));
}
/**
* test structural interface matching for object literals
*/
public void testStructuralInterfaceMatching41_1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I2() {}",
"/** @type {number} */",
"I2.prototype.length;"),
LINE_JOINER.join(
"/** @type {I2} */",
"var o1 = {length : 123};",
"/** @type {I2} */",
"var i;",
"i = o1;"));
}
/**
* test structural interface matching for object literals
*/
public void testStructuralInterfaceMatching42() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I2() {}",
"/** @type {number} */",
"I2.prototype.length;"),
LINE_JOINER.join(
"/** @type {{length: number}} */",
"var o1 = {length : 123};",
"/** @type {I2} */",
"var i;",
"i = o1;"));
}
/**
* test structural interface matching for object literals
*/
public void testStructuralInterfaceMatching43() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I2() {}",
"/** @type {number} */",
"I2.prototype.length;"),
LINE_JOINER.join(
"var o1 = {length : 123};",
"/** @type {I2} */",
"var i;",
"i = o1;"));
}
public void testStructuralInterfaceMatching44() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */ function I() {}",
"/** @type {!Function} */ I.prototype.removeEventListener;",
"/** @type {!Function} */ I.prototype.addEventListener;",
"/** @constructor */ function C() {}",
"/** @type {!Function} */ C.prototype.addEventListener;"),
LINE_JOINER.join(
"/** @param {C|I} x */",
"function f(x) { x.addEventListener(); }",
"f(new C());"));
}
/**
* Currently, the structural interface matching does not support structural
* matching for template types
* Using @template @interfaces requires @implements them explicitly.
*/
public void testStructuralInterfaceMatching45() throws Exception {
testTypes(
LINE_JOINER.join(
"/**",
" * @record",
" * @template X",
" */",
"function I() {}",
"/** @constructor */",
"function C() {}",
"var /** !I */ i = new C;"));
}
public void testStructuralInterfaceMatching46() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @interface */",
"function I2() {}",
"/**",
" * @interface",
" * @extends {I2}",
" */",
"function I3() {}",
"/**",
" * @record",
" * @extends {I3}",
" */",
"function I4() {}",
"/** @type {I4} */",
"var i4;",
"/** @type {I2} */",
"var i2;",
"i4 = i2;"));
}
public void testStructuralInterfaceMatching47() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function I2() {}",
"/**",
" * @interface",
" * @extends {I2}",
" */",
"function I3() {}",
"/**",
" * @record",
" * @extends {I3}",
" */",
"function I4() {}"),
LINE_JOINER.join(
"/** @type {I4} */",
"var i4;",
"/** @type {I2} */",
"var i2;",
"i4 = i2;"));
}
public void testStructuralInterfaceMatching48() throws Exception {
testTypesWithExtraExterns(
"",
LINE_JOINER.join(
"/** @interface */",
"function I2() {}",
"/**",
" * @record",
" * @extends {I2}",
" */",
"function I3() {}",
"/** @type {I3} */",
"var i3;",
"/** @type {I2} */",
"var i2;",
"i3 = i2;"));
}
public void testStructuralInterfaceMatching49() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function I2() {}",
"/**",
" * @record",
" * @extends {I2}",
" */",
"function I3() {}"),
LINE_JOINER.join(
"/** @type {I3} */",
"var i3;",
"/** @type {I2} */",
"var i2;",
"i3 = i2;"));
}
public void testStructuralInterfaceMatching49_2() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @record */",
"function I2() {}",
"/**",
" * @record",
" * @extends {I2}",
" */",
"function I3() {}"),
LINE_JOINER.join(
"/** @type {I3} */",
"var i3;",
"/** @type {I2} */",
"var i2;",
"i3 = i2;"));
}
public void testStructuralInterfaceMatching50() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function I2() {}",
"/**",
" * @record",
" * @extends {I2}",
" */",
"function I3() {}"),
LINE_JOINER.join(
"/** @type {I3} */",
"var i3;",
"/** @type {{length : number}} */",
"var r = {length: 123};",
"i3 = r;"));
}
public void testStructuralInterfaceMatching1_1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function Interface1() {}",
"/** @type {number} */",
"Interface1.prototype.length;",
"",
"/** @constructor */",
"function C1() {}",
"/** @type {number} */",
"C1.prototype.length;"),
LINE_JOINER.join(
"/** @type{Interface1} */",
"var obj1;",
"/** @type{C1} */",
"var obj2 = new C1();",
"obj1 = obj2;"),
LINE_JOINER.join(
"assignment",
"found : (C1|null)",
"required: (Interface1|null)"));
}
/**
* structural interface matching will also be able to
* structurally match ordinary function types
* check if the return types of the ordinary function types match
* (should not match, since I7 is declared with @interface)
*/
public void testStructuralInterfaceMatching22_2() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
EXTERNS_FOR_LONG_MATCHING_CHAIN_RECORD,
"/** @interface */",
"function I7() {}",
"/** @type{function(): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"),
LINE_JOINER.join(
"assignment",
"found : (C7|null)",
"required: (I7|null)"));
}
/**
* declared with @interface, no structural interface matching
*/
public void testStructuralInterfaceMatching30_3() throws Exception {
testTypesWithExtraExterns(
// I5 and C5 shares the same type structure
LINE_JOINER.join(
"/** @interface */ function I5() {}",
"/** @constructor @implements {I5} */ function C5() {}",
"/** @interface */",
"function I7() {}",
"/** @type{function(this:C5, C5, C5, I5=): I5} */",
"I7.prototype.getElement = function(){};",
"",
"/** @constructor */",
"function C7() {}",
"/** @type{function(this:I5, I5, C5=, I5=): C5} */",
"C7.prototype.getElement = function(){};"),
LINE_JOINER.join(
"/** @type {I7} */",
"var i7;",
"/** @type {C7} */",
"var c7;",
"",
"i7 = c7;"),
LINE_JOINER.join(
"assignment",
"found : (C7|null)",
"required: (I7|null)"));
}
public void testRecordWithOptionalProperty() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor */ function Foo() {};",
"Foo.prototype.str = 'foo';",
"",
"var /** {str: string, opt_num: (undefined|number)} */ x = new Foo;"));
}
public void testRecordWithUnknownProperty() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor */ function Foo() {};",
"Foo.prototype.str = 'foo';",
"",
"var /** {str: string, unknown: ?} */ x = new Foo;"),
LINE_JOINER.join(
"initializing variable",
"found : Foo",
"required: {str: string, unknown: ?}"));
}
public void testRecordWithOptionalUnknownProperty() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor */ function Foo() {};",
"Foo.prototype.str = 'foo';",
"",
"var /** {str: string, opt_unknown: (?|undefined)} */ x = new Foo;"));
}
public void testRecordWithTopProperty() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor */ function Foo() {};",
"Foo.prototype.str = 'foo';",
"",
"var /** {str: string, top: *} */ x = new Foo;"),
LINE_JOINER.join(
"initializing variable",
"found : Foo",
"required: {str: string, top: *}"));
}
public void testStructuralInterfaceWithOptionalProperty() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function Rec() {}",
"/** @type {string} */ Rec.prototype.str;",
"/** @type {(number|undefined)} */ Rec.prototype.opt_num;",
"",
"/** @constructor */ function Foo() {}",
"Foo.prototype.str = 'foo';",
"",
"var /** !Rec */ x = new Foo;"));
}
public void testStructuralInterfaceWithUnknownProperty() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function Rec() {}",
"/** @type {string} */ Rec.prototype.str;",
"/** @type {?} */ Rec.prototype.unknown;",
"",
"/** @constructor */ function Foo() {}",
"Foo.prototype.str = 'foo';",
"",
"var /** !Rec */ x = new Foo;"),
LINE_JOINER.join(
"initializing variable",
"found : Foo",
"required: Rec"));
}
public void testStructuralInterfaceWithOptionalUnknownProperty() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function Rec() {}",
"/** @type {string} */ Rec.prototype.str;",
"/** @type {?|undefined} */ Rec.prototype.opt_unknown;",
"",
"/** @constructor */ function Foo() {}",
"Foo.prototype.str = 'foo';",
"",
"var /** !Rec */ x = new Foo;"));
}
public void testOptionalUnknownIsAssignableToUnknown() throws Exception {
testTypes(
LINE_JOINER.join(
"function f(/** (undefined|?) */ opt_unknown) {",
" var /** ? */ unknown = opt_unknown;",
"}"));
}
public void testStructuralInterfaceWithTopProperty() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function Rec() {}",
"/** @type {string} */ Rec.prototype.str;",
"/** @type {*} */ Rec.prototype.top;",
"",
"/** @constructor */ function Foo() {}",
"Foo.prototype.str = 'foo';",
"",
"var /** !Rec */ x = new Foo;"),
LINE_JOINER.join(
"initializing variable",
"found : Foo",
"required: Rec"));
}
public void testStructuralInterfaceCycleDoesntCrash() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function Foo() {};",
"/** @return {MutableFoo} */ Foo.prototype.toMutable;",
"/** @record */ function MutableFoo() {};",
"/** @param {Foo} from */ MutableFoo.prototype.copyFrom;",
"",
"/** @record */ function Bar() {};",
"/** @return {MutableBar} */ Bar.prototype.toMutable;",
"/** @record */ function MutableBar() {};",
"/** @param {Bar} from */ MutableBar.prototype.copyFrom;",
"",
"/** @constructor @implements {MutableBar} */ function MutableBarImpl() {};",
"/** @override */ MutableBarImpl.prototype.copyFrom = function(from) {};",
"/** @constructor @implements {MutableFoo} */ function MutableFooImpl() {};",
"/** @override */ MutableFooImpl.prototype.copyFrom = function(from) {};"));
}
public void testStructuralInterfacesMatchOwnProperties1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function WithProp() {}",
"/** @type {number} */ WithProp.prototype.prop;",
"",
"/** @constructor */",
"function Foo() {",
" /** @type {number} */ this.prop = 5;",
"}",
"var /** !WithProp */ wp = new Foo;"));
}
public void testStructuralInterfacesMatchOwnProperties2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function WithProp() {}",
"/** @type {number} */ WithProp.prototype.prop;",
"",
"/** @constructor */",
"function Foo() {",
" /** @type {number} */ this.oops = 5;",
"}",
"var /** !WithProp */ wp = new Foo;"),
LINE_JOINER.join(
"initializing variable",
"found : Foo",
"required: WithProp"));
}
public void testStructuralInterfacesMatchOwnProperties3() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function WithProp() {}",
"/** @type {number} */ WithProp.prototype.prop;",
"",
"/** @constructor */",
"function Foo() {",
" /** @type {string} */ this.prop = 'str';",
"}",
"var /** !WithProp */ wp = new Foo;"),
LINE_JOINER.join(
"initializing variable",
"found : Foo",
"required: WithProp"));
}
public void testStructuralInterfacesMatchFunctionNamespace1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function WithProp() {}",
"/** @type {number} */ WithProp.prototype.prop;",
"",
"var ns = function() {};",
"/** @type {number} */ ns.prop;",
"var /** !WithProp */ wp = ns;"));
}
public void testStructuralInterfacesMatchFunctionNamespace2() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function WithProp() {}",
"/** @type {number} */ WithProp.prototype.prop;",
"",
"var ns = function() {};",
"/** @type {number} */ ns.oops;",
"var /** !WithProp */ wp = ns;"),
LINE_JOINER.join(
"initializing variable",
"found : function (): undefined",
"required: WithProp"));
}
public void testStructuralInterfacesMatchFunctionNamespace3() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */ function WithProp() {}",
"/** @type {number} */ WithProp.prototype.prop;",
"",
"var ns = function() {};",
"/** @type {string} */ ns.prop;",
"var /** !WithProp */ wp = ns;"),
LINE_JOINER.join(
"initializing variable",
"found : function (): undefined",
"required: WithProp"));
}
public void testRecursiveTemplatizedStructuralInterface() throws Exception {
testTypes(
LINE_JOINER.join(
"/**",
" * @record",
" * @template T",
" */",
"var Rec = function() { };",
"/** @type {!Rec<T>} */",
"Rec.prototype.p;",
"",
"/**",
" * @constructor @implements {Rec<T>}",
" * @template T",
" */",
"var Foo = function() {};",
"/** @override */",
"Foo.prototype.p = new Foo;"));
}
public void testCovarianceForRecordType1() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor ",
" * @extends {C} ",
" */",
"function C2() {}"),
LINE_JOINER.join(
"/** @type {{prop: C}} */",
"var r1;",
"/** @type {{prop: C2}} */",
"var r2;",
"r1 = r2;"));
}
public void testCovarianceForRecordType2() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor ",
" * @extends {C} ",
" */",
"function C2() {}"),
LINE_JOINER.join(
"/** @type {{prop: C, prop2: C}} */",
"var r1;",
"/** @type {{prop: C2, prop2: C}} */",
"var r2;",
"r1 = r2;"));
}
public void testCovarianceForRecordType3() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor @extends {C} */",
"function C2() {}"),
LINE_JOINER.join(
"/** @type {{prop: C}} */",
"var r1;",
"/** @type {{prop: C2, prop2: C}} */",
"var r2;",
"r1 = r2;"));
}
public void testCovarianceForRecordType4() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor @extends {C} */",
"function C2() {}"),
LINE_JOINER.join(
"/** @type {{prop: C, prop2: C}} */",
"var r1;",
"/** @type {{prop: C2}} */",
"var r2;",
"r1 = r2;"),
LINE_JOINER.join(
"assignment",
"found : {prop: (C2|null)}",
"required: {prop: (C|null), prop2: (C|null)}"));
}
public void testCovarianceForRecordType5() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor */",
"function C2() {}"),
LINE_JOINER.join(
"/** @type {{prop: C}} */",
"var r1;",
"/** @type {{prop: C2}} */",
"var r2;",
"r1 = r2;"),
LINE_JOINER.join(
"assignment",
"found : {prop: (C2|null)}",
"required: {prop: (C|null)}"));
}
public void testCovarianceForRecordType6() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor @extends {C} */",
"function C2() {}"),
LINE_JOINER.join(
"/** @type {{prop: C2}} */",
"var r1;",
"/** @type {{prop: C}} */",
"var r2;",
"r1 = r2;"),
LINE_JOINER.join(
"assignment",
"found : {prop: (C|null)}",
"required: {prop: (C2|null)}"));
}
public void testCovarianceForRecordType7() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor @extends {C} */",
"function C2() {}"),
LINE_JOINER.join(
"/** @type {{prop: C2, prop2: C2}} */",
"var r1;",
"/** @type {{prop: C2, prop2: C}} */",
"var r2;",
"r1 = r2;"),
LINE_JOINER.join(
"assignment",
"found : {prop: (C2|null), prop2: (C|null)}",
"required: {prop: (C2|null), prop2: (C2|null)}"));
}
public void testCovarianceForRecordType8() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function Foo(){}",
"/** @type {number} */",
"Foo.prototype.x = 5",
"/** @type {string} */",
"Foo.prototype.y = 'str'"),
LINE_JOINER.join(
"/** @type {{x: number, y: string}} */",
"var r1 = {x: 1, y: 'value'};",
"",
"/** @type {!Foo} */",
"var f = new Foo();",
"r1 = f;"));
}
public void testCovarianceForRecordType9() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function Foo(){}",
"/** @type {number} */",
"Foo.prototype.x1 = 5",
"/** @type {string} */",
"Foo.prototype.y = 'str'"),
LINE_JOINER.join(
"/** @type {{x: number, y: string}} */",
"var r1 = {x: 1, y: 'value'};",
"",
"/** @type {!Foo} */",
"var f = new Foo();",
"f = r1;"),
LINE_JOINER.join(
"assignment",
"found : {x: number, y: string}",
"required: Foo"));
}
public void testCovarianceForRecordType10() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function Foo() {}",
"/** @type {{x: !Foo}} */",
"Foo.prototype.x = {x: new Foo()};"),
LINE_JOINER.join(
"/** @type {!Foo} */",
"var o = new Foo();",
"",
"/** @type {{x: !Foo}} */",
"var r = {x : new Foo()};",
"r = o;"),
LINE_JOINER.join(
"assignment",
"found : Foo",
"required: {x: Foo}"));
}
public void testCovarianceForRecordType11() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function Foo() {}",
"/** @constructor @implements {Foo} */",
"function Bar1() {}",
"/** @return {number} */",
"Bar1.prototype.y = function (){return 1;};",
"/** @constructor @implements {Foo} */",
"function Bar() {}",
"/** @return {string} */",
"Bar.prototype.y = function (){return 'test';};"),
LINE_JOINER.join(
"function fun(/** Foo */f) {",
" f.y();",
"}",
"fun(new Bar1())",
"fun(new Bar());"));
}
public void testCovarianceForRecordType12() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function Foo() {}",
"/** @constructor @implements {Foo} */",
"function Bar1() {}",
"/** @constructor @implements {Foo} */",
"function Bar() {}",
"/** @return {undefined} */",
"Bar.prototype.y = function (){};"),
LINE_JOINER.join(
"/** @type{Foo} */",
"var f = new Bar1();",
"f.y();"));
}
public void testCovarianceForRecordType13() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function I() {}",
"/** @constructor @implements {I} */",
"function C() {}",
"/** @return {undefined} */",
"C.prototype.y = function (){};"),
LINE_JOINER.join(
"/** @type{{x: {obj: I}}} */",
"var ri;",
"ri.x.obj.y();"));
}
public void testCovarianceForRecordType14() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function I() {}",
"/** @constructor */",
"function C() {}",
"/** @return {undefined} */",
"C.prototype.y = function (){};"),
LINE_JOINER.join(
"/** @type{({x: {obj: I}}|{x: {obj: C}})} */",
"var ri;",
"ri.x.obj.y();"));
}
public void testCovarianceForRecordType15() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @return {undefined} */",
"C.prototype.y1 = function (){};",
"/** @constructor */",
"function C1() {}",
"/** @return {undefined} */",
"C1.prototype.y = function (){};"),
LINE_JOINER.join(
"/** @type{({x: {obj: C}}|{x: {obj: C1}})} */",
"var ri;",
"ri.x.obj.y1();",
"ri.x.obj.y();"));
}
public void testCovarianceForRecordType16() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @return {number} */",
"C.prototype.y = function (){return 1;};",
"/** @constructor */",
"function C1() {}",
"/** @return {string} */",
"C1.prototype.y = function (){return 'test';};"),
LINE_JOINER.join(
"/** @type{({x: {obj: C}}|{x: {obj: C1}})} */",
"var ri;",
"ri.x.obj.y();"));
}
public void testCovarianceForRecordType17() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @interface */",
"function Foo() {}",
"/** @constructor @implements {Foo} */",
"function Bar1() {}",
"Bar1.prototype.y = function (){return {};};",
"/** @constructor @implements {Foo} */",
"function Bar() {}",
"/** @return {number} */",
"Bar.prototype.y = function (){return 1;};"),
LINE_JOINER.join(
"/** @type {Foo} */ var f;",
"f.y();"));
}
public void testCovarianceForRecordType18() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor*/",
"function Bar1() {}",
"/** @type {{x: number}} */",
"Bar1.prototype.prop;",
"/** @constructor */",
"function Bar() {}",
"/** @type {{x: number, y: number}} */",
"Bar.prototype.prop;"),
LINE_JOINER.join(
"/** @type {{x: number}} */ var f;",
"f.z;"));
}
public void testCovarianceForRecordType19() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function Bar1() {}",
"/** @type {number} */",
"Bar1.prototype.prop;",
"/** @type {number} */",
"Bar1.prototype.prop1;",
"/** @constructor */",
"function Bar2() {}",
"/** @type {number} */",
"Bar2.prototype.prop;"),
LINE_JOINER.join(
"/** @type {(Bar1|Bar2)} */ var b;",
"var x = b.prop1"));
}
public void testCovarianceForRecordType20() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function Bar1() {}",
"/** @type {number} */",
"Bar1.prototype.prop;",
"/** @type {number} */",
"Bar1.prototype.prop1;",
"/** @type {number} */",
"Bar1.prototype.prop2;"),
LINE_JOINER.join(
"/** @type {{prop2:number}} */ var c;",
"/** @type {(Bar1|{prop:number, prop2: number})} */ var b;",
// there should be no warning saying that
// prop2 is not defined on b;
"var x = b.prop2"));
}
public void testCovarianceForRecordType20_2() throws Exception {
testTypesWithExtraExterns(
"",
LINE_JOINER.join(
"/** @type {{prop2:number}} */ var c;",
"/** @type {({prop:number, prop1: number, prop2: number}|",
"{prop:number, prop2: number})} */ var b;",
// there should be no warning saying that
// prop2 is not defined on b;
"var x = b.prop2"));
}
public void testOptimizePropertyMap1() throws Exception {
// For non object-literal types such as Function, the behavior doesn't change.
// The stray property is added as unknown.
testTypes(LINE_JOINER.join(
"/** @return {!Function} */",
"function f() {",
" var x = function() {};",
" /** @type {number} */",
" x.prop = 123;",
" return x;",
"}",
"function g(/** !Function */ x) {",
" var /** null */ n = x.prop;",
"}"));
}
public void testOptimizePropertyMap2() throws Exception {
// Don't add the inferred property to all Foo values.
testTypes(LINE_JOINER.join(
"/** @typedef {{a:number}} */",
"var Foo;",
"function f(/** !Foo */ x) {",
" var y = x;",
" /** @type {number} */",
" y.b = 123;",
"}",
"function g(/** !Foo */ x) {",
" var /** null */ n = x.b;",
"}"),
"Property b never defined on x");
}
public void testOptimizePropertyMap3() throws Exception {
// For @record types, add the stray property to the index as before.
testTypes(LINE_JOINER.join(
"/** @record */",
"function Foo() {}",
"/** @type {number} */",
"Foo.prototype.a;",
"function f(/** !Foo */ x) {",
" var y = x;",
" /** @type {number} */",
" y.b = 123;",
"}",
"function g(/** !Foo */ x) {",
" var /** null */ n = x.b;",
"}"));
}
public void testOptimizePropertyMap4() throws Exception {
testTypes(LINE_JOINER.join(
"function f(x) {",
" var y = { a: 1, b: 2 };",
"}",
"function g(x) {",
" return x.b + 1;",
"}"));
}
public void testOptimizePropertyMap5() throws Exception {
// Tests that we don't declare the properties on Object (so they don't appear on
// all object types).
testTypes(LINE_JOINER.join(
"function f(x) {",
" var y = { a: 1, b: 2 };",
"}",
"function g() {",
" var x = { c: 123 };",
" return x.a + 1;",
"}"),
"Property a never defined on x");
}
public void testOptimizePropertyMap6() throws Exception {
// The stray property doesn't appear on other inline record types.
testTypes(LINE_JOINER.join(
"function f(/** {a:number} */ x) {",
" var y = x;",
" /** @type {number} */",
" y.b = 123;",
"}",
"function g(/** {c:number} */ x) {",
" var /** null */ n = x.b;",
"}"),
"Property b never defined on x");
}
public void testOptimizePropertyMap7() throws Exception {
testTypes(LINE_JOINER.join(
"function f() {",
" var x = {a:1};",
" x.b = 2;",
"}",
"function g() {",
" var y = {a:1};",
" return y.b + 1;",
"}"),
"Property b never defined on y");
}
public void testOptimizePropertyMap8() throws Exception {
testTypes(LINE_JOINER.join(
"function f(/** {a:number, b:number} */ x) {}",
"function g(/** {c:number} */ x) {",
" var /** null */ n = x.b;",
"}"),
"Property b never defined on x");
}
public void testOptimizePropertyMap9() throws Exception {
// Don't add the stray property to all types that meet with {a: number, c: string}.
testTypes(LINE_JOINER.join(
"/** @constructor */",
"function Foo() {",
" this.a = 123;",
"}",
"function f(/** {a: number, c: string} */ x) {",
" x.b = 234;",
"}",
"function g(/** !Foo */ x) {",
" return x.b + 5;",
"}"),
"Property b never defined on Foo");
}
public void testCovarianceForRecordType21() throws Exception {
testTypesWithExtraExterns(
"",
LINE_JOINER.join(
"/** @constructor */",
"function Bar1() {};",
"/** @type {number} */",
"Bar1.prototype.propName;",
"/** @type {number} */",
"Bar1.prototype.propName1;",
"/** @type {{prop2:number}} */ var c;",
"/** @type {(Bar1|{propName:number, propName1: number})} */ var b;",
"var x = b.prop2;"),
"Property prop2 never defined on b");
}
public void testCovarianceForRecordType22() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function Bar() {}",
"/** @type {number} */",
"Bar.prototype.prop2;",
"/** @constructor */",
"function Bar1() {}",
"/** @type {number} */",
"Bar1.prototype.prop;",
"/** @type {number} */",
"Bar1.prototype.prop1;",
"/** @type {number} */",
"Bar1.prototype.prop2;"),
LINE_JOINER.join(
"/** @type {(Bar1|{prop:number, prop1: number})} */ var b;",
// there should be no warning saying that
// prop2 is not defined on b;
"var x = b.prop2"));
}
public void testCovarianceForRecordType23() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function A() {}",
"/** @constructor @extends{A} */",
"function B() {}",
"",
"/** @constructor */",
"function C() {}",
"/** @type {B} */",
"C.prototype.prop2;",
"/** @type {number} */",
"C.prototype.prop3;",
"",
"/** @constructor */",
"function D() {}",
"/** @type {number} */",
"D.prototype.prop;",
"/** @type {number} */",
"D.prototype.prop1;",
"/** @type {B} */",
"D.prototype.prop2;"),
LINE_JOINER.join(
"/** @type {{prop2: A}} */ var record;",
"var xhr = new C();",
"if (true) { xhr = new D(); }",
// there should be no warning saying that
// prop2 is not defined on b;
"var x = xhr.prop2"));
}
public void testCovarianceForRecordType24() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"",
"/** @type {!Function} */",
"C.prototype.abort = function() {};",
"",
"/** @type{number} */",
"C.prototype.test2 = 1;"),
LINE_JOINER.join(
"function f() {",
" /** @type{{abort: !Function, count: number}} */",
" var x;",
"}",
"",
"function f2() {",
" /** @type{(C|{abort: Function})} */",
" var y;",
" y.abort();",
"}"));
}
public void testCovarianceForRecordType25() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"",
"/** @type {!Function} */",
"C.prototype.abort = function() {};",
"",
"/** @type{number} */",
"C.prototype.test2 = 1;"),
LINE_JOINER.join(
"function f() {",
" /** @type{!Function} */ var f;",
" var x = {abort: f, count: 1}",
" return x;",
"}",
"",
"function f2() {",
" /** @type{(C|{test2: number})} */",
" var y;",
" y.abort();",
"}"));
}
public void testCovarianceForRecordType26() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"",
"C.prototype.abort = function() {};",
"",
"/** @type{number} */",
"C.prototype.test2 = 1;"),
LINE_JOINER.join(
"function f() {",
" /** @type{{abort: !Function}} */",
" var x;",
"}",
"",
"function f2() {",
" /** @type{(C|{test2: number})} */",
" var y;",
" /** @type {C} */ (y).abort();",
"}"));
}
public void testCovarianceForRecordType26AndAHalf() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"",
"C.prototype.abort = function() {};",
"",
"/** @type{number} */",
"C.prototype.test2 = 1;",
"var g = function /** !C */(){};"),
LINE_JOINER.join(
"function f() {",
" /** @type{{abort: !Function}} */",
" var x;",
"}",
"function f2() {",
" var y = g();",
" y.abort();",
"}"));
}
public void testCovarianceForRecordType27() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function C(){}",
"/** @constructor @extends {C} */",
"function C2() {}"),
LINE_JOINER.join(
"/** @type {{prop2:C}} */ var c;",
"/** @type {({prop:number, prop1: number, prop2: C}|",
"{prop:number, prop1: number, prop2: number})} */ var b;",
"var x = b.prop2;"));
}
public void testCovarianceForRecordType28() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function XMLHttpRequest() {}",
"/**",
" * @return {undefined}",
" */",
"XMLHttpRequest.prototype.abort = function() {};",
"",
"/** @constructor */",
"function XDomainRequest() {}",
"",
"XDomainRequest.prototype.abort = function() {};"),
LINE_JOINER.join(
"/**",
" * @typedef {{abort: !Function, close: !Function}}",
" */",
"var WritableStreamSink;",
"function sendCrossOrigin() {",
" var xhr = new XMLHttpRequest;",
" xhr = new XDomainRequest;",
" return function() {",
" xhr.abort();",
" };",
"}"));
}
public void testCovarianceForRecordType29() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function XMLHttpRequest() {}",
"/**",
" * @type {!Function}",
" */",
"XMLHttpRequest.prototype.abort = function() {};",
"",
"/** @constructor */",
"function XDomainRequest() {}",
"/**",
" * @type {!Function}",
" */",
"XDomainRequest.prototype.abort = function() {};"),
LINE_JOINER.join(
"/**",
" * @typedef {{close: !Function, abort: !Function}}",
" */",
"var WritableStreamSink;",
"function sendCrossOrigin() {",
" var xhr = new XMLHttpRequest;",
" xhr = new XDomainRequest;",
" return function() {",
" xhr.abort();",
" };",
"}"));
}
public void testCovarianceForRecordType30() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function A() {}"
),
LINE_JOINER.join(
"/**",
" * @type {{prop1: (A)}}",
" */",
"var r1;",
"/**",
" * @type {{prop1: (A|undefined)}}",
" */",
"var r2;",
"r1 = r2"),
LINE_JOINER.join(
"assignment",
"found : {prop1: (A|null|undefined)}",
"required: {prop1: (A|null)}"));
}
public void testCovarianceForRecordType31() throws Exception {
testTypesWithExtraExterns(
LINE_JOINER.join(
"/** @constructor */",
"function A() {}"
),
LINE_JOINER.join(
"/**",
" * @type {{prop1: (A|null)}}",
" */",
"var r1;",
"/**",
" * @type {{prop1: (A|null|undefined)}}",
" */",
"var r2;",
"r1 = r2"),
LINE_JOINER.join(
"assignment",
"found : {prop1: (A|null|undefined)}",
"required: {prop1: (A|null)}"));
}
public void testDuplicateVariableDefinition1() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record */",
"function A() {}",
"/** @type {number} */",
"A.prototype.prop;",
"/** @record */",
"function B() {}",
"/** @type {number} */",
"B.prototype.prop;",
"/** @constructor */",
"function C() {}",
"/** @type {number} */",
"C.prototype.prop;",
"/** @return {(A|B|C)} */",
"function fun () {}",
"/** @return {(B|A|C)} */",
"function fun () {}"),
"variable fun redefined, original definition at [testcode]:14");
}
public void testDuplicateVariableDefinition3() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {};",
"/** @type {{x:number}} */ ns.x;",
"/** @type {{x:number}} */ ns.x;"));
}
public void testDuplicateVariableDefinition3_1() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {};",
"/** @type {{x:number}} */ ns.x;",
"/** @type {{x:string}} */ ns.x;"),
"variable ns.x redefined with type {x: string}, original definition "
+ "at [testcode]:2 with type {x: number}");
}
public void testDuplicateVariableDefinition3_2() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {};",
"/** @type {{x:number}} */ ns.x;",
"/** @type {{x:number, y:boolean}} */ ns.x;"),
"variable ns.x redefined with type {x: number, y: boolean}, "
+ "original definition at [testcode]:2 with type {x: number}");
}
public void testDuplicateVariableDefinition4() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {};",
"/** @record */ function rec3(){}",
"/** @record */ function rec4(){}",
"/** @type {!rec3} */ ns.x;",
"/** @type {!rec4} */ ns.x;"));
}
public void testDuplicateVariableDefinition5() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {};",
"/** @record */ function rec3(){}",
"/** @record */ function rec4(){}",
"/** @type {number} */ rec4.prototype.prop;",
"/** @type {!rec3} */ ns.x;",
"/** @type {!rec4} */ ns.x;"),
"variable ns.x redefined with type rec4, original definition at "
+ "[testcode]:5 with type rec3");
}
public void testDuplicateVariableDefinition6() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {};",
"/** @record */ function rec3(){}",
"/** @type {number} */ rec3.prototype.prop;",
"/** @record */ function rec4(){}",
"/** @type {!rec3} */ ns.x;",
"/** @type {!rec4} */ ns.x;"),
"variable ns.x redefined with type rec4, original definition at "
+ "[testcode]:5 with type rec3");
}
/**
* check bug fix 22713201 (the first case)
*/
public void testDuplicateVariableDefinition7() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @typedef {{prop:TD2}} */",
" var TD1;",
"",
" /** @typedef {{prop:TD1}} */",
" var TD2;",
"",
" var /** TD1 */ td1;",
" var /** TD2 */ td2;",
"",
" td1 = td2;"));
}
public void testDuplicateVariableDefinition8() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {}",
"/** @record */ function rec(){}",
"/** @type {number} */ rec.prototype.prop;",
"",
"/** @type {!rec} */ ns.x;",
"/** @type {{prop:number}} */ ns.x;",
"",
"/** @type {{prop:number}} */ ns.y;",
"/** @type {!rec} */ ns.y;"));
}
public void testDuplicateVariableDefinition8_2() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {}",
"/** @record */ function rec(){}",
"/** @type {number} */ rec.prototype.prop;",
"",
"/** @type {!rec} */ ns.x;",
"/** @type {{prop:string}} */ ns.x;",
"",
"/** @type {{prop:number}} */ ns.y;",
"/** @type {!rec} */ ns.y;"),
"variable ns.x redefined with type {prop: string}, original "
+ "definition at [testcode]:5 with type rec");
}
public void testDuplicateVariableDefinition8_3() throws Exception {
testTypes(
LINE_JOINER.join(
"var ns = {}",
"/** @record */ function rec(){}",
"/** @type {string} */ rec.prototype.prop;",
"",
"/** @type {!rec} */ ns.x;",
"/** @type {{prop:string}} */ ns.x;",
"",
"/** @type {{prop:number}} */ ns.y;",
"/** @type {!rec} */ ns.y;"),
"variable ns.y redefined with type rec, original definition at "
+ "[testcode]:8 with type {prop: number}");
}
public void testDuplicateVariableDefinition8_4() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */ function I() {}",
"/** @type {T} */ I.prototype.prop;",
"var ns = {}",
"/** @record */ function rec(){}",
"/** @type {I} */ rec.prototype.prop;",
"",
"/** @type {!rec} */ ns.x;",
"/** @type {{prop:I}} */ ns.x;"));
}
public void testDuplicateVariableDefinition8_5() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */ function I() {}",
"/** @type {T} */ I.prototype.prop;",
"var ns = {}",
"/** @record */ function rec(){}",
"/** @type {I<number>} */ rec.prototype.prop;",
"",
"/** @type {!rec} */ ns.x;",
"/** @type {{prop:I<number>}} */ ns.x;"));
}
public void testDuplicateVariableDefinition8_6() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */ function I() {}",
"/** @type {T} */ I.prototype.prop;",
"var ns = {}",
"/** @record */ function rec(){}",
"/** @type {I<number>} */ rec.prototype.prop;",
"",
"/** @type {!rec} */ ns.x;",
"/** @type {{prop:I<string>}} */ ns.x;"),
"variable ns.x redefined with type {prop: (I<string>|null)}, "
+ "original definition at [testcode]:7 with type rec");
}
// should have no warning, need to handle equivalence checking for
// structural types with template types
public void testDuplicateVariableDefinition8_7() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @record @template T */",
"function rec(){}",
"/** @type {T} */ rec.prototype.value;",
"",
"/** @type {rec<string>} */ ns.x;",
"/** @type {{value: string}} */ ns.x;"),
"variable ns.x redefined with type {value: string}, "
+ "original definition at [testcode]:5 with type (null|rec<string>)");
}
public void testModuloNullUndefThatWorkedWithoutSpecialSubtypingRules() throws Exception {
testTypes(LINE_JOINER.join(
"/** @constructor */",
"function Foo() {}",
"function f(/** function(?Foo, !Foo) */ x) {",
" return /** @type {function(!Foo, ?Foo)} */ (x);",
"}"));
testTypes(LINE_JOINER.join(
"/** @constructor */",
"function Foo() {}",
"function f(/** !Array<!Foo> */ to, /** !Array<?Foo> */ from) {",
" to = from;",
"}"));
testTypes(LINE_JOINER.join(
"function f(/** ?Object */ x) {",
" return {} instanceof x;",
"}"));
testTypes(LINE_JOINER.join(
"function f(/** ?Function */ x) {",
" return x();",
"}"));
}
public void testEs5ClassExtendingEs6Class() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"class Foo {}",
"/** @constructor @extends {Foo} */ var Bar = function() {};"),
"ES5 class Bar cannot extend ES6 class Foo");
}
public void testEs5ClassExtendingEs6Class_noWarning() throws Exception {
setLanguageInAndOut(LanguageMode.ECMASCRIPT_2015, LanguageMode.ECMASCRIPT5);
testTypes(
LINE_JOINER.join(
"class A {}",
"/** @constructor @extends {A} */",
"const B = createSubclass(A);"));
}
public void testNonNullTemplatedThis() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"",
"/** ",
" @return {THIS} ",
" @this {THIS}",
" @template THIS",
"*/",
"C.prototype.method = function() {};",
"",
"/** @return {C|null} */",
"function f() {",
" return x;",
"};",
"",
"/** @type {string} */ var s = f().method();"),
"initializing variable\n" +
"found : C\n" +
"required: string");
}
// Github issue #2222: https://github.com/google/closure-compiler/issues/2222
public void testSetPrototypeToNewInstance() throws Exception {
testTypes(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype = new C;"));
}
public void testFilterNoResolvedType() throws Exception {
testClosureTypes(
LINE_JOINER.join(
"goog.forwardDeclare('Foo');",
"/**",
" * @param {boolean} pred",
" * @param {?Foo} x",
" */",
"function f(pred, x) {",
" var y;",
" if (pred) {",
" y = null;",
" } else {",
" y = x;",
" }",
" var /** number */ z = y;",
"}"),
// Tests that the type of y is (NoResolvedType|null) and not (Foo|null)
LINE_JOINER.join(
"initializing variable",
"found : (NoResolvedType|null)",
"required: number"));
}
public void testNoResolvedTypeDoesntCauseInfiniteLoop() throws Exception {
testClosureTypes(LINE_JOINER.join(
"goog.forwardDeclare('Foo');",
"goog.forwardDeclare('Bar');",
"/** @const */",
"var goog = {};",
"goog.forwardDeclare = function(x) {};",
"/** @const */",
"goog.asserts = {};",
"goog.asserts.assert = function(x, y) {};",
"/** @interface */",
"var Baz = function() {};",
"/** @return {!Bar} */",
"Baz.prototype.getBar = function() {};",
"/** @constructor */",
"var Qux = function() {",
" /** @type {?Foo} */",
" this.jobRuntimeTracker_ = null;",
"};",
"/** @param {!Baz} job */",
"Qux.prototype.runRenderJobs_ = function(job) {",
" for (var i = 0; i < 10; i++) {",
" if (this.jobRuntimeTracker_) {",
" goog.asserts.assert(job.getBar, '');",
" }",
" }",
"};"),
null);
}
private void testTypes(String js) {
testTypes(js, (String) null);
}
private void testTypes(String js, String description) {
testTypes(js, description, false);
}
private void testTypes(String js, DiagnosticType type) {
testTypes(js, type, false);
}
private void testClosureTypes(String js, String description) {
testClosureTypesMultipleWarnings(js,
description == null ? null : ImmutableList.of(description));
}
private void testClosureTypesMultipleWarnings(
String js, List<String> descriptions) {
compiler.initOptions(compiler.getOptions());
Node n = compiler.parseTestCode(js);
Node externs = IR.root();
Node externAndJsRoot = IR.root(externs, n);
assertEquals("parsing error: " +
Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
// For processing goog.addDependency for forward typedefs.
new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR, false).process(externs, n);
new TypeCheck(compiler,
new ClosureReverseAbstractInterpreter(registry).append(
new SemanticReverseAbstractInterpreter(registry))
.getFirst(),
registry)
.processForTesting(null, n);
assertEquals(
"unexpected error(s) : " +
Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
if (descriptions == null) {
assertEquals(
"unexpected warning(s) : " +
Joiner.on(", ").join(compiler.getWarnings()),
0, compiler.getWarningCount());
} else {
assertEquals(
"unexpected warning(s) : " +
Joiner.on(", ").join(compiler.getWarnings()),
descriptions.size(), compiler.getWarningCount());
Set<String> actualWarningDescriptions = new HashSet<>();
for (int i = 0; i < descriptions.size(); i++) {
actualWarningDescriptions.add(compiler.getWarnings()[i].description);
}
assertEquals(
new HashSet<>(descriptions), actualWarningDescriptions);
}
}
void testTypes(String js, String description, boolean isError) {
testTypes(DEFAULT_EXTERNS, js, description, isError);
}
void testTypes(
String externs, String js, String description, boolean isError) {
parseAndTypeCheck(externs, js);
JSError[] errors = compiler.getErrors();
if (description != null && isError) {
assertTrue("expected an error", errors.length > 0);
assertEquals(description, errors[0].description);
errors = Arrays.asList(errors).subList(1, errors.length).toArray(
new JSError[errors.length - 1]);
}
if (errors.length > 0) {
fail("unexpected error(s):\n" + LINE_JOINER.join(errors));
}
JSError[] warnings = compiler.getWarnings();
if (description != null && !isError) {
assertTrue("expected a warning", warnings.length > 0);
assertEquals(description, warnings[0].description);
warnings = Arrays.asList(warnings).subList(1, warnings.length).toArray(
new JSError[warnings.length - 1]);
}
if (warnings.length > 0) {
fail("unexpected warnings(s):\n" + LINE_JOINER.join(warnings));
}
}
void testTypes(String js, DiagnosticType diagnosticType, boolean isError) {
testTypes(DEFAULT_EXTERNS, js, diagnosticType, isError);
}
void testTypesWithExterns(String externs, String js) {
testTypes(externs, js, (String) null, false);
}
void testTypesWithExtraExterns(String externs, String js) {
testTypes(DEFAULT_EXTERNS + "\n" + externs, js, (String) null, false);
}
void testTypesWithExtraExterns(
String externs, String js, String description) {
testTypes(DEFAULT_EXTERNS + "\n" + externs, js, description, false);
}
void testTypes(String externs, String js, DiagnosticType diagnosticType,
boolean isError) {
parseAndTypeCheck(externs, js);
JSError[] errors = compiler.getErrors();
if (diagnosticType != null && isError) {
assertTrue("expected an error", errors.length > 0);
assertEquals(diagnosticType, errors[0].getType());
errors = Arrays.asList(errors).subList(1, errors.length).toArray(
new JSError[errors.length - 1]);
}
if (errors.length > 0) {
fail("unexpected error(s):\n" + LINE_JOINER.join(errors));
}
JSError[] warnings = compiler.getWarnings();
if (diagnosticType != null && !isError) {
assertTrue("expected a warning", warnings.length > 0);
assertEquals(diagnosticType, warnings[0].getType());
warnings = Arrays.asList(warnings).subList(1, warnings.length).toArray(
new JSError[warnings.length - 1]);
}
if (warnings.length > 0) {
fail("unexpected warnings(s):\n" + LINE_JOINER.join(warnings));
}
}
/**
* Parses and type checks the JavaScript code.
*/
private Node parseAndTypeCheck(String js) {
return parseAndTypeCheck(DEFAULT_EXTERNS, js);
}
private Node parseAndTypeCheck(String externs, String js) {
return parseAndTypeCheckWithScope(externs, js).root;
}
/**
* Parses and type checks the JavaScript code and returns the TypedScope used
* whilst type checking.
*/
private TypeCheckResult parseAndTypeCheckWithScope(String js) {
return parseAndTypeCheckWithScope(DEFAULT_EXTERNS, js);
}
private TypeCheckResult parseAndTypeCheckWithScope(
String externs, String js) {
compiler.init(
ImmutableList.of(SourceFile.fromCode("[externs]", externs)),
ImmutableList.of(SourceFile.fromCode("[testcode]", js)),
compiler.getOptions());
Node n = compiler.getInput(new InputId("[testcode]")).getAstRoot(compiler);
Node externsNode = compiler.getInput(new InputId("[externs]"))
.getAstRoot(compiler);
Node externAndJsRoot = IR.root(externsNode, n);
assertEquals("parsing error: " +
Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
if (compiler.getOptions().getLanguageIn().isEs6OrHigher()) {
List<PassFactory> passes = new ArrayList<>();
TranspilationPasses.addEs6EarlyPasses(passes);
TranspilationPasses.addEs6LatePasses(passes);
TranspilationPasses.addRewritePolyfillPass(passes);
PhaseOptimizer phaseopt = new PhaseOptimizer(compiler, null);
phaseopt.consume(passes);
phaseopt.process(externsNode, externAndJsRoot);
}
TypedScope s = makeTypeCheck().processForTesting(externsNode, n);
return new TypeCheckResult(n, s);
}
private Node typeCheck(Node n) {
Node externsNode = IR.root();
Node externAndJsRoot = IR.root(externsNode);
externAndJsRoot.addChildToBack(n);
makeTypeCheck().processForTesting(null, n);
return n;
}
private TypeCheck makeTypeCheck() {
return new TypeCheck(compiler, new SemanticReverseAbstractInterpreter(registry), registry);
}
void testTypes(String js, String[] warnings) {
Node n = compiler.parseTestCode(js);
assertEquals(0, compiler.getErrorCount());
Node externsNode = IR.root();
// create a parent node for the extern and source blocks
IR.root(externsNode, n);
makeTypeCheck().processForTesting(null, n);
assertEquals(0, compiler.getErrorCount());
if (warnings != null) {
assertEquals(warnings.length, compiler.getWarningCount());
JSError[] messages = compiler.getWarnings();
for (int i = 0; i < warnings.length && i < compiler.getWarningCount();
i++) {
assertEquals(warnings[i], messages[i].description);
}
} else {
assertEquals(0, compiler.getWarningCount());
}
}
String suppressMissingProperty(String ... props) {
String result = "function dummy(x) { ";
for (String prop : props) {
result += "x." + prop + " = 3;";
}
return result + "}";
}
private static class TypeCheckResult {
private final Node root;
private final TypedScope scope;
private TypeCheckResult(Node root, TypedScope scope) {
this.root = root;
this.scope = scope;
}
}
}