From 34616d97a34356fba6025f9b7ac67152fb8021d6 Mon Sep 17 00:00:00 2001 From: bradfordcsmith Date: Mon, 11 Dec 2017 10:24:46 -0800 Subject: [PATCH] Complete replacement of RUPP with RemoveUnusedCode. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=178636068 --- .../javascript/jscomp/CompilerOptions.java | 2 +- .../javascript/jscomp/DefaultPassConfig.java | 48 +- .../jscomp/RemoveUnusedClassProperties.java | 2 +- .../javascript/jscomp/RemoveUnusedCode.java | 49 +- .../RemoveUnusedPrototypeProperties.java | 89 -- .../javascript/jscomp/MultiPassTest.java | 2 +- .../javascript/jscomp/OptimizeCallsTest.java | 6 +- ...moveUnusedCodePrototypePropertiesTest.java | 26 +- .../jscomp/RemoveUnusedCodeTest.java | 17 + .../RemoveUnusedPrototypePropertiesTest.java | 840 ------------------ 10 files changed, 77 insertions(+), 1004 deletions(-) delete mode 100644 src/com/google/javascript/jscomp/RemoveUnusedPrototypeProperties.java delete mode 100644 test/com/google/javascript/jscomp/RemoveUnusedPrototypePropertiesTest.java diff --git a/src/com/google/javascript/jscomp/CompilerOptions.java b/src/com/google/javascript/jscomp/CompilerOptions.java index f42b1c6fa51..efc11f35b84 100644 --- a/src/com/google/javascript/jscomp/CompilerOptions.java +++ b/src/com/google/javascript/jscomp/CompilerOptions.java @@ -2286,7 +2286,7 @@ public void setExtractPrototypeMemberDeclarations(ExtractPrototypeMemberDeclarat public void setRemoveUnusedPrototypeProperties(boolean enabled) { this.removeUnusedPrototypeProperties = enabled; // InlineSimpleMethods makes similar assumptions to - // RemoveUnusedPrototypeProperties, so they are enabled together. + // RemoveUnusedCode, so they are enabled together. this.inlineGetters = enabled; } diff --git a/src/com/google/javascript/jscomp/DefaultPassConfig.java b/src/com/google/javascript/jscomp/DefaultPassConfig.java index aed81bb7d0b..100e803e435 100644 --- a/src/com/google/javascript/jscomp/DefaultPassConfig.java +++ b/src/com/google/javascript/jscomp/DefaultPassConfig.java @@ -820,8 +820,8 @@ protected List getOptimizations() { // After inlining some of the variable uses, some variables are unused. // Re-run remove unused vars to clean it up. - if (options.removeUnusedVars || options.removeUnusedLocalVars) { - passes.add(getRemoveUnusedCodeOnce()); + if (shouldRunRemoveUnusedCode()) { + passes.add(removeUnusedCodeOnce); } } @@ -1007,10 +1007,6 @@ private List getMainOptimizationLoop() { passes.add(optimizeCalls); } - if (options.removeUnusedVars || options.removeUnusedLocalVars) { - passes.add(getRemoveUnusedCode()); - } - if (options.j2clPassMode.shouldAddJ2clPasses()) { passes.add(j2clConstantHoisterPass); passes.add(j2clClinitPass); @@ -1041,8 +1037,8 @@ private List getCodeRemovingPasses() { passes.add(removeUnreachableCode); } - if (options.removeUnusedPrototypeProperties) { - passes.add(removeUnusedPrototypeProperties); + if (shouldRunRemoveUnusedCode()) { + passes.add(removeUnusedCode); } if (options.removeUnusedClassProperties) { @@ -1053,6 +1049,12 @@ private List getCodeRemovingPasses() { return passes; } + private boolean shouldRunRemoveUnusedCode() { + return options.removeUnusedVars + || options.removeUnusedLocalVars + || options.removeUnusedPrototypeProperties; + } + private final HotSwapPassFactory checkSideEffects = new HotSwapPassFactory("checkSideEffects") { @Override @@ -2710,23 +2712,6 @@ protected FeatureSet featureSet() { } }; - /** Remove prototype properties that do not appear to be used. */ - private final PassFactory removeUnusedPrototypeProperties = - new PassFactory(PassNames.REMOVE_UNUSED_PROTOTYPE_PROPERTIES, false) { - @Override - protected CompilerPass create(AbstractCompiler compiler) { - return new RemoveUnusedPrototypeProperties( - compiler, - options.removeUnusedPrototypePropertiesInExterns, - !options.removeUnusedVars); - } - - @Override - protected FeatureSet featureSet() { - return ES8_MODULES; - } - }; - /** Remove prototype properties that do not appear to be used. */ private final PassFactory removeUnusedClassProperties = new PassFactory(PassNames.REMOVE_UNUSED_CLASS_PROPERTIES, false) { @@ -2880,24 +2865,19 @@ protected FeatureSet featureSet() { } }; - private PassFactory getRemoveUnusedCode() { - return getRemoveUnusedCode(false /* isOneTimePass */); - } - - private PassFactory getRemoveUnusedCodeOnce() { - return getRemoveUnusedCode(true /* isOneTimePass */); - } + private PassFactory removeUnusedCodeOnce = getRemoveUnusedCode(true /* isOneTimePass */); + private PassFactory removeUnusedCode = getRemoveUnusedCode(false /* isOneTimePass */); private PassFactory getRemoveUnusedCode(boolean isOneTimePass) { /** Removes variables that are never used. */ return new PassFactory(PassNames.REMOVE_UNUSED_CODE, isOneTimePass) { @Override protected CompilerPass create(AbstractCompiler compiler) { - boolean removeOnlyLocals = options.removeUnusedLocalVars && !options.removeUnusedVars; boolean preserveAnonymousFunctionNames = options.anonymousFunctionNaming != AnonymousFunctionNamingPolicy.OFF; return new RemoveUnusedCode.Builder(compiler) - .removeGlobals(!removeOnlyLocals) + .removeLocalVars(options.removeUnusedLocalVars) + .removeGlobals(options.removeUnusedVars) .preserveFunctionExpressionNames(preserveAnonymousFunctionNames) .removeUnusedPrototypeProperties(options.removeUnusedPrototypeProperties) .allowRemovalOfExternProperties(options.removeUnusedPrototypePropertiesInExterns) diff --git a/src/com/google/javascript/jscomp/RemoveUnusedClassProperties.java b/src/com/google/javascript/jscomp/RemoveUnusedClassProperties.java index d78d52ad9c4..2b4e6694f84 100644 --- a/src/com/google/javascript/jscomp/RemoveUnusedClassProperties.java +++ b/src/com/google/javascript/jscomp/RemoveUnusedClassProperties.java @@ -32,7 +32,7 @@ * constructors or interfaces. Explicitly ignored is the possibility that * these properties may be indirectly referenced using "for-in" or * "Object.keys". This is the same assumption used with - * RemoveUnusedPrototypeProperties but is slightly wider in scope. + * RemoveUnusedCode but is slightly wider in scope. * * TODO(tomnguyen) Handle destructuring of objects/classes as cases where the field is used. * diff --git a/src/com/google/javascript/jscomp/RemoveUnusedCode.java b/src/com/google/javascript/jscomp/RemoveUnusedCode.java index 6165c4908aa..b3b050eff5e 100644 --- a/src/com/google/javascript/jscomp/RemoveUnusedCode.java +++ b/src/com/google/javascript/jscomp/RemoveUnusedCode.java @@ -96,6 +96,7 @@ class RemoveUnusedCode implements CompilerPass { private final CodingConvention codingConvention; + private final boolean removeLocalVars; private final boolean removeGlobals; private final boolean preserveFunctionExpressionNames; @@ -118,7 +119,7 @@ class RemoveUnusedCode implements CompilerPass { private final Multimap removablesForPropertyNames = HashMultimap.create(); /** Single value to use for all vars for which we cannot remove anything at all. */ - private final VarInfo canonicalTotallyUnremovableVarInfo; + private final VarInfo canonicalUnremovableVarInfo; /** * Keep track of scopes that we've traversed. @@ -133,6 +134,7 @@ class RemoveUnusedCode implements CompilerPass { RemoveUnusedCode(Builder builder) { this.compiler = builder.compiler; this.codingConvention = builder.compiler.getCodingConvention(); + this.removeLocalVars = builder.removeLocalVars; this.removeGlobals = builder.removeGlobals; this.preserveFunctionExpressionNames = builder.preserveFunctionExpressionNames; this.removeUnusedPrototypeProperties = builder.removeUnusedPrototypeProperties; @@ -140,13 +142,14 @@ class RemoveUnusedCode implements CompilerPass { this.scopeCreator = new Es6SyntacticScopeCreator(builder.compiler); // All Vars that are completely unremovable will share this VarInfo instance. - canonicalTotallyUnremovableVarInfo = new VarInfo(); - canonicalTotallyUnremovableVarInfo.setIsExplicitlyNotRemovable(); + canonicalUnremovableVarInfo = new VarInfo(); + canonicalUnremovableVarInfo.setIsExplicitlyNotRemovable(); } public static class Builder { private final AbstractCompiler compiler; + private boolean removeLocalVars = false; private boolean removeGlobals = false; private boolean preserveFunctionExpressionNames = false; private boolean removeUnusedPrototypeProperties = false; @@ -156,6 +159,11 @@ public static class Builder { this.compiler = compiler; } + Builder removeLocalVars(boolean value) { + this.removeLocalVars = value; + return this; + } + Builder removeGlobals(boolean value) { this.removeGlobals = value; return this; @@ -1085,7 +1093,8 @@ private void maybeRemoveUnusedTrailingParameters(Node argList, Scope fparamScope */ private VarInfo traverseVar(Var var) { checkNotNull(var); - if (var.isArguments()) { + if (removeLocalVars && var.isArguments()) { + // If we are considering removing local variables, that includes parameters. // If `arguments` is used in a function we must consider all parameters to be referenced. Scope functionScope = var.getScope().getClosestHoistScope(); Node paramList = NodeUtil.getFunctionParameters(functionScope.getRootNode()); @@ -1104,7 +1113,7 @@ private VarInfo traverseVar(Var var) { getVarInfo(paramVar).markAsReferenced(); } // `arguments` is never removable. - return canonicalTotallyUnremovableVarInfo; + return canonicalUnremovableVarInfo; } else { return getVarInfo(var); } @@ -1119,26 +1128,28 @@ private VarInfo traverseVar(Var var) { */ private VarInfo getVarInfo(Var var) { checkNotNull(var); - VarInfo varInfo = varInfoMap.get(var); - if (varInfo == null) { - boolean isGlobal = var.isGlobal(); - if (isGlobal && !removeGlobals && !removeUnusedPrototypeProperties) { - varInfo = canonicalTotallyUnremovableVarInfo; - } else if (codingConvention.isExported(var.getName(), !isGlobal)) { - varInfo = canonicalTotallyUnremovableVarInfo; - } else if (var.isArguments()) { - varInfo = canonicalTotallyUnremovableVarInfo; - } else { + boolean isGlobal = var.isGlobal(); + if (isGlobal && !removeGlobals) { + return canonicalUnremovableVarInfo; + } else if (!isGlobal && !removeLocalVars) { + return canonicalUnremovableVarInfo; + } else if (codingConvention.isExported(var.getName(), !isGlobal)) { + return canonicalUnremovableVarInfo; + } else if (var.isArguments()) { + return canonicalUnremovableVarInfo; + } else { + VarInfo varInfo = varInfoMap.get(var); + if (varInfo == null) { varInfo = new VarInfo(); - if (isGlobal && !removeGlobals) { - varInfo.setIsExplicitlyNotRemovable(); - } else if (var.getParentNode().isParamList()) { + if (var.getParentNode().isParamList()) { + // We don't know where a parameter value comes from, so setting a property on it + // has unknown side effects and makes it not removable. varInfo.propertyAssignmentsWillPreventRemoval = true; } varInfoMap.put(var, varInfo); } + return varInfo; } - return varInfo; } /** diff --git a/src/com/google/javascript/jscomp/RemoveUnusedPrototypeProperties.java b/src/com/google/javascript/jscomp/RemoveUnusedPrototypeProperties.java deleted file mode 100644 index 5b5dcc774ea..00000000000 --- a/src/com/google/javascript/jscomp/RemoveUnusedPrototypeProperties.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2006 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.AnalyzePrototypeProperties.NameInfo; -import com.google.javascript.jscomp.AnalyzePrototypeProperties.Symbol; -import com.google.javascript.rhino.Node; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Removes unused properties from prototypes. - * - * NOTE: When canModifyExterns is true, this pass removes unused prototype properties - * in externs. This helped code size in the past, before type-based optimizations existed - * (and can help now for things that are not disambiguated), but is unsafe. - * For example, it can remove a polyfilled method that is not used in the source, but is - * used by the external code, so the call is not visible to the compiler. - * Therefore, the default for canModifyExterns is false, even though that increases code size - * for some projects. - * - * @author nicksantos@google.com (Nick Santos) - */ -class RemoveUnusedPrototypeProperties implements CompilerPass { - - private static final Logger logger = - Logger.getLogger(RemoveUnusedPrototypeProperties.class.getName()); - - private final AbstractCompiler compiler; - private final boolean canModifyExterns; - private final boolean anchorUnusedVars; - - /** - * Creates a new pass for removing unused prototype properties, based - * on the uniqueness of property names. - * @param compiler The compiler. - * @param canModifyExterns If true, then we can remove prototype - * properties that are declared in the externs file. - * @param anchorUnusedVars If true, then we must keep unused variables - * and the prototype properties they reference, even if they are - * never used. - */ - RemoveUnusedPrototypeProperties(AbstractCompiler compiler, - boolean canModifyExterns, - boolean anchorUnusedVars) { - this.compiler = compiler; - this.canModifyExterns = canModifyExterns; - this.anchorUnusedVars = anchorUnusedVars; - } - - @Override - public void process(Node externRoot, Node root) { - AnalyzePrototypeProperties analyzer = - new AnalyzePrototypeProperties( - compiler, - null /* no module graph */, - canModifyExterns, - anchorUnusedVars, - false /* rootScopeUsesAreGlobal */); - analyzer.process(externRoot, root); - // Remove all properties under a given name if the property name is - // never referenced. - for (NameInfo nameInfo : analyzer.getAllNameInfo()) { - if (!nameInfo.isReferenced()) { - for (Symbol declaration : nameInfo.getDeclarations()) { - // Code-change reporting happens at the remove methods - declaration.remove(compiler); - } - if (logger.isLoggable(Level.FINE)) { - logger.fine("Removed unused prototype property: " + nameInfo.name); - } - } - } - } -} diff --git a/test/com/google/javascript/jscomp/MultiPassTest.java b/test/com/google/javascript/jscomp/MultiPassTest.java index 37c69a746ab..37702986b22 100644 --- a/test/com/google/javascript/jscomp/MultiPassTest.java +++ b/test/com/google/javascript/jscomp/MultiPassTest.java @@ -425,7 +425,7 @@ private void addRemoveUnusedVars() { new PassFactory("removeUnusedVars", false) { @Override protected CompilerPass create(AbstractCompiler compiler) { - return new RemoveUnusedCode.Builder(compiler).build(); + return new RemoveUnusedCode.Builder(compiler).removeLocalVars(true).build(); } @Override diff --git a/test/com/google/javascript/jscomp/OptimizeCallsTest.java b/test/com/google/javascript/jscomp/OptimizeCallsTest.java index 9b9a50c358f..5dd81da76d2 100644 --- a/test/com/google/javascript/jscomp/OptimizeCallsTest.java +++ b/test/com/google/javascript/jscomp/OptimizeCallsTest.java @@ -50,7 +50,11 @@ public void process(Node externs, Node root) { defFinder.process(externs, root); new PureFunctionIdentifier(compiler, defFinder).process(externs, root); - new RemoveUnusedCode.Builder(compiler).removeGlobals(true).build().process(externs, root); + new RemoveUnusedCode.Builder(compiler) + .removeLocalVars(true) + .removeGlobals(true) + .build() + .process(externs, root); final OptimizeCalls passes = new OptimizeCalls(compiler); passes.addPass(new OptimizeReturns(compiler)); diff --git a/test/com/google/javascript/jscomp/RemoveUnusedCodePrototypePropertiesTest.java b/test/com/google/javascript/jscomp/RemoveUnusedCodePrototypePropertiesTest.java index 83fb36d8a83..985dfdcfcd8 100644 --- a/test/com/google/javascript/jscomp/RemoveUnusedCodePrototypePropertiesTest.java +++ b/test/com/google/javascript/jscomp/RemoveUnusedCodePrototypePropertiesTest.java @@ -20,13 +20,14 @@ import com.google.javascript.rhino.Node; /** - * Tests for {@link RemoveUnusedCode} that cover functionality originally in - * {@link RemoveUnusedPrototypeProperties}. + * Tests for {@link RemoveUnusedCode} that cover removal of prototype properties and class + * properties. */ public final class RemoveUnusedCodePrototypePropertiesTest extends CompilerTestCase { private static final String EXTERNS = MINIMAL_EXTERNS + "IFoo.prototype.bar; var mExtern; mExtern.bExtern; mExtern['cExtern'];"; + private boolean keepLocals = true; private boolean keepGlobals = false; private boolean allowRemovalOfExternProperties = false; @@ -45,6 +46,7 @@ protected CompilerPass getProcessor(Compiler compiler) { @Override public void process(Node externs, Node root) { new RemoveUnusedCode.Builder(compiler) + .removeLocalVars(!keepLocals) .removeGlobals(!keepGlobals) .removeUnusedPrototypeProperties(true) .allowRemovalOfExternProperties(allowRemovalOfExternProperties) @@ -549,22 +551,6 @@ public void testHook2() throws Exception { "(new Foo()).method1();"); } - public void testRemoveInBlock() { - test(lines( - "if (true) {", - " if (true) {", - " var foo = function() {};", - " }", - "}"), - lines( - "if (true) {", - " if (true) {", - " }", - "}")); - - test("if (true) { let foo = function() {} }", "if (true);"); - } - public void testDestructuringProperty() { // Makes the cases below shorter because we don't have to add references // to globals to keep them around and just test prototype property removal. @@ -803,6 +789,8 @@ public void testEs6Extends() { } public void testAnonClasses() { + // Make sure class expression names are removed. + keepLocals = false; test( lines( "var C = class InnerC {", @@ -850,6 +838,8 @@ public void testAnonClasses() { } public void testBaseClassExpressionHasSideEffects() { + // Make sure names are removed from class expressions. + keepLocals = false; testSame( lines( "function getBaseClass() { return class {}; }", diff --git a/test/com/google/javascript/jscomp/RemoveUnusedCodeTest.java b/test/com/google/javascript/jscomp/RemoveUnusedCodeTest.java index 2d9082d1bc2..a24852572e5 100644 --- a/test/com/google/javascript/jscomp/RemoveUnusedCodeTest.java +++ b/test/com/google/javascript/jscomp/RemoveUnusedCodeTest.java @@ -47,6 +47,7 @@ protected CompilerPass getProcessor(final Compiler compiler) { @Override public void process(Node externs, Node root) { new RemoveUnusedCode.Builder(compiler) + .removeLocalVars(true) .removeGlobals(removeGlobal) .preserveFunctionExpressionNames(preserveFunctionExpressionNames) .build() @@ -55,6 +56,22 @@ public void process(Node externs, Node root) { }; } + public void testRemoveInBlock() { + test(lines( + "if (true) {", + " if (true) {", + " var foo = function() {};", + " }", + "}"), + lines( + "if (true) {", + " if (true) {", + " }", + "}")); + + test("if (true) { let foo = function() {} }", "if (true);"); + } + public void testDeclarationInSwitch() { test( lines( diff --git a/test/com/google/javascript/jscomp/RemoveUnusedPrototypePropertiesTest.java b/test/com/google/javascript/jscomp/RemoveUnusedPrototypePropertiesTest.java deleted file mode 100644 index 3816eb78894..00000000000 --- a/test/com/google/javascript/jscomp/RemoveUnusedPrototypePropertiesTest.java +++ /dev/null @@ -1,840 +0,0 @@ -/* - * Copyright 2006 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.CompilerOptions.LanguageMode; - -/** - * Tests for {@link RemoveUnusedPrototypeProperties}. - * - * @author nicksantos@google.com (Nick Santos) - */ -public final class RemoveUnusedPrototypePropertiesTest extends CompilerTestCase { - private static final String EXTERNS = - "IFoo.prototype.bar; var mExtern; mExtern.bExtern; mExtern['cExtern'];"; - - private boolean canRemoveExterns = false; - private boolean anchorUnusedVars = false; - - public RemoveUnusedPrototypePropertiesTest() { - super(EXTERNS); - } - - @Override - protected CompilerPass getProcessor(Compiler compiler) { - return new RemoveUnusedPrototypeProperties(compiler, - canRemoveExterns, anchorUnusedVars); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015); - enableNormalize(); - anchorUnusedVars = false; - canRemoveExterns = false; - } - - public void testAnalyzePrototypeProperties() { - // Basic removal for prototype properties - test("function e(){}" + - "e.prototype.a = function(){};" + - "e.prototype.b = function(){};" + - "var x = new e; x.a()", - "function e(){}" + - "e.prototype.a = function(){};" + - "var x = new e; x.a()"); - - // Basic removal for prototype replacement - test("function e(){}" + - "e.prototype = {a: function(){}, b: function(){}};" + - "var x=new e; x.a()", - "function e(){}" + - "e.prototype = {a: function(){}};" + - "var x = new e; x.a()"); - - // Unused properties that were referenced in the externs file should not be - // removed - test("function e(){}" + - "e.prototype.a = function(){};" + - "e.prototype.bExtern = function(){};" + - "var x = new e;x.a()", - "function e(){}" + - "e.prototype.a = function(){};" + - "e.prototype.bExtern = function(){};" + - "var x = new e; x.a()"); - testSame("function e(){}" - + "e.prototype = {a: function(){}, bExtern: function(){}};" - + "var x = new e; x.a()"); - - testSame( - lines( - "class C {", - " constructor() {}", - " bExtern() {}", - "}")); - } - - public void testAliasing1() { - // Aliasing a property is not enough for it to count as used - test("function e(){}" + - "e.prototype.method1 = function(){};" + - "e.prototype.method2 = function(){};" + - // aliases - "e.prototype.alias1 = e.prototype.method1;" + - "e.prototype.alias2 = e.prototype.method2;" + - "var x = new e; x.method1()", - "function e(){}" + - "e.prototype.method1 = function(){};" + - "var x = new e; x.method1()"); - - // Using an alias should keep it - test("function e(){}" + - "e.prototype.method1 = function(){};" + - "e.prototype.method2 = function(){};" + - // aliases - "e.prototype.alias1 = e.prototype.method1;" + - "e.prototype.alias2 = e.prototype.method2;" + - "var x=new e; x.alias1()", - "function e(){}" + - "e.prototype.method1 = function(){};" + - "e.prototype.alias1 = e.prototype.method1;" + - "var x = new e; x.alias1()"); - } - - public void testAliasing2() { - // Aliasing a property is not enough for it to count as used - test("function e(){}" + - "e.prototype.method1 = function(){};" + - // aliases - "e.prototype.alias1 = e.prototype.method1;" + - "(new e).method1()", - "function e(){}" + - "e.prototype.method1 = function(){};" + - "(new e).method1()"); - - // Using an alias should keep it - testSame( - "function e(){}" - + "e.prototype.method1 = function(){};" - // aliases - + "e.prototype.alias1 = e.prototype.method1;" - + "(new e).alias1()"); - } - - public void testAliasing3() { - // Aliasing a property is not enough for it to count as used - test("function e(){}" + - "e.prototype.method1 = function(){};" + - "e.prototype.method2 = function(){};" + - // aliases - "e.prototype['alias1'] = e.prototype.method1;" + - "e.prototype['alias2'] = e.prototype.method2;", - "function e(){}" + - "e.prototype.method1=function(){};" + - "e.prototype.method2=function(){};" + - "e.prototype[\"alias1\"]=e.prototype.method1;" + - "e.prototype[\"alias2\"]=e.prototype.method2;"); - } - - public void testAliasing4() { - // Aliasing a property is not enough for it to count as used - test("function e(){}" + - "e.prototype['alias1'] = e.prototype.method1 = function(){};" + - "e.prototype['alias2'] = e.prototype.method2 = function(){};", - "function e(){}" + - "e.prototype[\"alias1\"]=e.prototype.method1=function(){};" + - "e.prototype[\"alias2\"]=e.prototype.method2=function(){};"); - } - - public void testAliasing5() { - // An exported alias must preserved any referenced values in the - // referenced function. - test("function e(){}" + - "e.prototype.method1 = function(){this.method2()};" + - "e.prototype.method2 = function(){};" + - // aliases - "e.prototype['alias1'] = e.prototype.method1;", - "function e(){}" + - "e.prototype.method1=function(){this.method2()};" + - "e.prototype.method2=function(){};" + - "e.prototype[\"alias1\"]=e.prototype.method1;"); - } - - public void testAliasing6() { - // An exported alias must preserved any referenced values in the - // referenced function. - test("function e(){}" + - "e.prototype.method1 = function(){this.method2()};" + - "e.prototype.method2 = function(){};" + - // aliases - "window['alias1'] = e.prototype.method1;", - "function e(){}" + - "e.prototype.method1=function(){this.method2()};" + - "e.prototype.method2=function(){};" + - "window['alias1']=e.prototype.method1;"); - } - - public void testAliasing7() { - // An exported alias must preserved any referenced values in the - // referenced function. - testSame("function e(){}" + - "e.prototype['alias1'] = e.prototype.method1 = " + - "function(){this.method2()};" + - "e.prototype.method2 = function(){};"); - } - - public void testStatementRestriction() { - testSame("function e(){}" + - "var x = e.prototype.method1 = function(){};" + - "var y = new e; x()"); - } - - public void testExportedMethodsByNamingConvention() { - String classAndItsMethodAliasedAsExtern = - "function Foo() {}" + - "Foo.prototype.method = function() {};" + // not removed - "Foo.prototype.unused = function() {};" + // removed - "var _externInstance = new Foo();" + - "Foo.prototype._externMethod = Foo.prototype.method"; // aliased here - - String compiled = - "function Foo(){}" + - "Foo.prototype.method = function(){};" + - "var _externInstance = new Foo;" + - "Foo.prototype._externMethod = Foo.prototype.method"; - - test(classAndItsMethodAliasedAsExtern, compiled); - } - - public void testMethodsFromExternsFileNotExported() { - canRemoveExterns = true; - String classAndItsMethodAliasedAsExtern = - "function Foo() {}" + - "Foo.prototype.bar_ = function() {};" + - "Foo.prototype.unused = function() {};" + - "var instance = new Foo;" + - "Foo.prototype.bar = Foo.prototype.bar_"; - - String compiled = - "function Foo(){}" + - "var instance = new Foo;"; - - test(classAndItsMethodAliasedAsExtern, compiled); - } - - public void testExportedMethodsByNamingConventionAlwaysExported() { - canRemoveExterns = true; - String classAndItsMethodAliasedAsExtern = - "function Foo() {}" + - "Foo.prototype.method = function() {};" + // not removed - "Foo.prototype.unused = function() {};" + // removed - "var _externInstance = new Foo();" + - "Foo.prototype._externMethod = Foo.prototype.method"; // aliased here - - String compiled = - "function Foo(){}" + - "Foo.prototype.method = function(){};" + - "var _externInstance = new Foo;" + - "Foo.prototype._externMethod = Foo.prototype.method"; - - test(classAndItsMethodAliasedAsExtern, compiled); - } - - public void testExternMethodsFromExternsFile() { - String classAndItsMethodAliasedAsExtern = - "function Foo() {}" + - "Foo.prototype.bar_ = function() {};" + // not removed - "Foo.prototype.unused = function() {};" + // removed - "var instance = new Foo;" + - "Foo.prototype.bar = Foo.prototype.bar_"; // aliased here - - String compiled = - "function Foo(){}" + - "Foo.prototype.bar_ = function(){};" + - "var instance = new Foo;" + - "Foo.prototype.bar = Foo.prototype.bar_"; - - test(classAndItsMethodAliasedAsExtern, compiled); - } - - public void testPropertyReferenceGraph() { - // test a prototype property graph that looks like so: - // b -> a, c -> b, c -> a, d -> c, e -> a, e -> f - String constructor = "function Foo() {}"; - String defA = - "Foo.prototype.a = function() { Foo.superClass_.a.call(this); };"; - String defB = "Foo.prototype.b = function() { this.a(); };"; - String defC = "Foo.prototype.c = function() { " + - "Foo.superClass_.c.call(this); this.b(); this.a(); };"; - String defD = "Foo.prototype.d = function() { this.c(); };"; - String defE = "Foo.prototype.e = function() { this.a(); this.f(); };"; - String defF = "Foo.prototype.f = function() { };"; - String fullClassDef = constructor + defA + defB + defC + defD + defE + defF; - - // ensure that all prototypes are compiled out if none are used - test(fullClassDef, ""); - - // make sure that the right prototypes are called for each use - String callA = "(new Foo()).a();"; - String callB = "(new Foo()).b();"; - String callC = "(new Foo()).c();"; - String callD = "(new Foo()).d();"; - String callE = "(new Foo()).e();"; - String callF = "(new Foo()).f();"; - test(fullClassDef + callA, constructor + defA + callA); - test(fullClassDef + callB, constructor + defA + defB + callB); - test(fullClassDef + callC, constructor + defA + defB + defC + callC); - test(fullClassDef + callD, constructor + defA + defB + defC + defD + callD); - test(fullClassDef + callE, constructor + defA + defE + defF + callE); - test(fullClassDef + callF, constructor + defF + callF); - - test(fullClassDef + callA + callC, - constructor + defA + defB + defC + callA + callC); - test(fullClassDef + callB + callC, - constructor + defA + defB + defC + callB + callC); - test(fullClassDef + callA + callB + callC, - constructor + defA + defB + defC + callA + callB + callC); - } - - public void testPropertiesDefinedWithGetElem() { - testSame("function Foo() {} Foo.prototype['elem'] = function() {};"); - testSame("function Foo() {} Foo.prototype[1 + 1] = function() {};"); - } - - public void testQuotedProperties() { - // Basic removal for prototype replacement - testSame("function e(){}" + - "e.prototype = {'a': function(){}, 'b': function(){}};"); - } - - public void testNeverRemoveImplicitlyUsedProperties() { - testSame("function Foo() {} " + - "Foo.prototype.length = 3; " + - "Foo.prototype.toString = function() { return 'Foo'; }; " + - "Foo.prototype.valueOf = function() { return 'Foo'; }; "); - } - - public void testPropertyDefinedInBranch() { - test("function Foo() {} if (true) Foo.prototype.baz = function() {};", - "if (true);"); - test("function Foo() {} while (true) Foo.prototype.baz = function() {};", - "while (true);"); - test("function Foo() {} for (;;) Foo.prototype.baz = function() {};", - "for (;;);"); - test("function Foo() {} do Foo.prototype.baz = function() {}; while(true);", - "do; while(true);"); - } - - public void testUsingAnonymousObjectsToDefeatRemoval() { - String constructor = "function Foo() {}"; - String declaration = constructor + "Foo.prototype.baz = 3;"; - test(declaration, ""); - testSame(declaration + "var x = {}; x.baz = 5;"); - testSame(declaration + "var x = {baz: 5};"); - test(declaration + "var x = {'baz': 5};", - "var x = {'baz': 5};"); - } - - public void testGlobalFunctionsInGraph() { - test( - "var x = function() { (new Foo).baz(); };" + - "var y = function() { x(); };" + - "function Foo() {}" + - "Foo.prototype.baz = function() { y(); };", - ""); - } - - public void testGlobalFunctionsInGraph2() { - // In this example, Foo.prototype.baz is a global reference to - // Foo, and Foo has a reference to baz. So everything stays in. - // TODO(nicksantos): We should be able to make the graph more fine-grained - // here. Instead of Foo.prototype.bar creating a global reference to Foo, - // it should create a reference from .bar to Foo. That will let us - // compile this away to nothing. - testSame( - "var x = function() { (new Foo).baz(); };" + - "var y = function() { x(); };" + - "function Foo() { this.baz(); }" + - "Foo.prototype.baz = function() { y(); };"); - } - - public void testGlobalFunctionsInGraph3() { - test( - "var x = function() { (new Foo).baz(); };" + - "var y = function() { x(); };" + - "function Foo() { this.baz(); }" + - "Foo.prototype.baz = function() { x(); };", - "var x = function() { (new Foo).baz(); };" + - "function Foo() { this.baz(); }" + - "Foo.prototype.baz = function() { x(); };"); - } - - public void testGlobalFunctionsInGraph4() { - test( - "var x = function() { (new Foo).baz(); };" + - "var y = function() { x(); };" + - "function Foo() { Foo.prototype.baz = function() { y(); }; }", - ""); - } - - public void testGlobalFunctionsInGraph5() { - test( - "function Foo() {}" + - "Foo.prototype.methodA = function() {};" + - "function x() { (new Foo).methodA(); }" + - "Foo.prototype.methodB = function() { x(); };", - ""); - - anchorUnusedVars = true; - test( - "function Foo() {}" + - "Foo.prototype.methodA = function() {};" + - "function x() { (new Foo).methodA(); }" + - "Foo.prototype.methodB = function() { x(); };", - - "function Foo() {}" + - "Foo.prototype.methodA = function() {};" + - "function x() { (new Foo).methodA(); }"); - } - - public void testGlobalFunctionsInGraph6() { - testSame( - "function Foo() {}" + - "Foo.prototype.methodA = function() {};" + - "function x() { (new Foo).methodA(); }" + - "Foo.prototype.methodB = function() { x(); };" + - "(new Foo).methodB();"); - } - - public void testGlobalFunctionsInGraph7() { - testSame( - "function Foo() {}" + - "Foo.prototype.methodA = function() {};" + - "this.methodA();"); - } - - public void testGlobalFunctionsInGraph8() { - test( - lines( - "let x = function() { (new Foo).baz(); };", - "const y = function() { x(); };", - "function Foo() { Foo.prototype.baz = function() { y(); }; }"), - ""); - } - - public void testGetterBaseline() { - anchorUnusedVars = true; - test( - "function Foo() {}" + - "Foo.prototype = { " + - " methodA: function() {}," + - " methodB: function() { x(); }" + - "};" + - "function x() { (new Foo).methodA(); }", - - "function Foo() {}" + - "Foo.prototype = { " + - " methodA: function() {}" + - "};" + - "function x() { (new Foo).methodA(); }"); - } - - public void testGetter1() { - test( - "function Foo() {}" + - "Foo.prototype = { " + - " get methodA() {}," + - " get methodB() { x(); }" + - "};" + - "function x() { (new Foo).methodA; }", - - "function Foo() {}" + - "Foo.prototype = {};"); - - anchorUnusedVars = true; - test( - "function Foo() {}" + - "Foo.prototype = { " + - " get methodA() {}," + - " get methodB() { x(); }" + - "};" + - "function x() { (new Foo).methodA; }", - - "function Foo() {}" + - "Foo.prototype = { " + - " get methodA() {}" + - "};" + - "function x() { (new Foo).methodA; }"); - } - - public void testGetter2() { - anchorUnusedVars = true; - test( - "function Foo() {}" + - "Foo.prototype = { " + - " get methodA() {}," + - " set methodA(a) {}," + - " get methodB() { x(); }," + - " set methodB(a) { x(); }" + - "};" + - "function x() { (new Foo).methodA; }", - - "function Foo() {}" + - "Foo.prototype = { " + - " get methodA() {}," + - " set methodA(a) {}" + - "};" + - "function x() { (new Foo).methodA; }"); - } - - public void testHook1() throws Exception { - test( - "/** @constructor */ function Foo() {}" + - "Foo.prototype.method1 = Math.random() ?" + - " function() { this.method2(); } : function() { this.method3(); };" + - "Foo.prototype.method2 = function() {};" + - "Foo.prototype.method3 = function() {};", - ""); - } - - public void testHook2() throws Exception { - testSame( - "/** @constructor */ function Foo() {}" + - "Foo.prototype.method1 = Math.random() ?" + - " function() { this.method2(); } : function() { this.method3(); };" + - "Foo.prototype.method2 = function() {};" + - "Foo.prototype.method3 = function() {};" + - "(new Foo()).method1();"); - } - - public void testRemoveInBlock() { - test(lines( - "if (true) {", - " if (true) {", - " var foo = function() {};", - " }", - "}"), - lines( - "if (true) {", - " if (true) {", - " }", - "}")); - - testSame("if (true) { let foo = function() {} }"); - } - - public void testDestructuringProperty() { - testSame( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "var {a:x} = new Foo();")); - - test( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "Foo.prototype.b = function() {}", - "var {a} = new Foo();"), - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "var {a} = new Foo();")); - - test( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "Foo.prototype.b = function() {}", - "var {a:x} = new Foo();"), - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "var {a:x} = new Foo();")); - - testSame( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "Foo.prototype.b = function() {}", - "var {a, b} = new Foo();")); - - testSame( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "Foo.prototype.b = function() {}", - "var {a:x, b:y} = new Foo();")); - - testSame( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "({a:x} = new Foo());")); - - testSame( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "function f({a:x}) {}; f(new Foo());")); - - testSame( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "var {a : x = 3} = new Foo();")); - - test( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "Foo.prototype.b = function() {}", - "var {a : a = 3} = new Foo();"), - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "var {a : a = 3} = new Foo();")); - - testSame( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "let { a : [b, c, d] } = new Foo();")); - - testSame( - lines( - "function Foo() {}", - "Foo.prototype.a = function() {};", - "const { a : { b : { c : d = '' }}} = new Foo();")); - - } - - public void testEs6Class() { - testSame( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - "}")); - - test( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - " foo() {}", - "}", - "var c = new C " - ), - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - "}", - "var c = new C")); - - testSame( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - " foo() {}", - "}", - "var c = new C ", - "c.foo()" - )); - } - - public void testEs6StaticMethodNotRemoved() { - testSame( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - " static foo() {}", - "}" - )); - } - - public void testEs6ClassGetterSetter() { - test( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - " get foo() {}", - " set foo(val) {}", - "}", - "var c = new C " - ), - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - "}", - "var c = new C")); - - testSame( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - " get foo() {}", - " set foo(val) {}", - "}", - "var c = new C ", - "c.foo = 3;")); - - testSame( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - " get foo() {}", - " set foo(val) {}", - "}", - "var c = new C ", - "c.foo;")); - } - - public void testEs6Extends() { - testSame( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - "}", - "class D extends C {", - " constructor() {}", - "}")); - - testSame( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - " foo() {}", - "}", - "class D extends C {", - " constructor() {}", - " foo() {", - " return super.foo()", - " }", - "}", - "var d = new D", - "d.foo()")); - - test( - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - " foo() {}", - "}", - "class D extends C {", - " constructor() {}", - " foo() {", - " return super.foo()", - " }", - "}", - "var d = new D"), - lines( - "class C {", - " constructor() {", - " this.x = 1;", - " }", - "}", - "class D extends C {", - " constructor() {}", - "}", - "var d = new D")); - } - - public void testAnonClasses() { - test( - lines( - "var C = class {", - " constructor() {", - " this.x = 1;", - " }", - " foo() {}", - "}"), - lines( - "var C = class {", - " constructor() {", - " this.x = 1;", - " }", - "}")); - - testSame( - lines( - "var C = class {", - " constructor() {", - " this.x = 1;", - " }", - " foo() {}", - "}", - "var c = new C()", - "c.foo()")); - - test( - lines( - "var C = class {}", - "C.D = class {", - " constructor() {", - " this.x = 1;", - " }", - " foo() {}", - "}"), - lines( - "var C = class {}", - "C.D = class{", - " constructor() {", - " this.x = 1;", - " }", - "}")); - - testSame( - lines( - "foo(class {", - " constructor() { }", - " bar() { }", - "})")); - } - - public void testModules() { - testSame("export default function(){}"); - testSame("export class C {};"); - testSame("export {Bar}"); - - testSame("import { square, diag } from 'lib';"); - testSame("import * as lib from 'lib';"); - } -}