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

com.google.javascript.jscomp.GatherSideEffectSubexpressionsCallback 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 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.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.CodingConvention.SubclassRelationship;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.ArrayList;
import java.util.List;

/**
 * Callback that gathers subexpressions that may have side effects
 * and appends copies of those subexpressions to the replacements
 * list.  In the case of branching subexpressions, it simplifies the
 * subexpression before adding it to the replacement list.
 *
 */
class GatherSideEffectSubexpressionsCallback implements Callback {

  /**
   * Used by GatherSideEffectSubexpressionsCallback to notify client
   * code about side effect expressions that should be kept.
   */
  interface SideEffectAccumulator {

    /**
     * Returns true if the "mixin" and "inherits" function calls
     * should be treated as if they had side effects.
     */
    boolean classDefiningCallsHaveSideEffects();

    /**
     * Adds subtree to the list of nodes that have side effects.
     *
     * @param original - root of the tree.
     */
    void keepSubTree(Node original);

    /**
     * Simplifies a subtree whose root node is an AND or OR expression
     * and adds the resulting subtree to the list of nodes that have
     * side effects.
     *
     * @param original - root of the and/or expression.
     */
    void keepSimplifiedShortCircuitExpression(Node original);

    /**
     * Simplifies a subtree whose root node is a HOOK expression
     * and adds the resulting subtree to the list of nodes that have
     * side effects.
     *
     * @param hook - root of the hook expression.
     * @param thenHasSideEffects - then branch has side effects
     * @param elseHasSideEffects - else branch has side effects
     */
    void keepSimplifiedHookExpression(Node hook,
                                      boolean thenHasSideEffects,
                                      boolean elseHasSideEffects);
  }

  /**
   * Populates the provided replacement list by appending copies of
   * subtrees that have side effects.
   *
   * It is OK if this class tears up the original tree, because
   * we're going to throw the tree out anyway.
   */
  static final class GetReplacementSideEffectSubexpressions
      implements SideEffectAccumulator {
    private final AbstractCompiler compiler;
    private final List replacements;

    /**
     * Creates the accumulator.
     *
     * @param compiler - the AbstractCompiler
     * @param replacements - list to accumulate into
     */
    GetReplacementSideEffectSubexpressions(AbstractCompiler compiler,
        List replacements) {
      this.compiler = compiler;
      this.replacements = replacements;
    }

    @Override
    public boolean classDefiningCallsHaveSideEffects() {
      return true;
    }

    @Override
    public void keepSubTree(Node original) {
      if (original.getParent() != null) {
        original.detach();
      }
      replacements.add(original);
    }

    @Override
    public void keepSimplifiedShortCircuitExpression(Node original) {
      Preconditions.checkArgument(
          (original.isAnd()) || (original.isOr()),
          "Expected: AND or OR, Got: %s",
          original.getToken());
      Node left = original.getFirstChild();
      Node right = left.getNext();
      Node simplifiedRight = simplifyShortCircuitBranch(right);
      original.detachChildren();
      original.addChildToBack(left);
      original.addChildToBack(simplifiedRight);
      keepSubTree(original);
    }

    @Override
    public void keepSimplifiedHookExpression(Node hook,
                                             boolean thenHasSideEffects,
                                             boolean elseHasSideEffects) {
      Preconditions.checkArgument(hook.isHook(), "Expected: HOOK, Got: %s", hook.getToken());
      Node condition = hook.getFirstChild();
      Node thenBranch = condition.getNext();
      Node elseBranch = thenBranch.getNext();
      if (thenHasSideEffects && elseHasSideEffects) {
        hook.detachChildren();
        hook.addChildToBack(condition);
        hook.addChildToBack(simplifyShortCircuitBranch(thenBranch));
        hook.addChildToBack(simplifyShortCircuitBranch(elseBranch));
        keepSubTree(hook);
      } else if (thenHasSideEffects || elseHasSideEffects) {
        Token type = thenHasSideEffects ? Token.AND : Token.OR;
        Node body = thenHasSideEffects ? thenBranch : elseBranch;
        Node simplified = new Node(
            type, condition.detach(),
            simplifyShortCircuitBranch(body))
            .useSourceInfoIfMissingFrom(hook);
        keepSubTree(simplified);
      } else {
        throw new IllegalArgumentException(
            "keepSimplifiedHookExpression must keep at least 1 branch");
      }
    }

    private Node simplifyShortCircuitBranch(Node node) {
      List parts = new ArrayList<>();
      NodeTraversal.traverseEs6(
          compiler, node,
          new GatherSideEffectSubexpressionsCallback(
              compiler,
              new GetReplacementSideEffectSubexpressions(compiler, parts)));

      Node ret = null;
      for (Node part : parts) {
        if (ret != null) {
          ret = IR.comma(ret, part).srcref(node);
        } else {
          ret = part;
        }
      }

      if (ret == null) {
        throw new IllegalArgumentException(
            "expected at least one side effect subexpression in short "
            + "circuit branch.");
      }

      return ret;
    }
  }

