/* * 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; /** * Tests for {@link PeepholeSubstituteAlternateSyntax} in isolation. * Tests for the interaction of multiple peephole passes are in * PeepholeIntegrationTest. */ public class PeepholeSubstituteAlternateSyntaxTest extends CompilerTestCase { // Externs for built-in constructors // Needed for testFoldLiteralObjectConstructors(), // testFoldLiteralArrayConstructors() and testFoldRegExp...() private static final String FOLD_CONSTANTS_TEST_EXTERNS = "var Object = function f(){};\n" + "var RegExp = function f(a){};\n" + "var Array = function f(a){};\n"; private boolean late = true; // TODO(user): Remove this when we no longer need to do string comparison. private PeepholeSubstituteAlternateSyntaxTest(boolean compareAsTree) { super(FOLD_CONSTANTS_TEST_EXTERNS, compareAsTree); } public PeepholeSubstituteAlternateSyntaxTest() { super(FOLD_CONSTANTS_TEST_EXTERNS); } @Override public void setUp() throws Exception { late = true; super.setUp(); enableLineNumberCheck(true); disableNormalize(); } @Override public CompilerPass getProcessor(final Compiler compiler) { CompilerPass peepholePass = new PeepholeOptimizationsPass(compiler, new PeepholeSubstituteAlternateSyntax(late)) .setRetraverseOnChange(false); return peepholePass; } @Override protected int getNumRepetitions() { return 1; } private void foldSame(String js) { testSame(js); } private void fold(String js, String expected) { test(js, expected); } void assertResultString(String js, String expected) { assertResultString(js, expected, false); } // TODO(user): This is same as fold() except it uses string comparison. Any // test that needs tell us where a folding is constructing an invalid AST. void assertResultString(String js, String expected, boolean normalize) { PeepholeSubstituteAlternateSyntaxTest scTest = new PeepholeSubstituteAlternateSyntaxTest(false); if (normalize) { scTest.enableNormalize(); } else { scTest.disableNormalize(); } scTest.test(js, expected); } /** Check that removing blocks with 1 child works */ public void testFoldOneChildBlocks() { late = false; fold("function f(){if(x)a();x=3}", "function f(){x&&a();x=3}"); fold("function f(){if(x){a()}x=3}", "function f(){x&&a();x=3}"); fold("function f(){if(x){return 3}}", "function f(){if(x)return 3}"); fold("function f(){if(x){a()}}", "function f(){x&&a()}"); fold("function f(){if(x){throw 1}}", "function f(){if(x)throw 1;}"); // Try it out with functions fold("function f(){if(x){foo()}}", "function f(){x&&foo()}"); fold("function f(){if(x){foo()}else{bar()}}", "function f(){x?foo():bar()}"); // Try it out with properties and methods fold("function f(){if(x){a.b=1}}", "function f(){if(x)a.b=1}"); fold("function f(){if(x){a.b*=1}}", "function f(){x&&(a.b*=1)}"); fold("function f(){if(x){a.b+=1}}", "function f(){x&&(a.b+=1)}"); fold("function f(){if(x){++a.b}}", "function f(){x&&++a.b}"); fold("function f(){if(x){a.foo()}}", "function f(){x&&a.foo()}"); // Try it out with throw/catch/finally [which should not change] fold("function f(){try{foo()}catch(e){bar(e)}finally{baz()}}", "function f(){try{foo()}catch(e){bar(e)}finally{baz()}}"); // Try it out with switch statements fold("function f(){switch(x){case 1:break}}", "function f(){switch(x){case 1:break}}"); // Do while loops stay in a block if that's where they started fold("function f(){if(e1){do foo();while(e2)}else foo2()}", "function f(){if(e1){do foo();while(e2)}else foo2()}"); // Test an obscure case with do and while fold("if(x){do{foo()}while(y)}else bar()", "if(x){do foo();while(y)}else bar()"); // Play with nested IFs fold("function f(){if(x){if(y)foo()}}", "function f(){x&&y&&foo()}"); fold("function f(){if(x){if(y)foo();else bar()}}", "function f(){x&&(y?foo():bar())}"); fold("function f(){if(x){if(y)foo()}else bar()}", "function f(){x?y&&foo():bar()}"); fold("function f(){if(x){if(y)foo();else bar()}else{baz()}}", "function f(){x?y?foo():bar():baz()}"); fold("if(e1){while(e2){if(e3){foo()}}}else{bar()}", "if(e1)while(e2)e3&&foo();else bar()"); fold("if(e1){with(e2){if(e3){foo()}}}else{bar()}", "if(e1)with(e2)e3&&foo();else bar()"); fold("if(a||b){if(c||d){var x;}}", "if(a||b)if(c||d)var x"); fold("if(x){ if(y){var x;}else{var z;} }", "if(x)if(y)var x;else var z"); // NOTE - technically we can remove the blocks since both the parent // and child have elses. But we don't since it causes ambiguities in // some cases where not all descendent ifs having elses fold("if(x){ if(y){var x;}else{var z;} }else{var w}", "if(x)if(y)var x;else var z;else var w"); fold("if (x) {var x;}else { if (y) { var y;} }", "if(x)var x;else if(y)var y"); // Here's some of the ambiguous cases fold("if(a){if(b){f1();f2();}else if(c){f3();}}else {if(d){f4();}}", "if(a)if(b){f1();f2()}else c&&f3();else d&&f4()"); fold("function f(){foo()}", "function f(){foo()}"); fold("switch(x){case y: foo()}", "switch(x){case y:foo()}"); fold("try{foo()}catch(ex){bar()}finally{baz()}", "try{foo()}catch(ex){bar()}finally{baz()}"); } /** Try to minimize returns */ public void testFoldReturns() { fold("function f(){if(x)return 1;else return 2}", "function f(){return x?1:2}"); fold("function f(){if(x)return 1;return 2}", "function f(){return x?1:2}"); fold("function f(){if(x)return;return 2}", "function f(){return x?void 0:2}"); fold("function f(){if(x)return 1+x;else return 2-x}", "function f(){return x?1+x:2-x}"); fold("function f(){if(x)return 1+x;return 2-x}", "function f(){return x?1+x:2-x}"); fold("function f(){if(x)return y += 1;else return y += 2}", "function f(){return x?(y+=1):(y+=2)}"); fold("function f(){if(x)return;else return 2-x}", "function f(){if(x);else return 2-x}"); fold("function f(){if(x)return;return 2-x}", "function f(){return x?void 0:2-x}"); fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;{}}"); fold("function f(){if(x)return x;return}", "function f(){if(x)return x}"); foldSame("function f(){for(var x in y) { return x.y; } return k}"); } public void testCombineIfs1() { fold("function f() {if (x) return 1; if (y) return 1}", "function f() {if (x||y) return 1;}"); fold("function f() {if (x) return 1; if (y) foo(); else return 1}", "function f() {if ((!x)&&y) foo(); else return 1;}"); } public void testCombineIfs2() { // combinable but not yet done foldSame("function f() {if (x) throw 1; if (y) throw 1}"); // Can't combine, side-effect fold("function f(){ if (x) g(); if (y) g() }", "function f(){ x&&g(); y&&g() }"); // Can't combine, side-effect fold("function f(){ if (x) y = 0; if (y) y = 0; }", "function f(){ x&&(y = 0); y&&(y = 0); }"); } public void testCombineIfs3() { foldSame("function f() {if (x) return 1; if (y) {g();f()}}"); } /** Try to minimize assignments */ public void testFoldAssignments() { fold("function f(){if(x)y=3;else y=4;}", "function f(){y=x?3:4}"); fold("function f(){if(x)y=1+a;else y=2+a;}", "function f(){y=x?1+a:2+a}"); // and operation assignments fold("function f(){if(x)y+=1;else y+=2;}", "function f(){y+=x?1:2}"); fold("function f(){if(x)y-=1;else y-=2;}", "function f(){y-=x?1:2}"); fold("function f(){if(x)y%=1;else y%=2;}", "function f(){y%=x?1:2}"); fold("function f(){if(x)y|=1;else y|=2;}", "function f(){y|=x?1:2}"); // sanity check, don't fold if the 2 ops don't match foldSame("function f(){x ? y-=1 : y+=2}"); // sanity check, don't fold if the 2 LHS don't match foldSame("function f(){x ? y-=1 : z-=1}"); // sanity check, don't fold if there are potential effects foldSame("function f(){x ? y().a=3 : y().a=4}"); } public void testRemoveDuplicateStatements() { fold("if (a) { x = 1; x++ } else { x = 2; x++ }", "x=(a) ? 1 : 2; x++"); fold("if (a) { x = 1; x++; y += 1; z = pi; }" + " else { x = 2; x++; y += 1; z = pi; }", "x=(a) ? 1 : 2; x++; y += 1; z = pi;"); fold("function z() {" + "if (a) { foo(); return !0 } else { goo(); return !0 }" + "}", "function z() {(a) ? foo() : goo(); return !0}"); fold("function z() {if (a) { foo(); x = true; return true " + "} else { goo(); x = true; return true }}", "function z() {(a) ? foo() : goo(); x = !0; return !0}"); fold("function z() {" + " if (a) { bar(); foo(); return true }" + " else { bar(); goo(); return true }" + "}", "function z() {" + " if (a) { bar(); foo(); }" + " else { bar(); goo(); }" + " return !0;" + "}"); } public void testNotCond() { fold("function f(){if(!x)foo()}", "function f(){x||foo()}"); fold("function f(){if(!x)b=1}", "function f(){x||(b=1)}"); fold("if(!x)z=1;else if(y)z=2", "if(x){y&&(z=2);}else{z=1;}"); fold("if(x)y&&(z=2);else z=1;", "x ? y&&(z=2) : z=1"); foldSame("function f(){if(!(x=1))a.b=1}"); } public void testAndParenthesesCount() { fold("function f(){if(x||y)a.foo()}", "function f(){(x||y)&&a.foo()}"); fold("function f(){if(x.a)x.a=0}", "function f(){x.a&&(x.a=0)}"); foldSame("function f(){if(x()||y()){x()||y()}}"); } public void testFoldLogicalOpStringCompare() { // side-effects // There is two way to parse two &&'s and both are correct. assertResultString("if(foo() && false) z()", "foo()&&0&&z()"); } public void testFoldNot() { fold("while(!(x==y)){a=b;}" , "while(x!=y){a=b;}"); fold("while(!(x!=y)){a=b;}" , "while(x==y){a=b;}"); fold("while(!(x===y)){a=b;}", "while(x!==y){a=b;}"); fold("while(!(x!==y)){a=b;}", "while(x===y){a=b;}"); // Because !(x=NaN don't fold < and > cases. foldSame("while(!(x>y)){a=b;}"); foldSame("while(!(x>=y)){a=b;}"); foldSame("while(!(x 20)) {foo();foo()}", "if(f() > 20){foo();foo()}"); } public void testFoldLoopBreakLate() { late = true; fold("for(;;) if (a) break", "for(;!a;);"); foldSame("for(;;) if (a) { f(); break }"); fold("for(;;) if (a) break; else f()", "for(;!a;) { { f(); } }"); fold("for(;a;) if (b) break", "for(;a && !b;);"); fold("for(;a;) { if (b) break; if (c) break; }", "for(;(a && !b);) if (c) break;"); fold("for(;(a && !b);) if (c) break;", "for(;(a && !b) && !c;);"); // 'while' is normalized to 'for' enableNormalize(true); fold("while(true) if (a) break", "for(;1&&!a;);"); } public void testFoldLoopBreakEarly() { late = false; foldSame("for(;;) if (a) break"); foldSame("for(;;) if (a) { f(); break }"); foldSame("for(;;) if (a) break; else f()"); foldSame("for(;a;) if (b) break"); foldSame("for(;a;) { if (b) break; if (c) break; }"); foldSame("while(1) if (a) break"); enableNormalize(true); foldSame("while(1) if (a) break"); } public void testFoldConditionalVarDeclaration() { fold("if(x) var y=1;else y=2", "var y=x?1:2"); fold("if(x) y=1;else var y=2", "var y=x?1:2"); foldSame("if(x) var y = 1; z = 2"); foldSame("if(x||y) y = 1; var z = 2"); foldSame("if(x) { var y = 1; print(y)} else y = 2 "); foldSame("if(x) var y = 1; else {y = 2; print(y)}"); } public void testFoldReturnResult() { fold("function f(){return false;}", "function f(){return !1}"); foldSame("function f(){return null;}"); fold("function f(){return void 0;}", "function f(){return}"); fold("function f(){return;}", "function f(){}"); foldSame("function f(){return void foo();}"); fold("function f(){return undefined;}", "function f(){return}"); fold("function f(){if(a()){return undefined;}}", "function f(){if(a()){return}}"); } public void testFoldStandardConstructors() { foldSame("new Foo('a')"); foldSame("var x = new goog.Foo(1)"); foldSame("var x = new String(1)"); foldSame("var x = new Number(1)"); foldSame("var x = new Boolean(1)"); enableNormalize(); fold("var x = new Object('a')", "var x = Object('a')"); fold("var x = new RegExp('')", "var x = RegExp('')"); fold("var x = new Error('20')", "var x = Error(\"20\")"); fold("var x = new Array(20)", "var x = Array(20)"); } public void testSubsituteReturn() { fold("function f() { while(x) { return }}", "function f() { while(x) { break }}"); foldSame("function f() { while(x) { return 5 } }"); foldSame("function f() { a: { return 5 } }"); fold("function f() { while(x) { return 5} return 5}", "function f() { while(x) { break } return 5}"); fold("function f() { while(x) { return x} return x}", "function f() { while(x) { break } return x}"); fold("function f() { while(x) { if (y) { return }}}", "function f() { while(x) { if (y) { break }}}"); fold("function f() { while(x) { if (y) { return }} return}", "function f() { while(x) { if (y) { break }}}"); fold("function f() { while(x) { if (y) { return 5 }} return 5}", "function f() { while(x) { if (y) { break }} return 5}"); // It doesn't matter if x is changed between them. We are still returning // x at whatever x value current holds. The whole x = 1 is skipped. fold("function f() { while(x) { if (y) { return x } x = 1} return x}", "function f() { while(x) { if (y) { break } x = 1} return x}"); // RemoveUnreachableCode would take care of the useless breaks. fold("function f() { while(x) { if (y) { return x } return x} return x}", "function f() { while(x) { if (y) {} break }return x}"); // A break here only breaks out of the inner loop. foldSame("function f() { while(x) { while (y) { return } } }"); foldSame("function f() { while(1) { return 7} return 5}"); foldSame("function f() {" + " try { while(x) {return f()}} catch (e) { } return f()}"); foldSame("function f() {" + " try { while(x) {return f()}} finally {alert(1)} return f()}"); // Both returns has the same handler fold("function f() {" + " try { while(x) { return f() } return f() } catch (e) { } }", "function f() {" + " try { while(x) { break } return f() } catch (e) { } }"); // We can't fold this because it'll change the order of when foo is called. foldSame("function f() {" + " try { while(x) { return foo() } } finally { alert(1) } " + " return foo()}"); // This is fine, we have no side effect in the return value. fold("function f() {" + " try { while(x) { return 1 } } finally { alert(1) } return 1}", "function f() {" + " try { while(x) { break } } finally { alert(1) } return 1}" ); foldSame("function f() { try{ return a } finally { a = 2 } return a; }"); fold( "function f() { switch(a){ case 1: return a; default: g();} return a;}", "function f() { switch(a){ case 1: break; default: g();} return a; }"); } public void testSubsituteBreakForThrow() { foldSame("function f() { while(x) { throw Error }}"); fold("function f() { while(x) { throw Error } throw Error }", "function f() { while(x) { break } throw Error}"); foldSame("function f() { while(x) { throw Error(1) } throw Error(2)}"); foldSame("function f() { while(x) { throw Error(1) } return Error(2)}"); foldSame("function f() { while(x) { throw 5 } }"); foldSame("function f() { a: { throw 5 } }"); fold("function f() { while(x) { throw 5} throw 5}", "function f() { while(x) { break } throw 5}"); fold("function f() { while(x) { throw x} throw x}", "function f() { while(x) { break } throw x}"); foldSame("function f() { while(x) { if (y) { throw Error }}}"); fold("function f() { while(x) { if (y) { throw Error }} throw Error}", "function f() { while(x) { if (y) { break }} throw Error}"); fold("function f() { while(x) { if (y) { throw 5 }} throw 5}", "function f() { while(x) { if (y) { break }} throw 5}"); // It doesn't matter if x is changed between them. We are still throwing // x at whatever x value current holds. The whole x = 1 is skipped. fold("function f() { while(x) { if (y) { throw x } x = 1} throw x}", "function f() { while(x) { if (y) { break } x = 1} throw x}"); // RemoveUnreachableCode would take care of the useless breaks. fold("function f() { while(x) { if (y) { throw x } throw x} throw x}", "function f() { while(x) { if (y) {} break }throw x}"); // A break here only breaks out of the inner loop. foldSame("function f() { while(x) { while (y) { throw Error } } }"); foldSame("function f() { while(1) { throw 7} throw 5}"); foldSame("function f() {" + " try { while(x) {throw f()}} catch (e) { } throw f()}"); foldSame("function f() {" + " try { while(x) {throw f()}} finally {alert(1)} throw f()}"); // Both throws has the same handler fold("function f() {" + " try { while(x) { throw f() } throw f() } catch (e) { } }", "function f() {" + " try { while(x) { break } throw f() } catch (e) { } }"); // We can't fold this because it'll change the order of when foo is called. foldSame("function f() {" + " try { while(x) { throw foo() } } finally { alert(1) } " + " throw foo()}"); // This is fine, we have no side effect in the throw value. fold("function f() {" + " try { while(x) { throw 1 } } finally { alert(1) } throw 1}", "function f() {" + " try { while(x) { break } } finally { alert(1) } throw 1}" ); foldSame("function f() { try{ throw a } finally { a = 2 } throw a; }"); fold( "function f() { switch(a){ case 1: throw a; default: g();} throw a;}", "function f() { switch(a){ case 1: break; default: g();} throw a; }"); } public void testRemoveDuplicateReturn() { fold("function f() { return; }", "function f(){}"); foldSame("function f() { return a; }"); fold("function f() { if (x) { return a } return a; }", "function f() { if (x) {} return a; }"); foldSame( "function f() { try { if (x) { return a } } catch(e) {} return a; }"); foldSame( "function f() { try { if (x) {} } catch(e) {} return 1; }"); // finally clauses may have side effects foldSame( "function f() { try { if (x) { return a } } finally { a++ } return a; }"); // but they don't matter if the result doesn't have side effects and can't // be affect by side-effects. fold("function f() { try { if (x) { return 1 } } finally {} return 1; }", "function f() { try { if (x) {} } finally {} return 1; }"); fold("function f() { switch(a){ case 1: return a; } return a; }", "function f() { switch(a){ case 1: } return a; }"); fold("function f() { switch(a){ " + " case 1: return a; case 2: return a; } return a; }", "function f() { switch(a){ " + " case 1: break; case 2: } return a; }"); } public void testRemoveDuplicateThrow() { foldSame("function f() { throw a; }"); fold("function f() { if (x) { throw a } throw a; }", "function f() { if (x) {} throw a; }"); foldSame( "function f() { try { if (x) {throw a} } catch(e) {} throw a; }"); foldSame( "function f() { try { if (x) {throw 1} } catch(e) {f()} throw 1; }"); foldSame( "function f() { try { if (x) {throw 1} } catch(e) {f()} throw 1; }"); foldSame( "function f() { try { if (x) {throw 1} } catch(e) {throw 1}}"); fold( "function f() { try { if (x) {throw 1} } catch(e) {throw 1} throw 1; }", "function f() { try { if (x) {throw 1} } catch(e) {} throw 1; }"); // finally clauses may have side effects foldSame( "function f() { try { if (x) { throw a } } finally { a++ } throw a; }"); // but they don't matter if the result doesn't have side effects and can't // be affect by side-effects. fold("function f() { try { if (x) { throw 1 } } finally {} throw 1; }", "function f() { try { if (x) {} } finally {} throw 1; }"); fold("function f() { switch(a){ case 1: throw a; } throw a; }", "function f() { switch(a){ case 1: } throw a; }"); fold("function f() { switch(a){ " + "case 1: throw a; case 2: throw a; } throw a; }", "function f() { switch(a){ case 1: break; case 2: } throw a; }"); } public void testNestedIfCombine() { fold("if(x)if(y){while(1){}}", "if(x&&y){while(1){}}"); fold("if(x||z)if(y){while(1){}}", "if((x||z)&&y){while(1){}}"); fold("if(x)if(y||z){while(1){}}", "if((x)&&(y||z)){while(1){}}"); foldSame("if(x||z)if(y||z){while(1){}}"); fold("if(x)if(y){if(z){while(1){}}}", "if(x&&y&&z){while(1){}}"); } public void testFoldTrueFalse() { fold("x = true", "x = !0"); fold("x = false", "x = !1"); } public void testIssue291() { fold("if (true) { f.onchange(); }", "if (1) f.onchange();"); foldSame("if (f) { f.onchange(); }"); foldSame("if (f) { f.bar(); } else { f.onchange(); }"); fold("if (f) { f.bonchange(); }", "f && f.bonchange();"); foldSame("if (f) { f['x'](); }"); } public void testUndefined() { foldSame("var x = undefined"); foldSame("function f(f) {var undefined=2;var x = undefined;}"); this.enableNormalize(); fold("var x = undefined", "var x=void 0"); foldSame( "var undefined = 1;" + "function f() {var undefined=2;var x = undefined;}"); foldSame("function f(undefined) {}"); foldSame("try {} catch(undefined) {}"); foldSame("for (undefined in {}) {}"); foldSame("undefined++;"); fold("undefined += undefined;", "undefined += void 0;"); } public void testSplitCommaExpressions() { late = false; // Don't try to split in expressions. foldSame("while (foo(), !0) boo()"); foldSame("var a = (foo(), !0);"); foldSame("a = (foo(), !0);"); // Don't try to split COMMA under LABELs. foldSame("a:a(),b()"); fold("(x=2), foo()", "x=2; foo()"); fold("foo(), boo();", "foo(); boo()"); fold("(a(), b()), (c(), d());", "a(); b(); (c(), d());"); fold("a(); b(); (c(), d());", "a(); b(); c(); d();"); fold("foo(), true", "foo();true"); fold("foo();true", "foo();1"); fold("function x(){foo(), !0}", "function x(){foo(); !0}"); fold("function x(){foo(); !0}", "function x(){foo(); 1}"); } public void testComma1() { late = false; fold("1, 2", "1; 2"); fold("1; 2", "1; 1"); late = true; foldSame("1, 2"); } public void testComma2() { late = false; test("1, a()", "1; a()"); late = true; foldSame("1, a()"); } public void testComma3() { late = false; test("1, a(), b()", "1; a(); b()"); late = true; foldSame("1, a(), b()"); } public void testComma4() { late = false; test("a(), b()", "a();b()"); late = true; foldSame("a(), b()"); } public void testComma5() { late = false; test("a(), b(), 1", "a();b();1"); late = true; foldSame("a(), b(), 1"); } public void testObjectLiteral() { test("({})", "1"); test("({a:1})", "1"); testSame("({a:foo()})"); testSame("({'a':foo()})"); } public void testArrayLiteral() { test("([])", "1"); test("([1])", "1"); test("([a])", "1"); testSame("([foo()])"); } public void testStringArraySplitting() { testSame("var x=['1','2','3','4']"); testSame("var x=['1','2','3','4','5']"); test("var x=['1','2','3','4','5','6']", "var x='123456'.split('')"); test("var x=['1','2','3','4','5','00']", "var x='1 2 3 4 5 00'.split(' ')"); test("var x=['1','2','3','4','5','6','7']", "var x='1234567'.split('')"); test("var x=['1','2','3','4','5','6','00']", "var x='1 2 3 4 5 6 00'.split(' ')"); test("var x=[' ,',',',',',',',',',',']", "var x=' ,;,;,;,;,;,'.split(';')"); test("var x=[',,',' ',',',',',',',',']", "var x=',,; ;,;,;,;,'.split(';')"); test("var x=['a,',' ',',',',',',',',']", "var x='a,; ;,;,;,;,'.split(';')"); // all possible delimiters used, leave it alone testSame("var x=[',', ' ', ';', '{', '}']"); } public void testRemoveElseCause() { test("function f() {" + " if(x) return 1;" + " else if(x) return 2;" + " else if(x) return 3 }", "function f() {" + " if(x) return 1;" + "{ if(x) return 2;" + "{ if(x) return 3 } } }"); } public void testRemoveElseCause1() { test("function f() { if (x) throw 1; else f() }", "function f() { if (x) throw 1; { f() } }"); } public void testRemoveElseCause2() { test("function f() { if (x) return 1; else f() }", "function f() { if (x) return 1; { f() } }"); test("function f() { if (x) return; else f() }", "function f() { if (x) {} else { f() } }"); // This case is handled by minimize exit points. testSame("function f() { if (x) return; f() }"); } public void testRemoveElseCause3() { testSame("function f() { a:{if (x) break a; else f() } }"); testSame("function f() { if (x) { a:{ break a } } else f() }"); testSame("function f() { if (x) a:{ break a } else f() }"); } public void testRemoveElseCause4() { testSame("function f() { if (x) { if (y) { return 1; } } else f() }"); } public void testBindToCall1() { test("(goog.bind(f))()", "f()"); test("(goog.bind(f,a))()", "f.call(a)"); test("(goog.bind(f,a,b))()", "f.call(a,b)"); test("(goog.bind(f))(a)", "f(a)"); test("(goog.bind(f,a))(b)", "f.call(a,b)"); test("(goog.bind(f,a,b))(c)", "f.call(a,b,c)"); test("(goog.partial(f))()", "f()"); test("(goog.partial(f,a))()", "f(a)"); test("(goog.partial(f,a,b))()", "f(a,b)"); test("(goog.partial(f))(a)", "f(a)"); test("(goog.partial(f,a))(b)", "f(a,b)"); test("(goog.partial(f,a,b))(c)", "f(a,b,c)"); test("((function(){}).bind())()", "((function(){}))()"); test("((function(){}).bind(a))()", "((function(){})).call(a)"); test("((function(){}).bind(a,b))()", "((function(){})).call(a,b)"); test("((function(){}).bind())(a)", "((function(){}))(a)"); test("((function(){}).bind(a))(b)", "((function(){})).call(a,b)"); test("((function(){}).bind(a,b))(c)", "((function(){})).call(a,b,c)"); // Without using type information we don't know "f" is a function. testSame("(f.bind())()"); testSame("(f.bind(a))()"); testSame("(f.bind())(a)"); testSame("(f.bind(a))(b)"); // Don't rewrite if the bind isn't the immediate call target testSame("(goog.bind(f)).call(g)"); } public void testBindToCall2() { test("(goog$bind(f))()", "f()"); test("(goog$bind(f,a))()", "f.call(a)"); test("(goog$bind(f,a,b))()", "f.call(a,b)"); test("(goog$bind(f))(a)", "f(a)"); test("(goog$bind(f,a))(b)", "f.call(a,b)"); test("(goog$bind(f,a,b))(c)", "f.call(a,b,c)"); test("(goog$partial(f))()", "f()"); test("(goog$partial(f,a))()", "f(a)"); test("(goog$partial(f,a,b))()", "f(a,b)"); test("(goog$partial(f))(a)", "f(a)"); test("(goog$partial(f,a))(b)", "f(a,b)"); test("(goog$partial(f,a,b))(c)", "f(a,b,c)"); // Don't rewrite if the bind isn't the immediate call target testSame("(goog$bind(f)).call(g)"); } public void testBindToCall3() { // TODO(johnlenz): The code generator wraps free calls with (0,...) to // prevent leaking "this", but the parser doesn't unfold it, making a // AST comparison fail. For now do a string comparison to validate the // correct code is in fact generated. // The FREE call wrapping should be moved out of the code generator // and into a denormalizing pass. new StringCompareTestCase().testBindToCall3(); } public void testSimpleFunctionCall() { test("var a = String(23)", "var a = '' + 23"); test("var a = String('hello')", "var a = '' + 'hello'"); testSame("var a = String('hello', bar());"); testSame("var a = String({valueOf: function() { return 1; }});"); } private static class StringCompareTestCase extends CompilerTestCase { StringCompareTestCase() { super("", false); } @Override protected CompilerPass getProcessor(Compiler compiler) { CompilerPass peepholePass = new PeepholeOptimizationsPass(compiler, new PeepholeSubstituteAlternateSyntax(false)); return peepholePass; } public void testBindToCall3() { test("(goog.bind(f.m))()", "(0,f.m)()"); test("(goog.bind(f.m,a))()", "f.m.call(a)"); test("(goog.bind(f.m))(a)", "(0,f.m)(a)"); test("(goog.bind(f.m,a))(b)", "f.m.call(a,b)"); test("(goog.partial(f.m))()", "(0,f.m)()"); test("(goog.partial(f.m,a))()", "(0,f.m)(a)"); test("(goog.partial(f.m))(a)", "(0,f.m)(a)"); test("(goog.partial(f.m,a))(b)", "(0,f.m)(a,b)"); // Without using type information we don't know "f" is a function. testSame("f.m.bind()()"); testSame("f.m.bind(a)()"); testSame("f.m.bind()(a)"); testSame("f.m.bind(a)(b)"); // Don't rewrite if the bind isn't the immediate call target testSame("goog.bind(f.m).call(g)"); } } }