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

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

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * 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.CoverageInstrumentationPass.CoverageReach;
import com.google.javascript.jscomp.CoverageInstrumentationPass.InstrumentOption;
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<>(); /** * 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); } 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()); instrumentForCoverageInternal(options.instrumentBranchCoverage); return null; }); } private void instrumentForCoverageInternal(boolean instrumentBranchCoverage) { Tracer tracer = newTracer("instrumentationPass"); InstrumentOption instrumentOption = InstrumentOption.LINE_ONLY; if (instrumentBranchCoverage) { instrumentOption = InstrumentOption.BRANCH_ONLY; } process(new CoverageInstrumentationPass(this, CoverageReach.ALL, instrumentOption)); 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.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; } @Override public JSTypeRegistry getTypeRegistry() { if (typeRegistry == null) { typeRegistry = new JSTypeRegistry(oldErrorReporter, forwardDeclaredTypes); } return typeRegistry; } @Override 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 // Only used by passes in the old type checker. 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 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(); } Node getExternsRoot() { return externsRoot; } @Override 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(); String 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); } } 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 helpers //------------------------------------------------------------------------ @Override 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; /** 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; } Map getCssNames() { return 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 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)); 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); } checkState(SYNTHETIC_CODE_INPUT_ID.equals(input.getInputId())); jsRoot.removeChild(astRoot); input.getModule().remove(input); inputsById.remove(input.getInputId()); reportChangeToChangeScope(astRoot); astRoot.setDeleted(true); 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 Node ensureLibraryInjected(String resourceName, boolean force) { boolean shouldInject = force || (!options.skipNonTranspilationPasses && !options.preventLibraryInjection); if (injectedLibraries.containsKey(resourceName) || !shouldInject) { return lastInjectedLibrary; } if (hasTypeCheckingRun()) { throw new RuntimeException("runtime library injected after type checking:" + resourceName); } // 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); } // If we've already started optimizations, then we need to normalize this. if (getLifeCycleStage().isNormalized()) { Normalize.normalizeSyntheticCode(this, ast, "jscomp_" + resourceName + "_"); } // 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