  private static final ImmutableSet FORBIDDEN_TYPES =
      ImmutableSet.of(Token.BLOCK, Token.SCRIPT, Token.VAR, Token.EXPR_RESULT, Token.RETURN);
  private final AbstractCompiler compiler;
  private final SideEffectAccumulator accumulator;

  /**
   * @param compiler - AbstractCompiler object
   * @param accumulator - object that will accumulate roots of
   *                      subtrees that have side effects.
   */
  GatherSideEffectSubexpressionsCallback(AbstractCompiler compiler,
                                         SideEffectAccumulator accumulator) {
    this.compiler = compiler;
    this.accumulator = accumulator;
  }

  /**
   * Determines if a call defines a class inheritance or mixing
   * relation, according to the current coding convention.
   */
  private boolean isClassDefiningCall(Node callNode) {
    SubclassRelationship classes =
        compiler.getCodingConvention().getClassesDefinedByCall(callNode);
    return classes != null;
  }

  /**
   * Computes the list of subtrees whose root nodes have side effects.
   *
   * 

If the current subtree's root has side effects this method should * call accumulator.keepSubTree and return 'false' to add the * subtree to the result list and avoid avoid traversing the nodes children. * *

Branching nodes whose then or else branch contain side effects * must be simplified by doing a recursive traversal; this method * should call the appropriate accumulator 'keepSimplified' method * and return 'false' to stop the regular traversal. */ @Override public boolean shouldTraverse( NodeTraversal traversal, Node node, Node parent) { if (FORBIDDEN_TYPES.contains(node.getToken()) || NodeUtil.isControlStructure(node)) { throw new IllegalArgumentException(node.getToken() + " nodes are not supported."); } // Do not recurse into nested functions. if (node.isFunction()) { return false; } // simplify and maybe keep hook expression. if (node.isHook()) { return processHook(node); } // simplify and maybe keep AND/OR expression. if ((node.isAnd()) || (node.isOr())) { return processShortCircuitExpression(node); } if (!NodeUtil.nodeTypeMayHaveSideEffects(node, compiler)) { return true; } else { // Node type suggests that the expression has side effects. if (node.isCall()) { return processFunctionCall(node); } else if (node.isNew()) { return processConstructorCall(node); } else { accumulator.keepSubTree(node); return false; } } } /** * Processes an AND or OR expression. * * @return true to continue traversal, false otherwise */ boolean processShortCircuitExpression(Node node) { Preconditions.checkArgument( (node.isAnd()) || (node.isOr()), "Expected: AND or OR, Got: %s", node.getToken()); // keep whole expression if RHS of the branching expression // contains a call. Node left = node.getFirstChild(); Node right = left.getNext(); if (NodeUtil.mayHaveSideEffects(right, compiler)) { accumulator.keepSimplifiedShortCircuitExpression(node); return false; } else { return true; } } /** * Processes a HOOK expression. * * @return true to continue traversal, false otherwise */ boolean processHook(Node node) { Preconditions.checkArgument(node.isHook(), "Expected: HOOK, Got: %s", node.getToken()); Node condition = node.getFirstChild(); Node ifBranch = condition.getNext(); Node elseBranch = ifBranch.getNext(); boolean thenHasSideEffects = NodeUtil.mayHaveSideEffects( ifBranch, compiler); boolean elseHasSideEffects = NodeUtil.mayHaveSideEffects( elseBranch, compiler); if (thenHasSideEffects || elseHasSideEffects) { accumulator.keepSimplifiedHookExpression( node, thenHasSideEffects, elseHasSideEffects); return false; } else { return true; } } /** * Processes a CALL expression. * * @return true to continue traversal, false otherwise */ boolean processFunctionCall(Node node) { Preconditions.checkArgument(node.isCall(), "Expected: CALL, Got: %s", node.getToken()); // Calls to functions that are known to be "pure" have no side // effects. Node functionName = node.getFirstChild(); if (functionName.isName() || functionName.isGetProp()) { if (!accumulator.classDefiningCallsHaveSideEffects() && isClassDefiningCall(node)) { return true; } } if (!NodeUtil.functionCallHasSideEffects(node)) { return true; } accumulator.keepSubTree(node); return false; } /** * Processes a NEW expression. * * @return true to continue traversal, false otherwise */ boolean processConstructorCall(Node node) { Preconditions.checkArgument(node.isNew(), "Expected: NEW, Got: %s", node.getToken()); // Calls to constructors that are known to be "pure" have no // side effects. if (!NodeUtil.constructorCallHasSideEffects(node)) { return true; } accumulator.keepSubTree(node); return false; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy