/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.Multimap; import com.google.javascript.rhino.Node; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; /** * Unit test for the Compiler DisambiguateProperties pass. * */ public final class DisambiguatePropertiesTest extends TypeICompilerTestCase { private DisambiguateProperties lastPass; private static String renameFunctionDefinition = "/** @const */ var goog = {};\n" + "/** @const */ goog.reflect = {};\n" + "/** @return {string} */\n" + "goog.reflect.objectProperty = function(prop, obj) { return ''; };\n"; public DisambiguatePropertiesTest() { super(DEFAULT_EXTERNS); parseTypeInfo = true; } @Override protected void setUp() throws Exception { super.setUp(); super.enableNormalize(); } @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); options.setWarningLevel(DiagnosticGroups.NEW_CHECK_TYPES_EXTRA_CHECKS, CheckLevel.OFF); return options; } @Override protected CompilerPass getProcessor(final Compiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node root) { Map<String, CheckLevel> propertiesToErrorFor = new HashMap<>(); propertiesToErrorFor.put("foobar", CheckLevel.ERROR); // This must be created after type checking is run as it depends on // any mismatches found during checking. lastPass = new DisambiguateProperties(compiler, propertiesToErrorFor); lastPass.process(externs, root); } }; } @Override protected int getNumRepetitions() { return 1; } public void testOneType1() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;"; testSets(js, js, "{a=[[Foo.prototype]]}"); js = renameFunctionDefinition + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F[goog.reflect.objectProperty('a', F)] = 0;"; testSets(js, js, "{a=[[Foo.prototype]]}"); } public void testOneType2() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype = {a: 0};\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;"; String expected = "{a=[[Foo.prototype]]}"; testSets(js, js, expected); js = renameFunctionDefinition + "/** @constructor */ function Foo() {}\n" + "Foo.prototype = {a: 0};\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F[goog.reflect.objectProperty('a', F)] = 0;"; testSets(js, js, expected); } public void testOneType3() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype = { get a() {return 0}," + " set a(b) {} };\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;"; String expected = "{a=[[Foo.prototype]]}"; testSets(js, js, expected); js = renameFunctionDefinition + "/** @constructor */ function Foo() {}\n" + "Foo.prototype = { get a() {return 0}," + " set a(b) {} };\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F[goog.reflect.objectProperty('a', F)] = 0;"; testSets(js, js, expected); } public void testOneType4() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype = {'a': 0};\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F['a'] = 0;"; String expected = "{}"; testSets(js, js, expected); } public void testPrototypeAndInstance1() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;"; testSets(js, js, "{a=[[Foo.prototype]]}"); js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;"; testSets(js, js, "{a=[[Foo.prototype]]}"); } public void testPrototypeAndInstance2() { String js = "" + "/** @constructor @template T */ " + "function Foo() {" + " this.a = 0;" + "}\n" + "/** @type {Foo.<string>} */\n" + "var f1 = new Foo;\n" + "f1.a = 0;" + "/** @type {Foo.<string>} */\n" + "var f2 = new Foo;\n" + "f2.a = 0;"; testSets(js, js, "{a=[[Foo]]}"); } public void testPrototypeAndInstance3() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "new Foo().a = 0;"; testSets(js, js, "{a=[[Foo.prototype]]}"); } public void testPrototypeAndInstance4() { String js = "" + "/** @constructor @template T */ " + "function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @type {Foo.<string>} */\n" + "var f = new Foo;\n" + "f.a = 0;"; testSets(js, js, "{a=[[Foo.prototype]]}"); } public void testTwoTypes1() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;" + "/** @type {Bar} */\n" + "var B = new Bar;\n" + "B.a = 0;"; String output = "" + "/** @constructor */function Foo(){}" + "Foo.prototype.Foo_prototype$a=0;" + "/** @type {Foo} */" + "var F=new Foo;" + "F.Foo_prototype$a=0;" + "/** @constructor */ function Bar(){}" + "Bar.prototype.Bar_prototype$a=0;" + "/** @type {Bar} */" + "var B=new Bar;" + "B.Bar_prototype$a=0"; testSets(js, output, "{a=[[Bar.prototype], [Foo.prototype]]}"); } public void testTwoTypes2() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype = {a: 0};" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype = {a: 0};" + "/** @type {Bar} */\n" + "var B = new Bar;\n" + "B.a = 0;"; String output = "" + "/** @constructor */ function Foo(){}" + "Foo.prototype = {Foo_prototype$a: 0};" + "/** @type {Foo} */" + "var F=new Foo;" + "F.Foo_prototype$a=0;" + "/** @constructor */ function Bar(){}" + "Bar.prototype = {Bar_prototype$a: 0};" + "/** @type {Bar} */" + "var B=new Bar;" + "B.Bar_prototype$a=0"; testSets(js, output, "{a=[[Bar.prototype], [Foo.prototype]]}"); } public void testTwoTypes3() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype = { get a() {return 0}," + " set a(b) {} };\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype = { get a() {return 0}," + " set a(b) {} };\n" + "/** @type {Bar} */\n" + "var B = new Bar;\n" + "B.a = 0;"; String output = "" + "/** @constructor */ function Foo(){}" + "Foo.prototype = { get Foo_prototype$a() {return 0}," + " set Foo_prototype$a(b) {} };\n" + "/** @type {Foo} */\n" + "var F=new Foo;" + "F.Foo_prototype$a=0;" + "/** @constructor */ function Bar(){}" + "Bar.prototype = { get Bar_prototype$a() {return 0}," + " set Bar_prototype$a(b) {} };\n" + "/** @type {Bar} */\n" + "var B=new Bar;" + "B.Bar_prototype$a=0"; testSets(js, output, "{a=[[Bar.prototype], [Foo.prototype]]}"); } public void testTwoTypes4() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype = {a: 0};" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype = {'a': 0};" + "/** @type {Bar} */\n" + "var B = new Bar;\n" + "B['a'] = 0;"; String output = "" + "/** @constructor */ function Foo(){}" + "Foo.prototype = {a: 0};" + "/** @type {Foo} */ var F=new Foo;" + "F.a=0;" + "/** @constructor */ function Bar(){}" + "Bar.prototype = {'a': 0};" + "/** @type {Bar} */ var B=new Bar;" + "B['a']=0"; testSets(js, output, "{a=[[Foo.prototype]]}"); } public void testTwoTypes5() { String js = "" + "/** @constructor @template T */ function Foo() { this.a = 0; }\n" + "/** @type {Foo<string>} */\n" + "var F = new Foo;\n" + "F.a = 0;" + "/** @constructor @template T */ function Bar() { this.a = 0; }\n" + "/** @type {Bar<string>} */\n" + "var B = new Bar;\n" + "B.a = 0;"; String output = "" + "/** @constructor @template T */ function Foo(){ this.Foo$a = 0; }" + "/** @type {Foo<string>} */" + "var F=new Foo;" + "F.Foo$a=0;" + "/** @constructor @template T */ function Bar(){ this.Bar$a = 0; }" + "/** @type {Bar<string>} */ var B=new Bar;" + "B.Bar$a=0"; testSets(js, output, "{a=[[Bar], [Foo]]}"); } public void testTwoFields() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;" + "Foo.prototype.b = 0;" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;" + "F.b = 0;"; String output = "" + "/** @constructor */\n" + "function Foo() {}\n" + "Foo.prototype.a=0;\n" + "Foo.prototype.b=0;" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;\n" + "F.b = 0"; testSets(js, output, "{a=[[Foo.prototype]], b=[[Foo.prototype]]}"); } public void testTwoSeparateFieldsTwoTypes() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;" + "Foo.prototype.b = 0;" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.a = 0;" + "F.b = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;" + "Bar.prototype.b = 0;" + "/** @type {Bar} */\n" + "var B = new Bar;\n" + "B.a = 0;" + "B.b = 0;"; String output = "" + "/** @constructor */ function Foo(){}" + "Foo.prototype.Foo_prototype$a=0;" + "Foo.prototype.Foo_prototype$b=0;" + "/** @type {Foo} */ var F=new Foo;" + "F.Foo_prototype$a=0;" + "F.Foo_prototype$b=0;" + "/** @constructor */ function Bar(){}" + "Bar.prototype.Bar_prototype$a=0;" + "Bar.prototype.Bar_prototype$b=0;" + "/** @type {Bar} */ var B=new Bar;" + "B.Bar_prototype$a=0;" + "B.Bar_prototype$b=0"; testSets(js, output, "{a=[[Bar.prototype], [Foo.prototype]], b=[[Bar.prototype], [Foo.prototype]]}"); } public void testUnionType_1() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;" + "/** @type {Bar|Foo} */\n" + "var B = new Bar;\n" + "B.a = 0;\n" + "B = new Foo;\n" + "B.a = 0;\n" + "/** @constructor */ function Baz() {}\n" + "Baz.prototype.a = 0;\n"; this.mode = TypeInferenceMode.OTI_ONLY; testSets(js, "{a=[[Bar.prototype, Foo.prototype], [Baz.prototype]]}"); // NTI knows that B has type Bar this.mode = TypeInferenceMode.NTI_ONLY; testSets(js, "{a=[[Bar.prototype], [Baz.prototype], [Foo.prototype]]}"); } public void testUnionType_2() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;" + "var B = /** @type {Bar|Foo} */ (new Bar);\n" + "B.a = 0;\n" + "B = new Foo;\n" + "B.a = 0;\n" + "/** @constructor */ function Baz() {}\n" + "Baz.prototype.a = 0;\n"; testSets(js, "{a=[[Bar.prototype, Foo.prototype], [Baz.prototype]]}"); } public void testIgnoreUnknownType() { String js = "" + "/** @constructor */\n" + "function Foo() {}\n" + "Foo.prototype.blah = 3;\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.blah = 0;\n" + "var U = function() { return {} };\n" + "U().blah();"; String expected = "" + "/** @constructor */ function Foo(){}" + "Foo.prototype.blah=3;" + "/** @type {Foo} */" + "var F = new Foo;F.blah=0;" + "var U=function(){return{}};U().blah()"; this.mode = TypeInferenceMode.OTI_ONLY; testSets(js, expected, "{}"); this.mode = TypeInferenceMode.NTI_ONLY; testSets("", js, expected, "{}", NewTypeInference.INEXISTENT_PROPERTY, "Property blah never defined on Object{}"); } public void testIgnoreUnknownType1() { String js = LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.blah = 3;", "/** @type {Foo} */", "var F = new Foo;", "F.blah = 0;", "/** @return {Object} */", "var U = function() { return {} };", "U().blah();"); this.mode = TypeInferenceMode.OTI_ONLY; testSets(js, "{blah=[[Foo.prototype]]}"); this.mode = TypeInferenceMode.NTI_ONLY; testSets(js, "{}"); } public void testIgnoreUnknownType2() { String js = "" + "/** @constructor */\n" + "function Foo() {}\n" + "Foo.prototype.blah = 3;\n" + "/** @type {Foo} */\n" + "var F = new Foo;\n" + "F.blah = 0;\n" + "/** @constructor */\n" + "function Bar() {}\n" + "Bar.prototype.blah = 3;\n" + "/** @return {Object} */\n" + "var U = function() { return {} };\n" + "U().blah();"; testSets(js, "{}"); } public void testIgnoreUnknownType3() { String js = LINE_JOINER.join( "/** @constructor */", "function Foo() {}", "Foo.prototype.blah = 3;", "/** @type {Foo} */", "var F = new Foo;", "F.blah = 0;", "/** @constructor */", "function Bar() {}", "Bar.prototype.blah = 3;", "/** @return {Object} */", "var U = function() { return new Bar; };", "U().blah();"); testSets(js, "{}"); } public void testUnionTypeTwoFields() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "Foo.prototype.b = 0;\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;\n" + "Bar.prototype.b = 0;\n" + "var B = /** @type {Foo|Bar} */ (new Bar);\n" + "B.a = 0;\n" + "B.b = 0;\n" + "B = new Foo;\n" + "/** @constructor */ function Baz() {}\n" + "Baz.prototype.a = 0;\n" + "Baz.prototype.b = 0;\n"; testSets(js, "{a=[[Bar.prototype, Foo.prototype], [Baz.prototype]]," + " b=[[Bar.prototype, Foo.prototype], [Baz.prototype]]}"); } public void testCast() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;" + "var F = /** @type {Foo|Bar} */ (new Foo);\n" + "(/** @type {Bar} */(F)).a = 0;"; String output = "" + "/** @constructor */ function Foo(){}\n" + "Foo.prototype.Foo_prototype$a=0;\n" + "/** @constructor */ function Bar(){}\n" + "Bar.prototype.Bar_prototype$a=0;\n" + "var F = /** @type {Foo|Bar} */ (new Foo);\n" + "/** @type {Bar} */ (F).Bar_prototype$a=0;"; testSets(js, output, "{a=[[Bar.prototype], [Foo.prototype]]}"); } public void testConstructorFields() { String js = "" + "/** @constructor */\n" + "var Foo = function() { this.a = 0; };\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;" + "new Foo"; String output = "" + "/** @constructor */ var Foo=function(){this.Foo$a=0};" + "/** @constructor */ function Bar(){}" + "Bar.prototype.Bar_prototype$a=0;" + "new Foo"; testSets(js, output, "{a=[[Bar.prototype], [Foo]]}"); } public void testStaticProperty() { String js = "" + "/** @constructor */ function Foo() {} \n" + "/** @constructor */ function Bar() {}\n" + "Foo.a = 0;" + "Bar.a = 0;"; String output; this.mode = TypeInferenceMode.OTI_ONLY; output = "" + "/** @constructor */ function Foo(){}" + "/** @constructor */ function Bar(){}" + "Foo.function__new_Foo___undefined$a = 0;" + "Bar.function__new_Bar___undefined$a = 0;"; testSets(js, output, "{a=[[function (new:Bar): undefined]," + " [function (new:Foo): undefined]]}"); this.mode = TypeInferenceMode.NTI_ONLY; output = "" + "/** @constructor */ function Foo(){}" + "/** @constructor */ function Bar(){}" + "Foo.Foo__function_new_Foo__undefined__$a = 0;" + "Bar.Bar__function_new_Bar__undefined__$a = 0;"; testSets(js, output, "{a=[[Bar<|function(new:Bar):undefined|>], [Foo<|function(new:Foo):undefined|>]]}"); } public void testSupertypeWithSameField() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @constructor\n* @extends {Foo} */ function Bar() {}\n" + "/** @override */\n" + "Bar.prototype.a = 0;\n" + "/** @type {Bar} */\n" + "var B = new Bar;\n" + "B.a = 0;" + "/** @constructor */ function Baz() {}\n" + "Baz.prototype.a = function(){};\n"; String output = "" + "/** @constructor */ function Foo(){}" + "Foo.prototype.Foo_prototype$a=0;" + "/** @constructor @extends {Foo} */ function Bar(){}" + "/** @override */" + "Bar.prototype.Foo_prototype$a=0;" + "/** @type {Bar} */" + "var B = new Bar;" + "B.Foo_prototype$a=0;" + "/** @constructor */ function Baz(){}Baz.prototype.Baz_prototype$a=function(){};"; testSets(js, output, "{a=[[Baz.prototype], [Foo.prototype]]}"); } public void testScopedType() { String js = "" + "/** @const */ var g = {};\n" + "/** @constructor */ g.Foo = function() {};\n" + "g.Foo.prototype.a = 0;" + "/** @constructor */ g.Bar = function() {};\n" + "g.Bar.prototype.a = 0;"; String output = "" + "/** @const */ var g={};" + "/** @constructor */ g.Foo=function(){};" + "g.Foo.prototype.g_Foo_prototype$a=0;" + "/** @constructor */ g.Bar=function(){};" + "g.Bar.prototype.g_Bar_prototype$a=0;"; testSets(js, output, "{a=[[g.Bar.prototype], [g.Foo.prototype]]}"); } public void testUnresolvedType() { // NOTE(nicksantos): This behavior seems very wrong to me. String js = "" + "var g = {};" + "/** @constructor \n @extends {?} */ " + "var Foo = function() {};\n" + "Foo.prototype.a = 0;" + "/** @constructor */ var Bar = function() {};\n" + "Bar.prototype.a = 0;"; String output = "" + "var g={};" + "/** @constructor @extends {?} */ var Foo=function(){};" + "Foo.prototype.Foo_prototype$a=0;" + "/** @constructor */ var Bar=function(){};" + "Bar.prototype.Bar_prototype$a=0;"; setExpectParseWarningsThisTest(); testSets(js, output, "{a=[[Bar.prototype], [Foo.prototype]]}"); } public void testNamedType() { String js = "" + "/** @const */ var g = {};" + "/** @constructor \n @extends {g.Late} */ var Foo = function() {}\n" + "Foo.prototype.a = 0;" + "/** @constructor */ var Bar = function() {}\n" + "Bar.prototype.a = 0;" + "/** @constructor */ g.Late = function() {}"; String output = "" + "/** @const */ var g={};" + "/** @constructor @extends {g.Late} */ var Foo=function(){};" + "Foo.prototype.Foo_prototype$a=0;" + "/** @constructor */ var Bar=function(){};" + "Bar.prototype.Bar_prototype$a=0;" + "/** @constructor */ g.Late = function(){}"; testSets(js, output, "{a=[[Bar.prototype], [Foo.prototype]]}"); } public void testUnknownType() { String js = "" + "/** @constructor */ var Foo = function() {};\n" + "/** @constructor */ var Bar = function() {};\n" + "/** @return {?} */ function fun() {}\n" + "Foo.prototype.a = fun();\n" + "fun().a;\n" + "Bar.prototype.a = 0;"; testSets(js, js, "{}"); } // When objects flow to untyped code, it is the programmer's responsibility to // use them in a type-safe way, otherwise disambiguation will be wrong. public void testUntypedCodeWrongDisambiguation1() { String js = "" + "/** @constructor */\n" + "function Foo() { this.p1 = 0; }\n" + "/** @constructor */\n" + "function Bar() { this.p1 = 1; }\n" + "var arr = [new Foo, new Bar];\n" + "var /** !Foo */ z = arr[1];\n" + "z.p1;\n"; String output = "" + "/** @constructor */ function Foo() { this.Foo$p1 = 0; }\n" + "/** @constructor */ function Bar() { this.Bar$p1 = 1; }\n" + "var arr = [new Foo, new Bar];\n" + "var /** !Foo */z = arr[1];\n" + "z.Foo$p1;\n"; this.mode = TypeInferenceMode.OTI_ONLY; testSets(js, output, "{p1=[[Bar], [Foo]]}"); this.mode = TypeInferenceMode.NTI_ONLY; testSets("", js, output, "{p1=[[Bar], [Foo]]}", NewTypeInference.MISTYPED_ASSIGN_RHS, LINE_JOINER.join( "The right side in the assignment is not a subtype of the left side.", "Expected : Foo", "Found : Bar|Foo", "More details:", "The found type is a union that includes an unexpected type: Bar")); } // When objects flow to untyped code, it is the programmer's responsibility to // use them in a type-safe way, otherwise disambiguation will be wrong. public void testUntypedCodeWrongDisambiguation2() { String js = "" + "/** @constructor */\n" + "function Foo() { this.p1 = 0; }\n" + "/** @constructor */\n" + "function Bar() { this.p1 = 1; }\n" + "function select(cond, x, y) { return cond ? x : y; }\n" + "/**\n" + " * @param {!Foo} x\n" + " * @param {!Bar} y\n" + " * @return {!Foo}\n" + " */\n" + "function f(x, y) {\n" + " var /** !Foo */ z = select(false, x, y);\n" + " return z;\n" + "}\n" + "f(new Foo, new Bar).p1;\n"; String output = "" + "/** @constructor */ function Foo() { this.Foo$p1 = 0; }\n" + "/** @constructor */ function Bar() { this.Bar$p1 = 1; }\n" + "function select(cond, x, y) { return cond ? x : y; }\n" + "/**\n" + " * @param {!Foo} x\n" + " * @param {!Bar} y\n" + " * @return {!Foo}\n" + " */\n" + "function f(x, y) {\n" + " var /** !Foo */ z = select(false, x, y);\n" + " return z;\n" + "}\n" + "f(new Foo, new Bar).Foo$p1;\n"; testSets(js, output, "{p1=[[Bar], [Foo]]}"); } public void testEnum() { String js = "" + "/** @enum {string} */ var En = {\n" + " A: 'first',\n" + " B: 'second'\n" + "};\n" + "var EA = En.A;\n" + "var EB = En.B;\n" + "/** @constructor */ function Foo(){};\n" + "Foo.prototype.A = 0;\n" + "Foo.prototype.B = 0;\n"; String output = "" + "/** @enum {string} */ var En={A:'first',B:'second'};" + "var EA=En.A;" + "var EB=En.B;" + "/** @constructor */ function Foo(){};" + "Foo.prototype.Foo_prototype$A=0;" + "Foo.prototype.Foo_prototype$B=0"; testSets(js, output, "{A=[[Foo.prototype]], B=[[Foo.prototype]]}"); } public void testEnumOfObjects() { String js = "" + "/** @constructor */ function Formatter() {}" + "Formatter.prototype.format = function() {};" + "/** @constructor */ function Unrelated() {}" + "Unrelated.prototype.format = function() {};" + "/** @enum {!Formatter} */ var Enum = {\n" + " A: new Formatter()\n" + "};\n" + "Enum.A.format();\n"; String output = "" + "/** @constructor */ function Formatter() {}" + "Formatter.prototype.Formatter_prototype$format = function() {};" + "/** @constructor */ function Unrelated() {}" + "Unrelated.prototype.Unrelated_prototype$format = function() {};" + "/** @enum {!Formatter} */ var Enum = {\n" + " A: new Formatter()\n" + "};\n" + "Enum.A.Formatter_prototype$format();\n"; testSets(js, output, "{format=[[Formatter.prototype], [Unrelated.prototype]]}"); } public void testEnumOfObjects2() { String js = "" + "/** @constructor */ function Formatter() {}" + "Formatter.prototype.format = function() {};" + "/** @constructor */ function Unrelated() {}" + "Unrelated.prototype.format = function() {};" + "/** @enum {?Formatter} */ var Enum = {\n" + " A: new Formatter(),\n" + " B: new Formatter()\n" + "};\n" + "function f() {\n" + " var formatter = window.toString() ? Enum.A : Enum.B;\n" + " formatter.format();\n" + "}"; String output = "" + "/** @constructor */ function Formatter() {}" + "Formatter.prototype.format = function() {};" + "/** @constructor */ function Unrelated() {}" + "Unrelated.prototype.format = function() {};" + "/** @enum {?Formatter} */ var Enum = {\n" + " A: new Formatter(),\n" + " B: new Formatter()\n" + "};\n" + "function f() {\n" + " var formatter = window.toString() ? Enum.A : Enum.B;\n" + " formatter.format();\n" + "}"; testSets(js, output, "{}"); } public void testEnumOfObjects3() { String js = "" + "/** @constructor */ function Formatter() {}" + "Formatter.prototype.format = function() {};" + "/** @constructor */ function Unrelated() {}" + "Unrelated.prototype.format = function() {};" + "/** @enum {!Formatter} */ var Enum = {\n" + " A: new Formatter(),\n" + " B: new Formatter()\n" + "};\n" + "/** @enum {!Enum} */ var SubEnum = {\n" + " C: Enum.A\n" + "};\n" + "function f() {\n" + " var formatter = SubEnum.C\n" + " formatter.format();\n" + "}"; String output = "" + "/** @constructor */ function Formatter() {}" + "Formatter.prototype.Formatter_prototype$format = function() {};" + "/** @constructor */ function Unrelated() {}" + "Unrelated.prototype.Unrelated_prototype$format = function() {};" + "/** @enum {!Formatter} */ var Enum = {\n" + " A: new Formatter(),\n" + " B: new Formatter()\n" + "};\n" + "/** @enum {!Enum} */ var SubEnum = {\n" + " C: Enum.A\n" + "};\n" + "function f() {\n" + " var formatter = SubEnum.C\n" + " formatter.Formatter_prototype$format();\n" + "}"; testSets(js, output, "{format=[[Formatter.prototype], [Unrelated.prototype]]}"); } public void testUntypedExterns() { String externs = "var untypedvar; untypedvar.alert = function() {x};"; String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "Foo.prototype.alert = 0;\n" + "Foo.prototype.untypedvar = 0;\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;\n" + "Bar.prototype.alert = 0;\n" + "Bar.prototype.untypedvar = 0;\n" + "untypedvar.alert();"; String output = "" + "/** @constructor */ function Foo(){}" + "Foo.prototype.Foo_prototype$a=0;" + "Foo.prototype.alert=0;" + "Foo.prototype.Foo_prototype$untypedvar=0;" + "/** @constructor */ function Bar(){}" + "Bar.prototype.Bar_prototype$a=0;" + "Bar.prototype.alert=0;" + "Bar.prototype.Bar_prototype$untypedvar=0;" + "untypedvar.alert();"; testSets(externs, js, output, "{a=[[Bar.prototype], [Foo.prototype]]" + ", untypedvar=[[Bar.prototype], [Foo.prototype]]}"); } public void testUnionTypeInvalidation() { String externs = "" + "/** @constructor */ function Baz() {}" + "Baz.prototype.a"; String js = "" + "/** @constructor */ function Ind() {this.a=0}\n" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;\n" + "var F = /** @type {Foo|Bar} */ (new Foo);\n" + "F.a = 1;\n" + "F = new Bar;\n" + "/** @type {Baz} */\n" + "var Z = new Baz;\n" + "Z.a = 1;\n" + "var B = /** @type {Bar|Baz} */ (new Baz);\n" + "B.a = 1;\n" + "B = new Bar;\n"; // Only the constructor field a of Ind is renamed, as Foo is related to Baz // through Bar in the unions Bar|Baz and Foo|Bar. String output = "" + "/** @constructor */ function Ind() { this.Ind$a = 0; }\n" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;\n" + "var F = /** @type {Foo|Bar} */ (new Foo);\n" + "F.a = 1;\n" + "F = new Bar;\n" + "/** @type {Baz} */\n" + "var Z = new Baz;\n" + "Z.a = 1;\n" + "var B = /** @type {Bar|Baz} */ (new Baz);" + "B.a = 1;" + "B = new Bar;"; testSets(externs, js, output, "{a=[[Ind]]}"); } public void testUnionAndExternTypes() { String externs = "" + "/** @constructor */ function Foo() { }" + "Foo.prototype.a = 4;\n"; String js = "" + "/** @constructor */ function Bar() { this.a = 2; }\n" + "/** @constructor */ function Baz() { this.a = 3; }\n" + "/** @constructor */ function Buz() { this.a = 4; }\n" + "/** @constructor */ function T1() { this.a = 3; }\n" + "/** @constructor */ function T2() { this.a = 3; }\n" + "/**\n" + " * @param {(Bar|Baz)} b\n" + " * @param {(Baz|Buz)} c\n" + " * @param {(Buz|Foo)} d\n" + " */\n" + "function f(b, c, d) {\n" + " b.a = 5; c.a = 6; d.a = 7;\n" + "}"; String output = "" + "/** @constructor */ function Bar() { this.a = 2; }\n" + "/** @constructor */ function Baz() { this.a = 3; }\n" + "/** @constructor */ function Buz() { this.a = 4; }\n" + "/** @constructor */ function T1() { this.T1$a = 3; }\n" + "/** @constructor */ function T2() { this.T2$a = 3; }\n" + "/**\n" + " * @param {Bar|Baz} b\n" + " * @param {Baz|Buz} c\n" + " * @param {Buz|Foo} d\n" + " */\n" + "function f(b, c, d) {\n" + " b.a = 5; c.a = 6; d.a = 7;\n" + "}"; // We are testing the skipping of multiple types caused by unionizing with // extern types. testSets(externs, js, output, "{a=[[T1], [T2]]}"); } public void testTypedExterns() { String externs = "" + "/** @constructor */ function Window() {};\n" + "Window.prototype.alert;" + "/** @type {Window} */" + "var window;"; String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.alert = 0;\n" + "window.alert('blarg');"; String output = "" + "/** @constructor */ function Foo(){}" + "Foo.prototype.Foo_prototype$alert=0;" + "window.alert('blarg');"; testSets(externs, js, output, "{alert=[[Foo.prototype]]}"); } public void testSubtypesWithSameField() { String js = LINE_JOINER.join( "/** @constructor */", "function Top() {}", "/** @constructor @extends Top */", "function Foo() {}", "Foo.prototype.a;", "/** @constructor @extends Top */", "function Bar() {}", "Bar.prototype.a;", "/** @param {Top} top */", "function foo(top) {", " var x = top.a;", "}", "foo(new Foo);", "foo(new Bar);"); testSets(js, "{}"); } public void testSupertypeReferenceOfSubtypeProperty() { String externs = "" + "/** @constructor */ function Ext() {}" + "Ext.prototype.a;"; String js = "" + "/** @constructor */ function Foo() {}\n" + "/** @constructor @extends {Foo} */ function Bar() {}\n" + "Bar.prototype.a;\n" + "/** @param {Foo} foo */" + "function foo(foo) {\n" + " var x = foo.a;\n" + "}\n"; String result = "" + "/** @constructor */ function Foo() {}\n" + "/** @constructor @extends {Foo} */ function Bar() {}\n" + "Bar.prototype.Bar_prototype$a;\n" + "/** @param {Foo} foo */\n" + "function foo(foo$jscomp$1) {\n" + " var x = foo$jscomp$1.Bar_prototype$a;\n" + "}\n"; testSets(externs, js, result, "{a=[[Bar.prototype]]}"); } public void testObjectLiteralNotRenamed() { String js = "" + "var F = {a:'a', b:'b'};" + "F.a = 'z';"; testSets(js, js, "{}"); } public void testObjectLiteralReflected() { String js = "" + "/** @const */ var goog = {};" + "goog.reflect = {};" + "goog.reflect.object = function(x, y) { return y; };" + "/** @constructor */ function F() {}" + "/** @type {number} */ F.prototype.foo = 3;" + "/** @constructor */ function G() {}" + "/** @type {number} */ G.prototype.foo = 3;" + "goog.reflect.object(F, {foo: 5});"; String result = "" + "/** @const */ var goog = {};" + "goog.reflect = {};" + "goog.reflect.object = function(x, y) { return y; };" + "/** @constructor */ function F() {}" + "/** @type {number} */ F.prototype.F_prototype$foo = 3;" + "/** @constructor */ function G() {}" + "/** @type {number} */ G.prototype.G_prototype$foo = 3;" + "goog.reflect.object(F, {F_prototype$foo: 5});"; testSets(js, result, "{foo=[[F.prototype], [G.prototype]]}"); } public void testObjectLiteralBlocksPropertiesOnOtherTypes() { String js = LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.myprop = 123;", "}", "var x = (new Foo).myprop;", "var y = { myprop: 'asdf' };"); testSets(js, js, "{}"); } public void testObjectLiteralDefineProperties() { String externs = LINE_JOINER.join( "Object.defineProperties = function(typeRef, definitions) {}", "/** @constructor */ function FooBar() {}", "/** @type {string} */ FooBar.prototype.bar_;", "/** @type {string} */ FooBar.prototype.bar;"); String js = LINE_JOINER.join( "/** @struct @constructor */ var Foo = function() {", " this.bar_ = 'bar';", "};", "/** @type {?} */ Foo.prototype.bar;", "Object.defineProperties(Foo.prototype, {", " bar: {", " configurable: true,", " enumerable: true,", " /** @this {Foo} */ get: function() { return this.bar_;},", " /** @this {Foo} */ set: function(value) { this.bar_ = value; }", " }", "});"); String result = LINE_JOINER.join( "/** @struct @constructor */ var Foo = function() {", " this.Foo$bar_ = 'bar';", "};", "/** @type {?} */ Foo.prototype.Foo_prototype$bar;", "Object.defineProperties(Foo.prototype, {", " Foo_prototype$bar: {", " configurable: true,", " enumerable: true,", " /** @this {Foo} */ get: function() { return this.Foo$bar_;},", " /** @this {Foo} */ set: function(value) { this.Foo$bar_ = value; }", " }", "});"); testSets(externs, js, result, "{bar=[[Foo.prototype]], bar_=[[Foo]]}"); } public void testObjectLiteralDefinePropertiesQuoted() { String externs = LINE_JOINER.join( "Object.defineProperties = function(typeRef, definitions) {}", "/** @constructor */ function FooBar() {}", "/** @type {string} */ FooBar.prototype.bar_;", "/** @type {string} */ FooBar.prototype.bar;"); String js = LINE_JOINER.join( "/** @struct @constructor */ var Foo = function() {", " this.bar_ = 'bar';", "};", "/** @type {?} */ Foo.prototype['bar'];", "Object.defineProperties(Foo.prototype, {", " 'bar': {", " configurable: true,", " enumerable: true,", " /** @this {Foo} */ get: function() { return this.bar_;},", " /** @this {Foo} */ set: function(value) { this.bar_ = value; }", " }", "});"); String result = LINE_JOINER.join( "/** @struct @constructor */ var Foo = function() {", " this.Foo$bar_ = 'bar';", "};", "/** @type {?} */ Foo.prototype['bar'];", "Object.defineProperties(Foo.prototype, {", " 'bar': {", " configurable: true,", " enumerable: true,", " /** @this {Foo} */ get: function() { return this.Foo$bar_;},", " /** @this {Foo} */ set: function(value) { this.Foo$bar_ = value; }", " }", "});"); testSets(externs, js, result, "{bar_=[[Foo]]}"); } public void testObjectLiteralLends() { String js = "" + "var mixin = function(x) { return x; };" + "/** @constructor */ function F() {}" + "/** @type {number} */ F.prototype.foo = 3;" + "/** @constructor */ function G() {}" + "/** @type {number} */ G.prototype.foo = 3;" + "mixin(/** @lends {F.prototype} */ ({foo: 5}));"; String result = "" + "var mixin = function(x) { return x; };" + "/** @constructor */ function F() {}" + "/** @type {number} */ F.prototype.F_prototype$foo = 3;" + "/** @constructor */ function G() {}" + "/** @type {number} */ G.prototype.G_prototype$foo = 3;" + "mixin(/** @lends {F.prototype} */ ({F_prototype$foo: 5}));"; testSets(js, result, "{foo=[[F.prototype], [G.prototype]]}"); } public void testClosureInherits() { String js = "" + "var goog = {};" + "/** @param {Function} childCtor Child class.\n" + " * @param {Function} parentCtor Parent class. */\n" + "goog.inherits = function(childCtor, parentCtor) {\n" + " /** @constructor */\n" + " function tempCtor() {};\n" + " tempCtor.prototype = parentCtor.prototype;\n" + " childCtor.superClass_ = parentCtor.prototype;\n" + " childCtor.prototype = new tempCtor();\n" + " childCtor.prototype.constructor = childCtor;\n" + "};" + "/** @constructor */ function Top() {}\n" + "Top.prototype.f = function() {};" + "/** @constructor \n@extends Top*/ function Foo() {}\n" + "goog.inherits(Foo, Top);\n" + "/** @override */\n" + "Foo.prototype.f = function() {" + " Foo.superClass_.f();" + "};\n" + "/** @constructor \n* @extends Foo */ function Bar() {}\n" + "goog.inherits(Bar, Foo);\n" + "/** @override */\n" + "Bar.prototype.f = function() {" + " Bar.superClass_.f();" + "};\n" + "(new Bar).f();\n"; testSets(js, "{f=[[Top.prototype]]}"); } public void testSkipNativeFunctionMethod() { String js = "" + "/** @constructor */ function Foo(){};" + "/** @constructor\n @extends Foo */" + "function Bar() { Foo.call(this); };"; // call should not be renamed testSame(js, null); } public void testSkipNativeObjectMethod() { String js = "" + "/** @constructor */ function Foo(){};" + "(new Foo).hasOwnProperty('x');"; testSets(js, js, "{}"); } public void testExtendNativeType() { String externs = "" + "/** @constructor \n @return {string} */" + "function Date(opt_1, opt_2, opt_3, opt_4, opt_5, opt_6, opt_7) {}" + "/** @override */ Date.prototype.toString = function() {}"; String js = "" + "/** @constructor\n @extends {Date} */ function SuperDate() {};\n" + "(new SuperDate).toString();"; testSets(externs, js, js, "{}"); } public void testStringFunction() { // Extern functions are not renamed, but user functions on a native // prototype object are. String js = "" + "/** @constructor */ function Foo() {};\n" + "Foo.prototype.foo = function() {};\n" + "String.prototype.foo = function() {};\n" + "var a = 'str'.toString().foo();\n"; String output = "" + "/** @constructor */ function Foo() {};\n" + "Foo.prototype.Foo_prototype$foo = function() {};\n" + "String.prototype.String_prototype$foo = function() {};\n" + "var a = 'str'.toString().String_prototype$foo();\n"; testSets(js, output, "{foo=[[Foo.prototype], [String.prototype]]}"); } public void testUnusedTypeInExterns() { String externs = "" + "/** @constructor */ function Foo() {};\n" + "Foo.prototype.a"; String js = "" + "/** @constructor */ function Bar() {};\n" + "Bar.prototype.a;" + "/** @constructor */ function Baz() {};\n" + "Baz.prototype.a;"; String output = "" + "/** @constructor */ function Bar() {};\n" + "Bar.prototype.Bar_prototype$a;" + "/** @constructor */ function Baz() {};\n" + "Baz.prototype.Baz_prototype$a"; testSets(externs, js, output, "{a=[[Bar.prototype], [Baz.prototype]]}"); } public void testInterface() { String js = "" + "/** @interface */ function I() {};\n" + "I.prototype.a;\n" + "/** @constructor \n @implements {I} */ function Foo() {};\n" + "Foo.prototype.a;\n" + "/** @type {I} */\n" + "var F = new Foo;" + "var x = F.a;"; testSets(js, "{a=[[Foo.prototype, I.prototype]]}"); } public void testInterface_noDirectImplementors() { String js = "" + "/** @interface */\n" + "function I() {}\n" + "I.prototype.a;\n" + "I.prototype.b;\n" + "/** @interface @extends {I} */\n" + "function J() {}\n" + "/** @constructor @implements {J} */\n" + "function Foo() {}\n" + "Foo.prototype.a;\n" + "Foo.prototype.b;\n" + "function f(/** !I */ x) {\n" + " return x.a;\n" + "}\n" + "/** @interface */\n" + "function Z() {}\n" + "Z.prototype.a;\n" + "Z.prototype.b;"; String output = "" + "/** @interface */\n" + "function I() {}\n" + "I.prototype.Foo_prototype$a;\n" + "I.prototype.Foo_prototype$b;\n" + "/** @interface @extends {I} */\n" + "function J() {}\n" + "/** @constructor @implements {J} */\n" + "function Foo() {}\n" + "Foo.prototype.Foo_prototype$a;\n" + "Foo.prototype.Foo_prototype$b;\n" + "function f(/** !I */ x){\n" + " return x.Foo_prototype$a;\n" + "}\n" + "/** @interface */\n" + "function Z() {}\n" + "Z.prototype.Z_prototype$a;\n" + "Z.prototype.Z_prototype$b;"; testSets( js, output, "{a=[[Foo.prototype, I.prototype], [Z.prototype]]," + " b=[[Foo.prototype, I.prototype], [Z.prototype]]}"); } public void testInterfaceOfSuperclass() { String js = "" + "/** @interface */ function I() {};\n" + "I.prototype.a;\n" + "/** @constructor \n @implements {I} */ function Foo() {};\n" + "Foo.prototype.a;\n" + "/** @constructor \n @extends Foo */ function Bar() {};\n" + "Bar.prototype.a;\n" + "/** @type {Bar} */\n" + "var B = new Bar;" + "B.a = 0"; testSets(js, "{a=[[Foo.prototype, I.prototype]]}"); } public void testInterfaceOfSuperclass2() { String js = LINE_JOINER.join( "/** @const */ var goog = {};", "goog.abstractMethod = function(var_args) {};", "/** @interface */ function I() {}", "I.prototype.a = function(x) {};", "/** @constructor @implements {I} */ function Foo() {}", "/** @override */ Foo.prototype.a = goog.abstractMethod;", "/** @constructor @extends Foo */ function Bar() {}", "/** @override */ Bar.prototype.a = function(x) {};"); testSets(js, "{a=[[Foo.prototype, I.prototype]]}"); } public void testTwoInterfacesWithSomeInheritance() { String js = "" + "/** @interface */ function I() {};\n" + "I.prototype.a;\n" + "/** @interface */ function I2() {};\n" + "I2.prototype.a;\n" + "/** @constructor \n @implements {I} */ function Foo() {};\n" + "Foo.prototype.a;\n" + "/** @constructor \n @extends {Foo} \n @implements {I2}*/\n" + "function Bar() {};\n" + "Bar.prototype.a;\n" + "/** @type {Bar} */\n" + "var B = new Bar;" + "B.a = 0"; testSets(js, "{a=[[Foo.prototype, I.prototype, I2.prototype]]}"); } public void testInvalidatingInterface() { String js = "" + "/** @interface */ function I2() {};\n" + "I2.prototype.a;\n" + "/** @constructor */ function Bar() {}\n" + "/** @type {I} */\n" + "var i = new Bar;\n" // Make I invalidating + "/** @constructor \n @implements {I} \n @implements {I2} */" + "function Foo() {};\n" + "/** @override */\n" + "Foo.prototype.a = 0;\n" + "(new Foo).a = 0;" + "/** @interface */ function I() {};\n" + "I.prototype.a;\n"; this.mode = TypeInferenceMode.OTI_ONLY; testSets(js, "{}", TypeValidator.TYPE_MISMATCH_WARNING); this.mode = TypeInferenceMode.NTI_ONLY; testSets(js, "{}", NewTypeInference.MISTYPED_ASSIGN_RHS); } public void testMultipleInterfaces() { String js = "" + "/** @interface */ function I() {};\n" + "/** @interface */ function I2() {};\n" + "I2.prototype.a;\n" + "/** @constructor \n @implements {I} \n @implements {I2} */" + "function Foo() {};\n" + "/** @override */" + "Foo.prototype.a = 0;\n" + "(new Foo).a = 0"; testSets(js, "{a=[[Foo.prototype, I2.prototype]]}"); } public void testInterfaceWithSupertypeImplementor() { String js = "" + "/** @interface */ function C() {}\n" + "C.prototype.foo = function() {};\n" + "/** @constructor */ function A (){}\n" + "A.prototype.foo = function() {};\n" + "/** @constructor \n @implements {C} \n @extends {A} */\n" + "function B() {}\n" + "/** @type {C} */ var b = new B();\n" + "b.foo();\n"; testSets(js, "{foo=[[A.prototype, C.prototype]]}"); } public void testSuperInterface() { String js = "" + "/** @interface */ function I() {};\n" + "I.prototype.a;\n" + "/** @interface \n @extends I */ function I2() {};\n" + "/** @constructor \n @implements {I2} */" + "function Foo() {};\n" + "/** @override */\n" + "Foo.prototype.a = 0;\n" + "(new Foo).a = 0"; testSets(js, "{a=[[Foo.prototype, I.prototype]]}"); } public void testSuperInterface2() { String js = LINE_JOINER.join( "/** @interface */", "function High(){}", "High.prototype.prop = function() {};", "/**", " * @interface", " * @extends {High}", " */", "function Low() {}", "Low.prototype.prop = function() {};", "/**", " * @constructor", " * @implements {Low}", " */", "function A() {}", "A.prototype.prop = function() {};"); testSets(js, js, "{prop=[[A.prototype, High.prototype, Low.prototype]]}"); } public void testSuperInterface3() { testSets( LINE_JOINER.join( "/** @interface */", "function I0() {}", "I0.prototype.prop = function() {};", "/** @interface */", "function I1() {}", "I1.prototype.prop = function() {};", "/** @interface */", "function I2() {}", "I2.prototype.prop = function() {};", "/**", " * @interface", " * @extends {I1}", " * @extends {I2}", " */", "function Mixin() {}", "/**", " * @constructor", " * @implements {Mixin}", " */", "function C() {}", "C.prototype.prop = function() {};", "/**", " * @constructor", " * @implements {I1}", " */", "function D() {}", "D.prototype.prop = function() {};"), LINE_JOINER.join( "/** @interface */", "function I0() {}", "I0.prototype.I0_prototype$prop = function() {};", "/** @interface */", "function I1() {}", "I1.prototype.C_prototype$prop = function() {};", "/** @interface */", "function I2() {}", "I2.prototype.C_prototype$prop = function() {};", "/**", " * @interface", " * @extends {I1}", " * @extends {I2}", " */", "function Mixin() {}", "/**", " * @constructor", " * @implements {Mixin}", " */", "function C() {}", "C.prototype.C_prototype$prop = function() {};", "/**", " * @constructor", " * @implements {I1}", " */", "function D() {}", "D.prototype.C_prototype$prop = function() {};"), "{prop=[[C.prototype, D.prototype, I1.prototype, I2.prototype], [I0.prototype]]}"); } public void testInterfaceUnionWithCtor() { String js = "" + "/** @interface */ function I() {};\n" + "/** @type {!Function} */ I.prototype.addEventListener;\n" + "/** @constructor \n * @implements {I} */ function Impl() {};\n" + "/** @type {!Function} */ Impl.prototype.addEventListener;" + "/** @constructor */ function C() {};\n" + "/** @type {!Function} */ C.prototype.addEventListener;" + "/** @param {C|I} x */" + "function f(x) { x.addEventListener(); };\n" + "f(new C()); f(new Impl());"; testSets(js, js, "{addEventListener=[[C.prototype, I.prototype, Impl.prototype]]}"); } public void testExternInterfaceUnionWithCtor() { String externs = "" + "/** @interface */ function I() {};\n" + "/** @type {!Function} */ I.prototype.addEventListener;\n" + "/** @constructor \n * @implements {I} */ function Impl() {};\n" + "/** @type {!Function} */ Impl.prototype.addEventListener;"; String js = "" + "/** @constructor */ function C() {};\n" + "/** @type {!Function} */ C.prototype.addEventListener;" + "/** @param {C|I} x */" + "function f(x) { x.addEventListener(); };\n" + "f(new C()); f(new Impl());"; testSets(externs, js, js, "{}"); } public void testAliasedTypeIsNotDisambiguated() { String js = LINE_JOINER.join( "/** @return {SecondAlias} */", "function f() { return new Second; }", "function g() { f().blah; }", "", "/** @constructor */", "function Second() {", " /** @type {number} */", " this.blah = 5;", "};", "var /** @const */ SecondAlias = Second;"); testSets(js, js, "{blah=[[Second]]}"); } public void testConstructorsWithTypeErrorsAreNotDisambiguated() { String js = LINE_JOINER.join( "/** @constructor */", "function Foo(){}", "Foo.prototype.alias = function() {};", "", "/** @constructor */", "function Bar(){};", "/** @return {void} */", "Bar.prototype.alias;", "", "Bar = Foo;", "", "(new Bar()).alias();"); this.mode = TypeInferenceMode.OTI_ONLY; testSets("", js, js, "{}", TypeValidator.TYPE_MISMATCH_WARNING, "assignment\n" + "found : function (new:Foo): undefined\n" + "required: function (new:Bar): undefined"); this.mode = TypeInferenceMode.NTI_ONLY; testSets("", js, js, "{}", NewTypeInference.MISTYPED_ASSIGN_RHS, LINE_JOINER.join( "The right side in the assignment is not a subtype of the left side.", "Expected : Bar<|function(new:Bar):?|>", "Found : Foo<|function(new:Foo):undefined|>", "More details:", "Incompatible types for property prototype.", "Expected : Bar.prototype", "Found : Foo.prototype")); } public void testStructuralTypingWithDisambiguatePropertyRenaming1() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "function f(/** I */ i) { return i.x; }"); // In this case, I.prototype.x and Bar.prototype.x could be the // same property since Bar <: I (under structural interface matching). // If there is no code that uses a Bar as an I, however, then we // will consider the two types distinct and disambiguate the properties // with different names. String output = LINE_JOINER.join( "/** @record */", "function I(){}/** @type {number} */I.prototype.Foo_prototype$x;", "/** @constructor @implements {I} */", "function Foo(){}/** @type {number} */Foo.prototype.Foo_prototype$x;", "/** @constructor */", "function Bar(){}/** @type {number} */Bar.prototype.Bar_prototype$x;", "function f(/** I */ i){return i.Foo_prototype$x}"); testSets(js, output, "{x=[[Bar.prototype], [Foo.prototype, I.prototype]]}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming1_1() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "function f(/** I */ i) { return i.x; }", "f(new Bar());"); testSets(js, js, "{}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming1_2() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "function f(/** I */ i) { return i.x; }", "f({x:5});"); testSets(js, js, "{}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming1_3() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "function f(/** I */ i) { return i.x; }", "function g(/** {x:number} */ i) { return f(i); }", "g(new Bar());"); testSets(js, js, "{}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming1_4() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "function f(/** !I */ i) { return i.x; }", "function g(/** {x:number} */ i) { return f(i); }", "g(new Bar());"); testSets(js, js, "{}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming1_5() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "function g(/** I */ i) { return f.x; }", "var /** I */ i = new Bar();", "g(i);"); testSets(js, js, "{}"); } /** * a test case where registerMismatch registers a strict mismatch * but not a regular mismatch. */ public void testStructuralTypingWithDisambiguatePropertyRenaming1_6() throws Exception { String js = LINE_JOINER.join( "/** @record */ function I() {}", "/** @type {!Function} */ I.prototype.addEventListener;", "/** @constructor */ function C() {}", "/** @type {!Function} */ C.prototype.addEventListener;", "/** @param {I} x */", "function f(x) { x.addEventListener(); }", "f(new C());"); testSets(js, js, "{}"); } /** * a test case where registerMismatch registers a strict mismatch * but not a regular mismatch. */ public void testStructuralTypingWithDisambiguatePropertyRenaming1_7() throws Exception { String js = LINE_JOINER.join( "/** @record */ function I() {}", "/** @type {!Function} */ I.prototype.addEventListener;", "/** @constructor */ function C() {}", "/** @type {!Function} */ C.prototype.addEventListener;", "/** @type {I} */ var x;", "x = new C()"); testSets(js, js, "{}"); } public void testDisambiguatePropertiesClassCastedToUnrelatedInterface() { String js = LINE_JOINER.join( "/** @interface */", "function Foo() {}", "Foo.prototype.prop1;", "Foo.prototype.prop2;", "/** @constructor */", "function Bar() {", " this.prop1 = 123;", "}", "var x = /** @type {!Foo} */ (new Bar);", "/** @constructor */", "function Baz() {", " this.prop1 = 123;", "}"); testSets(js, js, "{}"); } public void testDontInvalidateForGenericsMismatch() { String js = LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {", " this.prop = 123;", "}", "/** @param {!Foo<number>} x */", "function f(x) {", " return (/** @type {!Foo<string>} */ (x)).prop;", "}", "/** @constructor */", "function Bar() {", " this.prop = 123;", "}"); String output = LINE_JOINER.join( "/**", " * @constructor", " * @template T", " */", "function Foo() {", " this.Foo$prop = 123;", "}", "/** @param {!Foo<number>} x */", "function f(x) {", " return (/** @type {!Foo<string>} */ (x)).Foo$prop;", "}", "/** @constructor */", "function Bar() {", " this.Bar$prop = 123;", "}"); this.mode = TypeInferenceMode.OTI_ONLY; testSets(js, output, "{prop=[[Bar], [Foo]]}"); this.mode = TypeInferenceMode.NTI_ONLY; testSets("", js, output, "{prop=[[Bar], [Foo]]}", NewTypeInference.INVALID_CAST, LINE_JOINER.join( "invalid cast - the types do not have a common subtype", "from: Foo<number>", "to : Foo<string>")); } public void testStructuralTypingWithDisambiguatePropertyRenaming2() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "/** @param {Foo|Bar} i */", "function f(i) { return i.x; }"); testSets(js, js, "{x=[[Bar.prototype, Foo.prototype, I.prototype]]}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming3() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "/** @param {I} i */", "function f(i) { return i.x; }", "f(new Bar());"); testSets(js, js, "{}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming3_1() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */\n" + "function Foo(){}\n" + "/** @type {number} */\n" + "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "/** @param {(I|Bar)} i */", "function f(i) { return i.x; }", "f(new Bar());"); testSets(js, js, "{x=[[Bar.prototype, Foo.prototype, I.prototype]]}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming4() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "/** @param {Foo|I} i */", "function f(i) { return i.x; }"); String output = LINE_JOINER.join( "/** @record */", "function I(){}/** @type {number} */I.prototype.Foo_prototype$x;", "/** @constructor @implements {I} */", "function Foo(){}/** @type {number} */Foo.prototype.Foo_prototype$x;", "/** @constructor */", "function Bar(){}/** @type {number} */Bar.prototype.Bar_prototype$x;", "/** @param {Foo|I} i */", "function f(i){return i.Foo_prototype$x}"); testSets(js, output, "{x=[[Bar.prototype], [Foo.prototype, I.prototype]]}"); } public void testStructuralTypingWithDisambiguatePropertyRenaming5() { String js = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.x;", "", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.x;", "", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.x;", "", "function f(/** Bar */ i) { return i.x; }"); String output = LINE_JOINER.join( "/** @record */", "function I(){}", "/** @type {number} */", "I.prototype.Foo_prototype$x;", "/** @constructor @implements {I} */", "function Foo(){}", "/** @type {number} */", "Foo.prototype.Foo_prototype$x;", "/** @constructor */", "function Bar(){}", "/** @type {number} */", "Bar.prototype.Bar_prototype$x;", "function f(/** Bar */ i){return i.Bar_prototype$x}"); testSets(js, output, "{x=[[Bar.prototype], [Foo.prototype, I.prototype]]}"); } /** * Tests that the type based version skips renaming on types that have a * mismatch, and the type tightened version continues to work as normal. */ public void testMismatchInvalidation() { String js = "" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a = 0;\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a = 0;\n" + "/** @type {Foo} */\n" + "var F = new Bar;\n" + "F.a = 0;"; this.mode = TypeInferenceMode.OTI_ONLY; testSets("", js, js, "{}", TypeValidator.TYPE_MISMATCH_WARNING, LINE_JOINER.join( "initializing variable", "found : Bar", "required: (Foo|null)")); this.mode = TypeInferenceMode.NTI_ONLY; testSets("", js, js, "{}", NewTypeInference.MISTYPED_ASSIGN_RHS, LINE_JOINER.join( "The right side in the assignment is not a subtype of the left side.", "Expected : Foo|null", "Found : Bar\n")); } public void testBadCast() { String js = "/** @constructor */ function Foo() {};\n" + "Foo.prototype.a = 0;\n" + "/** @constructor */ function Bar() {};\n" + "Bar.prototype.a = 0;\n" + "var a = /** @type {!Foo} */ (new Bar);\n" + "a.a = 4;"; this.mode = TypeInferenceMode.OTI_ONLY; testSets("", js, js, "{}", TypeValidator.INVALID_CAST, "invalid cast - must be a subtype or supertype\n" + "from: Bar\n" + "to : Foo"); this.mode = TypeInferenceMode.NTI_ONLY; testSets("", js, js, "{}", NewTypeInference.INVALID_CAST, "invalid cast - the types do not have a common subtype\n" + "from: Bar\n" + "to : Foo"); } public void testDeterministicNaming() { String js = "/** @constructor */function A() {}\n" + "/** @return {string} */A.prototype.f = function() {return 'a';};\n" + "/** @constructor */function B() {}\n" + "/** @return {string} */B.prototype.f = function() {return 'b';};\n" + "/** @constructor */function C() {}\n" + "/** @return {string} */C.prototype.f = function() {return 'c';};\n" + "/** @type {A|B} */var ab = 1 ? new B : new A;\n" + "/** @type {string} */var n = ab.f();\n"; String output = "/** @constructor */ function A() {}\n" + "/** @return {string} */ A.prototype.A_prototype$f = function() { return'a'; };\n" + "/** @constructor */ function B() {}\n" + "/** @return {string} */ B.prototype.A_prototype$f = function() { return'b'; };\n" + "/** @constructor */ function C() {}\n" + "/** @return {string} */ C.prototype.C_prototype$f = function() { return'c'; };\n" + "/** @type {A|B} */ var ab = 1 ? new B : new A;\n" + "/** @type {string} */ var n = ab.A_prototype$f();\n"; for (int i = 0; i < 5; i++) { testSets(js, output, "{f=[[A.prototype, B.prototype], [C.prototype]]}"); } } public void testObjectLiteral() { String js = "/** @constructor */ function Foo() {}\n" + "Foo.prototype.a;\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.a;\n" + "var F = /** @type {Foo} */({ a: 'a' });\n"; String output = "/** @constructor */ function Foo() {}\n" + "Foo.prototype.Foo_prototype$a;\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.Bar_prototype$a;\n" + "var F = /** @type {Foo} */ ({ Foo_prototype$a: 'a' });"; this.mode = TypeInferenceMode.OTI_ONLY; testSets(js, output, "{a=[[Bar.prototype], [Foo.prototype]]}"); this.mode = TypeInferenceMode.NTI_ONLY; testSets(js, "{}", NewTypeInference.INVALID_CAST); } public void testCustomInherits() { String js = "Object.prototype.inheritsFrom = function(shuper) {\n" + " /** @constructor */\n" + " function Inheriter() { }\n" + " Inheriter.prototype = shuper.prototype;\n" + " this.prototype = new Inheriter();\n" + " this.superConstructor = shuper;\n" + "};\n" + "function Foo(var1, var2, strength) {\n" + " Foo.superConstructor.call(this, strength);\n" + "}" + "Foo.inheritsFrom(Object);"; testSets(js, js, "{}"); } public void testSkipNativeFunctionStaticProperty() { String js = "" + "/** @param {!Function} ctor */\n" + "function addSingletonGetter(ctor) { ctor.a; }\n" + "/** @constructor */ function Foo() {}\n" + "Foo.a = 0;" + "/** @constructor */ function Bar() {}\n" + "Bar.a = 0;"; String output = "" + "/** @param {!Function} ctor */" + "function addSingletonGetter(ctor){ctor.a}" + "/** @constructor */ function Foo(){}" + "Foo.a=0;" + "/** @constructor */ function Bar(){}" + "Bar.a=0"; testSets(js, output, "{}"); } public void testStructuralInterfacesInExterns() { String externs = LINE_JOINER.join( "/** @record */", "var I = function() {};", "/** @return {string} */", "I.prototype.baz = function() {};"); String js = LINE_JOINER.join( "/** @constructor */", "function Bar() {}", "Bar.prototype.baz = function() { return ''; };", "", "/** @constructor */", "function Foo() {}", "Foo.prototype.baz = function() { return ''; };"); testSets(externs, js, js, "{}"); } public void testPropInParentInterface1() { String js = LINE_JOINER.join( "/** @interface */", "function MyIterable() {}", "MyIterable.prototype.iterator = function() {};", "/**", " * @interface", " * @extends {MyIterable}", " * @template T", " */", "function MyCollection() {}", "/**", " * @constructor", " * @implements {MyCollection<?>}", " */", "function MyAbstractCollection() {}", "/** @override */", "MyAbstractCollection.prototype.iterator = function() {};"); testSets(js, "{iterator=[[MyAbstractCollection.prototype, MyIterable.prototype]]}"); } public void testPropInParentInterface2() { String js = LINE_JOINER.join( "/** @interface */", "function MyIterable() {}", "MyIterable.prototype.iterator = function() {};", "/**", " * @interface", " * @extends {MyIterable}", " */", "function MyCollection() {}", "/**", " * @constructor", " * @implements {MyCollection<?>}", " */", "function MyAbstractCollection() {}", "/** @override */", "MyAbstractCollection.prototype.iterator = function() {};"); testSets(js, "{iterator=[[MyAbstractCollection.prototype, MyIterable.prototype]]}"); } public void testPropInParentInterface3() { String js = LINE_JOINER.join( "/** @interface */", "function MyIterable() {}", "MyIterable.prototype.iterator = function() {};", "/**", " * @interface", " * @extends {MyIterable}", " */", "function MyCollection() {}", "/**", " * @constructor", " * @implements {MyCollection}", " */", "function MyAbstractCollection() {}", "/** @override */", "MyAbstractCollection.prototype.iterator = function() {};"); String output = LINE_JOINER.join( "/** @interface */", "function MyIterable() {}", "MyIterable.prototype.MyAbstractCollection_prototype$iterator = function() {};", "/**", " * @interface", " * @extends {MyIterable}", " */", "function MyCollection() {}", "/**", " * @constructor", " * @implements {MyCollection}", " */", "function MyAbstractCollection() {}", "/** @override */", "MyAbstractCollection.prototype.MyAbstractCollection_prototype$iterator = function() {};"); testSets(js, output, "{iterator=[[MyAbstractCollection.prototype, MyIterable.prototype]]}"); } // In function subtyping, the type of THIS should be contravariant, like the argument types. // Because it's not, we get wrong disambiguation. // On top of that, this can happen in OTI when types are joined during generics instantiation. // Just documenting the behavior here. public void testUnsafeTypingOfThis() { String js = LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.myprop = 123;", "}", "Foo.prototype.method = function() { this.myprop++; };", "/** @constructor */", "function Bar() {", " this.myprop = 123;", "}", "/**", " * @param {function(this:T)} callback", " * @param {T} thisobj", " * @template T", " */", "function myArrayPrototypeMap(callback, thisobj) {", " callback.call(thisobj);", "}", "myArrayPrototypeMap(Foo.prototype.method, new Bar);"); String output = LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.Foo$myprop = 123;", "}", "Foo.prototype.method = function() { this.Foo$myprop++; };", "/** @constructor */", "function Bar() {", " this.Bar$myprop = 123;", "}", "/**", " * @param {function(this:T)} callback", " * @param {T} thisobj", " * @template T", " */", "function myArrayPrototypeMap(callback, thisobj) {", " callback.call(thisobj);", "}", "myArrayPrototypeMap(Foo.prototype.method, new Bar);"); this.mode = TypeInferenceMode.OTI_ONLY; testSets(js, output, "{method=[[Foo.prototype]], myprop=[[Bar], [Foo]]}"); this.mode = TypeInferenceMode.NTI_ONLY; testSets("", js, output, "{method=[[Foo.prototype]], myprop=[[Bar], [Foo]]}", NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function myArrayPrototypeMap.", "Expected : function(this:Bar|Foo):?", "Found : function(this:Foo):?\n")); js = LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.myprop = 123;", "}", "Foo.prototype.method = function() { this.myprop++; };", "/** @constructor */", "function Bar() {", " this.myprop = 123;", "}", "/** @param {function(this:(!Foo|!Bar))} callback */", "function f(callback) {", " callback.call(new Bar);", "}", "f(Foo.prototype.method);"); output = LINE_JOINER.join( "/** @constructor */", "function Foo() {", " this.Foo$myprop = 123;", "}", "Foo.prototype.method = function() { this.Foo$myprop++; };", "/** @constructor */", "function Bar() {", " this.Bar$myprop = 123;", "}", "/** @param {function(this:(!Foo|!Bar))} callback */", "function f(callback) {", " callback.call(new Bar);", "}", "f(Foo.prototype.method);"); testSets("", js, output, "{method=[[Foo.prototype]], myprop=[[Bar], [Foo]]}", NewTypeInference.INVALID_ARGUMENT_TYPE, LINE_JOINER.join( "Invalid type for parameter 1 of function f.", "Expected : function(this:Bar|Foo):?", "Found : function(this:Foo):?\n")); } public void testErrorOnProtectedProperty() { testError("function addSingletonGetter(foo) { foo.foobar = 'a'; };", DisambiguateProperties.Warnings.INVALIDATION); assertThat(getLastCompiler().getErrors()[0].toString()).contains("foobar"); } public void testMismatchForbiddenInvalidation() { testError( "/** @constructor */ function F() {}" + "/** @type {number} */ F.prototype.foobar = 3;" + "/** @return {number} */ function g() { return new F(); }", DisambiguateProperties.Warnings.INVALIDATION); assertThat(getLastCompiler().getErrors()[0].toString()).contains("Consider fixing errors"); } public void testUnionTypeInvalidationError() { String externs = "" + "/** @constructor */ function Baz() {}" + "Baz.prototype.foobar"; String js = "" + "/** @constructor */ function Ind() {this.foobar=0}\n" + "/** @constructor */ function Foo() {}\n" + "Foo.prototype.foobar = 0;\n" + "/** @constructor */ function Bar() {}\n" + "Bar.prototype.foobar = 0;\n" + "/** @type {Foo|Bar} */\n" + "var F = new Foo;\n" + "F.foobar = 1\n;" + "F = new Bar;\n" + "/** @type {Baz} */\n" + "var Z = new Baz;\n" + "Z.foobar = 1\n;"; test( DEFAULT_EXTERNS + externs, js, (String) null, DisambiguateProperties.Warnings.INVALIDATION_ON_TYPE, null); assertThat(getLastCompiler().getErrors()[0].toString()).contains("foobar"); } public void testDontCrashOnNonConstructorsWithPrototype() { String externs = LINE_JOINER.join( "function f(x) { return x; }", "f.prototype.method = function() {};"); test(DEFAULT_EXTERNS + externs, "" , "", null, null); } private void testSets(String js, String expected, String fieldTypes) { test(js, expected); assertEquals(fieldTypes, mapToString(lastPass.getRenamedTypesForTesting())); } private void testSets(String externs, String js, String expected, String fieldTypes) { testSets(externs, js, expected, fieldTypes, null, null); } private void testSets(String externs, String js, String expected, String fieldTypes, DiagnosticType warning, String description) { test(DEFAULT_EXTERNS + externs, js, expected, null, warning, description); assertEquals(fieldTypes, mapToString(lastPass.getRenamedTypesForTesting())); } /** * Compiles the code and checks that the set of types for each field matches * the expected value. * * <p>The format for the set of types for fields is: * {field=[[Type1, Type2]]} */ private void testSets(String js, String fieldTypes) { test(js, null, null, null); assertEquals(fieldTypes, mapToString(lastPass.getRenamedTypesForTesting())); } /** * Compiles the code and checks that the set of types for each field matches * the expected value. * * <p>The format for the set of types for fields is: * {field=[[Type1, Type2]]} */ private void testSets(String js, String fieldTypes, DiagnosticType warning) { testWarning(js, warning); assertEquals(fieldTypes, mapToString(lastPass.getRenamedTypesForTesting())); } /** Sorts the map and converts to a string for comparison purposes. */ private <T> String mapToString(Multimap<String, Collection<T>> map) { TreeMap<String, String> retMap = new TreeMap<>(); for (String key : map.keySet()) { TreeSet<String> treeSet = new TreeSet<>(); for (Collection<T> collection : map.get(key)) { Set<String> subSet = new TreeSet<>(); for (T type : collection) { subSet.add(type.toString()); } treeSet.add(subSet.toString()); } retMap.put(key, treeSet.toString()); } return retMap.toString(); } }