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

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

/*
 * Copyright 2004 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.common.base.Strings.isNullOrEmpty;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.debugging.sourcemap.SourceMapConsumerV3;
import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping;
import com.google.javascript.jscomp.CompilerOptions.DevMode;
import com.google.javascript.jscomp.CompilerOptions.InstrumentOption;
import com.google.javascript.jscomp.CoverageInstrumentationPass.CoverageReach;
import com.google.javascript.jscomp.SortingErrorManager.ErrorReportGenerator;
import com.google.javascript.jscomp.deps.BrowserModuleResolver;
import com.google.javascript.jscomp.deps.BrowserWithTransformedPrefixesModuleResolver;
import com.google.javascript.jscomp.deps.JsFileRegexParser;
import com.google.javascript.jscomp.deps.ModuleLoader;
import com.google.javascript.jscomp.deps.ModuleLoader.ModuleResolverFactory;
import com.google.javascript.jscomp.deps.NodeModuleResolver;
import com.google.javascript.jscomp.deps.SortedDependencies.MissingProvideException;
import com.google.javascript.jscomp.deps.WebpackModuleResolver;
import com.google.javascript.jscomp.diagnostic.LogFile;
import com.google.javascript.jscomp.modules.ModuleMap;
import com.google.javascript.jscomp.modules.ModuleMetadataMap;
import com.google.javascript.jscomp.parsing.Config;
import com.google.javascript.jscomp.parsing.ParserRunner;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.jscomp.parsing.parser.trees.Comment;
import com.google.javascript.jscomp.resources.ResourceLoader;
import com.google.javascript.jscomp.type.ChainableReverseAbstractInterpreter;
import com.google.javascript.jscomp.type.ClosureReverseAbstractInterpreter;
import com.google.javascript.jscomp.type.ReverseAbstractInterpreter;
import com.google.javascript.jscomp.type.SemanticReverseAbstractInterpreter;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/**
 * Compiler (and the other classes in this package) does the following:
 * 
    *
  • parses JS code *
  • checks for undefined variables *
  • performs optimizations such as constant folding and constants inlining *
  • renames variables (to short names) *
  • outputs compact JavaScript code *
* * External variables are declared in 'externs' files. For instance, the file * may include definitions for global javascript/browser objects such as * window, document. */ // TODO(tbreisacher): Rename Compiler to JsCompiler and remove this suppression. @SuppressWarnings("JavaLangClash") public class Compiler extends AbstractCompiler implements ErrorHandler, SourceFileMapping { static final DiagnosticType MODULE_DEPENDENCY_ERROR = DiagnosticType.error("JSC_MODULE_DEPENDENCY_ERROR", "Bad dependency: {0} -> {1}. " + "Modules must be listed in dependency order."); static final DiagnosticType MISSING_ENTRY_ERROR = DiagnosticType.error( "JSC_MISSING_ENTRY_ERROR", "required entry point \"{0}\" never provided"); static final DiagnosticType MISSING_MODULE_ERROR = DiagnosticType.error( "JSC_MISSING_MODULE_ERROR", "unknown module \"{0}\" specified in entry point spec"); static final DiagnosticType INCONSISTENT_MODULE_DEFINITIONS = DiagnosticType.error( "JSC_INCONSISTENT_MODULE_DEFINITIONS", "Serialized module definitions are not consistent with the module definitions supplied in " + "the command line"); private static final String CONFIG_RESOURCE = "com.google.javascript.jscomp.parsing.ParserConfig"; CompilerOptions options = null; private PassConfig passes = null; // The externs inputs private List externs; // The source module graph, denoting dependencies between chunks. private JSModuleGraph moduleGraph; // The module loader for resolving paths into module URIs. private ModuleLoader moduleLoader; // Map of module names to module types - used for module rewriting private final Map moduleTypesByName; // error manager to which error management is delegated private ErrorManager errorManager; // Warnings guard for filtering warnings. private WarningsGuard warningsGuard; // Compile-time injected libraries. The node points to the last node of // the library, so code can be inserted after. private final Map injectedLibraries = new LinkedHashMap<>(); // Node of the final injected library. Future libraries will be injected // after this node. private Node lastInjectedLibrary; // Parse tree root nodes Node externsRoot; Node jsRoot; Node externAndJsRoot; // Used for debugging; to see the compiled code between passes private String lastJsSource = null; private FeatureSet featureSet; private final Map inputsById = new ConcurrentHashMap<>(); private final Map scriptNodeByFilename = new ConcurrentHashMap<>(); private transient IncrementalScopeCreator scopeCreator = null; private ImmutableMap inputPathByWebpackId; /** * Subclasses are responsible for loading sources that were not provided as explicit inputs to the * compiler. For example, looking up sources referenced within sourcemaps. */ public static class ExternalSourceLoader { public SourceFile loadSource(String filename) { throw new RuntimeException("Cannot load without a valid loader."); } } // Original sources referenced by the source maps. private final ConcurrentHashMap sourceMapOriginalSources = new ConcurrentHashMap<>(); /** Configured {@link SourceMapInput}s, plus any source maps discovered in source files. */ ConcurrentHashMap inputSourceMaps = new ConcurrentHashMap<>(); // Map from filenames to lists of all the comments in each file. private Map> commentsPerFile = new ConcurrentHashMap<>(); /** The source code map */ private SourceMap sourceMap; /** The externs created from the exports. */ private String externExports = null; private UniqueIdSupplier uniqueIdSupplier = new UniqueIdSupplier(); /** * Ids for function inlining so that each declared name remains * unique. */ private int uniqueNameId = 0; /** * Whether to assume there are references to the RegExp Global object * properties. */ private boolean hasRegExpGlobalReferences = true; /** Detects Google-specific coding conventions. */ CodingConvention defaultCodingConvention = new ClosureCodingConvention(); private JSTypeRegistry typeRegistry; private volatile Config parserConfig = null; private volatile Config externsParserConfig = null; private ReverseAbstractInterpreter abstractInterpreter; private TypeValidator typeValidator; // The compiler can ask phaseOptimizer for things like which pass is currently // running, or which functions have been changed by optimizations private PhaseOptimizer phaseOptimizer = null; public PerformanceTracker tracker; // Types that have been forward declared private Set forwardDeclaredTypes = new HashSet<>(); private boolean typeCheckingHasRun = false; // This error reporter gets the messages from the current Rhino parser or TypeRegistry. private final ErrorReporter oldErrorReporter = RhinoErrorReporter.forOldRhino(this); /** Error strings used for reporting JSErrors */ public static final DiagnosticType OPTIMIZE_LOOP_ERROR = DiagnosticType.error( "JSC_OPTIMIZE_LOOP_ERROR", "Exceeded max number of optimization iterations: {0}"); public static final DiagnosticType MOTION_ITERATIONS_ERROR = DiagnosticType.error("JSC_MOTION_ITERATIONS_ERROR", "Exceeded max number of code motion iterations: {0}"); private final CompilerExecutor compilerExecutor = createCompilerExecutor(); /** * Logger for the whole com.google.javascript.jscomp domain - * setting configuration for this logger affects all loggers * in other classes within the compiler. */ public static final Logger logger = Logger.getLogger("com.google.javascript.jscomp"); private final PrintStream outStream; private GlobalVarReferenceMap globalRefMap = null; private volatile double progress = 0.0; private String lastPassName; private Set externProperties = null; private AccessorSummary accessorSummary = AccessorSummary.create(ImmutableMap.of()); private static final Joiner pathJoiner = Joiner.on(File.separator); // Starts at 0, increases as "interesting" things happen. // Nothing happens at time START_TIME, the first pass starts at time 1. // The correctness of scope-change tracking relies on Node/getIntProp // returning 0 if the custom attribute on a node hasn't been set. private int changeStamp = 1; private final Timeline changeTimeline = new Timeline<>(); private final Timeline deleteTimeline = new Timeline<>(); /** * When mapping symbols from a source map, we must repeatedly combine the path of the original * file with the path from the source map to compute the SourceFile of the underlying code. When * iterating through adjacent symbols this computation ends up computing the same thing. * *

