/* * Copyright 2009 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.javascript.jscomp.MakeDeclaredNamesUnique.InlineRenamer; import com.google.javascript.rhino.Node; /** * @author johnlenz@google.com (John Lenz) */ public final class MakeDeclaredNamesUniqueTest extends Es6CompilerTestCase { private boolean useDefaultRenamer = false; private boolean invert = false; private boolean removeConst = false; private final String localNamePrefix = "unique_"; @Override protected CompilerPass getProcessor(final Compiler compiler) { if (!invert) { return new CompilerPass() { @Override public void process(Node externs, Node root) { compiler.resetUniqueNameId(); MakeDeclaredNamesUnique renamer; if (useDefaultRenamer) { renamer = new MakeDeclaredNamesUnique(); } else { renamer = new MakeDeclaredNamesUnique(new InlineRenamer(compiler.getCodingConvention(), compiler.getUniqueNameIdSupplier(), localNamePrefix, removeConst, true, null)); } NodeTraversal.traverseRootsEs6(compiler, renamer, externs, root); } }; } else { return MakeDeclaredNamesUnique.getContextualRenameInverter(compiler); } } @Override protected int getNumRepetitions() { // The normalize pass is only run once. return 1; } @Override public void setUp() { removeConst = false; invert = false; useDefaultRenamer = false; } private void testWithInversion(String original, String expected) { invert = false; test(original, expected); invert = true; test(expected, original); invert = false; } private void testWithInversionEs6(String original, String expected) { invert = false; testEs6(original, expected); invert = true; testEs6(expected, original); invert = false; } private void testSameWithInversion(String externs, String original) { invert = false; testSame(externs, original, null); invert = true; testSame(externs, original, null); invert = false; } private void testSameWithInversion(String original) { testSameWithInversion("", original); } private void testSameWithInversionEs6(String original) { invert = false; testSameEs6(original); invert = true; testSameEs6(original); invert = false; } private static String wrapInFunction(String s) { return "function f(){" + s + "}"; } private void testInFunction(String original, String expected) { test(wrapInFunction(original), wrapInFunction(expected)); } private void testInFunctionEs6(String original, String expected) { testEs6(wrapInFunction(original), wrapInFunction(expected)); } public void testMakeLocalNamesUniqueWithContext1() { // Set the test type this.useDefaultRenamer = true; invert = true; test( "var a;function foo(){var a$jscomp$inline_1; a = 1}", "var a;function foo(){var a$jscomp$0; a = 1}"); test( "var a;function foo(){var a$jscomp$inline_1;}", "var a;function foo(){var a;}"); testEs6( "let a;function foo(){let a$jscomp$inline_1; a = 1}", "let a;function foo(){let a$jscomp$0; a = 1}"); testEs6( "const a = 1;function foo(){let a$jscomp$inline_1;}", "const a = 1;function foo(){let a;}"); testEs6( "class A {} function foo(){class A$jscomp$inline_1 {}}", "class A {} function foo(){class A {}}"); } public void testMakeLocalNamesUniqueWithContext2() { // Set the test type this.useDefaultRenamer = true; // Verify global names are untouched. testSameWithInversion("var a;"); testSameWithInversionEs6("let a;"); // Verify global names are untouched. testSameWithInversion("a;"); // Local names are made unique. testWithInversion( "var a;function foo(a){var b;a}", "var a;function foo(a$jscomp$1){var b;a$jscomp$1}"); testWithInversion( "var a;function foo(){var b;a}function boo(){var b;a}", "var a;function foo(){var b;a}function boo(){var b$jscomp$1;a}"); testWithInversion( "function foo(a){var b}" + "function boo(a){var b}", "function foo(a){var b}" + "function boo(a$jscomp$1){var b$jscomp$1}"); testWithInversionEs6( "let a;function foo(a){let b;a}", "let a;function foo(a$jscomp$1){let b;a$jscomp$1}"); testWithInversionEs6( "let a;function foo(){let b;a}function boo(){let b;a}", "let a;function foo(){let b;a}function boo(){let b$jscomp$1;a}"); testWithInversionEs6( "function foo(a){let b}" + "function boo(a){let b}", "function foo(a){let b}" + "function boo(a$jscomp$1){let b$jscomp$1}"); // Verify functions expressions are renamed. testWithInversion( "var a = function foo(){foo()};var b = function foo(){foo()};", "var a = function foo(){foo()};var b = function foo$jscomp$1(){foo$jscomp$1()};"); testWithInversionEs6( "let a = function foo(){foo()};let b = function foo(){foo()};", "let a = function foo(){foo()};let b = function foo$jscomp$1(){foo$jscomp$1()};"); // Verify catch exceptions names are made unique testSameWithInversion("try { } catch(e) {e;}"); // Inversion does not handle exceptions correctly. test( "try { } catch(e) {e;}; try { } catch(e) {e;}", "try { } catch(e) {e;}; try { } catch(e$jscomp$1) {e$jscomp$1;}"); test( "try { } catch(e) {e; try { } catch(e) {e;}};", "try { } catch(e) {e; try { } catch(e$jscomp$1) {e$jscomp$1;} }; "); } public void testMakeLocalNamesUniqueWithContext3() { // Set the test type this.useDefaultRenamer = true; String externs = "var extern1 = {};"; // Verify global names are untouched. testSameWithInversion(externs, "var extern1 = extern1 || {};"); // Verify global names are untouched. testSame(externs, "var extern1 = extern1 || {};", null); } public void testMakeLocalNamesUniqueWithContext4() { // Set the test type this.useDefaultRenamer = true; // Inversion does not handle exceptions correctly. testInFunction( "var e; try { } catch(e) {e;}; try { } catch(e) {e;}", "var e; try { } catch(e$jscomp$1) {e$jscomp$1;}; try { } catch(e$jscomp$2) {e$jscomp$2;}"); testInFunction( "var e; try { } catch(e) {e; try { } catch(e) {e;}}", "var e; try { } catch(e$jscomp$1) {e$jscomp$1; try { } catch(e$jscomp$2) {e$jscomp$2;} }"); testInFunction( "try { } catch(e) {e;}; try { } catch(e) {e;} var e;", "try { } catch(e$jscomp$1) {e$jscomp$1;}; try { } catch(e$jscomp$2) {e$jscomp$2;} var e;"); testInFunction( "try { } catch(e) {e; try { } catch(e) {e;}} var e;", "try { } catch(e$jscomp$1) {e$jscomp$1; try { } catch(e$jscomp$2) {e$jscomp$2;} } var e;"); invert = true; testInFunctionEs6( "var e; try { } catch(e$jscomp$0) {e$jscomp$0;}; try { } catch(e$jscomp$1) {e$jscomp$1;}", "var e; try { } catch(e) {e;}; try { } catch(e) {e;}"); testInFunctionEs6( "var e; try { } catch(e$jscomp$1) {e$jscomp$1; try { } catch(e$jscomp$2) {e$jscomp$2;} };", "var e; try { } catch(e$jscomp$0) {e$jscomp$0; try { } catch(e) {e;} };"); testInFunctionEs6( "try { } catch(e) {e;}; try { } catch(e$jscomp$1) {e$jscomp$1;};var e$jscomp$2;", "try { } catch(e) {e;}; try { } catch(e) {e;};var e$jscomp$0;"); testInFunctionEs6( "try { } catch(e) {e; try { } catch(e$jscomp$1) {e$jscomp$1;} };var e$jscomp$2;", "try { } catch(e) {e; try { } catch(e) {e;} };var e$jscomp$0;"); } public void testMakeLocalNamesUniqueWithContext5() { // Set the test type this.useDefaultRenamer = true; testWithInversion( "function f(){var f; f = 1}", "function f(){var f$jscomp$1; f$jscomp$1 = 1}"); testWithInversion( "function f(f){f = 1}", "function f(f$jscomp$1){f$jscomp$1 = 1}"); testWithInversion( "function f(f){var f; f = 1}", "function f(f$jscomp$1){var f$jscomp$1; f$jscomp$1 = 1}"); test( "var fn = function f(){var f; f = 1}", "var fn = function f(){var f$jscomp$1; f$jscomp$1 = 1}"); test( "var fn = function f(f){f = 1}", "var fn = function f(f$jscomp$1){f$jscomp$1 = 1}"); test( "var fn = function f(f){var f; f = 1}", "var fn = function f(f$jscomp$1){var f$jscomp$1; f$jscomp$1 = 1}"); } public void testArguments() { // Set the test type this.useDefaultRenamer = true; // Don't distinguish between "arguments", it can't be made unique. testSameWithInversion( "function foo(){var arguments;function bar(){var arguments;}}"); invert = true; // Don't introduce new references to arguments, it is special. test( "function foo(){var arguments$jscomp$1;}", "function foo(){var arguments$jscomp$0;}"); } public void testClassInForLoop() { useDefaultRenamer = true; testSameEs6("for (class a {};;) { break; }"); } public void testFunctionInForLoop() { useDefaultRenamer = true; testSameEs6("for (function a() {};;) { break; }"); } public void testLetsInSeparateBlocks() { useDefaultRenamer = true; testEs6( LINE_JOINER.join( "if (x) {", " let e;", " alert(e);", "}", "if (y) {", " let e;", " alert(e);", "}"), LINE_JOINER.join( "if (x) {", " let e;", " alert(e);", "}", "if (y) {", " let e$jscomp$1;", " alert(e$jscomp$1);", "}")); } public void testConstInGlobalHoistScope() { useDefaultRenamer = true; testSameEs6( LINE_JOINER.join( "if (true) {", " const x = 1; alert(x);", "}")); testEs6( LINE_JOINER.join( "if (true) {", " const x = 1; alert(x);", "} else {", " const x = 1; alert(x);", "}"), LINE_JOINER.join( "if (true) {", " const x = 1; alert(x);", "} else {", " const x$jscomp$1 = 1; alert(x$jscomp$1);", "}")); } public void testMakeLocalNamesUniqueWithoutContext() { // Set the test type this.useDefaultRenamer = false; test("var a;", "var a$jscomp$unique_0"); testEs6("let a;", "let a$jscomp$unique_0"); // Verify undeclared names are untouched. testSame("a;"); // Local names are made unique. test("var a;" + "function foo(a){var b;a}", "var a$jscomp$unique_0;" + "function foo$jscomp$unique_1(a$jscomp$unique_2){" + " var b$jscomp$unique_3;a$jscomp$unique_2}"); test("var a;" + "function foo(){var b;a}" + "function boo(){var b;a}", "var a$jscomp$unique_0;" + "function foo$jscomp$unique_1(){var b$jscomp$unique_3;a$jscomp$unique_0}" + "function boo$jscomp$unique_2(){var b$jscomp$unique_4;a$jscomp$unique_0}"); testEs6( "let a; function foo(a) {let b; a; }", LINE_JOINER.join( "let a$jscomp$unique_0;", "function foo$jscomp$unique_1(a$jscomp$unique_2) {", " let b$jscomp$unique_3;", " a$jscomp$unique_2;", "}")); testEs6( LINE_JOINER.join( "let a;", "function foo() { let b; a; }", "function boo() { let b; a; }"), LINE_JOINER.join( "let a$jscomp$unique_0;", "function foo$jscomp$unique_1() {", " let b$jscomp$unique_3;", " a$jscomp$unique_0;", "}", "function boo$jscomp$unique_2() {", " let b$jscomp$unique_4;", " a$jscomp$unique_0;", "}")); // Verify function expressions are renamed. test("var a = function foo(){foo()};", "var a$jscomp$unique_0 = function foo$jscomp$unique_1(){foo$jscomp$unique_1()};"); testEs6("const a = function foo(){foo()};", "const a$jscomp$unique_0 = function foo$jscomp$unique_1(){foo$jscomp$unique_1()};"); // Verify catch exceptions names are made unique test("try { } catch(e) {e;}", "try { } catch(e$jscomp$unique_0) {e$jscomp$unique_0;}"); test("try { } catch(e) {e;};" + "try { } catch(e) {e;}", "try { } catch(e$jscomp$unique_0) {e$jscomp$unique_0;};" + "try { } catch(e$jscomp$unique_1) {e$jscomp$unique_1;}"); test("try { } catch(e) {e; " + "try { } catch(e) {e;}};", "try { } catch(e$jscomp$unique_0) {e$jscomp$unique_0; " + "try { } catch(e$jscomp$unique_1) {e$jscomp$unique_1;} }; "); } public void testMakeLocalNamesUniqueWithoutContext2() { // Set the test type this.useDefaultRenamer = false; test("var _a;", "var JSCompiler__a$jscomp$unique_0"); test("var _a = function _b(_c) { var _d; };", "var JSCompiler__a$jscomp$unique_0 = function JSCompiler__b$jscomp$unique_1(" + "JSCompiler__c$jscomp$unique_2) { var JSCompiler__d$jscomp$unique_3; };"); testEs6("let _a;", "let JSCompiler__a$jscomp$unique_0"); testEs6("const _a = function _b(_c) { let _d; };", "const JSCompiler__a$jscomp$unique_0 = function JSCompiler__b$jscomp$unique_1(" + "JSCompiler__c$jscomp$unique_2) { let JSCompiler__d$jscomp$unique_3; };"); } public void testOnlyInversion() { invert = true; test("function f(a, a$jscomp$1) {}", "function f(a, a$jscomp$0) {}"); test("function f(a$jscomp$1, b$jscomp$2) {}", "function f(a, b) {}"); test("function f(a$jscomp$1, a$jscomp$2) {}", "function f(a, a$jscomp$0) {}"); testEs6("try { } catch(e) {e; try { } catch(e$jscomp$1) {e$jscomp$1;} }; ", "try { } catch(e) {e; try { } catch(e) {e;} }; "); testSame("var a$jscomp$1;"); testSame("function f() { var $jscomp$; }"); testSame("var CONST = 3; var b = CONST;"); test("function f() {var CONST = 3; var ACONST$jscomp$1 = 2;}", "function f() {var CONST = 3; var ACONST = 2;}"); } public void testOnlyInversion2() { invert = true; testEs6("function f() {try { } catch(e) {e;}; try { } catch(e$jscomp$0) {e$jscomp$0;}}", "function f() {try { } catch(e) {e;}; try { } catch(e) {e;}}"); } public void testOnlyInversion3() { invert = true; test(LINE_JOINER.join( "function x1() {", " var a$jscomp$1;", " function x2() {", " var a$jscomp$2;", " }", " function x3() {", " var a$jscomp$3;", " }", "}"), LINE_JOINER.join( "function x1() {", " var a$jscomp$0;", " function x2() {", " var a;", " }", " function x3() {", " var a;", " }", "}")); } public void testOnlyInversion4() { invert = true; test(LINE_JOINER.join( "function x1() {", " var a$jscomp$0;", " function x2() {", " var a;a$jscomp$0++", " }", "}"), LINE_JOINER.join( "function x1() {", " var a$jscomp$1;", " function x2() {", " var a;a$jscomp$1++", " }", "}")); } public void testConstRemovingRename1() { removeConst = true; test("(function () {var CONST = 3; var ACONST$jscomp$1 = 2;})", "(function () {var CONST$jscomp$unique_0 = 3; var ACONST$jscomp$unique_1 = 2;})"); } public void testConstRemovingRename2() { removeConst = true; test("var CONST = 3; var b = CONST;", "var CONST$jscomp$unique_0 = 3; var b$jscomp$unique_1 = CONST$jscomp$unique_0;"); } public void testRestParam() { testEs6( "function f(...x) { x; }", "function f$jscomp$unique_0(...x$jscomp$unique_1) { x$jscomp$unique_1; }"); } public void testVarParamSameName0() { test( LINE_JOINER.join( "function f(x) {", " if (!x) var x = 6;", "}"), LINE_JOINER.join( "function f$jscomp$unique_0(x$jscomp$unique_1) {", " if (!x$jscomp$unique_1) var x$jscomp$unique_1 = 6;", "}")); } public void testVarParamSameName1() { test( LINE_JOINER.join( "function f(x) {", " if (!x) x = 6;", "}"), LINE_JOINER.join( "function f$jscomp$unique_0(x$jscomp$unique_1) {", " if (!x$jscomp$unique_1) x$jscomp$unique_1 = 6; ", "}")); } public void testVarParamSameAsLet0() { testEs6( LINE_JOINER.join( "function f(x) {", " if (!x) { let x = 6; }", "}"), LINE_JOINER.join( "function f$jscomp$unique_0(x$jscomp$unique_1) {", " if (!x$jscomp$unique_1) { let x$jscomp$unique_2 = 6; }", "}")); } }