This repository has been archived on 2023-06-18. You can view files and clone it, but cannot push or open issues or pull requests.
ima02/resources/defects4j-checkout-closure-1f/test/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntaxTest.java

1079 lines
39 KiB
Java
Raw Normal View History

2023-04-25 11:33:41 +00:00
/*
* 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) != x>=NaN don't fold < and > cases.
foldSame("while(!(x>y)){a=b;}");
foldSame("while(!(x>=y)){a=b;}");
foldSame("while(!(x<y)){a=b;}");
foldSame("while(!(x<=y)){a=b;}");
foldSame("while(!(x<=NaN)){a=b;}");
// NOT forces a boolean context
fold("x = !(y() && true)", "x = !y()");
// This will be further optimized by PeepholeFoldConstants.
fold("x = !true", "x = !1");
}
public void testFoldRegExpConstructor() {
enableNormalize();
// Cannot fold all the way to a literal because there are too few arguments.
fold("x = new RegExp", "x = RegExp()");
// Empty regexp should not fold to // since that is a line comment in JS
fold("x = new RegExp(\"\")", "x = RegExp(\"\")");
fold("x = new RegExp(\"\", \"i\")", "x = RegExp(\"\",\"i\")");
// Bogus flags should not fold
testSame("x = RegExp(\"foobar\", \"bogus\")",
PeepholeSubstituteAlternateSyntax.INVALID_REGULAR_EXPRESSION_FLAGS);
// Can Fold
fold("x = new RegExp(\"foobar\")", "x = /foobar/");
fold("x = RegExp(\"foobar\")", "x = /foobar/");
fold("x = new RegExp(\"foobar\", \"i\")", "x = /foobar/i");
// Make sure that escaping works
fold("x = new RegExp(\"\\\\.\", \"i\")", "x = /\\./i");
fold("x = new RegExp(\"/\", \"\")", "x = /\\//");
fold("x = new RegExp(\"[/]\", \"\")", "x = /[/]/");
fold("x = new RegExp(\"///\", \"\")", "x = /\\/\\/\\//");
fold("x = new RegExp(\"\\\\\\/\", \"\")", "x = /\\//");
fold("x = new RegExp(\"\\n\")", "x = /\\n/");
fold("x = new RegExp('\\\\\\r')", "x = /\\r/");
// Don't fold really long regexp literals, because Opera 9.2's
// regexp parser will explode.
String longRegexp = "";
for (int i = 0; i < 200; i++) longRegexp += "x";
foldSame("x = RegExp(\"" + longRegexp + "\")");
// Shouldn't fold RegExp unnormalized because
// we can't be sure that RegExp hasn't been redefined
disableNormalize();
foldSame("x = new RegExp(\"foobar\")");
}
public void testVersionSpecificRegExpQuirks() {
enableNormalize();
// Don't fold if the flags contain 'g'
enableEcmaScript5(false);
fold("x = new RegExp(\"foobar\", \"g\")",
"x = RegExp(\"foobar\",\"g\")");
fold("x = new RegExp(\"foobar\", \"ig\")",
"x = RegExp(\"foobar\",\"ig\")");
// ... unless in ECMAScript 5 mode per section 7.8.5 of ECMAScript 5.
enableEcmaScript5(true);
fold("x = new RegExp(\"foobar\", \"ig\")",
"x = /foobar/ig");
// Don't fold things that crash older versions of Safari and that don't work
// as regex literals on other old versions of Safari
enableEcmaScript5(false);
fold("x = new RegExp(\"\\u2028\")", "x = RegExp(\"\\u2028\")");
fold("x = new RegExp(\"\\\\\\\\u2028\")", "x = /\\\\u2028/");
// Sunset Safari exclusions for ECMAScript 5 and later.
enableEcmaScript5(true);
fold("x = new RegExp(\"\\u2028\\u2029\")", "x = /\\u2028\\u2029/");
fold("x = new RegExp(\"\\\\u2028\")", "x = /\\u2028/");
fold("x = new RegExp(\"\\\\\\\\u2028\")", "x = /\\\\u2028/");
}
public void testFoldRegExpConstructorStringCompare() {
// Might have something to do with the internal representation of \n and how
// it is used in node comparison.
assertResultString("x=new RegExp(\"\\n\", \"i\")", "x=/\\n/i", true);
}
public void testContainsUnicodeEscape() throws Exception {
assertTrue(!PeepholeSubstituteAlternateSyntax.containsUnicodeEscape(""));
assertTrue(!PeepholeSubstituteAlternateSyntax.containsUnicodeEscape("foo"));
assertTrue(PeepholeSubstituteAlternateSyntax.containsUnicodeEscape(
"\u2028"));
assertTrue(PeepholeSubstituteAlternateSyntax.containsUnicodeEscape(
"\\u2028"));
assertTrue(
PeepholeSubstituteAlternateSyntax.containsUnicodeEscape("foo\\u2028"));
assertTrue(!PeepholeSubstituteAlternateSyntax.containsUnicodeEscape(
"foo\\\\u2028"));
assertTrue(PeepholeSubstituteAlternateSyntax.containsUnicodeEscape(
"foo\\\\u2028bar\\u2028"));
}
public void testFoldLiteralObjectConstructors() {
enableNormalize();
// Can fold when normalized
fold("x = new Object", "x = ({})");
fold("x = new Object()", "x = ({})");
fold("x = Object()", "x = ({})");
disableNormalize();
// Cannot fold above when not normalized
foldSame("x = new Object");
foldSame("x = new Object()");
foldSame("x = Object()");
enableNormalize();
// Cannot fold, the constructor being used is actually a local function
foldSame("x = " +
"(function f(){function Object(){this.x=4};return new Object();})();");
}
public void testFoldLiteralArrayConstructors() {
enableNormalize();
// No arguments - can fold when normalized
fold("x = new Array", "x = []");
fold("x = new Array()", "x = []");
fold("x = Array()", "x = []");
// One argument - can be fold when normalized
fold("x = new Array(0)", "x = []");
fold("x = Array(0)", "x = []");
fold("x = new Array(\"a\")", "x = [\"a\"]");
fold("x = Array(\"a\")", "x = [\"a\"]");
// One argument - cannot be fold when normalized
fold("x = new Array(7)", "x = Array(7)");
fold("x = Array(7)", "x = Array(7)");
fold("x = new Array(y)", "x = Array(y)");
fold("x = Array(y)", "x = Array(y)");
fold("x = new Array(foo())", "x = Array(foo())");
fold("x = Array(foo())", "x = Array(foo())");
// More than one argument - can be fold when normalized
fold("x = new Array(1, 2, 3, 4)", "x = [1, 2, 3, 4]");
fold("x = Array(1, 2, 3, 4)", "x = [1, 2, 3, 4]");
fold("x = new Array('a', 1, 2, 'bc', 3, {}, 'abc')",
"x = ['a', 1, 2, 'bc', 3, {}, 'abc']");
fold("x = Array('a', 1, 2, 'bc', 3, {}, 'abc')",
"x = ['a', 1, 2, 'bc', 3, {}, 'abc']");
fold("x = new Array(Array(1, '2', 3, '4'))", "x = [[1, '2', 3, '4']]");
fold("x = Array(Array(1, '2', 3, '4'))", "x = [[1, '2', 3, '4']]");
fold("x = new Array(Object(), Array(\"abc\", Object(), Array(Array())))",
"x = [{}, [\"abc\", {}, [[]]]]");
fold("x = new Array(Object(), Array(\"abc\", Object(), Array(Array())))",
"x = [{}, [\"abc\", {}, [[]]]]");
disableNormalize();
// Cannot fold above when not normalized
foldSame("x = new Array");
foldSame("x = new Array()");
foldSame("x = Array()");
foldSame("x = new Array(0)");
foldSame("x = Array(0)");
foldSame("x = new Array(\"a\")");
foldSame("x = Array(\"a\")");
foldSame("x = new Array(7)");
foldSame("x = Array(7)");
foldSame("x = new Array(foo())");
foldSame("x = Array(foo())");
foldSame("x = new Array(1, 2, 3, 4)");
foldSame("x = Array(1, 2, 3, 4)");
foldSame("x = new Array('a', 1, 2, 'bc', 3, {}, 'abc')");
foldSame("x = Array('a', 1, 2, 'bc', 3, {}, 'abc')");
foldSame("x = new Array(Array(1, '2', 3, '4'))");
foldSame("x = Array(Array(1, '2', 3, '4'))");
foldSame("x = new Array(" +
"Object(), Array(\"abc\", Object(), Array(Array())))");
foldSame("x = new Array(" +
"Object(), Array(\"abc\", Object(), Array(Array())))");
}
public void testMinimizeExprCondition() {
fold("(x ? true : false) && y()", "x&&y()");
fold("(x ? false : true) && y()", "(!x)&&y()");
fold("(x ? true : y) && y()", "(x || y)&&y()");
fold("(x ? y : false) && y()", "(x && y)&&y()");
fold("(x && true) && y()", "x && y()");
fold("(x && false) && y()", "0&&y()");
fold("(x || true) && y()", "1&&y()");
fold("(x || false) && y()", "x&&y()");
}
public void testMinimizeWhileCondition() {
// This test uses constant folding logic, so is only here for completeness.
fold("while(!!true) foo()", "while(1) foo()");
// These test tryMinimizeCondition
fold("while(!!x) foo()", "while(x) foo()");
fold("while(!(!x&&!y)) foo()", "while(x||y) foo()");
fold("while(x||!!y) foo()", "while(x||y) foo()");
fold("while(!(!!x&&y)) foo()", "while(!x||!y) foo()");
fold("while(!(!x&&y)) foo()", "while(x||!y) foo()");
fold("while(!(x||!y)) foo()", "while(!x&&y) foo()");
fold("while(!(x||y)) foo()", "while(!x&&!y) foo()");
fold("while(!(!x||y-z)) foo()", "while(x&&!(y-z)) foo()");
fold("while(!(!(x/y)||z+w)) foo()", "while(x/y&&!(z+w)) foo()");
foldSame("while(!(x+y||z)) foo()");
foldSame("while(!(x&&y*z)) foo()");
fold("while(!(!!x&&y)) foo()", "while(!x||!y) foo()");
fold("while(x&&!0) foo()", "while(x) foo()");
fold("while(x||!1) foo()", "while(x) foo()");
fold("while(!((x,y)&&z)) foo()", "while(!(x,y)||!z) foo()");
}
public void testMinimizeForCondition() {
// This test uses constant folding logic, so is only here for completeness.
// These could be simplified to "for(;;) ..."
fold("for(;!!true;) foo()", "for(;1;) foo()");
// Don't bother with FOR inits as there are normalized out.
fold("for(!!true;;) foo()", "for(!0;;) foo()");
// These test tryMinimizeCondition
fold("for(;!!x;) foo()", "for(;x;) foo()");
// sanity check
foldSame("for(a in b) foo()");
foldSame("for(a in {}) foo()");
foldSame("for(a in []) foo()");
fold("for(a in !!true) foo()", "for(a in !0) foo()");
}
public void testMinimizeCondition_example1() {
// Based on a real failing code sample.
fold("if(!!(f() > 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)");
}
}
}