/* * 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.common.collect.Lists; import com.google.common.collect.Sets; import com.google.javascript.jscomp.AbstractCompiler.LifeCycleStage; import com.google.javascript.jscomp.FunctionInjector.CanInlineResult; import com.google.javascript.jscomp.FunctionInjector.InliningMode; import com.google.javascript.jscomp.NodeTraversal.Callback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import junit.framework.TestCase; import java.util.List; import java.util.Set; /** * Inline function tests. * @author johnlenz@google.com (John Lenz) */ public class FunctionInjectorTest extends TestCase { static final InliningMode INLINE_DIRECT = InliningMode.DIRECT; static final InliningMode INLINE_BLOCK = InliningMode.BLOCK; private boolean assumeStrictThis = false; private boolean assumeMinimumCapture = false; @Override protected void setUp() throws Exception { super.setUp(); assumeStrictThis = false; } private FunctionInjector getInjector() { Compiler compiler = new Compiler(); return new FunctionInjector( compiler, compiler.getUniqueNameIdSupplier(), true, assumeStrictThis, assumeMinimumCapture); } public void testIsSimpleFunction1() { assertTrue(getInjector().isDirectCallNodeReplacementPossible( prep("function f(){}"))); } public void testIsSimpleFunction2() { assertTrue(getInjector().isDirectCallNodeReplacementPossible( prep("function f(){return 0;}"))); } public void testIsSimpleFunction3() { assertTrue(getInjector().isDirectCallNodeReplacementPossible( prep("function f(){return x ? 0 : 1}"))); } public void testIsSimpleFunction4() { assertFalse(getInjector().isDirectCallNodeReplacementPossible( prep("function f(){return;}"))); } public void testIsSimpleFunction5() { assertFalse(getInjector().isDirectCallNodeReplacementPossible( prep("function f(){return 0; return 0;}"))); } public void testIsSimpleFunction6() { assertFalse(getInjector().isDirectCallNodeReplacementPossible( prep("function f(){var x=true;return x ? 0 : 1}"))); } public void testIsSimpleFunction7() { assertFalse(getInjector().isDirectCallNodeReplacementPossible( prep("function f(){if (x) return 0; else return 1}"))); } public void testCanInlineReferenceToFunction1() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){}; foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction2() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){}; foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction3() { // NOTE: FoldConstants will convert this to a empty function, // so there is no need to explicitly support it. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(){return;}; foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction4() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return;}; foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction5() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return true;}; foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction6() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return true;}; foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction7() { // In var initialization. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return true;}; var x=foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction8() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return true;}; var x=foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction9() { // In assignment. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return true;}; var x; x=foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction10() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return true;}; var x; x=foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction11() { // In expression. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return true;}; var x; x=x+foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction12() { // "foo" is not known to be side-effect free, it might change the value // of "x", so it can't be inlined. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(){return true;}; var x; x=x+foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction12b() { // "foo" is not known to be side-effect free, it might change the value // of "x", so it can't be inlined. helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "function foo(){return true;}; var x; x=x+foo();", "foo", INLINE_BLOCK, true); } // TODO(nicksantos): Re-enable with side-effect detection. // public void testCanInlineReferenceToFunction13() { // // ... if foo is side-effect free we can inline here. // helperCanInlineReferenceToFunction(true, // "/** @nosideeffects */ function foo(){return true;};" + // "var x; x=x+foo();", "foo", INLINE_BLOCK); // } public void testCanInlineReferenceToFunction14() { // Simple call with parameters helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; foo(x);", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction15() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; foo(x);", "foo", INLINE_BLOCK); } // TODO(johnlenz): remove this constant once this has been proven in // production code. static final CanInlineResult NEW_VARS_IN_GLOBAL_SCOPE = CanInlineResult.YES; public void testCanInlineReferenceToFunction16() { // Function "foo" as it contains "var b" which // must be brought into the global scope. helperCanInlineReferenceToFunction(NEW_VARS_IN_GLOBAL_SCOPE, "function foo(a){var b;return a;}; foo(goo());", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction17() { // This doesn't bring names into the global name space. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return a;}; " + "function x() { foo(goo()); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction18() { // Parameter has side-effects. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return a;} foo(x++);", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction19() { // Parameter has mutable parameter referenced more than once. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return a+a} foo([]);", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction20() { helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return a+a} foo({});", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction21() { helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return a+a} foo(new Date);", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction22() { helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return a+a} foo(true && new Date);", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction23() { // variables to global scope. helperCanInlineReferenceToFunction(NEW_VARS_IN_GLOBAL_SCOPE, "function foo(a){return a;}; foo(x++);", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction24() { // ... this is OK, because it doesn't introduce a new global name. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return a;}; " + "function x() { foo(x++); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction25() { // Parameter has side-effects. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return a+a;}; foo(x++);", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction26() { helperCanInlineReferenceToFunction(NEW_VARS_IN_GLOBAL_SCOPE, "function foo(a){return a+a;}; foo(x++);", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction27() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return a+a;}; " + "function x() { foo(x++); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction28() { // Parameter has side-effects. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; foo(goo());", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction29() { helperCanInlineReferenceToFunction(NEW_VARS_IN_GLOBAL_SCOPE, "function foo(a){return true;}; foo(goo());", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction30() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; " + "function x() { foo(goo()); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction31() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a) {return true;}; " + "function x() {foo.call(this, 1);}", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction32() { helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() { foo.apply(this, [1]); }", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction33() { // No special handling is required for method calls passing this. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; " + "function x() { foo.bar(this, 1); }", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction34() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; " + "function x() { foo.call(this, goo()); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction35() { helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() { foo.apply(this, goo()); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction36() { helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; " + "function x() { foo.bar(this, goo()); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction37() { helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() { foo.call(null, 1); }", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction38() { assumeStrictThis = false; helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() { foo.call(null, goo()); }", "foo", INLINE_BLOCK); assumeStrictThis = true; helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; " + "function x() { foo.call(null, goo()); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction39() { helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() { foo.call(bar, 1); }", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction40() { assumeStrictThis = false; helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() { foo.call(bar, goo()); }", "foo", INLINE_BLOCK); assumeStrictThis = true; helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; " + "function x() { foo.call(bar, goo()); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction41() { helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() { foo.call(new bar(), 1); }", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction42() { assumeStrictThis = false; helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() { foo.call(new bar(), goo()); }", "foo", INLINE_BLOCK); assumeStrictThis = true; helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(a){return true;}; " + "function x() { foo.call(new bar(), goo()); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction43() { // Handle the case of a missing 'this' value in a call. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(){return true;}; " + "function x() { foo.call(); }", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction44() { assumeStrictThis = false; // Handle the case of a missing 'this' value in a call. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(){return true;}; " + "function x() { foo.call(); }", "foo", INLINE_BLOCK); assumeStrictThis = true; // Handle the case of a missing 'this' value in a call. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return true;}; " + "function x() { foo.call(); }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction45() { // Call with inner function expression. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return function() {return true;}}; foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction46() { // Call with inner function expression. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return function() {return true;}}; foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction47() { // Call with inner function expression and variable decl. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(){var a; return function() {return true;}}; foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction48() { // Call with inner function expression and variable decl. // TODO(johnlenz): should we validate no values in scope? helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){var a; return function() {return true;}}; foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction49() { // Call with inner function expression. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return function() {var a; return true;}}; foo();", "foo", INLINE_DIRECT); } public void testCanInlineReferenceToFunction50() { // Call with inner function expression. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){return function() {var a; return true;}}; foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunction51() { // Call with inner function statement. helperCanInlineReferenceToFunction(CanInlineResult.YES, "function foo(){function x() {var a; return true;} return x}; foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression1() { // Call in if condition helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() { if (foo(1)) throw 'test'; }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression2() { // Call in return expression helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() { return foo(1); }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression3() { // Call in switch expression helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() { switch(foo(1)) { default:break; } }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression4() { // Call in hook condition helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {foo(1)?0:1 }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression5() { // Call in hook side-effect free condition helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() {true?foo(1):1 }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression5a() { // Call in hook side-effect free condition helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {true?foo(1):1 }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression6() { // Call in expression statement "condition" helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {foo(1) && 1 }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression7() { // Call in expression statement after side-effect free "condition" helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() {1 && foo(1) }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression7a() { // Call in expression statement after side-effect free "condition" helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {1 && foo(1) }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression8() { // Call in expression statement after side-effect free operator helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {1 + foo(1) }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression9() { // Call in VAR expression. helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {var b = 1 + foo(1)}", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression10() { // Call in assignment expression. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(a){return true;}; " + "function x() {var b; b += 1 + foo(1) }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression10a() { // Call in assignment expression. helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {var b; b += 1 + foo(1) }", "foo", INLINE_BLOCK, true); } // TODO(nicksantos): Re-enable with side-effect detection. // public void testCanInlineReferenceToFunctionInExpression11() { // helperCanInlineReferenceToFunction(true, // "/** @nosideeffects */ function foo(a){return true;}; " + // "function x() {var b; b += 1 + foo(1) }", // "foo", INLINE_BLOCK); // } public void testCanInlineReferenceToFunctionInExpression12() { helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {var a,b,c; a = b = c = foo(1) }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression13() { helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(a){return true;}; " + "function x() {var a,b,c; a = b = c = 1 + foo(1) }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression14() { // ... foo can not be inlined because of possible changes to "c". helperCanInlineReferenceToFunction(CanInlineResult.NO, "var a = {}, b = {}, c;" + "a.test = 'a';" + "b.test = 'b';" + "c = a;" + "function foo(){c = b; return 'foo'};" + "c.test=foo();", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression14a() { // ... foo can be inlined despite possible changes to "c". helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "var a = {}, b = {}, c;" + "a.test = 'a';" + "b.test = 'b';" + "c = a;" + "function foo(){c = b; return 'foo'};" + "c.test=foo();", "foo", INLINE_BLOCK, true); } // TODO(nicksantos): Re-enable with side-effect detection. // public void testCanInlineReferenceToFunctionInExpression15() { // // ... foo can be inlined as it is side-effect free. // helperCanInlineReferenceToFunction(true, // "var a = {}, b = {}, c;" + // "a.test = 'a';" + // "b.test = 'b';" + // "c = a;" + // "/** @nosideeffects */ function foo(){return 'foo'};" + // "c.test=foo();", // "foo", INLINE_BLOCK); // } // public void testCanInlineReferenceToFunctionInExpression16() { // // ... foo can not be inlined because of possible side-effects of x() // helperCanInlineReferenceToFunction(false, // "var a = {}, b = {}, c;" + // "a.test = 'a';" + // "b.test = 'b';" + // "c = a;" + // "function x(){return c};" + // "/** @nosideeffects */ function foo(){return 'foo'};" + // "x().test=foo();", // "foo", INLINE_BLOCK); // } // public void testCanInlineReferenceToFunctionInExpression17() { // // ... foo can be inlined because of x() is side-effect free. // helperCanInlineReferenceToFunction(true, // "var a = {}, b = {}, c;" + // "a.test = 'a';" + // "b.test = 'b';" + // "c = a;" + // "/** @nosideeffects */ function x(){return c};" + // "/** @nosideeffects */ function foo(){return 'foo'};" + // "x().test=foo();", // "foo", INLINE_BLOCK); // } public void testCanInlineReferenceToFunctionInExpression18() { // Call in within a call helperCanInlineReferenceToFunction(CanInlineResult.AFTER_PREPARATION, "function foo(){return _g();}; " + "function x() {1 + foo()() }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression19() { // ... unless foo is known to be side-effect free, it might actually // change the value of "_g" which would unfortunately change the behavior, // so we can't inline here. helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(){return a;}; " + "function x() {1 + _g(foo()) }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression19a() { // ... unless foo is known to be side-effect free, it might actually // change the value of "_g" which would unfortunately change the behavior, // so we can't inline here. helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "function foo(){return a;}; " + "function x() {1 + _g(foo()) }", "foo", INLINE_BLOCK, true); } // TODO(nicksantos): Re-enable with side-effect detection. // public void testCanInlineReferenceToFunctionInExpression20() { // helperCanInlineReferenceToFunction(true, // "/** @nosideeffects */ function foo(){return a;}; " + // "function x() {1 + _g(foo()) }", // "foo", INLINE_BLOCK); // } public void testCanInlineReferenceToFunctionInExpression21() { // Assignments to object are problematic if the call has side-effects, // as the object that is being referred to can change. // Note: This could be changed be inlined if we in some way make "z" // as not escaping from the local scope. helperCanInlineReferenceToFunction(CanInlineResult.NO, "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() { z.gack = foo(1) }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression21a() { // Assignments to object are problematic if the call has side-effects, // as the object that is being referred to can change. // Note: This could be changed be inlined if we in some way make "z" // as not escaping from the local scope. helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() { z.gack = foo(1) }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression22() { // ... foo() is after a side-effect helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(){return a;}; " + "function x() {1 + _g(_a(), foo()) }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression22a() { // ... foo() is after a side-effect helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "function foo(){return a;}; " + "function x() {1 + _g(_a(), foo()) }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInExpression23() { // ... foo() is after a side-effect helperCanInlineReferenceToFunction(CanInlineResult.NO, "function foo(){return a;}; " + "function x() {1 + _g(_a(), foo.call(this)) }", "foo", INLINE_BLOCK); } public void testCanInlineReferenceToFunctionInExpression23a() { // ... foo() is after a side-effect helperCanInlineReferenceToFunction( CanInlineResult.AFTER_PREPARATION, "function foo(){return a;}; " + "function x() {1 + _g(_a(), foo.call(this)) }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInLoop1() { helperCanInlineReferenceToFunction( CanInlineResult.YES, "function foo(){return a;}; " + "while(1) { foo(); }", "foo", INLINE_BLOCK, true); } public void testCanInlineReferenceToFunctionInLoop2() { // If function contains function, don't inline it into a loop. // TODO(johnlenz): this can be improved by looking to see // if the inner function contains any references to values defined // in the outer function. helperCanInlineReferenceToFunction( CanInlineResult.NO, "function foo(){return function() {};}; " + "while(1) { foo(); }", "foo", INLINE_BLOCK, true); } public void testInline1() { helperInlineReferenceToFunction( "function foo(){}; foo();", "function foo(){}; void 0", "foo", INLINE_DIRECT); } public void testInline2() { helperInlineReferenceToFunction( "function foo(){}; foo();", "function foo(){}; {}", "foo", INLINE_BLOCK); } public void testInline3() { helperInlineReferenceToFunction( "function foo(){return;}; foo();", "function foo(){return;}; {}", "foo", INLINE_BLOCK); } public void testInline4() { helperInlineReferenceToFunction( "function foo(){return true;}; foo();", "function foo(){return true;}; true;", "foo", INLINE_DIRECT); } public void testInline5() { helperInlineReferenceToFunction( "function foo(){return true;}; foo();", "function foo(){return true;}; {true;}", "foo", INLINE_BLOCK); } public void testInline6() { // In var initialization. helperInlineReferenceToFunction( "function foo(){return true;}; var x=foo();", "function foo(){return true;}; var x=true;", "foo", INLINE_DIRECT); } public void testInline7() { helperInlineReferenceToFunction( "function foo(){return true;}; var x=foo();", "function foo(){return true;}; var x;" + "{x=true}", "foo", INLINE_BLOCK); } public void testInline8() { // In assignment. helperInlineReferenceToFunction( "function foo(){return true;}; var x; x=foo();", "function foo(){return true;}; var x; x=true;", "foo", INLINE_DIRECT); } public void testInline9() { helperInlineReferenceToFunction( "function foo(){return true;}; var x; x=foo();", "function foo(){return true;}; var x;{x=true}", "foo", INLINE_BLOCK); } public void testInline10() { // In expression. helperInlineReferenceToFunction( "function foo(){return true;}; var x; x=x+foo();", "function foo(){return true;}; var x; x=x+true;", "foo", INLINE_DIRECT); } public void testInline11() { // Simple call with parameters helperInlineReferenceToFunction( "function foo(a){return true;}; foo(x);", "function foo(a){return true;}; true;", "foo", INLINE_DIRECT); } public void testInline12() { helperInlineReferenceToFunction( "function foo(a){return true;}; foo(x);", "function foo(a){return true;}; {true}", "foo", INLINE_BLOCK); } public void testInline13() { // Parameter has side-effects. helperInlineReferenceToFunction( "function foo(a){return a;}; " + "function x() { foo(x++); }", "function foo(a){return a;}; " + "function x() {{var a$$inline_0=x++;" + "a$$inline_0}}", "foo", INLINE_BLOCK); } public void testInline14() { // Parameter has side-effects. helperInlineReferenceToFunction( "function foo(a){return a+a;}; foo(x++);", "function foo(a){return a+a;}; " + "{var a$$inline_0=x++;" + " a$$inline_0+" + "a$$inline_0;}", "foo", INLINE_BLOCK); } public void testInline15() { // Parameter has mutable, references more than once. helperInlineReferenceToFunction( "function foo(a){return a+a;}; foo(new Date());", "function foo(a){return a+a;}; " + "{var a$$inline_0=new Date();" + " a$$inline_0+" + "a$$inline_0;}", "foo", INLINE_BLOCK); } public void testInline16() { // Parameter is large, references more than once. helperInlineReferenceToFunction( "function foo(a){return a+a;}; foo(function(){});", "function foo(a){return a+a;}; " + "{var a$$inline_0=function(){};" + " a$$inline_0+" + "a$$inline_0;}", "foo", INLINE_BLOCK); } public void testInline17() { // Parameter has side-effects. helperInlineReferenceToFunction( "function foo(a){return true;}; foo(goo());", "function foo(a){return true;};" + "{var a$$inline_0=goo();true}", "foo", INLINE_BLOCK); } public void testInline18() { // This doesn't bring names into the global name space. helperInlineReferenceToFunction( "function foo(a){var b;return a;}; " + "function x() { foo(goo()); }", "function foo(a){var b;return a;}; " + "function x() {{var a$$inline_0=goo();" + "var b$$inline_1;a$$inline_0}}", "foo", INLINE_BLOCK); } public void testInline19() { // Properly alias. helperInlineReferenceToFunction( "var x = 1; var y = 2;" + "function foo(a,b){x = b; y = a;}; " + "function bar() { foo(x,y); }", "var x = 1; var y = 2;" + "function foo(a,b){x = b; y = a;}; " + "function bar() {" + "{var a$$inline_0=x;" + "x = y;" + "y = a$$inline_0;}" + "}", "foo", INLINE_BLOCK); } public void testInline19b() { helperInlineReferenceToFunction( "var x = 1; var y = 2;" + "function foo(a,b){y = a; x = b;}; " + "function bar() { foo(x,y); }", "var x = 1; var y = 2;" + "function foo(a,b){y = a; x = b;}; " + "function bar() {" + "{var b$$inline_1=y;" + "y = x;" + "x = b$$inline_1;}" + "}", "foo", INLINE_BLOCK); } public void testInlineIntoLoop() { helperInlineReferenceToFunction( "function foo(a){var b;return a;}; " + "for(;1;){ foo(1); }", "function foo(a){var b;return a;}; " + "for(;1;){ {" + "var b$$inline_1=void 0;1}}", "foo", INLINE_BLOCK); helperInlineReferenceToFunction( "function foo(a){var b;return a;}; " + "do{ foo(1); } while(1)", "function foo(a){var b;return a;}; " + "do{ {" + "var b$$inline_1=void 0;1}}while(1)", "foo", INLINE_BLOCK); helperInlineReferenceToFunction( "function foo(a){for(var b in c)return a;}; " + "for(;1;){ foo(1); }", "function foo(a){var b;for(b in c)return a;}; " + "for(;1;){ {JSCompiler_inline_label_foo_2:{" + "var b$$inline_1=void 0;for(b$$inline_1 in c){" + "1;break JSCompiler_inline_label_foo_2" + "}}}}", "foo", INLINE_BLOCK); } public void testInlineFunctionWithInnerFunction1() { // Call with inner function expression. helperInlineReferenceToFunction( "function foo(){return function() {return true;}}; foo();", "function foo(){return function() {return true;}};" + "(function() {return true;})", "foo", INLINE_DIRECT); } public void testInlineFunctionWithInnerFunction2() { // Call with inner function expression. helperInlineReferenceToFunction( "function foo(){return function() {return true;}}; foo();", "function foo(){return function() {return true;}};" + "{(function() {return true;})}", "foo", INLINE_BLOCK); } public void testInlineFunctionWithInnerFunction3() { // Call with inner function expression. helperInlineReferenceToFunction( "function foo(){return function() {var a; return true;}}; foo();", "function foo(){return function() {var a; return true;}};" + "(function() {var a; return true;});", "foo", INLINE_DIRECT); } public void testInlineFunctionWithInnerFunction4() { // Call with inner function expression. helperInlineReferenceToFunction( "function foo(){return function() {var a; return true;}}; foo();", "function foo(){return function() {var a; return true;}};" + "{(function() {var a$$inline_0; return true;});}", "foo", INLINE_BLOCK); } public void testInlineFunctionWithInnerFunction5() { // Call with inner function statement. helperInlineReferenceToFunction( "function foo(){function x() {var a; return true;} return x}; foo();", "function foo(){function x(){var a;return true}return x};" + "{var x$$inline_0 = function(){" + "var a$$inline_1;return true};x$$inline_0}", "foo", INLINE_BLOCK); } public void testInlineReferenceInExpression1() { // Call in if condition helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() { if (foo(1)) throw 'test'; }", "function foo(a){return true;}; " + "function x() { var JSCompiler_inline_result$$0; " + "{JSCompiler_inline_result$$0=true;}" + "if (JSCompiler_inline_result$$0) throw 'test'; }", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression2() { // Call in return expression helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() { return foo(1); }", "function foo(a){return true;}; " + "function x() { var JSCompiler_inline_result$$0; " + "{JSCompiler_inline_result$$0=true;}" + "return JSCompiler_inline_result$$0; }", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression3() { // Call in switch expression helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() { switch(foo(1)) { default:break; } }", "function foo(a){return true;}; " + "function x() { var JSCompiler_inline_result$$0; " + "{JSCompiler_inline_result$$0=true;}" + "switch(JSCompiler_inline_result$$0) { default:break; } }", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression4() { // Call in hook condition helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() {foo(1)?0:1 }", "function foo(a){return true;}; " + "function x() { var JSCompiler_inline_result$$0; " + "{JSCompiler_inline_result$$0=true;}" + "JSCompiler_inline_result$$0?0:1 }", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression5() { // Call in expression statement "condition" helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() {foo(1)&&1 }", "function foo(a){return true;}; " + "function x() { var JSCompiler_inline_result$$0; " + "{JSCompiler_inline_result$$0=true;}" + "JSCompiler_inline_result$$0&&1 }", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression6() { // Call in expression statement after side-effect free "condition" helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() {1 + foo(1) }", "function foo(a){return true;}; " + "function x() { var JSCompiler_inline_result$$0; " + "{JSCompiler_inline_result$$0=true;}" + "1 + JSCompiler_inline_result$$0 }", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression7() { // Call in expression statement "condition" helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() {foo(1) && 1 }", "function foo(a){return true;}; " + "function x() { var JSCompiler_inline_result$$0; " + "{JSCompiler_inline_result$$0=true;}" + "JSCompiler_inline_result$$0&&1 }", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression8() { // Call in expression statement after side-effect free operator helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() {1 + foo(1) }", "function foo(a){return true;}; " + "function x() { var JSCompiler_inline_result$$0;" + "{JSCompiler_inline_result$$0=true;}" + "1 + JSCompiler_inline_result$$0 }", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression9() { // Call in VAR expression. helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() {var b = 1 + foo(1)}", "function foo(a){return true;}; " + "function x() { " + "var JSCompiler_inline_result$$0;" + "{JSCompiler_inline_result$$0=true;}" + "var b = 1 + JSCompiler_inline_result$$0 " + "}", "foo", INLINE_BLOCK, true); } // TODO(nicksantos): Re-enable with side-effect detection. // public void testInlineReferenceInExpression10() { // // Call in assignment expression. // helperInlineReferenceToFunction( // "/** @nosideeffects */ function foo(a){return true;}; " + // "function x() {var b; b += 1 + foo(1) }", // "function foo(a){return true;}; " + // "function x() {var b;" + // "{var JSCompiler_inline_result$$0; " + // "JSCompiler_inline_result$$0=true;}" + // "b += 1 + JSCompiler_inline_result$$0 }", // "foo", INLINE_BLOCK); // } public void testInlineReferenceInExpression11() { // Call under label helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() {a:foo(1)?0:1 }", "function foo(a){return true;}; " + "function x() {" + " a:{" + " var JSCompiler_inline_result$$0; " + " {JSCompiler_inline_result$$0=true;}" + " JSCompiler_inline_result$$0?0:1 " + " }" + "}", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression12() { helperInlineReferenceToFunction( "function foo(a){return true;}" + "function x() { 1?foo(1):1; }", "function foo(a){return true}" + "function x() {" + " if(1) {" + " {true;}" + " } else {" + " 1;" + " }" + "}", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression13() { helperInlineReferenceToFunction( "function foo(a){return true;}; " + "function x() { goo() + (1?foo(1):1) }", "function foo(a){return true;}; " + "function x() { var JSCompiler_temp_const$$0=goo();" + "var JSCompiler_temp$$1;" + "if(1) {" + " {JSCompiler_temp$$1=true;} " + "} else {" + " JSCompiler_temp$$1=1;" + "}" + "JSCompiler_temp_const$$0 + JSCompiler_temp$$1" + "}", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression14() { helperInlineReferenceToFunction( "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() { z.gack = foo(1) }", "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() {" + "var JSCompiler_temp_const$$0=z;" + "var JSCompiler_inline_result$$1;" + "{" + "z= {};" + "JSCompiler_inline_result$$1 = true;" + "}" + "JSCompiler_temp_const$$0.gack = JSCompiler_inline_result$$1;" + "}", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression15() { helperInlineReferenceToFunction( "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() { z.gack = foo.call(this,1) }", "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() {" + "var JSCompiler_temp_const$$0=z;" + "var JSCompiler_inline_result$$1;" + "{" + "z= {};" + "JSCompiler_inline_result$$1 = true;" + "}" + "JSCompiler_temp_const$$0.gack = JSCompiler_inline_result$$1;" + "}", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression16() { helperInlineReferenceToFunction( "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() { z[bar()] = foo(1) }", "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() {" + "var JSCompiler_temp_const$$1=z;" + "var JSCompiler_temp_const$$0=bar();" + "var JSCompiler_inline_result$$2;" + "{" + "z= {};" + "JSCompiler_inline_result$$2 = true;" + "}" + "JSCompiler_temp_const$$1[JSCompiler_temp_const$$0] = " + "JSCompiler_inline_result$$2;" + "}", "foo", INLINE_BLOCK, true); } public void testInlineReferenceInExpression17() { helperInlineReferenceToFunction( "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() { z.y.x.gack = foo(1) }", "var z = {};" + "function foo(a){z = {};return true;}; " + "function x() {" + "var JSCompiler_temp_const$$0=z.y.x;" + "var JSCompiler_inline_result$$1;" + "{" + "z= {};" + "JSCompiler_inline_result$$1 = true;" + "}" + "JSCompiler_temp_const$$0.gack = JSCompiler_inline_result$$1;" + "}", "foo", INLINE_BLOCK, true); } public void testInlineWithinCalls1() { // Call in within a call helperInlineReferenceToFunction( "function foo(){return _g;}; " + "function x() {1 + foo()() }", "function foo(){return _g;}; " + "function x() { var JSCompiler_inline_result$$0;" + "{JSCompiler_inline_result$$0=_g;}" + "1 + JSCompiler_inline_result$$0() }", "foo", INLINE_BLOCK, true); } // TODO(nicksantos): Re-enable with side-effect detection. // public void testInlineWithinCalls2() { // helperInlineReferenceToFunction( // "/** @nosideeffects */ function foo(){return true;}; " + // "function x() {1 + _g(foo()) }", // "function foo(){return true;}; " + // "function x() { {var JSCompiler_inline_result$$0; " + // "JSCompiler_inline_result$$0=true;}" + // "1 + _g(JSCompiler_inline_result$$0) }", // "foo", INLINE_BLOCK, true); // } public void testInlineAssignmentToConstant() { // Call in within a call helperInlineReferenceToFunction( "function foo(){return _g;}; " + "function x(){var CONSTANT_RESULT = foo(); }", "function foo(){return _g;}; " + "function x() {" + " var JSCompiler_inline_result$$0;" + " {JSCompiler_inline_result$$0=_g;}" + " var CONSTANT_RESULT = JSCompiler_inline_result$$0;" + "}", "foo", INLINE_BLOCK, true); } public void testBug1897706() { helperInlineReferenceToFunction( "function foo(a){}; foo(x())", "function foo(a){}; {var a$$inline_0=x()}", "foo", INLINE_BLOCK); helperInlineReferenceToFunction( "function foo(a){bar()}; foo(x())", "function foo(a){bar()}; {var a$$inline_0=x();bar()}", "foo", INLINE_BLOCK); helperInlineReferenceToFunction( "function foo(a,b){bar()}; foo(x(),y())", "function foo(a,b){bar()};" + "{var a$$inline_0=x();var b$$inline_1=y();bar()}", "foo", INLINE_BLOCK); } /** * Test case * * var a = {}, b = {} * a.test = "a", b.test = "b" * c = a; * foo() { c=b; return "a" } * c.teste * */ public void helperCanInlineReferenceToFunction( final CanInlineResult expectedResult, final String code, final String fnName, final InliningMode mode) { helperCanInlineReferenceToFunction( expectedResult, code, fnName, mode, false); } public void helperCanInlineReferenceToFunction( final CanInlineResult expectedResult, final String code, final String fnName, final InliningMode mode, boolean allowDecomposition) { final Compiler compiler = new Compiler(); final FunctionInjector injector = new FunctionInjector( compiler, compiler.getUniqueNameIdSupplier(), allowDecomposition, assumeStrictThis, assumeMinimumCapture); final Node tree = parse(compiler, code); Node externsRoot = new Node(Token.EMPTY); Node mainRoot = tree; final Node fnNode = findFunction(tree, fnName); final Set unsafe = FunctionArgumentInjector.findModifiedParameters(fnNode); // can-inline tester Method tester = new Method() { @Override public boolean call(NodeTraversal t, Node n, Node parent) { CanInlineResult result = injector.canInlineReferenceToFunction( t, n, fnNode, unsafe, mode, NodeUtil.referencesThis(fnNode), NodeUtil.containsFunction(NodeUtil.getFunctionBody(fnNode))); assertEquals(expectedResult, result); return true; } }; compiler.resetUniqueNameId(); TestCallback test = new TestCallback(fnName, tester); NodeTraversal.traverse(compiler, tree, test); } public void helperInlineReferenceToFunction( String code, final String expectedResult, final String fnName, final InliningMode mode) { helperInlineReferenceToFunction( code, expectedResult, fnName, mode, false); } private void validateSourceInfo(Compiler compiler, Node subtree) { (new LineNumberCheck(compiler)).setCheckSubTree(subtree); // Source information problems are reported as compiler errors. if (compiler.getErrorCount() != 0) { String msg = "Error encountered: "; for (JSError err : compiler.getErrors()) { msg += err.toString() + "\n"; } assertTrue(msg, compiler.getErrorCount() == 0); } } public void helperInlineReferenceToFunction( String code, final String expectedResult, final String fnName, final InliningMode mode, final boolean decompose) { final Compiler compiler = new Compiler(); final FunctionInjector injector = new FunctionInjector( compiler, compiler.getUniqueNameIdSupplier(), decompose, assumeStrictThis, assumeMinimumCapture); List externsInputs = Lists.newArrayList( SourceFile.fromCode("externs", "")); CompilerOptions options = new CompilerOptions(); options.setCodingConvention(new GoogleCodingConvention()); compiler.init(externsInputs, Lists.newArrayList( SourceFile.fromCode("code", code)), options); Node parseRoot = compiler.parseInputs(); Node externsRoot = parseRoot.getFirstChild(); final Node tree = parseRoot.getLastChild(); assertNotNull(tree); assertTrue(tree != externsRoot); final Node expectedRoot = parseExpected(new Compiler(), expectedResult); Node mainRoot = tree; MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler); mark.process(externsRoot, mainRoot); Normalize normalize = new Normalize(compiler, false); normalize.process(externsRoot, mainRoot); compiler.setLifeCycleStage(LifeCycleStage.NORMALIZED); final Node fnNode = findFunction(tree, fnName); assertNotNull(fnNode); final Set unsafe = FunctionArgumentInjector.findModifiedParameters(fnNode); assertNotNull(fnNode); // inline tester Method tester = new Method() { @Override public boolean call(NodeTraversal t, Node n, Node parent) { CanInlineResult canInline = injector.canInlineReferenceToFunction( t, n, fnNode, unsafe, mode, NodeUtil.referencesThis(fnNode), NodeUtil.containsFunction(NodeUtil.getFunctionBody(fnNode))); assertTrue("canInlineReferenceToFunction should not be CAN_NOT_INLINE", CanInlineResult.NO != canInline); if (decompose) { assertTrue("canInlineReferenceToFunction " + "should be CAN_INLINE_AFTER_DECOMPOSITION", CanInlineResult.AFTER_PREPARATION == canInline); Set knownConstants = Sets.newHashSet(); ExpressionDecomposer decomposer = new ExpressionDecomposer( compiler, compiler.getUniqueNameIdSupplier(), knownConstants); injector.setKnownConstants(knownConstants); injector.maybePrepareCall(n); assertTrue("canInlineReferenceToFunction " + "should be CAN_INLINE", CanInlineResult.YES != canInline); } Node result = injector.inline( t, n, fnName, fnNode, mode); validateSourceInfo(compiler, result); String explanation = expectedRoot.checkTreeEquals(tree.getFirstChild()); assertNull("\nExpected: " + toSource(expectedRoot) + "\nResult: " + toSource(tree.getFirstChild()) + "\n" + explanation, explanation); return true; } }; compiler.resetUniqueNameId(); TestCallback test = new TestCallback(fnName, tester); NodeTraversal.traverse(compiler, tree, test); } interface Method { boolean call(NodeTraversal t, Node n, Node parent); } class TestCallback implements Callback { private final String callname; private final Method method; private boolean complete = false; TestCallback(String callname, Method method) { this.callname = callname; this.method = method; } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { return !complete; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isCall()) { Node callee; if (NodeUtil.isGet(n.getFirstChild())) { callee = n.getFirstChild().getFirstChild(); } else { callee = n.getFirstChild(); } if (callee.isName() && callee.getString().equals(callname)) { complete = method.call(t, n, parent); } } if (parent == null) { assertTrue(complete); } } } private static Node findFunction(Node n, String name) { if (n.isFunction()) { if (n.getFirstChild().getString().equals(name)) { return n; } } for (Node c : n.children()) { Node result = findFunction(c, name); if (result != null) { return result; } } return null; } private static Node prep(String js) { Compiler compiler = new Compiler(); Node n = compiler.parseTestCode(js); assertEquals(0, compiler.getErrorCount()); return n.getFirstChild(); } private static Node parse(Compiler compiler, String js) { Node n = compiler.parseTestCode(js); assertEquals(0, compiler.getErrorCount()); return n; } private static Node parseExpected(Compiler compiler, String js) { Node n = compiler.parseTestCode(js); String message = "Unexpected errors: "; JSError[] errs = compiler.getErrors(); for (int i = 0; i < errs.length; i++){ message += "\n" + errs[i].toString(); } assertEquals(message, 0, compiler.getErrorCount()); return n; } private static String toSource(Node n) { return new CodePrinter.Builder(n) .setPrettyPrint(false) .setLineBreak(false) .setSourceMap(null) .build(); } }