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

com.google.javascript.jscomp.DefinitionsRemover 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.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;

/**
 * Models an assignment that defines a variable and the removal of it.
 *
 */
class DefinitionsRemover {

  /**
   * This logic must match {@link isDefinitionNode}.
   *
   * @return an {@link Definition} object if the node contains a definition or {@code null}
   *     otherwise.
   */
  static Definition getDefinition(Node n, boolean isExtern) {
    Node parent = n.getParent();
    if (parent == null) {
      return null;
    }

    if (NodeUtil.isVarDeclaration(n) && (isExtern || n.hasChildren())) {
      return new VarDefinition(n, isExtern);
    } else if (parent.isFunction() && parent.getFirstChild() == n) {
      if (!NodeUtil.isFunctionExpression(parent)) {
        return new NamedFunctionDefinition(parent, isExtern);
      } else if (!n.getString().isEmpty()) {
        return new FunctionExpressionDefinition(parent, isExtern);
      }
    } else if (parent.isAssign() && parent.getFirstChild() == n) {
      return new AssignmentDefinition(parent, isExtern);
    } else if (NodeUtil.isObjectLitKey(n)) {
      return new ObjectLiteralPropertyDefinition(parent, n, n.getFirstChild(), isExtern);
    } else if (parent.isParamList()) {
      Node function = parent.getParent();
      return new FunctionArgumentDefinition(function, n, isExtern);
    } else if (parent.getToken() == Token.COLON && parent.getFirstChild() == n && isExtern) {
      Node grandparent = parent.getParent();
      Preconditions.checkState(grandparent.getToken() == Token.LB);
      Preconditions.checkState(grandparent.getParent().getToken() == Token.LC);
      return new RecordTypePropertyDefinition(n);
    } else if (isExtern && n.isGetProp() && parent.isExprResult() && n.isQualifiedName()) {
      return new ExternalNameOnlyDefinition(n);
    }
    return null;
  }

  /**
   * This logic must match {@link getDefinition}.
   *
   * @return Whether a definition object can be created.
   */
  static boolean isDefinitionNode(Node n) {
    Node parent = n.getParent();
    if (parent == null) {
      return false;
    }

    if (NodeUtil.isVarDeclaration(n) && (n.isFromExterns() || n.hasChildren())) {
      return true;
    } else if (parent.isFunction() && parent.getFirstChild() == n) {
      if (!NodeUtil.isFunctionExpression(parent)) {
        return true;
      } else if (!n.getString().isEmpty()) {
        return true;
      }
    } else if (parent.isAssign() && parent.getFirstChild() == n) {
      return true;
    } else if (NodeUtil.isObjectLitKey(n)) {
      return true;
    } else if (parent.isParamList()) {
      return true;
    } else if (parent.getToken() == Token.COLON
        && parent.getFirstChild() == n
        && n.isFromExterns()) {
      Node grandparent = parent.getParent();
      Preconditions.checkState(grandparent.getToken() == Token.LB);
      Preconditions.checkState(grandparent.getParent().getToken() == Token.LC);
      return true;
    } else if (n.isFromExterns() && parent.isExprResult() && n.isGetProp() && n.isQualifiedName()) {
      return true;
    }
    return false;
  }


  abstract static class Definition {

    private final boolean isExtern;

    Definition(boolean isExtern) {
      this.isExtern = isExtern;
    }

    /**
     * Removes this definition from the AST if it is not an extern.
     *
     * This method should not be called on a definition for which isExtern()
     * is true.
     */
    public void remove(AbstractCompiler compiler) {
      if (!isExtern) {
        performRemove(compiler);
      } else {
        throw new IllegalStateException("Attempt to remove() an extern definition.");
      }
    }

    /**
     * Subclasses should override to remove the definition from the AST.
     */
    protected abstract void performRemove(AbstractCompiler compiler);

    /**
     * Variable or property name represented by this definition.
     * For example, in the case of assignments this method would
     * return the NAME, GETPROP or GETELEM expression that acts as the
     * assignment left hand side.
     *
     * @return the L-Value associated with this definition.
     *         The node's type is always NAME, GETPROP or GETELEM.
     */
    public abstract Node getLValue();

    /**
     * Value expression that acts as the right hand side of the
     * definition statement.
     */
    public abstract Node getRValue();

    /**
     * Returns true if the definition is an extern.
     */
    public boolean isExtern() {
      return isExtern;
    }

    @Override
    public String toString() {
      return getLValue().getQualifiedName() + " = " + getRValue();
    }
  }

  /**
   * Represents an name-only external definition.  The definition's
   * RHS is missing.
   */
  abstract static class IncompleteDefinition extends Definition {
    private static final ImmutableSet ALLOWED_TYPES =
        ImmutableSet.of(Token.NAME, Token.GETPROP, Token.GETELEM);
    private final Node lValue;

    IncompleteDefinition(Node lValue, boolean inExterns) {
      super(inExterns);
      Preconditions.checkNotNull(lValue);
      Preconditions.checkArgument(
          ALLOWED_TYPES.contains(lValue.getToken()),
          "Unexpected lValue type %s",
          lValue.getToken());
      this.lValue = lValue;
    }

    @Override
    public Node getLValue() {
      return lValue;
    }

    @Override
    public Node getRValue() {
      return null;
    }
  }

  /**
   * Represents an unknown definition.
   */
  static final class UnknownDefinition extends IncompleteDefinition {
    UnknownDefinition(Node lValue, boolean inExterns) {
      super(lValue, inExterns);
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      throw new IllegalArgumentException("Can't remove an UnknownDefinition");
    }
  }

