com.google.javascript.jscomp.NodeUtil Java Examples

The following examples show how to use com.google.javascript.jscomp.NodeUtil. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: TypeConversionPass.java    From clutz with MIT License 6 votes vote down vote up
private void createTypeAlias(Node n, Node parent) {
  JSDocInfo bestJSDocInfo = NodeUtil.getBestJSDocInfo(n);
  if (bestJSDocInfo != null && bestJSDocInfo.hasTypedefType()) {
    String name;
    switch (n.getToken()) {
      case NAME:
        name = n.getString();
        break;
      case GETPROP:
        // Inner typedef
        name = n.getSecondChild().getString();
        break;
      default:
        name = n.getFirstChild().getString();
        break;
    }
    Node typeDef = Node.newString(Token.TYPE_ALIAS, name);
    nodeComments.moveComment(n, typeDef);
    types.put(name, typeDef);
    typeDef.setJSDocInfo(bestJSDocInfo);
    replaceExpressionOrAssignment(n, parent, typeDef);
  }
}
 
Example #2
Source File: NodeModulePass.java    From js-dossier with Apache License 2.0 6 votes vote down vote up
@Override
public void visit(NodeTraversal t, final Node n, final Node parent) {
  if (NodeUtil.isNameDeclaration(n)) {
    RequireDetector detector = new RequireDetector();
    traverse(t.getCompiler(), n, detector);

    if (detector.foundRequire) {
      Node addAfter = n;
      for (Node last = n.getLastChild();
          last != null && last != n.getFirstChild();
          last = n.getLastChild()) {
        n.removeChild(last);

        Node newDecl = declaration(last, n.getToken()).srcrefTree(last);
        parent.addChildAfter(newDecl, addAfter);
        addAfter = newDecl;
        t.reportCodeChange();
      }
    }
  }
}
 
Example #3
Source File: MinimizeExitPoints.java    From astor with GNU General Public License v2.0 6 votes vote down vote up
/**
 * Move all the child nodes following start in srcParent to the end of
 * destParent's child list.
 * @param start The start point in the srcParent child list.
 * @param srcParent The parent node of start.
 * @param destParent The destination node.
 */
static private void moveAllFollowing(
    Node start, Node srcParent, Node destParent) {
  for (Node n = start.getNext(); n != null; n = start.getNext()) {
    boolean isFunctionDeclaration =
        NodeUtil.isFunctionDeclaration(n);

    srcParent.removeChild(n);

    if (isFunctionDeclaration) {
      destParent.addChildToFront(n);
    } else {
      destParent.addChildToBack(n);
    }
  }
}
 
Example #4
Source File: RemoveGoogScopePass.java    From clutz with MIT License 6 votes vote down vote up
private void maybeReassignAlias(Node assign) {
  Node lhs = assign.getFirstChild();
  if (!lhs.isGetProp()) {
    // TODO(dpurpura): Add support for GET_ELEM.  (e.g. Foo['abc'])
    return;
  }

  // Find the name of the deepest first child.
  String alias = null;
  for (Node child = lhs; child != null; child = child.getFirstChild()) {
    if (child.isName()) {
      alias = child.getQualifiedName();
    }
  }

  checkNotNull(alias, "Missing name for alias");
  if (aliasToProvidedNamespace.containsKey(alias)) {
    String providedNamespace = aliasToProvidedNamespace.get(alias);

    String suffix = lhs.getQualifiedName().substring(alias.length());
    Node fullName = NodeUtil.newQName(compiler, providedNamespace + suffix);
    assign.replaceChild(lhs, fullName);
  }
  return;
}
 
Example #5
Source File: TypeAnnotationPass.java    From clutz with MIT License 6 votes vote down vote up
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
  JSDocInfo bestJSDocInfo = NodeUtil.getBestJSDocInfo(n);
  if (bestJSDocInfo == null) {
    return;
  }

  // Add visibility for private and protected.
  if (Visibility.PRIVATE.equals(bestJSDocInfo.getVisibility())) {
    n.putProp(Node.ACCESS_MODIFIER, Visibility.PRIVATE);
  } else if (Visibility.PROTECTED.equals(bestJSDocInfo.getVisibility())) {
    n.putProp(Node.ACCESS_MODIFIER, Visibility.PROTECTED);
  }

  // Change variable declarations to constants
  if (bestJSDocInfo.isConstant() && (n.isVar() || n.isLet())) {
    n.setToken(Token.CONST);
  }
}
 
Example #6
Source File: CollectModuleMetadata.java    From clutz with MIT License 6 votes vote down vote up
private void maybeAddProvidesExport(Node exportsName) {
  String fullname = exportsName.getQualifiedName();

  if (providesObjectChildren.containsKey(fullname)) {
    googProvideNamespaceToNode.put(fullname, exportsName);
    addExport(fullname, fullname, nameUtil.lastStepOfName(exportsName));

  } else if (exportsName.isGetProp()
      && providesObjectChildren.containsKey(exportsName.getFirstChild().getQualifiedName())) {
    googProvideNamespaceToNode.put(fullname, exportsName);

    // functions declared on functions should be exported.
    // static functions on classes should not be exported.
    String parentName = exportsName.getFirstChild().getQualifiedName();
    @Nullable Node parentNode = googProvideNamespaceToNode.get(parentName);
    JSDocInfo jsDoc = parentNode != null ? NodeUtil.getBestJSDocInfo(parentNode) : null;

    if (providesObjectChildren.containsKey(parentName)
        && (jsDoc == null || !jsDoc.isConstructor())) {
      addExport(fullname, fullname, nameUtil.lastStepOfName(exportsName));
    }
  }
}
 
Example #7
Source File: TypeConversionPass.java    From clutz with MIT License 6 votes vote down vote up
/**
 * Factory method for creating a new ClassMemberDeclarationOnThis on a declaration internal to a
 * class via the "this" keyword.
 *
 * <ul>
 *   <li>{@code this.a = 5}
 *   <li>{@code this.b}
 * </ul>
 *
 * Returns null if the expression node is an invalid member declaration.
 */
@Nullable
static ClassMemberDeclaration newDeclarationOnThis(Node n) {
  Node fullName = getFullName(n);
  // Node MUST start with "this." and be shallow, i.e. "this.foo".
  // "this.foo.bar" and other nestings are not declarations and are ignored.
  // fullName is a binary tree and multiple parts are represented by GETPROP
  // nodes recursively on the left (first) child, so a first child of THIS is
  // sufficient to ensure the name is of the form "this.foo".
  if (!fullName.isGetProp() || !fullName.getFirstChild().isThis()) {
    return null;
  }

  Node classNode = NodeUtil.getEnclosingClass(n);
  String memberName = fullName.getLastChild().getString();
  if (classNode == null) {
    return null;
  }

  return new ClassMemberDeclaration(n, false, classNode, memberName);
}
 
Example #8
Source File: TypeConversionPass.java    From clutz with MIT License 6 votes vote down vote up
/**
 * Check if we can safely generate a field initializer. We only do this if
 * - the assignment receiver is marked as const and there is no enclosing function
 *   - in general we should only try to raise the initializer of a static field if it is in the
 *     same scope as the class declaration. Calculating this is challenging, so currently we
 *     assume that if this static is the top-level of a module it has the same scope as the class
 *     declaration.
 * - the assignment rhs is a literal and there is no enclosing function, or it is a constructor.
 *   - class members' initializers should be raised only if the original initializer is on a class
 *     property or in the constructor, the two places where a member could get initialized. Since
 *     a non-literal initializer in the constructor may be using items only in the scope of the
 *     constructor, we don't try to raise this case.
 */
private boolean canPromoteFieldInitializer(ClassMemberDeclaration declaration) {
  Node fnNode = NodeUtil.getEnclosingFunction(declaration.exprRoot);

  if (isConst(declaration) && fnNode == null) {
    return true;
  }

  if (!NodeUtil.isLiteralValue(declaration.rhs, false)) {
    return false;
  }

  if (fnNode != null) {
    String fnName = getEnclosingFunctionName(fnNode);
    if (!"constructor".equals(fnName)) {
      return false;
    }
  }

  return true;
}
 
Example #9
Source File: AbstractClosureVisitor.java    From jsinterop-generator with Apache License 2.0 6 votes vote down vote up
protected ParameterNameInfo getParameterInfo(
    FunctionType functionType, int parameterIndex, String parentFqn) {

  if (functionType.getSource() != null) {
    String originalName =
        NodeUtil.getFunctionParameters(functionType.getSource())
            .getChildAtIndex(parameterIndex)
            .getString();
    return getParameterInfo(parentFqn, originalName, false);
  }

  // functionType doesn't have source, it's a anonymous function type.
  // e.g.: /** @type {function(Event):boolean} */ var eventCallback;
  // Parameters for anonymous FunctionType don't have names.
  return getParameterInfo(parentFqn, "p" + parameterIndex, true);
}
 
Example #10
Source File: TypeConversionPass.java    From clutz with MIT License 5 votes vote down vote up
private ClassMemberDeclaration(Node n, boolean isStatic, Node classNode, String memberName) {
  this.exprRoot = n;
  this.rhs = getRhs(n);
  this.jsDoc = NodeUtil.getBestJSDocInfo(n);
  this.isStatic = isStatic;
  this.classNode = classNode;
  this.memberName = memberName;
}
 
Example #11
Source File: CollectModuleMetadata.java    From clutz with MIT License 5 votes vote down vote up
/**
 * Register namespace name to global scope so that other files can call 'goog.require' on the
 * qualified name.
 */
void registerNamespaceToGlobalScope(String namespace) {
  providesObjectChildren.put(namespace, new LinkedHashSet<String>());
  if (isJsLibrary) {
    maybeAddExport(NodeUtil.newQName(compiler, namespace));
  }
  fileToModule.put(file, this);
  namespaceToModule.put(namespace, this);
}
 
Example #12
Source File: TypeConversionPass.java    From clutz with MIT License 5 votes vote down vote up
private String getEnclosingFunctionName(Node fnNode) {
  if (fnNode.isArrowFunction()) {
    return null;
  }

  // Use the QualifiedName if the function is on an object/namespace: `foo.moreFoo()`;
  // otherwise, use the string on the node: `foo` for `function foo()`
  Node fnParent = fnNode.getParent();
  if (fnParent.isGetProp() || fnParent.isCall()) {
    return NodeUtil.getName(fnNode);
  }

  /*
   * For the specific case below, when fnNode is the anonymous function then fnParent
   * is an ASSIGN node and getString() is an invalid operation on an ASSIGN node.
   *
   * Thus, in this case, there isn't an enclosing function name and so null should be
   * returned.
   *
   * class A {
   *   constructor() {
   *     this.x = function() {
   *       this.y;
   *     }
   *   }
   * }
   */
  if (fnParent.isAssign()) {
    return null;
  }

  return fnParent.getString();
}
 
Example #13
Source File: CollectModuleMetadata.java    From clutz with MIT License 5 votes vote down vote up
private void addExport(String exportName, String importName, String identifier) {
  exportedNamespacesToSymbols.put(exportName, identifier);
  importedNamespacesToSymbols.put(importName, identifier);

  Node namespace = NodeUtil.newQName(compiler, importName);
  if (namespace.isGetProp()) {
    String parentName = namespace.getFirstChild().getQualifiedName();
    if (providesObjectChildren.containsKey(parentName)) {
      providesObjectChildren.get(parentName).add(identifier);
    }
  }
}
 
Example #14
Source File: TypeAnnotationPass.java    From clutz with MIT License 5 votes vote down vote up
/**
 * Attempts to set the type of parameter represented by node by extracting it from @param
 * annotations in the function's jsDoc. Returns true if it succeeds (i.e. if it finds the type).
 */
private boolean setParameterTypeFromFunctionDoc(Node node, Node parent) {
  JSDocInfo parentDocInfo = NodeUtil.getBestJSDocInfo(parent.getParent());
  if (parentDocInfo == null) {
    return false;
  }
  String parameterName =
      node.isObjectPattern()
          ? parentDocInfo.getParameterNameAt(parent.getIndexOfChild(node))
          : node.getString();
  JSTypeExpression parameterType = parentDocInfo.getParameterType(parameterName);
  if (parameterType == null) {
    return false;
  }
  TypeDeclarationNode parameterTypeNode = convertTypeNodeAST(parameterType.getRoot());
  // Parameter is declared using verbose @param syntax before the function definition.
  Node attachTypeExpr = node;
  // Modify the primary AST to represent a function parameter as a
  // REST node, if the type indicates it is a rest parameter.
  if (parameterType.getRoot().getToken() == Token.ITER_REST) {
    attachTypeExpr = IR.iterRest(IR.name(node.getString()));
    nodeComments.replaceWithComment(node, attachTypeExpr);
  }
  // Modify the AST to represent an optional parameter
  if (parameterType.getRoot().getToken() == Token.EQUALS) {
    attachTypeExpr = IR.name(node.getString());
    if (!node.getParent().isDefaultValue()) {
      attachTypeExpr.putBooleanProp(Node.OPT_ES6_TYPED, true);
    } else if (node.getParent().getSecondChild().isName()
        && node.getParent().getSecondChild().getString().equals("undefined")) {
      // if default value is "undefined" add undefined to the type
      parameterTypeNode = flatUnionType(ImmutableList.of(parameterTypeNode, undefinedType()));
    }
    nodeComments.replaceWithComment(node, attachTypeExpr);
  }
  setTypeExpression(attachTypeExpr, parameterTypeNode);
  return true;
}
 
Example #15
Source File: TypeConversionPass.java    From clutz with MIT License 5 votes vote down vote up
/**
 * Adds a class node to the top level scope.
 *
 * <p>This determines the classname using the nearest available name node.
 */
private void addClassToScope(Node n) {
  Preconditions.checkState(n.isClass());
  String className = NodeUtil.getName(n);
  if (className == null) {
    // We do not emit an error here as there can be anonymous classes without names.
    return;
  }
  addTypeToScope(n, className);
}
 
Example #16
Source File: NameUtil.java    From clutz with MIT License 5 votes vote down vote up
/**
 * Returns the new name string with a prefix replaced with the new prefix. Returns input name if
 * prefix does not exist.
 */
public String replacePrefixInName(String name, String prefix, String newPrefix) {
  Node nameNode = NodeUtil.newQName(compiler, name);
  // Placeholder node to ensure name has a parent
  Node placeholder = new Node(Token.EMPTY, nameNode);
  replacePrefixInName(nameNode, prefix, newPrefix);
  return placeholder.getFirstChild().getQualifiedName();
}
 
Example #17
Source File: NameUtil.java    From clutz with MIT License 5 votes vote down vote up
/**
 * In-place replaces a prefix with a new prefix in a name node. Does nothing if prefix does not
 * exist.
 */
public void replacePrefixInName(Node name, String prefix, String newPrefix) {
  if (name.matchesQualifiedName(prefix)) {
    Node newName = NodeUtil.newQName(compiler, newPrefix);
    JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(name);
    newName.setJSDocInfo(jsdoc);
    name.replaceWith(newName);
  } else {
    if (name.isGetProp()) {
      replacePrefixInName(name.getFirstChild(), prefix, newPrefix);
    }
  }
}
 
Example #18
Source File: MinimizeExitPoints.java    From astor with GNU General Public License v2.0 5 votes vote down vote up
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
  switch (n.getType()) {
    case Token.LABEL:
      tryMinimizeExits(
          n.getLastChild(), Token.BREAK, n.getFirstChild().getString());
      break;

    case Token.FOR:
    case Token.WHILE:
      tryMinimizeExits(
          NodeUtil.getLoopCodeBlock(n), Token.CONTINUE, null);
      break;

    case Token.DO:
      tryMinimizeExits(
          NodeUtil.getLoopCodeBlock(n), Token.CONTINUE, null);

      Node cond = NodeUtil.getConditionExpression(n);
      if (NodeUtil.getImpureBooleanValue(cond) == TernaryValue.FALSE) {
        // Normally, we wouldn't be able to optimize BREAKs inside a loop
        // but as we know the condition will always false, we can treat them
        // as we would a CONTINUE.
        tryMinimizeExits(
            n.getFirstChild(), Token.BREAK, null);
      }
      break;

    case Token.FUNCTION:
      tryMinimizeExits(
          n.getLastChild(), Token.RETURN, null);
      break;
  }
}
 
Example #19
Source File: TypeConversionPass.java    From clutz with MIT License 4 votes vote down vote up
/**
 * Attempts to convert a ES5 superclass call into a ES6 super() call.
 *
 * <p>Examples:
 *
 * <pre>
 * B.call(this, args) -> super(args);
 * B.prototype.foo.call(this, args) ->super.foo(args);
 * A.base(this, 'constructor', args) -> super(args);
 * A.base(this, 'foo', args) -> super.foo(args);
 * </pre>
 *
 * <p>This returns without any modification if the node is not an superclass call statement.
 */
private void maybeReplaceSuperCall(Node callNode) {
  Preconditions.checkState(callNode.isCall());
  String callName = callNode.getFirstChild().getQualifiedName();

  // First validate that we are inside a constructor call that extends another class
  Node classNode = NodeUtil.getEnclosingClass(callNode);
  if (callName == null || classNode == null) {
    return;
  }

  String className = NodeUtil.getName(classNode);

  // Translate super constructor or super method calls as follows:
  // A.base(this, 'constructor', args) -> super(args);
  // A.base(this, 'foo', args) -> super.foo(args);
  if (callName.equals(className + ".base") && callNode.getSecondChild().isThis()) {
    // Super calls for root classes are not converted
    if (classNode.getSecondChild().isEmpty()) {
      compiler.report(
          JSError.make(
              callNode,
              GentsErrorManager.GENTS_CLASS_PASS_ERROR,
              String.format("Cannot call superclass in root class %s", className)));
      return;
    }
    String methodName = callNode.getChildAtIndex(2).getString();

    if ("constructor".equals(methodName)) {
      nodeComments.replaceWithComment(callNode.getFirstChild(), IR.superNode());
    } else {
      nodeComments.replaceWithComment(
          callNode.getFirstChild(), NodeUtil.newQName(compiler, "super." + methodName));
    }

    // Remove twice to get rid of "this" and the method name
    callNode.removeChild(callNode.getSecondChild());
    callNode.removeChild(callNode.getSecondChild());
    compiler.reportChangeToEnclosingScope(callNode);
    return;
  }

  String superClassName = classNode.getSecondChild().getQualifiedName();
  // B.call(this, args) -> super(args);
  if (callName.equals(superClassName + ".call") && callNode.getSecondChild().isThis()) {
    nodeComments.replaceWithComment(callNode.getFirstChild(), IR.superNode());

    callNode.removeChild(callNode.getSecondChild());
    compiler.reportChangeToEnclosingScope(callNode);
    return;
  }

  // B.prototype.foo.call(this, args) -> super.foo(args);
  if (callName.startsWith(superClassName + ".prototype.") && callName.endsWith(".call")) {
    if (callNode.getSecondChild().isThis()) {
      // Determine name of method being called
      Node nameNode = callNode.getFirstFirstChild();
      Node n = nameNode;
      while (!n.getLastChild().getString().equals("prototype")) {
        n = n.getFirstChild();
      }
      nameNode.detach();

      nodeComments.replaceWithComment(n, IR.superNode());
      nodeComments.replaceWithComment(callNode.getFirstChild(), nameNode);
      callNode.removeChild(callNode.getSecondChild());
      compiler.reportChangeToEnclosingScope(callNode);
      return;
    }
  }
}
 
Example #20
Source File: NameUtil.java    From clutz with MIT License 4 votes vote down vote up
/** Returns the last identifier of a qualified name string. */
public String lastStepOfName(String name) {
  return lastStepOfName(NodeUtil.newQName(compiler, name));
}
 
Example #21
Source File: NameUtil.java    From clutz with MIT License 4 votes vote down vote up
/**
 * Gets the longest namespace that is a prefix of the name string. Returns null if no namespaces
 * are valid prefixes.
 */
@Nullable
public String findLongestNamePrefix(String name, Set<String> namespaces) {
  return findLongestNamePrefix(NodeUtil.newQName(compiler, name), namespaces);
}
 
Example #22
Source File: DeclarationGenerator.java    From clutz with MIT License 4 votes vote down vote up
/**
 * Closure does not require all types to be explicitly provided, if they are only used in type
 * positions. However, our emit phases only emits goog.provided symbols and namespaces, so this
 * extra pass is required, in order to have valid output.
 */
private void processUnprovidedTypes(Set<String> provides, Set<String> transitiveProvides) {
  /**
   * A new set of types can be discovered while visiting unprovided types. To prevent an infinite
   * loop in a pathological case, limit to a number of passes.
   *
   * <p>TODO(rado): investigate https://github.com/angular/clutz/pull/246 and removing this pass
   * altogether.
   */
  int maxTypeUsedDepth = 5;
  Set<String> typesEmitted = new LinkedHashSet<>();
  while (maxTypeUsedDepth > 0) {
    int typesUsedCount = typesUsed.size();
    // AFAICT, there is no api for going from type to symbol, so iterate all symbols first.
    for (TypedVar symbol : compiler.getTopScope().getAllSymbols()) {
      String name = symbol.getName();
      String namespace = getNamespace(name);
      // skip unused symbols, symbols already emitted or symbols whose namespace is emitted
      // (unless the symbols have their own provide).
      if (!typesUsed.contains(name)
          || typesEmitted.contains(name)
          || (!transitiveProvides.contains(name) && typesEmitted.contains(namespace))) {
        continue;
      }

      // skip provided symbols (as default or in an namespace).
      if (provides.contains(name)
          || (!transitiveProvides.contains(name) && provides.contains(namespace))) {
        continue;
      }
      // skip emit for provided inner symbols too as they are covered by the walkInnerSymbols
      // pass.
      if (isInnerSymbol(provides, name)) {
        continue;
      }

      // Skip extern symbols (they have a separate pass) and skip built-ins.
      // Built-ins can be indentified by having null as input file.
      CompilerInput symbolInput = this.compiler.getInput(new InputId(symbol.getInputName()));
      if (symbolInput == null || symbolInput.isExtern()) continue;

      // Type inference sometimes creates symbols for undeclared qualified names when narrowing
      // their type in a flow scope. These should not be emitted and can be detected by noticing
      // the declaration 'node' is not used as an lvalue.
      Node nameNode = symbol.getNode();
      if (!NodeUtil.isLValue(nameNode) && !nameNode.getParent().isExprResult()) {
        continue;
      }

      // A symbol with a name, but a null type is likely a typedef. DeclareNamespace cannot handle
      // this scenario, but declareTypedefNamespace
      if (symbol.getType() == null) {
        JSType typedef = compiler.getTypeRegistry().getGlobalType(name);
        if (typedef != null) {
          declareTypedefNamespace(symbol, typedef, Collections.emptySet());
          typesEmitted.add(name);
        }
        continue;
      }

      declareNamespace(
          namespace,
          symbol,
          name,
          /* isDefault */ true,
          Collections.<String>emptySet(),
          /* isExtern */ false);
      typesEmitted.add(name);
    }
    // if no new types seen, safely break out.
    if (typesUsed.size() == typesUsedCount) break;
    maxTypeUsedDepth--;
  }
}
 
Example #23
Source File: MinimizeExitPoints.java    From astor with GNU General Public License v2.0 4 votes vote down vote up
/**
 * Look for exits (returns, breaks, or continues, depending on the context) at
 * the end of a block and removes them by moving the if node's siblings,
 * if any, into the opposite condition block.
 *
 * @param srcBlock The block to inspect.
 * @param destBlock The block to move sibling nodes into.
 * @param ifNode The if node to work with.
 * @param exitType The type of exit to look for.
 * @param labelName The name associated with the exit, if any.
 * @nullable labelName null for anything excepted for named-break associated
 *           with a label.
 */
private void tryMinimizeIfBlockExits(Node srcBlock, Node destBlock,
    Node ifNode, int exitType, String labelName) {
  Node exitNodeParent = null;
  Node exitNode = null;

  // Pick an exit node candidate.
  if (srcBlock.isBlock()) {
    if (!srcBlock.hasChildren()) {
      return;
    }
    exitNodeParent = srcBlock;
    exitNode = exitNodeParent.getLastChild();
  } else {
    // Just a single statement, if it isn't an exit bail.
    exitNodeParent = ifNode;
    exitNode = srcBlock;
  }

  // Verify the candidate.
  if (!matchingExitNode(exitNode, exitType, labelName)) {
    return;
  }

  // Take case of the if nodes siblings, if any.
  if (ifNode.getNext() != null) {
    // Move siblings of the if block into the opposite
    // logic block of the exit.
    Node newDestBlock = IR.block().srcref(ifNode);
    if (destBlock == null) {
      // Only possible if this is the false block.
      ifNode.addChildToBack(newDestBlock);
    } else if (destBlock.isEmpty()) {
      // Use the new block.
      ifNode.replaceChild(destBlock, newDestBlock);
    } else if (destBlock.isBlock()) {
      // Reuse the existing block.
      newDestBlock = destBlock;
    } else {
      // Add the existing statement to the new block.
      ifNode.replaceChild(destBlock, newDestBlock);
      newDestBlock.addChildToBack(destBlock);
    }

    // Move all the if node's following siblings.
    moveAllFollowing(ifNode, ifNode.getParent(), newDestBlock);
  }

  // Get rid of the "exit", replace with an empty item if needed.
  NodeUtil.removeChild(exitNodeParent, exitNode);

  compiler.reportCodeChange();
}