All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.RemoveUnusedClassProperties Maven / Gradle / Ivy

/*
 * 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;

import static com.google.common.base.Preconditions.checkState;

import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.TypeI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * This pass looks for properties that are never read and removes them.
 * These can be properties created using "this", or static properties of
 * 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.
 *
 * TODO(tomnguyen) Handle destructuring of objects/classes as cases where the field is used.
 *
 * @author [email protected] (John Lenz)
 */
class RemoveUnusedClassProperties
    implements CompilerPass, NodeTraversal.Callback {
  private final AbstractCompiler compiler;
  private final Set used = new HashSet<>();
  private final List candidates = new ArrayList<>();

  private final boolean removeUnusedConstructorProperties;

  RemoveUnusedClassProperties(
      AbstractCompiler compiler, boolean removeUnusedConstructorProperties) {
    this.compiler = compiler;
    used.addAll(compiler.getExternProperties());
    this.removeUnusedConstructorProperties = removeUnusedConstructorProperties;
  }

  @Override
  public void process(Node externs, Node root) {
    NodeTraversal.traverseEs6(compiler, root, this);
    removeUnused();
  }

  private void removeUnused() {
    for (Node n : candidates) {
      if (NodeUtil.isObjectLitKey(n)) {
        String propName = NodeUtil.getObjectLitKeyName(n);
        if (!used.contains(propName)) {
          // If the property definition has side-effect, finding a place for it
          // can be tricky so just leave it in place.
          if (!n.isStringKey()
              || !NodeUtil.mayHaveSideEffects(n.getFirstChild(), compiler)) {
            Node parent = n.getParent();
            parent.removeChild(n);
            NodeUtil.markFunctionsDeleted(n, compiler);
            compiler.reportChangeToEnclosingScope(parent);
          }
        }
      } else {
        checkState(n.isGetProp(), n);
        String propName = n.getLastChild().getString();
        if (!used.contains(propName)) {

          Node parent = n.getParent();
          Node replacement;
          /**
           * Whether the parent of the GETPROP is replaced or GETPROP itself.
           * In some cases the parent of GETPROP is an EXPRRESULT, while the replacement is always
           * just a plain expression.
           */
          boolean replaceParent = true;
          if (NodeUtil.isLhsByDestructuring(n)) {
            if (parent.isStringKey()) {
              // Remove the entire object-key entry
              compiler.reportChangeToEnclosingScope(parent);
              parent.detach();
              continue;
            }
            replacement = IR.empty();
            replaceParent = parent.isRest();
          } else if (NodeUtil.isAssignmentOp(parent)) {
            Node assign = parent;
            checkState(
                assign != null && NodeUtil.isAssignmentOp(assign) && assign.getFirstChild() == n);
            compiler.reportChangeToEnclosingScope(assign);
            // 'this.x = y' to 'y'
            replacement = assign.getLastChild().detach();
          } else if (parent.isInc() || parent.isDec()) {
            compiler.reportChangeToEnclosingScope(parent);
            replacement = IR.number(0).srcref(parent);
          } else if (parent.isExprResult()) {
            replacement = IR.number(0).srcref(n);
            replaceParent = false;
          } else {
            throw new IllegalStateException("unexpected: " + parent);
          }

          // If the property expression is complex preserve that part of the
          // expression.
          if (!n.isQualifiedName()) {
            Node preserved = n.getFirstChild();
            while (preserved.isGetProp()) {
              preserved = preserved.getFirstChild();
            }
            replacement = IR.comma(
                preserved.detach(),
                replacement)
                .srcref(parent);
          }

          compiler.reportChangeToEnclosingScope(parent);
          if (replaceParent) {
            parent.replaceWith(replacement);
          } else {
            parent.replaceChild(n, replacement);
          }
        }
      }
    }
  }

  @Override
  public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
    return true;
  }

  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    switch (n.getToken()) {
       case GETPROP: {
         String propName = n.getLastChild().getString();
         if (compiler.getCodingConvention().isExported(propName)
             || isPinningPropertyUse(n)
             || !isRemovablePropertyDefinition(n)) {
           used.add(propName);
         } else {
           // This is a definition of a property but it is only removable
           // if it is defined on "this".
           candidates.add(n);
         }
         break;
      }
      case OBJECTLIT:
      case OBJECT_PATTERN: {
        // Assume any object literal definition might be a reflection on the
        // class property.
        if (!NodeUtil.isObjectDefinePropertiesDefinition(n.getParent())) {
          addObjectNodePropertiesToUsed(n);
        }
        break;
      }
      case CLASS: {
        Node classMemberDefs = n.getLastChild();
        for (Node m : classMemberDefs.children()) {
          // Computed props are treated as unremovable for now.
          if (!m.isComputedProp()) {
            candidates.add(m);
          }
        }
        break;
      }
      case CALL: {
        // Look for properties referenced through the property rename functions.
        Node target = n.getFirstChild();
        if (n.hasMoreThanOneChild()
            && compiler
                .getCodingConvention()
                .isPropertyRenameFunction(target.getOriginalQualifiedName())) {
           Node propName = target.getNext();
           if (propName.isString()) {
             used.add(propName.getString());
           }
         } else if (NodeUtil.isObjectDefinePropertiesDefinition(n)) {
           if (n.getChildCount() == 3 && n.getLastChild().isObjectLit()) {
             Node objlit = n.getLastChild();
             for (Node c : objlit.children()) {
               if (!c.isQuotedString()) {
                 candidates.add(c);
               } else {
                 used.add(c.getString());
               }
             }
           }
         }
         break;
      }
      default:
        break;
    }
  }

  private boolean isRemovablePropertyDefinition(Node n) {
    checkState(n.isGetProp(), n);
    Node target = n.getFirstChild();
    return target.isThis()
        || (this.removeUnusedConstructorProperties && isConstructor(target))
        || (target.isGetProp()
            && target.getLastChild().getString().equals("prototype"));
  }

  private boolean isConstructor(Node n) {
    TypeI type = n.getTypeI();
    return type != null && (type.isConstructor() || type.isInterface());
  }

  /**
   * @return Whether the property is used in a way that prevents its removal.
   */
  private static boolean isPinningPropertyUse(Node n) {
    // Rather than looking for cases that are uses, we assume all references are
    // pinning uses unless they are:
    //  - a simple assignment (x.a = 1)
    //  - an expression statement (x.a;)
    //  - a compound assignment or increment (x++, x += 1) whose result is
    //    otherwise unused
    //  - a lhs of destructuring assignment ([x.a] = [1])

    Node parent = n.getParent();

    if (NodeUtil.isLhsByDestructuring(n)) {
      return false;
    } else if (n == parent.getFirstChild()) {
      if (parent.isAssign() || parent.isExprResult()) {
        // A simple assignment or expression statement doesn't pin the property.
        return false;
      } else if (NodeUtil.isAssignmentOp(parent)
            || parent.isInc() || parent.isDec()) {
        // In general, compound assignments are both reads and writes, but
        // if the property is never otherwise read we can consider it simply
        // a write.
        // However if the assign expression is used as part of a larger
        // expression, we must consider it a read. For example:
        //    x = (y.a += 1);
        return NodeUtil.isExpressionResultUsed(parent);
      }
    }
    return true;
  }

  /** Adds the property names of the Object Lit or Object Pattern node n to the used set */
  private void addObjectNodePropertiesToUsed(Node n) {
    for (Node c : n.children()) {
      if (!c.isComputedProp()) {
        // Objects can contain computed_prop fields
        // and we are ignoring computed props.
        used.add(NodeUtil.getObjectLitKeyName(c));
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy