/*
 * Copyright 2009 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.PassFactory.createEmptyPass;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES2018;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES5;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES6;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES8;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES8_MODULES;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES_NEXT;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.TYPESCRIPT;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.TYPE_CHECK_SUPPORTED;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.javascript.jscomp.AbstractCompiler.LifeCycleStage;
import com.google.javascript.jscomp.CompilerOptions.ExtractPrototypeMemberDeclarationsMode;
import com.google.javascript.jscomp.CompilerOptions.Reach;
import com.google.javascript.jscomp.CoverageInstrumentationPass.CoverageReach;
import com.google.javascript.jscomp.CoverageInstrumentationPass.InstrumentOption;
import com.google.javascript.jscomp.ExtractPrototypeMemberDeclarations.Pattern;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.jscomp.PassFactory.HotSwapPassFactory;
import com.google.javascript.jscomp.ijs.ConvertToTypedInterface;
import com.google.javascript.jscomp.lint.CheckArrayWithGoogObject;
import com.google.javascript.jscomp.lint.CheckDuplicateCase;
import com.google.javascript.jscomp.lint.CheckEmptyStatements;
import com.google.javascript.jscomp.lint.CheckEnums;
import com.google.javascript.jscomp.lint.CheckEs6ModuleFileStructure;
import com.google.javascript.jscomp.lint.CheckEs6Modules;
import com.google.javascript.jscomp.lint.CheckInterfaces;
import com.google.javascript.jscomp.lint.CheckJSDocStyle;
import com.google.javascript.jscomp.lint.CheckMissingSemicolon;
import com.google.javascript.jscomp.lint.CheckNoMutatedEs6Exports;
import com.google.javascript.jscomp.lint.CheckNullabilityModifiers;
import com.google.javascript.jscomp.lint.CheckNullableReturn;
import com.google.javascript.jscomp.lint.CheckPrimitiveAsObject;
import com.google.javascript.jscomp.lint.CheckPrototypeProperties;
import com.google.javascript.jscomp.lint.CheckRequiresAndProvidesSorted;
import com.google.javascript.jscomp.lint.CheckUnusedLabels;
import com.google.javascript.jscomp.lint.CheckUselessBlocks;
import com.google.javascript.jscomp.parsing.ParserRunner;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Pass factories and meta-data for native JSCompiler passes.
 *
 * @author nicksantos@google.com (Nick Santos)
 *
 * Apache Royale copied DefaultPassConfig and modified it to replace
 * two passes with "WithModuleSupport" passes.
 * 
 * NOTE(dimvar): this needs some non-trivial refactoring. The pass config should
 * use as little state as possible. The recommended way for a pass to leave
 * behind some state for a subsequent pass is through the compiler object.
 * Any other state remaining here should only be used when the pass config is
 * creating the list of checks and optimizations, not after passes have started
 * executing. For example, the field namespaceForChecks should be in Compiler.
 */
public final class RoyaleClosurePassConfig extends PassConfig {

  /* For the --mark-as-compiled pass */
  private static final String COMPILED_CONSTANT_NAME = "COMPILED";

  /* Constant name for Closure's locale */
  private static final String CLOSURE_LOCALE_CONSTANT_NAME = "goog.LOCALE";

  static final DiagnosticType CANNOT_USE_PROTOTYPE_AND_VAR =
      DiagnosticType.error("JSC_CANNOT_USE_PROTOTYPE_AND_VAR",
          "Rename prototypes and inline variables cannot be used together.");

  // Miscellaneous errors.
  private static final java.util.regex.Pattern GLOBAL_SYMBOL_NAMESPACE_PATTERN =
    java.util.regex.Pattern.compile("^[a-zA-Z0-9$_]+$");

  /**
   * A global namespace to share across checking passes.
   */
  private transient GlobalNamespace namespaceForChecks = null;

  /** A symbol table for registering references that get removed during preprocessing. */
  private final transient PreprocessorSymbolTable.CachedInstanceFactory
      preprocessorSymbolTableFactory;

  /**
   * Global state necessary for doing hotswap recompilation of files with references to
   * processed goog.modules.
   */
  private transient ClosureRewriteModule.GlobalRewriteState moduleRewriteState = null;

  /**
   * Whether to protect "hidden" side-effects.
   * @see CheckSideEffects
   */
  private final boolean protectHiddenSideEffects;
  
  /** name of a source file we can use to inject other code */
  private String sourceFileName;

  /** file of already renamed vars */
  private File varRenameMapFile;

  private Set<String> propertyNamesToKeep;
  
  public RoyaleClosurePassConfig(CompilerOptions options, String sourceFileName, File varRenameMapFile, Set<String> propertyNamesToKeep) {
    super(options);

    // The current approach to protecting "hidden" side-effects is to
    // wrap them in a function call that is stripped later, this shouldn't
    // be done in IDE mode where AST changes may be unexpected.
    protectHiddenSideEffects = options != null && options.shouldProtectHiddenSideEffects();
    preprocessorSymbolTableFactory = new PreprocessorSymbolTable.CachedInstanceFactory();
    this.varRenameMapFile = varRenameMapFile;
    this.sourceFileName = sourceFileName;
    this.propertyNamesToKeep = propertyNamesToKeep;
  }

  GlobalNamespace getGlobalNamespace() {
    return namespaceForChecks;
  }

  @Nullable
  PreprocessorSymbolTable getPreprocessorSymbolTable() {
    return preprocessorSymbolTableFactory.getInstanceOrNull();
  }

  void maybeInitializeModuleRewriteState() {
    if (options.allowsHotswapReplaceScript() && this.moduleRewriteState == null) {
      this.moduleRewriteState = new ClosureRewriteModule.GlobalRewriteState();
    }
  }

  @Override
  protected List<PassFactory> getTranspileOnlyPasses() {
    List<PassFactory> passes = new ArrayList<PassFactory>();

    if (options.needsTranspilationFrom(TYPESCRIPT)) {
      passes.add(convertEs6TypedToEs6);
    }

    passes.add(checkVariableReferencesForTranspileOnly);
    passes.add(gatherModuleMetadataPass);

    if (options.getLanguageIn().toFeatureSet().has(FeatureSet.Feature.MODULES)) {
      passes.add(rewriteGoogJsImports);
      switch (options.getEs6ModuleTranspilation()) {
        case COMPILE:
          TranspilationPasses.addEs6ModulePass(passes, preprocessorSymbolTableFactory);
          break;
        case TO_COMMON_JS_LIKE_MODULES:
          TranspilationPasses.addEs6ModuleToCjsPass(passes);
          break;
        case RELATIVIZE_IMPORT_PATHS:
          TranspilationPasses.addEs6RewriteImportPathPass(passes);
          break;
        case NONE:
          // nothing
          break;
      }
    }

    passes.add(checkSuper);

    // It's important that the Dart super accessors pass run *before* es6ConvertSuper,
    // which is a "late" ES6 pass. This is enforced in the assertValidOrder method.
    if (options.dartPass && options.needsTranspilationFrom(ES6)) {
      passes.add(dartSuperAccessorsPass);
    }

    TranspilationPasses.addPreTypecheckTranspilationPasses(passes, options);

    TranspilationPasses.addPostCheckTranspilationPasses(passes, options);

    if (options.needsTranspilationFrom(ES6)) {
      if (options.rewritePolyfills) {
        TranspilationPasses.addRewritePolyfillPass(passes);
      }
    }

    passes.add(injectRuntimeLibraries);

    assertAllOneTimePasses(passes);
    assertValidOrderForChecks(passes);
    return passes;
  }

  @Override
  protected List<PassFactory> getWhitespaceOnlyPasses() {
    List<PassFactory> passes = new ArrayList<PassFactory>();

    if (options.processCommonJSModules) {
      passes.add(rewriteCommonJsModules);
    } else if (options.getLanguageIn().toFeatureSet().has(FeatureSet.Feature.MODULES)) {
      passes.add(rewriteScriptsToEs6Modules);
    }

    if (options.wrapGoogModulesForWhitespaceOnly) {
      passes.add(whitespaceWrapGoogModules);
    }
    return passes;
  }

  private void addTypeCheckerPasses(List<PassFactory> checks, CompilerOptions options) {
    if (!options.allowsHotswapReplaceScript()) {
      checks.add(inlineTypeAliases);
    }
    if (options.checkTypes || options.inferTypes) {
      checks.add(resolveTypes);
      checks.add(inferTypes);
      if (options.checkTypes) {
        checks.add(checkTypes);
      } else {
        checks.add(inferJsDocInfo);
      }

      // We assume that only clients who are going to re-compile, or do in-depth static analysis,
      // will need the typed scope creator after the compile job.
      if (!options.preservesDetailedSourceInfo() && !options.allowsHotswapReplaceScript()) {
        checks.add(clearTypedScopePass);
      }
    }
  }

  @Override
  protected List<PassFactory> getChecks() {
    List<PassFactory> checks = new ArrayList<PassFactory>();

    checks.add(gatherGettersAndSetters);

    if (options.shouldGenerateTypedExterns()) {
      checks.add(closureGoogScopeAliases);
      checks.add(closureRewriteClass);
      checks.add(generateIjs);
      checks.add(whitespaceWrapGoogModules);
      return checks;
    }

    checks.add(createEmptyPass("beforeStandardChecks"));

    if (options.processCommonJSModules) {
      checks.add(rewriteCommonJsModules);
    } else if (options.getLanguageIn().toFeatureSet().has(FeatureSet.Feature.MODULES)) {
      checks.add(rewriteScriptsToEs6Modules);
    }

    // Note: ChromePass can rewrite invalid @type annotations into valid ones, so should run before
    // JsDoc checks.
    if (options.isChromePassEnabled()) {
      checks.add(chromePass);
    }

    // Verify JsDoc annotations and check ES6 modules
    checks.add(checkJsDocAndEs6Modules);

    if (options.needsTranspilationFrom(TYPESCRIPT)) {
      checks.add(convertEs6TypedToEs6);
    }

    checks.add(gatherModuleMetadataPass);

    if (options.enables(DiagnosticGroups.LINT_CHECKS)) {
      checks.add(lintChecks);
    }

    if (options.closurePass && options.enables(DiagnosticGroups.LINT_CHECKS)) {
      checks.add(checkRequiresAndProvidesSorted);
    }

    if (options.enables(DiagnosticGroups.MISSING_REQUIRE)
        || options.enables(DiagnosticGroups.STRICT_MISSING_REQUIRE)
        || options.enables(DiagnosticGroups.EXTRA_REQUIRE)) {
      checks.add(checkRequires);
    }

    checks.add(checkVariableReferences);

    if (options.getLanguageIn().toFeatureSet().has(FeatureSet.Feature.MODULES)) {
      checks.add(rewriteGoogJsImports);
      TranspilationPasses.addEs6ModulePass(checks, preprocessorSymbolTableFactory);
    }

    checks.add(checkStrictMode);

    if (options.closurePass) {
      checks.add(closureCheckModule);
      checks.add(closureRewriteModule);
    }

    if (options.declaredGlobalExternsOnWindow) {
      checks.add(declaredGlobalExternsOnWindow);
    }

    checks.add(checkSuper);

    if (options.closurePass) {
      checks.add(closureGoogScopeAliases);
      checks.add(closureRewriteClass);
    }

    checks.add(checkSideEffects);

    if (options.enables(DiagnosticGroups.MISSING_PROVIDE)) {
      checks.add(checkProvides);
    }

    if (options.angularPass) {
      checks.add(angularPass);
    }

    if (propertyNamesToKeep != null && propertyNamesToKeep.size() > 0) {
      checks.add(keepPropertyNamesPass);
    }

    if (!options.generateExportsAfterTypeChecking && options.generateExports) {
      checks.add(generateExports);
    }

    if (options.exportTestFunctions) {
      checks.add(exportTestFunctions);
    }

    if (options.closurePass) {
      checks.add(closurePrimitives);
    }

    // It's important that the PolymerPass run *after* the ClosurePrimitives and ChromePass rewrites
    // and *before* the suspicious code checks. This is enforced in the assertValidOrder method.
    if (options.polymerVersion != null) {
      checks.add(polymerPass);
    }

    if (options.checkSuspiciousCode
        || options.enables(DiagnosticGroups.GLOBAL_THIS)
        || options.enables(DiagnosticGroups.DEBUGGER_STATEMENT_PRESENT)) {
      checks.add(suspiciousCode);
    }

    if (options.closurePass && options.checkMissingGetCssNameLevel.isOn()) {
      checks.add(closureCheckGetCssName);
    }

    if (options.syntheticBlockStartMarker != null) {
      // This pass must run before the first fold constants pass.
      checks.add(createSyntheticBlocks);
    }

    checks.add(checkVars);

    if (options.inferConsts) {
      checks.add(inferConsts);
    }

    if (options.computeFunctionSideEffects) {
      checks.add(checkRegExp);
    }

    // This pass should run before types are assigned.
    if (options.processObjectPropertyString) {
      checks.add(objectPropertyStringPreprocess);
    }

    // It's important that the Dart super accessors pass run *before* es6ConvertSuper,
    // which is a "late" ES6 pass. This is enforced in the assertValidOrder method.
    if (options.dartPass && !options.getOutputFeatureSet().contains(ES6)) {
      checks.add(dartSuperAccessorsPass);
    }

    // Passes running before this point should expect to see language features up to ES_2017.
    checks.add(createEmptyPass(PassNames.BEFORE_ES_2017_TRANSPILATION));

    TranspilationPasses.addPreTypecheckTranspilationPasses(checks, options);

    if (options.rewritePolyfills && !options.checksOnly) {
      TranspilationPasses.addRewritePolyfillPass(checks);
    }

    checks.add(injectRuntimeLibraries);
    checks.add(createEmptyPass(PassNames.BEFORE_TYPE_CHECKING));

    addTypeCheckerPasses(checks, options);

    if (options.j2clPassMode.shouldAddJ2clPasses()) {
      checks.add(j2clSourceFileChecker);
    }

    if (!options.disables(DiagnosticGroups.CHECK_USELESS_CODE)
        || !options.disables(DiagnosticGroups.MISSING_RETURN)) {
      checks.add(checkControlFlow);
    }
    
    /* The AS compiler should have checked the same thing
     * that CheckAccessControls checks.  CheckAccessControls
     * also outputs false positives when compiling modules.

    // CheckAccessControls only works if check types is on.
    if (options.isTypecheckingEnabled()
        && (!options.disables(DiagnosticGroups.ACCESS_CONTROLS)
            || options.enables(DiagnosticGroups.CONSTANT_PROPERTY))) {
      checks.add(checkAccessControls);
    }
     */

    /* The AS compiler should have checked the same thing
     * that CheckConsts checks.  CheckConsts
     * also outputs false positives when compiling modules.
    checks.add(checkConsts);
     */

    // Analyzer checks must be run after typechecking.
    if (options.enables(DiagnosticGroups.ANALYZER_CHECKS) && options.isTypecheckingEnabled()) {
      checks.add(analyzerChecks);
    }

    if (options.checkGlobalNamesLevel.isOn()) {
      checks.add(checkGlobalNames);
    }

    if (!options.getConformanceConfigs().isEmpty()) {
      checks.add(checkConformance);
    }

    // Replace 'goog.getCssName' before processing defines but after the
    // other checks have been done.
    if (options.closurePass && !options.shouldPreserveGoogLibraryPrimitives()) {
      checks.add(closureReplaceGetCssName);
    }

    if (options.getTweakProcessing().isOn()) {
      checks.add(processTweaks);
    }

    if (options.instrumentationTemplate != null || options.recordFunctionInformation) {
      checks.add(computeFunctionNames);
    }

    if (options.checksOnly) {
      // Run process defines here so that warnings/errors from that pass are emitted as part of
      // checks.
      // TODO(rluble): Split process defines into two stages, one that performs only checks to be
      // run here, and the one that actually changes the AST that would run in the optimization
      // phase.
      checks.add(processDefines);
    }

    if (options.j2clPassMode.shouldAddJ2clPasses()) {
      checks.add(j2clChecksPass);
    }

    if (options.shouldRunTypeSummaryChecksLate()) {
      checks.add(generateIjs);
    }

    // When options.generateExportsAfterTypeChecking is true, run GenerateExports after
    // both type checkers, not just after NTI.
    if (options.generateExportsAfterTypeChecking && options.generateExports) {
      checks.add(generateExports);
    }

    checks.add(createEmptyPass(PassNames.AFTER_STANDARD_CHECKS));

    if (!options.checksOnly) {
      // At this point all checks have been done.
      // There's no need to complete transpilation if we're only running checks.
      TranspilationPasses.addPostCheckTranspilationPasses(checks, options);

      if (options.needsTranspilationFrom(ES6)) {
        // This pass used to be necessary to make the typechecker happy with class-side inheritance.
        // It's not necessary for typechecking now that class transpilation is post-typechecking,
        // but it turned out to be necessary to avoid CollapseProperties breaking static inheritance
        // TODO(b/116054203): try to only run this pass when property collapsing is enabled during
        // the optimizations. Even better, find a way to get rid of this pass completely.
        checks.add(convertStaticInheritance);
      }
    }

    assertAllOneTimePasses(checks);
    assertValidOrderForChecks(checks);

    return checks;
  }

  @Override
  protected List<PassFactory> getOptimizations() {
    List<PassFactory> passes = new ArrayList<PassFactory>();

    if (options.skipNonTranspilationPasses) {
      return passes;
    }

    passes.add(removeWeakSources);

    passes.add(garbageCollectChecks);

    // i18n
    // If you want to customize the compiler to use a different i18n pass,
    // you can create a PassConfig that calls replacePassFactory
    // to replace this.
    if (options.replaceMessagesWithChromeI18n) {
      passes.add(replaceMessagesForChrome);
    } else if (options.messageBundle != null) {
      passes.add(replaceMessages);
    }

    // Defines in code always need to be processed.
    passes.add(processDefines);

    if (options.getTweakProcessing().shouldStrip()
        || !options.stripTypes.isEmpty()
        || !options.stripNameSuffixes.isEmpty()
        || !options.stripTypePrefixes.isEmpty()
        || !options.stripNamePrefixes.isEmpty()) {
      passes.add(stripCode);
    }

    passes.add(normalize);

    // Create extern exports after the normalize because externExports depends on unique names.
    if (options.isExternExportsEnabled() || options.externExportsPath != null) {
      passes.add(externExports);
    }

    // Gather property names in externs so they can be queried by the
    // optimizing passes.
    passes.add(gatherExternProperties);

    // Should be run before runtimeTypeCheck and instrumentForCoverage as they rewrite code that
    // this pass expects to see.
    if (options.j2clPassMode.shouldAddJ2clPasses()) {
      passes.add(j2clPass);
      passes.add(j2clUtilGetDefineRewriterPass);
    }

    if (options.instrumentForCoverage) {
      passes.add(instrumentForCodeCoverage);
    }

    if (options.runtimeTypeCheck) {
      passes.add(runtimeTypeCheck);
    }

    passes.add(createEmptyPass(PassNames.BEFORE_STANDARD_OPTIMIZATIONS));

    if (options.replaceIdGenerators) {
      passes.add(replaceIdGenerators);
    }

    // Optimizes references to the arguments variable.
    if (options.optimizeArgumentsArray) {
      passes.add(optimizeArgumentsArray);
    }

    // Abstract method removal works best on minimally modified code, and also
    // only needs to run once.
    if (options.closurePass && (options.removeAbstractMethods || options.removeClosureAsserts)) {
      passes.add(closureCodeRemoval);
    }

    if (options.removeJ2clAsserts) {
      passes.add(j2clAssertRemovalPass);
    }

    // Property disambiguation should only run once and needs to be done
    // soon after type checking, both so that it can make use of type
    // information and so that other passes can take advantage of the renamed
    // properties.
    if (options.disambiguatePrivateProperties) {
      passes.add(disambiguatePrivateProperties);
    }

    assertAllOneTimePasses(passes);

    // Inline aliases so that following optimizations don't have to understand alias chains.
    if (options.shouldCollapseProperties()) {
      passes.add(aggressiveInlineAliases);
    }

    // Inline getters/setters in J2CL classes so that Object.defineProperties() calls (resulting
    // from desugaring) don't block class stripping.
    if (options.j2clPassMode.shouldAddJ2clPasses() && options.shouldCollapseProperties()) {
      // Relies on collapseProperties-triggered aggressive alias inlining.
      passes.add(j2clPropertyInlinerPass);
    }

    // Collapsing properties can undo constant inlining, so we do this before
    // the main optimization loop.
    if (options.shouldCollapseProperties()) {
      passes.add(collapseProperties);
    }

    if (options.inferConsts) {
      passes.add(inferConsts);
    }

    // TODO(mlourenco): Ideally this would be in getChecks() instead of getOptimizations(). But
    // for that it needs to understand constant properties as well. See b/31301233#10.
    // Needs to happen after inferConsts and collapseProperties. Detects whether invocations of
    // the method goog.string.Const.from are done with an argument which is a string literal.
    passes.add(checkConstParams);

    // Running RemoveUnusedCode before disambiguate properties allows disambiguate properties to be
    // more effective if code that would prevent disambiguation can be removed.
    // TODO(b/66971163): Rename options since we're not actually using smartNameRemoval here now.
    if (options.extraSmartNameRemoval && options.smartNameRemoval) {

      // These passes remove code that is dead because of define flags.
      // If the dead code is weakly typed, running these passes before property
      // disambiguation results in more code removal.
      // The passes are one-time on purpose. (The later runs are loopable.)
      if (options.foldConstants && (options.inlineVariables || options.inlineLocalVariables)) {
        passes.add(earlyInlineVariables);
        passes.add(earlyPeepholeOptimizations);
      }

      passes.add(removeUnusedCodeOnce);
    }

    // RewritePolyfills is overly generous in the polyfills it adds.  After type
    // checking and early smart name removal, we can use the new type information
    // to figure out which polyfilled prototype methods are actually called, and
    // which were "false alarms" (i.e. calling a method of the same name on a
    // user-provided class).  We also remove any polyfills added by code that
    // was smart-name-removed.  This is a one-time pass, since it does not work
    // after inlining - we do not attempt to aggressively remove polyfills used
    // by code that is only flow-sensitively dead.
    if (options.rewritePolyfills) {
      passes.add(removeUnusedPolyfills);
    }

    // Property disambiguation should only run once and needs to be done
    // soon after type checking, both so that it can make use of type
    // information and so that other passes can take advantage of the renamed
    // properties.
    if (options.shouldDisambiguateProperties() && options.isTypecheckingEnabled()) {
      passes.add(disambiguateProperties);
    }

    if (options.computeFunctionSideEffects) {
      passes.add(markPureFunctions);
    } else if (options.markNoSideEffectCalls) {
      // TODO(user) The properties that this pass adds to CALL and NEW
      // AST nodes increase the AST's in-memory size.  Given that we are
      // already running close to our memory limits, we could run into
      // trouble if we end up using the @nosideeffects annotation a lot
      // or compute @nosideeffects annotations by looking at function
      // bodies.  It should be easy to propagate @nosideeffects
      // annotations as part of passes that depend on this property and
      // store the result outside the AST (which would allow garbage
      // collection once the pass is done).
      passes.add(markNoSideEffectCalls);
    }

    if (options.smartNameRemoval) {
      passes.addAll(getCodeRemovingPasses());
      // TODO(b/66971163): Remove this early loop or rename the option that enables it
      // to something more appropriate.
    }

    // This needs to come after the inline constants pass, which is run within
    // the code removing passes.
    if (options.closurePass) {
      passes.add(closureOptimizePrimitives);
    }

    // ReplaceStrings runs after CollapseProperties in order to simplify
    // pulling in values of constants defined in enums structures. It also runs
    // after disambiguate properties and smart name removal so that it can
    // correctly identify logging types and can replace references to string
    // expressions.
    if (!options.replaceStringsFunctionDescriptions.isEmpty()) {
      passes.add(replaceStrings);
    }

    // TODO(user): This forces a first crack at crossChunkCodeMotion
    // before devirtualization. Once certain functions are devirtualized,
    // it confuses crossChunkCodeMotion ability to recognized that
    // it is recursive.

    // TODO(user): This is meant for a temporary quick win.
    // In the future, we might want to improve our analysis in
    // CrossChunkCodeMotion so we don't need to do this.
    if (options.shouldRunCrossChunkCodeMotion()) {
      passes.add(crossModuleCodeMotion);
    }

    // Method devirtualization benefits from property disambiguation so
    // it should run after that pass but before passes that do
    // optimizations based on global names (like cross module code motion
    // and inline functions).  Smart Name Removal does better if run before
    // this pass.
    if (options.devirtualizePrototypeMethods) {
      passes.add(devirtualizePrototypeMethods);
    }

    if (options.customPasses != null) {
      passes.add(getCustomPasses(
          CustomPassExecutionTime.BEFORE_OPTIMIZATION_LOOP));
    }

    passes.add(createEmptyPass(PassNames.BEFORE_MAIN_OPTIMIZATIONS));

    // Because FlowSensitiveInlineVariables does not operate on the global scope due to compilation
    // time, we need to run it once before InlineFunctions so that we don't miss inlining
    // opportunities when a function will be inlined into the global scope.
    if (options.inlineVariables || options.inlineLocalVariables) {
      passes.add(flowSensitiveInlineVariables);
    }

    passes.addAll(getMainOptimizationLoop());
    passes.add(createEmptyPass(PassNames.AFTER_MAIN_OPTIMIZATIONS));

    passes.add(createEmptyPass("beforeModuleMotion"));

    if (options.shouldRunCrossChunkCodeMotion()) {
      passes.add(crossModuleCodeMotion);
    }

    if (options.shouldRunCrossChunkMethodMotion()) {
      passes.add(crossModuleMethodMotion);
    }

    passes.add(createEmptyPass("afterModuleMotion"));

    // Some optimizations belong outside the loop because running them more
    // than once would either have no benefit or be incorrect.
    if (options.customPasses != null) {
      passes.add(getCustomPasses(
          CustomPassExecutionTime.AFTER_OPTIMIZATION_LOOP));
    }

    if (options.inlineVariables || options.inlineLocalVariables) {
      passes.add(flowSensitiveInlineVariables);

      // After inlining some of the variable uses, some variables are unused.
      // Re-run remove unused vars to clean it up.
      if (shouldRunRemoveUnusedCode()) {
        passes.add(removeUnusedCodeOnce);
      }
    }

    if (options.collapseAnonymousFunctions) {
      passes.add(collapseAnonymousFunctions);
    }

    // Move functions before extracting prototype member declarations.
    if (options.moveFunctionDeclarations
        // renamePrefixNamescape relies on moveFunctionDeclarations
        // to preserve semantics.
        || options.renamePrefixNamespace != null) {
      passes.add(moveFunctionDeclarations);
    }

    if (options.anonymousFunctionNaming == AnonymousFunctionNamingPolicy.MAPPED) {
      passes.add(nameMappedAnonymousFunctions);
    }

    // The mapped name anonymous function pass makes use of information that
    // the extract prototype member declarations pass removes so the former
    // happens before the latter.
    if (options.extractPrototypeMemberDeclarations != ExtractPrototypeMemberDeclarationsMode.OFF) {
      passes.add(extractPrototypeMemberDeclarations);
    }

    if (options.shouldAmbiguateProperties()
        && options.propertyRenaming == PropertyRenamingPolicy.ALL_UNQUOTED
        && options.isTypecheckingEnabled()) {
      passes.add(ambiguateProperties);
    }

    if (options.propertyRenaming == PropertyRenamingPolicy.ALL_UNQUOTED) {
      passes.add(renameProperties);
    }

    // Reserve global names added to the "windows" object.
    if (options.reserveRawExports) {
      passes.add(gatherRawExports);
    }

    // This comes after property renaming because quoted property names must
    // not be renamed.
    if (options.convertToDottedProperties) {
      passes.add(convertToDottedProperties);
    }

    // Property renaming must happen before this pass runs since this
    // pass may convert dotted properties into quoted properties.  It
    // is beneficial to run before alias strings, alias keywords and
    // variable renaming.
    if (options.rewriteFunctionExpressions) {
      passes.add(rewriteFunctionExpressions);
    }

    // This comes after converting quoted property accesses to dotted property
    // accesses in order to avoid aliasing property names.
    if (!options.aliasableStrings.isEmpty() || options.aliasAllStrings) {
      passes.add(aliasStrings);
    }

    if (options.coalesceVariableNames) {
      // Passes after this point can no longer depend on normalized AST
      // assumptions because the code is marked as un-normalized
      passes.add(coalesceVariableNames);

      // coalesceVariables creates identity assignments and more redundant code
      // that can be removed, rerun the peephole optimizations to clean them
      // up.
      if (options.foldConstants) {
        passes.add(peepholeOptimizationsOnce);
      }
    }

    // Passes after this point can no longer depend on normalized AST assumptions.
    passes.add(markUnnormalized);

    if (options.collapseVariableDeclarations) {
      passes.add(exploitAssign);
      passes.add(collapseVariableDeclarations);
    }

    // This pass works best after collapseVariableDeclarations.
    passes.add(denormalize);

    if (options.instrumentationTemplate != null) {
      passes.add(instrumentFunctions);
    }

    if (options.variableRenaming != VariableRenamingPolicy.ALL) {
      // If we're leaving some (or all) variables with their old names,
      // then we need to undo any of the markers we added for distinguishing
      // local variables ("x" -> "x$jscomp$1").
      passes.add(invertContextualRenaming);
    }

    if (options.variableRenaming != VariableRenamingPolicy.OFF) {
      passes.add(renameVars);
    }

    // This pass should run after names stop changing.
    if (options.processObjectPropertyString) {
      passes.add(objectPropertyStringPostprocess);
    }

    if (options.labelRenaming) {
      passes.add(renameLabels);
    }

    if (options.foldConstants) {
      passes.add(latePeepholeOptimizations);
    }

    if (options.anonymousFunctionNaming == AnonymousFunctionNamingPolicy.UNMAPPED) {
      passes.add(nameUnmappedAnonymousFunctions);
    }

    if (protectHiddenSideEffects) {
      passes.add(stripSideEffectProtection);
    }

    if (options.renamePrefixNamespace != null) {
      if (!GLOBAL_SYMBOL_NAMESPACE_PATTERN.matcher(
          options.renamePrefixNamespace).matches()) {
        throw new IllegalArgumentException(
            "Illegal character in renamePrefixNamespace name: "
            + options.renamePrefixNamespace);
      }
      passes.add(rescopeGlobalSymbols);
    }

    // Safety checks
    passes.add(checkAstValidity);
    passes.add(varCheckValidity);

    // Raise to ES6, if allowed
    if (options.getOutputFeatureSet().contains(ES6)) {
      passes.add(optimizeToEs6);
    }

    assertValidOrderForOptimizations(passes);
    return passes;
  }

  /** Creates the passes for the main optimization loop. */
  private List<PassFactory> getMainOptimizationLoop() {
    List<PassFactory> passes = new ArrayList<PassFactory>();
    if (options.inlineGetters) {
      passes.add(inlineSimpleMethods);
    }

    passes.addAll(getCodeRemovingPasses());

    if (options.getInlineFunctionsLevel() != Reach.NONE) {
      passes.add(inlineFunctions);
    }

    if (options.shouldInlineProperties() && options.isTypecheckingEnabled()) {
      passes.add(inlineProperties);
    }

    if (options.removeUnusedVars || options.removeUnusedLocalVars) {
      if (options.deadAssignmentElimination) {
        passes.add(deadAssignmentsElimination);

        // The Polymer source is usually not included in the compilation, but it creates
        // getters/setters for many properties in compiled code. Dead property assignment
        // elimination is only safe when it knows about getters/setters. Therefore, we skip
        // it if the polymer pass is enabled.
        if (options.polymerVersion == null) {
          passes.add(deadPropertyAssignmentElimination);
        }
      }
    }

    if (options.optimizeCalls) {
      passes.add(optimizeCalls);
    }

    if (options.j2clPassMode.shouldAddJ2clPasses()) {
      passes.add(j2clConstantHoisterPass);
      passes.add(j2clClinitPass);
    }

    assertAllLoopablePasses(passes);
    return passes;
  }

  /** Creates several passes aimed at removing code. */
  private List<PassFactory> getCodeRemovingPasses() {
    List<PassFactory> passes = new ArrayList<PassFactory>();
    if (options.collapseObjectLiterals) {
      passes.add(collapseObjectLiterals);
    }

    if (options.inlineVariables || options.inlineLocalVariables) {
      passes.add(inlineVariables);
    } else if (options.inlineConstantVars) {
      passes.add(inlineConstants);
    }

    if (options.foldConstants) {
      passes.add(peepholeOptimizations);
    }

    if (options.removeDeadCode) {
      passes.add(removeUnreachableCode);
    }

    if (shouldRunRemoveUnusedCode()) {
      passes.add(removeUnusedCode);
    }

    assertAllLoopablePasses(passes);
    return passes;
  }

  private boolean shouldRunRemoveUnusedCode() {
    return options.removeUnusedVars
        || options.removeUnusedLocalVars
        || options.removeUnusedPrototypeProperties
        || options.isRemoveUnusedClassProperties()
        || options.isRemoveUnusedConstructorProperties();
  }

  private final HotSwapPassFactory checkSideEffects =
      new HotSwapPassFactory("checkSideEffects") {
        @Override
        protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
          return new CheckSideEffects(
              compiler, options.checkSuspiciousCode, protectHiddenSideEffects);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Removes the "protector" functions that were added by CheckSideEffects. */
  private final PassFactory stripSideEffectProtection =
      new PassFactory(PassNames.STRIP_SIDE_EFFECT_PROTECTION, true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CheckSideEffects.StripProtection(compiler);
        }

        @Override
        public FeatureSet featureSet() {
          return FeatureSet.latest();
        }
      };

  /** Checks for code that is probably wrong (such as stray expressions). */
  private final HotSwapPassFactory suspiciousCode =
      new HotSwapPassFactory("suspiciousCode") {
        @Override
        protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
          List<Callback> sharedCallbacks = new ArrayList<Callback>();
          if (options.checkSuspiciousCode) {
            sharedCallbacks.add(new CheckSuspiciousCode());
            sharedCallbacks.add(new CheckDuplicateCase(compiler));
          }

          if (options.enables(DiagnosticGroups.GLOBAL_THIS)) {
            sharedCallbacks.add(new CheckGlobalThis(compiler));
          }

          if (options.enables(DiagnosticGroups.DEBUGGER_STATEMENT_PRESENT)) {
            sharedCallbacks.add(new CheckDebuggerStatement(compiler));
          }

          return combineChecks(compiler, sharedCallbacks);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Verify that all the passes are one-time passes. */
  private static void assertAllOneTimePasses(List<PassFactory> passes) {
    for (PassFactory pass : passes) {
      checkState(pass.isOneTimePass());
    }
  }

  /** Verify that all the passes are multi-run passes. */
  private static void assertAllLoopablePasses(List<PassFactory> passes) {
    for (PassFactory pass : passes) {
      checkState(!pass.isOneTimePass());
    }
  }

  /**
   * Checks that {@code pass1} comes before {@code pass2} in {@code passList}, if both are present.
   */
  private void assertPassOrder(
      List<PassFactory> passList, PassFactory pass1, PassFactory pass2, String msg) {
    int pass1Index = passList.indexOf(pass1);
    int pass2Index = passList.indexOf(pass2);
    if (pass1Index != -1 && pass2Index != -1) {
      checkState(pass1Index < pass2Index, msg);
    }
  }

  /**
   * Certain checks need to run in a particular order. For example, the PolymerPass
   * will not work correctly unless it runs after the goog.provide() processing.
   * This enforces those constraints.
   * @param checks The list of check passes
   */
  private void assertValidOrderForChecks(List<PassFactory> checks) {
    assertPassOrder(
        checks,
        chromePass,
        checkJsDocAndEs6Modules,
        "The ChromePass must run before after JsDoc and Es6 module checking.");
    assertPassOrder(
        checks,
        closureRewriteModule,
        processDefines,
        "Must rewrite goog.module before processing @define's, so that @defines in modules work.");
    assertPassOrder(
        checks,
        closurePrimitives,
        polymerPass,
        "The Polymer pass must run after goog.provide processing.");
    assertPassOrder(
        checks,
        chromePass,
        polymerPass,
        "The Polymer pass must run after ChromePass processing.");
    assertPassOrder(
        checks,
        polymerPass,
        suspiciousCode,
        "The Polymer pass must run before suspiciousCode processing.");
    assertPassOrder(
        checks,
        dartSuperAccessorsPass,
        TranspilationPasses.es6ConvertSuper,
        "The Dart super accessors pass must run before ES6->ES3 super lowering.");

    if (checks.contains(closureGoogScopeAliases)) {
      checkState(
          checks.contains(checkVariableReferences),
          "goog.scope processing requires variable checking");
    }
    assertPassOrder(
        checks,
        checkVariableReferences,
        closureGoogScopeAliases,
        "Variable checking must happen before goog.scope processing.");

    assertPassOrder(
        checks,
        gatherModuleMetadataPass,
        closureCheckModule,
        "Need to gather module metadata before checking closure modules.");
  }

  /**
   * Certain optimizations need to run in a particular order. For example, OptimizeCalls must run
   * before RemoveSuperMethodsPass, because the former can invalidate assumptions in the latter.
   * This enforces those constraints.
   * @param optimizations The list of optimization passes
   */
  private void assertValidOrderForOptimizations(List<PassFactory> optimizations) {
    assertPassOrder(
        optimizations,
        processDefines,
        j2clUtilGetDefineRewriterPass,
        "J2CL define re-writing should be done after processDefines since it relies on "
            + "collectDefines which has side effects.");
  }

  /** Checks that all constructed classes are goog.require()d. */
  private final HotSwapPassFactory checkRequires =
      new HotSwapPassFactory("checkMissingAndExtraRequires") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new CheckMissingAndExtraRequires(
              compiler, CheckMissingAndExtraRequires.Mode.FULL_COMPILE);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Makes sure @constructor is paired with goog.provides(). */
  private final HotSwapPassFactory checkProvides =
      new HotSwapPassFactory("checkProvides") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new CheckProvides(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  private static final DiagnosticType GENERATE_EXPORTS_ERROR =
      DiagnosticType.error(
          "JSC_GENERATE_EXPORTS_ERROR",
          "Exports can only be generated if export symbol/property functions are set.");

  /** Verifies JSDoc annotations are used properly and checks for ES6 modules. */
  private final HotSwapPassFactory checkJsDocAndEs6Modules =
      new HotSwapPassFactory("checkJsDocAndEs6Modules") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          ImmutableList.Builder<Callback> callbacks =
              ImmutableList.<Callback>builder()
                  .add(new CheckJSDoc(compiler))
                  .add(new Es6CheckModule(compiler));
          return combineChecks(compiler, callbacks.build());
        }

        @Override
        protected FeatureSet featureSet() {
          return FeatureSet.latest();
        }
      };

  /** Generates exports for @export annotations. */
  private final PassFactory generateExports =
      new PassFactory(PassNames.GENERATE_EXPORTS, true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          CodingConvention convention = compiler.getCodingConvention();
          if (convention.getExportSymbolFunction() != null
              && convention.getExportPropertyFunction() != null) {
            final GenerateExports pass =
                new GenerateExports(
                    compiler,
                    options.exportLocalPropertyDefinitions,
                    convention.getExportSymbolFunction(),
                    convention.getExportPropertyFunction());
            return new CompilerPass() {
              @Override
              public void process(Node externs, Node root) {
                pass.process(externs, root);
                compiler.addExportedNames(pass.getExportedVariableNames());
              }
            };
          } else {
            return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR);
          }
        }

        @Override
        protected FeatureSet featureSet() {
          return ES2018;
        }
      };

  private final PassFactory generateIjs =
      new PassFactory("generateIjs", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new ConvertToTypedInterface(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Generates exports for functions associated with JsUnit. */
  private final PassFactory exportTestFunctions =
      new PassFactory(PassNames.EXPORT_TEST_FUNCTIONS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          CodingConvention convention = compiler.getCodingConvention();
          if (convention.getExportSymbolFunction() != null) {
            return new ExportTestFunctions(
                compiler,
                convention.getExportSymbolFunction(),
                convention.getExportPropertyFunction());
          } else {
            return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR);
          }
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

    private final PassFactory keepPropertyNamesPass = 
        new PassFactory("keep-property-names", true) {
          @Override
          protected CompilerPass create(final AbstractCompiler compiler) {
            return new CompilerPass() {
              @Override
              public void process(Node externs, Node root) {

                Node propsObj = new Node(Token.OBJECTLIT);
                for(String nameToKeep : propertyNamesToKeep)
                {
                  Node nameStringKey = IR.stringKey(nameToKeep);
                  JSDocInfoBuilder builder = new JSDocInfoBuilder(true);
                  builder.recordExport();
                  JSDocInfo jsDocInfo = builder.build();
                  nameStringKey.setJSDocInfo(jsDocInfo);

                  Node propertyDescriptor = new Node(Token.OBJECTLIT);
                  propertyDescriptor.addChildToBack(IR.propdef(IR.stringKey("get"), NodeUtil.emptyFunction()));

                  Node prop = IR.propdef(nameStringKey, propertyDescriptor);
                  propsObj.addChildToBack(prop);
                }

                Node definePropertiesTarget = NodeUtil.newQName(compiler, "Object.defineProperties");
                Node definePropertiesCall = IR.call(definePropertiesTarget, IR.objectlit(), propsObj);
                Node expression = IR.exprResult(definePropertiesCall);
                
                Node scriptNode = compiler.getScriptNode(sourceFileName);
                scriptNode.addChildToBack(expression);
                compiler.reportChangeToEnclosingScope(expression);
              }
            };
          }
    
          @Override
          protected FeatureSet featureSet() {
            return ES_NEXT;
          }
        };

  /** Raw exports processing pass. */
  private final PassFactory gatherRawExports =
      new PassFactory(PassNames.GATHER_RAW_EXPORTS, true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          final GatherRawExports pass = new GatherRawExports(compiler);

          return new CompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              pass.process(externs, root);
              compiler.addExportedNames(pass.getExportedVariableNames());
            }
          };
        }

        @Override
        public FeatureSet featureSet() {
          // Should be FeatureSet.latest() since it's a trivial pass, but must match "normalize"
          // TODO(johnlenz): Update this and normalize to latest()
          return ES8_MODULES;
        }
      };

  /** Closure pre-processing pass. */
  private final HotSwapPassFactory closurePrimitives =
      new HotSwapPassFactory("closurePrimitives") {
        @SuppressWarnings("deprecation")
		@Override
        protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
          preprocessorSymbolTableFactory.maybeInitialize(compiler);
          final ProcessClosurePrimitivesWithModuleSupport pass =
              new ProcessClosurePrimitivesWithModuleSupport(
                  compiler,
                  preprocessorSymbolTableFactory.getInstanceOrNull(),
                  options.brokenClosureRequiresLevel,
                  options.shouldPreservesGoogProvidesAndRequires());

          return new HotSwapCompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              pass.process(externs, root);
              compiler.addExportedNames(pass.getExportedVariableNames());
            }

            @Override
            public void hotSwapScript(Node scriptRoot, Node originalRoot) {
              pass.hotSwapScript(scriptRoot, originalRoot);
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Process AngularJS-specific annotations. */
  private final HotSwapPassFactory angularPass =
      new HotSwapPassFactory(PassNames.ANGULAR_PASS) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new AngularPass(compiler);
        }

        @Override
        public FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /**
   * The default i18n pass. A lot of the options are not configurable, because ReplaceMessages has a
   * lot of legacy logic.
   */
  private final PassFactory replaceMessages =
      new PassFactory(PassNames.REPLACE_MESSAGES, true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new ReplaceMessages(
              compiler,
              options.messageBundle,
              /* warn about message dupes */
              true,
              /* allow messages with goog.getMsg */
              JsMessage.Style.CLOSURE,
              /* if we can't find a translation, don't worry about it. */
              false);
        }

        @Override
        public FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  private final PassFactory replaceMessagesForChrome =
      new PassFactory(PassNames.REPLACE_MESSAGES, true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new ReplaceMessagesForChrome(
              compiler,
              new GoogleJsMessageIdGenerator(options.tcProjectId),
              /* warn about message dupes */
              true,
              /* allow messages with goog.getMsg */
              JsMessage.Style.CLOSURE);
        }

        @Override
        public FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Applies aliases and inlines goog.scope. */
  private final HotSwapPassFactory closureGoogScopeAliases =
      new HotSwapPassFactory("closureGoogScopeAliases") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          preprocessorSymbolTableFactory.maybeInitialize(compiler);
          return new ScopedAliases(
              compiler,
              preprocessorSymbolTableFactory.getInstanceOrNull(),
              options.getAliasTransformationHandler());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  private final PassFactory injectRuntimeLibraries =
      new PassFactory("InjectRuntimeLibraries", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new InjectRuntimeLibraries(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES2018;
        }
      };

  /** Desugars ES6_TYPED features into ES6 code. */
  final HotSwapPassFactory convertEs6TypedToEs6 =
      new HotSwapPassFactory("convertEs6Typed") {
        @Override
        protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
          return new Es6TypedToEs6Converter(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return TYPESCRIPT;
        }
      };

  private final PassFactory convertStaticInheritance =
      new PassFactory("Es6StaticInheritance", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new Es6ToEs3ClassSideInheritance(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return TYPE_CHECK_SUPPORTED;
        }
      };

  private final PassFactory inlineTypeAliases =
      new PassFactory(PassNames.INLINE_TYPE_ALIASES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new InlineAliases(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES2018;
        }
      };

  /** Inlines type aliases if they are explicitly or effectively const. */
  private final PassFactory aggressiveInlineAliases =
      new PassFactory("aggressiveInlineAliases", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new AggressiveInlineAliases(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  private final PassFactory removeWeakSources =
      new PassFactory("removeWeakSources", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new RemoveWeakSources(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return FeatureSet.latest();
        }
      };

  private final PassFactory declaredGlobalExternsOnWindow =
      new PassFactory(PassNames.DECLARED_GLOBAL_EXTERNS_ON_WINDOW, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new DeclaredGlobalExternsOnWindow(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Rewrites goog.defineClass */
  private final HotSwapPassFactory closureRewriteClass =
      new HotSwapPassFactory(PassNames.CLOSURE_REWRITE_CLASS) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new ClosureRewriteClass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Checks of correct usage of goog.module */
  private final HotSwapPassFactory closureCheckModule =
      new HotSwapPassFactory("closureCheckModule") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new ClosureCheckModule(compiler, compiler.getModuleMetadataMap());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Rewrites goog.module */
  private final HotSwapPassFactory closureRewriteModule =
      new HotSwapPassFactory("closureRewriteModule") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          preprocessorSymbolTableFactory.maybeInitialize(compiler);
          maybeInitializeModuleRewriteState();
          return new ClosureRewriteModule(
              compiler, preprocessorSymbolTableFactory.getInstanceOrNull(), moduleRewriteState);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Rewrite imports for Closure Library's goog.js file to global goog references. */
  private final HotSwapPassFactory rewriteGoogJsImports =
      new HotSwapPassFactory("rewriteGoogJsImports") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new RewriteGoogJsImports(compiler, RewriteGoogJsImports.Mode.LINT_AND_REWRITE);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Checks that CSS class names are wrapped in goog.getCssName */
  private final PassFactory closureCheckGetCssName =
      new PassFactory("closureCheckGetCssName", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new CheckMissingGetCssName(
              compiler,
              options.checkMissingGetCssNameLevel,
              options.checkMissingGetCssNameBlacklist);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /**
   * Processes goog.getCssName. The cssRenamingMap is used to lookup replacement values for the
   * classnames. If null, the raw class names are inlined.
   */
  private final PassFactory closureReplaceGetCssName =
      new PassFactory("closureReplaceGetCssName", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CompilerPass() {
            @Override
            public void process(Node externs, Node jsRoot) {
              Map<String, Integer> newCssNames = null;
              if (options.gatherCssNames) {
                newCssNames = new HashMap<String, Integer>();
              }
              ReplaceCssNames pass =
                  new ReplaceCssNames(compiler, newCssNames, options.cssRenamingWhitelist);
              pass.process(externs, jsRoot);
              compiler.setCssNames(newCssNames);
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return ES2018;
        }
      };

  /**
   * Creates synthetic blocks to prevent FoldConstants from moving code past markers in the source.
   */
  private final PassFactory createSyntheticBlocks =
      new PassFactory("createSyntheticBlocks", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new CreateSyntheticBlocks(
              compiler, options.syntheticBlockStartMarker, options.syntheticBlockEndMarker);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  private final PassFactory earlyPeepholeOptimizations =
      new PassFactory("earlyPeepholeOptimizations", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          List<AbstractPeepholeOptimization> peepholeOptimizations = new ArrayList<AbstractPeepholeOptimization>();
          peepholeOptimizations.add(new PeepholeRemoveDeadCode());
          if (compiler.getOptions().j2clPassMode.shouldAddJ2clPasses()) {
            peepholeOptimizations.add(new J2clEqualitySameRewriterPass());
          }
          return new PeepholeOptimizationsPass(compiler, getName(), peepholeOptimizations);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  private final PassFactory earlyInlineVariables =
      new PassFactory("earlyInlineVariables", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      InlineVariables.Mode mode;
      if (options.inlineVariables) {
        mode = InlineVariables.Mode.ALL;
      } else if (options.inlineLocalVariables) {
        mode = InlineVariables.Mode.LOCALS_ONLY;
      } else {
        throw new IllegalStateException("No variable inlining option set.");
      }
      return new InlineVariables(compiler, mode, true);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Various peephole optimizations. */
  private static CompilerPass createPeepholeOptimizationsPass(
      AbstractCompiler compiler, String passName) {
    final boolean late = false;
    final boolean useTypesForOptimization = compiler.getOptions().useTypesForLocalOptimization;
    List<AbstractPeepholeOptimization> optimizations = new ArrayList<AbstractPeepholeOptimization>();
    optimizations.add(new MinimizeExitPoints());
    optimizations.add(new PeepholeMinimizeConditions(late));
    optimizations.add(new PeepholeSubstituteAlternateSyntax(late));
    optimizations.add(new PeepholeReplaceKnownMethods(late, useTypesForOptimization));
    optimizations.add(new PeepholeRemoveDeadCode());
    if (compiler.getOptions().j2clPassMode.shouldAddJ2clPasses()) {
      optimizations.add(new J2clEqualitySameRewriterPass());
      optimizations.add(new J2clStringValueOfRewriterPass());
    }
    optimizations.add(new PeepholeFoldConstants(late, useTypesForOptimization));
    optimizations.add(new PeepholeCollectPropertyAssignments());
    return new PeepholeOptimizationsPass(compiler, passName, optimizations);
  }

  /** Various peephole optimizations. */
  private final PassFactory peepholeOptimizations =
      new PassFactory(PassNames.PEEPHOLE_OPTIMIZATIONS, false /* oneTimePass */) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return createPeepholeOptimizationsPass(compiler, getName());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Various peephole optimizations. */
  private final PassFactory peepholeOptimizationsOnce =
      new PassFactory(PassNames.PEEPHOLE_OPTIMIZATIONS, true /* oneTimePass */) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return createPeepholeOptimizationsPass(compiler, getName());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Same as peepholeOptimizations but aggressively merges code together */
  private final PassFactory latePeepholeOptimizations =
      new PassFactory("latePeepholeOptimizations", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          final boolean late = true;
          final boolean useTypesForOptimization = options.useTypesForLocalOptimization;
          return new PeepholeOptimizationsPass(
              compiler,
              getName(),
              new StatementFusion(options.aggressiveFusion),
              new PeepholeRemoveDeadCode(),
              new PeepholeMinimizeConditions(late),
              new PeepholeSubstituteAlternateSyntax(late),
              new PeepholeReplaceKnownMethods(late, useTypesForOptimization),
              new PeepholeFoldConstants(late, useTypesForOptimization),
              new PeepholeReorderConstantExpression());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Checks that all variables are defined. */
  private final HotSwapPassFactory checkVars =
      new HotSwapPassFactory(PassNames.CHECK_VARS) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new VarCheck(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Infers constants. */
  private final PassFactory inferConsts =
      new PassFactory(PassNames.INFER_CONSTS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new InferConsts(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Checks for RegExp references. */
  private final PassFactory checkRegExp =
      new PassFactory(PassNames.CHECK_REG_EXP, true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          final CheckRegExp pass = new CheckRegExp(compiler);

          return new CompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              pass.process(externs, root);
              compiler.setHasRegExpGlobalReferences(pass.isGlobalRegExpPropertiesUsed());
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Checks that references to variables look reasonable. */
  private final HotSwapPassFactory checkVariableReferencesForTranspileOnly =
      new HotSwapPassFactory(PassNames.CHECK_VARIABLE_REFERENCES) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new VariableReferenceCheck(compiler, true);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Checks that references to variables look reasonable. */
  private final HotSwapPassFactory checkVariableReferences =
      new HotSwapPassFactory(PassNames.CHECK_VARIABLE_REFERENCES) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new VariableReferenceCheck(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Checks that references to variables look reasonable. */
  private final HotSwapPassFactory checkSuper =
      new HotSwapPassFactory("checkSuper") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new CheckSuper(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Pre-process goog.testing.ObjectPropertyString. */
  private final PassFactory objectPropertyStringPreprocess =
      new PassFactory("ObjectPropertyStringPreprocess", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new ObjectPropertyStringPreprocess(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Creates a typed scope and adds types to the type registry. */
  final HotSwapPassFactory resolveTypes =
      new HotSwapPassFactory(PassNames.RESOLVE_TYPES) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new GlobalTypeResolver(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return TYPE_CHECK_SUPPORTED;
        }
      };

  /** Clears the typed scope when we're done. */
  private final PassFactory clearTypedScopePass =
      new PassFactory("clearTypedScopePass", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return new ClearTypedScope();
    }

    @Override
    protected FeatureSet featureSet() {
      return FeatureSet.latest();
    }
  };

  /** Runs type inference. */
  final HotSwapPassFactory inferTypes =
      new HotSwapPassFactory(PassNames.INFER_TYPES) {
        @Override
        protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
          return new HotSwapCompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              checkNotNull(topScope);
              checkNotNull(getTypedScopeCreator());

              makeTypeInference(compiler).process(externs, root);
            }

            @Override
            public void hotSwapScript(Node scriptRoot, Node originalRoot) {
              makeTypeInference(compiler).inferAllScopes(scriptRoot);
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return TYPE_CHECK_SUPPORTED;
        }
      };

  private final HotSwapPassFactory inferJsDocInfo =
      new HotSwapPassFactory("inferJsDocInfo") {
        @Override
        protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
          return new HotSwapCompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              checkNotNull(topScope);
              checkNotNull(getTypedScopeCreator());

              makeInferJsDocInfo(compiler).process(externs, root);
            }

            @Override
            public void hotSwapScript(Node scriptRoot, Node originalRoot) {
              makeInferJsDocInfo(compiler).hotSwapScript(scriptRoot, originalRoot);
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return TYPE_CHECK_SUPPORTED;
        }
      };

  /** Checks type usage */
  private final HotSwapPassFactory checkTypes =
      new HotSwapPassFactory(PassNames.CHECK_TYPES) {
        @Override
        protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
          return new HotSwapCompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              checkNotNull(topScope);
              checkNotNull(getTypedScopeCreator());

              TypeCheck check = makeTypeCheck(compiler);
              check.process(externs, root);
              compiler.getErrorManager().setTypedPercent(check.getTypedPercent());
            }

            @Override
            public void hotSwapScript(Node scriptRoot, Node originalRoot) {
              makeTypeCheck(compiler).check(scriptRoot, false);
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return TYPE_CHECK_SUPPORTED;
        }
      };

  /**
   * Checks possible execution paths of the program for problems: missing return statements and dead
   * code.
   */
  private final HotSwapPassFactory checkControlFlow =
      new HotSwapPassFactory("checkControlFlow") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          List<Callback> callbacks = new ArrayList<Callback>();
          if (!options.disables(DiagnosticGroups.CHECK_USELESS_CODE)) {
            callbacks.add(new CheckUnreachableCode(compiler));
          }
          if (!options.disables(DiagnosticGroups.MISSING_RETURN)) {
            callbacks.add(new CheckMissingReturn(compiler));
          }
          return combineChecks(compiler, callbacks);
        }

        @Override
        public FeatureSet featureSet() {
          return ES2018;
        }
      };

  /** Checks access controls. Depends on type-inference. */
  /*
  private final HotSwapPassFactory checkAccessControls =
      new HotSwapPassFactory("checkAccessControls") {
    @Override
    protected HotSwapCompilerPass create(AbstractCompiler compiler) {
      return new CheckAccessControls(
          compiler, options.enforceAccessControlCodingConventions);
    }

    @Override
    protected FeatureSet featureSet() {
      return TYPE_CHECK_SUPPORTED;
    }
  };*/

  private final HotSwapPassFactory lintChecks =
      new HotSwapPassFactory(PassNames.LINT_CHECKS) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          ImmutableList.Builder<Callback> callbacks =
              ImmutableList.<Callback>builder()
                  .add(new CheckEmptyStatements(compiler))
                  .add(new CheckEnums(compiler))
                  .add(new CheckEs6ModuleFileStructure(compiler))
                  .add(new CheckEs6Modules(compiler))
                  .add(new CheckNoMutatedEs6Exports(compiler))
                  .add(new CheckInterfaces(compiler))
                  .add(new CheckJSDocStyle(compiler))
                  .add(new CheckMissingSemicolon(compiler))
                  .add(new CheckNullabilityModifiers(compiler))
                  .add(new CheckPrimitiveAsObject(compiler))
                  .add(new CheckPrototypeProperties(compiler))
                  .add(new CheckUnusedLabels(compiler))
                  .add(new CheckUselessBlocks(compiler));
          return combineChecks(compiler, callbacks.build());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  private final HotSwapPassFactory analyzerChecks =
      new HotSwapPassFactory(PassNames.ANALYZER_CHECKS) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          ImmutableList.Builder<Callback> callbacks = ImmutableList.builder();
          if (options.enables(DiagnosticGroups.ANALYZER_CHECKS_INTERNAL)) {
            callbacks
                .add(new CheckNullableReturn(compiler))
                .add(new CheckArrayWithGoogObject(compiler))
                .add(new ImplicitNullabilityCheck(compiler));
          }
          // These are grouped together for better execution efficiency.
          if (options.enables(DiagnosticGroups.UNUSED_PRIVATE_PROPERTY)) {
            callbacks.add(new CheckUnusedPrivateProperties(compiler));
          }
          if (options.enables(DiagnosticGroups.MISSING_CONST_PROPERTY)) {
            callbacks.add(new CheckConstPrivateProperties(compiler));
          }
          return combineChecks(compiler, callbacks.build());
        }

        @Override
        protected FeatureSet featureSet() {
          return TYPE_CHECK_SUPPORTED;
        }
      };

  private final HotSwapPassFactory checkRequiresAndProvidesSorted =
      new HotSwapPassFactory("checkRequiresAndProvidesSorted") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new CheckRequiresAndProvidesSorted(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Executes the given callbacks with a {@link CombinedCompilerPass}. */
  private static HotSwapCompilerPass combineChecks(AbstractCompiler compiler,
      List<Callback> callbacks) {
    checkArgument(!callbacks.isEmpty());
    return new CombinedCompilerPass(compiler, callbacks);
  }

  /** A compiler pass that resolves types in the global scope. */
  class GlobalTypeResolver implements HotSwapCompilerPass {
    private final AbstractCompiler compiler;

    GlobalTypeResolver(AbstractCompiler compiler) {
      this.compiler = compiler;
    }

    @Override
    public void process(Node externs, Node root) {
      this.compiler.setTypeCheckingHasRun(true);
      if (topScope == null) {
        regenerateGlobalTypedScope(compiler, root.getParent());
      } else {
        compiler.getTypeRegistry().resolveTypes();
      }
    }
    @Override
    public void hotSwapScript(Node scriptRoot, Node originalRoot) {
      patchGlobalTypedScope(compiler, scriptRoot);
    }
  }

  /** A compiler pass that clears the global scope. */
  class ClearTypedScope implements CompilerPass {
    @Override
    public void process(Node externs, Node root) {
      clearTypedScope();
    }
  }

  /** Checks global name usage. */
  private final PassFactory checkGlobalNames =
      new PassFactory("checkGlobalNames", true) {
    @Override
    protected CompilerPass create(final AbstractCompiler compiler) {
      return new CompilerPass() {
        @Override
        public void process(Node externs, Node jsRoot) {
          // Create a global namespace for analysis by check passes.
          // Note that this class does all heavy computation lazily,
          // so it's OK to create it here.
          namespaceForChecks = new GlobalNamespace(compiler, externs, jsRoot);
          new CheckGlobalNames(compiler, options.checkGlobalNamesLevel)
              .injectNamespace(namespaceForChecks).process(externs, jsRoot);
        }
      };
    }

    @Override
    protected FeatureSet featureSet() {
      return TYPE_CHECK_SUPPORTED;
    }
  };

  /** Checks that the code is ES5 strict compliant. */
  private final PassFactory checkStrictMode =
      new PassFactory("checkStrictMode", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new StrictModeCheck(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Process goog.tweak.getTweak() calls. */
  private final PassFactory processTweaks = new PassFactory("processTweaks", true) {
    @Override
    protected CompilerPass create(final AbstractCompiler compiler) {
      return new CompilerPass() {
        @Override
        public void process(Node externs, Node jsRoot) {
          new ProcessTweaks(compiler,
              options.getTweakProcessing().shouldStrip(),
              options.getTweakReplacements()).process(externs, jsRoot);
        }
      };
    }

    @Override
    protected FeatureSet featureSet() {
      return TYPE_CHECK_SUPPORTED;
    }
  };

  /** Override @define-annotated constants. */
  private final PassFactory processDefines =
      new PassFactory("processDefines", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CompilerPass() {
            @Override
            public void process(Node externs, Node jsRoot) {
              HashMap<String, Node> replacements = new HashMap<String, Node>();
              replacements.putAll(compiler.getDefaultDefineValues());
              replacements.putAll(getAdditionalReplacements(options));
              replacements.putAll(options.getDefineReplacements());
              new ProcessDefines(compiler, ImmutableMap.copyOf(replacements), !options.checksOnly)
                  .injectNamespace(namespaceForChecks)
                  .process(externs, jsRoot);
            }
          };
        }

        @Override
        public FeatureSet featureSet() {
          return TYPE_CHECK_SUPPORTED;
        }
      };

  /**
   * Strips code for smaller compiled code. This is useful for removing debug
   * statements to prevent leaking them publicly.
   */
  private final PassFactory stripCode = new PassFactory("stripCode", true) {
    @Override
    protected CompilerPass create(final AbstractCompiler compiler) {
      return new CompilerPass() {
        @Override
        public void process(Node externs, Node jsRoot) {
          CompilerOptions options = compiler.getOptions();
          StripCode pass = new StripCode(compiler, options.stripTypes, options.stripNameSuffixes,
              options.stripTypePrefixes, options.stripNamePrefixes);
          if (options.getTweakProcessing().shouldStrip()) {
            pass.enableTweakStripping();
          }
          pass.process(externs, jsRoot);
        }
      };
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Release references to data that is only needed during checks. */
  final PassFactory garbageCollectChecks =
      new HotSwapPassFactory("garbageCollectChecks") {
    @Override
    protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
      return new HotSwapCompilerPass() {
        @Override
        public void process(Node externs, Node jsRoot) {
          // Kill the global namespace so that it can be garbage collected
          // after all passes are through with it.
          namespaceForChecks = null;
        }

        @Override
        public void hotSwapScript(Node scriptRoot, Node originalRoot) {
          process(null, null);
        }
      };
    }

    @Override
    public FeatureSet featureSet() {
      return FeatureSet.latest();
    }
  };

  /** Checks that all constants are not modified */
  /*
  private final PassFactory checkConsts =
      new PassFactory("checkConsts", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new ConstCheck(compiler);
        }

        @Override
        public FeatureSet featureSet() {
          return ES2018;
        }
      };*/

  /** Checks that the arguments are constants */
  private final PassFactory checkConstParams =
      new PassFactory(PassNames.CHECK_CONST_PARAMS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new ConstParamCheck(compiler);
        }

        @Override
        public FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Computes the names of functions for later analysis. */
  private final PassFactory computeFunctionNames =
      new PassFactory("computeFunctionNames", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              CollectFunctionNames pass = new CollectFunctionNames(compiler);
              pass.process(externs, root);
              compiler.setFunctionNames(pass.getFunctionNames());
            }
          };
        }

        @Override
        public FeatureSet featureSet() {
          return ES2018;
        }
      };

  /** Inserts run-time type assertions for debugging. */
  private final PassFactory runtimeTypeCheck =
      new PassFactory(PassNames.RUNTIME_TYPE_CHECK, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new RuntimeTypeCheck(compiler, options.runtimeTypeCheckLogFunction);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES5;
        }
      };

  /** Generates unique ids. */
  private final PassFactory replaceIdGenerators =
      new PassFactory(PassNames.REPLACE_ID_GENERATORS, true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              ReplaceIdGenerators pass =
                  new ReplaceIdGenerators(
                      compiler,
                      options.idGenerators,
                      options.generatePseudoNames,
                      options.idGeneratorsMapSerialized,
                      options.xidHashFunction);
              pass.process(externs, root);
              compiler.setIdGeneratorMap(pass.getSerializedIdMappings());
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Replace strings. */
  private final PassFactory replaceStrings =
      new PassFactory("replaceStrings", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              ReplaceStrings pass =
                  new ReplaceStrings(
                      compiler,
                      options.replaceStringsPlaceholderToken,
                      options.replaceStringsFunctionDescriptions,
                      options.replaceStringsReservedStrings,
                      options.replaceStringsInputMap);
              pass.process(externs, root);
              compiler.setStringMap(pass.getStringMap());
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Optimizes the "arguments" array. */
  private final PassFactory optimizeArgumentsArray =
      new PassFactory(PassNames.OPTIMIZE_ARGUMENTS_ARRAY, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new OptimizeArgumentsArray(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Remove variables set to goog.abstractMethod. */
  private final PassFactory closureCodeRemoval =
      new PassFactory("closureCodeRemoval", true) {
    @Override
    protected CompilerPass create(final AbstractCompiler compiler) {
      return new ClosureCodeRemoval(compiler, options.removeAbstractMethods,
          options.removeClosureAsserts);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Special case optimizations for closure functions. */
  private final PassFactory closureOptimizePrimitives =
      new PassFactory("closureOptimizePrimitives", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new ClosureOptimizePrimitives(
              compiler,
              compiler.getOptions().propertyRenaming == PropertyRenamingPolicy.ALL_UNQUOTED,
              compiler.getOptions().getOutputFeatureSet().contains(ES6));
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Puts global symbols into a single object. */
  private final PassFactory rescopeGlobalSymbols =
      new PassFactory("rescopeGlobalSymbols", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return new RescopeGlobalSymbols(
          compiler,
          options.renamePrefixNamespace,
          options.renamePrefixNamespaceAssumeCrossChunkNames);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Collapses names in the global scope. */
  private final PassFactory collapseProperties =
      new PassFactory(PassNames.COLLAPSE_PROPERTIES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new CollapsePropertiesWithModuleSupport(compiler, options.getPropertyCollapseLevel(), sourceFileName, varRenameMapFile);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Rewrite properties as variables. */
  private final PassFactory collapseObjectLiterals =
      new PassFactory(PassNames.COLLAPSE_OBJECT_LITERALS, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new InlineObjectLiterals(compiler, compiler.getUniqueNameIdSupplier());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Disambiguate property names based on the coding convention. */
  private final PassFactory disambiguatePrivateProperties =
      new PassFactory(PassNames.DISAMBIGUATE_PRIVATE_PROPERTIES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new DisambiguatePrivateProperties(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Disambiguate property names based on type information. */
  private final PassFactory disambiguateProperties =
      new PassFactory(PassNames.DISAMBIGUATE_PROPERTIES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new DisambiguateProperties(compiler, options.propertyInvalidationErrors);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Rewrite instance methods as static methods, to make them easier to inline. */
  private final PassFactory devirtualizePrototypeMethods =
      new PassFactory(PassNames.DEVIRTUALIZE_PROTOTYPE_METHODS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          OptimizeCalls passes = new OptimizeCalls(compiler);
          passes.addPass(new DevirtualizePrototypeMethods(compiler));
          return passes;
        }

        @Override
        protected FeatureSet featureSet() {
          return ES5;
        }
      };

  /**
   * Optimizes unused function arguments, unused return values, and inlines constant parameters.
   * Also runs RemoveUnusedCode.
   */
  private final PassFactory optimizeCalls =
      new PassFactory(PassNames.OPTIMIZE_CALLS, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          OptimizeCalls passes = new OptimizeCalls(compiler);
          // Remove unused return values.
          passes.addPass(new OptimizeReturns(compiler));
          // Remove all parameters that are constants or unused.
          passes.addPass(new OptimizeParameters(compiler));
          return passes;
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /**
   * Look for function calls that are pure, and annotate them
   * that way.
   */
  private final PassFactory markPureFunctions =
      new PassFactory("markPureFunctions", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return new PureFunctionIdentifier.Driver(
          compiler, options.debugFunctionSideEffectsPath);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Look for function calls that have no side effects, and annotate them that way. */
  private final PassFactory markNoSideEffectCalls =
      new PassFactory(PassNames.MARK_NO_SIDE_EFFECT_CALLS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new MarkNoSideEffectCalls(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES5;
        }
      };

  /** Inlines variables heuristically. */
  private final PassFactory inlineVariables =
      new PassFactory(PassNames.INLINE_VARIABLES, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          InlineVariables.Mode mode;
          if (options.inlineVariables) {
            mode = InlineVariables.Mode.ALL;
          } else if (options.inlineLocalVariables) {
            mode = InlineVariables.Mode.LOCALS_ONLY;
          } else {
            throw new IllegalStateException("No variable inlining option set.");
          }
          return new InlineVariables(compiler, mode, true);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Inlines variables that are marked as constants. */
  private final PassFactory inlineConstants =
      new PassFactory("inlineConstants", false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new InlineVariables(compiler, InlineVariables.Mode.CONSTANTS_ONLY, true);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Use data flow analysis to remove dead branches. */
  private final PassFactory removeUnreachableCode =
      new PassFactory(PassNames.REMOVE_UNREACHABLE_CODE, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new UnreachableCodeElimination(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8;
        }
      };

  /**
   * Use data flow analysis to remove dead branches.
   */
  private final PassFactory removeUnusedPolyfills =
      new PassFactory("removeUnusedPolyfills", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return new RemoveUnusedPolyfills(compiler);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Inlines simple methods, like getters */
  private final PassFactory inlineSimpleMethods =
      new PassFactory("inlineSimpleMethods", false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new InlineSimpleMethods(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Kills dead assignments. */
  private final PassFactory deadAssignmentsElimination =
      new PassFactory(PassNames.DEAD_ASSIGNMENT_ELIMINATION, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new DeadAssignmentsElimination(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Kills dead property assignments. */
  private final PassFactory deadPropertyAssignmentElimination =
      new PassFactory("deadPropertyAssignmentElimination", false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new DeadPropertyAssignmentElimination(compiler);
        }

        @Override
        public FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Inlines function calls. */
  private final PassFactory inlineFunctions =
      new PassFactory(PassNames.INLINE_FUNCTIONS, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new InlineFunctions(
              compiler,
              compiler.getUniqueNameIdSupplier(),
              options.getInlineFunctionsLevel(),
              options.assumeStrictThis() || options.expectStrictModeInput(),
              options.assumeClosuresOnlyCaptureReferences,
              options.maxFunctionSizeAfterInlining);
        }

        @Override
        public FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Inlines constant properties. */
  private final PassFactory inlineProperties =
      new PassFactory(PassNames.INLINE_PROPERTIES, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new InlineProperties(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES5;
        }
      };

  private final PassFactory removeUnusedCodeOnce = getRemoveUnusedCode(true /* isOneTimePass */);
  private final PassFactory removeUnusedCode = getRemoveUnusedCode(false /* isOneTimePass */);

  private PassFactory getRemoveUnusedCode(boolean isOneTimePass) {
    /** Removes variables that are never used. */
    return new PassFactory(PassNames.REMOVE_UNUSED_CODE, isOneTimePass) {
      @Override
      protected CompilerPass create(AbstractCompiler compiler) {
        boolean preserveAnonymousFunctionNames =
            options.anonymousFunctionNaming != AnonymousFunctionNamingPolicy.OFF;
        return new RemoveUnusedCode.Builder(compiler)
            .removeLocalVars(options.removeUnusedLocalVars)
            .removeGlobals(options.removeUnusedVars)
            .preserveFunctionExpressionNames(preserveAnonymousFunctionNames)
            .removeUnusedPrototypeProperties(options.removeUnusedPrototypeProperties)
            .allowRemovalOfExternProperties(options.removeUnusedPrototypePropertiesInExterns)
            .removeUnusedThisProperties(options.isRemoveUnusedClassProperties())
            .removeUnusedObjectDefinePropertiesDefinitions(options.isRemoveUnusedClassProperties())
            .removeUnusedConstructorProperties(options.isRemoveUnusedConstructorProperties())
            .build();
      }

      @Override
      public FeatureSet featureSet() {
        return ES8_MODULES;
      }
    };

  }

  /** Move global symbols to a deeper common module */
  private final PassFactory crossModuleCodeMotion =
      new PassFactory(PassNames.CROSS_CHUNK_CODE_MOTION, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new CrossChunkCodeMotion(
              compiler,
              compiler.getModuleGraph(),
              options.parentChunkCanSeeSymbolsDeclaredInChildren);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Move methods to a deeper common module */
  private final PassFactory crossModuleMethodMotion =
      new PassFactory(PassNames.CROSS_CHUNK_METHOD_MOTION, false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new CrossChunkMethodMotion(
              compiler,
              compiler.getCrossModuleIdGenerator(),
              // Only move properties in externs if we're not treating
              // them as exports.
              options.removeUnusedPrototypePropertiesInExterns,
              options.crossChunkCodeMotionNoStubMethods);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** A data-flow based variable inliner. */
  private final PassFactory flowSensitiveInlineVariables =
      new PassFactory(PassNames.FLOW_SENSITIVE_INLINE_VARIABLES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new FlowSensitiveInlineVariables(compiler);
        }

        @Override
        public FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Uses register-allocation algorithms to use fewer variables. */
  private final PassFactory coalesceVariableNames =
      new PassFactory(PassNames.COALESCE_VARIABLE_NAMES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new CoalesceVariableNames(compiler, options.generatePseudoNames);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Some simple, local collapses (e.g., {@code var x; var y;} becomes {@code var x,y;}. */
  private final PassFactory exploitAssign =
      new PassFactory(PassNames.EXPLOIT_ASSIGN, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new PeepholeOptimizationsPass(compiler, getName(), new ExploitAssigns());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Some simple, local collapses (e.g., {@code var x; var y;} becomes {@code var x,y;}. */
  private final PassFactory collapseVariableDeclarations =
      new PassFactory(PassNames.COLLAPSE_VARIABLE_DECLARATIONS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new CollapseVariableDeclarations(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Extracts common sub-expressions. */
  private final PassFactory extractPrototypeMemberDeclarations =
      new PassFactory(PassNames.EXTRACT_PROTOTYPE_MEMBER_DECLARATIONS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          Pattern pattern;
          switch (options.extractPrototypeMemberDeclarations) {
            case USE_GLOBAL_TEMP:
              pattern = Pattern.USE_GLOBAL_TEMP;
              break;
            case USE_IIFE:
              pattern = Pattern.USE_IIFE;
              break;
            default:
              throw new IllegalStateException("unexpected");
          }

          return new ExtractPrototypeMemberDeclarations(compiler, pattern);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8;
        }
      };

  /** Rewrites common function definitions to be more compact. */
  private final PassFactory rewriteFunctionExpressions =
      new PassFactory(PassNames.REWRITE_FUNCTION_EXPRESSIONS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new FunctionRewriter(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return FeatureSet.latest();
        }
      };

  /** Collapses functions to not use the VAR keyword. */
  private final PassFactory collapseAnonymousFunctions =
      new PassFactory(PassNames.COLLAPSE_ANONYMOUS_FUNCTIONS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new CollapseAnonymousFunctions(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Moves function declarations to the top, to simulate actual hoisting. */
  private final PassFactory moveFunctionDeclarations =
      new PassFactory(PassNames.MOVE_FUNCTION_DECLARATIONS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new MoveFunctionDeclarations(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8;
        }
      };

  private final PassFactory nameUnmappedAnonymousFunctions =
      new PassFactory(PassNames.NAME_ANONYMOUS_FUNCTIONS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new NameAnonymousFunctions(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  private final PassFactory nameMappedAnonymousFunctions =
      new PassFactory(PassNames.NAME_ANONYMOUS_FUNCTIONS, true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              NameAnonymousFunctionsMapped naf =
                  new NameAnonymousFunctionsMapped(
                      compiler, options.inputAnonymousFunctionNamingMap);
              naf.process(externs, root);
              compiler.setAnonymousFunctionNameMap(naf.getFunctionMap());
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /**
   * Alias string literals with global variables, to avoid creating lots of
   * transient objects.
   */
  private final PassFactory aliasStrings = new PassFactory("aliasStrings", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return new AliasStrings(
          compiler,
          compiler.getModuleGraph(),
          options.aliasAllStrings ? null : options.aliasableStrings,
          options.aliasStringsBlacklist,
          options.outputJsStringUsage);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Handling for the ObjectPropertyString primitive. */
  private final PassFactory objectPropertyStringPostprocess =
      new PassFactory("ObjectPropertyStringPostprocess", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return new ObjectPropertyStringPostprocess(compiler);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES5;
    }
  };

  /**
   * Renames properties so that the two properties that never appear on the same object get the same
   * name.
   */
  private final PassFactory ambiguateProperties =
      new PassFactory(PassNames.AMBIGUATE_PROPERTIES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new AmbiguateProperties(
              compiler,
              options.getPropertyReservedNamingFirstChars(),
              options.getPropertyReservedNamingNonFirstChars());
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Mark the point at which the normalized AST assumptions no longer hold. */
  private final PassFactory markUnnormalized =
      new PassFactory("markUnnormalized", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              compiler.setLifeCycleStage(LifeCycleStage.RAW);
            }
          };
        }

        @Override
        protected FeatureSet featureSet() {
          return FeatureSet.latest();
        }
      };

  private final PassFactory normalize =
      new PassFactory(PassNames.NORMALIZE, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new Normalize(compiler, false);
        }

        @Override
        protected FeatureSet featureSet() {
          // TODO(johnlenz): Update this and gatherRawExports to latest()
          return ES8_MODULES;
        }
      };

  private final PassFactory externExports =
      new PassFactory(PassNames.EXTERN_EXPORTS, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new ExternExportsPass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES5;
        }
      };

  /** Denormalize the AST for code generation. */
  private final PassFactory denormalize = new PassFactory("denormalize", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return new Denormalize(compiler);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Inverting name normalization. */
  private final PassFactory invertContextualRenaming =
      new PassFactory("invertContextualRenaming", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return MakeDeclaredNamesUnique.getContextualRenameInverter(compiler);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Renames properties. */
  private final PassFactory renameProperties =
      new PassFactory("renameProperties", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          checkState(options.propertyRenaming == PropertyRenamingPolicy.ALL_UNQUOTED);
          final VariableMap prevPropertyMap = options.inputPropertyMap;
          return new CompilerPass() {
            @Override
            public void process(Node externs, Node root) {
              RenamePropertiesWithModuleSupport rprop =
                  new RenamePropertiesWithModuleSupport(
                      compiler,
                      options.generatePseudoNames,
                      prevPropertyMap,
                      options.getPropertyReservedNamingFirstChars(),
                      options.getPropertyReservedNamingNonFirstChars(),
                      options.nameGenerator);
              rprop.process(externs, root);
              compiler.setPropertyMap(rprop.getPropertyMap());
            }
          };
        }
        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Renames variables. */
  private final PassFactory renameVars = new PassFactory("renameVars", true) {
    @Override
    protected CompilerPass create(final AbstractCompiler compiler) {
      final VariableMap prevVariableMap = options.inputVariableMap;
      return new CompilerPass() {
        @Override public void process(Node externs, Node root) {
          compiler.setVariableMap(runVariableRenaming(
              compiler, prevVariableMap, externs, root));
        }
      };
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  private VariableMap runVariableRenaming(
      AbstractCompiler compiler, VariableMap prevVariableMap,
      Node externs, Node root) {
    char[] reservedChars =
        options.anonymousFunctionNaming.getReservedCharacters();
    boolean preserveAnonymousFunctionNames =
        options.anonymousFunctionNaming != AnonymousFunctionNamingPolicy.OFF;
    Set<String> reservedNames = new HashSet<String>();
    if (options.renamePrefixNamespace != null) {
      // don't use the prefix name as a global symbol.
      reservedNames.add(options.renamePrefixNamespace);
    }
    reservedNames.addAll(compiler.getExportedNames());
    reservedNames.addAll(ParserRunner.getReservedVars());
    RenameVarsWithModuleSupport rn = new RenameVarsWithModuleSupport(
        compiler,
        options.renamePrefix,
        options.variableRenaming == VariableRenamingPolicy.LOCAL,
        preserveAnonymousFunctionNames,
        options.generatePseudoNames,
        options.shadowVariables,
        options.preferStableNames,
        prevVariableMap,
        reservedChars,
        reservedNames,
        options.nameGenerator);
    rn.process(externs, root);
    return rn.getVariableMap();
  }

  /** Renames labels */
  private final PassFactory renameLabels =
      new PassFactory("renameLabels", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new RenameLabels(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Convert bracket access to dot access */
  private final PassFactory convertToDottedProperties =
      new PassFactory(PassNames.CONVERT_TO_DOTTED_PROPERTIES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new ConvertToDottedProperties(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  private final PassFactory checkAstValidity =
      new PassFactory("checkAstValidity", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new AstValidator(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return FeatureSet.latest();
        }
      };

  /** Checks that all variables are defined. */
  private final PassFactory varCheckValidity =
      new PassFactory("varCheckValidity", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new VarCheck(compiler, true);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Adds instrumentations according to an instrumentation template. */
  private final PassFactory instrumentFunctions =
      new PassFactory("instrumentFunctions", true) {
    @Override
    protected CompilerPass create(final AbstractCompiler compiler) {
      return new InstrumentFunctions(
          compiler, compiler.getFunctionNames(),
          options.instrumentationTemplate, options.appNameStr);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES5;
    }
  };

  private final PassFactory instrumentForCodeCoverage =
      new PassFactory("instrumentForCodeCoverage", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          // TODO(johnlenz): make global instrumentation an option
          if (options.instrumentBranchCoverage) {
            return new CoverageInstrumentationPass(
                compiler, CoverageReach.CONDITIONAL, InstrumentOption.BRANCH_ONLY);
          } else {
            return new CoverageInstrumentationPass(compiler, CoverageReach.CONDITIONAL);
          }
        }

        @Override
        protected FeatureSet featureSet() {
          return ES5;
        }
      };

  /** Extern property names gathering pass. */
  private final PassFactory gatherExternProperties =
      new PassFactory("gatherExternProperties", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new GatherExternProperties(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
     };

  /**
   * Runs custom passes that are designated to run at a particular time.
   */
  private PassFactory getCustomPasses(
      final CustomPassExecutionTime executionTime) {
    return new PassFactory("runCustomPasses", true) {
      @Override
      protected CompilerPass create(final AbstractCompiler compiler) {
        return runInSerial(options.customPasses.get(executionTime));
      }

      @Override
      protected FeatureSet featureSet() {
        return ES5;
      }
    };
  }

  /** Create a compiler pass that runs the given passes in serial. */
  private static CompilerPass runInSerial(
      final Collection<CompilerPass> passes) {
    return new CompilerPass() {
      @Override public void process(Node externs, Node root) {
        for (CompilerPass pass : passes) {
          pass.process(externs, root);
        }
      }
    };
  }

  @VisibleForTesting
  static Map<String, Node> getAdditionalReplacements(CompilerOptions options) {
    Map<String, Node> additionalReplacements = new HashMap<String, Node>();

    if (options.markAsCompiled || options.closurePass) {
      additionalReplacements.put(COMPILED_CONSTANT_NAME, IR.trueNode());
    }

    if (options.closurePass && options.locale != null) {
      additionalReplacements.put(CLOSURE_LOCALE_CONSTANT_NAME,
          IR.string(options.locale));
    }

    return additionalReplacements;
  }

  /** Rewrites Polymer({}) */
  private final HotSwapPassFactory polymerPass =
      new HotSwapPassFactory("polymerPass") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new PolymerPass(
              compiler,
              compiler.getOptions().polymerVersion,
              compiler.getOptions().polymerExportPolicy,
              compiler.getOptions().propertyRenaming == PropertyRenamingPolicy.ALL_UNQUOTED);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  private final PassFactory chromePass = new PassFactory("chromePass", true) {
    @Override
    protected CompilerPass create(AbstractCompiler compiler) {
      return new ChromePass(compiler);
    }

    @Override
    protected FeatureSet featureSet() {
      return ES8_MODULES;
    }
  };

  /** Rewrites the super accessors calls to support Dart Dev Compiler output. */
  private final HotSwapPassFactory dartSuperAccessorsPass =
      new HotSwapPassFactory("dartSuperAccessorsPass") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new DartSuperAccessorsPass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  /** Rewrites J2CL constructs to be more optimizable. */
  private final PassFactory j2clConstantHoisterPass =
      new PassFactory("j2clConstantHoisterPass", false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new J2clConstantHoisterPass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Optimizes J2CL clinit methods. */
  private final PassFactory j2clClinitPass =
      new PassFactory("j2clClinitPass", false) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          List<Node> changedScopeNodes = compiler.getChangedScopeNodesForPass(getName());
          return new J2clClinitPrunerPass(compiler, changedScopeNodes);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Rewrites J2CL constructs to be more optimizable. */
  private final PassFactory j2clPropertyInlinerPass =
      new PassFactory("j2clES6Pass", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new J2clPropertyInlinerPass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Rewrites J2CL constructs to be more optimizable. */
  private final PassFactory j2clPass =
      new PassFactory("j2clPass", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new J2clPass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Rewrites J2CL constructs to be more optimizable. */
  private final PassFactory j2clUtilGetDefineRewriterPass =
      new PassFactory("j2clUtilGetDefineRewriterPass", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new J2clUtilGetDefineRewriterPass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  private final PassFactory j2clAssertRemovalPass =
      new PassFactory("j2clAssertRemovalPass", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new J2clAssertRemovalPass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  private final PassFactory j2clSourceFileChecker =
      new PassFactory("j2clSourceFileChecker", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new J2clSourceFileChecker(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return FeatureSet.latest();
        }
      };

  private final PassFactory j2clChecksPass =
      new PassFactory("j2clChecksPass", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new J2clChecksPass(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES2018;
        }
      };

  private final PassFactory checkConformance =
      new PassFactory("checkConformance", true) {
        @Override
        protected CompilerPass create(final AbstractCompiler compiler) {
          return new CheckConformance(
              compiler, ImmutableList.copyOf(options.getConformanceConfigs()));
        }

        @Override
        protected FeatureSet featureSet() {
          return TYPE_CHECK_SUPPORTED;
        }
      };

  /** Optimizations that output ES6 features. */
  private final PassFactory optimizeToEs6 =
      new PassFactory("optimizeToEs6", true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new SubstituteEs6Syntax(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  /** Rewrites goog.module in whitespace only mode */
  private final HotSwapPassFactory whitespaceWrapGoogModules =
      new HotSwapPassFactory("whitespaceWrapGoogModules") {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new WhitespaceWrapGoogModules(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES8_MODULES;
        }
      };

  private final PassFactory rewriteCommonJsModules =
      new PassFactory(PassNames.REWRITE_COMMON_JS_MODULES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new ProcessCommonJSModules(compiler);
        }

        @Override
        public FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  private final PassFactory rewriteScriptsToEs6Modules =
      new PassFactory(PassNames.REWRITE_SCRIPTS_TO_ES6_MODULES, true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new Es6RewriteScriptsToModules(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  private final HotSwapPassFactory gatherModuleMetadataPass =
      new HotSwapPassFactory(PassNames.GATHER_MODULE_METADATA) {
        @Override
        protected HotSwapCompilerPass create(AbstractCompiler compiler) {
          return new GatherModuleMetadata(
              compiler, options.processCommonJSModules, options.moduleResolutionMode);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };

  private final PassFactory gatherGettersAndSetters =
      new PassFactory(PassNames.GATHER_GETTERS_AND_SETTERS, /* isOneTimePass= */ true) {
        @Override
        protected CompilerPass create(AbstractCompiler compiler) {
          return new GatherGettersAndSetterProperties(compiler);
        }

        @Override
        protected FeatureSet featureSet() {
          return ES_NEXT;
        }
      };
}
