/* * Copyright 2011 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; /** * Unit tests for {@link ShadowVariables}. * */ public final class ShadowVariablesTest extends Es6CompilerTestCase { // Use pseudo names to make test easier to read. private boolean generatePseudoNames = false; private RenameVars pass = null; @Override protected CompilerPass getProcessor(Compiler compiler) { pass = new RenameVars( compiler, "", false, false, generatePseudoNames, true, false, null, null, null, new DefaultNameGenerator()); return pass; } @Override protected int getNumRepetitions() { return 1; } @Override protected void setUp() throws Exception { super.setUp(); generatePseudoNames = false; } @Override protected void tearDown() throws Exception { super.tearDown(); pass = null; } public void testShadowSimple1() { test("function foo(x) { return function (y) {} }", "function b(a) { return function (a) {} }"); generatePseudoNames = true; test("function foo ( x ) { return function ( y ) {} }", "function $foo$$($x$$) { return function ($x$$) {} }"); } public void testShadowSimple2() { test("function foo(x,y) { return function (y,z) {} }", "function c(a,b) { return function (a,b) {} }"); generatePseudoNames = true; test("function foo ( x , y ) { return function ( y , z ) {} }", "function $foo$$($x$$,$y$$) { return function ($x$$,$y$$) {} }"); } /** * If we have a choice, pick out the most used variable to shadow. */ public void testShadowMostUsedVar() { generatePseudoNames = true; test("function foo () {var x ; var y ; y ; y ; y ; x ;" + " return function ( k ) {} }", "function $foo$$() {var $x$$; var $y$$; $y$$;$y$$;$y$$;$x$$;" + " return function ($y$$) {} }"); } public void testNoShadowReferencedVariables() { generatePseudoNames = true; // Unsafe to shadow function names on IE8 test(LINE_JOINER.join( "function f1() {", " var x; x; x; x;", " return function f2(y) {", " return function f3() {", " x;", " };", " };", "}"), LINE_JOINER.join( "function $f1$$() {", " var $x$$; $x$$; $x$$; $x$$;", " return function $f2$$($y$$) {", " return function $f3$$() {", " $x$$;", " };", " };", "}")); } public void testNoShadowGlobalVariables() { generatePseudoNames = true; test("var x ; x ; function foo () { return function ( y ) {}}", "var $x$$; $x$$; function $foo$$() { return function ($y$$) {}}"); } public void testShadowBleedInFunctionName() { generatePseudoNames = true; test("function foo () { function b ( y ) { y } b ; b ;}", "function $foo$$() { function $b$$($b$$) {$b$$} $b$$; $b$$;}"); } public void testNoShadowLessPopularName() { generatePseudoNames = true; // We make sure that y doesn't pick x as a shadow and remains to be renamed // to 'a'. // If we do shadow y with whatever x renames to (say b) we will // get 4 b's and 7 a's while currently we get 3 b's and 8 a's. // I believe this arrangement will always be better for gzipping. test("function f1 ( x ) {" + " function f2 ( y ) {} x ; x ;}" + "function f3 ( i ) {" + " var k ; var j ; j ; j ; j ; j ; j ; j ;}", "function $f1$$($x$$) {" + " function $f2$$($y$$) {} $x$$;$x$$;}" + "function $f3$$($i$$) {" + " var $k$$; var $j$$;$j$$;$j$$;$j$$;$j$$;$j$$;$j$$;}"); } public void testShadowFunctionName() { generatePseudoNames = true; test("var g = function() {" + " var x ; return function(){function y (){}}}", "var $g$$ = function() {" + " var $x$$; return function(){function $x$$(){}}}"); } public void testShadowLotsOfScopes1() { generatePseudoNames = true; test("var g = function( x ) { return function() { return function() {" + " return function() { var y }}}}", "var $g$$ = function($x$$) { return function() { return function() {" + " return function() { var $x$$ }}}}"); } public void testShadowLotsOfScopes2() { generatePseudoNames = true; // 'y' doesn't have a candidate to shadow due to upward referencing. test("var g = function( x ) { return function( y ) " + " {return function() {return function() { x }}}}", "var $g$$ = function($x$$) { return function($y$$) " + " {return function() {return function() { $x$$ }}}}"); test("var g = function( x ) { return function() " + " {return function( y ) {return function() { x }}}}", "var $g$$ = function($x$$) { return function() " + " {return function($y$$) {return function() { $x$$ }}}}"); test("var g = function( x ) { return function() " + " {return function() {return function( y ) { x }}}}", "var $g$$ = function($x$$) { return function() " + " {return function() {return function($y$$) { $x$$ }}}}"); } public void testShadowLotsOfScopes3() { generatePseudoNames = true; // 'y' doesn't have a candidate to shadow due to upward referencing. test("var g = function( x ) { return function() " + " {return function() {return function() { x }; var y }}}", "var $g$$ = function($x$$) { return function() " + " {return function() {return function() { $x$$ }; var $y$$}}}"); test("var g = function( x ) { return function() " + " {return function() {return function() { x }}; var y }}", "var $g$$ = function($x$$) { return function() " + " {return function() {return function() { $x$$ }}; var $y$$}}"); test("var g = function( x ) { return function() " + " {return function() {return function() { x }}}; var y }", "var $g$$ = function($x$$) { return function() " + " {return function() {return function() { $x$$ }}}; var $y$$}"); } public void testShadowLotsOfScopes4() { // Make sure we do get the optimal shadowing scheme where test("var g = function(x) { return function() { return function() {" + " return function(){return function(){};var m};var n};var o}}", "var b = function(a) { return function() { return function() {" + " return function(){return function(){};var a};var a};var a}}"); } public void testShadowLotsOfScopes5() { generatePseudoNames = true; test("var g = function( x ) {" + " return function() { return function() {" + " return function() { return function() {" + " x }; o };var n };var o };var p }", "var $g$$ = function($x$$) {" + " return function() { return function() {" + " return function() { return function() {" + " $x$$};$o$$};var $p$$};var $o$$};var $p$$}"); test("var g = function( x ) {" + " return function() { return function() {" + " return function() { return function() {" + " x }; p };var n };var o };var p }", "var $g$$ = function($x$$) {" + " return function() { return function() {" + " return function() { return function() {" + " $x$$};$p$$};var $o$$};var $o$$};var $p$$}"); } public void testShadowWithShadowAlready() { test("var g = function(x) { return function() { return function() {" + " return function(){return function(){x}};var p};var o};var p}", "var c = function(b) { return function() { return function() {" + " return function(){return function(){b}};var a};var a};var a}"); test("var g = function(x) { return function() { return function() {" + " return function(){return function(){x};p};var p};var o};var p}", "var c = function(b) { return function() { return function() {" + " return function(){return function(){b};a};var a};var a};var a}"); } public void testShadowBug1() { generatePseudoNames = true; test("function f ( x ) { return function( y ) {" + " return function( x ) { x + y ; }}}", "function $f$$($x$$) { return function($y$$) {" + " return function($x$$) { $x$$ + $y$$; }}}"); } public void testOptimal() { // A test for a case that wasn't optimal in a single pass algorithm. test("function f(x) { function g(y) { function h(x) {}}}", "function c(a) { function b(a) { function b(a) {}}}"); } public void testSharingAcrossInnerScopes() { test("function f() {var f=function g(){g()}; var x=function y(){y()}}", "function c() {var d=function a(){a()}; var e=function b(){b()}}"); test("function f(x) { return x ? function(y){} : function(z) {} }", "function b(a) { return a ? function(a){} : function(a) {} }"); } public void testExportedLocal1() { test("function f(a) { a();a();a(); return function($super){} }", "function b(a) { a();a();a(); return function($super){} }"); } public void testExportedLocal2() { test("function f($super) { $super();$super(); return function(a){} }", "function a($super) { $super();$super(); return function(b){} }"); } public void testRenameMapHasNoDuplicates() { test("function foo(x) { return function (y) {} }", "function b(a) { return function (a) {} }"); VariableMap vm = pass.getVariableMap(); try { vm.getNewNameToOriginalNameMap(); } catch (java.lang.IllegalArgumentException unexpected) { fail("Invalid VariableMap generated: " + vm.getOriginalNameToNewNameMap()); } } public void testBug4172539() { // All the planets must line up. When we look at the 2nd inner function, // y can shadow x, also m can shadow x as well. Now all that is left for // n to shadow is 'y'. Now because y has already shadowed x, the pseudo // name maps has already updated y gets $x$$. This mean n will be updated // with "$x$$" in the name map which is incorrect. That is the reason // why we can't update the pseudo name map on-the-fly. generatePseudoNames = true; test( LINE_JOINER.join( "function f(x) {", " x;x;x;", " return function (y) { y; x };", " return function (y) {", " y;", " return function (m, n) {", " m;m;m;", " };", " };", "}"), LINE_JOINER.join( "function $f$$($x$$) {", " $x$$;$x$$;$x$$;", " return function ($y$$) { $y$$; $x$$ };", " return function ($x$$) {", " $x$$;", " return function ($x$$, $y$$) {", " $x$$;$x$$;$x$$;", " };", " };", "}")); } public void testBlocks() { // Unsafe to shadow nested "var"s test(LINE_JOINER.join( "function f() {", " var x = 1;", " {", " var y = 2;", " {", " var z = 3;", " }", " }", "}"), LINE_JOINER.join( "function a() {", " var b = 1;", " {", " var c = 2;", " {", " var d = 3;", " }", " }", "}")); // Safe to shadow nested "let"s testEs6(LINE_JOINER.join( "function f() {", " let x = 1;", " {", " let y = 2;", " {", " let z = 3;", " }", " }", "}"), LINE_JOINER.join( "function b() {", " let a = 1;", " {", " let a = 2;", " {", " let a = 3;", " }", " }", "}")); testEs6(LINE_JOINER.join( "function f() {", " let x = 1;", " {", " let y = x;", " {", " let z = y;", " let w = x;", " }", " }", "}"), LINE_JOINER.join( "function c() {", " let a = 1;", " {", " let b = a;", " {", " let d = b;", " let e = a;", " }", " }", "}")); } public void testCatch() { // Unsafe to shadow caught exceptions on IE8 since they are not block scoped test(LINE_JOINER.join( "function f(a) {", " try {", " } catch (e) {", " }", "}"), LINE_JOINER.join( "function a(b) {", " try {", " } catch (c) {", " }", "}")); } }