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

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

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
Show newest version
/*
 * Copyright 2016 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.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import java.util.HashMap;
import java.util.Map;

/**
 * A pass for deleting methods that only make a super call with no change in arguments. This pass is
 * useful for cleaning up methods that may become super-only calls after other optimizations have
 * run, such as removal of casts or assertions. This pass must run after those optimizations, {@link
 * ProcessClosurePrimitives}, {@link Es6ConvertSuper}, and type checking.
 */
public final class RemoveSuperMethodsPass implements CompilerPass {

  /** Marker for recognizing super calls converted by {@link ProcessClosurePrimitives}. */
  private static final String SUPERCLASS_MARKER = "\\.superClass_\\.";

  private static final String PROTOTYPE_MARKER = "\\.prototype\\.";

  private final AbstractCompiler compiler;

  private final Map removeCandidates;

  public RemoveSuperMethodsPass(AbstractCompiler compiler) {
    this.compiler = compiler;
    this.removeCandidates = new HashMap<>();
  }

  @Override
  public void process(Node externs, Node root) {
    NodeTraversal.traverseEs6(compiler, root, new RemoveSuperMethodsCallback());
    NodeTraversal.traverseEs6(compiler, root, new FilterDuplicateMethods());
    for (Map.Entry entry : removeCandidates.entrySet()) {
      Node removalTarget = entry.getValue().getGrandparent();
      Node removalParent = removalTarget.getParent();
      removalTarget.detach();
      compiler.reportChangeToEnclosingScope(removalParent);
    }
  }

  private class RemoveSuperMethodsCallback extends AbstractPostOrderCallback {
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      // must be function assignment where the function body has only one statement
      if (n.isFunction() && parent.isAssign() && parent.getParent().isExprResult()) {
        // TODO(moz): Removing methods annotated with @wizaction have caused internal breakage.
        // Figure out what {@link WizPass} might have done wrong and maybe remove this bailout.
        if (parent.getJSDocInfo() != null && parent.getJSDocInfo().isWizaction()) {
          return;
        }
        Node block = n.getLastChild();
        if (!block.hasOneChild()) {
          return;
        }
        Node statement = block.getFirstChild();
        if ((statement.isExprResult() || statement.isReturn())
            && statement.hasOneChild()
            && statement.getFirstChild().isCall()) {
          String methodName = NodeUtil.getName(n);
          if (methodName == null) {
            return;
          }
          Node call = statement.getFirstChild();
          if (argumentsMatch(n, call)
              && returnMatches(call)
              && functionNameMatches(methodName, call)) {
            removeCandidates.put(methodName, n);
          }
        }
      }
    }

    /**
     * Returns true if the call has the same name as the enclosing function and the call occurs on
     * the superclass.
     *
     * @param enclosingMethodName qualified name of the method to examine.
     * @param call the CALL node inside the function.
     */
    private boolean functionNameMatches(String enclosingMethodName, Node call) {
      String callName = call.getFirstChild().getQualifiedName();
      if (callName == null) {
        return false;
      }

      String[] methodNameSplitted = enclosingMethodName.split(PROTOTYPE_MARKER);
      if (methodNameSplitted.length != 2) {
        return false;
      }
      // The enclosing method name will have the form ns.Foo.prototype.bar
      // The CALL should either be ns.Foo.superClass_.bar.call or ns.FooBase.prototype.bar.call
      String enclosingClassName = methodNameSplitted[0];
      String shortMethodNameConcatedWithCall = methodNameSplitted[1].concat(".call");

      String[] callNameSplittedBySuperClassMarker = callName.split(SUPERCLASS_MARKER);
      if (callNameSplittedBySuperClassMarker.length == 2
          && callNameSplittedBySuperClassMarker[0].equals(enclosingClassName)
          && callNameSplittedBySuperClassMarker[1].equals(shortMethodNameConcatedWithCall)) {
        // matched superClass_ marker generated by ProcessClosurePrimitives. No further
        // name checks needed.
        return true;
      }

      String[] callNameSplittedByPrototypeMarker = callName.split(PROTOTYPE_MARKER);
      if (callNameSplittedByPrototypeMarker.length != 2
          || !callNameSplittedByPrototypeMarker[1].equals(shortMethodNameConcatedWithCall)) {
        return false;
      }

      // Check that call references the superclass
      String calledClass = callNameSplittedByPrototypeMarker[0];
      JSType subclassType = compiler.getTypeIRegistry().getType(enclosingClassName);
      JSType calledClassType = compiler.getTypeIRegistry().getType(calledClass);
      if (subclassType == null || calledClassType == null) {
        return false;
      }
      if (subclassType.toObjectType() == null
          || subclassType.toObjectType().getConstructor() == null) {
        return false;
      }
      FunctionType superClassConstructor =
          subclassType.toObjectType().getConstructor().getSuperClassConstructor();
      // TODO(moz): Investigate why this could be null
      if (superClassConstructor == null) {
        return false;
      }
      return superClassConstructor.getInstanceType().equals(calledClassType);
    }

    private boolean returnMatches(Node call) {
      // no match if function being called does not have function type
      JSType childType = call.getFirstChild().getJSType();
      if (childType == null || !childType.isFunctionType()) {
        return false;
      }
      // no match if function being called has a return value, but result of the call is not part
      // of return statement
      JSType returnType = childType.toMaybeFunctionType().getReturnType();
      if (returnType != null && !returnType.isVoidType() && !returnType.isUnknownType()) {
        return call.getParent().isReturn();
      }
      return true;
    }

    /** Returns true if the arguments of the call are the parameters of the enclosing method */
    private boolean argumentsMatch(Node enclosingMethod, Node call) {
      Node paramList = enclosingMethod.getSecondChild();

      // The CALL should have 2 more children than the param list. The first is the
      // function being called, the second is a THIS argument.
      int numExtraCallChildren = 2;
      if (paramList.getChildCount() + numExtraCallChildren != call.getChildCount()) {
        return false;
      }

      if (!call.getSecondChild().isThis()) {
        return false;
      }

      Node callArg = call.getChildAtIndex(numExtraCallChildren);
      Node param = paramList.getFirstChild();
      for (; param != null; param = param.getNext(), callArg = callArg.getNext()) {
        if (!callArg.isName() || !param.matchesQualifiedName(callArg)) {
          return false;
        }
      }
      return true;
    }
  }

  /**
   * Some projects intentionally keep duplicate methods, so bail out on those.
   */
  private class FilterDuplicateMethods extends AbstractPostOrderCallback {
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (n.isFunction() && parent.isAssign() && parent.getParent().isExprResult()) {
        String methodName = NodeUtil.getName(n);
        if (removeCandidates.containsKey(methodName) && removeCandidates.get(methodName) != n) {
          removeCandidates.remove(methodName);
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy