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

com.google.javascript.jscomp.deps.ModuleLoader Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
Show newest version
/*
 * 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.deps;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.ErrorHandler;
import com.google.javascript.jscomp.JSError;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Provides compile-time locate semantics for ES6 and CommonJS modules.
 *
 * @see "https://tc39.github.io/ecma262/#sec-module-semantics"
 * @see "http://wiki.commonjs.org/wiki/Modules/1.1"
 */
public final class ModuleLoader {

  public static final DiagnosticType MODULE_CONFLICT = DiagnosticType.warning(
      "JSC_MODULE_CONFLICT", "File has both goog.module and ES6 modules: {0}");

  /** According to the spec, the forward slash should be the delimiter on all platforms. */
  public static final String MODULE_SLASH = ModuleNames.MODULE_SLASH;

  /** The default module root, the current directory. */
  public static final String DEFAULT_FILENAME_PREFIX = "." + MODULE_SLASH;

  public static final DiagnosticType LOAD_WARNING =
      DiagnosticType.warning("JSC_JS_MODULE_LOAD_WARNING", "Failed to load module \"{0}\"");

  public static final DiagnosticType INVALID_MODULE_PATH =
      DiagnosticType.warning(
          "JSC_INVALID_MODULE_PATH", "Invalid module path \"{0}\" for resolution mode \"{1}\"");

  private final ErrorHandler errorHandler;

  /** Root URIs to match module roots against. */
  private final ImmutableList moduleRootPaths;
  /** The set of all known input module URIs (including trailing .js), after normalization. */
  private final ImmutableSet modulePaths;

  /** Used to canonicalize paths before resolution. */
  private final PathResolver pathResolver;

  private final ModuleResolver moduleResolver;

  /**
   * Creates an instance of the module loader which can be used to locate ES6 and CommonJS modules.
   *
   * @param inputs All inputs to the compilation process.
   */
  public ModuleLoader(
      @Nullable ErrorHandler errorHandler,
      Iterable moduleRoots,
      Iterable inputs,
      PathResolver pathResolver,
      ResolutionMode resolutionMode,
      Map packageJsonMainEntries) {
    checkNotNull(moduleRoots);
    checkNotNull(inputs);
    checkNotNull(pathResolver);
    this.pathResolver = pathResolver;
    this.errorHandler = errorHandler == null ? new NoopErrorHandler() : errorHandler;
    this.moduleRootPaths = createRootPaths(moduleRoots, pathResolver);
    this.modulePaths =
        resolvePaths(
            Iterables.transform(Iterables.transform(inputs, UNWRAP_DEPENDENCY_INFO), pathResolver),
            moduleRootPaths);

    switch (resolutionMode) {
      case BROWSER:
        this.moduleResolver =
            new BrowserModuleResolver(this.modulePaths, this.moduleRootPaths, this.errorHandler);
        break;
      case LEGACY:
      default:
        this.moduleResolver =
            new LegacyModuleResolver(this.modulePaths, this.moduleRootPaths, this.errorHandler);
        break;
      case NODE:
        this.moduleResolver =
            new NodeModuleResolver(
                this.modulePaths, this.moduleRootPaths, packageJsonMainEntries, this.errorHandler);
        break;
    }
  }

  public ModuleLoader(
      @Nullable ErrorHandler errorHandler,
      Iterable moduleRoots,
      Iterable inputs,
      ResolutionMode resolutionMode) {
    this(errorHandler, moduleRoots, inputs, PathResolver.RELATIVE, resolutionMode);
  }

  public ModuleLoader(
      @Nullable ErrorHandler errorHandler,
      Iterable moduleRoots,
      Iterable inputs,
      PathResolver pathResolver) {
    this(errorHandler, moduleRoots, inputs, pathResolver, ResolutionMode.LEGACY);
  }

