Node tryFuseStatementsAggressively(Node n) {
    if (!NodeUtil.isStatementBlock(n)) {
      return n;
    }

    Node cur = n.getFirstChild();
    while (cur != null) {
      if (!cur.isExprResult()) {
        cur = cur.getNext();
        continue;
      }
      Node next = cur.getNext();
      while (next != null && next.isExprResult()) {
        next = next.getNext();
      }
      if (cur.getNext() != next) {
        cur = fuseIntoOneStatement(n, cur, next);
        reportCodeChange();
      }
      if (cur.isExprResult() && next != null && isFusableControlStatement(next)) {
        fuseExpressionIntoControlFlowStatement(cur, next);
        reportCodeChange();
        next = next.getNext();
      }
      cur = next;
    }

    return n;
  }
    void visitSubtree(Node n, Node parent) {
      if (n.isCall()) {
        boolean isModuleDetected = codingConvention.extractIsModuleFile(n, parent);
        if (isModuleDetected) {
          this.isModuleFile = true;
        }

        String require = codingConvention.extractClassNameIfRequire(n, parent);
        if (require != null) {
          requires.add(require);
        }

        String provide = codingConvention.extractClassNameIfProvide(n, parent);
        if (provide != null) {
          provides.add(provide);
        }
        return;
      } else if (parent != null && !parent.isExprResult() && !parent.isScript()) {
        return;
      }

      for (Node child = n.getFirstChild(); child != null; child = child.getNext()) {
        visitSubtree(child, n);
      }
    }
 /**
  * @return Whether the node is an expression result of an assignment to a property of
  *     PolymerElement.
  */
 private boolean isPolymerElementPropExpr(Node value) {
   return value != null
       && value.isExprResult()
       && value.getFirstChild().getFirstChild().isGetProp()
       && NodeUtil.getRootOfQualifiedName(value.getFirstChild().getFirstChild())
           .matchesQualifiedName(POLYMER_ELEMENT_NAME);
 }
  private static void fuseExpressionIntoControlFlowStatement(Node before, Node control) {
    Preconditions.checkArgument(before.isExprResult(), "before must be expression result");

    // Now we are just left with two statements. The comma tree of the first
    // n - 1 statements (which can be used in an expression) and the last
    // statement. We perform specific fusion based on the last statement's type.
    switch (control.getToken()) {
      case IF:
      case RETURN:
      case THROW:
      case SWITCH:
      case EXPR_RESULT:
        before.getParent().removeChild(before);
        fuseExpressionIntoFirstChild(before.removeFirstChild(), control);
        return;
      case FOR:
        before.getParent().removeChild(before);
        if (NodeUtil.isForIn(control)) {
          fuseExpressionIntoSecondChild(before.removeFirstChild(), control);
        } else {
          fuseExpressionIntoFirstChild(before.removeFirstChild(), control);
        }
        return;
      case LABEL:
        fuseExpressionIntoControlFlowStatement(before, control.getLastChild());
        return;
      case BLOCK:
        fuseExpressionIntoControlFlowStatement(before, control.getFirstChild());
        return;
      default:
        throw new IllegalStateException("Statement fusion missing.");
    }
  }
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (!n.isCall() || !n.getFirstChild().isName()) {
        return;
      }

      Node callTarget = n.getFirstChild();
      String callName = callTarget.getString();

      if (startMarkerName.equals(callName)) {
        if (!parent.isExprResult()) {
          compiler.report(t.makeError(n, INVALID_MARKER_USAGE, startMarkerName));
          return;
        }
        markerStack.push(parent);
        return;
      }

      if (!endMarkerName.equals(callName)) {
        return;
      }

      Node endMarkerNode = parent;
      if (!endMarkerNode.isExprResult()) {
        compiler.report(t.makeError(n, INVALID_MARKER_USAGE, endMarkerName));
        return;
      }

      if (markerStack.isEmpty()) {
        compiler.report(t.makeError(n, UNMATCHED_END_MARKER, startMarkerName, endMarkerName));
        return;
      }

      Node startMarkerNode = markerStack.pop();
      if (endMarkerNode.getParent() != startMarkerNode.getParent()) {
        // The end marker isn't in the same block as the start marker.
        compiler.report(t.makeError(n, UNMATCHED_END_MARKER, startMarkerName, endMarkerName));
        return;
      }

      // This is a valid marker set add it to the list of markers to process.
      validMarkers.add(new Marker(startMarkerNode, endMarkerNode));
    }
  @Override
  void add(Node n, Context context) {
    Node parent = n.getParent();
    if (parent != null && (parent.isBlock() || parent.isScript())) {
      if (n.isFunction()) {
        add(getFunctionAnnotation(n));
      } else if (n.isExprResult() && n.getFirstChild().isAssign()) {
        Node rhs = n.getFirstChild().getLastChild();
        add(getTypeAnnotation(rhs));
      } else if (n.isVar() && n.getFirstChild().getFirstChild() != null) {
        add(getTypeAnnotation(n.getFirstChild().getFirstChild()));
      }
    }

    super.add(n, context);
  }
  private boolean canFuseIntoOneStatement(Node block) {
    // If we are favoring semi-colon, we shouldn't fuse script blocks.
    if (!favorsCommaOverSemiColon && !block.isBlock()) {
      return false;
    }

    // Nothing to do here.
    if (!block.hasChildren() || block.hasOneChild()) {
      return false;
    }

    Node last = block.getLastChild();

    for (Node c = block.getFirstChild(); c != null; c = c.getNext()) {
      if (!c.isExprResult() && c != last) {
        return false;
      }
    }

    return isFusableControlStatement(last);
  }
  /**
   * Updates the initial assignment to a collapsible property at global scope by changing it to a
   * variable declaration (e.g. a.b = 1 -> var a$b = 1). The property's value may either be a
   * primitive or an object literal or function whose properties aren't collapsible.
   *
   * @param alias The flattened property name (e.g. "a$b")
   * @param refName The name for the reference being updated.
   * @param ref An object containing information about the assignment getting updated
   */
  private void updateSimpleDeclaration(String alias, Name refName, Ref ref) {
    Node rvalue = ref.node.getNext();
    Node parent = ref.node.getParent();
    Node grandparent = parent.getParent();
    Node greatGrandparent = grandparent.getParent();

    if (rvalue != null && rvalue.isFunction()) {
      checkForHosedThisReferences(rvalue, refName.docInfo, refName);
    }

    // Create the new alias node.
    Node nameNode =
        NodeUtil.newName(compiler, alias, grandparent.getFirstChild(), refName.getFullName());
    NodeUtil.copyNameAnnotations(ref.node.getLastChild(), nameNode);

    if (grandparent.isExprResult()) {
      // BEFORE: a.b.c = ...;
      //   exprstmt
      //     assign
      //       getprop
      //         getprop
      //           name a
      //           string b
      //         string c
      //       NODE
      // AFTER: var a$b$c = ...;
      //   var
      //     name a$b$c
      //       NODE

      // Remove the r-value (NODE).
      parent.removeChild(rvalue);
      nameNode.addChildToFront(rvalue);

      Node varNode = IR.var(nameNode);
      greatGrandparent.replaceChild(grandparent, varNode);
    } else {
      // This must be a complex assignment.
      Preconditions.checkNotNull(ref.getTwin());

      // BEFORE:
      // ... (x.y = 3);
      //
      // AFTER:
      // var x$y;
      // ... (x$y = 3);

      Node current = grandparent;
      Node currentParent = grandparent.getParent();
      for (;
          !currentParent.isScript() && !currentParent.isBlock();
          current = currentParent, currentParent = currentParent.getParent()) {}

      // Create a stub variable declaration right
      // before the current statement.
      Node stubVar = IR.var(nameNode.cloneTree()).useSourceInfoIfMissingFrom(nameNode);
      currentParent.addChildBefore(stubVar, current);

      parent.replaceChild(ref.node, nameNode);
    }

    compiler.reportCodeChange();
  }
Example #9
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);
      }
    }
  }
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    JSDocInfo docInfo = n.getJSDocInfo();
    if (docInfo != null && docInfo.isExport()) {

      if (parent.isAssign() && (n.isFunction() || n.isClass())) {
        JSDocInfo parentInfo = parent.getJSDocInfo();
        if (parentInfo != null && parentInfo.isExport()) {
          // ScopedAliases produces export annotations on both the function/class
          // node and assign node, we only want to visit the assign node.
          return;
        }
      }

      String export = null;
      GenerateNodeContext context = null;

      switch (n.getType()) {
        case Token.FUNCTION:
        case Token.CLASS:
          if (parent.isScript()) {
            export = NodeUtil.getName(n);
            context = new GenerateNodeContext(n, Mode.EXPORT);
          }
          break;

        case Token.MEMBER_FUNCTION_DEF:
          export = n.getString();
          context = new GenerateNodeContext(n, Mode.EXPORT);
          break;

        case Token.ASSIGN:
          Node grandparent = parent.getParent();
          if (parent.isExprResult() && !n.getLastChild().isAssign()) {
            if (grandparent != null
                && grandparent.isScript()
                && n.getFirstChild().isQualifiedName()) {
              export = n.getFirstChild().getQualifiedName();
              context = new GenerateNodeContext(n, Mode.EXPORT);
            } else if (allowLocalExports && n.getFirstChild().isGetProp()) {
              Node target = n.getFirstChild();
              export = target.getLastChild().getString();
              context = new GenerateNodeContext(n, Mode.EXTERN);
            }
          }
          break;

        case Token.VAR:
        case Token.LET:
        case Token.CONST:
          if (parent.isScript()) {
            if (n.getFirstChild().hasChildren() && !n.getFirstChild().getFirstChild().isAssign()) {
              export = n.getFirstChild().getString();
              context = new GenerateNodeContext(n, Mode.EXPORT);
            }
          }
          break;

        case Token.GETPROP:
          if (allowLocalExports && parent.isExprResult()) {
            export = n.getLastChild().getString();
            context = new GenerateNodeContext(n, Mode.EXTERN);
          }
          break;

        case Token.STRING_KEY:
        case Token.GETTER_DEF:
        case Token.SETTER_DEF:
          if (allowLocalExports) {
            export = n.getString();
            context = new GenerateNodeContext(n, Mode.EXTERN);
          }
          break;
      }

      if (export != null) {
        exports.put(export, context);
      } else {
        // Don't produce extra warnings for functions values of object literals
        if (!n.isFunction() || !NodeUtil.isObjectLitKey(parent)) {
          if (allowLocalExports) {
            compiler.report(t.makeError(n, EXPORT_ANNOTATION_NOT_ALLOWED));
          } else {
            compiler.report(t.makeError(n, NON_GLOBAL_ERROR));
          }
        }
      }
    }
  }
  private void visitArrayPattern(NodeTraversal t, Node arrayPattern, Node parent) {
    Node rhs, nodeToDetach;
    if (NodeUtil.isNameDeclaration(parent) && !NodeUtil.isEnhancedFor(parent.getParent())) {
      // The array pattern is the only child, because Es6SplitVariableDeclarations
      // has already run.
      Preconditions.checkState(arrayPattern.getNext() == null);
      rhs = arrayPattern.getLastChild();
      nodeToDetach = parent;
    } else if (parent.isAssign()) {
      rhs = arrayPattern.getNext();
      nodeToDetach = parent.getParent();
      Preconditions.checkState(nodeToDetach.isExprResult());
    } else if (parent.isArrayPattern() || parent.isDefaultValue() || parent.isStringKey()) {
      // This is a nested array pattern. Don't do anything now; we'll visit it
      // after visiting the parent.
      return;
    } else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) {
      visitDestructuringPatternInEnhancedFor(arrayPattern);
      return;
    } else {
      Preconditions.checkState(parent.isCatch() || parent.isForOf());
      cannotConvertYet(
          arrayPattern, "ARRAY_PATTERN that is a child of a " + Token.name(parent.getType()));
      return;
    }

    // Convert 'var [x, y] = rhs' to:
    // var temp = rhs;
    // var x = temp[0];
    // var y = temp[1];
    String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++);
    Node tempDecl =
        IR.var(IR.name(tempVarName), rhs.detachFromParent())
            .useSourceInfoIfMissingFromForTree(arrayPattern);
    nodeToDetach.getParent().addChildBefore(tempDecl, nodeToDetach);

    int i = 0;
    for (Node child = arrayPattern.getFirstChild(), next; child != null; child = next, i++) {
      next = child.getNext();
      if (child.isEmpty()) {
        continue;
      }

      Node newLHS, newRHS;
      if (child.isDefaultValue()) {
        Node getElem = IR.getelem(IR.name(tempVarName), IR.number(i));
        //   [x = defaultValue] = rhs;
        // becomes
        //   var temp = rhs;
        //   x = (temp[0] === undefined) ? defaultValue : temp[0];
        newLHS = child.getFirstChild().detachFromParent();
        newRHS = defaultValueHook(getElem, child.getLastChild().detachFromParent());
      } else if (child.isRest()) {
        newLHS = child.detachFromParent();
        newLHS.setType(Token.NAME);
        // [].slice.call(temp, i)
        newRHS =
            IR.call(
                IR.getprop(IR.getprop(IR.arraylit(), IR.string("slice")), IR.string("call")),
                IR.name(tempVarName),
                IR.number(i));
      } else {
        newLHS = child.detachFromParent();
        newRHS = IR.getelem(IR.name(tempVarName), IR.number(i));
      }
      Node newNode;
      if (parent.isAssign()) {
        Node assignment = IR.assign(newLHS, newRHS);
        newNode = IR.exprResult(assignment);
      } else {
        newNode = IR.declaration(newLHS, newRHS, parent.getType());
      }
      newNode.useSourceInfoIfMissingFromForTree(arrayPattern);

      nodeToDetach.getParent().addChildBefore(newNode, nodeToDetach);
      // Explicitly visit the LHS of the new node since it may be a nested
      // destructuring pattern.
      visit(t, newLHS, newLHS.getParent());
    }
    nodeToDetach.detachFromParent();
    compiler.reportCodeChange();
  }