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

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

There is a newer version: 9.0.8
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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

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(Node n)}.
   *
   * @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.isNameDeclaration(parent) && n.isName() && (isExtern || n.hasChildren())) {
      return new VarDefinition(n, isExtern);
    } else if (parent.isFunction() && parent.getFirstChild() == n) {
      if (NodeUtil.isFunctionDeclaration(parent)) {
        return new NamedFunctionDefinition(parent, isExtern);
      } else if (!n.getString().isEmpty()) {
        return new FunctionExpressionDefinition(parent, isExtern);
      }
    } else if (parent.isClass() && parent.getFirstChild() == n) {
      if (!NodeUtil.isClassExpression(parent)) {
        return new NamedClassDefinition(parent, isExtern);
      } else if (!n.isEmpty()) {
        return new ClassExpressionDefinition(parent, isExtern);
      }
    } else if (n.isMemberFunctionDef() && parent.isClassMembers()) {
      return new MemberFunctionDefinition(n, isExtern);
    } else if (parent.isAssign() && parent.getFirstChild() == n) {
      return new AssignmentDefinition(parent, isExtern);
    } else if (NodeUtil.isObjectLitKey(n)) {
      return new ObjectLiteralPropertyDefinition(n, n.getFirstChild(), isExtern);
    } else if (NodeUtil.getEnclosingType(n, Token.PARAM_LIST) != null && n.isName()) {
      Node paramList = NodeUtil.getEnclosingType(n, Token.PARAM_LIST);
      Node function = paramList.getParent();
      return new FunctionArgumentDefinition(function, n, isExtern);
    } else if (parent.getToken() == Token.COLON && parent.getFirstChild() == n && isExtern) {
      Node grandparent = parent.getParent();
      checkState(grandparent.getToken() == Token.LB);
      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(Node, boolean)}.
   *
   * @return Whether a definition object can be created.
   */
  static boolean isDefinitionNode(Node n) {
    Node parent = n.getParent();
    if (parent == null) {
      return false;
    }

    if (NodeUtil.isNameDeclaration(parent) && n.isName()
        && (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.isClass() && parent.getFirstChild() == n) {
      if (!NodeUtil.isClassExpression(parent)) {
        return true;
      } else if (!n.isEmpty()) {
        return true;
      }
    } else if (n.isMemberFunctionDef() && parent.isClassMembers()) {
      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();
      checkState(grandparent.getToken() == Token.LB);
      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;
    private final String simplifiedName;

    Definition(boolean isExtern, String simplifiedName) {
      this.isExtern = isExtern;
      this.simplifiedName = simplifiedName;
    }

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

    public String getSimplifiedName() {
      return simplifiedName;
    }

    /**
     * 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, NameBasedDefinitionProvider.getSimplifiedName(lValue));
      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);
      checkArgument(function.isFunction());
      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) {
      this(node, inExterns, NameBasedDefinitionProvider.getSimplifiedName(node.getFirstChild()));
    }

    FunctionDefinition(Node node, boolean inExterns, String name) {
      super(inExterns, name);
      checkArgument(node.isFunction(), node);
      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();
      NodeUtil.markFunctionsDeleted(function, compiler);
    }
  }

  /**
   * 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);
      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 class member function.
   */

  static final class MemberFunctionDefinition extends FunctionDefinition {

    protected final Node memberFunctionDef;

    MemberFunctionDefinition(Node node, boolean inExterns) {
      super(node.getFirstChild(), inExterns, NameBasedDefinitionProvider.getSimplifiedName(node));
      checkState(node.isMemberFunctionDef(), node);
      memberFunctionDef = node;
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      NodeUtil.deleteNode(memberFunctionDef, compiler);
    }

    @Override
    public Node getLValue() {
      // As far as we know, only the property name matters so the target can be an object literal
      return IR.getprop(IR.objectlit(), memberFunctionDef.getString());
    }
  }

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

    protected final Node c;

    ClassDefinition(Node node, boolean inExterns) {
      super(inExterns, NameBasedDefinitionProvider.getSimplifiedName(node.getFirstChild()));
      Preconditions.checkArgument(node.isClass());
      c = node;
    }

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

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

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

    @Override
    public void performRemove(AbstractCompiler compiler) {
      NodeUtil.deleteNode(c, compiler);
    }
  }

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

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

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

    AssignmentDefinition(Node node, boolean inExterns) {
      super(inExterns, NameBasedDefinitionProvider.getSimplifiedName(node.getFirstChild()));
      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);
      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 name;
    private final Node value;

    ObjectLiteralPropertyDefinition(Node name, Node value, boolean isExtern) {
      super(isExtern, NameBasedDefinitionProvider.getSimplifiedName(getLValue(name)));

      this.name = name;
      this.value = value;
    }

    @Override
    public void performRemove(AbstractCompiler compiler) {
      NodeUtil.deleteNode(name, compiler);
    }

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

    private static Node getLValue(Node name) {
      // 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:
        case MEMBER_FUNCTION_DEF:
          // TODO(johnlenz): return a GETELEM for quoted strings.
          return IR.getprop(
              IR.objectlit(),
              IR.string(name.getString()));
        default:
          throw new IllegalStateException("Unexpected left Token: " + name.getToken());
      }
    }

    @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, NameBasedDefinitionProvider.getSimplifiedName(node));
      checkArgument(NodeUtil.isNameDeclaration(node.getParent()) && node.isName());
      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();
      checkState(var.getFirstChild() == var.getLastChild(), "AST should be normalized first");
      Node parent = var.getParent();
      Node rValue = name.removeFirstChild();
      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