  public ModuleLoader(
      @Nullable ErrorHandler errorHandler,
      Iterable moduleRoots,
      Iterable inputs) {
    this(errorHandler, moduleRoots, inputs, PathResolver.RELATIVE, ResolutionMode.LEGACY);
  }

  public ModuleLoader(
      @Nullable ErrorHandler errorHandler,
      Iterable moduleRoots,
      Iterable inputs,
      PathResolver pathResolver,
      ResolutionMode resolutionMode) {
    this(errorHandler, moduleRoots, inputs, pathResolver, resolutionMode, null);
  }

  @VisibleForTesting
  public Map getPackageJsonMainEntries() {
    return this.moduleResolver.getPackageJsonMainEntries();
  }

  /**
   * A path to a module.  Provides access to the module's closurized name
   * and a way to resolve relative paths.
   */
  public class ModulePath {
    private final String path;

    private ModulePath(String path) {
      this.path = path;
    }

    @Override
    public String toString() {
      return path;
    }

    /**
     * Turns a filename into a JS identifier that can be used in rewritten code.
     * Removes leading ./, replaces / with $, removes trailing .js
     * and replaces - with _.
     */
    public String toJSIdentifier() {
      return ModuleNames.toJSIdentifier(path);
    }

    /**
     * Turns a filename into a JS identifier that is used for moduleNames in
     * rewritten code. Removes leading ./, replaces / with $, removes trailing .js
     * and replaces - with _. All moduleNames get a "module$" prefix.
     */
    public String toModuleName() {
      return ModuleNames.toModuleName(path);
    }

    /**
     * Find a JS module {@code requireName}. See
     * https://nodejs.org/api/modules.html#modules_all_together
     *
     * @return The normalized module URI, or {@code null} if not found.
     */
    @Nullable
    public ModulePath resolveJsModule(String moduleAddress) {
      return resolveJsModule(moduleAddress, null, -1, -1);
    }

    /**
     * Find a JS module {@code requireName}. See
     * https://nodejs.org/api/modules.html#modules_all_together
     *
     * @return The normalized module URI, or {@code null} if not found.
     */
    @Nullable
    public ModulePath resolveJsModule(
        String moduleAddress, String sourcename, int lineno, int colno) {
      String loadAddress =
          moduleResolver.resolveJsModule(this.path, moduleAddress, sourcename, lineno, colno);

      if (loadAddress != null) {
        return new ModulePath(loadAddress);
      }

      return null;
    }

    /**
     * Treats the module address as a path and returns the name of that module. Does not verify that
     * there is actually a JS file at the provided URI.
     *
     * 

Primarily used for per-file ES6 module transpilation */ public ModulePath resolveModuleAsPath(String moduleAddress) { if (!moduleAddress.endsWith(".js")) { moduleAddress += ".js"; } String path = ModuleNames.escapePath(moduleAddress); if (isRelativeIdentifier(moduleAddress)) { String ourPath = this.path; int lastIndex = ourPath.lastIndexOf(MODULE_SLASH); path = ModuleNames.canonicalizePath( ourPath.substring(0, lastIndex + MODULE_SLASH.length()) + path); } return new ModulePath(normalize(path, moduleRootPaths)); } } /** Resolves a path into a {@link ModulePath}. */ public ModulePath resolve(String path) { return new ModulePath( normalize(ModuleNames.escapePath(pathResolver.apply(path)), moduleRootPaths)); } /** Whether this is relative to the current file, or a top-level identifier. */ public static boolean isRelativeIdentifier(String name) { return name.startsWith("." + MODULE_SLASH) || name.startsWith(".." + MODULE_SLASH); } /** Whether this is absolute to the compilation. */ public static boolean isAbsoluteIdentifier(String name) { return name.startsWith(MODULE_SLASH); } /** Whether this is neither absolute or relative. */ public static boolean isAmbiguousIdentifier(String name) { return !isAbsoluteIdentifier(name) && !isRelativeIdentifier(name); } /** Whether name is a path-based identifier (has a '/' character) */ public static boolean isPathIdentifier(String name) { return name.contains(MODULE_SLASH); } /** * @param roots List of module root paths. This path prefix will be removed from module paths when * resolved. */ private static ImmutableList createRootPaths( Iterable roots, PathResolver resolver) { ImmutableList.Builder builder = ImmutableList.builder(); for (String root : roots) { String rootModuleName = ModuleNames.escapePath(resolver.apply(root)); if (isAmbiguousIdentifier(rootModuleName)) { rootModuleName = MODULE_SLASH + rootModuleName; } builder.add(rootModuleName); } return builder.build(); } /** * @param modulePaths List of modules. Modules can be relative to the compilation root or absolute * file system paths (or even absolute paths from the compilation root). * @param roots List of module roots which anchor absolute path references. * @return List of normalized modules which always have a leading slash */ private static ImmutableSet resolvePaths( Iterable modulePaths, Iterable roots) { ImmutableSet.Builder resolved = ImmutableSet.builder(); Set knownPaths = new HashSet<>(); for (String name : modulePaths) { String canonicalizedPath = ModuleNames.escapePath(name); if (!knownPaths.add(normalize(canonicalizedPath, roots))) { // Having root paths "a" and "b" and source files "a/f.js" and "b/f.js" is ambiguous. throw new IllegalArgumentException( "Duplicate module path after resolving: " + name); } if (isAmbiguousIdentifier(canonicalizedPath)) { canonicalizedPath = MODULE_SLASH + canonicalizedPath; } resolved.add(canonicalizedPath); } return resolved.build(); } /** Normalizes the name and resolves it against the module roots. */ private static String normalize(String path, Iterable moduleRootPaths) { String normalizedPath = path; if (isAmbiguousIdentifier(normalizedPath)) { normalizedPath = MODULE_SLASH + normalizedPath; } // Find a moduleRoot that this URI is under. If none, use as is. for (String moduleRoot : moduleRootPaths) { if (normalizedPath.startsWith(moduleRoot)) { // Make sure that e.g. path "foobar/test.js" is not matched by module "foo", by checking for // a leading slash. String trailing = normalizedPath.substring(moduleRoot.length()); if (trailing.startsWith(MODULE_SLASH)) { return trailing.substring(MODULE_SLASH.length()); } } } // Not underneath any of the roots. return path; } /** An enum indicating whether to absolutize paths. */ public enum PathResolver implements Function { RELATIVE { @Override public String apply(String path) { return path; } }, @GwtIncompatible("Paths.get, Path.toAbsolutePath") ABSOLUTE { @Override public String apply(String path) { return Paths.get(path).toAbsolutePath().toString(); } }; } private static final Function UNWRAP_DEPENDENCY_INFO = new Function() { @Override public String apply(DependencyInfo info) { return info.getName(); } }; /** A trivial module loader with no roots. */ public static final ModuleLoader EMPTY = new ModuleLoader( null, ImmutableList.of(), ImmutableList.of(), ResolutionMode.LEGACY); /** An enum used to specify what algorithm to use to locate non path-based modules */ public enum ResolutionMode { /** * Mimics the behavior of MS Edge. * * Modules must begin with a "." or "/" character. * Modules must include the file extension * MS Edge was the only browser to define a module resolution behavior at the time of this * writing. */ BROWSER, /** * Keeps the same behavior the compiler has used historically. * * Modules which do not begin with a "." or "/" character are assumed to be relative to the * compilation root. * ".js" file extensions are added if the import omits them. */ LEGACY, /** * Uses the node module resolution algorithm. * * Modules which do not begin with a "." or "/" character are looked up from the appropriate * node_modules folder. * Includes the ability to require directories and JSON files. * Exact match, then ".js", then ".json" file extensions are searched. */ NODE } private final class NoopErrorHandler implements ErrorHandler { public void report(CheckLevel level, JSError error) {} } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy