All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.TranspilationPasses Maven / Gradle / Ivy

/*
 * Copyright 2016 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.checkState;

import com.google.javascript.jscomp.CompilerOptions.ChunkOutputType;
import com.google.javascript.jscomp.Es6RewriteDestructuring.ObjectDestructuringRewriteMode;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.rhino.Node;

/** Provides a single place to manage transpilation passes. */
public class TranspilationPasses {

  private TranspilationPasses() {}

  public static void addEs6ModulePass(
      PassListBuilder passes,
      final PreprocessorSymbolTable.CachedInstanceFactory preprocessorTableFactory) {
    passes.maybeAdd(
        PassFactory.builder()
            .setName("es6RewriteModule")
            .setInternalFactory(
                (compiler) -> {
                  preprocessorTableFactory.maybeInitialize(compiler);
                  return new Es6RewriteModules(
                      compiler,
                      compiler.getModuleMetadataMap(),
                      compiler.getModuleMap(),
                      preprocessorTableFactory.getInstanceOrNull(),
                      compiler.getTopScope(),
                      compiler.getOptions().getChunkOutputType());
                })
            .build());
  }

  public static void addTranspilationRuntimeLibraries(PassListBuilder passes) {
    // Inject runtime libraries needed for the transpilation we will have to do. Should run before
    // typechecking.
    passes.maybeAdd(injectTranspilationRuntimeLibraries);
  }

  public static void addEs6ModuleToCjsPass(PassListBuilder passes) {
    passes.maybeAdd(es6RewriteModuleToCjs);
  }

  public static void addEs6RewriteImportPathPass(PassListBuilder passes) {
    passes.maybeAdd(es6RelativizeImportPaths);
  }

