/** Inline a function into the call site. */
    private void inlineFunction(NodeTraversal t, Reference ref, FunctionState fs) {
      Function fn = fs.getFn();
      String fnName = fn.getName();
      Node fnNode = fs.getSafeFnNode();

      Node newExpr = injector.inline(ref, fnName, fnNode);
      if (!newExpr.isEquivalentTo(ref.callNode)) {
        t.getCompiler().reportChangeToEnclosingScope(newExpr);
      }
      t.getCompiler().addToDebugLog("Inlined function: " + fn.getName());
    }
  @Override
  public void exitScope(NodeTraversal traversal) {
    Preconditions.checkNotNull(traversal);

    // This is the case when we are exiting the global scope where we had never
    // collected argument access list. Since we do not perform this optimization
    // for the global scope, we will skip this exit point.
    if (currentArgumentsAccess == null) {
      return;
    }

    Node function = traversal.getScopeRoot();
    if (!function.isFunction()) {
      return;
    }

    // Attempt to replace the argument access and if the AST has been change,
    // report back to the compiler.
    if (tryReplaceArguments(traversal.getScope())) {
      traversal.getCompiler().reportCodeChange();
    }

    // After the attempt to replace the arguments. The currentArgumentsAccess
    // is stale and as we exit the Scope, no longer holds all the access to the
    // current scope anymore. We'll pop the access list from the outer scope
    // and set it as currentArgumentsAccess if the outer scope is not the global
    // scope.
    if (!argumentsAccessStack.isEmpty()) {
      currentArgumentsAccess = argumentsAccessStack.pop();
    } else {
      currentArgumentsAccess = null;
    }
  }
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (!NodeUtil.isReferenceName(n)) {
        return;
      }

      Scope referencedIn = t.getScope();
      String oldName = n.getString();
      Scope current = referencedIn;
      boolean doRename = false;
      String newName = null;
      while (current != null) {
        Map<String, String> renamesAtCurrentLevel = renameMap.get(current.getRootNode());
        if (current.isDeclared(oldName, false)) {
          return;
        } else if (renamesAtCurrentLevel != null && renamesAtCurrentLevel.containsKey(oldName)) {
          doRename = true;
          newName = renamesAtCurrentLevel.get(oldName);
          break;
        } else {
          current = current.getParent();
        }
      }
      if (doRename) {
        n.setString(newName);
        t.getCompiler().reportCodeChange();
      }
    }
  private static void reportIfWasEmpty(NodeTraversal t, Node block) {
    Preconditions.checkState(block.isBlock());

    // A semicolon is distinguished from a block without children by
    // annotating it with EMPTY_BLOCK.  Blocks without children are
    // usually intentional, especially with loops.
    if (!block.hasChildren() && block.isAddedBlock()) {
      t.getCompiler().report(t.makeError(block, SUSPICIOUS_SEMICOLON));
    }
  }
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    // TODO(moz): Add support for renaming classes.
    if (!n.isLet() && !n.isConst() && !NodeUtil.isBlockScopedFunctionDeclaration(n)) {
      return;
    }

    Scope scope = t.getScope();
    Node nameNode = n.getFirstChild();
    if (!n.isFunction()
        && !nameNode.hasChildren()
        && (parent == null || !NodeUtil.isEnhancedFor(parent))) {
      nameNode.addChildToFront(IR.name("undefined").useSourceInfoIfMissingFrom(nameNode));
    }

    String oldName = nameNode.getString();
    if (n.isLet() || n.isConst()) {
      blockScopedDeclarations.add(n);
    }
    Scope hoistScope = scope.getClosestHoistScope();
    boolean doRename = false;
    if (scope != hoistScope) {
      doRename = hoistScope.isDeclared(oldName, true) || undeclaredNames.contains(oldName);
      String newName =
          doRename ? oldName + "$" + compiler.getUniqueNameIdSupplier().get() : oldName;
      Var oldVar = scope.getVar(oldName);
      scope.undeclare(oldVar);
      hoistScope.declare(newName, nameNode, oldVar.input);
      if (doRename) {
        nameNode.setString(newName);
        Node scopeRoot = scope.getRootNode();
        if (!renameMap.containsKey(scopeRoot)) {
          renameMap.put(scopeRoot, new HashMap<String, String>());
        }

        renameMap.get(scopeRoot).put(oldName, newName);
      }
    }
    if (doRename) {
      t.getCompiler().reportCodeChange();
    }
  }
 private static void reportIfNaN(NodeTraversal t, Node n) {
   if (NodeUtil.isNaN(n)) {
     t.getCompiler().report(t.makeError(n.getParent(), SUSPICIOUS_COMPARISON_WITH_NAN));
   }
 }
Пример #7
0
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    // VOID nodes appear when there are extra semicolons at the BLOCK level.
    // I've been unable to think of any cases where this indicates a bug,
    // and apparently some people like keeping these semicolons around,
    // so we'll allow it.
    if (n.isEmpty() || n.isComma()) {
      return;
    }

    if (parent == null) {
      return;
    }

    int pt = parent.getType();
    if (pt == Token.COMMA) {
      Node gramps = parent.getParent();
      if (gramps.isCall() && parent == gramps.getFirstChild()) {
        // Semantically, a direct call to eval is different from an indirect
        // call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first
        // expression to a comma to be a no-op if it's used to indirect
        // an eval.
        if (n == parent.getFirstChild()
            && parent.getChildCount() == 2
            && n.getNext().isName()
            && "eval".equals(n.getNext().getString())) {
          return;
        }
      }

      if (n == parent.getLastChild()) {
        for (Node an : parent.getAncestors()) {
          int ancestorType = an.getType();
          if (ancestorType == Token.COMMA) continue;
          if (ancestorType != Token.EXPR_RESULT && ancestorType != Token.BLOCK) return;
          else break;
        }
      }
    } else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) {
      if (pt == Token.FOR
          && parent.getChildCount() == 4
          && (n == parent.getFirstChild() || n == parent.getFirstChild().getNext().getNext())) {
        // Fall through and look for warnings for the 1st and 3rd child
        // of a for.
      } else {
        return; // it might be ok to not have a side-effect
      }
    }

    boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType());
    if (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler())) {
      if (n.isQualifiedName() && n.getJSDocInfo() != null) {
        // This no-op statement was there so that JSDoc information could
        // be attached to the name. This check should not complain about it.
        return;
      } else if (n.isExprResult()) {
        // we already reported the problem when we visited the child.
        return;
      }

      String msg = "This code lacks side-effects. Is there a bug?";
      if (n.isString()) {
        msg = "Is there a missing '+' on the previous line?";
      } else if (isSimpleOp) {
        msg =
            "The result of the '"
                + Token.name(n.getType()).toLowerCase()
                + "' operator is not being used.";
      }

      t.getCompiler().report(t.makeError(n, level, USELESS_CODE_ERROR, msg));
      // TODO(johnlenz): determine if it is necessary to
      // try to protect side-effect free statements as well.
      if (!NodeUtil.isStatement(n)) {
        problemNodes.add(n);
      }
    }
  }