/**
  * The scope that we should declare this function in, if it needs to be declared in a scope.
  * Notice that TypedScopeCreator takes care of most scope-declaring.
  */
 private Scope getScopeDeclaredIn() {
   int dotIndex = fnName.indexOf(".");
   if (dotIndex != -1) {
     String rootVarName = fnName.substring(0, dotIndex);
     Var rootVar = scope.getVar(rootVarName);
     if (rootVar != null) {
       return rootVar.getScope();
     }
   }
   return scope;
 }
Example #2
0
    @Override
    public void enterScope(NodeTraversal t) {
      if (t.inGlobalScope()) return;

      Iterator<Var> it = t.getScope().getVars();
      while (it.hasNext()) {
        Var current = it.next();
        if (current.isBleedingFunction()) {
          localBleedingFunctions.add(current);
          localBleedingFunctionsPerScope.put(t.getScope().getParent(), current);
        }
      }
    }
  private boolean isRemovableVar(Var var) {
    // Global variables are off-limits if the user might be using them.
    if (!removeGlobals && var.isGlobal()) {
      return false;
    }

    // Referenced variables are off-limits.
    if (referenced.contains(var)) {
      return false;
    }

    // Exported variables are off-limits.
    if (codingConvention.isExported(var.getName())) {
      return false;
    }

    return true;
  }
  /**
   * Look at all the property assigns to all variables. These may or may not count as references.
   * For example, <code>
   * var x = {};
   * x.foo = 3; // not a reference.
   * var y = foo();
   * y.foo = 3; // is a reference.
   * </code> Interpreting assigments could mark a variable as referenced that wasn't referenced
   * before, in order to keep it alive. Because we find references by lazily traversing subtrees,
   * marking a variable as referenced could trigger new traversals of new subtrees, which could find
   * new references.
   *
   * <p>Therefore, this interpretation needs to be run to a fixed point.
   */
  private void interpretAssigns() {
    boolean changes = false;
    do {
      changes = false;

      // We can't use traditional iterators and iterables for this list,
      // because our lazily-evaluated continuations will modify it while
      // we traverse it.
      for (int current = 0; current < maybeUnreferenced.size(); current++) {
        Var var = maybeUnreferenced.get(current);
        if (referenced.contains(var)) {
          maybeUnreferenced.remove(current);
          current--;
        } else {
          boolean assignedToUnknownValue = false;
          boolean hasPropertyAssign = false;

          if (var.getParentNode().getType() == Token.VAR
              && !NodeUtil.isForIn(var.getParentNode().getParent())) {
            Node value = var.getInitialValue();
            assignedToUnknownValue = value != null && !NodeUtil.isLiteralValue(value, true);
          } else {
            // This was initialized to a function arg or a catch param
            // or a for...in variable.
            assignedToUnknownValue = true;
          }

          for (Assign assign : assignsByVar.get(var)) {
            if (assign.isPropertyAssign) {
              hasPropertyAssign = true;
            } else if (!NodeUtil.isLiteralValue(assign.assignNode.getLastChild(), true)) {
              assignedToUnknownValue = true;
            }
          }

          if (assignedToUnknownValue && hasPropertyAssign) {
            changes = markReferencedVar(var) || changes;
            maybeUnreferenced.remove(current);
            current--;
          }
        }
      }
    } while (changes);
  }
 private boolean isParameter(Var v) {
   return v.getParentNode().getType() == Token.LP;
 }
  /**
   * Traverses everything in the current scope and marks variables that are referenced.
   *
   * <p>During traversal, we identify subtrees that will only be referenced if their enclosing
   * variables are referenced. Instead of traversing those subtrees, we create a continuation for
   * them, and traverse them lazily.
   */
  private void traverseNode(Node n, Node parent, Scope scope) {
    int type = n.getType();
    Var var = null;
    switch (type) {
      case Token.FUNCTION:
        // If this function is a removable var, then create a continuation
        // for it instead of traversing immediately.
        if (NodeUtil.isFunctionDeclaration(n)) {
          var = scope.getVar(n.getFirstChild().getString());
        }

        if (var != null && isRemovableVar(var)) {
          continuations.put(var, new Continuation(n, scope));
        } else {
          traverseFunction(n, scope);
        }
        return;

      case Token.ASSIGN:
        Assign maybeAssign = Assign.maybeCreateAssign(n);
        if (maybeAssign != null) {
          // Put this in the assign map. It might count as a reference,
          // but we won't know that until we have an index of all assigns.
          var = scope.getVar(maybeAssign.nameNode.getString());
          if (var != null) {
            assignsByVar.put(var, maybeAssign);
            assignsByNode.put(maybeAssign.nameNode, maybeAssign);

            if (isRemovableVar(var) && !maybeAssign.mayHaveSecondarySideEffects) {
              // If the var is unreferenced and performing this assign has
              // no secondary side effects, then we can create a continuation
              // for it instead of traversing immediately.
              continuations.put(var, new Continuation(n, scope));
              return;
            }
          }
        }
        break;

      case Token.CALL:
        // Look for calls to inheritance-defining calls (such as goog.inherits).
        SubclassRelationship subclassRelationship = codingConvention.getClassesDefinedByCall(n);
        if (subclassRelationship != null) {
          Var subclassVar = scope.getVar(subclassRelationship.subclassName);
          // Don't try to track the inheritance calls for non-globals. It would
          // be more correct to only not track when the subclass does not
          // reference a constructor, but checking that it is a global is
          // easier and mostly the same.
          if (subclassVar != null && subclassVar.isGlobal() && !referenced.contains(subclassVar)) {
            // Save a reference to the EXPR node.
            inheritsCalls.put(subclassVar, parent);
            continuations.put(subclassVar, new Continuation(n, scope));
            return;
          }
        }
        break;

      case Token.NAME:
        var = scope.getVar(n.getString());
        if (parent.getType() == Token.VAR) {
          Node value = n.getFirstChild();
          if (value != null
              && var != null
              && isRemovableVar(var)
              && !NodeUtil.mayHaveSideEffects(value)) {
            // If the var is unreferenced and creating its value has no side
            // effects, then we can create a continuation for it instead
            // of traversing immediately.
            continuations.put(var, new Continuation(n, scope));
            return;
          }
        } else {

          // If arguments is escaped, we just assume the worst and continue
          // on all the parameters.
          if ("arguments".equals(n.getString()) && scope.isLocal()) {
            Node lp = scope.getRootNode().getFirstChild().getNext();
            for (Node a = lp.getFirstChild(); a != null; a = a.getNext()) {
              markReferencedVar(scope.getVar(a.getString()));
            }
          }

          // All name references that aren't declarations or assigns
          // are references to other vars.
          if (var != null) {
            // If that var hasn't already been marked referenced, then
            // start tracking it.  If this is an assign, do nothing
            // for now.
            if (isRemovableVar(var)) {
              if (!assignsByNode.containsKey(n)) {
                markReferencedVar(var);
              }
            } else {
              markReferencedVar(var);
            }
          }
        }
        break;
    }

    for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
      traverseNode(c, n, scope);
    }
  }