  /**
   * Adds transpilation passes that should run at the beginning of the optimization phase. Passes
   * added in this method either use {@code TranspilationPasses.processTranspile} or early-exit by
   * checking their feature in the script's featureset. So they will only get run if the feature
   * they're responsible for removing exists in the script.
   */
  public static void addEarlyOptimizationTranspilationPasses(
      PassListBuilder passes, CompilerOptions options) {

    passes.maybeAdd(reportUntranspilableFeatures);

    // Note that we detect feature by feature rather than by yearly languages
    // in order to handle FeatureSet.BROWSER_2020, which is ES2019 without the new RegExp features.
    // However, RegExp features are not transpiled, and this does not imply that we allow arbitrary
    // selection of features to transpile.  They still must be done in chronological order based on.
    // This greatly simplifies testing and the requirements for the transpilation passes.

    if (options.needsTranspilationOf(Feature.REGEXP_FLAG_D)) {
      passes.maybeAdd(
          createFeatureRemovalPass(
              "markEs2022FeaturesNotRequiringTranspilationAsRemoved", Feature.REGEXP_FLAG_D));
    }

    if (options.needsTranspilationOf(Feature.PUBLIC_CLASS_FIELDS)
        || options.needsTranspilationOf(Feature.CLASS_STATIC_BLOCK)
        || options.needsTranspilationOf(Feature.CLASSES)) {
      // Make sure that a variable is created to hold every class definition.
      // This allows us to add static properties and methods by adding properties
      // to that variable.
      passes.maybeAdd(es6RewriteClassExtends);
      passes.maybeAdd(es6ExtractClasses);
    }

    if (options.needsTranspilationOf(Feature.PUBLIC_CLASS_FIELDS)
        || options.needsTranspilationOf(Feature.CLASS_STATIC_BLOCK)) {
      passes.maybeAdd(rewriteClassMembers);
    }

    if (options.needsTranspilationOf(Feature.NUMERIC_SEPARATOR)) {
      // Numeric separators are flagged as present by the parser,
      // but never actually represented in the AST.
      // The only thing we need to do is mark them as not present in the AST.
      passes.maybeAdd(
          createFeatureRemovalPass("markNumericSeparatorsRemoved", Feature.NUMERIC_SEPARATOR));
    }

    if (options.needsTranspilationOf(Feature.LOGICAL_ASSIGNMENT)) {
      passes.maybeAdd(rewriteLogicalAssignmentOperatorsPass);
    }

    if (options.needsTranspilationOf(Feature.OPTIONAL_CHAINING)) {
      passes.maybeAdd(rewriteOptionalChainingOperator);
    }

    if (options.needsTranspilationOf(Feature.BIGINT)) {
      passes.maybeAdd(createFeatureRemovalPass("markBigintsRemoved", Feature.BIGINT));
    }

    if (options.needsTranspilationOf(Feature.NULL_COALESCE_OP)) {
      passes.maybeAdd(rewriteNullishCoalesceOperator);
    }

    if (options.needsTranspilationOf(Feature.OPTIONAL_CATCH_BINDING)) {
      passes.maybeAdd(rewriteCatchWithNoBinding);
    }

    if (options.getChunkOutputType() != ChunkOutputType.ES_MODULES) {
      // Default output mode of JSCompiler is a script, unless chunkOutputType is set to
      // `ES_MODULES` where each output chunk is an ES module.
      passes.maybeAdd(createFeatureRemovalPass("markModulesRemoved", Feature.MODULES));
      // Since import.meta cannot be transpiled, it is passed-through when the output format
      // is a module. Otherwise it must be marked removed.
      passes.maybeAdd(createFeatureRemovalPass("markImportMetaRemoved", Feature.IMPORT_META));
      // Dynamic imports are preserved for open source output only when the chunk output type is
      // ES_MODULES
      passes.maybeAdd(createFeatureRemovalPass("markDynamicImportRemoved", Feature.DYNAMIC_IMPORT));
    }

    if (options.needsTranspilationOf(Feature.FOR_AWAIT_OF)
        || options.needsTranspilationOf(Feature.ASYNC_GENERATORS)) {
      passes.maybeAdd(rewriteAsyncIteration);
    }

    if (options.needsTranspilationOf(Feature.OBJECT_LITERALS_WITH_SPREAD)
        || options.needsTranspilationOf(Feature.OBJECT_PATTERN_REST)) {
      passes.maybeAdd(rewriteObjectSpread);
      if (!options.needsTranspilationOf(Feature.OBJECT_DESTRUCTURING)
          && options.needsTranspilationOf(Feature.OBJECT_PATTERN_REST)) {
        // We only need to transpile away object destructuring that uses `...`, rather than
        // all destructuring.
        // For this to work correctly for object destructuring in parameter lists and variable
        // declarations, we need to normalize them a bit first.
        passes.maybeAdd(es6RenameVariablesInParamLists);
        passes.maybeAdd(es6SplitVariableDeclarations);
        passes.maybeAdd(
            getEs6RewriteDestructuring(ObjectDestructuringRewriteMode.REWRITE_OBJECT_REST));
      }
    }

    if (options.needsTranspilationOf(Feature.ASYNC_FUNCTIONS)) {
      passes.maybeAdd(rewriteAsyncFunctions);
    }

    if (options.needsTranspilationOf(Feature.EXPONENT_OP)) {
      passes.maybeAdd(rewriteExponentialOperator);
    }

    if (options.needsTranspilationFrom(
        FeatureSet.BARE_MINIMUM.with(
            Feature.BINARY_LITERALS,
            Feature.OCTAL_LITERALS,
            Feature.REGEXP_FLAG_U,
            Feature.REGEXP_FLAG_Y))) {
      // Binary and octal literals are effectively transpiled by the parser.
      // There's no transpilation we can do for the new regexp flags.
      passes.maybeAdd(
          createFeatureRemovalPass(
              "markEs6FeaturesNotRequiringTranspilationAsRemoved",
              Feature.BINARY_LITERALS,
              Feature.OCTAL_LITERALS,
              Feature.REGEXP_FLAG_U,
              Feature.REGEXP_FLAG_Y));
    }

    if (options.needsTranspilationOf(Feature.EXTENDED_OBJECT_LITERALS)) {
      passes.maybeAdd(es6NormalizeShorthandProperties);
    }
    if (options.needsTranspilationOf(Feature.CLASSES)) {
      passes.maybeAdd(es6ConvertSuper);
    }
    if (options.needsTranspilationFrom(
        FeatureSet.BARE_MINIMUM.with(Feature.ARRAY_DESTRUCTURING, Feature.OBJECT_DESTRUCTURING))) {
      passes.maybeAdd(es6RenameVariablesInParamLists);
      passes.maybeAdd(es6SplitVariableDeclarations);
      passes.maybeAdd(
          getEs6RewriteDestructuring(ObjectDestructuringRewriteMode.REWRITE_ALL_OBJECT_PATTERNS));
    }
    if (options.needsTranspilationOf(Feature.NEW_TARGET)) {
      passes.maybeAdd(rewriteNewDotTarget);
    }
    if (options.needsTranspilationOf(Feature.ARROW_FUNCTIONS)) {
      passes.maybeAdd(es6RewriteArrowFunction);
    }
    if (options.needsTranspilationOf(Feature.CLASSES)) {
      passes.maybeAdd(es6RewriteClass);
    }
    if (options.needsTranspilationFrom(
        FeatureSet.BARE_MINIMUM.with(Feature.REST_PARAMETERS, Feature.SPREAD_EXPRESSIONS))) {
      passes.maybeAdd(es6RewriteRestAndSpread);
    }
  }

  /**
   * Adds transpilation passes that should not be run until after normalization has been done.
   * Passes added in this method either use {@code TranspilationPasses.processTranspile} or
   * early-exit by checking their feature in the script's featureset. So they will only get run if
   * the feature they're responsible for removing exists in the script.
   */
  public static void addPostNormalizationTranspilationPasses(
      PassListBuilder passes, CompilerOptions options) {
    // TODO(b/197349249): Move passes from `addEarlyOptimizationTranspilationPasses()` to here
    // until that method can be deleted as a no-op.

    if (options.needsTranspilationFrom(
        FeatureSet.BARE_MINIMUM.with(
            Feature.COMPUTED_PROPERTIES, Feature.MEMBER_DECLARATIONS, Feature.TEMPLATE_LITERALS))) {
      passes.maybeAdd(lateConvertEs6ToEs3);
    }

    if (options.needsTranspilationOf(Feature.FOR_OF)) {
      passes.maybeAdd(es6ForOf);
    }

    if (options.needsTranspilationOf(Feature.BLOCK_SCOPED_FUNCTION_DECLARATION)) {
      passes.maybeAdd(rewriteBlockScopedFunctionDeclaration);
    }

    if (options.needsTranspilationFrom(
        FeatureSet.BARE_MINIMUM.with(Feature.LET_DECLARATIONS, Feature.CONST_DECLARATIONS))) {
      passes.maybeAdd(rewriteBlockScopedDeclaration);
    }

    if (options.needsTranspilationOf(Feature.GENERATORS)) {
      passes.maybeAdd(rewriteGenerators);
    }
    // This pass must run at the end of all transpiler passes. It validates that only supported
    // features remain in the compiler's featureSet
    passes.maybeAdd(
        TranspilationPasses.createPostTranspileUnsupportedFeaturesRemovedCheck(
            "postTranspileUnsupportedFeaturesRemovedCheck"));
  }

  /** Adds the pass to inject ES2015 polyfills, which goes after the late ES2015 passes. */
  public static void addRewritePolyfillPass(PassListBuilder passes) {
    passes.maybeAdd(rewritePolyfills);
  }

  /** Rewrites ES6 modules */
  private static final PassFactory es6RewriteModuleToCjs =
      PassFactory.builder()
          .setName("es6RewriteModuleToCjs")
          .setInternalFactory(Es6RewriteModulesToCommonJsModules::new)
          .build();

  /** Rewrites ES6 modules import paths to be browser compliant */
  private static final PassFactory es6RelativizeImportPaths =
      PassFactory.builder()
          .setName("es6RelativizeImportPaths")
          .setInternalFactory(Es6RelativizeImportPaths::new)
          .build();

  private static final PassFactory rewriteAsyncFunctions =
      PassFactory.builder()
          .setName("rewriteAsyncFunctions")
          .setInternalFactory(RewriteAsyncFunctions::create)
          .build();

