/* * 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.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.javascript.rhino.Node; import java.util.*; /** * Tests for {@link RenameVars}. */ public class RenameVarsTest extends CompilerTestCase { private static final String DEFAULT_PREFIX = ""; private String prefix = DEFAULT_PREFIX; private VariableMap previouslyUsedMap = new VariableMap(ImmutableMap.of()); private RenameVars renameVars; private boolean withClosurePass = false; private boolean localRenamingOnly = false; private boolean preserveFunctionExpressionNames = false; private boolean useGoogleCodingConvention = true; private boolean generatePseudoNames = false; private boolean shouldShadow = false; @Override protected CodingConvention getCodingConvention() { if (useGoogleCodingConvention) { return new GoogleCodingConvention(); } else { return CodingConventions.getDefault(); } } @Override protected CompilerPass getProcessor(Compiler compiler) { if (withClosurePass) { return new ClosurePassAndRenameVars(compiler); } else { return renameVars = new RenameVars(compiler, prefix, localRenamingOnly, preserveFunctionExpressionNames, generatePseudoNames, shouldShadow, previouslyUsedMap, null, null); } } @Override protected int getNumRepetitions() { return 1; } @Override protected void setUp() throws Exception { super.setUp(); previouslyUsedMap = new VariableMap(ImmutableMap.of()); prefix = DEFAULT_PREFIX; withClosurePass = false; localRenamingOnly = false; preserveFunctionExpressionNames = false; generatePseudoNames = false; shouldShadow = false; // TODO(johnlenz): Enable Normalize during these tests. } public void testRenameSimple() { test("function Foo(v1, v2) {return v1;} Foo();", "function a(b, c) {return b;} a();"); } public void testRenameGlobals() { test("var Foo; var Bar, y; function x() { Bar++; }", "var a; var b, c; function d() { b++; }"); } public void testRenameLocals() { test("(function (v1, v2) {}); (function (v3, v4) {});", "(function (a, b) {}); (function (a, b) {});"); test("function f1(v1, v2) {}; function f2(v3, v4) {};", "function c(a, b) {}; function d(a, b) {};"); } public void testRenameRedeclaredGlobals() { test("function f1(v1, v2) {f1()};" + "/** @suppress {duplicate} */" + "function f1(v3, v4) {f1()};", "function a(b, c) {a()};" + "function a(b, c) {a()};"); localRenamingOnly = true; test("function f1(v1, v2) {f1()};" + "/** @suppress {duplicate} */" + "function f1(v3, v4) {f1()};", "function f1(a, b) {f1()};" + "function f1(a, b) {f1()};"); } public void testRecursiveFunctions1() { test("var walk = function walk(node, aFunction) {" + " walk(node, aFunction);" + "};", "var a = function a(b, c) {" + " a(b, c);" + "};"); localRenamingOnly = true; test("var walk = function walk(node, aFunction) {" + " walk(node, aFunction);" + "};", "var walk = function walk(a, b) {" + " walk(a, b);" + "};"); } public void testRecursiveFunctions2() { preserveFunctionExpressionNames = true; test("var walk = function walk(node, aFunction) {" + " walk(node, aFunction);" + "};", "var c = function walk(a, b) {" + " walk(a, b);" + "};"); localRenamingOnly = true; test("var walk = function walk(node, aFunction) {" + " walk(node, aFunction);" + "};", "var walk = function walk(a, b) {" + " walk(a, b);" + "};"); } public void testRenameLocalsClashingWithGlobals() { test("function a(v1, v2) {return v1;} a();", "function a(b, c) {return b;} a();"); } public void testRenameNested() { test("function f1(v1, v2) { (function(v3, v4) {}) }", "function a(b, c) { (function(d, e) {}) }"); test("function f1(v1, v2) { function f2(v3, v4) {} }", "function a(b, c) { function d(e, f) {} }"); } public void testBleedingRecursiveFunctions1() { // On IE, bleeding functions will interfere with each other if // they are in the same scope. In the below example, we want to be // sure that a and b get separate names. test("var x = function a(x) { return x ? 1 : a(1); };" + "var y = function b(x) { return x ? 2 : b(2); };", "var c = function b(a) { return a ? 1 : b(1); };" + "var e = function d(a) { return a ? 2 : d(2); };"); } public void testBleedingRecursiveFunctions2() { test("function f() {" + " var x = function a(x) { return x ? 1 : a(1); };" + " var y = function b(x) { return x ? 2 : b(2); };" + "}", "function d() {" + " var e = function b(a) { return a ? 1 : b(1); };" + " var f = function a(c) { return c ? 2 : a(2); };" + "}"); } public void testBleedingRecursiveFunctions3() { test("function f() {" + " var x = function a(x) { return x ? 1 : a(1); };" + " var y = function b(x) { return x ? 2 : b(2); };" + " var z = function c(x) { return x ? y : c(2); };" + "}", "function f() {" + " var g = function c(a) { return a ? 1 : c(1); };" + " var d = function a(b) { return b ? 2 : a(2); };" + " var h = function b(e) { return e ? d : b(2); };" + "}"); } public void testRenameWithExterns1() { String externs = "var foo;"; test(externs, "var bar; foo(bar);", "var a; foo(a);", null, null); } public void testRenameWithExterns2() { String externs = "var a;"; test(externs, "var b = 5", "var b = 5", null, null); } public void testDoNotRenameExportedName() { test("_foo()", "_foo()"); } public void testRenameWithNameOverlap() { test("var a = 1; var b = 2; b + b;", "var a = 1; var b = 2; b + b;"); } public void testRenameWithPrefix1() { prefix = "PRE_"; test("function Foo(v1, v2) {return v1} Foo();", "function PRE_(a, b) {return a} PRE_();"); prefix = DEFAULT_PREFIX; } public void testRenameWithPrefix2() { prefix = "PRE_"; test("function Foo(v1, v2) {var v3 = v1 + v2; return v3;} Foo();", "function PRE_(a, b) {var c = a + b; return c;} PRE_();"); prefix = DEFAULT_PREFIX; } public void testRenameWithPrefix3() { prefix = "a"; test("function Foo() {return 1;}" + "function Bar() {" + " var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z," + " A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,aa,ab;" + " Foo();" + "} Bar();", "function a() {return 1;}" + "function aa() {" + " var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A," + " B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,$,ba,ca;" + " a();" + "} aa();"); prefix = DEFAULT_PREFIX; } public void testNamingBasedOnOrderOfOccurrence() { test("var q,p,m,n,l,k; " + "(function (r) {}); try { } catch(s) {}; var t = q + q;", "var a,b,c,d,e,f; " + "(function(g) {}); try { } catch(h) {}; var i = a + a;" ); test("(function(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z," + "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,$){});" + "var a4,a3,a2,a1,b4,b3,b2,b1,ab,ac,ad,fg;function foo(){};", "(function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z," + "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,$){});" + "var aa,ba,ca,da,ea,fa,ga,ha,ia,ja,ka,la;function ma(){};"); } public void testStableRenameSimple() { VariableMap expectedVariableMap = makeVariableMap( "Foo", "a", "L 0", "b", "L 1", "c"); testRenameMap("function Foo(v1, v2) {return v1;} Foo();", "function a(b, c) {return b;} a();", expectedVariableMap); expectedVariableMap = makeVariableMap( "Foo", "a", "L 0", "b", "L 1", "c", "L 2", "d"); testRenameMapUsingOldMap("function Foo(v1, v2, v3) {return v1;} Foo();", "function a(b, c, d) {return b;} a();", expectedVariableMap); } public void testStableRenameGlobals() { VariableMap expectedVariableMap = makeVariableMap( "Foo", "a", "Bar", "b", "y", "c", "x", "d"); testRenameMap("var Foo; var Bar, y; function x() { Bar++; }", "var a; var b, c; function d() { b++; }", expectedVariableMap); expectedVariableMap = makeVariableMap( "Foo", "a", "Bar", "b", "y", "c", "x", "d", "Baz", "f", "L 0" , "e"); testRenameMapUsingOldMap( "var Foo, Baz; var Bar, y; function x(R) { return R + Bar++; }", "var a, f; var b, c; function d(e) { return e + b++; }", expectedVariableMap); } public void testStableRenameWithPointlesslyAnonymousFunctions() { VariableMap expectedVariableMap = makeVariableMap("L 0", "a", "L 1", "b"); testRenameMap("(function (v1, v2) {}); (function (v3, v4) {});", "(function (a, b) {}); (function (a, b) {});", expectedVariableMap); expectedVariableMap = makeVariableMap("L 0", "a", "L 1", "b", "L 2", "c"); testRenameMapUsingOldMap("(function (v0, v1, v2) {});" + "(function (v3, v4) {});", "(function (a, b, c) {});" + "(function (a, b) {});", expectedVariableMap); } public void testStableRenameLocalsClashingWithGlobals() { test("function a(v1, v2) {return v1;} a();", "function a(b, c) {return b;} a();"); previouslyUsedMap = renameVars.getVariableMap(); test("function bar(){return;}function a(v1, v2) {return v1;} a();", "function d(){return;}function a(b, c) {return b;} a();"); } public void testStableRenameNested() { VariableMap expectedVariableMap = makeVariableMap( "f1", "a", "L 0", "b", "L 1", "c", "L 2", "d", "L 3", "e"); testRenameMap("function f1(v1, v2) { (function(v3, v4) {}) }", "function a(b, c) { (function(d, e) {}) }", expectedVariableMap); expectedVariableMap = makeVariableMap( "f1", "a", "L 0", "b", "L 1", "c", "L 2", "d", "L 3", "e", "L 4", "f"); testRenameMapUsingOldMap( "function f1(v1, v2) { (function(v3, v4, v5) {}) }", "function a(b, c) { (function(d, e, f) {}) }", expectedVariableMap); } public void testStableRenameWithExterns1() { String externs = "var foo;"; test(externs, "var bar; foo(bar);", "var a; foo(a);", null, null); previouslyUsedMap = renameVars.getVariableMap(); test(externs, "var bar, baz; foo(bar, baz);", "var a, b; foo(a, b);", null, null); } public void testStableRenameWithExterns2() { String externs = "var a;"; test(externs, "var b = 5", "var b = 5", null, null); previouslyUsedMap = renameVars.getVariableMap(); test(externs, "var b = 5, catty = 9;", "var b = 5, c=9;", null, null); } public void testStableRenameWithNameOverlap() { test("var a = 1; var b = 2; b + b;", "var a = 1; var b = 2; b + b;"); previouslyUsedMap = renameVars.getVariableMap(); test("var a = 1; var c, b = 2; b + b;", "var a = 1; var c, b = 2; b + b;"); } public void testStableRenameWithAnonymousFunctions() { VariableMap expectedVariableMap = makeVariableMap("L 0", "a", "foo", "b"); testRenameMap("function foo(bar){return bar;}foo(function(h){return h;});", "function b(a){return a}b(function(a){return a;})", expectedVariableMap); expectedVariableMap = makeVariableMap("foo", "b", "L 0", "a", "L 1", "c"); testRenameMapUsingOldMap( "function foo(bar) {return bar;}foo(function(g,h) {return g+h;});", "function b(a){return a}b(function(a,c){return a+c;})", expectedVariableMap); } public void testStableRenameSimpleExternsChanges() { VariableMap expectedVariableMap = makeVariableMap( "Foo", "a", "L 0", "b", "L 1", "c"); testRenameMap("function Foo(v1, v2) {return v1;} Foo();", "function a(b, c) {return b;} a();", expectedVariableMap); expectedVariableMap = makeVariableMap("L 0", "b", "L 1", "c", "L 2", "a"); String externs = "var Foo;"; testRenameMapUsingOldMap(externs, "function Foo(v1, v2, v0) {return v1;} Foo();", "function Foo(b, c, a) {return b;} Foo();", expectedVariableMap); } public void testStableRenameSimpleLocalNameExterned() { test("function Foo(v1, v2) {return v1;} Foo();", "function a(b, c) {return b;} a();"); previouslyUsedMap = renameVars.getVariableMap(); String externs = "var b;"; test(externs, "function Foo(v1, v2) {return v1;} Foo(b);", "function a(d, c) {return d;} a(b);", null, null); } public void testStableRenameSimpleGlobalNameExterned() { test("function Foo(v1, v2) {return v1;} Foo();", "function a(b, c) {return b;} a();"); previouslyUsedMap = renameVars.getVariableMap(); String externs = "var Foo;"; test(externs, "function Foo(v1, v2, v0) {return v1;} Foo();", "function Foo(b, c, a) {return b;} Foo();", null, null); } public void testStableRenameWithPrefix1AndUnstableLocalNames() { prefix = "PRE_"; test("function Foo(v1, v2) {return v1} Foo();", "function PRE_(a, b) {return a} PRE_();"); previouslyUsedMap = renameVars.getVariableMap(); prefix = "PRE_"; test("function Foo(v0, v1, v2) {return v1} Foo();", "function PRE_(a, b, c) {return b} PRE_();"); } public void testStableRenameWithPrefix2() { prefix = "a"; test("function Foo() {return 1;}" + "function Bar() {" + " var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z," + " A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,aa,ab;" + " Foo();" + "} Bar();", "function a() {return 1;}" + "function aa() {" + " var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A," + " B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,$,ba,ca;" + " a();" + "} aa();"); previouslyUsedMap = renameVars.getVariableMap(); prefix = "a"; test("function Foo() {return 1;}" + "function Baz() {return 1;}" + "function Bar() {" + " var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z," + " A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,aa,ab;" + " Foo();" + "} Bar();", "function a() {return 1;}" + "function ab() {return 1;}" + "function aa() {" + " var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A," + " B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,$,ba,ca;" + " a();" + "} aa();"); } public void testContrivedExampleWhereConsistentRenamingIsWorse() { previouslyUsedMap = makeVariableMap( "Foo", "LongString", "L 0", "b", "L 1", "c"); test("function Foo(v1, v2) {return v1;} Foo();", "function LongString(b, c) {return b;} LongString();"); previouslyUsedMap = renameVars.getVariableMap(); VariableMap expectedVariableMap = makeVariableMap( "Foo", "LongString", "L 0", "b", "L 1", "c"); assertVariableMapsEqual(expectedVariableMap, previouslyUsedMap); } public void testExportSimpleSymbolReservesName() { test("var goog, x; goog.exportSymbol('a', x);", "var a, b; a.exportSymbol('a', b);"); withClosurePass = true; test("var goog, x; goog.exportSymbol('a', x);", "var b, c; b.exportSymbol('a', c);"); } public void testExportComplexSymbolReservesName() { test("var goog, x; goog.exportSymbol('a.b', x);", "var a, b; a.exportSymbol('a.b', b);"); withClosurePass = true; test("var goog, x; goog.exportSymbol('a.b', x);", "var b, c; b.exportSymbol('a.b', c);"); } public void testExportToNonStringDoesntExplode() { withClosurePass = true; test("var goog, a, b; goog.exportSymbol(a, b);", "var a, b, c; a.exportSymbol(b, c);"); } public void testDollarSignSuperExport1() { useGoogleCodingConvention = false; // See http://code.google.com/p/closure-compiler/issues/detail?id=32 test("var x = function($super,duper,$fantastic){}", "var c = function($super, a, b){}"); localRenamingOnly = false; test("var $super = 1", "var a = 1"); useGoogleCodingConvention = true; test("var x = function($super,duper,$fantastic){}", "var c = function($super,a,b){}"); } public void testDollarSignSuperExport2() { boolean normalizedExpectedJs = false; super.enableNormalize(false); useGoogleCodingConvention = false; // See http://code.google.com/p/closure-compiler/issues/detail?id=32 test("var x = function($super,duper,$fantastic){};" + "var y = function($super,duper){};", "var c = function($super, a, b){};" + "var d = function($super, a){};"); localRenamingOnly = false; test("var $super = 1", "var a = 1"); useGoogleCodingConvention = true; test("var x = function($super,duper,$fantastic){};" + "var y = function($super,duper){};", "var c = function($super, a, b ){};" + "var d = function($super,a){};"); super.disableNormalize(); } public void testPseudoNames() { generatePseudoNames = false; // See http://code.google.com/p/closure-compiler/issues/detail?id=32 test("var foo = function(a, b, c){}", "var d = function(a, b, c){}"); generatePseudoNames = true; test("var foo = function(a, b, c){}", "var $foo$$ = function($a$$, $b$$, $c$$){}"); test("var a = function(a, b, c){}", "var $a$$ = function($a$$, $b$$, $c$$){}"); } private void testRenameMapUsingOldMap(String input, String expected, VariableMap expectedMap) { previouslyUsedMap = renameVars.getVariableMap(); testRenameMap("", input, expected, expectedMap); } private void testRenameMapUsingOldMap(String externs, String input, String expected, VariableMap expectedMap) { previouslyUsedMap = renameVars.getVariableMap(); testRenameMap(externs, input, expected, expectedMap); } private void testRenameMap(String input, String expected, VariableMap expectedRenameMap) { testRenameMap("", input, expected, expectedRenameMap); } private void testRenameMap(String externs, String input, String expected, VariableMap expectedRenameMap) { test(externs, input, expected, null, null); VariableMap renameMap = renameVars.getVariableMap(); assertVariableMapsEqual(expectedRenameMap, renameMap); } private VariableMap makeVariableMap(String... keyValPairs) { Preconditions.checkArgument(keyValPairs.length % 2 == 0); ImmutableMap.Builder renameMap = ImmutableMap.builder(); for (int i = 0; i < keyValPairs.length; i += 2) { renameMap.put(keyValPairs[i], keyValPairs[i + 1]); } return new VariableMap(renameMap.build()); } private static void assertVariableMapsEqual(VariableMap a, VariableMap b) { Map ma = a.getOriginalNameToNewNameMap(); Map mb = b.getOriginalNameToNewNameMap(); assertEquals("VariableMaps not equal", ma, mb); } private class ClosurePassAndRenameVars implements CompilerPass { private final Compiler compiler; private ClosurePassAndRenameVars(Compiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { ProcessClosurePrimitives closurePass = new ProcessClosurePrimitives( compiler, null, CheckLevel.WARNING); closurePass.process(externs, root); renameVars = new RenameVars(compiler, prefix, false, false, false, false, previouslyUsedMap, null, closurePass.getExportedVariableNames()); renameVars.process(externs, root); } } }