/* * Copyright 2005 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.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * {@link RenameProperties} tests. * */ public class RenamePropertiesTest extends CompilerTestCase { private static final String EXTERNS = "var window;" + "prop.toString;" + "var google = { gears: { factory: {}, workerPool: {} } };"; private RenameProperties renameProperties; private static boolean generatePseudoNames = false; private static boolean useAffinity = false; private VariableMap prevUsedPropertyMap = null; public RenamePropertiesTest() { super(EXTERNS); enableNormalize(); } @Override protected void tearDown() throws Exception { super.tearDown(); prevUsedPropertyMap = null; useAffinity = false; } @Override protected int getNumRepetitions() { // The RenameProperties pass should only be run once over a parse tree. return 1; } public void testPrototypeProperties() { test("Bar.prototype.getA = function(){}; bar.getA();" + "Bar.prototype.getB = function(){};", "Bar.prototype.a = function(){}; bar.a();" + "Bar.prototype.b = function(){}"); } public void testPrototypePropertiesAsObjLitKeys1() { test("Bar.prototype = {2: function(){}, getA: function(){}}; bar[2]();", "Bar.prototype = {2: function(){}, a: function(){}}; bar[2]();"); } public void testPrototypePropertiesAsObjLitKeys2() { testSame("Bar.prototype = {get 2(){}}; bar[2];"); testSame("Bar.prototype = {get 'a'(){}}; bar['a'];"); test("Bar.prototype = {get getA(){}}; bar.getA;", "Bar.prototype = {get a(){}}; bar.a;"); } public void testPrototypePropertiesAsObjLitKeys3() { testSame("Bar.prototype = {set 2(x){}}; bar[2];"); testSame("Bar.prototype = {set 'a'(x){}}; bar['a'];"); test("Bar.prototype = {set getA(x){}}; bar.getA;", "Bar.prototype = {set a(x){}}; bar.a;"); } public void testMixedQuotedAndUnquotedObjLitKeys1() { test("Bar = {getA: function(){}, 'getB': function(){}}; bar.getA();", "Bar = {a: function(){}, 'getB': function(){}}; bar.a();"); } public void testMixedQuotedAndUnquotedObjLitKeys2() { test("Bar = {getA: function(){}, 'getB': function(){}}; bar.getA();", "Bar = {a: function(){}, 'getB': function(){}}; bar.a();"); } public void testQuotedPrototypeProperty() { testSame("Bar.prototype['getA'] = function(){}; bar['getA']();"); } public void testOverlappingOriginalAndGeneratedNames() { test("Bar.prototype = {b: function(){}, a: function(){}}; bar.b();", "Bar.prototype = {a: function(){}, b: function(){}}; bar.a();"); } public void testRenamePropertiesWithLeadingUnderscores() { test("Bar.prototype = {_getA: function(){}, _b: 0}; bar._getA();", "Bar.prototype = {a: function(){}, b: 0}; bar.a();"); } public void testPropertyAddedToObject() { test("var foo = {}; foo.prop = '';", "var foo = {}; foo.a = '';"); } public void testPropertyAddedToFunction() { test("var foo = function(){}; foo.prop = '';", "var foo = function(){}; foo.a = '';"); } public void testPropertyOfObjectOfUnknownType() { test("var foo = x(); foo.prop = '';", "var foo = x(); foo.a = '';"); } public void testSetPropertyOfThis() { test("this.prop = 'bar'", "this.a = 'bar'"); } public void testReadPropertyOfThis() { test("f(this.prop);", "f(this.a);"); } public void testObjectLiteralInLocalScope() { test("function x() { var foo = {prop1: 'bar', prop2: 'baz'}; }", "function x() { var foo = {a: 'bar', b: 'baz'}; }"); } public void testIncorrectAttemptToAccessQuotedProperty() { // The correct way to call the quoted 'getFoo' method is: bar['getFoo'](). test("Bar.prototype = {'B': 0, 'getFoo': function(){}}; bar.getFoo();", "Bar.prototype = {'B': 0, 'getFoo': function(){}}; bar.a();"); } public void testSetQuotedPropertyOfThis() { testSame("this['prop'] = 'bar';"); } public void testExternedPropertyName() { test("Bar.prototype = {toString: function(){}, foo: 0}; bar.toString();", "Bar.prototype = {toString: function(){}, a: 0}; bar.toString();"); } public void testExternedPropertyNameDefinedByObjectLiteral() { test("function x() { var foo = google.gears.factory; }", "function x() { var foo = google.gears.factory; }"); } public void testAvoidingConflictsBetweenQuotedAndUnquotedPropertyNames() { test("Bar.prototype.foo = function(){}; Bar.prototype['a'] = 0; bar.foo();", "Bar.prototype.b = function(){}; Bar.prototype['a'] = 0; bar.b();"); } public void testSamePropertyNameQuotedAndUnquoted() { test("Bar.prototype.prop = function(){}; y = {'prop': 0};", "Bar.prototype.a = function(){}; y = {'prop': 0};"); } public void testStaticAndInstanceMethodWithSameName() { test("Bar = function(){}; Bar.getA = function(){}; " + "Bar.prototype.getA = function(){}; Bar.getA(); bar.getA();", "Bar = function(){}; Bar.a = function(){}; " + "Bar.prototype.a = function(){}; Bar.a(); bar.a();"); } public void testRenamePropertiesFunctionCall1() { test("var foo = {myProp: 0}; f(foo[JSCompiler_renameProperty('myProp')]);", "var foo = {a: 0}; f(foo['a']);"); } public void testRenamePropertiesFunctionCall2() { test("var foo = {myProp: 0}; " + "f(JSCompiler_renameProperty('otherProp.myProp.someProp')); " + "foo.myProp = 1; foo.theirProp = 2; foo.yourProp = 3;", "var foo = {a: 0}; f('b.a.c'); " + "foo.a = 1; foo.d = 2; foo.e = 3;"); } public void testRemoveRenameFunctionStubs1() { test("function JSCompiler_renameProperty(x) { return x; }", ""); } public void testRemoveRenameFunctionStubs2() { test("function JSCompiler_renameProperty(x) { return x; }" + "var foo = {myProp: 0}; f(foo[JSCompiler_renameProperty('myProp')]);", "var foo = {a: 0}; f(foo['a']);"); } public void testGeneratePseudoNames() { generatePseudoNames = true; test("var foo={}; foo.bar=1; foo['abc']=2", "var foo={}; foo.$bar$=1; foo['abc']=2"); generatePseudoNames = false; } public void testModules() { String module1Js = "function Bar(){} Bar.prototype.getA=function(x){};" + "var foo;foo.getA(foo);foo.doo=foo;foo.bloo=foo;"; String module2Js = "function Far(){} Far.prototype.getB=function(x){};" + "var too;too.getB(too);too.woo=too;too.bloo=too;"; String module3Js = "function Car(){} Car.prototype.getC=function(x){};" + "var noo;noo.getC(noo);noo.zoo=noo;noo.cloo=noo;"; JSModule module1 = new JSModule("m1"); module1.add(SourceFile.fromCode("input1", module1Js)); JSModule module2 = new JSModule("m2"); module2.add(SourceFile.fromCode("input2", module2Js)); JSModule module3 = new JSModule("m3"); module3.add(SourceFile.fromCode("input3", module3Js)); JSModule[] modules = new JSModule[] { module1, module2, module3 }; Compiler compiler = compileModules("", modules); Result result = compiler.getResult(); assertTrue(result.success); assertEquals("function Bar(){}Bar.prototype.b=function(x){};" + "var foo;foo.b(foo);foo.f=foo;foo.a=foo;", compiler.toSource(module1)); assertEquals("function Far(){}Far.prototype.c=function(x){};" + "var too;too.c(too);too.g=too;too.a=too;", compiler.toSource(module2)); // Note that properties that occur most often globally get the earliest // names. The "getC" property, which doesn't occur until module 3, is // renamed to an earlier name in the alphabet than "woo", which appears // in module 2, because "getC" occurs more total times across all modules. // Might be better to give early modules the shortest names, but this is // how the pass currently works. assertEquals("function Car(){}Car.prototype.d=function(x){};" + "var noo;noo.d(noo);noo.h=noo;noo.e=noo;", compiler.toSource(module3)); } public void testPropertyAffinity() { // 'y' gets to be 'b' because it appears with z often. // Other wise, 'x' gets to be 'b' because of alphabetical ordering. useAffinity = true; test("var foo={};foo.x=1;foo.y=2;foo.z=3;" + "function f1() { foo.z; foo.z; foo.z; foo.y}" + "function f2() { foo.x}", "var foo={};foo.c=1;foo.b=2;foo.a=3;" + "function f1() { foo.a; foo.a; foo.a; foo.b}" + "function f2() { foo.c}"); test("var foo={};foo.x=1;foo.y=2;foo.z=3;" + "function f1() { foo.z; foo.z; foo.z; foo.y}" + "function f2() { foo.z; foo.z; foo.z; foo.x}", "var foo={};foo.b=1;foo.c=2;foo.a=3;" + "function f1() { foo.a; foo.a; foo.a; foo.c}" + "function f2() { foo.a; foo.a; foo.a; foo.b}"); } public void testPropertyAffinityOff() { useAffinity = false; test("var foo={};foo.x=1;foo.y=2;foo.z=3;" + "function f1() { foo.z; foo.z; foo.z; foo.y}" + "function f2() { foo.x}", "var foo={};foo.b=1;foo.c=2;foo.a=3;" + "function f1() { foo.a; foo.a; foo.a; foo.c}" + "function f2() { foo.b}"); test("var foo={};foo.x=1;foo.y=2;foo.z=3;" + "function f1() { foo.z; foo.z; foo.z; foo.y}" + "function f2() { foo.z; foo.z; foo.z; foo.x}", "var foo={};foo.b=1;foo.c=2;foo.a=3;" + "function f1() { foo.a; foo.a; foo.a; foo.c}" + "function f2() { foo.a; foo.a; foo.a; foo.b}"); } public void testPrototypePropertiesStable() { testStableRenaming( "Bar.prototype.getA = function(){}; bar.getA();" + "Bar.prototype.getB = function(){};", "Bar.prototype.a = function(){}; bar.a();" + "Bar.prototype.b = function(){}", "Bar.prototype.get = function(){}; bar.get();" + "Bar.prototype.getA = function(){}; bar.getA();" + "Bar.prototype.getB = function(){};", "Bar.prototype.c = function(){}; bar.c();" + "Bar.prototype.a = function(){}; bar.a();" + "Bar.prototype.b = function(){}"); } public void testPrototypePropertiesAsObjLitKeysStable() { testStableRenaming( "Bar.prototype = {2: function(){}, getA: function(){}}; bar[2]();", "Bar.prototype = {2: function(){}, a: function(){}}; bar[2]();", "Bar.prototype = {getB: function(){},getA: function(){}}; bar.getB();", "Bar.prototype = {b: function(){},a: function(){}}; bar.b();"); } public void testMixedQuotedAndUnquotedObjLitKeysStable() { testStableRenaming( "Bar = {getA: function(){}, 'getB': function(){}}; bar.getA();", "Bar = {a: function(){}, 'getB': function(){}}; bar.a();", "Bar = {get: function(){}, getA: function(){}, 'getB': function(){}};" + "bar.getA();bar.get();", "Bar = {b: function(){}, a: function(){}, 'getB': function(){}};" + "bar.a();bar.b();"); } public void testOverlappingOriginalAndGeneratedNamesStable() { testStableRenaming( "Bar.prototype = {b: function(){}, a: function(){}}; bar.b();", "Bar.prototype = {a: function(){}, b: function(){}}; bar.a();", "Bar.prototype = {c: function(){}, b: function(){}, a: function(){}};" + "bar.b();", "Bar.prototype = {c: function(){}, a: function(){}, b: function(){}};" + "bar.a();"); } public void testStableWithTrickyExternsChanges() { test("Bar.prototype = {b: function(){}, a: function(){}}; bar.b();", "Bar.prototype = {a: function(){}, b: function(){}}; bar.a();"); prevUsedPropertyMap = renameProperties.getPropertyMap(); String externs = EXTERNS + "prop.b;"; test(externs, "Bar.prototype = {new_f: function(){}, b: function(){}, " + "a: function(){}};bar.b();", "Bar.prototype = {c:function(){}, b:function(){}, a:function(){}};" + "bar.b();", null, null); } public void testRenamePropertiesWithLeadingUnderscoresStable() { testStableRenaming( "Bar.prototype = {_getA: function(){}, _b: 0}; bar._getA();", "Bar.prototype = {a: function(){}, b: 0}; bar.a();", "Bar.prototype = {_getA: function(){}, _c: 1, _b: 0}; bar._getA();", "Bar.prototype = {a: function(){}, c: 1, b: 0}; bar.a();"); } public void testPropertyAddedToObjectStable() { testStableRenaming("var foo = {}; foo.prop = '';", "var foo = {}; foo.a = '';", "var foo = {}; foo.prop = ''; foo.a='';", "var foo = {}; foo.a = ''; foo.b='';"); } public void testAvoidingConflictsBetQuotedAndUnquotedPropertyNamesStable() { testStableRenaming( "Bar.prototype.foo = function(){}; Bar.prototype['b'] = 0; bar.foo();", "Bar.prototype.a = function(){}; Bar.prototype['b'] = 0; bar.a();", "Bar.prototype.foo = function(){}; Bar.prototype['a'] = 0; bar.foo();", "Bar.prototype.b = function(){}; Bar.prototype['a'] = 0; bar.b();"); } public void testRenamePropertiesFunctionCallStable() { testStableRenaming( "var foo = {myProp: 0}; " + "f(JSCompiler_renameProperty('otherProp.myProp.someProp')); " + "foo.myProp = 1; foo.theirProp = 2; foo.yourProp = 3;", "var foo = {a: 0}; f('b.a.c'); " + "foo.a = 1; foo.d = 2; foo.e = 3;", "var bar = {newProp: 0}; var foo = {myProp: 0}; " + "f(JSCompiler_renameProperty('otherProp.myProp.someProp')); " + "foo.myProp = 1; foo.theirProp = 2; foo.yourProp = 3;", "var bar = {f: 0}; var foo = {a: 0}; f('b.a.c'); " + "foo.a = 1; foo.d = 2; foo.e = 3;"); } private void testStableRenaming(String input1, String expected1, String input2, String expected2) { test(input1, expected1); prevUsedPropertyMap = renameProperties.getPropertyMap(); test(input2, expected2); } private Compiler compileModules(String externs, JSModule[] modules) { SourceFile externsInput = SourceFile.fromCode("externs", externs); CompilerOptions options = new CompilerOptions(); options.propertyRenaming = PropertyRenamingPolicy.ALL_UNQUOTED; Compiler compiler = new Compiler(); compiler.compileModules( ImmutableList.of(externsInput), Lists.newArrayList(modules), options); return compiler; } @Override public CompilerPass getProcessor(Compiler compiler) { return renameProperties = new RenameProperties(compiler, useAffinity, generatePseudoNames, prevUsedPropertyMap); } }