  private static final PassFactory rewriteAsyncIteration =
      PassFactory.builder()
          .setName("rewriteAsyncIteration")
          .setInternalFactory(RewriteAsyncIteration::create)
          .build();

  private static final PassFactory rewriteObjectSpread =
      PassFactory.builder()
          .setName("rewriteObjectSpread")
          .setInternalFactory(RewriteObjectSpread::new)
          .build();

  private static final PassFactory rewriteCatchWithNoBinding =
      PassFactory.builder()
          .setName("rewriteCatchWithNoBinding")
          .setInternalFactory(RewriteCatchWithNoBinding::new)
          .build();

  private static final PassFactory rewriteNewDotTarget =
      PassFactory.builder()
          .setName("rewriteNewDotTarget")
          .setInternalFactory(RewriteNewDotTarget::new)
          .build();

  private static final PassFactory rewriteExponentialOperator =
      PassFactory.builder()
          .setName("rewriteExponentialOperator")
          .setInternalFactory(Es7RewriteExponentialOperator::new)
          .build();

  private static final PassFactory es6NormalizeShorthandProperties =
      PassFactory.builder()
          .setName("es6NormalizeShorthandProperties")
          .setInternalFactory(Es6NormalizeShorthandProperties::new)
          .build();

  static final PassFactory es6RewriteClassExtends =
      PassFactory.builder()
          .setName(PassNames.ES6_REWRITE_CLASS_EXTENDS)
          .setInternalFactory(Es6RewriteClassExtendsExpressions::new)
          .build();

  static final PassFactory es6ExtractClasses =
      PassFactory.builder()
          .setName(PassNames.ES6_EXTRACT_CLASSES)
          .setInternalFactory(Es6ExtractClasses::new)
          .build();

  static final PassFactory rewriteClassMembers =
      PassFactory.builder()
          .setName("RewriteClassMembers")
          .setInternalFactory(RewriteClassMembers::new)
          .build();

  static final PassFactory es6RewriteClass =
      PassFactory.builder()
          .setName("Es6RewriteClass")
          .setInternalFactory(Es6RewriteClass::new)
          .build();

  static final PassFactory getEs6RewriteDestructuring(ObjectDestructuringRewriteMode rewriteMode) {
    return PassFactory.builder()
        .setName("Es6RewriteDestructuring")
        .setInternalFactory(
            (compiler) ->
                new Es6RewriteDestructuring.Builder(compiler)
                    .setDestructuringRewriteMode(rewriteMode)
                    .build())
        .build();
  }

  static final PassFactory es6RenameVariablesInParamLists =
      PassFactory.builder()
          .setName("Es6RenameVariablesInParamLists")
          .setInternalFactory(Es6RenameVariablesInParamLists::new)
          .build();

  static final PassFactory es6RewriteArrowFunction =
      PassFactory.builder()
          .setName("Es6RewriteArrowFunction")
          .setInternalFactory(Es6RewriteArrowFunction::new)
          .build();

  static final PassFactory rewritePolyfills =
      PassFactory.builder()
          .setName("RewritePolyfills")
          .setInternalFactory(
              (compiler) ->
                  new RewritePolyfills(
                      compiler,
                      compiler.getOptions().getRewritePolyfills(),
                      compiler.getOptions().getIsolatePolyfills()))
          .build();

  static final PassFactory es6SplitVariableDeclarations =
      PassFactory.builder()
          .setName("Es6SplitVariableDeclarations")
          .setInternalFactory(Es6SplitVariableDeclarations::new)
          .build();

  static final PassFactory es6ConvertSuper =
      PassFactory.builder()
          .setName("es6ConvertSuper")
          .setInternalFactory(Es6ConvertSuper::new)
          .build();

  /** Injects runtime library code needed for transpiled ES2015+ code. */
  static final PassFactory injectTranspilationRuntimeLibraries =
      PassFactory.builder()
          .setName("es6InjectRuntimeLibraries")
          .setInternalFactory(InjectTranspilationRuntimeLibraries::new)
          .build();

  /** Transpiles REST parameters and SPREAD in both array literals and function calls. */
  static final PassFactory es6RewriteRestAndSpread =
      PassFactory.builder()
          .setName("es6RewriteRestAndSpread")
          .setInternalFactory(Es6RewriteRestAndSpread::new)
          .build();

  /**
   * Does the main ES2015 to ES3 conversion. There are a few other passes which run before this one,
   * to convert constructs which are not converted by this pass.
   */
  static final PassFactory lateConvertEs6ToEs3 =
      PassFactory.builder()
          .setName("lateConvertEs6")
          .setInternalFactory(LateEs6ToEs3Converter::new)
          .build();

  static final PassFactory es6ForOf =
      PassFactory.builder().setName("es6ForOf").setInternalFactory(Es6ForOfConverter::new).build();

  static final PassFactory rewriteBlockScopedFunctionDeclaration =
      PassFactory.builder()
          .setName("Es6RewriteBlockScopedFunctionDeclaration")
          .setInternalFactory(Es6RewriteBlockScopedFunctionDeclaration::new)
          .build();

  static final PassFactory rewriteBlockScopedDeclaration =
      PassFactory.builder()
          .setName("Es6RewriteBlockScopedDeclaration")
          .setInternalFactory(Es6RewriteBlockScopedDeclaration::new)
          .build();

  static final PassFactory rewriteGenerators =
      PassFactory.builder()
          .setName("rewriteGenerators")
          .setInternalFactory(Es6RewriteGenerators::new)
          .build();

  static final PassFactory rewriteLogicalAssignmentOperatorsPass =
      PassFactory.builder()
          .setName("rewriteLogicalAssignmentOperatorsPass")
          .setInternalFactory(RewriteLogicalAssignmentOperatorsPass::new)
          .build();

  static final PassFactory rewriteOptionalChainingOperator =
      PassFactory.builder()
          .setName("rewriteOptionalChainingOperator")
          .setInternalFactory(RewriteOptionalChainingOperator::new)
          .build();

  static final PassFactory rewriteNullishCoalesceOperator =
      PassFactory.builder()
          .setName("rewriteNullishCoalesceOperator")
          .setInternalFactory(RewriteNullishCoalesceOperator::new)
          .build();

  static final PassFactory reportUntranspilableFeatures =
      PassFactory.builder()
          .setName("reportUntranspilableFeatures")
          .setInternalFactory(
              (compiler) ->
                  new ReportUntranspilableFeatures(
                      compiler, compiler.getOptions().getOutputFeatureSet()))
          .build();

  /**
   * Returns true if the script's featureSet contains any feature from the given featureSet.
   *
   * 

The script's featureSet gets accurately maintained during transpile. It can be relied upon * to check if a feature exists in the AST or not. */ static boolean doesScriptHaveAnyOfTheseFeatures(Node script, FeatureSet featureSet) { FeatureSet features = NodeUtil.getFeatureSetOfScript(script); return features != null && features.containsAtLeastOneOf(featureSet); } /** * Runs the given transpilation callbacks on every source script if the given {@code * featuresToRunFor} are unsupported in the output language and exist in that script. * * @param compiler An AbstractCompiler * @param combinedRoot The combined root for all JS files * @param featuresToRunFor features for which these callbacks run * @param callbacks The callbacks that should be invoked if a file has output-unsupported * features. * @deprecated Please use a regular NodeTraversal object directly, using `shouldTraverse` to skip * SCRIPT node if desired. */ @Deprecated static void processTranspile( AbstractCompiler compiler, Node combinedRoot, FeatureSet featuresToRunFor, NodeTraversal.Callback... callbacks) { FeatureSet languageOutFeatures = compiler.getOptions().getOutputFeatureSet(); if (languageOutFeatures.contains(featuresToRunFor)) { // all featuresToRunFor are supported by languageOut and don't need to get transpiled. return; } for (Node singleRoot = combinedRoot.getFirstChild(); singleRoot != null; singleRoot = singleRoot.getNext()) { checkState(singleRoot.isScript()); // only run the callbacks if any feature from the given featureSet exists in the script if (doesScriptHaveAnyOfTheseFeatures(singleRoot, featuresToRunFor)) { for (NodeTraversal.Callback callback : callbacks) { NodeTraversal.traverse(compiler, singleRoot, callback); } } } } /** * Removes the given features from the FEATURE_SET prop of all scripts under root. Also removes * from the compiler's featureset. */ static void maybeMarkFeatureAsTranspiledAway( AbstractCompiler compiler, Node root, Feature feature) { // We don't bother to do this if the compiler has halting errors, which avoids unnecessary // warnings from AstValidator warning that the features are still there. if (!compiler.hasHaltingErrors()) { compiler.markFeatureNotAllowed(feature); NodeUtil.removeFeatureFromAllScripts(root, feature, compiler); } } /** * Removes the given features from the FEATURE_SET prop of all scripts under root. Also removes * from the compiler's featureset. */ static void maybeMarkFeaturesAsTranspiledAway( AbstractCompiler compiler, Node root, Feature transpiledFeature, Feature... moreTranspiledFeatures) { if (!compiler.hasHaltingErrors()) { maybeMarkFeatureAsTranspiledAway(compiler, root, transpiledFeature); for (Feature feature : moreTranspiledFeatures) { maybeMarkFeatureAsTranspiledAway(compiler, root, feature); } } } /** * Removes the given features from the FEATURE_SET prop of all scripts under root. Also removes * from the compiler's featureset. */ // TODO: b/293467820 - Potentially have a single method that accepts a Collection static void maybeMarkFeaturesAsTranspiledAway( AbstractCompiler compiler, Node root, FeatureSet transpiledFeatures) { // We don't bother to do this if the compiler has halting errors, which avoids unnecessary // warnings from AstValidator warning that the features are still there. if (!compiler.hasHaltingErrors()) { for (Feature feature : transpiledFeatures.getFeatures()) { maybeMarkFeatureAsTranspiledAway(compiler, root, feature); } } } static void postTranspileCheckUnsupportedFeaturesRemoved(AbstractCompiler compiler) { FeatureSet outputFeatures = compiler.getOptions().getOutputFeatureSet(); FeatureSet currentFeatures = compiler.getAllowableFeatures(); // features modules, importMeta and Dynamic module import may exist in the output even though // unsupported if (compiler.getOptions().getChunkOutputType() == ChunkOutputType.ES_MODULES) { currentFeatures = currentFeatures .without(Feature.MODULES) .without(Feature.IMPORT_META) .without(Feature.DYNAMIC_IMPORT); } if (outputFeatures.getFeatures().isEmpty()) { // In some cases (e.g. targets built using `gen_closurized_js`), the outputFeatures is not // set. Only when set, confirm that the output featureSet is respected by JSCompiler. return; } if (!outputFeatures.contains(currentFeatures)) { // Confirm that the output featureSet is respected by JSCompiler. FeatureSet diff = currentFeatures.without(outputFeatures); throw new IllegalStateException( "Unsupported feature(s) leaked to output code:" + diff.getFeatures()); } } /** * Returns a pass that just removes features from the source scripts' FeatureSet and the * compiler's featureset. * *

Doing this indicates that the AST no longer contains uses of the features, or that they are * no longer of concern for some other reason. */ private static PassFactory createFeatureRemovalPass( String passName, final Feature featureToRemove, final Feature... moreFeaturesToRemove) { return PassFactory.builder() .setName(passName) .setInternalFactory( (compiler) -> ((Node externs, Node root) -> maybeMarkFeaturesAsTranspiledAway( compiler, root, featureToRemove, moreFeaturesToRemove))) .build(); } /** * Returns a pass that just checks that post-transpile only supported features exist in the code. */ private static PassFactory createPostTranspileUnsupportedFeaturesRemovedCheck(String passName) { return PassFactory.builder() .setName(passName) .setInternalFactory( (compiler) -> ((Node externs, Node root) -> postTranspileCheckUnsupportedFeaturesRemoved(compiler))) .build(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy