/* * 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 com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.rhino.Node; import java.util.HashMap; import java.util.Map; /** * Unit test for AmbiguateProperties Compiler pass. * */ public final class AmbiguatePropertiesTest extends CompilerTestCase { private AmbiguateProperties lastPass; private static final String EXTERNS = LINE_JOINER.join( "Function.prototype.call=function(){};", "Function.prototype.inherits=function(){};", "/** @const */ var Object = {};", "Object.defineProperties = function(typeRef, definitions) {};", "prop.toString;", "var google = { gears: { factory: {}, workerPool: {} } };"); public AmbiguatePropertiesTest() { super(EXTERNS); enableNormalize(); enableTypeCheck(); enableClosurePass(); enableGatherExternProperties(); } @Override protected CompilerPass getProcessor(final Compiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node root) { lastPass = AmbiguateProperties.makePassForTesting(compiler, new char[] {'$'}, new char[] {'$'}); lastPass.process(externs, root); } }; } @Override protected int getNumRepetitions() { return 1; } @Override protected CompilerOptions getOptions() { // no missing properties check CompilerOptions options = new CompilerOptions(); options.setLanguageIn(LanguageMode.ECMASCRIPT5); return options; } public void testOneVar1() { test("/** @constructor */ var Foo = function(){};Foo.prototype.b = 0;", "/** @constructor */ var Foo = function(){};Foo.prototype.a = 0;"); } public void testOneVar2() { test(LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {b: 0};"), LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {a: 0};")); } public void testOneVar3() { test( LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {get b() {return 0}};"), LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {get a() {return 0}};")); } public void testOneVar4() { test( LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {set b(a) {}};"), LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {set a(a) {}};")); } public void testTwoVar1() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.z=0;", "Foo.prototype.z=0;", "Foo.prototype.x=0;"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.a=0;", "Foo.prototype.a=0;", "Foo.prototype.b=0;"); test(js, output); } public void testTwoVar2() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {z:0, z:1, x:0};"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {a:0, a:1, b:0};"); test(js, output); } public void testTwoIndependentVar() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.b = 0;", "/** @constructor */ var Bar = function(){};", "Bar.prototype.c = 0;"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.a=0;", "/** @constructor */ var Bar = function(){};", "Bar.prototype.a=0;"); test(js, output); } public void testTwoTypesTwoVar() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.r = 0;", "Foo.prototype.g = 0;", "/** @constructor */ var Bar = function(){};", "Bar.prototype.c = 0;", "Bar.prototype.r = 0;"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.a=0;", "Foo.prototype.b=0;", "/** @constructor */ var Bar = function(){};", "Bar.prototype.b=0;", "Bar.prototype.a=0;"); test(js, output); } public void testUnion() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @constructor */ var Bar = function(){};", "Foo.prototype.foodoo=0;", "Bar.prototype.bardoo=0;", "/** @type {Foo|Bar} */", "var U;", "U.joint;", "U.joint"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @constructor */ var Bar = function(){};", "Foo.prototype.b=0;", "Bar.prototype.b=0;", "/** @type {Foo|Bar} */", "var U;", "U.a;", "U.a"); test(js, output); } public void testUnions() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @constructor */ var Bar = function(){};", "/** @constructor */ var Baz = function(){};", "/** @constructor */ var Bat = function(){};", "Foo.prototype.lone1=0;", "Bar.prototype.lone2=0;", "Baz.prototype.lone3=0;", "Bat.prototype.lone4=0;", "/** @type {Foo|Bar} */", "var U1;", "U1.j1;", "U1.j2;", "/** @type {Baz|Bar} */", "var U2;", "U2.j3;", "U2.j4;", "/** @type {Baz|Bat} */", "var U3;", "U3.j5;", "U3.j6"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @constructor */ var Bar = function(){};", "/** @constructor */ var Baz = function(){};", "/** @constructor */ var Bat = function(){};", "Foo.prototype.c=0;", "Bar.prototype.e=0;", "Baz.prototype.e=0;", "Bat.prototype.c=0;", "/** @type {Foo|Bar} */", "var U1;", "U1.a;", "U1.b;", "/** @type {Baz|Bar} */", "var U2;", "U2.c;", "U2.d;", "/** @type {Baz|Bat} */", "var U3;", "U3.a;", "U3.b"); test(js, output); } public void testExtends() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.x=0;", "/** @constructor \n @extends Foo */ var Bar = function(){};", "goog.inherits(Bar, Foo);", "Bar.prototype.y=0;", "Bar.prototype.z=0;", "/** @constructor */ var Baz = function(){};", "Baz.prototype.l=0;", "Baz.prototype.m=0;", "Baz.prototype.n=0;", "(new Baz).m\n"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.a=0;", "/** @constructor \n @extends Foo */ var Bar = function(){};", "goog.inherits(Bar, Foo);", "Bar.prototype.b=0;", "Bar.prototype.c=0;", "/** @constructor */ var Baz = function(){};", "Baz.prototype.b=0;", "Baz.prototype.a=0;", "Baz.prototype.c=0;", "(new Baz).a\n"); test(js, output); } public void testLotsOfVars() { StringBuilder js = new StringBuilder(); StringBuilder output = new StringBuilder(); js.append("/** @constructor */ var Foo = function(){};\n"); js.append("/** @constructor */ var Bar = function(){};\n"); output.append(js); int vars = 10; for (int i = 0; i < vars; i++) { js.append("Foo.prototype.var").append(i).append(" = 0;"); js.append("Bar.prototype.var").append(i + 10000).append(" = 0;"); output.append("Foo.prototype.").append((char) ('a' + i)).append("=0;"); output.append("Bar.prototype.").append((char) ('a' + i)).append("=0;"); } test(js.toString(), output.toString()); } public void testLotsOfClasses() { StringBuilder b = new StringBuilder(); int classes = 10; for (int i = 0; i < classes; i++) { String c = "Foo" + i; b.append("/** @constructor */ var ").append(c).append(" = function(){};\n"); b.append(c).append(".prototype.varness").append(i).append(" = 0;"); } String js = b.toString(); test(js, js.replaceAll("varness\\d+", "a")); } public void testFunctionType() { String js = LINE_JOINER.join( "/** @constructor */ function Foo(){};", "/** @return {Bar} */", "Foo.prototype.fun = function() { return new Bar(); };", "/** @constructor */ function Bar(){};", "Bar.prototype.bazz;", "(new Foo).fun().bazz();"); String output = LINE_JOINER.join( "/** @constructor */ function Foo(){};", "/** @return {Bar} */", "Foo.prototype.a = function() { return new Bar(); };", "/** @constructor */ function Bar(){};", "Bar.prototype.a;", "(new Foo).a().a();"); test(js, output); } public void testPrototypePropertiesAsObjLitKeys1() { test("/** @constructor */ function Bar() {};" + "Bar.prototype = {2: function(){}, getA: function(){}};", "/** @constructor */ function Bar() {};" + "Bar.prototype = {2: function(){}, a: function(){}};"); } public void testPrototypePropertiesAsObjLitKeys2() { testSame("/** @constructor */ function Bar() {};" + "Bar.prototype = {2: function(){}, 'getA': function(){}};"); } public void testQuotedPrototypeProperty() { testSame("/** @constructor */ function Bar() {};" + "Bar.prototype['getA'] = function(){};" + "var bar = new Bar();" + "bar['getA']();"); } public void testObjectDefineProperties() { String js = LINE_JOINER.join( "/** @constructor */ var Bar = function(){};", "Bar.prototype.bar = 0;", "/** @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( "/** @constructor */ var Bar = function(){};", "Bar.prototype.a = 0;", "/** @struct @constructor */ var Foo = function() {", " this.b = 'bar';", "};", "/** @type {?} */ Foo.prototype.a;", "Object.defineProperties(Foo.prototype, {", " a: {", " configurable: true,", " enumerable: true,", " /** @this {Foo} */ get: function() { return this.b;},", " /** @this {Foo} */ set: function(value) { this.b = value; }", " }", "});"); test(js, result); } public void testObjectDefinePropertiesQuoted() { String js = LINE_JOINER.join( "/** @constructor */ var Bar = function(){};", "Bar.prototype.bar = 0;", "/** @struct @constructor */ var Foo = function() {", " this.bar_ = 'bar';", "};", "/** @type {?} */ Foo.prototype['bar'];", "Object.defineProperties(Foo.prototype, {", " 'a': {", " configurable: true,", " enumerable: true,", " /** @this {Foo} */ get: function() { return this.bar_;},", " /** @this {Foo} */ set: function(value) { this.bar_ = value; }", " }", "});"); String result = LINE_JOINER.join( "/** @constructor */ var Bar = function(){};", "Bar.prototype.b = 0;", "/** @struct @constructor */ var Foo = function() {", " this.b = 'bar';", "};", "/** @type {?} */ Foo.prototype['bar'];", "Object.defineProperties(Foo.prototype, {", " 'a': {", " configurable: true,", " enumerable: true,", " /** @this {Foo} */ get: function() { return this.b;},", " /** @this {Foo} */ set: function(value) { this.b = value; }", " }", "});"); test(js, result); } public void testOverlappingOriginalAndGeneratedNames() { test( LINE_JOINER.join( "/** @constructor */ function Bar(){};", "Bar.prototype.b = function(){};", "Bar.prototype.a = function(){};", "var bar = new Bar();", "bar.b();"), LINE_JOINER.join( "/** @constructor */ function Bar(){};", "Bar.prototype.a = function(){};", "Bar.prototype.b = function(){};", "var bar = new Bar();", "bar.a();")); } public void testPropertyAddedToObject() { testSame("var foo = {}; foo.prop = '';"); } public void testPropertyAddedToFunction() { test("var foo = function(){}; foo.prop = '';", "var foo = function(){}; foo.a = '';"); } public void testPropertyOfObjectOfUnknownType() { testSame("var foo = x(); foo.prop = '';"); } public void testPropertyOnParamOfUnknownType() { testSame( LINE_JOINER.join( "/** @constructor */ function Foo(){};", "Foo.prototype.prop = 0;", "function go(aFoo){", " aFoo.prop = 1;", "}")); } public void testSetPropertyOfGlobalThis() { testSame("this.prop = 'bar'"); } public void testReadPropertyOfGlobalThis() { testSame("Object.prototype.prop;", "f(this.prop);", null); } public void testSetQuotedPropertyOfThis() { testSame("this['prop'] = 'bar';"); } public void testExternedPropertyName() { test( LINE_JOINER.join( "/** @constructor */ var Bar = function(){};", "/** @override */ Bar.prototype.toString = function(){};", "Bar.prototype.func = function(){};", "var bar = new Bar();", "bar.toString();"), LINE_JOINER.join( "/** @constructor */ var Bar = function(){};", "/** @override */ Bar.prototype.toString = function(){};", "Bar.prototype.a = function(){};", "var bar = new Bar();", "bar.toString();")); } public void testExternedPropertyNameDefinedByObjectLiteral() { testSame("/**@constructor*/function Bar(){};Bar.prototype.factory"); } public void testStaticAndInstanceMethodWithSameName() { test("/** @constructor */function Bar(){}; Bar.getA = function(){}; " + "Bar.prototype.getA = function(){}; Bar.getA();" + "var bar = new Bar(); bar.getA();", "/** @constructor */function Bar(){}; Bar.a = function(){};" + "Bar.prototype.a = function(){}; Bar.a();" + "var bar = new Bar(); bar.a();"); } public void testStaticAndInstanceProperties() { test("/** @constructor */function Bar(){};" + "Bar.getA = function(){}; " + "Bar.prototype.getB = function(){};", "/** @constructor */function Bar(){}; Bar.a = function(){};" + "Bar.prototype.a = function(){};"); } public void testStaticAndSubInstanceProperties() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.x=0;", "/** @constructor \n @extends Foo */ var Bar = function(){};", "goog.inherits(Bar, Foo);", "Bar.y=0;", "Bar.prototype.z=0;\n"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.a=0;", "/** @constructor \n @extends Foo */ var Bar = function(){};", "goog.inherits(Bar, Foo);", "Bar.a=0;", "Bar.prototype.a=0;\n"); test(js, output); } public void testStaticWithFunctions() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function() {};", "Foo.x = 0;", "/** @param {!Function} x */ function f(x) { x.y = 1 }", "f(Foo)"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function() {};", "Foo.a = 0;", "/** @param {!Function} x */ function f(x) { x.y = 1 }", "f(Foo)"); test(js, output); js = LINE_JOINER.join( "/** @constructor */ var Foo = function() {};", "Foo.x = 0;", "/** @param {!Function} x */ function f(x) { x.y = 1; x.x = 2;}", "f(Foo)"); testSame(js); js = LINE_JOINER.join( "/** @constructor */ var Foo = function() {};", "Foo.x = 0;", "/** @constructor */ var Bar = function() {};", "Bar.y = 0;"); output = LINE_JOINER.join( "/** @constructor */ var Foo = function() {};", "Foo.a = 0;", "/** @constructor */ var Bar = function() {};", "Bar.a = 0;"); test(js, output); } public void testTypeMismatch() { testSame(EXTERNS, LINE_JOINER.join( "/** @constructor */var Foo = function(){};", "/** @constructor */var Bar = function(){};", "Foo.prototype.b = 0;", "/** @type {Foo} */", "var F = new Bar();"), TypeValidator.TYPE_MISMATCH_WARNING, LINE_JOINER.join( "initializing variable", "found : Bar", "required: (Foo|null)")); } public void testRenamingMap() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.z=0;", "Foo.prototype.z=0;", "Foo.prototype.x=0;", "Foo.prototype.y=0;"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype.a=0;", "Foo.prototype.a=0;", "Foo.prototype.b=0;", "Foo.prototype.c=0;"); test(js, output); Map<String, String> answerMap = new HashMap<>(); answerMap.put("x", "b"); answerMap.put("y", "c"); answerMap.put("z", "a"); assertEquals(answerMap, lastPass.getRenamingMap()); } public void testInline() { String js = LINE_JOINER.join( "/** @interface */ function Foo(){}", "Foo.prototype.x = function(){};", "/**", " * @constructor", " * @implements {Foo}", " */", "function Bar(){}", "Bar.prototype.y;", "/** @override */", "Bar.prototype.x = function() { return this.y; };", "Bar.prototype.z = function() {};\n" // Simulates inline getters. + "/** @type {Foo} */ (new Bar).y;"); String output = LINE_JOINER.join( "/** @interface */ function Foo(){}", "Foo.prototype.b = function(){};", "/**", " * @constructor", " * @implements {Foo}", " */", "function Bar(){}", "Bar.prototype.a;", "/** @override */", "Bar.prototype.b = function() { return this.a; };", "Bar.prototype.c = function() {};\n" // Simulates inline getters. + "(new Bar).a;"); test(js, output); } public void testImplementsAndExtends() { String js = LINE_JOINER.join( "/** @interface */ function Foo() {}", "/** @constructor */ function Bar(){}", "Bar.prototype.y = function() { return 3; };", "/**", " * @constructor", " * @extends {Bar}", " * @implements {Foo}", " */", "function SubBar(){ }", "/** @param {Foo} x */ function f(x) { x.z = 3; }", "/** @param {SubBar} x */ function g(x) { x.z = 3; }"); String output = LINE_JOINER.join( "/** @interface */ function Foo(){}", "/** @constructor */ function Bar(){}", "Bar.prototype.b = function() { return 3; };", "/**", " * @constructor", " * @extends {Bar}", " * @implements {Foo}", " */", "function SubBar(){}", "/** @param {Foo} x */ function f(x) { x.a = 3; }", "/** @param {SubBar} x */ function g(x) { x.a = 3; }"); test(js, output); } public void testImplementsAndExtends2() { String js = LINE_JOINER.join( "/** @interface */ function A() {}", "/**", " * @constructor", " */", "function C1(){}", "/**", " * @constructor", " * @extends {C1}", " * @implements {A}", " */", "function C2(){}", "/** @param {C1} x */ function f(x) { x.y = 3; }", "/** @param {A} x */ function g(x) { x.z = 3; }\n"); String output = LINE_JOINER.join( "/** @interface */ function A(){}", "/**", " * @constructor", " */", "function C1(){}", "/**", " * @constructor", " * @extends {C1}", " * @implements {A}", " */", "function C2(){}", "/** @param {C1} x */ function f(x) { x.a = 3; }", "/** @param {A} x */ function g(x) { x.b = 3; }\n"); test(js, output); } public void testExtendsInterface() { String js = LINE_JOINER.join( "/** @interface */ function A() {}", "/** @interface \n @extends {A} */ function B() {}", "/** @param {A} x */ function f(x) { x.y = 3; }", "/** @param {B} x */ function g(x) { x.z = 3; }\n"); String output = LINE_JOINER.join( "/** @interface */ function A(){}", "/** @interface \n @extends {A} */ function B(){}", "/** @param {A} x */ function f(x) { x.a = 3; }", "/** @param {B} x */ function g(x) { x.b = 3; }\n"); test(js, output); } public void testFunctionSubType() { String js = LINE_JOINER.join( "Function.prototype.a = 1;", "function f() {}", "f.y = 2;\n"); String output = LINE_JOINER.join( "Function.prototype.a = 1;", "function f() {}", "f.b = 2;\n"); test(js, output); } public void testFunctionSubType2() { String js = LINE_JOINER.join( "Function.prototype.a = 1;", "/** @constructor */ function F() {}", "F.y = 2;\n"); String output = LINE_JOINER.join( "Function.prototype.a = 1;", "/** @constructor */ function F() {}", "F.b = 2;\n"); test(js, output); } public void testPredeclaredType() { String js = LINE_JOINER.join( "goog.addDependency('zzz.js', ['goog.Foo'], []);", "/** @constructor */ ", "function A() {", " this.x = 3;", "}", "/** @param {goog.Foo} x */", "function f(x) { x.y = 4; }"); String result = LINE_JOINER.join( "0;", "/** @constructor */ ", "function A() {", " this.a = 3;", "}", "/** @param {goog.Foo} x */", "function f(x) { x.y = 4; }"); test(js, result); } public void testBug14291280() { String js = LINE_JOINER.join( "/** @constructor \n @template T */\n", "function C() {\n", " this.aa = 1;\n", "}\n", "/** @return {C.<T>} */\n", "C.prototype.method = function() {\n", " return this;\n", "}\n", "/** @type {C.<string>} */\n", "var x = new C().method();\n", "x.bb = 2;\n", "x.cc = 3;"); String result = LINE_JOINER.join( "/** @constructor \n @template T */\n", "function C() {", " this.b = 1;", "}", "/** @return {C.<T>} */\n", "C.prototype.a = function() {", " return this;", "};", "/** @type {C.<string>} */\n", "var x = (new C).a();", "x.c = 2;", "x.d = 3;"); test(js, result); } public void testAmbiguateWithAnAlias() { String js = LINE_JOINER.join( "/** @constructor */ function Foo() { this.abc = 5; }\n", "/** @const */ var alias = Foo;\n", "/** @constructor @extends alias */\n", "function Bar() {\n", " this.xyz = 7;\n", "}"); String result = LINE_JOINER.join( "/** @constructor */ function Foo() { this.a = 5; }\n", "/** @const */ var alias = Foo;\n", "/** @constructor @extends alias */\n", "function Bar() {\n", " this.b = 7;\n", "}"); test(js, result); } public void testAmbiguateWithAliases() { String js = LINE_JOINER.join( "/** @constructor */ function Foo() { this.abc = 5; }\n", "/** @const */ var alias = Foo;\n", "/** @constructor @extends alias */\n", "function Bar() {\n", " this.def = 7;\n", "}\n", "/** @constructor @extends alias */\n", "function Baz() {\n", " this.xyz = 8;\n", "}"); String result = LINE_JOINER.join( "/** @constructor */ function Foo() { this.a = 5; }\n", "/** @const */ var alias = Foo;\n", "/** @constructor @extends alias */\n", "function Bar() {\n", " this.b = 7;\n", "}\n", "/** @constructor @extends alias */\n", "function Baz() {\n", " this.b = 8;\n", "}"); test(js, result); } // See https://github.com/google/closure-compiler/issues/1358 public void testAmbiguateWithStructuralInterfaces() { String js = LINE_JOINER.join( "/** @record */", "function Record() {}", "/** @type {number|undefined} */", "Record.prototype.recordProp;", "", "function f(/** !Record */ a) { use(a.recordProp); }", "", "/** @constructor */", "function Type() {", " /** @const */", " this.classProp = 'a';", "}", "f(new Type)"); testSame(js); } // See https://github.com/google/closure-compiler/issues/2119 public void testUnrelatedObjectLiterals() { testSame(LINE_JOINER.join( "/** @constructor */ function Foo() {}", "/** @constructor */ function Bar() {}", "var lit1 = {a: new Foo()};", "var lit2 = {b: new Bar()};")); } public void testObjectLitTwoVar() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {z:0, z:1, x:0};"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {a:0, a:1, b:0};"); test(js, output); } public void testObjectLitTwoIndependentVar() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {b: 0};", "/** @constructor */ var Bar = function(){};", "Bar.prototype = {c: 0};"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {a: 0};", "/** @constructor */ var Bar = function(){};", "Bar.prototype = {a: 0};"); test(js, output); } public void testObjectLitTwoTypesTwoVar() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {r: 0, g: 0};", "/** @constructor */ var Bar = function(){};", "Bar.prototype = {c: 0, r: 0};"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {a: 0, b: 0};", "/** @constructor */ var Bar = function(){};", "Bar.prototype = {b: 0, a: 0};"); test(js, output); } public void testObjectLitUnion() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @constructor */ var Bar = function(){};", "Foo.prototype = {foodoo: 0};", "Bar.prototype = {bardoo: 0};", "/** @type {Foo|Bar} */", "var U;", "U.joint;"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @constructor */ var Bar = function(){};", "Foo.prototype = {a: 0};", "Bar.prototype = {a: 0};", "/** @type {Foo|Bar} */", "var U;", "U.b;"); test(js, output); } public void testObjectLitUnions() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @constructor */ var Bar = function(){};", "/** @constructor */ var Baz = function(){};", "/** @constructor */ var Bat = function(){};", "Foo.prototype = {lone1: 0};", "Bar.prototype = {lone2: 0};", "Baz.prototype = {lone3: 0};", "Bat.prototype = {lone4: 0};", "/** @type {Foo|Bar} */", "var U1;", "U1.j1;", "U1.j2;", "/** @type {Baz|Bar} */", "var U2;", "U2.j3;", "U2.j4;", "/** @type {Baz|Bat} */", "var U3;", "U3.j5;", "U3.j6"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "/** @constructor */ var Bar = function(){};", "/** @constructor */ var Baz = function(){};", "/** @constructor */ var Bat = function(){};", "Foo.prototype = {c: 0};", "Bar.prototype = {e: 0};", "Baz.prototype = {e: 0};", "Bat.prototype = {c: 0};", "/** @type {Foo|Bar} */", "var U1;", "U1.a;", "U1.b;", "/** @type {Baz|Bar} */", "var U2;", "U2.c;", "U2.d;", "/** @type {Baz|Bat} */", "var U3;", "U3.a;", "U3.b"); test(js, output); } public void testObjectLitExtends() { String js = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {x: 0};", "/** @constructor \n @extends Foo */ var Bar = function(){};", "goog.inherits(Bar, Foo);", "Bar.prototype = {y: 0, z: 0};", "/** @constructor */ var Baz = function(){};", "Baz.prototype = {l: 0, m: 0, n: 0};", "(new Baz).m\n"); String output = LINE_JOINER.join( "/** @constructor */ var Foo = function(){};", "Foo.prototype = {a: 0};", "/** @constructor \n @extends Foo */ var Bar = function(){};", "goog.inherits(Bar, Foo);", "Bar.prototype = {b: 0, c: 0};", "/** @constructor */ var Baz = function(){};", "Baz.prototype = {b: 0, a: 0, c: 0};", "(new Baz).a\n"); test(js, output); } public void testObjectLitLotsOfClasses() { StringBuilder b = new StringBuilder(); int classes = 10; for (int i = 0; i < classes; i++) { String c = "Foo" + i; b.append("/** @constructor */ var ").append(c).append(" = function(){};\n"); b.append(c).append(".prototype = {varness").append(i).append(": 0};"); } String js = b.toString(); test(js, js.replaceAll("varness\\d+", "a")); } public void testObjectLitFunctionType() { String js = LINE_JOINER.join( "/** @constructor */ function Foo(){};", "Foo.prototype = { /** @return {Bar} */ fun: function() { return new Bar(); } };", "/** @constructor */ function Bar(){};", "Bar.prototype.bazz;", "(new Foo).fun().bazz();"); String output = LINE_JOINER.join( "/** @constructor */ function Foo(){};", "Foo.prototype = { /** @return {Bar} */ a: function() { return new Bar(); } };", "/** @constructor */ function Bar(){};", "Bar.prototype.a;", "(new Foo).a().a();"); test(js, output); } public void testObjectLitOverlappingOriginalAndGeneratedNames() { test( LINE_JOINER.join( "/** @constructor */ function Bar(){};", "Bar.prototype = {b: function(){}, a: function(){}};", "var bar = new Bar();", "bar.b();"), LINE_JOINER.join( "/** @constructor */ function Bar(){};", "Bar.prototype = {a: function(){}, b: function(){}};", "var bar = new Bar();", "bar.a();")); } }