private void splitFiles(String[] input) {
    Compiler compiler = new Compiler();
    List<SourceFile> files = Lists.newArrayList();

    for (int i = 0; i < input.length; i++) {
      files.add(SourceFile.fromCode("file" + i, input[i]));
    }

    compiler.init(ImmutableList.<SourceFile>of(), files, new CompilerOptions());
    compiler.parse();
    Node original = compiler.getRoot();
    Node root = original.cloneTree();

    AstParallelizer parallelizer = AstParallelizer.createNewFileLevelAstParallelizer(root);
    List<Node> forest = parallelizer.split();
    assertEquals(input.length, forest.size());
    int i = 0;
    for (Node n : forest) {
      Node tree = compiler.parseTestCode(input[i++]);
      assertEquals(compiler.toSource(tree), compiler.toSource(n));
    }

    parallelizer.join();
    assertTrue(original.isEquivalentTo(root));
  }
    /** 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());
    }
  /**
   * Splits at function level with {@link AstParallelizer#split()}, verify the output matches what
   * is expected and then verify {@link AstParallelizer#join()} can reverse the whole process.
   */
  private void splitFunctions(String input, String... output) {
    Compiler compiler = new Compiler();
    Node original = compiler.parseTestCode(input);
    Node root = original.cloneTree();
    AstParallelizer parallelizer =
        AstParallelizer.createNewFunctionLevelAstParallelizer(root, true);
    List<Node> forest = parallelizer.split();
    assertEquals(output.length, forest.size());
    int i = 0;
    for (Node n : forest) {
      Node tree = compiler.parseTestCode(output[i++]);
      assertEquals(compiler.toSource(tree), compiler.toSource(n));
    }

    parallelizer.join();
    assertTrue(original.isEquivalentTo(root));
  }
  private void tryConvertToNumber(Node n) {
    switch (n.getType()) {
      case NUMBER:
        // Nothing to do
        return;
      case AND:
      case OR:
      case COMMA:
        tryConvertToNumber(n.getLastChild());
        return;
      case HOOK:
        tryConvertToNumber(n.getSecondChild());
        tryConvertToNumber(n.getLastChild());
        return;
      case NAME:
        if (!NodeUtil.isUndefined(n)) {
          return;
        }
        break;
    }

    Double result = NodeUtil.getNumberValue(n, shouldUseTypes);
    if (result == null) {
      return;
    }

    double value = result;

    Node replacement = NodeUtil.numberNode(value, n);
    if (replacement.isEquivalentTo(n)) {
      return;
    }

    n.getParent().replaceChild(n, replacement);
    reportCodeChange();
  }
  /**
   * Returns whether two nodes are equivalent, taking into account the template parameters that were
   * provided to this matcher. If the template comparison node is a parameter node, then only the
   * types of the node must match. Otherwise, the node must be equal and the child nodes must be
   * equivalent according to the same function. This differs from the built in Node equivalence
   * function with the special comparison.
   */
  private boolean matchesNode(Node template, Node ast) {
    if (isTemplateParameterNode(template)) {
      int paramIndex = (int) (template.getDouble());
      Node previousMatch = paramNodeMatches.get(paramIndex);
      if (previousMatch != null) {
        // If this named node has already been matched against, make sure all
        // subsequent usages of the same named node are equivalent.
        return ast.isEquivalentTo(previousMatch);
      }

      // Only the types need to match for the template parameters, which allows
      // the template function to express arbitrary expressions.
      JSType templateType = template.getJSType();

      Preconditions.checkNotNull(templateType, "null template parameter type.");

      // TODO(johnlenz): We shouldn't spend time checking template whose
      // types whose definitions aren't included (NoResolvedType). Alternately
      // we should treat them as "unknown" and perform loose matches.
      if (templateType.isNoResolvedType()) {
        return false;
      }

      MatchResult matchResult = typeMatchingStrategy.match(templateType, ast.getJSType());
      isLooseMatch = matchResult.isLooseMatch();
      boolean isMatch = matchResult.isMatch();
      if (isMatch && previousMatch == null) {
        paramNodeMatches.set(paramIndex, ast);
      }
      return isMatch;
    } else if (isTemplateLocalNameNode(template)) {
      // If this template name node was already matched against, then make sure
      // all subsequent usages of the same template name node are equivalent in
      // the matched code.
      // For example, this code will handle the case:
      // function template() {
      //   var a = 'str';
      //   fn(a);
      // }
      //
      // will only match test code:
      //   var b = 'str';
      //   fn(b);
      //
      // but it will not match:
      //   var b = 'str';
      //   fn('str');
      int paramIndex = (int) (template.getDouble());
      boolean previouslyMatched = this.localVarMatches.get(paramIndex) != null;
      if (previouslyMatched) {
        // If this named node has already been matched against, make sure all
        // subsequent usages of the same named node are equivalent.
        return ast.getString().equals(this.localVarMatches.get(paramIndex));
      } else {
        this.localVarMatches.set(paramIndex, ast.getString());
      }
    }

    // Template and AST shape has already been checked, but continue look for
    // other template variables (parameters and locals) that must be checked.
    Node templateChild = template.getFirstChild();
    Node astChild = ast.getFirstChild();
    while (templateChild != null) {
      if (!matchesNode(templateChild, astChild)) {
        return false;
      }
      templateChild = templateChild.getNext();
      astChild = astChild.getNext();
    }

    return true;
  }
  /**
   * Verifies that the compiler pass's JS output matches the expected output and (optionally) that
   * an expected warning is issued. Or, if an error is expected, this method just verifies that the
   * error is encountered.
   *
   * @param compiler A compiler that has been initialized via {@link Compiler#init}
   * @param expected Expected output, or null if an error is expected
   * @param error Expected error, or null if no error is expected
   * @param warning Expected warning, or null if no warning is expected
   * @param description The description of the expected warning, or null if no warning is expected
   *     or if the warning's description should not be examined
   */
  private void test(
      Compiler compiler,
      List<SourceFile> expected,
      DiagnosticType error,
      DiagnosticType warning,
      String description) {
    RecentChange recentChange = new RecentChange();
    compiler.addChangeHandler(recentChange);

    Node root = compiler.parseInputs();
    assertNotNull("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root);
    if (!expectParseWarningsThisTest) {
      assertEquals(
          "Unexpected parse warnings(s): " + Joiner.on("\n").join(compiler.getWarnings()),
          0,
          compiler.getWarnings().length);
    }

    if (astValidationEnabled) {
      (new AstValidator(compiler)).validateRoot(root);
    }
    Node externsRoot = root.getFirstChild();
    Node mainRoot = root.getLastChild();

    // Save the tree for later comparison.
    Node rootClone = root.cloneTree();
    Node externsRootClone = rootClone.getFirstChild();
    Node mainRootClone = rootClone.getLastChild();
    Map<Node, Node> mtoc = NodeUtil.mapMainToClone(mainRoot, mainRootClone);

    int numRepetitions = getNumRepetitions();
    ErrorManager[] errorManagers = new ErrorManager[numRepetitions];
    int aggregateWarningCount = 0;
    List<JSError> aggregateWarnings = Lists.newArrayList();
    boolean hasCodeChanged = false;

    assertFalse("Code should not change before processing", recentChange.hasCodeChanged());

    for (int i = 0; i < numRepetitions; ++i) {
      if (compiler.getErrorCount() == 0) {
        errorManagers[i] = new BlackHoleErrorManager(compiler);

        // Only run process closure primitives once, if asked.
        if (closurePassEnabled && i == 0) {
          recentChange.reset();
          new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR, false)
              .process(null, mainRoot);
          hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
        }

        // Only run the type checking pass once, if asked.
        // Running it twice can cause unpredictable behavior because duplicate
        // objects for the same type are created, and the type system
        // uses reference equality to compare many types.
        if (!runTypeCheckAfterProcessing && typeCheckEnabled && i == 0) {
          TypeCheck check = createTypeCheck(compiler, typeCheckLevel);
          check.processForTesting(externsRoot, mainRoot);
        }

        // Only run the normalize pass once, if asked.
        if (normalizeEnabled && i == 0) {
          normalizeActualCode(compiler, externsRoot, mainRoot);
        }

        if (enableInferConsts && i == 0) {
          new InferConsts(compiler).process(externsRoot, mainRoot);
        }

        if (computeSideEffects && i == 0) {
          PureFunctionIdentifier.Driver mark =
              new PureFunctionIdentifier.Driver(compiler, null, false);
          mark.process(externsRoot, mainRoot);
        }

        if (markNoSideEffects && i == 0) {
          MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler);
          mark.process(externsRoot, mainRoot);
        }

        if (gatherExternPropertiesEnabled && i == 0) {
          (new GatherExternProperties(compiler)).process(externsRoot, mainRoot);
        }

        recentChange.reset();

        getProcessor(compiler).process(externsRoot, mainRoot);
        if (astValidationEnabled) {
          (new AstValidator(compiler)).validateRoot(root);
        }
        if (checkLineNumbers) {
          (new LineNumberCheck(compiler)).process(externsRoot, mainRoot);
        }

        if (runTypeCheckAfterProcessing && typeCheckEnabled && i == 0) {
          TypeCheck check = createTypeCheck(compiler, typeCheckLevel);
          check.processForTesting(externsRoot, mainRoot);
        }

        hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
        aggregateWarningCount += errorManagers[i].getWarningCount();
        Collections.addAll(aggregateWarnings, compiler.getWarnings());

        if (normalizeEnabled) {
          boolean verifyDeclaredConstants = true;
          new Normalize.VerifyConstants(compiler, verifyDeclaredConstants)
              .process(externsRoot, mainRoot);
        }
      }
    }

    if (error == null) {
      assertEquals(
          "Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()),
          0,
          compiler.getErrorCount());

      // Verify the symbol table.
      ErrorManager symbolTableErrorManager = new BlackHoleErrorManager(compiler);
      Node expectedRoot = null;
      if (expected != null) {
        expectedRoot = parseExpectedJs(expected);
        expectedRoot.detachFromParent();
      }

      JSError[] stErrors = symbolTableErrorManager.getErrors();
      if (expectedSymbolTableError != null) {
        assertEquals("There should be one error.", 1, stErrors.length);
        assertEquals(expectedSymbolTableError, stErrors[0].getType());
      } else {
        assertEquals(
            "Unexpected symbol table error(s): " + Joiner.on("\n").join(stErrors),
            0,
            stErrors.length);
      }

      if (warning == null) {
        assertEquals(
            "Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings),
            0,
            aggregateWarningCount);
      } else {
        assertEquals(
            "There should be one warning, repeated "
                + numRepetitions
                + " time(s). Warnings: "
                + aggregateWarnings,
            numRepetitions,
            aggregateWarningCount);
        for (int i = 0; i < numRepetitions; ++i) {
          JSError[] warnings = errorManagers[i].getWarnings();
          JSError actual = warnings[0];
          assertEquals(warning, actual.getType());

          // Make sure that source information is always provided.
          if (!allowSourcelessWarnings) {
            assertTrue(
                "Missing source file name in warning",
                actual.sourceName != null && !actual.sourceName.isEmpty());
            assertTrue("Missing line number in warning", -1 != actual.lineNumber);
            assertTrue("Missing char number in warning", -1 != actual.getCharno());
          }

          if (description != null) {
            assertEquals(description, actual.description);
          }
        }
      }

      // If we ran normalize on the AST, we must also run normalize on the
      // clone before checking for changes.
      if (normalizeEnabled) {
        normalizeActualCode(compiler, externsRootClone, mainRootClone);
      }

      boolean codeChange = !mainRootClone.isEquivalentTo(mainRoot);
      boolean externsChange = !externsRootClone.isEquivalentTo(externsRoot);

      // Generally, externs should not be changed by the compiler passes.
      if (externsChange && !allowExternsChanges) {
        String explanation = externsRootClone.checkTreeEquals(externsRoot);
        fail(
            "Unexpected changes to externs"
                + "\nExpected: "
                + compiler.toSource(externsRootClone)
                + "\nResult:   "
                + compiler.toSource(externsRoot)
                + "\n"
                + explanation);
      }

      if (!codeChange && !externsChange) {
        assertFalse(
            "compiler.reportCodeChange() was called " + "even though nothing changed",
            hasCodeChanged);
      } else {
        assertTrue(
            "compiler.reportCodeChange() should have been called."
                + "\nOriginal: "
                + mainRootClone.toStringTree()
                + "\nNew: "
                + mainRoot.toStringTree(),
            hasCodeChanged);
      }

      // Check correctness of the changed-scopes-only traversal
      NodeUtil.verifyScopeChanges(mtoc, mainRoot, false, compiler);

      if (expected != null) {
        if (compareAsTree) {
          String explanation;
          if (compareJsDoc) {
            explanation = expectedRoot.checkTreeEqualsIncludingJsDoc(mainRoot);
          } else {
            explanation = expectedRoot.checkTreeEquals(mainRoot);
          }
          assertNull(
              "\nExpected: "
                  + compiler.toSource(expectedRoot)
                  + "\nResult:   "
                  + compiler.toSource(mainRoot)
                  + "\n"
                  + explanation,
              explanation);
        } else if (expected != null) {
          String[] expectedSources = new String[expected.size()];
          for (int i = 0; i < expected.size(); ++i) {
            try {
              expectedSources[i] = expected.get(i).getCode();
            } catch (IOException e) {
              throw new RuntimeException("failed to get source code", e);
            }
          }
          assertEquals(Joiner.on("").join(expectedSources), compiler.toSource(mainRoot));
        }
      }

      // Verify normalization is not invalidated.
      Node normalizeCheckRootClone = root.cloneTree();
      Node normalizeCheckExternsRootClone = normalizeCheckRootClone.getFirstChild();
      Node normalizeCheckMainRootClone = normalizeCheckRootClone.getLastChild();
      new PrepareAst(compiler).process(normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
      String explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
      assertNull(
          "Node structure normalization invalidated."
              + "\nExpected: "
              + compiler.toSource(normalizeCheckMainRootClone)
              + "\nResult:   "
              + compiler.toSource(mainRoot)
              + "\n"
              + explanation,
          explanation);

      // TODO(johnlenz): enable this for most test cases.
      // Currently, this invalidates test for while-loops, for-loop
      // initializers, and other naming.  However, a set of code
      // (Closure primitive rewrites, etc) runs before the Normalize pass,
      // so this can't be force on everywhere.
      if (normalizeEnabled) {
        new Normalize(compiler, true)
            .process(normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
        explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
        assertNull(
            "Normalization invalidated."
                + "\nExpected: "
                + compiler.toSource(normalizeCheckMainRootClone)
                + "\nResult:   "
                + compiler.toSource(mainRoot)
                + "\n"
                + explanation,
            explanation);
      }
    } else {
      String errors = "";
      for (JSError actualError : compiler.getErrors()) {
        errors += actualError.description + "\n";
      }
      assertEquals("There should be one error. " + errors, 1, compiler.getErrorCount());
      assertEquals(errors, error, compiler.getErrors()[0].getType());

      if (warning != null) {
        String warnings = "";
        for (JSError actualError : compiler.getWarnings()) {
          warnings += actualError.description + "\n";
        }
        assertEquals("There should be one warning. " + warnings, 1, compiler.getWarningCount());
        assertEquals(warnings, warning, compiler.getWarnings()[0].getType());
      }
    }
  }