/* * 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; /** * Tests for {@link CrossModuleCodeMotion}. * */ public class CrossModuleCodeMotionTest extends CompilerTestCase { private static final String EXTERNS = "alert"; public CrossModuleCodeMotionTest() { super(EXTERNS); } @Override public void setUp() { super.enableLineNumberCheck(true); } @Override public CompilerPass getProcessor(Compiler compiler) { return new CrossModuleCodeMotion(compiler, compiler.getModuleGraph()); } public void testFunctionMovement1() { // This tests lots of things: // 1) f1 is declared in m1, and used in m2. Move it to m2 // 2) f2 is declared in m1, and used in m3 twice. Move it to m3 // 3) f3 is declared in m1, and used in m2+m3. It stays put // 4) g declared in m1 and never used. It stays put // 5) h declared in m2 and never used. It stays put // 6) f4 declared in m1 and used in m2 as var. It moves to m2 JSModule[] modules = createModuleStar( // m1 "function f1(a) { alert(a); }" + "function f2(a) { alert(a); }" + "function f3(a) { alert(a); }" + "function f4() { alert(1); }" + "function g() { alert('ciao'); }", // m2 "f1('hi'); f3('bye'); var a = f4;" + "function h(a) { alert('h:' + a); }", // m3 "f2('hi'); f2('hi'); f3('bye');"); test(modules, new String[] { // m1 "function f3(a) { alert(a); }" + "function g() { alert('ciao'); }", // m2 "function f4() { alert(1); }" + "function f1(a) { alert(a); }" + "f1('hi'); f3('bye'); var a = f4;" + "function h(a) { alert('h:' + a); }", // m3 "function f2(a) { alert(a); }" + "f2('hi'); f2('hi'); f3('bye');", }); } public void testFunctionMovement2() { // having f declared as a local variable should block the migration to m2 JSModule[] modules = createModuleStar( // m1 "function f(a) { alert(a); }" + "function g() {var f = 1; f++}", // m2 "f(1);"); test(modules, new String[] { // m1 "function g() {var f = 1; f++}", // m2 "function f(a) { alert(a); }" + "f(1);", }); } public void testFunctionMovement3() { // having f declared as a arg should block the migration to m2 JSModule[] modules = createModuleStar( // m1 "function f(a) { alert(a); }" + "function g(f) {f++}", // m2 "f(1);"); test(modules, new String[] { // m1 "function g(f) {f++}", // m2 "function f(a) { alert(a); }" + "f(1);", }); } public void testFunctionMovement4() { // Try out moving a function which returns a closure JSModule[] modules = createModuleStar( // m1 "function f(){return function(a){}}", // m2 "var a = f();" ); test(modules, new String[] { // m1 "", // m2 "function f(){return function(a){}}" + "var a = f();", }); } public void testFunctionMovement5() { // Try moving a recursive function [using factorials for kicks] JSModule[] modules = createModuleStar( // m1 "function f(n){return (n<1)?1:f(n-1)}", // m2 "var a = f(4);" ); test(modules, new String[] { // m1 "", // m2 "function f(n){return (n<1)?1:f(n-1)}" + "var a = f(4);", }); } public void testFunctionMovement5b() { // Try moving a recursive function declared differently. JSModule[] modules = createModuleStar( // m1 "var f = function(n){return (n<1)?1:f(n-1)};", // m2 "var a = f(4);" ); test(modules, new String[] { // m1 "", // m2 "var f = function(n){return (n<1)?1:f(n-1)};" + "var a = f(4);", }); } public void testFunctionMovement6() { // Try out moving to the common ancestor JSModule[] modules = createModuleChain( // m1 "function f(){return 1}", // m2 "var a = f();", // m3 "var b = f();" ); test(modules, new String[] { // m1 "", // m2 "function f(){return 1}" + "var a = f();", // m3 "var b = f();", }); } public void testFunctionMovement7() { // Try out moving to the common ancestor with deeper ancestry chain JSModule[] modules = createModules( // m1 "function f(){return 1}", // m2 "", // m3 "var a = f();", // m4 "var b = f();", // m5 "var c = f();" ); modules[1].addDependency(modules[0]); modules[2].addDependency(modules[1]); modules[3].addDependency(modules[1]); modules[4].addDependency(modules[1]); test(modules, new String[] { // m1 "", // m2 "function f(){return 1}", // m3 "var a = f();", // m4 "var b = f();", // m5 "var c = f();", }); } public void testFunctionMovement8() { // Check what happens with named functions JSModule[] modules = createModuleChain( // m1 "var v = function f(){return 1}", // m2 "v();" ); test(modules, new String[] { // m1 "", // m2 "var v = function f(){return 1};" + "v();", }); } public void testFunctionNonMovement1() { // This tests lots of things: // 1) we can't move it if it is a class with non-const attributes accessed // 2) if it's in an if statement, we can't move it // 3) if it's in an while statement, we can't move it [with some extra // block elements] testSame(createModuleStar( // m1 "function f(){};f.prototype.bar=new f;" + "if(a)function f2(){}" + "{{while(a)function f3(){}}}", // m2 "var a = new f();f2();f3();")); } public void testFunctionNonMovement2() { // A generic case where 2 modules depend on the first one. But it's the // common ancestor, so we can't move. testSame(createModuleStar( // m1 "function f(){return 1}", // m2 "var a = f();", // m3 "var b = f();")); } public void testClassMovement1() { test(createModuleStar( // m1 "function f(){} f.prototype.bar=function (){};", // m2 "var a = new f();"), new String[] { "", "function f(){} f.prototype.bar=function (){};" + "var a = new f();" }); } public void testClassMovement2() { // NOTE: this is the result of two iterations test(createModuleChain( // m1 "function f(){} f.prototype.bar=3; f.prototype.baz=5;", // m2 "f.prototype.baq = 7;", // m3 "f.prototype.baz = 9;", // m4 "var a = new f();"), new String[] { // m1 "", // m2 "", // m3 "function f(){} f.prototype.bar=3; f.prototype.baz=5;" + "f.prototype.baq = 7;" + "f.prototype.baz = 9;", // m4 "var a = new f();" }); } public void testClassMovement3() { // NOTE: this is the result of two iterations test(createModuleChain( // m1 "var f = function() {}; f.prototype.bar=3; f.prototype.baz=5;", // m2 "f = 7;", // m3 "f = 9;", // m4 "f = 11;"), new String[] { // m1 "", // m2 "", // m3 "var f = function() {}; f.prototype.bar=3; f.prototype.baz=5;" + "f = 7;" + "f = 9;", // m4 "f = 11;" }); } public void testClassMovement4() { testSame(createModuleStar( // m1 "function f(){} f.prototype.bar=3; f.prototype.baz=5;", // m2 "f.prototype.baq = 7;", // m3 "var a = new f();")); } public void testClassMovement5() { JSModule[] modules = createModules( // m1 "function f(){} f.prototype.bar=3; f.prototype.baz=5;", // m2 "", // m3 "f.prototype.baq = 7;", // m4 "var a = new f();"); modules[1].addDependency(modules[0]); modules[2].addDependency(modules[1]); modules[3].addDependency(modules[1]); test(modules, new String[] { // m1 "", // m2 "function f(){} f.prototype.bar=3; f.prototype.baz=5;", // m3 "f.prototype.baq = 7;", // m4 + "var a = new f();" }); } public void testClassMovement6() { test(createModuleChain( // m1 "function Foo(){} function Bar(){} goog.inherits(Bar, Foo);" + "new Foo();", // m2 "new Bar();"), new String[] { // m1 "function Foo(){} new Foo();", // m2 "function Bar(){} goog.inherits(Bar, Foo); new Bar();" }); } public void testClassMovement7() { testSame(createModuleChain( // m1 "function Foo(){} function Bar(){} goog.inherits(Bar, Foo);" + "new Bar();", // m2 "new Foo();")); } public void testStubMethodMovement1() { test(createModuleChain( // m1 "function Foo(){} " + "Foo.prototype.bar = JSCompiler_stubMethod(x);", // m2 "new Foo();"), new String[] { // m1 "", "function Foo(){} " + "Foo.prototype.bar = JSCompiler_stubMethod(x);" + "new Foo();" }); } public void testStubMethodMovement2() { test(createModuleChain( // m1 "function Foo(){} " + "Foo.prototype.bar = JSCompiler_unstubMethod(x);", // m2 "new Foo();"), new String[] { // m1 "", "function Foo(){} " + "Foo.prototype.bar = JSCompiler_unstubMethod(x);" + "new Foo();" }); } public void testNoMoveSideEffectProperty() { testSame(createModuleChain( // m1 "function Foo(){} " + "Foo.prototype.bar = createSomething();", // m2 "new Foo();")); } public void testAssignMovement() { test(createModuleChain( // m1 "var f = 3;" + "f = 5;", // m2 "var h = f;"), new String[] { // m1 "", // m2 "var f = 3;" + "f = 5;" + "var h = f;" }); // don't move nested assigns testSame(createModuleChain( // m1 "var f = 3;" + "var g = f = 5;", // m2 "var h = f;")); } public void testNoClassMovement2() { test(createModuleChain( // m1 "var f = {};" + "f.h = 5;", // m2 "var h = f;"), new String[] { // m1 "", // m2 "var f = {};" + "f.h = 5;" + "var h = f;" }); // don't move nested getprop assigns testSame(createModuleChain( // m1 "var f = {};" + "var g = f.h = 5;", // m2 "var h = f;")); } public void testLiteralMovement1() { test(createModuleChain( // m1 "var f = {'hi': 'mom', 'bye': function() {}};", // m2 "var h = f;"), new String[] { // m1 "", // m2 "var f = {'hi': 'mom', 'bye': function() {}};" + "var h = f;" }); } public void testLiteralMovement2() { testSame(createModuleChain( // m1 "var f = {'hi': 'mom', 'bye': goog.nullFunction};", // m2 "var h = f;")); } public void testLiteralMovement3() { test(createModuleChain( // m1 "var f = ['hi', function() {}];", // m2 "var h = f;"), new String[] { // m1 "", // m2 "var f = ['hi', function() {}];" + "var h = f;" }); } public void testLiteralMovement4() { testSame(createModuleChain( // m1 "var f = ['hi', goog.nullFunction];", // m2 "var h = f;")); } public void testVarMovement1() { // test moving a variable JSModule[] modules = createModuleStar( // m1 "var a = 0;", // m2 "var x = a;" ); test(modules, new String[] { // m1 "", // m2 "var a = 0;" + "var x = a;", }); } public void testVarMovement2() { // Test moving 1 variable out of the block JSModule[] modules = createModuleStar( // m1 "var a = 0; var b = 1; var c = 2;", // m2 "var x = b;" ); test(modules, new String[] { // m1 "var a = 0; var c = 2;", // m2 "var b = 1;" + "var x = b;" }); } public void testVarMovement3() { // Test moving all variables out of the block JSModule[] modules = createModuleStar( // m1 "var a = 0; var b = 1;", // m2 "var x = a + b;" ); test(modules, new String[] { // m1 "", // m2 "var b = 1;" + "var a = 0;" + "var x = a + b;" }); } public void testVarMovement4() { // Test moving a function JSModule[] modules = createModuleStar( // m1 "var a = function(){alert(1)};", // m2 "var x = a;" ); test(modules, new String[] { // m1 "", // m2 "var a = function(){alert(1)};" + "var x = a;" }); } public void testVarMovement5() { // Don't move a function outside of scope testSame(createModuleStar( // m1 "var a = alert;", // m2 "var x = a;")); } public void testVarMovement6() { // Test moving a var with no assigned value JSModule[] modules = createModuleStar( // m1 "var a;", // m2 "var x = a;" ); test(modules, new String[] { // m1 "", // m2 "var a;" + "var x = a;" }); } public void testVarMovement7() { // Don't move a variable higher in the dependency tree testSame(createModuleStar( // m1 "function f() {g();}", // m2 "function g(){};")); } public void testVarMovement8() { JSModule[] modules = createModuleBush( // m1 "var a = 0;", // m2 -> m1 "", // m3 -> m2 "var x = a;", // m4 -> m2 "var y = a;" ); test(modules, new String[] { // m1 "", // m2 "var a = 0;", // m3 "var x = a;", // m4 "var y = a;" }); } public void testVarMovement9() { JSModule[] modules = createModuleTree( // m1 "var a = 0; var b = 1; var c = 3;", // m2 -> m1 "", // m3 -> m1 "", // m4 -> m2 "a;", // m5 -> m2 "a;c;", // m6 -> m3 "b;", // m7 -> m4 "b;c;" ); test(modules, new String[] { // m1 "var c = 3;", // m2 "var a = 0;", // m3 "var b = 1;", // m4 "a;", // m5 "a;c;", // m6 "b;", // m7 "b;c;" }); } public void testClone1() { test(createModuleChain( // m1 "function f(){} f.prototype.clone = function() { return new f };", // m2 "var a = (new f).clone();"), new String[] { // m1 "", "function f(){} f.prototype.clone = function() { return new f() };" + // m2 "var a = (new f).clone();" }); } public void testClone2() { test(createModuleChain( // m1 "function f(){}" + "f.prototype.cloneFun = function() {" + " return function() {new f}" + "};", // m2 "var a = (new f).cloneFun();"), new String[] { // m1 "", "function f(){}" + "f.prototype.cloneFun = function() {" + " return function() {new f}" + "};" + // m2 "var a = (new f).cloneFun();" }); } public void testBug4118005() { testSame(createModuleChain( // m1 "var m = 1;\n" + "(function () {\n" + " var x = 1;\n" + " m = function() { return x };\n" + "})();\n", // m2 "m();")); } public void testEmptyModule() { // When the dest module is empty, it might try to move the code to the // one of the modules that the empty module depends on. In some cases // this might ended up to be the same module as the definition of the code. // When that happens, CrossModuleCodeMotion might report a code change // while nothing is moved. This should not be a problem if we know all // modules are non-empty. JSModule m1 = new JSModule("m1"); m1.add(SourceFile.fromCode("m1", "function x() {}")); JSModule empty = new JSModule("empty"); empty.addDependency(m1); JSModule m2 = new JSModule("m2"); m2.add(SourceFile.fromCode("m2", "x()")); m2.addDependency(empty); JSModule m3 = new JSModule("m3"); m3.add(SourceFile.fromCode("m3", "x()")); m3.addDependency(empty); test(new JSModule[] {m1,empty,m2,m3}, new String[] { "", "function x() {}", "x()", "x()" }); } }