/* * Copyright 2004 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; public class RemoveUnusedVarsTest extends CompilerTestCase { private boolean removeGlobal; private boolean preserveFunctionExpressionNames; private boolean modifyCallSites; public RemoveUnusedVarsTest() { super("function alert() {}"); enableNormalize(); } @Override public void setUp() { removeGlobal = true; preserveFunctionExpressionNames = false; modifyCallSites = false; } @Override protected CompilerPass getProcessor(final Compiler compiler) { return new RemoveUnusedVars( compiler, removeGlobal, preserveFunctionExpressionNames, modifyCallSites); } public void testRemoveUnusedVars() { // Test lots of stuff test("var a;var b=3;var c=function(){};var x=A();var y; var z;" + "function A(){B()} function B(){C(b)} function C(){} " + "function X(){Y()} function Y(z){Z(x)} function Z(){y} " + "P=function(){A()}; " + "try{0}catch(e){a}", "var a;var b=3;A();function A(){B()}" + "function B(){C(b)}" + "function C(){}" + "P=function(){A()}" + ";try{0}catch(e){a}"); // Test removal from if {} blocks test("var i=0;var j=0;if(i>0){var k=1;}", "var i=0;if(i>0);"); // Test with for loop test("for (var i in booyah) {" + " if (i > 0) x += ', ';" + " var arg = 'foo';" + " if (arg.length > 40) {" + " var unused = 'bar';" + // this variable is unused " arg = arg.substr(0, 40) + '...';" + " }" + " x += arg;" + "}", "for(var i in booyah){if(i>0)x+=\", \";" + "var arg=\"foo\";if(arg.length>40)arg=arg.substr(0,40)+\"...\";" + "x+=arg}"); // Test with function expressions in another function call test("function A(){}" + "if(0){function B(){}}win.setTimeout(function(){A()})", "function A(){}" + "if(0);win.setTimeout(function(){A()})"); // Test with recursive functions test("function A(){A()}function B(){B()}B()", "function B(){B()}B()"); // Test with multiple var declarations. test("var x,y=2,z=3;A(x);B(z);var a,b,c=4;C()", "var x,z=3;A(x);B(z);C()"); // Test with for loop declarations test("for(var i=0,j=0;i<10;){}" + "for(var x=0,y=0;;y++){}" + "for(var a,b;;){a}" + "for(var c,d;;);" + "for(var item in items){}", "for(var i=0;i<10;);" + "for(var y=0;;y++);" + "for(var a;;)a;" + "for(;;);" + "for(var item in items);"); // Test multiple passes required test("var a,b,c,d;var e=[b,c];var x=e[3];var f=[d];print(f[0])", "var d;var f=[d];print(f[0])"); // Test proper scoping (static vs dynamic) test("var x;function A(){var x;B()}function B(){print(x)}A()", "var x;function A(){B()}function B(){print(x)}A()"); // Test closures in a return statement test("function A(){var x;return function(){print(x)}}A()", "function A(){var x;return function(){print(x)}}A()"); // Test other closures, multiple passes test("function A(){}function B(){" + "var c,d,e,f,g,h;" + "function C(){print(c)}" + "var handler=function(){print(d)};" + "var handler2=function(){handler()};" + "e=function(){print(e)};" + "if(1){function G(){print(g)}}" + "arr=[function(){print(h)}];" + "return function(){print(f)}}B()", "function B(){" + "var f,h;" + "if(1);" + "arr=[function(){print(h)}];" + "return function(){print(f)}}B()"); // Test exported names test("var a,b=1; function _A1() {this.foo(a)}", "var a;function _A1(){this.foo(a)}"); // Test undefined (i.e. externally defined) names test("undefinedVar = 1", "undefinedVar=1"); // Test unused vars with side effects test("var a,b=foo(),c=i++,d;var e=boo();var f;print(d);", "foo(); i++; var d; boo(); print(d)"); test("var a,b=foo()", "foo()"); test("var b=foo(),a", "foo()"); test("var a,b=foo(a)", "var a; foo(a);"); } public void testFunctionArgRemoval() { // remove all function arguments test("var b=function(c,d){return};b(1,2)", "var b=function(){return};b(1,2)"); // remove no function arguments testSame("var b=function(c,d){return c+d};b(1,2)"); testSame("var b=function(e,f,c,d){return c+d};b(1,2)"); // remove some function arguments test("var b=function(c,d,e,f){return c+d};b(1,2)", "var b=function(c,d){return c+d};b(1,2)"); test("var b=function(e,c,f,d,g){return c+d};b(1,2)", "var b=function(e,c,f,d){return c+d};b(1,2)"); } public void testFunctionArgRemovalFromCallSites() { this.modifyCallSites = true; // remove all function arguments test("var b=function(c,d){return};b(1,2)", "var b=function(){return};b()"); // remove no function arguments testSame("var b=function(c,d){return c+d};b(1,2)"); test("var b=function(e,f,c,d){return c+d};b(1,2)", "var b=function(c,d){return c+d};b()"); // remove some function arguments test("var b=function(c,d,e,f){return c+d};b(1,2)", "var b=function(c,d){return c+d};b(1,2)"); test("var b=function(e,c,f,d,g){return c+d};b(1,2)", "var b=function(c,d){return c+d};b(2)"); } public void testFunctionsDeadButEscaped() { testSame("function b(a) { a = 1; print(arguments[0]) }; b(6)"); testSame("function b(a) { a = 1; arguments=1; }; b(6)"); testSame("function b(a) { var c = 2; a = c; print(arguments[0]) }; b(6)"); } public void testVarInControlStructure() { test("if (true) var b = 3;", "if(true);"); test("if (true) var b = 3; else var c = 5;", "if(true);else;"); test("while (true) var b = 3;", "while(true);"); test("for (;;) var b = 3;", "for(;;);"); test("do var b = 3; while(true)", "do;while(true)"); test("with (true) var b = 3;", "with(true);"); test("f: var b = 3;","f:{}"); } public void testRValueHoisting() { test("var x = foo();", "foo()"); test("var x = {a: foo()};", "({a:foo()})"); test("var x=function y(){}", ""); } public void testModule() { test(createModules( "var unreferenced=1; function x() { foo(); }" + "function uncalled() { var x; return 2; }", "var a,b; function foo() { this.foo(a); } x()"), new String[] { "function x(){foo()}", "var a;function foo(){this.foo(a)}x()" }); } public void testRecursiveFunction1() { testSame("(function x(){return x()})()"); } public void testRecursiveFunction2() { test("var x = 3; (function x() { return x(); })();", "(function x$$1(){return x$$1()})()"); } public void testFunctionWithName1() { test("var x=function f(){};x()", "var x=function(){};x()"); preserveFunctionExpressionNames = true; testSame("var x=function f(){};x()"); } public void testFunctionWithName2() { test("foo(function bar(){})", "foo(function(){})"); preserveFunctionExpressionNames = true; testSame("foo(function bar(){})"); } public void testRemoveGlobal1() { removeGlobal = false; testSame("var x=1"); test("var y=function(x){var z;}", "var y=function(x){}"); } public void testRemoveGlobal2() { removeGlobal = false; testSame("var x=1"); test("function y(x){var z;}", "function y(x){}"); } public void testRemoveGlobal3() { removeGlobal = false; testSame("var x=1"); test("function x(){function y(x){var z;}y()}", "function x(){function y(x){}y()}"); } public void testRemoveGlobal4() { removeGlobal = false; testSame("var x=1"); test("function x(){function y(x){var z;}}", "function x(){}"); } public void testIssue168a() { test("function _a(){" + " (function(x){ _b(); })(1);" + "}" + "function _b(){" + " _a();" + "}", "function _a(){(function(){_b()})(1)}" + "function _b(){_a()}"); } public void testIssue168b() { removeGlobal = false; test("function a(){" + " (function(x){ b(); })(1);" + "}" + "function b(){" + " a();" + "}", "function a(){(function(x){b()})(1)}" + "function b(){a()}"); } public void testUnusedAssign1() { test("var x = 3; x = 5;", ""); } public void testUnusedAssign2() { test("function f(a) { a = 3; } this.x = f;", "function f(){} this.x=f"); } public void testUnusedAssign3() { // e can't be removed, so we don't try to remove the dead assign. // We might be able to improve on this case. test("try { throw ''; } catch (e) { e = 3; }", "try{throw\"\";}catch(e){e=3}"); } public void testUnusedAssign4() { test("function f(a, b) { this.foo(b); a = 3; } this.x = f;", "function f(a,b){this.foo(b);}this.x=f"); } public void testUnusedAssign5() { test("var z = function f() { f = 3; }; z();", "var z=function(){};z()"); } public void testUnusedAssign5b() { test("var z = function f() { f = alert(); }; z();", "var z=function(){alert()};z()"); } public void testUnusedAssign6() { test("var z; z = 3;", ""); } public void testUnusedAssign6b() { test("var z; z = alert();", "alert()"); } public void testUnusedAssign7() { // This loop is normalized to "var i;for(i in..." test("var a = 3; for (var i in {}) { i = a; }", // TODO(johnlenz): "i = a" should be removed here. "var a = 3; var i; for (i in {}) {i = a;}"); } public void testUnusedAssign8() { // This loop is normalized to "var i;for(i in..." test("var a = 3; for (var i in {}) { i = a; } alert(a);", // TODO(johnlenz): "i = a" should be removed here. "var a = 3; var i; for (i in {}) {i = a} alert(a);"); } public void testUnusedPropAssign1() { test("var x = {}; x.foo = 3;", ""); } public void testUnusedPropAssign1b() { test("var x = {}; x.foo = alert();", "alert()"); } public void testUnusedPropAssign2() { test("var x = {}; x['foo'] = 3;", ""); } public void testUnusedPropAssign2b() { test("var x = {}; x[alert()] = alert();", "alert(),alert()"); } public void testUnusedPropAssign3() { test("var x = {}; x['foo'] = {}; x['bar'] = 3", ""); } public void testUnusedPropAssign3b() { test("var x = {}; x[alert()] = alert(); x[alert() + alert()] = alert()", "alert(),alert();(alert() + alert()),alert()"); } public void testUnusedPropAssign4() { test("var x = {foo: 3}; x['foo'] = 5;", ""); } public void testUnusedPropAssign5() { test("var x = {foo: bar()}; x['foo'] = 5;", "var x={foo:bar()};x[\"foo\"]=5"); } public void testUnusedPropAssign6() { test("var x = function() {}; x.prototype.bar = function() {};", ""); } public void testUnusedPropAssign7() { test("var x = {}; x[x.foo] = x.bar;", ""); } public void testUnusedPropAssign7b() { testSame("var x = {}; x[x.foo] = alert(x.bar);"); } public void testUnusedPropAssign7c() { test("var x = {}; x[alert(x.foo)] = x.bar;", "var x={};x[alert(x.foo)]=x.bar"); } public void testUsedPropAssign1() { test("function f(x) { x.bar = 3; } f({});", "function f(x){x.bar=3}f({})"); } public void testUsedPropAssign2() { test("try { throw z; } catch (e) { e.bar = 3; }", "try{throw z;}catch(e){e.bar=3}"); } public void testUsedPropAssign3() { // This pass does not do flow analysis. test("var x = {}; x.foo = 3; x = bar();", "var x={};x.foo=3;x=bar()"); } public void testUsedPropAssign4() { test("var y = foo(); var x = {}; x.foo = 3; y[x.foo] = 5;", "var y=foo();var x={};x.foo=3;y[x.foo]=5"); } public void testUsedPropAssign5() { test("var y = foo(); var x = 3; y[x] = 5;", "var y=foo();var x=3;y[x]=5"); } public void testUsedPropAssign6() { test("var x = newNodeInDom(doc); x.innerHTML = 'new text';", "var x=newNodeInDom(doc);x.innerHTML=\"new text\""); } public void testUsedPropAssign7() { testSame("var x = {}; for (x in alert()) { x.foo = 3; }"); } public void testUsedPropAssign8() { testSame("for (var x in alert()) { x.foo = 3; }"); } public void testUsedPropAssign9() { testSame( "var x = {}; x.foo = newNodeInDom(doc); x.foo.innerHTML = 'new test';"); } public void testDependencies1() { test("var a = 3; var b = function() { alert(a); };", ""); } public void testDependencies1b() { test("var a = 3; var b = alert(function() { alert(a); });", "var a=3;alert(function(){alert(a)})"); } public void testDependencies1c() { test("var a = 3; var _b = function() { alert(a); };", "var a=3;var _b=function(){alert(a)}"); } public void testDependencies2() { test("var a = 3; var b = 3; b = function() { alert(a); };", ""); } public void testDependencies2b() { test("var a = 3; var b = 3; b = alert(function() { alert(a); });", "var a=3;alert(function(){alert(a)})"); } public void testDependencies2c() { testSame("var a=3;var _b=3;_b=function(){alert(a)}"); } public void testGlobalVarReferencesLocalVar() { testSame("var a=3;function f(){var b=4;a=b}alert(a + f())"); } public void testLocalVarReferencesGlobalVar1() { testSame("var a=3;function f(b, c){b=a; alert(b + c);} f();"); } public void testLocalVarReferencesGlobalVar2() { test("var a=3;function f(b, c){b=a; alert(c);} f();", "function f(b, c) { alert(c); } f();"); this.modifyCallSites = true; test("var a=3;function f(b, c){b=a; alert(c);} f();", "function f(c) { alert(c); } f();"); } public void testNestedAssign1() { test("var b = null; var a = (b = 3); alert(a);", "var a = 3; alert(a);"); } public void testNestedAssign2() { test("var a = 1; var b = 2; var c = (b = a); alert(c);", "var a = 1; var c = a; alert(c);"); } public void testNestedAssign3() { test("var b = 0; var z; z = z = b = 1; alert(b);", "var b = 0; b = 1; alert(b);"); } public void testCallSiteInteraction() { this.modifyCallSites = true; testSame("var b=function(){return};b()"); testSame("var b=function(c){return c};b(1)"); test("var b=function(c){};b.call(null, x)", "var b=function(){};b.call(null)"); test("var b=function(c){};b.apply(null, x)", "var b=function(){};b.apply(null, x)"); test("var b=function(c){return};b(1)", "var b=function(){return};b()"); test("var b=function(c){return};b(1,2)", "var b=function(){return};b()"); test("var b=function(c){return};b(1,2);b(3,4)", "var b=function(){return};b();b()"); // Here there is a unknown reference to the function so we can't // change the signature. test("var b=function(c,d){return d};b(1,2);b(3,4);b.length", "var b=function(c,d){return d};b(0,2);b(0,4);b.length"); test("var b=function(c){return};b(1,2);b(3,new x())", "var b=function(){return};b();b(new x())"); test("var b=function(c){return};b(1,2);b(new x(),4)", "var b=function(){return};b();b(new x())"); test("var b=function(c,d){return d};b(1,2);b(new x(),4)", "var b=function(c,d){return d};b(0,2);b(new x(),4)"); test("var b=function(c,d,e){return d};b(1,2,3);b(new x(),4,new x())", "var b=function(c,d){return d};b(0,2);b(new x(),4,new x())"); // Recursive calls are OK. test("var b=function(c,d){b(1,2);return d};b(3,4);b(5,6)", "var b=function(d){b(2);return d};b(4);b(6)"); testSame("var b=function(c){return arguments};b(1,2);b(3,4)"); // remove all function arguments test("var b=function(c,d){return};b(1,2)", "var b=function(){return};b()"); // remove no function arguments testSame("var b=function(c,d){return c+d};b(1,2)"); // remove some function arguments test("var b=function(e,f,c,d){return c+d};b(1,2)", "var b=function(c,d){return c+d};b()"); test("var b=function(c,d,e,f){return c+d};b(1,2)", "var b=function(c,d){return c+d};b(1,2)"); test("var b=function(e,c,f,d,g){return c+d};b(1,2)", "var b=function(c,d){return c+d};b(2)"); // multiple definitions of "b", the parameters can be removed but // the call sites are left unmodified for now. test("var b=function(c,d){};var b=function(e,f){};b(1,2)", "var b=function(){};var b=function(){};b(1,2)"); } public void testCallSiteInteraction_contructors() { this.modifyCallSites = true; // The third level tests that the functions which have already been looked // at get re-visited if they are changed by a call site removal. test("var Ctor1=function(a,b){return a};" + "var Ctor2=function(a,b){Ctor1.call(this,a,b)};" + "goog$inherits(Ctor2, Ctor1);" + "new Ctor2(1,2)", "var Ctor1=function(a){return a};" + "var Ctor2=function(a){Ctor1.call(this,a)};" + "goog$inherits(Ctor2, Ctor1);" + "new Ctor2(1)"); } public void testFunctionArgRemovalCausingInconsistency() { this.modifyCallSites = true; // Test the case where an unused argument is removed and the argument // contains a call site in its subtree (will cause the call site's parent // pointer to be null). test("var a=function(x,y){};" + "var b=function(z){};" + "a(new b, b)", "var a=function(){};" + "var b=function(){};" + "a(new b)"); } public void testRemoveUnusedVarsPossibleNpeCase() { this.modifyCallSites = true; test("var a = [];" + "var register = function(callback) {a[0] = callback};" + "register(function(transformer) {});" + "register(function(transformer) {});", "var register=function(){};register();register()"); } public void testDoNotOptimizeJSCompiler_renameProperty() { this.modifyCallSites = true; // Only the function definition can be modified, none of the call sites. test("function JSCompiler_renameProperty(a) {};" + "JSCompiler_renameProperty('a');", "function JSCompiler_renameProperty() {};" + "JSCompiler_renameProperty('a');"); } public void testDoNotOptimizeJSCompiler_ObjectPropertyString() { this.modifyCallSites = true; test("function JSCompiler_ObjectPropertyString(a, b) {};" + "JSCompiler_ObjectPropertyString(window,'b');", "function JSCompiler_ObjectPropertyString() {};" + "JSCompiler_ObjectPropertyString(window,'b');"); } public void testDoNotOptimizeSetters() { testSame("({set s(a) {}})"); } public void testRemoveSingletonClass1() { test("function goog$addSingletonGetter(a){}" + "/**@constructor*/function a(){}" + "goog$addSingletonGetter(a);", ""); } public void testRemoveInheritedClass1() { test("function goog$inherits(){}" + "/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "goog$inherits(b,a); new a", "function a(){} new a"); } public void testRemoveInheritedClass2() { test("function goog$inherits(){}" + "function goog$mixin(){}" + "/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "/**@constructor*/function c(){}" + "goog$inherits(b,a);" + "goog$mixin(c.prototype,b.prototype);", ""); } public void testRemoveInheritedClass3() { testSame("/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "goog$inherits(b,a); new b"); } public void testRemoveInheritedClass4() { testSame("function goog$inherits(){}" + "/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "goog$inherits(b,a);" + "/**@constructor*/function c(){}" + "goog$inherits(c,b); new c"); } public void testRemoveInheritedClass5() { test("function goog$inherits(){}" + "/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "goog$inherits(b,a);" + "/**@constructor*/function c(){}" + "goog$inherits(c,b); new b", "function goog$inherits(){}" + "function a(){}" + "function b(){}" + "goog$inherits(b,a); new b"); } public void testRemoveInheritedClass6() { test("function goog$mixin(){}" + "/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "/**@constructor*/function c(){}" + "/**@constructor*/function d(){}" + "goog$mixin(b.prototype,a.prototype);" + "goog$mixin(c.prototype,a.prototype); new c;" + "goog$mixin(d.prototype,a.prototype)", "function goog$mixin(){}" + "function a(){}" + "function c(){}" + "goog$mixin(c.prototype,a.prototype); new c"); } public void testRemoveInheritedClass7() { test("function goog$mixin(){}" + "/**@constructor*/function a(){alert(goog$mixin(a, a))}" + "/**@constructor*/function b(){}" + "goog$mixin(b.prototype,a.prototype); new a", "function goog$mixin(){}" + "function a(){alert(goog$mixin(a, a))} new a"); } public void testRemoveInheritedClass8() { test("/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "/**@constructor*/function c(){}" + "b.inherits(a);c.mixin(b.prototype)", ""); } public void testRemoveInheritedClass9() { testSame("/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "/**@constructor*/function c(){}" + "b.inherits(a);c.mixin(b.prototype);new c"); } public void testRemoveInheritedClass10() { test("function goog$inherits(){}" + "/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "goog$inherits(b,a); new a;" + "var c = a; var d = a.g; new b", "function goog$inherits(){}" + "function a(){} function b(){} goog$inherits(b,a); new a; new b"); } public void testRemoveInheritedClass11() { testSame("function goog$inherits(){}" + "function goog$mixin(a,b){goog$inherits(a,b)}" + "/**@constructor*/function a(){}" + "/**@constructor*/function b(){}" + "goog$mixin(b.prototype,a.prototype);new b"); } public void testRemoveInheritedClass12() { testSame("function goog$inherits(){}" + "/**@constructor*/function a(){}" + "var b = {};" + "goog$inherits(b.foo, a)"); } public void testReflectedMethods() { this.modifyCallSites = true; testSame( "/** @constructor */" + "function Foo() {}" + "Foo.prototype.handle = function(x, y) { alert(y); };" + "var x = goog.reflect.object(Foo, {handle: 1});" + "for (var i in x) { x[i].call(x); }" + "window['Foo'] = Foo;"); } public void testIssue618_1() { this.removeGlobal = false; testSame( "function f() {\n" + " var a = [], b;\n" + " a.push(b = []);\n" + " b[0] = 1;\n" + " return a;\n" + "}"); } public void testIssue618_2() { this.removeGlobal = false; testSame( "var b;\n" + "a.push(b = []);\n" + "b[0] = 1;\n"); } }