  /**
   * Represents an name-only external definition.  The definition's
   * RHS is missing.
   */
  static final class ExternalNameOnlyDefinition extends IncompleteDefinition {

    ExternalNameOnlyDefinition(Node lValue) {
      super(lValue, true);
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      throw new IllegalArgumentException(
          "Can't remove external name-only definition");
    }
  }

  /**
   * Represents a function formal parameter. The definition's RHS is missing.
   */
  static final class FunctionArgumentDefinition extends IncompleteDefinition {
    FunctionArgumentDefinition(Node function,
        Node argumentName,
        boolean inExterns) {
      super(argumentName, inExterns);
      Preconditions.checkArgument(function.isFunction());
      Preconditions.checkArgument(argumentName.isName());
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      throw new IllegalArgumentException(
          "Can't remove a FunctionArgumentDefinition");
    }
  }

  /**
   * Represents a function declaration or function expression.
   */
  abstract static class FunctionDefinition extends Definition {

    protected final Node function;

    FunctionDefinition(Node node, boolean inExterns) {
      super(inExterns);
      Preconditions.checkArgument(node.isFunction());
      function = node;
    }

    @Override
    public Node getLValue() {
      return function.getFirstChild();
    }

    @Override
    public Node getRValue() {
      return function;
    }
  }

  /**
   * Represents a function declaration without assignment node such as
   * {@code function foo()}.
   */
  static final class NamedFunctionDefinition extends FunctionDefinition {
    NamedFunctionDefinition(Node node, boolean inExterns) {
      super(node, inExterns);
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      compiler.reportChangeToEnclosingScope(function);
      function.detach();
    }
  }

  /**
   * Represents a function expression that acts as a RHS.  The defined
   * name is only reachable from within the function.
   */
  static final class FunctionExpressionDefinition extends FunctionDefinition {
    FunctionExpressionDefinition(Node node, boolean inExterns) {
      super(node, inExterns);
      Preconditions.checkArgument(
          NodeUtil.isFunctionExpression(node));
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      // replace internal name with ""
      function.replaceChild(function.getFirstChild(), IR.name(""));
      compiler.reportChangeToEnclosingScope(function.getFirstChild());
    }
  }

  /**
   * Represents a declaration within an assignment.
   */
  static final class AssignmentDefinition extends Definition {
    private final Node assignment;

    AssignmentDefinition(Node node, boolean inExterns) {
      super(inExterns);
      Preconditions.checkArgument(node.isAssign());
      assignment = node;
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      // A simple assignment. foo = bar() -> bar();
      Node parent = assignment.getParent();
      Node last = assignment.getLastChild();
      assignment.removeChild(last);
      parent.replaceChild(assignment, last);
      compiler.reportChangeToEnclosingScope(parent);
    }

    @Override
    public Node getLValue() {
      return assignment.getFirstChild();
    }

    @Override
    public Node getRValue() {
      return assignment.getLastChild();
    }
  }

  /**
   * Represents member declarations using a record type from externs.
   * Example: /** @typedef {{prop: number}} *\/ var typdef;
   */
  static final class RecordTypePropertyDefinition extends IncompleteDefinition {
    RecordTypePropertyDefinition(Node name) {
      super(IR.getprop(IR.objectlit(), name.cloneNode()),
            /** isExtern */ true);
      Preconditions.checkArgument(name.isString());
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      throw new UnsupportedOperationException("Can't remove RecordType def");
    }
  }


  /**
   * Represents member declarations using a object literal.
   * Example: var x = { e : function() { } };
   */
  static final class ObjectLiteralPropertyDefinition extends Definition {

    private final Node literal;
    private final Node name;
    private final Node value;

    ObjectLiteralPropertyDefinition(Node lit, Node name, Node value,
          boolean isExtern) {
      super(isExtern);

      this.literal = lit;
      this.name = name;
      this.value = value;
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      literal.removeChild(name);
      compiler.reportChangeToEnclosingScope(literal);
    }

    @Override
    public Node getLValue() {
      // TODO(user) revisit: object literal definitions are an example
      // of definitions whose LHS doesn't correspond to a node that
      // exists in the AST.  We will have to change the return type of
      // getLValue sooner or later in order to provide this added
      // flexibility.

      switch (name.getToken()) {
        case SETTER_DEF:
        case GETTER_DEF:
        case STRING_KEY:
          // TODO(johnlenz): return a GETELEM for quoted strings.
          return IR.getprop(
              IR.objectlit(),
              IR.string(name.getString()));
        default:
          throw new IllegalStateException("unexpected");
      }
    }

    @Override
    public Node getRValue() {
      return value;
    }
  }

  /**
   * Represents a VAR declaration with an assignment.
   */
  static final class VarDefinition extends Definition {
    private final Node name;
    VarDefinition(Node node, boolean inExterns) {
      super(inExterns);
      Preconditions.checkArgument(NodeUtil.isVarDeclaration(node));
      Preconditions.checkArgument(inExterns || node.hasChildren(),
          "VAR Declaration of %s must be assigned a value.", node.getString());
      name = node;
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      Node var = name.getParent();
      Preconditions.checkState(var.getFirstChild() == var.getLastChild(),
          "AST should be normalized first");
      Node parent = var.getParent();
      Node rValue = name.removeFirstChild();
      Preconditions.checkState(!NodeUtil.isLoopStructure(parent));
      parent.replaceChild(var, NodeUtil.newExpr(rValue));
      compiler.reportChangeToEnclosingScope(parent);
    }

    @Override
    public Node getLValue() {
      return name;
    }

    @Override
    public Node getRValue() {
      return name.getFirstChild();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy