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 com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
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.debugging.sourcemap.proto.Mapping.OriginalMapping;
import com.google.javascript.jscomp.CompilerOptions.DevMode;
import com.google.javascript.jscomp.ReferenceCollectingCallback.ReferenceCollection;
import com.google.javascript.jscomp.TypeValidator.TypeMismatch;
import com.google.javascript.jscomp.deps.ModuleLoader;
import com.google.javascript.jscomp.deps.SortedDependencies.MissingProvideException;
import com.google.javascript.jscomp.parsing.Config;
import com.google.javascript.jscomp.parsing.ParserRunner;
import com.google.javascript.jscomp.parsing.parser.trees.Comment;
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.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TypeIRegistry;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.FileSystems;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
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;

/**
 * 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. * */ public class Compiler extends AbstractCompiler implements ErrorHandler, SourceFileMapping { static final String SINGLETON_MODULE_NAME = "$singleton$"; 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_ENTRY_ERROR", "unknown module \"{0}\" specified in entry point spec"); // Used in PerformanceTracker static final String PARSING_PASS_NAME = "parseInputs"; static final String CROSS_MODULE_CODE_MOTION_NAME = "crossModuleCodeMotion"; static final String CROSS_MODULE_METHOD_MOTION_NAME = "crossModuleMethodMotion"; 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 JS source modules private List modules; // The graph of the JS source modules. Must be null if there are less than // 2 modules, because we use this as a signal for which passes to run. private JSModuleGraph moduleGraph; // The module loader for resolving paths into module URIs. private ModuleLoader moduleLoader; // The JS source inputs private List inputs; // 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; /** @see #getLanguageMode() */ private CompilerOptions.LanguageMode languageMode = CompilerOptions.LanguageMode.ECMASCRIPT3; private Map inputsById; // Function to load source files from disk or memory. private Function originalSourcesLoader = new Function() { @Override public SourceFile apply(String filename) { return SourceFile.fromFile(filename); } }; // Original sources referenced by the source maps. private ConcurrentHashMap sourceMapOriginalSources = new ConcurrentHashMap<>(); /** Configured {@link SourceMapInput}s, plus any source maps discovered in source files. */ private final ConcurrentHashMap inputSourceMaps = new ConcurrentHashMap<>(); // Map from filenames to lists of all the comments in each file. private Map> commentsPerFile = new HashMap<>(); /** The source code map */ private SourceMap sourceMap; /** The externs created from the exports. */ private String externExports = null; /** * 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; /** The function information map */ private FunctionInformationMap functionInformationMap; /** Debugging information */ private final StringBuilder debugLog = new StringBuilder(); /** Detects Google-specific coding conventions. */ CodingConvention defaultCodingConvention = new ClosureCodingConvention(); private JSTypeRegistry typeRegistry; private Config parserConfig = null; private 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; // Used by optimize-returns, optimize-parameters and remove-unused-variables private DefinitionUseSiteFinder defFinder = null; // Types that have been forward declared private final Set forwardDeclaredTypes = new HashSet<>(); // For use by the new type inference private GlobalTypeInfo symbolTable; private MostRecentTypechecker mostRecentTypechecker = MostRecentTypechecker.NONE; // 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_OPTIMIZE_LOOP_ERROR", "Exceeded max number of code motion iterations: {0}"); private final CompilerExecutor compilerExecutor = new CompilerExecutor(); /** * 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 static final Joiner pathJoiner = Joiner.on(File.separator); /** * 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 stream) { addChangeHandler(recentChange); this.outStream = stream; } /** * 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) { Preconditions.checkNotNull( errorManager, "the error manager cannot be null"); this.errorManager = 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); } @VisibleForTesting void setOriginalSourcesLoader( Function originalSourcesLoader) { this.originalSourcesLoader = originalSourcesLoader; } /** * 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.languageMode = options.getLanguageIn(); if (errorManager == null) { if (this.outStream == null) { setErrorManager( new LoggerErrorManager(createMessageFormatter(), logger)); } else { PrintStreamErrorManager printer = new PrintStreamErrorManager(createMessageFormatter(), this.outStream); printer.setSummaryDetailLevel(options.summaryDetailLevel); setErrorManager(printer); } } 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.checkTypes) { options.setDisambiguateProperties(false); options.setAmbiguateProperties(false); options.setInlineProperties(false); options.setUseTypesForLocalOptimization(false); options.setUseTypesForOptimization(false); } if (options.legacyCodeCompile) { options.setDisambiguateProperties(false); options.setAmbiguateProperties(false); options.useNonStrictWarningsGuard(); } initWarningsGuard(options.getWarningsGuard()); } public void printConfig(PrintStream printStream) { printStream.println("==== CompilerOptions ===="); printStream.println(options.toString()); printStream.println("==== WarningsGuard ===="); printStream.println(warningsGuard.toString()); } void initWarningsGuard(WarningsGuard warningsGuard) { this.warningsGuard = new ComposeWarningsGuard( new SuppressDocWarningsGuard(getDiagnosticGroups().getRegisteredGroups()), warningsGuard); } /** * When the CompilerOptions and its WarningsGuard overlap, reconcile * any discrepencies. */ 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 enabled, then turn off the // parser type warnings. options.setWarningLevel( DiagnosticGroup.forType( RhinoErrorReporter.TYPE_PARSE_ERROR), CheckLevel.OFF); } // With NTI, we still need OTI to run because the later passes that use // types only understand OTI types at the moment. // But we do not want to see the warnings from OTI. if (options.getNewTypeInference()) { options.checkTypes = true; // Suppress warnings from the const checks of CheckAccessControls so as to avoid // duplication. options.setWarningLevel(DiagnosticGroups.ACCESS_CONTROLS_CONST, CheckLevel.OFF); if (!options.reportOTIErrorsUnderNTI) { options.setWarningLevel( DiagnosticGroups.OLD_CHECK_TYPES, CheckLevel.OFF); options.setWarningLevel( DiagnosticGroups.OLD_REPORT_UNKNOWN_TYPES, CheckLevel.OFF); options.setWarningLevel( FunctionTypeBuilder.ALL_DIAGNOSTICS, CheckLevel.OFF); } options.setWarningLevel( DiagnosticGroup.forType(RhinoErrorReporter.TYPE_PARSE_ERROR), CheckLevel.WARNING); } if (options.checkGlobalThisLevel.isOn() && !options.disables(DiagnosticGroups.GLOBAL_THIS)) { options.setWarningLevel( DiagnosticGroups.GLOBAL_THIS, options.checkGlobalThisLevel); } if (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); } } private boolean expectStrictModeInput() { switch (options.getLanguageIn()) { case ECMASCRIPT3: case ECMASCRIPT5: case ECMASCRIPT6: return false; case ECMASCRIPT5_STRICT: case ECMASCRIPT6_STRICT: case ECMASCRIPT6_TYPED: return true; default: return options.isStrictModeInput(); } } /** * Initializes the instance state needed for a compile job. */ public void init( List externs, List inputs, CompilerOptions options) { JSModule module = new JSModule(SINGLETON_MODULE_NAME); for (SourceFile input : inputs) { module.add(input); } List modules = new ArrayList<>(1); modules.add(module); initModules(externs, modules, options); addFilesToSourceMap(inputs); if (options.printConfig) { printConfig(System.err); } } /** * 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); fillEmptyModules(modules); this.externs = makeCompilerInput(externs, true); // Generate the module graph, and report any errors in the module // specification as errors. this.modules = modules; if (modules.size() > 1) { 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; } } else { this.moduleGraph = null; } this.inputs = getAllInputsFromModules(modules); initBasedOnOptions(); initInputsByIdMap(); initAST(); } /** * Do any initialization that is dependent on the compiler options. */ private 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); } } } private List makeCompilerInput( List files, boolean isExtern) { List inputs = new ArrayList<>(files.size()); for (T file : files) { inputs.add(new CompilerInput(file, isExtern)); } 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())); } } /** * Empty modules get an empty "fill" file, so that we can move code into * an empty module. */ static String createFillFileName(String moduleName) { return moduleName + "$fillFile"; } /** * 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 static void fillEmptyModules(List modules) { for (JSModule module : modules) { if (module.getInputs().isEmpty()) { module.add(SourceFile.fromCode( createFillFileName(module.getName()), "")); } } } /** * Rebuilds the internal list of inputs by iterating over all modules. * This is necessary if inputs have been added to or removed from a module * after the {@link #init(List, List, CompilerOptions)} call. */ public void rebuildInputsFromModules() { inputs = getAllInputsFromModules(modules); initInputsByIdMap(); } /** * Builds a single list of all module inputs. Verifies that it contains no * duplicates. */ private static List getAllInputsFromModules( List modules) { List inputs = new ArrayList<>(); Map inputMap = new HashMap<>(); for (JSModule module : modules) { for (CompilerInput input : module.getInputs()) { String inputName = input.getName(); // NOTE(nicksantos): If an input is in more than one module, // it will show up twice in the inputs list, and then we // will get an error down the line. inputs.add(input); inputMap.put(inputName, module); } } return inputs; } 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}"); /** * Returns the relative path, resolved relative to the base path, where the * base path is interpreted as a filename rather than a directory. E.g.: * getRelativeTo("../foo/bar.js", "baz/bam/qux.js") --> "baz/foo/bar.js" */ private static String getRelativeTo(String relative, String base) { return FileSystems.getDefault().getPath(base) .resolveSibling(relative) .normalize() .toString() .replace(File.separator, "/"); } /** * Creates a map to make looking up an input by name fast. Also checks for * duplicate inputs. */ void initInputsByIdMap() { inputsById = new HashMap<>(); 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 : inputs) { 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); } public Result compile( SourceFile extern, SourceFile input, CompilerOptions options) { return compile(ImmutableList.of(extern), ImmutableList.of(input), options); } /** * Compiles a list of inputs. */ public Result compile( List externs, List inputs, CompilerOptions options) { // The compile method should only be called once. Preconditions.checkState(jsRoot == null); try { init(externs, inputs, options); if (hasErrors()) { return getResult(); } return compile(); } finally { Tracer t = newTracer("generateReport"); errorManager.generateReport(); stopTracer(t, "generateReport"); } } /** * Compiles a list of modules. */ public Result compileModules(List externs, List modules, CompilerOptions options) { // The compile method should only be called once. Preconditions.checkState(jsRoot == null); try { initModules(externs, modules, options); if (hasErrors()) { return getResult(); } return compile(); } finally { Tracer t = newTracer("generateReport"); errorManager.generateReport(); stopTracer(t, "generateReport"); } } private Result compile() { return runInCompilerThread(new Callable() { @Override public Result call() throws Exception { compileInternal(); return getResult(); } }); } /** * 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 compileInternal() { setProgress(0.0, null); CompilerOptionsPreprocessor.preprocess(options); parse(); // 15 percent of the work is assumed to be for parsing (based on some // minimal analysis on big JS projects, of course this depends on options) setProgress(0.15, "parse"); if (hasErrors()) { return; } if (!precheck()) { return; } if (options.skipNonTranspilationPasses) { // i.e. whitespace-only mode, which will not work with goog.module without: whitespaceOnlyPasses(); if (options.lowerFromEs6()) { transpileAndDontCheck(); } } else { check(); // check() also includes transpilation if (hasErrors()) { return; } if (!options.checksOnly && !options.shouldGenerateTypedExterns()) { optimize(); } } if (options.recordFunctionInformation) { recordFunctionInformation(); } if (options.devMode == DevMode.START_AND_END) { runSanityCheck(); } setProgress(1.0, "recordFunctionInformation"); if (tracker != null) { tracker.outputTracerReport(); } } public void parse() { parseInputs(); } PassConfig getPassConfig() { if (passes == null) { passes = createPassConfigInternal(); } return passes; } /** * Create the passes object. Clients should use setPassConfig instead of * overriding this. */ 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. Preconditions.checkNotNull(passes); Preconditions.checkState(this.passes == null, "setPassConfig was already called"); this.passes = passes; } /** * Carry out any special checks or procedures that need to be done before * proceeding with rest of the compilation process. * * @return true, to continue with compilation */ boolean precheck() { return true; } 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()) { pf.create(this).process(externsRoot, jsRoot); } } finally { stopTracer(t, "runTranspileOnlyPasses"); } } public 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 = new PhaseOptimizer(this, tracker, new PhaseOptimizer.ProgressRange(getProgress(), 1.0)); if (options.devMode == DevMode.EVERY_PASS) { phaseOptimizer.setSanityCheck(sanityCheck); } if (options.getCheckDeterminism()) { phaseOptimizer.setPrintAstHashcodes(true); } phaseOptimizer.consume(getPassConfig().getChecks()); phaseOptimizer.process(externsRoot, jsRoot); if (hasErrors()) { return; } if (options.getTweakProcessing().shouldStrip() || !options.stripTypes.isEmpty() || !options.stripNameSuffixes.isEmpty() || !options.stripTypePrefixes.isEmpty() || !options.stripNamePrefixes.isEmpty()) { stripCode(options.stripTypes, options.stripNameSuffixes, options.stripTypePrefixes, options.stripNamePrefixes); } runCustomPasses(CustomPassExecutionTime.BEFORE_OPTIMIZATIONS); phaseOptimizer = null; } private void externExports() { logger.fine("Creating extern file for exports"); startPass("externExports"); ExternExportsPass pass = new ExternExportsPass(this); process(pass); externExports = pass.getGeneratedExterns(); endPass(); } @Override void process(CompilerPass p) { p.process(externsRoot, jsRoot); } private final PassFactory sanityCheck = new PassFactory("sanityCheck", false) { @Override protected CompilerPass create(AbstractCompiler compiler) { return new SanityCheck(compiler); } }; private void maybeSanityCheck() { if (options.devMode == DevMode.EVERY_PASS) { runSanityCheck(); } } private void runSanityCheck() { sanityCheck.create(this).process(externsRoot, jsRoot); } /** * Strips code for smaller compiled code. This is useful for removing debug * statements to prevent leaking them publicly. */ void stripCode(Set stripTypes, Set stripNameSuffixes, Set stripTypePrefixes, Set stripNamePrefixes) { logger.fine("Strip code"); startPass("stripCode"); StripCode r = new StripCode(this, stripTypes, stripNameSuffixes, stripTypePrefixes, stripNamePrefixes); if (options.getTweakProcessing().shouldStrip()) { r.enableTweakStripping(); } process(r); endPass(); } /** * 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"); } } } private Tracer currentTracer = null; private String currentPassName = null; /** * Marks the beginning of a pass. */ void startPass(String passName) { Preconditions.checkState(currentTracer == null); currentPassName = passName; currentTracer = newTracer(passName); } /** * Marks the end of a pass. */ void endPass() { Preconditions.checkState(currentTracer != null, "Tracer should not be null at the end of a pass."); stopTracer(currentTracer, currentPassName); currentPassName = null; currentTracer = null; maybeSanityCheck(); } @Override final void beforePass(String passName) { // does nothing for now } @Override final void afterPass(String passName) { if (options.printSourceAfterEachPass) { String currentJsSource = getCurrentJsSource(); if (!currentJsSource.equals(this.lastJsSource)) { System.out.println(); System.out.println("// " + passName + " yields:"); System.out.println("// ************************************"); System.out.println(currentJsSource); lastJsSource = currentJsSource; } } } final String getCurrentJsSource() { List filenames = options.filesToPrintAfterEachPass; if (filenames.isEmpty()) { return toSource(); } else { StringBuilder builder = new StringBuilder(); for (String filename : filenames) { Node script = getScriptNode(filename); String source = script != null ? "// " + script.getSourceFileName() + "\n" + toSource(script) : "File '" + filename + "' not found"; builder.append(source); } return builder.toString(); } } final Node getScriptNode(String filename) { for (Node file : jsRoot.children()) { if (file.getSourceFileName() != null && file.getSourceFileName().endsWith(filename)) { return file; } } return null; } /** * 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() { PassConfig.State state = getPassConfig().getIntermediateState(); 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(), debugLog.toString(), state.variableMap, state.propertyMap, state.anonymousFunctionNameMap, state.stringMap, functionInformationMap, sourceMap, externExports, state.cssNames, state.idGeneratorMap, transpiledFiles); } /** * Returns the array of errors (never null). */ public JSError[] getErrors() { if (errorManager == null) { return new JSError[] {}; } return errorManager.getErrors(); } /** * Returns the array of warnings (never null). */ public JSError[] getWarnings() { if (errorManager == null) { return new JSError[] {}; } return errorManager.getWarnings(); } @Override public Node getRoot() { return externAndJsRoot; } @Override CompilerOptions.LanguageMode getLanguageMode() { return languageMode; } @Override void setLanguageMode(CompilerOptions.LanguageMode mode) { languageMode = mode; } /** * Creates a new id for making unique names. */ private int nextUniqueNameId() { return uniqueNameId++; } /** * Resets the unique name id counter */ @VisibleForTesting void resetUniqueNameId() { uniqueNameId = 0; } @Override Supplier getUniqueNameIdSupplier() { final Compiler self = this; return new Supplier() { @Override public String get() { return String.valueOf(self.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) { 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; } Preconditions.checkState(input.isExtern(), "Not an extern input: %s", input.getName()); inputsById.remove(id); externs.remove(input); Node root = 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); putCompilerInput(input.getInputId(), input); if (pos == SyntheticExternsPosition.START) { externsRoot.addChildToFront(ast.getAstRoot(this)); externs.add(0, input); } else { externsRoot.addChildToBack(ast.getAstRoot(this)); externs.add(input); } return input; } CompilerInput putCompilerInput(InputId id, CompilerInput input) { if (inputsById == null) { inputsById = new HashMap<>(); } 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()); Preconditions.checkNotNull(oldInput, "No input to replace: %s", ast.getInputId().getIdName()); Node newRoot = ast.getAstRoot(this); if (newRoot == null) { return false; } Node oldRoot = oldInput.getAstRoot(this); if (oldRoot != null) { oldRoot.replaceWith(newRoot); } else { getRoot().getLastChild().addChildToBack(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. Preconditions.checkState( newInput.getInputId().equals(oldInput.getInputId())); InputId inputIdOnAst = newInput.getAstRoot(this).getInputId(); Preconditions.checkState(newInput.getInputId().equals(inputIdOnAst)); inputs.remove(oldInput); 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 = ast.getAstRoot(this); if (newRoot == null) { return false; } getRoot().getLastChild().addChildToBack(newRoot); CompilerInput newInput = new CompilerInput(ast); // TODO(tylerg): handle this for multiple modules at some point. if (moduleGraph == null && !modules.isEmpty()) { // singleton module modules.get(0).add(newInput); } putCompilerInput(ast.getInputId(), newInput); return true; } @Override JSModuleGraph getModuleGraph() { return moduleGraph; } /** * Gets a module graph. This will always return a module graph, even * in the degenerate case when there's only one module. */ JSModuleGraph getDegenerateModuleGraph() { return moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph; } @Override public TypeIRegistry getTypeIRegistry() { switch (mostRecentTypechecker) { case NONE: // Even in compiles where typechecking is not enabled, some passes ask for the // type registry, eg, GatherExternProperties does. Also, in CheckAccessControls, // the constructor asks for a type registry, and this may happen before type checking // runs. So, in the NONE case, if NTI is enabled, return a new registry, since NTI is // the relevant type checker. If NTI is not enabled, return an old registry. return options.getNewTypeInference() ? getSymbolTable() : getTypeRegistry(); case OTI: return getTypeRegistry(); case NTI: return getSymbolTable(); default: throw new RuntimeException("Unhandled typechecker " + mostRecentTypechecker); } } @Override public JSTypeRegistry getTypeRegistry() { if (typeRegistry == null) { typeRegistry = new JSTypeRegistry(oldErrorReporter, forwardDeclaredTypes); } return typeRegistry; } @Override void forwardDeclareType(String typeName) { if (options.allowUnfulfilledForwardDeclarations()) { forwardDeclaredTypes.add(typeName); } } @Override void setMostRecentTypechecker(MostRecentTypechecker lastRun) { this.mostRecentTypechecker = lastRun; } @Override // Only used by jsdev public MemoizedScopeCreator getTypedScopeCreator() { return getPassConfig().getTypedScopeCreator(); } @SuppressWarnings("unchecked") DefaultPassConfig ensureDefaultPassConfig() { PassConfig passes = getPassConfig().getBasePassConfig(); Preconditions.checkState(passes instanceof DefaultPassConfig, "PassConfigs must eventually delegate to the DefaultPassConfig"); return (DefaultPassConfig) passes; } public SymbolTable buildKnownSymbolTable() { SymbolTable symbolTable = new SymbolTable(this, getTypeRegistry()); MemoizedScopeCreator 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, SyntacticScopeCreator.makeUntyped(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.fillJSDocInfo(externsRoot, jsRoot); symbolTable.fillSymbolVisibility(externsRoot, jsRoot); 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 Iterable getTypeMismatches() { return getTypeValidator().getMismatches(); } @Override Iterable getImplicitInterfaceUses() { return getTypeValidator().getImplicitInterfaceUses(); } @Override GlobalTypeInfo getSymbolTable() { if (this.symbolTable == null) { this.symbolTable = new GlobalTypeInfo(this, forwardDeclaredTypes); } return this.symbolTable; } @Override DefinitionUseSiteFinder getDefinitionFinder() { return this.defFinder; } @Override void setDefinitionFinder(DefinitionUseSiteFinder defFinder) { this.defFinder = defFinder; } //------------------------------------------------------------------------ // 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(); if (options.getTracerMode().isOn()) { tracker = new PerformanceTracker(externsRoot, jsRoot, options.getTracerMode(), this.outStream); addChangeHandler(tracker.getCodeChangeHandler()); } Tracer tracer = newTracer(PARSING_PASS_NAME); beforePass(PARSING_PASS_NAME); try { // Parse externs sources. for (CompilerInput input : externs) { Node n = input.getAstRoot(this); if (hasErrors()) { return null; } externsRoot.addChildToBack(n); } if (options.lowerFromEs6() || options.transformAMDToCJSModules || options.processCommonJSModules) { this.moduleLoader = new ModuleLoader(this, options.moduleRoots, inputs); if (options.processCommonJSModules) { this.moduleLoader.setPackageJsonMainEntries(processJsonInputs(inputs)); } if (options.lowerFromEs6()) { processEs6Modules(); } // Modules inferred in ProcessCommonJS pass. if (options.transformAMDToCJSModules || options.processCommonJSModules) { processAMDAndCommonJSModules(); } // 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 : inputs) { if (input.getKnownProvides().isEmpty()) { ModuleIdentifier modInfo = ModuleIdentifier.forFile(input.getSourceFile().getOriginalPath()); inputModuleIdentifiers.put(modInfo.getClosureNamespace(), 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 : inputs) { for (String require : input.getKnownRequires()) { if (inputModuleIdentifiers.containsKey(require) && !inputsToRewrite.containsKey(require)) { inputsToRewrite.put(require, inputModuleIdentifiers.get(require)); } } } if (!inputsToRewrite.isEmpty()) { processEs6Modules(new ArrayList<>(inputsToRewrite.values()), true); } } else { // Use an empty module loader if we're not actually dealing with modules. this.moduleLoader = ModuleLoader.EMPTY; } orderInputs(); // If in IDE mode, we ignore the error and keep going. if (hasErrors()) { return null; } // Build the AST. for (CompilerInput input : inputs) { Node n = input.getAstRoot(this); if (n == null) { continue; } if (devMode) { runSanityCheck(); 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.traverseEs6(this, n, sia); } jsRoot.addChildToBack(n); } if (hasErrors()) { return null; } return externAndJsRoot; } finally { afterPass(PARSING_PASS_NAME); stopTracer(tracer, PARSING_PASS_NAME); } } void orderInputsWithLargeStack() { runInCompilerThread(new Callable() { @Override public Void call() throws Exception { Tracer tracer = newTracer("orderInputsWithLargeStack"); try { orderInputs(); } finally { stopTracer(tracer, "orderInputsWithLargeStack"); } return null; } }); } void orderInputs() { hoistUnorderedExterns(); // Check if the sources need to be re-ordered. boolean staleInputs = false; if (options.dependencyOptions.needsManagement()) { for (CompilerInput input : inputs) { // 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 { inputs = getDegenerateModuleGraph().manageDependencies(options.dependencyOptions, inputs); 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())); } } if (options.dependencyOptions.needsManagement() && options.allowGoogProvideInExterns()) { hoistAllExterns(); } hoistNoCompileFiles(); if (staleInputs) { repartitionInputs(); } } /** * Hoists inputs with the @externs annotation and no provides or requires into the externs list. */ void hoistUnorderedExterns() { boolean staleInputs = false; for (CompilerInput input : inputs) { if (options.dependencyOptions.needsManagement()) { // If we're doing scanning dependency info anyway, use that // information to skip sources that obviously aren't externs. if (!input.getProvides().isEmpty() || !input.getRequires().isEmpty()) { continue; } } if (hoistIfExtern(input)) { staleInputs = true; } } if (staleInputs) { repartitionInputs(); } } /** * Hoists inputs with the @externs annotation into the externs list. */ void hoistAllExterns() { boolean staleInputs = false; for (CompilerInput input : inputs) { 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) { Node n = input.getAstRoot(this); // Inputs can have a null AST on a parse error. if (n == null) { return false; } JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isExterns()) { // If the input file is explicitly marked as an externs file, then // assume the programmer made a mistake and throw it into // the externs pile anyways. externsRoot.addChildToBack(n); input.setIsExtern(true); input.getModule().remove(input); externs.add(input); return true; } return false; } /** * Hoists inputs with the @nocompile annotation out of the inputs. */ void hoistNoCompileFiles() { boolean staleInputs = false; for (CompilerInput input : inputs) { Node n = input.getAstRoot(this); // Inputs can have a null AST on a parse error. if (n == null) { continue; } JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isNoCompile()) { input.getModule().remove(input); staleInputs = true; } } if (staleInputs) { repartitionInputs(); } } private void repartitionInputs() { fillEmptyModules(modules); 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(List 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 = input.getAstRoot(this); if (root == null) { continue; } rewriteJson.process(null, root); } return rewriteJson.getPackageJsonMainEntries(); } void processEs6Modules() { processEs6Modules(inputs, false); } void processEs6Modules(List inputsToProcess, boolean forceRewrite) { for (CompilerInput input : inputsToProcess) { input.setCompiler(this); Node root = input.getAstRoot(this); if (root == null) { continue; } new ProcessEs6Modules(this).processFile(root, forceRewrite); } } /** * Transforms AMD and CJS modules to something closure compiler can * process and creates JSModules and the corresponding dependency tree * on the way. */ void processAMDAndCommonJSModules() { for (CompilerInput input : inputs) { input.setCompiler(this); Node root = input.getAstRoot(this); if (root == null) { continue; } if (options.transformAMDToCJSModules) { new TransformAMDToCJSModule(this).process(null, root); } if (options.processCommonJSModules) { ProcessCommonJSModules cjs = new ProcessCommonJSModules(this, true); cjs.process(null, root); } } } public Node parse(SourceFile file) { initCompilerOptionsIfTesting(); addToDebugLog("Parsing: " + file.getName()); return new JsAst(file).getAstRoot(this); } private int syntheticCodeId = 0; @Override Node parseSyntheticCode(String js) { SourceFile source = SourceFile.fromCode(" [synthetic:" + (++syntheticCodeId) + "] ", js); addFilesToSourceMap(ImmutableList.of(source)); CompilerInput input = new CompilerInput(source); putCompilerInput(input.getInputId(), input); return input.getAstRoot(this); } /** * 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()); } } @Override Node parseSyntheticCode(String fileName, String js) { initCompilerOptionsIfTesting(); addFileToSourceMap(fileName, js); CompilerInput input = new CompilerInput(SourceFile.fromCode(fileName, js)); putCompilerInput(input.getInputId(), input); return input.getAstRoot(this); } @Override Node parseTestCode(String js) { initCompilerOptionsIfTesting(); initBasedOnOptions(); CompilerInput input = new CompilerInput( SourceFile.fromCode("[testcode]", js)); if (inputsById == null) { inputsById = new HashMap<>(); } putCompilerInput(input.getInputId(), input); return input.getAstRoot(this); } @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(new Callable() { @Override public String call() throws Exception { Tracer tracer = newTracer("toSource"); try { CodeBuilder cb = new CodeBuilder(); if (jsRoot != null) { int i = 0; 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 each input back to JS code. */ public String[] toSourceArray() { return runInCompilerThread(new Callable() { @Override public String[] call() throws Exception { Tracer tracer = newTracer("toSourceArray"); try { int numInputs = inputs.size(); String[] sources = new String[numInputs]; CodeBuilder cb = new CodeBuilder(); for (int i = 0; i < numInputs; i++) { Node scriptNode = inputs.get(i).getAstRoot(Compiler.this); cb.reset(); toSource(cb, i, scriptNode); sources[i] = cb.toString(); } return sources; } finally { stopTracer(tracer, "toSourceArray"); } } }); } /** * Converts the parse tree for a module back to JS code. */ public String toSource(final JSModule module) { return runInCompilerThread(new Callable() { @Override public String call() throws Exception { 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(); } }); } /** * Converts the parse tree for each input in a module back to JS code. */ public String[] toSourceArray(final JSModule module) { return runInCompilerThread(new Callable() { @Override public String[] call() throws Exception { 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; } }); } /** * 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(new Callable() { @Override public Void call() throws Exception { if (options.printInputDelimiter) { if ((cb.getLength() > 0) && !cb.endsWith("\n")) { cb.append("\n"); // Make sure that the label starts on a new line } Preconditions.checkState(root.isScript()); String delimiter = options.inputDelimiter; String inputName = root.getInputId().getIdName(); String sourceName = root.getSourceFileName(); Preconditions.checkState(sourceName != null); Preconditions.checkState(!sourceName.isEmpty()); delimiter = delimiter .replaceAll("%name%", Matcher.quoteReplacement(inputName)) .replaceAll("%num%", String.valueOf(inputSeqNum)); 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(this.typeRegistry); builder.setCompilerOptions(options); builder.setSourceMap(sourceMap); builder.setTagAsExterns(firstOutput && options.shouldGenerateTypedExterns()); builder.setTagAsStrict(firstOutput && shouldEmitUseStrict()); return builder.build(); } private boolean shouldEmitUseStrict() { switch (options.getLanguageOut()) { case ECMASCRIPT3: case ECMASCRIPT5: case ECMASCRIPT6: return false; default: return options.isEmitUseStrict(); } } /** * 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 //------------------------------------------------------------------------ public void optimize() { List optimizations = getPassConfig().getOptimizations(); if (optimizations.isEmpty()) { return; } // Ideally, this pass should be the first pass run, however: // 1) VariableReferenceCheck reports unexpected warnings if Normalize // is done first. // 2) ReplaceMessages, stripCode, and potentially custom passes rely on // unmodified local names. normalize(); // Create extern exports after the normalize because externExports depends on unique names. if (options.isExternExportsEnabled() || options.externExportsPath != null) { externExports(); } phaseOptimizer = new PhaseOptimizer(this, tracker, null); if (options.devMode == DevMode.EVERY_PASS) { phaseOptimizer.setSanityCheck(sanityCheck); } if (options.getCheckDeterminism()) { phaseOptimizer.setPrintAstHashcodes(true); } 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(); } public void normalize() { logger.fine("Normalizing"); startPass("normalize"); process(new Normalize(this, false)); endPass(); } @Override void prepareAst(Node root) { CompilerPass pass = new PrepareAst(this); pass.process(null, root); } void recordFunctionInformation() { logger.fine("Recording function information"); startPass("recordFunctionInformation"); RecordFunctionInformation recordFunctionInfoPass = new RecordFunctionInformation( this, getPassConfig().getIntermediateState().functionNames); process(recordFunctionInfoPass); functionInformationMap = recordFunctionInfoPass.getMap(); endPass(); } protected final RecentChange recentChange = new RecentChange(); private final List codeChangeHandlers = new ArrayList<>(); /** 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}"; private CompilerInput synthesizedExternsInput = null; private CompilerInput synthesizedExternsInputAtEnd = null; private ImmutableMap defaultDefineValues = ImmutableMap.of(); @Override void addChangeHandler(CodeChangeHandler handler) { codeChangeHandlers.add(handler); } @Override void removeChangeHandler(CodeChangeHandler handler) { codeChangeHandlers.remove(handler); } @Override void setScope(Node n) { if (phaseOptimizer != null) { phaseOptimizer.setScope(n); } } @Override Node getJsRoot() { return jsRoot; } @Override boolean hasScopeChanged(Node n) { if (phaseOptimizer == null) { return true; } return phaseOptimizer.hasScopeChanged(n); } @Override void reportChangeToEnclosingScope(Node n) { if (phaseOptimizer != null) { phaseOptimizer.reportChangeToEnclosingScope(n); phaseOptimizer.startCrossScopeReporting(); reportCodeChange(); phaseOptimizer.endCrossScopeReporting(); } else { reportCodeChange(); } } /** * 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 void reportCodeChange() { 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 ECMASCRIPT6: case ECMASCRIPT6_STRICT: case ECMASCRIPT_2015: return Config.LanguageMode.ECMASCRIPT6; case ECMASCRIPT6_TYPED: return Config.LanguageMode.TYPESCRIPT; case ECMASCRIPT7: case ECMASCRIPT_2016: return Config.LanguageMode.ECMASCRIPT7; case ECMASCRIPT8: case ECMASCRIPT_NEXT: return Config.LanguageMode.ECMASCRIPT8; default: throw new IllegalStateException("unexpected language mode: " + options.getLanguageIn()); } } @Override Config getParserConfig(ConfigContext context) { if (parserConfig == null) { Config.LanguageMode configLanguageMode = getParserConfigLanguageMode(options.getLanguageIn()); Config.StrictMode strictMode = 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. */ protected 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) { Preconditions.checkNotNull(options); return warningsGuard.level(error); } /** * Report an internal error. */ @Override void throwInternalError(String message, Exception cause) { String finalMessage = "INTERNAL COMPILER ERROR.\n" + "Please 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() && getErrorCount() > 0; } /** * 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(); } /** Called from the compiler passes, adds debug info */ @Override void addToDebugLog(String str) { if (options.useDebugLog) { debugLog.append(str); debugLog.append('\n'); logger.fine(str); } } @Override public SourceFile getSourceFileByName(String sourceName) { // Here we assume that the source name is the input name, this // is try 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; } @Override public void addInputSourceMap(String sourceFileName, SourceMapInput inputSourceMap) { inputSourceMaps.put(sourceFileName, inputSourceMap); } @Override 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. // SourceMap uses 1-indexing for both. OriginalMapping result = sourceMap.getSourceMap() .getMappingForLine(lineNumber, columnNumber + 1); if (result == null) { return null; } // The sourcemap will return a path relative to the sourcemap's file. // Translate it to one relative to our base directory. String path = getRelativeTo(result.getOriginalFile(), sourceMap.getOriginalPath()); sourceMapOriginalSources.putIfAbsent( path, originalSourcesLoader.apply(path)); return result.toBuilder() .setOriginalFile(path) .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 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(JSModule module) { if (module == null) { if (inputs.isEmpty()) { throw new IllegalStateException("No inputs"); } return inputs.get(0).getAstRoot(this); } List moduleInputs = module.getInputs(); if (!moduleInputs.isEmpty()) { return moduleInputs.get(0).getAstRoot(this); } throw new IllegalStateException("Root module has no inputs"); } public SourceMap getSourceMap() { return sourceMap; } VariableMap getVariableMap() { return getPassConfig().getIntermediateState().variableMap; } VariableMap getPropertyMap() { return getPassConfig().getIntermediateState().propertyMap; } VariableMap getStringMap() { return getPassConfig().getIntermediateState().stringMap; } @Override CompilerOptions getOptions() { return options; } FunctionInformationMap getFunctionalInformationMap() { return functionInformationMap; } /** * 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(newCompilerOptions()); } return errorManager; } @Override List getInputsInOrder() { return Collections.unmodifiableList(inputs); } /** * 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 inputs; } @VisibleForTesting List getExternsForTesting() { return externs; } @Override boolean hasRegExpGlobalReferences() { return hasRegExpGlobalReferences; } @Override void setHasRegExpGlobalReferences(boolean references) { hasRegExpGlobalReferences = references; } @Override void updateGlobalVarReferences(Map refMapPatch, Node collectionRoot) { Preconditions.checkState(collectionRoot.isScript() || collectionRoot.isBlock()); 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 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; } /** * 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 = 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) { languageMode = options.getLanguageIn(); Node js = ast.getAstRoot(this); Preconditions.checkNotNull(js); 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); this.getTypeRegistry().clearNamedTypes(); 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) { HotSwapCompilerPass pass = passFactory.getHotSwapPass(this); if (pass != null) { 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 doNotInject = !force && (options.skipNonTranspilationPasses || options.preventLibraryInjection); if (injectedLibraries.containsKey(resourceName) || doNotInject) { return lastInjectedLibrary; } // Load/parse the code. String originalCode = ResourceLoader.loadTextResource( Compiler.class, "js/" + resourceName + ".js"); Node ast = parseSyntheticCode(" [synthetic:" + resourceName + "] ", originalCode); // Look for string literals of the form 'require foo bar' or 'externs baz' or 'normalize'. // 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; case "declare": // 'declare name'; adds the name to the externs (with no type information). // Note that we could simply add the entire externs library, but that leads to // potentially-surprising behavior when the externs that are present depend on // whether or not a polyfill is used. Node var = IR.var(IR.name(words.get(1))); JSDocInfoBuilder jsdoc = new JSDocInfoBuilder(false); // Suppress duplicate-var warning in case this name is already defined in the externs. jsdoc.addSuppression("duplicate"); var.setJSDocInfo(jsdoc.build()); getSynthesizedExternsInputAtEnd() .getAstRoot(this) .addChildToBack(var); 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(); 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); reportCodeChange(); 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 ModuleLoader getModuleLoader() { return moduleLoader; } private void addFilesToSourceMap(Iterable files) { if (getOptions().sourceMapIncludeSourcesContent && getSourceMap() != null) { for (SourceFile file : files) { getSourceMap().addSourceFile(file); } } } private void addFileToSourceMap(String filename, String contents) { if (getOptions().sourceMapIncludeSourcesContent && getSourceMap() != null) { getSourceMap().addSourceFile(SourceFile.fromCode(filename, contents)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy