/** Checks if the given function matches the criteria for an inlinable function. */
  private boolean isCandidateFunction(Function fn) {
    // Don't inline exported functions.
    String fnName = fn.getName();
    if (compiler.getCodingConvention().isExported(fnName)) {
      // TODO(johnlenz): Should we allow internal references to be inlined?
      // An exported name can be replaced externally, any inlined instance
      // would not reflect this change.
      // To allow inlining we need to be able to distinguish between exports
      // that are used in a read-only fashion and those that can be replaced
      // by external definitions.
      return false;
    }

    // Don't inline this special function
    if (RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(fnName)) {
      return false;
    }

    // Don't inline if we are specializing and the function can't be fixed up
    if (specializationState != null
        && !specializationState.canFixupFunction(fn.getFunctionNode())) {
      return false;
    }

    Node fnNode = fn.getFunctionNode();
    return injector.doesFunctionMeetMinimumRequirements(fnName, fnNode);
  }
  @Override
  public void process(Node externs, Node root) {
    Preconditions.checkState(compiler.getLifeCycleStage().isNormalized());

    NodeTraversal.traverseEs6(compiler, root, new FindCandidateFunctions());
    if (fns.isEmpty()) {
      return; // Nothing left to do.
    }
    NodeTraversal.traverseEs6(compiler, root, new FindCandidatesReferences(fns, anonFns));
    trimCandidatesNotMeetingMinimumRequirements();
    if (fns.isEmpty()) {
      return; // Nothing left to do.
    }

    // Store the set of function names eligible for inlining and use this to
    // prevent function names from being moved into temporaries during
    // expression decomposition. If this movement were allowed it would prevent
    // the Inline callback from finding the function calls.
    //
    // This pass already assumes these are constants, so this is safe for anyone
    // using function inlining.
    //
    Set<String> fnNames = new HashSet<>(fns.keySet());
    injector.setKnownConstants(fnNames);

    trimCandidatesUsingOnCost();
    if (fns.isEmpty()) {
      return; // Nothing left to do.
    }
    resolveInlineConflicts();
    decomposeExpressions();
    NodeTraversal.traverseEs6(compiler, root, new CallVisitor(fns, anonFns, new Inline(injector)));

    removeInlinedFunctions();
  }
 /** @return Whether inlining the function reduces code size. */
 private boolean inliningLowersCost(FunctionState fs) {
   return injector.inliningLowersCost(
       fs.getModule(),
       fs.getFn().getFunctionNode(),
       fs.getReferences(),
       fs.getNamesToAlias(),
       fs.canRemove(),
       fs.getReferencesThis());
 }
 /**
  * For any call-site that needs it, prepare the call-site for inlining by rewriting the containing
  * expression.
  */
 private void decomposeExpressions() {
   for (FunctionState fs : fns.values()) {
     if (fs.canInline()) {
       for (Reference ref : fs.getReferences()) {
         if (ref.requiresDecomposition) {
           injector.maybePrepareCall(ref);
         }
       }
     }
   }
 }
    /** 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());
    }
  /**
   * Updates the FunctionState object for the given function. Checks if the given function matches
   * the criteria for an inlinable function.
   */
  private void maybeAddFunction(Function fn, JSModule module) {
    String name = fn.getName();
    FunctionState fs = getOrCreateFunctionState(name);

    // TODO(johnlenz): Maybe "smarten" FunctionState by adding this logic
    // to it?

    // If the function has multiple definitions, don't inline it.
    if (fs.hasExistingFunctionDefinition()) {
      fs.setInline(false);
      return;
    }
    Node fnNode = fn.getFunctionNode();
    if (enforceMaxSizeAfterInlining
        && !isAlwaysInlinable(fnNode)
        && maxSizeAfterInlining <= NodeUtil.countAstSizeUpToLimit(fnNode, maxSizeAfterInlining)) {
      fs.setInline(false);
      return;
    }
    // verify the function hasn't already been marked as "don't inline"
    if (fs.canInline()) {
      // store it for use when inlining.
      fs.setFn(fn);
      if (FunctionInjector.isDirectCallNodeReplacementPossible(fn.getFunctionNode())) {
        fs.inlineDirectly(true);
      }

      // verify the function meets all the requirements.
      // TODO(johnlenz): Minimum requirement checks are about 5% of the
      // run-time cost of this pass.
      if (!isCandidateFunction(fn)) {
        // It doesn't meet the requirements.
        fs.setInline(false);
      }

      // Set the module and gather names that need temporaries.
      if (fs.canInline()) {
        fs.setModule(module);

        Set<String> namesToAlias = FunctionArgumentInjector.findModifiedParameters(fnNode);
        if (!namesToAlias.isEmpty()) {
          fs.inlineDirectly(false);
          fs.setNamesToAlias(namesToAlias);
        }

        Node block = NodeUtil.getFunctionBody(fnNode);
        if (NodeUtil.referencesThis(block)) {
          fs.setReferencesThis(true);
        }

        if (NodeUtil.containsFunction(block)) {
          fs.setHasInnerFunctions(true);
          // If there are inner functions, we can inline into global scope
          // if there are no local vars or named functions.
          // TODO(johnlenz): this can be improved by looking at the possible
          // values for locals.  If there are simple values, or constants
          // we could still inline.
          if (!assumeMinimumCapture && hasLocalNames(fnNode)) {
            fs.setInline(false);
          }
        }
      }

      // Check if block inlining is allowed.
      if (fs.canInline() && !fs.canInlineDirectly()) {
        if (!blockFunctionInliningEnabled) {
          fs.setInline(false);
        }
      }
    }
  }