This object tracks the last source map we resolved to reduce that work. It caches: for a * given originalPath+sourceMapPath, what sourceFile did we find? */ private static class ResolvedSourceMap { public String originalPath; public String sourceMapPath; public String relativePath; } /** * There is exactly one cached resolvedSourceMap, which you can think of as a cache of size one. */ private final ResolvedSourceMap resolvedSourceMap = new ResolvedSourceMap(); /** * Creates a Compiler that reports errors and warnings to its logger. */ public Compiler() { this((PrintStream) null); } /** * Creates a Compiler that reports errors and warnings to an output stream. */ public Compiler(PrintStream outStream) { addChangeHandler(recentChange); this.outStream = outStream; this.moduleTypesByName = new HashMap<>(); } /** * Creates a Compiler that uses a custom error manager. */ public Compiler(ErrorManager errorManager) { this(); setErrorManager(errorManager); } /** * Sets the error manager. * * @param errorManager the error manager, it cannot be {@code null} */ public void setErrorManager(ErrorManager errorManager) { checkNotNull(errorManager, "the error manager cannot be null"); this.errorManager = new ThreadSafeDelegatingErrorManager(errorManager); } /** * Creates a message formatter instance corresponding to the value of * {@link CompilerOptions}. */ private MessageFormatter createMessageFormatter() { boolean colorize = options.shouldColorizeErrorOutput(); return options.errorFormat.toFormatter(this, colorize); } /** * Initializes the compiler options. It's called as part of a normal compile() job. * Public for the callers that are not doing a normal compile() job. */ public void initOptions(CompilerOptions options) { this.options = options; this.setFeatureSet(options.getLanguageIn().toFeatureSet()); if (errorManager == null) { if (this.outStream == null) { setErrorManager( new LoggerErrorManager(createMessageFormatter(), logger)); } else { ImmutableSet.Builder builder = ImmutableSet.builder(); builder.add( new PrintStreamErrorReportGenerator( createMessageFormatter(), this.outStream, options.summaryDetailLevel)); builder.addAll(options.getExtraReportGenerators()); setErrorManager(new SortingErrorManager(builder.build())); } } moduleLoader = ModuleLoader.EMPTY; reconcileOptionsWithGuards(); // TODO(johnlenz): generally, the compiler should not be changing the options object // provided by the user. This should be handled a different way. // Turn off type-based optimizations when type checking is off if (!options.isTypecheckingEnabled()) { options.setUseTypesForLocalOptimization(false); options.setUseTypesForOptimization(false); } if (options.assumeForwardDeclaredForMissingTypes) { this.forwardDeclaredTypes = new AbstractSet() { @Override public boolean contains(Object o) { return true; // Report all types as forward declared types. } @Override public boolean add(String e) { return false; } @Override public Iterator iterator() { return Collections.emptySet().iterator(); } @Override public int size() { return 0; } }; } initWarningsGuard(options.getWarningsGuard()); } public void printConfig(PrintStream printStream) { printStream.println("==== Externs ===="); printStream.println(externs); printStream.println("==== Inputs ===="); // To get a pretty-printed JSON module graph, change this line to // // printStream.println( // new GsonBuilder().setPrettyPrinting().create().toJson(moduleGraph.toJson())); // // TODO(bradfordcsmith): Come up with a JSON-printing version that will work when this code is // compiled with J2CL, so we can permanently improve this. printStream.println(Iterables.toString(moduleGraph.getAllInputs())); printStream.println("==== CompilerOptions ===="); printStream.println(options); printStream.println("==== WarningsGuard ===="); printStream.println(warningsGuard); } private void initWarningsGuard(WarningsGuard warningsGuard) { ImmutableList.Builder guards = ImmutableList.builder(); guards .add(new J2clSuppressWarningsGuard()) .add(new SuppressDocWarningsGuard(this, DiagnosticGroups.getRegisteredGroups())) .add(warningsGuard); if (this.options != null && this.options.shouldSkipUnsupportedPasses()) { guards.add( new DiagnosticGroupWarningsGuard( DiagnosticGroups.FEATURES_NOT_SUPPORTED_BY_PASS, CheckLevel.WARNING)); } this.warningsGuard = new ComposeWarningsGuard(guards.build()); } /** When the CompilerOptions and its WarningsGuard overlap, reconcile any discrepancies. */ protected void reconcileOptionsWithGuards() { // DiagnosticGroups override the plain checkTypes option. if (options.enables(DiagnosticGroups.CHECK_TYPES)) { options.checkTypes = true; } else if (options.disables(DiagnosticGroups.CHECK_TYPES)) { options.checkTypes = false; } else if (!options.checkTypes) { // If DiagnosticGroups did not override the plain checkTypes // option, and checkTypes is disabled, then turn off the // parser type warnings. options.setWarningLevel( DiagnosticGroup.forType( RhinoErrorReporter.TYPE_PARSE_ERROR), CheckLevel.OFF); } if (!options.checkTypes) { options.setWarningLevel(DiagnosticGroups.BOUNDED_GENERICS, CheckLevel.OFF); } if (options.expectStrictModeInput()) { options.setWarningLevel( DiagnosticGroups.ES5_STRICT, CheckLevel.ERROR); } // All passes must run the variable check. This synthesizes // variables later so that the compiler doesn't crash. It also // checks the externs file for validity. If you don't want to warn // about missing variable declarations, we shut that specific // error off. if (!options.checkSymbols && !options.enables(DiagnosticGroups.CHECK_VARIABLES)) { options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.OFF); } // If we're in transpile-only mode, we don't need to do checks for suspicious var usages. // Since we still have to run VariableReferenceCheck before transpilation to check block-scoped // variables, though, we disable the unnecessary warnings it produces relating to vars here. if (options.skipNonTranspilationPasses && !options.enables(DiagnosticGroups.CHECK_VARIABLES)) { options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.OFF); } // If we're in transpile-only mode, we don't need to check for missing requires unless the user // explicitly enables missing-provide checks. if (options.skipNonTranspilationPasses && !options.enables(DiagnosticGroups.MISSING_PROVIDE)) { options.setWarningLevel(DiagnosticGroups.MISSING_PROVIDE, CheckLevel.OFF); } if (options.brokenClosureRequiresLevel == CheckLevel.OFF) { options.setWarningLevel(DiagnosticGroups.MISSING_PROVIDE, CheckLevel.OFF); } } /** Initializes the instance state needed for a compile job. */ public final void init( List externs, List sources, CompilerOptions options) { JSModule module = new JSModule(JSModule.STRONG_MODULE_NAME); for (SourceFile source : sources) { module.add(new CompilerInput(source)); } List modules = new ArrayList<>(1); modules.add(module); initModules(externs, modules, options); addFilesToSourceMap(sources); } /** * Initializes the instance state needed for a compile job if the sources * are in modules. */ public void initModules( List externs, List modules, CompilerOptions options) { initOptions(options); checkFirstModule(modules); this.externs = makeExternInputs(externs); // Generate the module graph, and report any errors in the module specification as errors. try { this.moduleGraph = new JSModuleGraph(modules); } catch (JSModuleGraph.ModuleDependenceException e) { // problems with the module format. Report as an error. The // message gives all details. report(JSError.make(MODULE_DEPENDENCY_ERROR, e.getModule().getName(), e.getDependentModule().getName())); return; } // Creating the module graph can move weak source around, and end up with empty modules. fillEmptyModules(getModules()); this.commentsPerFile = new ConcurrentHashMap<>(moduleGraph.getInputCount()); initBasedOnOptions(); initInputsByIdMap(); initAST(); // If debug logging is enabled, write out the module / chunk graph in GraphViz format. // This graph is often too big to reasonably render. // Using gvpr(1) is recommended to extract the parts of the graph that are of interest. // `dot -Tpng graph_file.dot > graph_file.png` will render an image. try (LogFile moduleGraphLog = createOrReopenLog(this.getClass(), "chunk_graph.dot")) { moduleGraphLog.log(DotFormatter.toDot(moduleGraph.toGraphvizGraph())); } } /** * Do any initialization that is dependent on the compiler options. */ public void initBasedOnOptions() { inputSourceMaps.putAll(options.inputSourceMaps); // Create the source map if necessary. if (options.sourceMapOutputPath != null) { sourceMap = options.sourceMapFormat.getInstance(); sourceMap.setPrefixMappings(options.sourceMapLocationMappings); if (options.applyInputSourceMaps) { sourceMap.setSourceFileMapping(this); if (options.sourceMapIncludeSourcesContent) { for (SourceMapInput inputSourceMap : inputSourceMaps.values()) { addSourceMapSourceFiles(inputSourceMap); } } } } } private List makeExternInputs(List externSources) { List inputs = new ArrayList<>(externSources.size()); for (SourceFile file : externSources) { inputs.add(new CompilerInput(file, /* isExtern= */ true)); } return inputs; } private static final DiagnosticType EMPTY_MODULE_LIST_ERROR = DiagnosticType.error("JSC_EMPTY_MODULE_LIST_ERROR", "At least one module must be provided"); private static final DiagnosticType EMPTY_ROOT_MODULE_ERROR = DiagnosticType.error("JSC_EMPTY_ROOT_MODULE_ERROR", "Root module ''{0}'' must contain at least one source code input"); /** * Verifies that at least one module has been provided and that the first one * has at least one source code input. */ private void checkFirstModule(List modules) { if (modules.isEmpty()) { report(JSError.make(EMPTY_MODULE_LIST_ERROR)); } else if (modules.get(0).getInputs().isEmpty() && modules.size() > 1) { // The root module may only be empty if there is exactly 1 module. report(JSError.make(EMPTY_ROOT_MODULE_ERROR, modules.get(0).getName())); } } /** * Creates an OS specific path string from parts */ public static String joinPathParts(String... pathParts) { return pathJoiner.join(pathParts); } /** Fill any empty modules with a place holder file. It makes any cross module motion easier. */ private void fillEmptyModules(Iterable modules) { for (JSModule module : modules) { if (!module.getName().equals(JSModule.WEAK_MODULE_NAME) && module.getInputs().isEmpty()) { CompilerInput input = new CompilerInput(SourceFile.fromCode(createFillFileName(module.getName()), "")); input.setCompiler(this); module.add(input); } } } /** * Rebuilds the internal input map by iterating over all modules. This is necessary if inputs have * been added to or removed from a module after an {@link #init} or {@link #initModules} call. */ public void rebuildInputsFromModules() { initInputsByIdMap(); } static final DiagnosticType DUPLICATE_INPUT = DiagnosticType.error("JSC_DUPLICATE_INPUT", "Duplicate input: {0}"); static final DiagnosticType DUPLICATE_EXTERN_INPUT = DiagnosticType.error("JSC_DUPLICATE_EXTERN_INPUT", "Duplicate extern input: {0}"); /** * Creates a map to make looking up an input by name fast. Also checks for * duplicate inputs. */ void initInputsByIdMap() { inputsById.clear(); for (CompilerInput input : externs) { InputId id = input.getInputId(); CompilerInput previous = putCompilerInput(id, input); if (previous != null) { report(JSError.make(DUPLICATE_EXTERN_INPUT, input.getName())); } } for (CompilerInput input : moduleGraph.getAllInputs()) { InputId id = input.getInputId(); CompilerInput previous = putCompilerInput(id, input); if (previous != null) { report(JSError.make(DUPLICATE_INPUT, input.getName())); } } } /** * Sets up the skeleton of the AST (the externs and root). */ private void initAST() { jsRoot = IR.root(); externsRoot = IR.root(); externAndJsRoot = IR.root(externsRoot, jsRoot); } /** Compiles a single source file and a single externs file. */ public Result compile(SourceFile extern, SourceFile input, CompilerOptions options) { return compile(ImmutableList.of(extern), ImmutableList.of(input), options); } /** * Compiles a list of inputs. * *

This is a convenience method to wrap up all the work of compilation, including * generating the error and warning report. * *

NOTE: All methods called here must be public, because client code must be able to replicate * and customize this. */ public Result compile( List externs, List inputs, CompilerOptions options) { // The compile method should only be called once. checkState(jsRoot == null); try { init(externs, inputs, options); if (options.printConfig) { printConfig(System.err); } if (!hasErrors()) { parseForCompilation(); } if (!hasErrors()) { if (options.getInstrumentForCoverageOnly()) { // TODO(bradfordcsmith): The option to instrument for coverage only should belong to the // runner, not the compiler. instrumentForCoverage(); } else { stage1Passes(); if (!hasErrors()) { stage2Passes(); } } performPostCompilationTasks(); } } finally { generateReport(); } return getResult(); } /** * Generates a report of all warnings and errors found during compilation to stderr. * *

Client code must call this method explicitly if it doesn't use one of the convenience * methods that do so automatically. *

Always call this method, even if the compiler throws an exception. The report will include * information about the exception. */ public void generateReport() { Tracer t = newTracer("generateReport"); errorManager.generateReport(); stopTracer(t, "generateReport"); } /** * Compiles a list of modules. * *

This is a convenience method to wrap up all the work of compilation, including * generating the error and warning report. * *

NOTE: All methods called here must be public, because client code must be able to replicate * and customize this. */ public Result compileModules( List externs, List modules, CompilerOptions options) { // The compile method should only be called once. checkState(jsRoot == null); try { initModules(externs, modules, options); if (options.printConfig) { printConfig(System.err); } if (!hasErrors()) { parseForCompilation(); } if (!hasErrors()) { // TODO(bradfordcsmith): The option to instrument for coverage only should belong to the // runner, not the compiler. if (options.getInstrumentForCoverageOnly()) { instrumentForCoverage(); } else { stage1Passes(); if (!hasErrors()) { stage2Passes(); } } performPostCompilationTasks(); } } finally { generateReport(); } return getResult(); } /** * Perform compiler passes for stage 1 of compilation. * *

Stage 1 consists primarily of error and type checking passes. * *

{@code parseForCompilation()} must be called before this method is called. * *

The caller is responsible for also calling {@code generateReport()} to generate a report of * warnings and errors to stderr. See the invocation in {@link #compile} for a good example. */ public void stage1Passes() { checkState(moduleGraph != null, "No inputs. Did you call init() or initModules()?"); checkState(!hasErrors()); checkState(!options.getInstrumentForCoverageOnly()); runInCompilerThread( () -> { performChecksAndTranspilation(); return null; }); } /** * Perform compiler passes for stage 2 of compilation. * *

Stage 2 consists primarily of optimization passes. * *

{@code stage1Passes()} must be called before this method is called. * *

The caller is responsible for also calling {@code generateReport()} to generate a report of * warnings and errors to stderr. See the invocation in {@link #compile} for a good example. */ public void stage2Passes() { checkState(moduleGraph != null, "No inputs. Did you call init() or initModules()?"); checkState(!hasErrors()); checkState(!options.getInstrumentForCoverageOnly()); JSModule weakModule = moduleGraph.getModuleByName(JSModule.WEAK_MODULE_NAME); if (weakModule != null) { for (CompilerInput i : moduleGraph.getAllInputs()) { if (i.getSourceFile().isWeak()) { checkState( i.getModule() == weakModule, "Expected all weak files to be in the weak module."); } } } runInCompilerThread( () -> { if (options.shouldOptimize()) { performOptimizations(); } return null; }); } /** * Disable threads. This is for clients that run on AppEngine and * don't have threads. */ public void disableThreads() { compilerExecutor.disableThreads(); } /** * Sets the timeout when Compiler is run in a thread * @param timeout seconds to wait before timeout */ public void setTimeout(int timeout) { compilerExecutor.setTimeout(timeout); } /** * The primary purpose of this method is to run the provided code with a larger than standard * stack. */ T runInCompilerThread(Callable callable) { return compilerExecutor.runInCompilerThread( callable, options != null && options.getTracerMode().isOn()); } private void performChecksAndTranspilation() { if (options.skipNonTranspilationPasses) { // i.e. whitespace-only mode, which will not work with goog.module without: whitespaceOnlyPasses(); if (options.needsTranspilationFrom(options.getLanguageIn().toFeatureSet())) { transpileAndDontCheck(); } } else { check(); // check() also includes transpilation } } /** * Performs all the bookkeeping required at the end of a compilation. * *

This method must be called if the compilation makes it as far as doing checks. *

DON'T call it if the compiler threw an exception. *

DO call it even when {@code hasErrors()} returns true. */ public void performPostCompilationTasks() { runInCompilerThread( () -> { performPostCompilationTasksInternal(); return null; }); } /** * Performs all the bookkeeping required at the end of a compilation. */ private void performPostCompilationTasksInternal() { if (options.devMode == DevMode.START_AND_END) { runValidityCheck(); } setProgress(1.0, "recordFunctionInformation"); if (tracker != null) { if (options.getTracerOutput() == null) { tracker.outputTracerReport(this.outStream); } else { try (PrintStream out = new PrintStream(Files.newOutputStream(options.getTracerOutput()))) { tracker.outputTracerReport(out); } catch (Exception e) { throw new RuntimeException(e); } } } } /** * Instrument code for coverage. * *

{@code parseForCompilation()} must be called before this method is called. * *

The caller is responsible for also calling {@code generateReport()} to generate a report of * warnings and errors to stderr. See the invocation in {@link #compile} for a good example. * *

This method is mutually exclusive with stage1Passes() and stage2Passes(). * Either call those two methods or this one, but not both. */ public void instrumentForCoverage() { checkState(moduleGraph != null, "No inputs. Did you call init() or initModules()?"); checkState(!hasErrors()); runInCompilerThread( () -> { checkState(options.getInstrumentForCoverageOnly()); checkState(!hasErrors()); if (options.getInstrumentForCoverageOption() != InstrumentOption.NONE) { instrumentForCoverageInternal(options.getInstrumentForCoverageOption()); } return null; }); } private void instrumentForCoverageInternal(InstrumentOption instrumentForCoverageOption) { Tracer tracer = newTracer("instrumentationPass"); process(new CoverageInstrumentationPass(this, CoverageReach.ALL, instrumentForCoverageOption)); stopTracer(tracer, "instrumentationPass"); } /** * Parses input files in preparation for compilation. * *

Either {@code init()} or {@code initModules()} must be called first to set up the input * files to be read. *

TODO(bradfordcsmith): Rename this to parse() */ public void parseForCompilation() { runInCompilerThread( () -> { parseForCompilationInternal(); return null; }); } /** * Parses input files in preparation for compilation. * *

Either {@code init()} or {@code initModules()} must be called first to set up the input * files to be read. * *

TODO(bradfordcsmith): Rename this to parse() */ private void parseForCompilationInternal() { setProgress(0.0, null); CompilerOptionsPreprocessor.preprocess(options); maybeSetTracker(); parseInputs(); // Guesstimate. setProgress(0.15, "parse"); } /** * Parses input files without doing progress tracking that is part of a full compile. * *

Either {@code init()} or {@code initModules()} must be called first to set up the input * files to be read. *

TODO(bradfordcsmith): Rename this to parseIndependentOfCompilation() or similar. */ public void parse() { parseInputs(); } public Node parse(SourceFile file) { initCompilerOptionsIfTesting(); logger.finest("Parsing: " + file.getName()); return new JsAst(file).getAstRoot(this); } PassConfig getPassConfig() { if (passes == null) { passes = createPassConfigInternal(); } return passes; } /** Create the passes object. Clients should use setPassConfig instead of overriding this. */ protected PassConfig createPassConfigInternal() { return new DefaultPassConfig(options); } /** * @param passes The PassConfig to use with this Compiler. * @throws NullPointerException if passes is null * @throws IllegalStateException if this.passes has already been assigned */ public void setPassConfig(PassConfig passes) { // Important to check for null because if setPassConfig(null) is // called before this.passes is set, getPassConfig() will create a // new PassConfig object and use that, which is probably not what // the client wanted since they probably meant to use their // own PassConfig object. checkNotNull(passes); checkState(this.passes == null, "setPassConfig was already called"); this.passes = passes; } public void whitespaceOnlyPasses() { runCustomPasses(CustomPassExecutionTime.BEFORE_CHECKS); Tracer t = newTracer("runWhitespaceOnlyPasses"); try { for (PassFactory pf : getPassConfig().getWhitespaceOnlyPasses()) { pf.create(this).process(externsRoot, jsRoot); } } finally { stopTracer(t, "runWhitespaceOnlyPasses"); } } public void transpileAndDontCheck() { Tracer t = newTracer("runTranspileOnlyPasses"); try { for (PassFactory pf : getPassConfig().getTranspileOnlyPasses()) { if (hasErrors()) { return; } pf.create(this).process(externsRoot, jsRoot); } } finally { stopTracer(t, "runTranspileOnlyPasses"); } } private PhaseOptimizer createPhaseOptimizer() { PhaseOptimizer phaseOptimizer = new PhaseOptimizer(this, tracker); if (options.devMode == DevMode.EVERY_PASS) { phaseOptimizer.setValidityCheck(validityCheck); } if (options.getCheckDeterminism()) { phaseOptimizer.setPrintAstHashcodes(true); } return phaseOptimizer; } void check() { runCustomPasses(CustomPassExecutionTime.BEFORE_CHECKS); // We are currently only interested in check-passes for progress reporting // as it is used for IDEs, that's why the maximum progress is set to 1.0. phaseOptimizer = createPhaseOptimizer().withProgress( new PhaseOptimizer.ProgressRange(getProgress(), 1.0)); phaseOptimizer.consume(getPassConfig().getChecks()); phaseOptimizer.process(externsRoot, jsRoot); if (hasErrors()) { return; } runCustomPasses(CustomPassExecutionTime.BEFORE_OPTIMIZATIONS); phaseOptimizer = null; } @Override void setExternExports(String externExports) { this.externExports = externExports; } @Override void process(CompilerPass p) { p.process(externsRoot, jsRoot); } private final PassFactory validityCheck = PassFactory.builder() .setName("validityCheck") .setRunInFixedPointLoop(true) .setInternalFactory(ValidityCheck::new) .setFeatureSetForChecks() .build(); private void runValidityCheck() { validityCheck.create(this).process(externsRoot, jsRoot); } /** * Runs custom passes that are designated to run at a particular time. */ private void runCustomPasses(CustomPassExecutionTime executionTime) { if (options.customPasses != null) { Tracer t = newTracer("runCustomPasses"); try { for (CompilerPass p : options.customPasses.get(executionTime)) { process(p); } } finally { stopTracer(t, "runCustomPasses"); } } } @Override final void beforePass(String passName) { super.beforePass(passName); } @Override final void afterPass(String passName) { super.afterPass(passName); this.maybePrintSourceAfterEachPass(passName); } private void maybePrintSourceAfterEachPass(String passName) { if (!options.printSourceAfterEachPass) { return; } String currentJsSource = getCurrentJsSource(); if (currentJsSource.equals(this.lastJsSource)) { return; } if (this.getOptions().getDebugLogDirectory() == null) { System.err.println(); System.err.println("// " + passName + " yields:"); System.err.println("// ************************************"); System.err.println(currentJsSource); System.err.println("// ************************************"); } else { try (LogFile log = this.createOrReopenIndexedLog(this.getClass(), "src_after_pass", passName)) { log.log(currentJsSource); } } this.lastJsSource = currentJsSource; } final String getCurrentJsSource() { this.resetAndIntitializeSourceMap(); List fileNameRegexList = options.filesToPrintAfterEachPassRegexList; List moduleNameRegexList = options.chunksToPrintAfterEachPassRegexList; StringBuilder builder = new StringBuilder(); if (fileNameRegexList.isEmpty() && moduleNameRegexList.isEmpty()) { return toSource(); } if (!fileNameRegexList.isEmpty()) { checkNotNull(externsRoot); checkNotNull(jsRoot); for (Node fileNode : Iterables.concat(externsRoot.children(), jsRoot.children())) { String fileName = fileNode.getSourceFileName(); for (String regex : fileNameRegexList) { if (fileName.matches(regex)) { String source = "// " + fileName + "\n" + toSource(fileNode); builder.append(source); break; } } } if (builder.toString().isEmpty()) { throw new RuntimeException("No files matched any of: " + fileNameRegexList); } } if (!moduleNameRegexList.isEmpty()) { for (JSModule jsModule : getModules()) { for (String regex : moduleNameRegexList) { if (jsModule.getName().matches(regex)) { String source = "// module '" + jsModule.getName() + "'\n" + toSource(jsModule); builder.append(source); break; } } } if (builder.toString().isEmpty()) { throw new RuntimeException("No modules matched any of: " + moduleNameRegexList); } } return builder.toString(); } @Override @Nullable public final Node getScriptNode(String filename) { return scriptNodeByFilename.get(checkNotNull(filename)); } /** * Returns a new tracer for the given pass name. */ Tracer newTracer(String passName) { String comment = passName + (recentChange.hasCodeChanged() ? " on recently changed AST" : ""); if (options.getTracerMode().isOn() && tracker != null) { tracker.recordPassStart(passName, true); } return new Tracer("Compiler", comment); } void stopTracer(Tracer t, String passName) { long result = t.stop(); if (options.getTracerMode().isOn() && tracker != null) { tracker.recordPassStop(passName, result); } } /** * Returns the result of the compilation. */ public Result getResult() { Set transpiledFiles = new HashSet<>(); if (jsRoot != null) { for (Node scriptNode : jsRoot.children()) { if (scriptNode.getBooleanProp(Node.TRANSPILED)) { transpiledFiles.add(getSourceFileByName(scriptNode.getSourceFileName())); } } } return new Result( getErrors(), getWarnings(), this.variableMap, this.propertyMap, this.anonymousFunctionNameMap, this.stringMap, this.instrumentationMapping, this.sourceMap, this.externExports, this.cssNames, this.idGeneratorMap, transpiledFiles); } /** Returns the list of errors (never null). */ public ImmutableList getErrors() { return (errorManager == null) ? ImmutableList.of() : errorManager.getErrors(); } /** Returns the list of warnings (never null). */ public ImmutableList getWarnings() { return (errorManager == null) ? ImmutableList.of() : errorManager.getWarnings(); } @Override public Node getRoot() { return externAndJsRoot; } @Override FeatureSet getFeatureSet() { return featureSet; } @Override void setFeatureSet(FeatureSet fs) { featureSet = fs; } @Override UniqueIdSupplier getUniqueIdSupplier() { return this.uniqueIdSupplier; } /** Creates a new id for making unique names. */ @Deprecated private int nextUniqueNameId() { return uniqueNameId++; } /** Resets the unique name id counter */ @Deprecated @VisibleForTesting void resetUniqueNameId() { uniqueNameId = 0; } /** * Legacy supplier for getting unique names. * * @deprecated This is deprecated because this supplier fails to generate unique Ids across input * files when used for tagged template literal transpilation. Kindly use the new {@code * UniqueIdSupplier} instead. */ @Deprecated @Override Supplier getUniqueNameIdSupplier() { return () -> String.valueOf(Compiler.this.nextUniqueNameId()); } @Override boolean areNodesEqualForInlining(Node n1, Node n2) { if (options.shouldAmbiguateProperties() || options.shouldDisambiguateProperties()) { // The type based optimizations require that type information is preserved // during other optimizations. return n1.isEquivalentToTyped(n2); } else { return n1.isEquivalentTo(n2); } } //------------------------------------------------------------------------ // Inputs //------------------------------------------------------------------------ // TODO(nicksantos): Decide which parts of these belong in an AbstractCompiler // interface, and which ones should always be injected. @Override public CompilerInput getInput(InputId id) { // TODO(bradfordcsmith): Allowing null id is less ideal. Add checkNotNull(id) here and fix // call sites that break. if (id == null) { return null; } return inputsById.get(id); } /** * Removes an input file from AST. * @param id The id of the input to be removed. */ protected void removeExternInput(InputId id) { CompilerInput input = getInput(id); if (input == null) { return; } checkState(input.isExtern(), "Not an extern input: %s", input.getName()); scriptNodeByFilename.remove(input.getSourceFile().getName()); inputsById.remove(id); externs.remove(input); Node root = checkNotNull(input.getAstRoot(this)); if (root != null) { root.detach(); } } // Where to put a new synthetic externs file. private static enum SyntheticExternsPosition { START, END } CompilerInput newExternInput(String name, SyntheticExternsPosition pos) { SourceAst ast = new SyntheticAst(name); if (inputsById.containsKey(ast.getInputId())) { throw new IllegalArgumentException("Conflicting externs name: " + name); } CompilerInput input = new CompilerInput(ast, true); Node root = checkNotNull(ast.getAstRoot(this)); putCompilerInput(input.getInputId(), input); if (pos == SyntheticExternsPosition.START) { externsRoot.addChildToFront(root); externs.add(0, input); } else { externsRoot.addChildToBack(root); externs.add(input); } scriptNodeByFilename.put(input.getSourceFile().getName(), root); return input; } CompilerInput putCompilerInput(InputId id, CompilerInput input) { input.setCompiler(this); return inputsById.put(id, input); } /** * Replace a source input dynamically. Intended for incremental * re-compilation. * * If the new source input doesn't parse, then keep the old input * in the AST and return false. * * @return Whether the new AST was attached successfully. */ boolean replaceIncrementalSourceAst(JsAst ast) { CompilerInput oldInput = getInput(ast.getInputId()); checkNotNull(oldInput, "No input to replace: %s", ast.getInputId().getIdName()); Node newRoot = checkNotNull(ast.getAstRoot(this)); Node oldRoot = oldInput.getAstRoot(this); oldRoot.replaceWith(newRoot); CompilerInput newInput = new CompilerInput(ast); putCompilerInput(ast.getInputId(), newInput); JSModule module = oldInput.getModule(); if (module != null) { module.addAfter(newInput, oldInput); module.remove(oldInput); } // Verify the input id is set properly. checkState(newInput.getInputId().equals(oldInput.getInputId())); InputId inputIdOnAst = newInput.getAstRoot(this).getInputId(); checkState(newInput.getInputId().equals(inputIdOnAst)); return true; } /** * Add a new source input dynamically. Intended for incremental compilation. *

* If the new source input doesn't parse, it will not be added, and a false * will be returned. * * @param ast the JS Source to add. * @return true if the source was added successfully, false otherwise. * @throws IllegalStateException if an input for this ast already exists. */ boolean addNewSourceAst(JsAst ast) { CompilerInput oldInput = getInput(ast.getInputId()); if (oldInput != null) { throw new IllegalStateException( "Input already exists: " + ast.getInputId().getIdName()); } Node newRoot = checkNotNull(ast.getAstRoot(this)); getRoot().getLastChild().addChildToBack(newRoot); CompilerInput newInput = new CompilerInput(ast); // TODO(tylerg): handle this for multiple modules at some point. JSModule firstModule = Iterables.getFirst(getModules(), null); if (firstModule.getName().equals(JSModule.STRONG_MODULE_NAME)) { firstModule.add(newInput); } putCompilerInput(ast.getInputId(), newInput); return true; } /** * Gets the graph of JS source modules. * *

Returns null if {@code #init} or {@code #initModules} hasn't been called yet. Otherwise, the * result is always a module graph, even in the degenerate case where there's only one module. */ @Nullable @Override JSModuleGraph getModuleGraph() { return moduleGraph; } /** * Gets the JS source modules in dependency order. * *

Returns null if {@code #init} or {@code #initModules} hasn't been called yet. Otherwise, the * result is always non-empty, even in the degenerate case where there's only one module. */ @Nullable public Iterable getModules() { return moduleGraph != null ? moduleGraph.getAllModules() : null; } @Override public void clearJSTypeRegistry() { typeRegistry = null; typeValidator = null; abstractInterpreter = null; } @Override public JSTypeRegistry getTypeRegistry() { if (typeRegistry == null) { typeRegistry = new JSTypeRegistry(oldErrorReporter, forwardDeclaredTypes); } return typeRegistry; } @Override public void forwardDeclareType(String typeName) { forwardDeclaredTypes.add(typeName); } @Override void setTypeCheckingHasRun(boolean hasRun) { this.typeCheckingHasRun = hasRun; } @Override boolean hasTypeCheckingRun() { return this.typeCheckingHasRun; } @Override // Only used by jsdev public TypedScopeCreator getTypedScopeCreator() { return getPassConfig().getTypedScopeCreator(); } @Override IncrementalScopeCreator getScopeCreator() { return this.scopeCreator; } @Override void putScopeCreator(IncrementalScopeCreator creator) { this.scopeCreator = creator; } DefaultPassConfig ensureDefaultPassConfig() { PassConfig passes = getPassConfig().getBasePassConfig(); checkState( passes instanceof DefaultPassConfig, "PassConfigs must eventually delegate to the DefaultPassConfig"); return (DefaultPassConfig) passes; } public SymbolTable buildKnownSymbolTable() { SymbolTable symbolTable = new SymbolTable(this, getTypeRegistry()); TypedScopeCreator typedScopeCreator = getTypedScopeCreator(); if (typedScopeCreator != null) { symbolTable.addScopes(typedScopeCreator.getAllMemoizedScopes()); symbolTable.addSymbolsFrom(typedScopeCreator); } else { symbolTable.findScopes(externsRoot, jsRoot); } GlobalNamespace globalNamespace = ensureDefaultPassConfig().getGlobalNamespace(); if (globalNamespace != null) { symbolTable.addSymbolsFrom(globalNamespace); } ReferenceCollectingCallback refCollector = new ReferenceCollectingCallback( this, ReferenceCollectingCallback.DO_NOTHING_BEHAVIOR, new SyntacticScopeCreator(this)); refCollector.process(getRoot()); symbolTable.addSymbolsFrom(refCollector); PreprocessorSymbolTable preprocessorSymbolTable = ensureDefaultPassConfig().getPreprocessorSymbolTable(); if (preprocessorSymbolTable != null) { symbolTable.addSymbolsFrom(preprocessorSymbolTable); } symbolTable.fillNamespaceReferences(); symbolTable.fillPropertyScopes(); symbolTable.fillThisReferences(externsRoot, jsRoot); symbolTable.fillPropertySymbols(externsRoot, jsRoot); symbolTable.fillSuperReferences(externsRoot, jsRoot); symbolTable.fillJSDocInfo(externsRoot, jsRoot); symbolTable.fillSymbolVisibility(externsRoot, jsRoot); symbolTable.removeGeneratedSymbols(); return symbolTable; } @Override public TypedScope getTopScope() { return getPassConfig().getTopScope(); } @Override public ReverseAbstractInterpreter getReverseAbstractInterpreter() { if (abstractInterpreter == null) { ChainableReverseAbstractInterpreter interpreter = new SemanticReverseAbstractInterpreter(getTypeRegistry()); if (options.closurePass) { interpreter = new ClosureReverseAbstractInterpreter(getTypeRegistry()) .append(interpreter).getFirst(); } abstractInterpreter = interpreter; } return abstractInterpreter; } @Override TypeValidator getTypeValidator() { if (typeValidator == null) { typeValidator = new TypeValidator(this); } return typeValidator; } @Override public Iterable getTypeMismatches() { if (this.typeCheckingHasRun) { return getTypeValidator().getMismatches(); } throw new RuntimeException("Can't ask for type mismatches before type checking."); } @Override public Iterable getImplicitInterfaceUses() { if (this.typeCheckingHasRun) { return getTypeValidator().getImplicitInterfaceUses(); } throw new RuntimeException("Can't ask for type mismatches before type checking."); } public void maybeSetTracker() { if (!options.getTracerMode().isOn()) { return; } tracker = new PerformanceTracker(externsRoot, jsRoot, options.getTracerMode()); addChangeHandler(tracker.getCodeChangeHandler()); } //------------------------------------------------------------------------ // Parsing //------------------------------------------------------------------------ /** * Parses the externs and main inputs. * * @return A synthetic root node whose two children are the externs root * and the main root */ Node parseInputs() { boolean devMode = options.devMode != DevMode.OFF; // If old roots exist (we are parsing a second time), detach each of the // individual file parse trees. externsRoot.detachChildren(); jsRoot.detachChildren(); scriptNodeByFilename.clear(); Tracer tracer = newTracer(PassNames.PARSE_INPUTS); beforePass(PassNames.PARSE_INPUTS); try { // Parse externs sources. if (options.numParallelThreads > 1) { new PrebuildAst(this, options.numParallelThreads).prebuild(externs); } for (CompilerInput input : externs) { Node n = checkNotNull(input.getAstRoot(this)); if (hasErrors()) { return null; } externsRoot.addChildToBack(n); scriptNodeByFilename.put(input.getSourceFile().getName(), n); } if (options.transformAMDToCJSModules) { processAMDModules(moduleGraph.getAllInputs()); } if (options.getLanguageIn().toFeatureSet().has(FeatureSet.Feature.MODULES) || options.processCommonJSModules) { ModuleResolverFactory moduleResolverFactory = null; switch (options.getModuleResolutionMode()) { case BROWSER: moduleResolverFactory = BrowserModuleResolver.FACTORY; break; case NODE: // processJsonInputs requires a module loader to already be defined // so we redefine it afterwards with the package.json inputs moduleResolverFactory = new NodeModuleResolver.Factory(processJsonInputs(moduleGraph.getAllInputs())); break; case WEBPACK: moduleResolverFactory = new WebpackModuleResolver.Factory(inputPathByWebpackId); break; case BROWSER_WITH_TRANSFORMED_PREFIXES: moduleResolverFactory = new BrowserWithTransformedPrefixesModuleResolver.Factory( options.getBrowserResolverPrefixReplacements()); break; } this.moduleLoader = new ModuleLoader( null, options.moduleRoots, moduleGraph.getAllInputs(), moduleResolverFactory, ModuleLoader.PathResolver.RELATIVE, options.getPathEscaper()); } else { // Use an empty module loader if we're not actually dealing with modules. this.moduleLoader = ModuleLoader.EMPTY; } if (options.getDependencyOptions().needsManagement()) { findModulesFromEntryPoints( options.getLanguageIn().toFeatureSet().has(Feature.MODULES), options.processCommonJSModules); } else if (options.needsTranspilationFrom(FeatureSet.ES6_MODULES) || options.processCommonJSModules) { if (options.getLanguageIn().toFeatureSet().has(Feature.MODULES)) { parsePotentialModules(moduleGraph.getAllInputs()); } // Build a map of module identifiers for any input which provides no namespace. // These files could be imported modules which have no exports, but do have side effects. Map inputModuleIdentifiers = new HashMap<>(); for (CompilerInput input : moduleGraph.getAllInputs()) { if (input.getKnownProvides().isEmpty()) { ModuleLoader.ModulePath modPath = moduleLoader.resolve(input.getSourceFile().getOriginalPath()); inputModuleIdentifiers.put(modPath.toModuleName(), input); } } // Find out if any input attempted to import a module that had no exports. // In this case we must force module rewriting to occur on the imported file Map inputsToRewrite = new HashMap<>(); for (CompilerInput input : moduleGraph.getAllInputs()) { for (String require : input.getKnownRequiredSymbols()) { if (inputModuleIdentifiers.containsKey(require) && !inputsToRewrite.containsKey(require)) { inputsToRewrite.put(require, inputModuleIdentifiers.get(require)); } } } for (CompilerInput input : inputsToRewrite.values()) { input.setJsModuleType(CompilerInput.ModuleType.IMPORTED_SCRIPT); } } if (this.moduleLoader != null) { this.moduleLoader.setErrorHandler(this); } orderInputs(); // If in IDE mode, we ignore the error and keep going. if (hasErrors()) { return null; } // Build the AST. if (options.numParallelThreads > 1) { new PrebuildAst(this, options.numParallelThreads).prebuild(moduleGraph.getAllInputs()); } for (CompilerInput input : moduleGraph.getAllInputs()) { Node n = checkNotNull(input.getAstRoot(this)); if (devMode) { runValidityCheck(); if (hasErrors()) { return null; } } // TODO(johnlenz): we shouldn't need to check both isExternExportsEnabled and // externExportsPath. if (options.sourceMapOutputPath != null || options.isExternExportsEnabled() || options.externExportsPath != null || !options.replaceStringsFunctionDescriptions.isEmpty()) { // Annotate the nodes in the tree with information from the // input file. This information is used to construct the SourceMap. SourceInformationAnnotator sia = new SourceInformationAnnotator( input.getName(), options.devMode != DevMode.OFF); NodeTraversal.traverse(this, n, sia); } if (NodeUtil.isFromTypeSummary(n)) { input.setIsExtern(); externsRoot.addChildToBack(n); } else { jsRoot.addChildToBack(n); } scriptNodeByFilename.put(input.getSourceFile().getName(), n); } if (hasErrors()) { return null; } return externAndJsRoot; } finally { afterPass(PassNames.PARSE_INPUTS); stopTracer(tracer, PassNames.PARSE_INPUTS); } } void orderInputsWithLargeStack() { runInCompilerThread( () -> { Tracer tracer = newTracer("orderInputsWithLargeStack"); try { orderInputs(); } finally { stopTracer(tracer, "orderInputsWithLargeStack"); } return null; }); } void orderInputs() { maybeDoThreadedParsing(); // Before dependency pruning, save a copy of the original inputs to use for externs hoisting. ImmutableList originalInputs = ImmutableList.copyOf(moduleGraph.getAllInputs()); // Externs must be marked before dependency management since it needs to know what is an extern. markExterns(originalInputs); // Check if the sources need to be re-ordered. boolean staleInputs = false; if (options.getDependencyOptions().needsManagement()) { for (CompilerInput input : moduleGraph.getAllInputs()) { // Forward-declare all the provided types, so that they // are not flagged even if they are dropped from the process. for (String provide : input.getProvides()) { forwardDeclareType(provide); } } try { moduleGraph.manageDependencies(this, options.getDependencyOptions()); staleInputs = true; } catch (MissingProvideException e) { report(JSError.make( MISSING_ENTRY_ERROR, e.getMessage())); } catch (JSModuleGraph.MissingModuleException e) { report(JSError.make( MISSING_MODULE_ERROR, e.getMessage())); } } hoistExterns(originalInputs); // Manage dependencies may move weak sources around, and end up with empty modules. fillEmptyModules(getModules()); hoistNoCompileFiles(); if (staleInputs) { repartitionInputs(); } } /** * Find modules by recursively traversing dependencies starting with the entry points. * *

Causes a regex parse of every file, and a full parse of every file reachable from the entry * points (which would be required by later compilation passes regardless). * *

If the dependency mode is set to PRUNE_LEGACY, inputs which the regex parse does not * identify as ES modules and which do not contain any provide statements are considered to be * additional entry points. */ private void findModulesFromEntryPoints( boolean supportEs6Modules, boolean supportCommonJSModules) { maybeDoThreadedParsing(); List entryPoints = new ArrayList<>(); Map inputsByProvide = new HashMap<>(); Map inputsByIdentifier = new HashMap<>(); for (CompilerInput input : moduleGraph.getAllInputs()) { Iterable provides = Iterables.filter(input.getProvides(), p -> !p.startsWith("module$")); if (!options.getDependencyOptions().shouldDropMoochers() && Iterables.isEmpty(provides)) { entryPoints.add(input); } inputsByIdentifier.put( ModuleIdentifier.forFile(input.getPath().toString()).toString(), input); for (String provide : provides) { inputsByProvide.put(provide, input); } } for (ModuleIdentifier moduleIdentifier : options.getDependencyOptions().getEntryPoints()) { CompilerInput input = inputsByProvide.get(moduleIdentifier.toString()); if (input == null) { input = inputsByIdentifier.get(moduleIdentifier.toString()); } if (input != null) { entryPoints.add(input); } } Set workingInputSet = Sets.newHashSet(moduleGraph.getAllInputs()); for (CompilerInput entryPoint : entryPoints) { findModulesFromInput( entryPoint, /* wasImportedByModule = */ false, workingInputSet, inputsByIdentifier, inputsByProvide, supportEs6Modules, supportCommonJSModules); } } /** Traverse an input's dependencies to find additional modules. */ private void findModulesFromInput( CompilerInput input, boolean wasImportedByModule, Set inputs, Map inputsByIdentifier, Map inputsByProvide, boolean supportEs6Modules, boolean supportCommonJSModules) { if (!inputs.remove(input)) { // It's possible for a module to be included as both a script // and a module in the same compilation. In these cases, it should // be forced to be a module. if (wasImportedByModule && input.getJsModuleType() == CompilerInput.ModuleType.NONE) { input.setJsModuleType(CompilerInput.ModuleType.IMPORTED_SCRIPT); } return; } FindModuleDependencies findDeps = new FindModuleDependencies( this, supportEs6Modules, supportCommonJSModules, inputPathByWebpackId); findDeps.process(checkNotNull(input.getAstRoot(this))); // If this input was imported by another module, it is itself a module // so we force it to be detected as such. if (wasImportedByModule && input.getJsModuleType() == CompilerInput.ModuleType.NONE) { input.setJsModuleType(CompilerInput.ModuleType.IMPORTED_SCRIPT); } this.moduleTypesByName.put(input.getPath().toModuleName(), input.getJsModuleType()); Iterable allDeps = Iterables.concat( input.getRequiredSymbols(), input.getDynamicRequires(), input.getTypeRequires()); for (String requiredNamespace : allDeps) { CompilerInput requiredInput = null; boolean requiredByModuleImport = false; if (inputsByProvide.containsKey(requiredNamespace)) { requiredInput = inputsByProvide.get(requiredNamespace); } else if (inputsByIdentifier.containsKey(requiredNamespace)) { requiredByModuleImport = true; requiredInput = inputsByIdentifier.get(requiredNamespace); } if (requiredInput != null) { findModulesFromInput( requiredInput, requiredByModuleImport, inputs, inputsByIdentifier, inputsByProvide, supportEs6Modules, supportCommonJSModules); } } } /** Hoists inputs with the @externs annotation into the externs list. */ void hoistExterns(ImmutableList originalInputs) { boolean staleInputs = false; for (CompilerInput input : originalInputs) { if (hoistIfExtern(input)) { staleInputs = true; } } if (staleInputs) { repartitionInputs(); } } /** * Hoists a compiler input to externs if it contains the @externs annotation. * Return whether or not the given input was hoisted. */ private boolean hoistIfExtern(CompilerInput input) { if (input.getHasExternsAnnotation()) { // If the input file is explicitly marked as an externs file, then move it out of the main // JS root and put it with the other externs. Node root = input.getAstRoot(this); externsRoot.addChildToBack(root); scriptNodeByFilename.put(input.getSourceFile().getName(), root); JSModule module = input.getModule(); if (module != null) { module.remove(input); } externs.add(input); return true; } return false; } /** * Marks inputs with the @externs annotation as an Extern source file type. * This is so that externs marking can be done before dependency management, * and externs hoisting done after dependency management. */ private void markExterns(ImmutableList originalInputs) { for (CompilerInput input : originalInputs) { if (input.getHasExternsAnnotation()) { input.setIsExtern(); } } } /** * Hoists inputs with the @nocompile annotation out of the inputs. */ void hoistNoCompileFiles() { boolean staleInputs = false; maybeDoThreadedParsing(); // Iterate a copy because hoisting modifies what we're iterating over. for (CompilerInput input : ImmutableList.copyOf(moduleGraph.getAllInputs())) { if (input.getHasNoCompileAnnotation()) { input.getModule().remove(input); staleInputs = true; } } if (staleInputs) { repartitionInputs(); } } private void maybeDoThreadedParsing() { if (options.numParallelThreads > 1) { new PrebuildDependencyInfo(options.numParallelThreads).prebuild(moduleGraph.getAllInputs()); } } private void repartitionInputs() { fillEmptyModules(getModules()); rebuildInputsFromModules(); } /** * Transforms JSON files to a module export that closure compiler can process and keeps track of * any "main" entries in package.json files. */ Map processJsonInputs(Iterable inputsToProcess) { RewriteJsonToModule rewriteJson = new RewriteJsonToModule(this); for (CompilerInput input : inputsToProcess) { if (!input.getSourceFile().getOriginalPath().endsWith(".json")) { continue; } input.setCompiler(this); try { // JSON objects need wrapped in parens to parse properly input.getSourceFile().setCode("(" + input.getSourceFile().getCode() + ")"); } catch (IOException e) { continue; } Node root = checkNotNull(input.getAstRoot(this)); input.setJsModuleType(CompilerInput.ModuleType.JSON); rewriteJson.process(null, root); } return rewriteJson.getPackageJsonMainEntries(); } private List parsePotentialModules(Iterable inputsToProcess) { List filteredInputs = new ArrayList<>(); for (CompilerInput input : inputsToProcess) { // Only process files that are detected as ES6 modules if (!options.getDependencyOptions().shouldPrune() || !JsFileRegexParser.isSupported() || "es6".equals(input.getLoadFlags().get("module"))) { filteredInputs.add(input); } } if (options.numParallelThreads > 1) { new PrebuildAst(this, options.numParallelThreads).prebuild(filteredInputs); } for (CompilerInput input : filteredInputs) { input.setCompiler(this); // Call getRequires to force regex-based dependency parsing to happen. input.getRequires(); input.setJsModuleType(CompilerInput.ModuleType.ES6); } return filteredInputs; } /** Transforms AMD to CJS modules */ void processAMDModules(Iterable inputs) { for (CompilerInput input : inputs) { input.setCompiler(this); Node root = checkNotNull(input.getAstRoot(this)); new TransformAMDToCJSModule(this).process(null, root); } } /** * Allow subclasses to override the default CompileOptions object. */ protected CompilerOptions newCompilerOptions() { return new CompilerOptions(); } void initCompilerOptionsIfTesting() { if (options == null) { // initialization for tests that don't initialize the compiler // by the normal mechanisms. initOptions(newCompilerOptions()); } } private int syntheticCodeId = 0; @Override protected Node parseSyntheticCode(String js) { return parseSyntheticCode(" [synthetic:" + (++syntheticCodeId) + "] ", js); } @Override Node parseSyntheticCode(String fileName, String js) { initCompilerOptionsIfTesting(); SourceFile source = SourceFile.fromCode(fileName, js); addFilesToSourceMap(ImmutableList.of(source)); return parseCodeHelper(source); } @Override @VisibleForTesting Node parseTestCode(String js) { initCompilerOptionsIfTesting(); initBasedOnOptions(); return parseCodeHelper(SourceFile.fromCode("[testcode]", js)); } @Override @VisibleForTesting Node parseTestCode(ImmutableList code) { initCompilerOptionsIfTesting(); initBasedOnOptions(); return parseCodeHelper( Streams.mapWithIndex( code.stream(), (value, index) -> SourceFile.fromCode("testcode" + index, value)) .collect(Collectors.toList())); } private Node parseCodeHelper(SourceFile src) { CompilerInput input = new CompilerInput(src); putCompilerInput(input.getInputId(), input); Node root = input.getAstRoot(this); scriptNodeByFilename.put(input.getSourceFile().getName(), root); return checkNotNull(root); } private Node parseCodeHelper(List srcs) { Node root = IR.root(); for (SourceFile src : srcs) { root.addChildToBack(parseCodeHelper(src)); } return root; } @Override ErrorReporter getDefaultErrorReporter() { return oldErrorReporter; } //------------------------------------------------------------------------ // Convert back to source code //------------------------------------------------------------------------ /** * Converts the main parse tree back to JS code. */ @Override public String toSource() { return runInCompilerThread( () -> { Tracer tracer = newTracer("toSource"); try { CodeBuilder cb = new CodeBuilder(); if (jsRoot != null) { int i = 0; if (options.shouldPrintExterns()) { for (Node scriptNode = externsRoot.getFirstChild(); scriptNode != null; scriptNode = scriptNode.getNext()) { toSource(cb, i++, scriptNode); } } for (Node scriptNode = jsRoot.getFirstChild(); scriptNode != null; scriptNode = scriptNode.getNext()) { toSource(cb, i++, scriptNode); } } return cb.toString(); } finally { stopTracer(tracer, "toSource"); } }); } /** * Converts the parse tree for a module back to JS code. */ public String toSource(final JSModule module) { return runInCompilerThread( () -> { List inputs = module.getInputs(); int numInputs = inputs.size(); if (numInputs == 0) { return ""; } CodeBuilder cb = new CodeBuilder(); for (int i = 0; i < numInputs; i++) { Node scriptNode = inputs.get(i).getAstRoot(Compiler.this); if (scriptNode == null) { throw new IllegalArgumentException("Bad module: " + module.getName()); } toSource(cb, i, scriptNode); } return cb.toString(); }); } /** * Writes out JS code from a root node. If printing input delimiters, this * method will attach a comment to the start of the text indicating which * input the output derived from. If there were any preserve annotations * within the root's source, they will also be printed in a block comment * at the beginning of the output. */ public void toSource(final CodeBuilder cb, final int inputSeqNum, final Node root) { runInCompilerThread( () -> { if (options.printInputDelimiter) { if ((cb.getLength() > 0) && !cb.endsWith("\n")) { cb.append("\n"); // Make sure that the label starts on a new line } checkState(root.isScript()); String delimiter = options.inputDelimiter; String inputName = root.getInputId().getIdName(); String sourceName = root.getSourceFileName(); checkState(sourceName != null); checkState(!sourceName.isEmpty()); delimiter = delimiter .replace("%name%", Matcher.quoteReplacement(inputName)) .replace("%num%", String.valueOf(inputSeqNum)) .replace("%n%", "\n"); cb.append(delimiter).append("\n"); } if (root.getJSDocInfo() != null) { String license = root.getJSDocInfo().getLicense(); if (license != null && cb.addLicense(license)) { cb.append("/*\n").append(license).append("*/\n"); } } // If there is a valid source map, then indicate to it that the current // root node's mappings are offset by the given string builder buffer. if (options.sourceMapOutputPath != null) { sourceMap.setStartingPosition(cb.getLineIndex(), cb.getColumnIndex()); } // if LanguageMode is strict, only print 'use strict' // for the first input file String code = toSource(root, sourceMap, inputSeqNum == 0); if (!code.isEmpty()) { cb.append(code); // In order to avoid parse ambiguity when files are concatenated // together, all files should end in a semi-colon. Do a quick // heuristic check if there's an obvious semi-colon already there. int length = code.length(); char lastChar = code.charAt(length - 1); char secondLastChar = length >= 2 ? code.charAt(length - 2) : '\0'; boolean hasSemiColon = lastChar == ';' || (lastChar == '\n' && secondLastChar == ';'); if (!hasSemiColon) { cb.append(";"); } } return null; }); } /** * Generates JavaScript source code for an AST, doesn't generate source * map info. */ @Override public String toSource(Node n) { initCompilerOptionsIfTesting(); return toSource(n, null, true); } /** * Generates JavaScript source code for an AST. */ private String toSource(Node n, SourceMap sourceMap, boolean firstOutput) { CodePrinter.Builder builder = new CodePrinter.Builder(n); builder.setTypeRegistry(getTypeRegistry()); builder.setCompilerOptions(options); builder.setSourceMap(sourceMap); builder.setTagAsTypeSummary(!n.isFromExterns() && options.shouldGenerateTypedExterns()); builder.setTagAsStrict(firstOutput && options.shouldEmitUseStrict()); return builder.build(); } /** * Converts the parse tree for each input back to JS code. */ public String[] toSourceArray() { return runInCompilerThread( () -> { Tracer tracer = newTracer("toSourceArray"); try { int numInputs = moduleGraph.getInputCount(); String[] sources = new String[numInputs]; CodeBuilder cb = new CodeBuilder(); int i = 0; for (CompilerInput input : moduleGraph.getAllInputs()) { Node scriptNode = input.getAstRoot(Compiler.this); cb.reset(); toSource(cb, i, scriptNode); sources[i] = cb.toString(); i++; } return sources; } finally { stopTracer(tracer, "toSourceArray"); } }); } /** * Converts the parse tree for each input in a module back to JS code. */ public String[] toSourceArray(final JSModule module) { return runInCompilerThread( () -> { List inputs = module.getInputs(); int numInputs = inputs.size(); if (numInputs == 0) { return new String[0]; } String[] sources = new String[numInputs]; CodeBuilder cb = new CodeBuilder(); for (int i = 0; i < numInputs; i++) { Node scriptNode = inputs.get(i).getAstRoot(Compiler.this); if (scriptNode == null) { throw new IllegalArgumentException("Bad module input: " + inputs.get(i).getName()); } cb.reset(); toSource(cb, i, scriptNode); sources[i] = cb.toString(); } return sources; }); } /** * Stores a buffer of text to which more can be appended. This is just like a * StringBuilder except that we also track the number of lines. */ public static class CodeBuilder { private final StringBuilder sb = new StringBuilder(); private int lineCount = 0; private int colCount = 0; private final Set uniqueLicenses = new HashSet<>(); /** Removes all text, but leaves the line count unchanged. */ void reset() { sb.setLength(0); } /** Appends the given string to the text buffer. */ CodeBuilder append(String str) { sb.append(str); // Adjust the line and column information for the new text. int index = -1; int lastIndex = index; while ((index = str.indexOf('\n', index + 1)) >= 0) { ++lineCount; lastIndex = index; } if (lastIndex == -1) { // No new lines, append the new characters added. colCount += str.length(); } else { colCount = str.length() - (lastIndex + 1); } return this; } /** Returns all text in the text buffer. */ @Override public String toString() { return sb.toString(); } /** Returns the length of the text buffer. */ public int getLength() { return sb.length(); } /** Returns the (zero-based) index of the last line in the text buffer. */ int getLineIndex() { return lineCount; } /** Returns the (zero-based) index of the last column in the text buffer. */ int getColumnIndex() { return colCount; } /** Determines whether the text ends with the given suffix. */ boolean endsWith(String suffix) { return (sb.length() > suffix.length()) && suffix.equals(sb.substring(sb.length() - suffix.length())); } /** Adds a license and returns whether it is unique (has yet to be encountered). */ boolean addLicense(String license) { return uniqueLicenses.add(license); } } //------------------------------------------------------------------------ // Optimizations //------------------------------------------------------------------------ void performOptimizations() { checkState(options.shouldOptimize()); List optimizations = getPassConfig().getOptimizations(); if (optimizations.isEmpty()) { return; } phaseOptimizer = createPhaseOptimizer(); phaseOptimizer.consume(optimizations); phaseOptimizer.process(externsRoot, jsRoot); phaseOptimizer = null; } @Override void setCssRenamingMap(CssRenamingMap map) { options.cssRenamingMap = map; } @Override CssRenamingMap getCssRenamingMap() { return options.cssRenamingMap; } /** Control Flow Analysis. */ ControlFlowGraph computeCFG() { logger.fine("Computing Control Flow Graph"); Tracer tracer = newTracer("computeCFG"); ControlFlowAnalysis cfa = new ControlFlowAnalysis(this, true, false); process(cfa); stopTracer(tracer, "computeCFG"); return cfa.getCfg(); } @Override void prepareAst(Node root) { CompilerPass pass = new PrepareAst(this); pass.process(null, root); } protected final RecentChange recentChange = new RecentChange(); private final List codeChangeHandlers = new ArrayList<>(); private final Map, IndexProvider> indexProvidersByType = new LinkedHashMap<>(); /** Name of the synthetic input that holds synthesized externs. */ static final String SYNTHETIC_EXTERNS = "{SyntheticVarsDeclar}"; /** * Name of the synthetic input that holds synthesized externs which * must be at the end of the externs AST. */ static final String SYNTHETIC_EXTERNS_AT_END = "{SyntheticVarsAtEnd}"; /** Prefix of the generated file name for synthetic injected libraries */ static final String SYNTHETIC_CODE_PREFIX = " [synthetic:"; private static final InputId SYNTHETIC_CODE_INPUT_ID = new InputId(SYNTHETIC_CODE_PREFIX + "input]"); private CompilerInput synthesizedExternsInput = null; private CompilerInput synthesizedExternsInputAtEnd = null; private CompilerInput synthesizedCodeInput = null; private ImmutableMap defaultDefineValues = ImmutableMap.of(); @Override void addChangeHandler(CodeChangeHandler handler) { codeChangeHandlers.add(handler); } @Override void removeChangeHandler(CodeChangeHandler handler) { codeChangeHandlers.remove(handler); } @Override void addIndexProvider(IndexProvider indexProvider) { Class type = indexProvider.getType(); if (indexProvidersByType.put(type, indexProvider) != null) { throw new IllegalStateException( "A provider is already registered for index of type " + type.getSimpleName()); } } @SuppressWarnings("unchecked") @Override T getIndex(Class key) { IndexProvider indexProvider = (IndexProvider) indexProvidersByType.get(key); if (indexProvider == null) { return null; } return indexProvider.get(); } protected Node getExternsRoot() { return externsRoot; } @Override protected Node getJsRoot() { return jsRoot; } /** * Some tests don't want to call the compiler "wholesale," they may not want * to call check and/or optimize. With this method, tests can execute custom * optimization loops. */ @VisibleForTesting void setPhaseOptimizer(PhaseOptimizer po) { this.phaseOptimizer = po; } @Override public int getChangeStamp() { return changeStamp; } @Override List getChangedScopeNodesForPass(String passName) { List changedScopeNodes = changeTimeline.getSince(passName); changeTimeline.mark(passName); return changedScopeNodes; } @Override List getDeletedScopeNodesForPass(String passName) { List deletedScopeNodes = deleteTimeline.getSince(passName); deleteTimeline.mark(passName); return deletedScopeNodes; } @Override public void incrementChangeStamp() { changeStamp++; } private Node getChangeScopeForNode(Node n) { /** * Compiler change reporting usually occurs after the AST change has already occurred. In the * case of node removals those nodes are already removed from the tree and so have no parent * chain to walk. In these situations changes are reported instead against what (used to be) * their parent. If that parent is itself a script node then it's important to be able to * recognize it as the enclosing scope without first stepping to its parent as well. */ if (n.isScript()) { return n; } Node enclosingScopeNode = NodeUtil.getEnclosingChangeScopeRoot(n.getParent()); if (enclosingScopeNode == null) { throw new IllegalStateException( "An enclosing scope is required for change reports but node " + n + " doesn't have one."); } return enclosingScopeNode; } private void recordChange(Node n) { if (n.isDeleted()) { // Some complicated passes (like SmartNameRemoval) might both change and delete a scope in // the same pass, and they might even perform the change after the deletion because of // internal queueing. Just ignore the spurious attempt to mark changed after already marking // deleted. There's no danger of deleted nodes persisting in the AST since this is enforced // separately in ChangeVerifier. return; } n.setChangeTime(changeStamp); // Every code change happens at a different time changeStamp++; changeTimeline.add(n); } @Override boolean hasScopeChanged(Node n) { if (phaseOptimizer == null) { return true; } return phaseOptimizer.hasScopeChanged(n); } @Override public void reportChangeToChangeScope(Node changeScopeRoot) { checkState(changeScopeRoot.isScript() || changeScopeRoot.isFunction()); recordChange(changeScopeRoot); notifyChangeHandlers(); } @Override public void reportFunctionDeleted(Node n) { checkState(n.isFunction()); n.setDeleted(true); changeTimeline.remove(n); deleteTimeline.add(n); } @Override public void reportChangeToEnclosingScope(Node n) { recordChange(getChangeScopeForNode(n)); notifyChangeHandlers(); } private void notifyChangeHandlers() { for (CodeChangeHandler handler : codeChangeHandlers) { handler.reportChange(); } } @Override public CodingConvention getCodingConvention() { CodingConvention convention = options.getCodingConvention(); convention = convention != null ? convention : defaultCodingConvention; return convention; } private Config.LanguageMode getParserConfigLanguageMode( CompilerOptions.LanguageMode languageMode) { switch (languageMode) { case ECMASCRIPT3: return Config.LanguageMode.ECMASCRIPT3; case ECMASCRIPT5: case ECMASCRIPT5_STRICT: return Config.LanguageMode.ECMASCRIPT5; case ECMASCRIPT_2015: return Config.LanguageMode.ECMASCRIPT6; case ECMASCRIPT6_TYPED: return Config.LanguageMode.TYPESCRIPT; case ECMASCRIPT_2016: return Config.LanguageMode.ECMASCRIPT7; case ECMASCRIPT_2017: return Config.LanguageMode.ECMASCRIPT8; case ECMASCRIPT_2018: return Config.LanguageMode.ECMASCRIPT_2018; case ECMASCRIPT_2019: return Config.LanguageMode.ECMASCRIPT_2019; case ECMASCRIPT_2020: return Config.LanguageMode.ECMASCRIPT_2020; case UNSUPPORTED: return Config.LanguageMode.UNSUPPORTED; case ECMASCRIPT_NEXT: return Config.LanguageMode.ES_NEXT; case ECMASCRIPT_NEXT_IN: return Config.LanguageMode.ES_NEXT_IN; default: throw new IllegalStateException("Unexpected language mode: " + options.getLanguageIn()); } } @Override Config getParserConfig(ConfigContext context) { if (parserConfig == null || externsParserConfig == null) { synchronized (this) { if (parserConfig == null) { Config.LanguageMode configLanguageMode = getParserConfigLanguageMode( options.getLanguageIn()); Config.StrictMode strictMode = options.expectStrictModeInput() ? Config.StrictMode.STRICT : Config.StrictMode.SLOPPY; parserConfig = createConfig(configLanguageMode, strictMode); // Externs must always be parsed with at least ES5 language mode. externsParserConfig = configLanguageMode.equals(Config.LanguageMode.ECMASCRIPT3) ? createConfig(Config.LanguageMode.ECMASCRIPT5, strictMode) : parserConfig; } } } switch (context) { case EXTERNS: return externsParserConfig; default: return parserConfig; } } protected Config createConfig(Config.LanguageMode mode, Config.StrictMode strictMode) { Config config = ParserRunner.createConfig( mode, options.isParseJsDocDocumentation(), options.canContinueAfterErrors() ? Config.RunMode.KEEP_GOING : Config.RunMode.STOP_AFTER_ERROR, options.extraAnnotationNames, options.parseInlineSourceMaps, strictMode); return config; } // ------------------------------------------------------------------------ // Error reporting // ------------------------------------------------------------------------ /** * The warning classes that are available from the command-line, and are suppressible by the * {@code @suppress} annotation. */ public DiagnosticGroups getDiagnosticGroups() { return new DiagnosticGroups(); } @Override public void report(JSError error) { CheckLevel level = error.getDefaultLevel(); if (warningsGuard != null) { CheckLevel newLevel = warningsGuard.level(error); if (newLevel != null) { level = newLevel; } } if (level.isOn()) { initCompilerOptionsIfTesting(); if (getOptions().errorHandler != null) { getOptions().errorHandler.report(level, error); } errorManager.report(level, error); } } @Override public void report(CheckLevel ignoredLevel, JSError error) { report(error); } @Override public CheckLevel getErrorLevel(JSError error) { checkNotNull(options); return warningsGuard.level(error); } /** * Report an internal error. */ @Override void throwInternalError(String message, Throwable cause) { String finalMessage = "INTERNAL COMPILER ERROR.\nPlease report this problem.\n\n" + message; RuntimeException e = new RuntimeException(finalMessage, cause); if (cause != null) { e.setStackTrace(cause.getStackTrace()); } throw e; } /** * Gets the number of errors. */ public int getErrorCount() { return errorManager.getErrorCount(); } /** * Gets the number of warnings. */ public int getWarningCount() { return errorManager.getWarningCount(); } @Override boolean hasHaltingErrors() { return !getOptions().canContinueAfterErrors() && errorManager.hasHaltingErrors(); } /** * Consults the {@link ErrorManager} to see if we've encountered errors * that should halt compilation.

* * If {@link CompilerOptions#canContinueAfterErrors} is {@code true}, this function * always returns {@code false} without consulting the error manager. The * error manager will continue to be told about new errors and warnings, but * the compiler will complete compilation of all inputs.

*/ public boolean hasErrors() { return hasHaltingErrors(); } @Override SourceFile getSourceFileByName(String sourceName) { // Here we assume that the source name is the input name, this // is true of JavaScript parsed from source. if (sourceName != null) { CompilerInput input = inputsById.get(new InputId(sourceName)); if (input != null) { return input.getSourceFile(); } // Alternatively, the sourceName might have been reverse-mapped by // an input source-map, so let's look in our sourcemap original sources. return sourceMapOriginalSources.get(sourceName); } return null; } public CharSequence getSourceFileContentByName(String sourceName) { SourceFile file = getSourceFileByName(sourceName); checkNotNull(file); try { return file.getCode(); } catch (IOException e) { return null; } } @Override public void addInputSourceMap(String sourceFileName, SourceMapInput inputSourceMap) { inputSourceMaps.put(sourceFileName, inputSourceMap); if (options.sourceMapIncludeSourcesContent && sourceMap != null) { addSourceMapSourceFiles(inputSourceMap); } } /** * Adds file name to content mappings for all sources found in a source map. * This is used to populate sourcesContent array in the output source map * even for sources embedded in the input source map. */ private synchronized void addSourceMapSourceFiles(SourceMapInput inputSourceMap) { // synchronized annotation guards concurrent access to sourceMap during parsing. SourceMapConsumerV3 consumer = inputSourceMap.getSourceMap(errorManager); if (consumer == null) { return; } Collection sourcesContent = consumer.getOriginalSourcesContent(); if (sourcesContent == null) { return; } Iterator content = sourcesContent.iterator(); Iterator sources = consumer.getOriginalSources().iterator(); while (sources.hasNext() && content.hasNext()) { String code = content.next(); SourceFile source = SourceMapResolver.getRelativePath( inputSourceMap.getOriginalPath(), sources.next()); if (source != null) { sourceMap.addSourceFile(source.getOriginalPath(), code); } } if (sources.hasNext() || content.hasNext()) { throw new RuntimeException( "Source map's \"sources\" and \"sourcesContent\" lengths do not match."); } } @Override @Nullable public OriginalMapping getSourceMapping(String sourceName, int lineNumber, int columnNumber) { if (sourceName == null) { return null; } SourceMapInput sourceMap = inputSourceMaps.get(sourceName); if (sourceMap == null) { return null; } // JSCompiler uses 1-indexing for lineNumber and 0-indexing for columnNumber. // Sourcemaps use 1-indexing for both. SourceMapConsumerV3 consumer = sourceMap.getSourceMap(errorManager); if (consumer == null) { return null; } OriginalMapping result = consumer.getMappingForLine(lineNumber, columnNumber + 1); if (result == null) { return null; } // First check to see if the original file was loaded from an input source map. String sourceMapOriginalPath = sourceMap.getOriginalPath(); String resultOriginalPath = result.getOriginalFile(); final String relativePath; // Resolving the paths to a source file is expensive, so check the cache first. if (sourceMapOriginalPath.equals(resolvedSourceMap.originalPath) && resultOriginalPath.equals(resolvedSourceMap.sourceMapPath)) { relativePath = resolvedSourceMap.relativePath; } else { relativePath = resolveSibling(sourceMapOriginalPath, resultOriginalPath); SourceFile source = getSourceFileByName(relativePath); if (source == null && !isNullOrEmpty(resultOriginalPath)) { source = SourceMapResolver.getRelativePath( sourceMap.getOriginalPath(), result.getOriginalFile()); if (source != null) { sourceMapOriginalSources.putIfAbsent(relativePath, source); } } // Cache this resolved source for the next caller. resolvedSourceMap.originalPath = sourceMapOriginalPath; resolvedSourceMap.sourceMapPath = resultOriginalPath; resolvedSourceMap.relativePath = relativePath; } return result.toBuilder() .setOriginalFile(relativePath) .setColumnPosition(result.getColumnPosition() - 1) .build(); } @Override public String getSourceLine(String sourceName, int lineNumber) { if (lineNumber < 1) { return null; } SourceFile input = getSourceFileByName(sourceName); if (input != null) { return input.getLine(lineNumber); } return null; } @Override public Region getSourceLines(String sourceName, int lineNumber, int length) { if (lineNumber < 1) { return null; } SourceFile input = getSourceFileByName(sourceName); if (input != null) { return input.getLines(lineNumber, length); } return null; } @Override public Region getSourceRegion(String sourceName, int lineNumber) { if (lineNumber < 1) { return null; } SourceFile input = getSourceFileByName(sourceName); if (input != null) { return input.getRegion(lineNumber); } return null; } // ------------------------------------------------------------------------ // Package-private and Protected helpers // ------------------------------------------------------------------------ @Override protected Node getNodeForCodeInsertion(@Nullable JSModule module) { if (synthesizedCodeInput != null) { return synthesizedCodeInput.getAstRoot(this); } if (module == null) { if (moduleGraph == null || Iterables.isEmpty(moduleGraph.getAllInputs())) { throw new IllegalStateException("No inputs"); } CompilerInput firstInput = Iterables.getFirst(moduleGraph.getAllInputs(), null); // TODO(lharker): add `checkNotModule(`. return firstInput.getAstRoot(this); } List moduleInputs = module.getInputs(); if (!moduleInputs.isEmpty()) { return checkNotModule( moduleInputs.get(0).getAstRoot(this), "Cannot insert code into a module"); } throw new IllegalStateException("Root module has no inputs"); } public SourceMap getSourceMap() { return sourceMap; } /** * Ids for cross-module method stubbing, so that each method has * a unique id. */ private IdGenerator crossModuleIdGenerator = new IdGenerator(); /** * Keys are arguments passed to getCssName() found during compilation; values * are the number of times the key appeared as an argument to getCssName(). */ private Map cssNames = null; /** The variable renaming map */ private VariableMap variableMap = null; /** The property renaming map */ private VariableMap propertyMap = null; /** The naming map for anonymous functions */ private VariableMap anonymousFunctionNameMap = null; /** String replacement map */ private VariableMap stringMap = null; /** Mapping for Instrumentation parameter encoding */ private VariableMap instrumentationMapping = null; /** Id generator map */ private String idGeneratorMap = null; /** Names exported by goog.exportSymbol. */ private final Set exportedNames = new LinkedHashSet<>(); @Override public void setVariableMap(VariableMap variableMap) { this.variableMap = variableMap; } VariableMap getVariableMap() { return variableMap; } @Override public void setPropertyMap(VariableMap propertyMap) { this.propertyMap = propertyMap; } VariableMap getPropertyMap() { return this.propertyMap; } @Override public void setStringMap(VariableMap stringMap) { this.stringMap = stringMap; } @Override public void setCssNames(Map cssNames) { this.cssNames = cssNames; } @Override public void setIdGeneratorMap(String serializedIdMappings) { this.idGeneratorMap = serializedIdMappings; } @Override public IdGenerator getCrossModuleIdGenerator() { return crossModuleIdGenerator; } @Override public void setAnonymousFunctionNameMap(VariableMap functionMap) { this.anonymousFunctionNameMap = functionMap; } VariableMap getStringMap() { return this.stringMap; } @Override public void setInstrumentationMapping(VariableMap instrumentationMapping) { this.instrumentationMapping = instrumentationMapping; } public VariableMap getInstrumentationMapping() { return this.instrumentationMapping; } @Override public void addExportedNames(Set exportedNames) { this.exportedNames.addAll(exportedNames); } @Override public Set getExportedNames() { return exportedNames; } @Override public CompilerOptions getOptions() { return options; } /** * Sets the logging level for the com.google.javascript.jscomp package. */ public static void setLoggingLevel(Level level) { logger.setLevel(level); } /** Gets the DOT graph of the AST generated at the end of compilation. */ public String getAstDotGraph() throws IOException { if (jsRoot != null) { ControlFlowAnalysis cfa = new ControlFlowAnalysis(this, true, false); cfa.process(null, jsRoot); return DotFormatter.toDot(jsRoot, cfa.getCfg()); } else { return ""; } } @Override public ErrorManager getErrorManager() { if (options == null) { initOptions(new CompilerOptions()); } return errorManager; } @Override Iterable getInputsInOrder() { return moduleGraph.getAllInputs(); } @Override int getNumberOfInputs() { // In some testing cases inputs will be null, but obviously there must be at least one input. // The intended use of this method is to allow passes to estimate how much memory they will // need for data structures, so it's not necessary that the returned value be exactly right // in the corner cases where inputs ends up being null. return (moduleGraph != null) ? moduleGraph.getInputCount() : 1; } /** * Returns an unmodifiable view of the compiler inputs indexed by id. */ public Map getInputsById() { return Collections.unmodifiableMap(inputsById); } /** * Gets the externs in the order in which they are being processed. */ List getExternsInOrder() { return Collections.unmodifiableList(externs); } @VisibleForTesting List getInputsForTesting() { return moduleGraph != null ? ImmutableList.copyOf(moduleGraph.getAllInputs()) : null; } @VisibleForTesting List getExternsForTesting() { return externs; } @Override boolean hasRegExpGlobalReferences() { return hasRegExpGlobalReferences; } @Override void setHasRegExpGlobalReferences(boolean references) { hasRegExpGlobalReferences = references; } @Override void updateGlobalVarReferences(Map refMapPatch, Node collectionRoot) { checkState(collectionRoot.isScript() || collectionRoot.isRoot()); if (globalRefMap == null) { globalRefMap = new GlobalVarReferenceMap(getInputsInOrder(), getExternsInOrder()); } globalRefMap.updateGlobalVarReferences(refMapPatch, collectionRoot); } @Override GlobalVarReferenceMap getGlobalVarReferences() { return globalRefMap; } @Override CompilerInput getSynthesizedExternsInput() { if (synthesizedExternsInput == null) { synthesizedExternsInput = newExternInput(SYNTHETIC_EXTERNS, SyntheticExternsPosition.START); } return synthesizedExternsInput; } @Override InputId getSyntheticCodeInputId() { return SYNTHETIC_CODE_INPUT_ID; } @Override void initializeSyntheticCodeInput() { checkState(synthesizedCodeInput == null, "Already initialized synthetic input"); SourceAst ast = new SyntheticAst(SYNTHETIC_CODE_INPUT_ID.getIdName()); if (inputsById.containsKey(ast.getInputId())) { throw new IllegalStateException("Conflicting synthetic id name"); } CompilerInput input = new CompilerInput(ast, false); jsRoot.addChildToFront(checkNotNull(ast.getAstRoot(this))); JSModule firstModule = Iterables.getFirst(getModules(), null); if (firstModule.getName().equals(JSModule.STRONG_MODULE_NAME)) { firstModule.add(input); } input.setModule(firstModule); putCompilerInput(input.getInputId(), input); synthesizedCodeInput = input; commentsPerFile.put(SYNTHETIC_CODE_INPUT_ID.getIdName(), ImmutableList.of()); reportChangeToChangeScope(ast.getAstRoot(this)); } @Override void removeSyntheticCodeInput() { this.removeSyntheticCodeInput(/* mergeContentIntoFirstInput= */ false); } @Override void mergeSyntheticCodeInput() { this.removeSyntheticCodeInput(/* mergeContentIntoFirstInput= */ true); } /** * Deletes the synthesized code input and optionally moves all its subtree contents into the first * non-synthetic input */ private void removeSyntheticCodeInput(boolean mergeContentIntoFirstInput) { checkNotNull(synthesizedCodeInput, "Never initialized the synthetic input"); CompilerInput input = synthesizedCodeInput; Node astRoot = input.getAstRoot(this); checkState(astRoot.isFirstChildOf(jsRoot)); checkState(SYNTHETIC_CODE_INPUT_ID.equals(input.getInputId())); if (mergeContentIntoFirstInput && astRoot.hasChildren()) { // If we've inserted anything into the synthetic AST, move it into the top of the next script. Node next = astRoot.getNext(); checkNotNull(next, "Must provide at least one source"); checkNotModule( next, "Cannot remove synthetic code input until modules are rewritten: %s", next); next.addChildrenToFront(astRoot.removeChildren()); reportChangeToChangeScope(next); } jsRoot.removeChild(astRoot); // bookkeeping to mark scopes and nodes as deleted reportChangeToChangeScope(astRoot); astRoot.setDeleted(true); NodeUtil.markFunctionsDeleted(astRoot, this); input.getModule().remove(input); inputsById.remove(input.getInputId()); this.synthesizedCodeInput = null; } @Override CompilerInput getSynthesizedExternsInputAtEnd() { if (synthesizedExternsInputAtEnd == null) { synthesizedExternsInputAtEnd = newExternInput( SYNTHETIC_EXTERNS_AT_END, SyntheticExternsPosition.END); } return synthesizedExternsInputAtEnd; } @Override public double getProgress() { return progress; } @Override String getLastPassName() { return lastPassName; } @Override void setProgress(double newProgress, String passName) { this.lastPassName = passName; if (newProgress > 1.0) { progress = 1.0; } else { progress = newProgress; } } @Override void setExternProperties(Set externProperties) { this.externProperties = externProperties; } @Override Set getExternProperties() { return externProperties; } @Override AccessorSummary getAccessorSummary() { return accessorSummary; } @Override void setAccessorSummary(AccessorSummary summary) { this.accessorSummary = summary; } /** * Replaces one file in a hot-swap mode. The given JsAst should be made * from a new version of a file that already was present in the last compile * call. If the file is new, this will silently ignored. * * @param ast the ast of the file that is being replaced */ public void replaceScript(JsAst ast) { CompilerInput input = this.getInput(ast.getInputId()); if (!replaceIncrementalSourceAst(ast)) { return; } Node originalRoot = checkNotNull(input.getAstRoot(this)); processNewScript(ast, originalRoot); } /** * Adds a new Script AST to the compile state. If a script for the same file * already exists the script will not be added, instead a call to * #replaceScript should be used. * * @param ast the ast of the new file */ public void addNewScript(JsAst ast) { if (!addNewSourceAst(ast)) { return; } Node emptyScript = new Node(Token.SCRIPT); InputId inputId = ast.getInputId(); emptyScript.setInputId(inputId); emptyScript.setStaticSourceFile( SourceFile.fromCode(inputId.getIdName(), "")); processNewScript(ast, emptyScript); } private void processNewScript(JsAst ast, Node originalRoot) { setFeatureSet(options.getLanguageIn().toFeatureSet()); Node js = checkNotNull(ast.getAstRoot(this)); runHotSwap(originalRoot, js, this.getCleanupPassConfig()); // NOTE: If hot swap passes that use GlobalNamespace are added, we will need // to revisit this approach to clearing GlobalNamespaces runHotSwapPass(null, null, ensureDefaultPassConfig().garbageCollectChecks); // Type information is not reliable for hotswap runs. this.typeCheckingHasRun = false; this.removeSyntheticVarsInput(); runHotSwap(originalRoot, js, this.ensureDefaultPassConfig()); } /** * Execute the passes from a PassConfig instance over a single replaced file. */ private void runHotSwap( Node originalRoot, Node js, PassConfig passConfig) { for (PassFactory passFactory : passConfig.getChecks()) { runHotSwapPass(originalRoot, js, passFactory); } } private void runHotSwapPass(Node originalRoot, Node js, PassFactory passFactory) { if (!passFactory.isHotSwapable()) { return; } HotSwapCompilerPass pass = (HotSwapCompilerPass) passFactory.create(this); if (logger.isLoggable(Level.INFO)) { logger.info("Performing HotSwap for pass " + passFactory.getName()); } pass.hotSwapScript(js, originalRoot); } private PassConfig getCleanupPassConfig() { return new CleanupPasses(getOptions()); } private void removeSyntheticVarsInput() { String sourceName = Compiler.SYNTHETIC_EXTERNS; removeExternInput(new InputId(sourceName)); } @Override protected Node ensureLibraryInjected(String resourceName, boolean force) { boolean shouldInject = force || (!options.skipNonTranspilationPasses && !options.preventLibraryInjection); if (injectedLibraries.containsKey(resourceName) || !shouldInject) { return lastInjectedLibrary; } checkState(!hasTypeCheckingRun(), "runtime library injected after type checking"); checkState(!getLifeCycleStage().isNormalized(), "runtime library injected after normalization"); // Load/parse the code. String originalCode = ResourceLoader.loadTextResource( Compiler.class, "js/" + resourceName + ".js"); Node ast = parseSyntheticCode(SYNTHETIC_CODE_PREFIX + resourceName + "] ", originalCode); // Look for string literals of the form 'require foo bar' or 'declare baz''. // As we process each one, remove it from its parent. for (Node node = ast.getFirstChild(); node != null && node.isExprResult() && node.getFirstChild().isString(); node = ast.getFirstChild()) { String directive = node.getFirstChild().getString(); List words = Splitter.on(' ').limit(2).splitToList(directive); switch (words.get(0)) { case "use": // 'use strict' is ignored (and deleted). break; case "require": // 'require lib'; pulls in the named library before this one. ensureLibraryInjected(words.get(1), force); break; default: throw new RuntimeException("Bad directive: " + directive); } ast.removeChild(node); } // Insert the code immediately after the last-inserted runtime library. Node lastChild = ast.getLastChild(); for (Node child = ast.getFirstChild(); child != null; child = child.getNext()) { NodeUtil.markNewScopesChanged(child, this); } Node firstChild = ast.removeChildren(); if (firstChild == null) { // Handle require-only libraries. return lastInjectedLibrary; } Node parent = getNodeForCodeInsertion(null); if (lastInjectedLibrary == null) { parent.addChildrenToFront(firstChild); } else { parent.addChildrenAfter(firstChild, lastInjectedLibrary); } lastInjectedLibrary = lastChild; injectedLibraries.put(resourceName, lastChild); reportChangeToEnclosingScope(parent); return lastChild; } /** Returns the compiler version baked into the jar. */ @GwtIncompatible("java.util.ResourceBundle") public static String getReleaseVersion() { ResourceBundle config = ResourceBundle.getBundle(CONFIG_RESOURCE); return config.getString("compiler.version"); } /** Returns the compiler date baked into the jar. */ @GwtIncompatible("java.util.ResourceBundle") public static String getReleaseDate() { ResourceBundle config = ResourceBundle.getBundle(CONFIG_RESOURCE); return config.getString("compiler.date"); } @Override void addComments(String filename, List comments) { if (!getOptions().preservesDetailedSourceInfo()) { throw new UnsupportedOperationException( "addComments may only be called in IDE mode."); } commentsPerFile.put(filename, comments); } @Override public List getComments(String filename) { if (!getOptions().preservesDetailedSourceInfo()) { throw new UnsupportedOperationException( "getComments may only be called in IDE mode."); } return commentsPerFile.get(filename); } @Override void setDefaultDefineValues(ImmutableMap values) { this.defaultDefineValues = values; } @Override ImmutableMap getDefaultDefineValues() { return this.defaultDefineValues; } @Override public ModuleLoader getModuleLoader() { return moduleLoader; } private synchronized void addFilesToSourceMap(Iterable files) { // synchronized annotation guards concurrent access to sourceMap during parsing. if (getOptions().sourceMapIncludeSourcesContent && getSourceMap() != null) { for (SourceFile file : files) { try { getSourceMap().addSourceFile(file.getName(), file.getCode()); } catch (IOException e) { throw new RuntimeException("Cannot read code of a source map's source file.", e); } } } } private void renameModules(List newModules, List deserializedModules) { if (newModules == null) { return; } if (newModules.size() != deserializedModules.size()) { report(JSError.make(INCONSISTENT_MODULE_DEFINITIONS)); return; } for (int i = 0; i < deserializedModules.size(); i++) { JSModule deserializedModule = deserializedModules.get(i); JSModule newModule = newModules.get(i); deserializedModule.setName(newModule.getName()); } return; } public void initWebpackMap(ImmutableMap inputPathByWebpackId) { this.inputPathByWebpackId = inputPathByWebpackId; } protected CompilerExecutor createCompilerExecutor() { return new CompilerExecutor(); } protected CompilerExecutor getCompilerExecutor() { return compilerExecutor; } /** * Serializable state of the compiler. */ private static class CompilerState implements Serializable { private final Node externAndJsRoot; private final Node externsRoot; private final Node jsRoot; private final FeatureSet featureSet; private final List externs; private final Map scriptNodeByFilename; private final Map inputsById; private final JSTypeRegistry typeRegistry; private final TypeValidator typeValidator; private final boolean typeCheckingHasRun; private final CompilerInput synthesizedExternsInput; private final CompilerInput synthesizedExternsInputAtEnd; private final Map injectedLibraries; private final Node lastInjectedLibrary; private final boolean hasRegExpGlobalReferences; private final LifeCycleStage lifeCycleStage; private final Set externProperties; private final ImmutableList errors; private final ImmutableList warnings; private final JSModuleGraph moduleGraph; private final int uniqueNameId; private final UniqueIdSupplier uniqueIdSupplier; private final Set exportedNames; private final Map cssNames; private final VariableMap variableMap; private final VariableMap propertyMap; private final VariableMap anonymousFunctionaMap; private final VariableMap stringMap; private final String idGeneratorMap; private final IdGenerator crossModuleIdGenerator; private final ImmutableMap defaultDefineValues; private final Map annotationMap; private final ConcurrentHashMap inputSourceMaps; private final int changeStamp; CompilerState(Compiler compiler) { this.externsRoot = checkNotNull(compiler.externsRoot); this.jsRoot = checkNotNull(compiler.jsRoot); this.externAndJsRoot = checkNotNull(compiler.externAndJsRoot); this.featureSet = checkNotNull(compiler.featureSet); this.typeRegistry = compiler.typeRegistry; this.externs = compiler.externs; this.scriptNodeByFilename = checkNotNull(compiler.scriptNodeByFilename); this.inputsById = checkNotNull(compiler.inputsById); this.typeCheckingHasRun = compiler.typeCheckingHasRun; this.synthesizedExternsInput = compiler.synthesizedExternsInput; this.synthesizedExternsInputAtEnd = compiler.synthesizedExternsInputAtEnd; this.injectedLibraries = compiler.injectedLibraries; this.lastInjectedLibrary = compiler.lastInjectedLibrary; this.hasRegExpGlobalReferences = compiler.hasRegExpGlobalReferences; this.typeValidator = compiler.typeValidator; this.lifeCycleStage = compiler.getLifeCycleStage(); this.externProperties = compiler.externProperties; this.errors = compiler.errorManager.getErrors(); this.warnings = compiler.errorManager.getWarnings(); this.moduleGraph = compiler.moduleGraph; this.uniqueNameId = compiler.uniqueNameId; this.uniqueIdSupplier = compiler.uniqueIdSupplier; this.exportedNames = compiler.exportedNames; this.cssNames = compiler.cssNames; this.variableMap = compiler.variableMap; this.propertyMap = compiler.propertyMap; this.anonymousFunctionaMap = compiler.anonymousFunctionNameMap; this.stringMap = compiler.stringMap; this.idGeneratorMap = compiler.idGeneratorMap; this.crossModuleIdGenerator = compiler.crossModuleIdGenerator; this.defaultDefineValues = checkNotNull(compiler.defaultDefineValues); this.annotationMap = checkNotNull(compiler.annotationMap); this.inputSourceMaps = compiler.inputSourceMaps; this.changeStamp = compiler.changeStamp; } } @GwtIncompatible("ObjectOutputStream") public void saveState(OutputStream outputStream) throws IOException { // Do not close the outputstream, caller is responsible for closing it. final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); runInCompilerThread( () -> { Tracer tracer = newTracer("serializeCompilerState"); objectOutputStream.writeObject(new CompilerState(Compiler.this)); if (typeRegistry != null) { typeRegistry.saveContents(objectOutputStream); } stopTracer(tracer, "serializeCompilerState"); return null; }); } @GwtIncompatible("ObjectInputStream") public void restoreState(InputStream inputStream) throws IOException, ClassNotFoundException { initWarningsGuard(options.getWarningsGuard()); maybeSetTracker(); // Make a copy of the current module list so we can later reapply their names to the // deserialized modules. List newModules = null; if (getModules() != null) { newModules = ImmutableList.copyOf(getModules()); } class CompilerObjectInputStream extends ObjectInputStream implements HasCompiler { public CompilerObjectInputStream(InputStream in) throws IOException { super(in); } @Override public AbstractCompiler getCompiler() { return Compiler.this; } } // Do not close the input stream, caller is responsible for closing it. final ObjectInputStream objectInputStream = new CompilerObjectInputStream(inputStream); CompilerState compilerState = runInCompilerThread( new Callable() { @Override public CompilerState call() throws Exception { Tracer tracer = newTracer(PassNames.DESERIALIZE_COMPILER_STATE); logger.fine("Deserializing the CompilerState"); CompilerState compilerState = (CompilerState) objectInputStream.readObject(); logger.fine("Finished deserializing CompilerState"); if (compilerState.typeRegistry != null) { logger.fine("Deserializing the TypeRegistry"); compilerState.typeRegistry.restoreContents(objectInputStream); logger.fine("Finished deserializing TypeRegistry"); } stopTracer(tracer, PassNames.DESERIALIZE_COMPILER_STATE); return compilerState; } }); featureSet = compilerState.featureSet; externs = compilerState.externs; scriptNodeByFilename.clear(); scriptNodeByFilename.putAll(compilerState.scriptNodeByFilename); inputsById.clear(); inputsById.putAll(compilerState.inputsById); typeRegistry = compilerState.typeRegistry; externAndJsRoot = compilerState.externAndJsRoot; externsRoot = compilerState.externsRoot; jsRoot = compilerState.jsRoot; typeCheckingHasRun = compilerState.typeCheckingHasRun; synthesizedExternsInput = compilerState.synthesizedExternsInput; synthesizedExternsInputAtEnd = compilerState.synthesizedExternsInputAtEnd; injectedLibraries.clear(); injectedLibraries.putAll(compilerState.injectedLibraries); lastInjectedLibrary = compilerState.lastInjectedLibrary; hasRegExpGlobalReferences = compilerState.hasRegExpGlobalReferences; typeValidator = compilerState.typeValidator; setLifeCycleStage(compilerState.lifeCycleStage); externProperties = compilerState.externProperties; moduleGraph = compilerState.moduleGraph; uniqueNameId = compilerState.uniqueNameId; uniqueIdSupplier = compilerState.uniqueIdSupplier; exportedNames.clear(); exportedNames.addAll(compilerState.exportedNames); cssNames = compilerState.cssNames; variableMap = compilerState.variableMap; propertyMap = compilerState.propertyMap; stringMap = compilerState.stringMap; anonymousFunctionNameMap = compilerState.anonymousFunctionaMap; idGeneratorMap = compilerState.idGeneratorMap; crossModuleIdGenerator = compilerState.crossModuleIdGenerator; defaultDefineValues = checkNotNull(compilerState.defaultDefineValues); annotationMap = checkNotNull(compilerState.annotationMap); inputSourceMaps = compilerState.inputSourceMaps; changeStamp = compilerState.changeStamp; // Reapply module names to deserialized modules renameModules(newModules, ImmutableList.copyOf(getModules())); // restore errors. if (compilerState.errors != null) { for (JSError error : compilerState.errors) { report(CheckLevel.ERROR, error); } } if (compilerState.warnings != null) { for (JSError warning : compilerState.warnings) { report(CheckLevel.WARNING, warning); } } if (tracker != null) { tracker.updateAfterDeserialize(jsRoot); } } /** Returns the module type for the provided namespace. */ @Override @Nullable CompilerInput.ModuleType getModuleTypeByName(String moduleName) { return moduleTypesByName.get(moduleName); } private ModuleMetadataMap moduleMetadataMap; @Override public ModuleMetadataMap getModuleMetadataMap() { return moduleMetadataMap; } @Override public void setModuleMetadataMap(ModuleMetadataMap moduleMetadataMap) { this.moduleMetadataMap = moduleMetadataMap; } private ModuleMap moduleMap; @Override public ModuleMap getModuleMap() { return moduleMap; } @Override public void setModuleMap(ModuleMap moduleMap) { this.moduleMap = moduleMap; } /** * Simplistic implementation of the java.nio.file.Path resolveSibling method that works with GWT. * * @param fromPath - must be a file (not directory) * @param toPath - must be a file (not directory) */ private static String resolveSibling(String fromPath, String toPath) { // If the destination is an absolute path, nothing to do. if (toPath.startsWith("/")) { return toPath; } List fromPathParts = new ArrayList<>(Arrays.asList(fromPath.split("/"))); List toPathParts = new ArrayList<>(Arrays.asList(toPath.split("/"))); if (!fromPathParts.isEmpty()) { fromPathParts.remove(fromPathParts.size() - 1); } while (!fromPathParts.isEmpty() && !toPathParts.isEmpty()) { if (toPathParts.get(0).equals(".")) { toPathParts.remove(0); } else if (toPathParts.get(0).equals("..")) { toPathParts.remove(0); fromPathParts.remove(fromPathParts.size() - 1); } else { break; } } fromPathParts.addAll(toPathParts); return String.join("/", fromPathParts); } public void resetAndIntitializeSourceMap() { if (sourceMap == null) { return; } sourceMap.reset(); if (options.sourceMapIncludeSourcesContent) { if (options.applyInputSourceMaps) { // Add any input source map content files to the source map as potential sources for (SourceMapInput inputSourceMap : inputSourceMaps.values()) { addSourceMapSourceFiles(inputSourceMap); } } // Add all the compilation sources to the source map as potential sources Iterable allModules = getModules(); if (allModules != null) { List sourceFiles = new ArrayList<>(); for (JSModule module : allModules) { for (CompilerInput input : module.getInputs()) { sourceFiles.add(input.getSourceFile()); } } addFilesToSourceMap(sourceFiles); } } } private static Node checkNotModule(Node script, String msg, Object... args) { checkArgument(script.isScript(), script); if (!script.hasOneChild()) { return script; } checkState(!script.getFirstChild().isModuleBody(), msg, args); return script; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy