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

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

There is a newer version: 9.0.8
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.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES8_MODULES;

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.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.WarningsGuard.DiagnosticGroupState;
import com.google.javascript.jscomp.deps.JsFileParser;
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.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.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.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
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 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 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_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 JS source modules private List modules; private JSModuleGraph moduleGraph; // The module loader for resolving paths into module URIs. private ModuleLoader moduleLoader; // The JS source inputs private List inputs; // 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 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; /** * 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; /** 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<>(); // Type registry used by new type inference. private GlobalTypeInfo globalTypeInfo; 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_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 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 { PrintStreamErrorManager printer = new PrintStreamErrorManager(createMessageFormatter(), this.outStream); printer.setSummaryDetailLevel(options.summaryDetailLevel); setErrorManager(printer); } } 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.legacyCodeCompile) { options.setDisambiguateProperties(false); options.setAmbiguateProperties(false); options.useNonStrictWarningsGuard(); } 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 ===="); printStream.println(inputs); printStream.println("==== CompilerOptions ===="); printStream.println(options); printStream.println("==== WarningsGuard ===="); printStream.println(warningsGuard); } void initWarningsGuard(WarningsGuard warningsGuard) { this.warningsGuard = new ComposeWarningsGuard( new SuppressDocWarningsGuard(this, getDiagnosticGroups().getRegisteredGroups()), warningsGuard); } /** 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 enabled, then turn off the // parser type warnings. options.setWarningLevel( DiagnosticGroup.forType( RhinoErrorReporter.TYPE_PARSE_ERROR), CheckLevel.OFF); } DiagnosticGroupState ntiState = options.getWarningsGuard().enablesExplicitly(DiagnosticGroups.NEW_CHECK_TYPES); if (ntiState == DiagnosticGroupState.ON) { options.setNewTypeInference(true); } else if (ntiState == DiagnosticGroupState.OFF) { options.setNewTypeInference(false); } if (options.getNewTypeInference()) { // Suppress the const checks of CheckAccessControls; NTI performs these checks better. options.setWarningLevel(DiagnosticGroups.ACCESS_CONTROLS_CONST, CheckLevel.OFF); } // When running OTI after NTI, turn off the warnings from OTI. if (options.getNewTypeInference() && options.getRunOTIafterNTI()) { options.checkTypes = true; 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 (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); } } /** Initializes the instance state needed for a compile job. */ public final void init( List externs, List sources, CompilerOptions options) { JSModule module = new JSModule(SINGLETON_MODULE_NAME); for (SourceFile source : sources) { if (this.getPersistentInputStore() != null) { module.add(this.getPersistentInputStore().getCachedCompilerInput(source)); } else { 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); fillEmptyModules(modules); this.externs = makeExternInputs(externs); // Generate the module graph, and report any errors in the module // specification as errors. this.modules = modules; 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; } this.inputs = getAllInputsFromModules(modules); this.commentsPerFile = new ConcurrentHashMap<>(inputs.size()); initBasedOnOptions(); initInputsByIdMap(); initAST(); } /** * Exists only for some tests that want to reuse JSModules. * @deprecated Fix those tests. */ @Deprecated public void breakThisCompilerSoItsModulesCanBeReused() { moduleGraph.breakThisGraphSoItsModulesCanBeReused(); moduleGraph = null; } /** * 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); } } } 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())); } } /** * 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<>(); 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); } } 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}"); /** * 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 : 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); } /** 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( inputs != null && !inputs.isEmpty(), "No inputs. Did you call init() or initModules()?"); checkState(!hasErrors()); checkState(!options.getInstrumentForCoverageOnly()); runInCompilerThread( new Callable() { @Override public Void call() throws Exception { 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( inputs != null && !inputs.isEmpty(), "No inputs. Did you call init() or initModules()?"); checkState(!hasErrors()); checkState(!options.getInstrumentForCoverageOnly()); runInCompilerThread( new Callable() { @Override public Void call() throws Exception { 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(FeatureSet.ES6)) { 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(new Callable() { @Override public Void call() throws Exception { performPostCompilationTasksInternal(); return null; } }); } /** * Performs all the bookkeeping required at the end of a compilation. */ private void performPostCompilationTasksInternal() { if (options.recordFunctionInformation) { recordFunctionInformation(); } if (options.devMode == DevMode.START_AND_END) { runValidityCheck(); } setProgress(1.0, "recordFunctionInformation"); if (tracker != null) { tracker.outputTracerReport(); } } /** * 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( inputs != null && !inputs.isEmpty(), "No inputs. Did you call init() or initModules()?"); checkState(!hasErrors()); runInCompilerThread( new Callable() { @Override public Void call() throws Exception { 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( new Callable() { @Override public Void call() throws Exception { 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. */ 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 = new PassFactory("validityCheck", false) { @Override protected CompilerPass create(AbstractCompiler compiler) { return new ValidityCheck(compiler); } @Override protected FeatureSet featureSet() { return ES8_MODULES; } }; private void maybeRunValidityCheck() { if (options.devMode == DevMode.EVERY_PASS) { runValidityCheck(); } } 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"); } } } private Tracer currentTracer = null; private String currentPassName = null; /** * Marks the beginning of a pass. */ void startPass(String passName) { checkState(currentTracer == null); currentPassName = passName; currentTracer = newTracer(passName); beforePass(passName); } /** * Marks the end of a pass. */ void endPass(String passName) { checkState(currentTracer != null, "Tracer should not be null at the end of a pass."); stopTracer(currentTracer, currentPassName); afterPass(passName); currentPassName = null; currentTracer = null; maybeRunValidityCheck(); } @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); System.out.println("// ************************************"); lastJsSource = currentJsSource; } } } final String getCurrentJsSource() { SourceMap sourceMap = getSourceMap(); if (sourceMap != null) { sourceMap.reset(); } List fileNameRegexList = options.filesToPrintAfterEachPassRegexList; List moduleNameRegexList = options.modulesToPrintAfterEachPassRegexList; StringBuilder builder = new StringBuilder(); if (fileNameRegexList.isEmpty() && moduleNameRegexList.isEmpty()) { return toSource(); } if (!fileNameRegexList.isEmpty()) { checkNotNull(jsRoot); for (Node fileNode : 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 : modules) { 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 final Node getScriptNode(String filename) { checkNotNull(filename); if (jsRoot == null) { return null; } 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() { 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.functionInformationMap, this.sourceMap, this.externExports, this.cssNames, this.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 FeatureSet getFeatureSet() { return featureSet; } @Override void setFeatureSet(FeatureSet fs) { featureSet = fs; } /** * 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() { return new Supplier() { @Override public String get() { 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()); 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) { 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 = 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. checkState(newInput.getInputId().equals(oldInput.getInputId())); InputId inputIdOnAst = newInput.getAstRoot(this).getInputId(); 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; } /** * The graph of the JS source modules. * *

Must return null if there are less than 2 modules, * because we use this as a signal for which passes to run. * TODO(bradfordcsmith): Just check for a single module instead of null. */ @Override JSModuleGraph getModuleGraph() { if (moduleGraph != null && modules.size() > 1) { return moduleGraph; } else { return null; } } /** * Gets the list of modules. */ public List getModules() { return modules; } /** * 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; } @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() ? getGlobalTypeInfo() : getTypeRegistry(); case OTI: return getTypeRegistry(); case NTI: return getGlobalTypeInfo(); default: throw new RuntimeException("Unhandled typechecker " + mostRecentTypechecker); } } @Override public void clearTypeIRegistry() { switch (mostRecentTypechecker) { case OTI: typeRegistry = null; return; case NTI: globalTypeInfo = null; return; case NONE: return; 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) { forwardDeclaredTypes.add(typeName); } @Override void setMostRecentTypechecker(MostRecentTypechecker lastRun) { this.mostRecentTypechecker = lastRun; } @Override MostRecentTypechecker getMostRecentTypechecker() { return this.mostRecentTypechecker; } @Override // Only used by jsdev public MemoizedTypedScopeCreator getTypedScopeCreator() { return getPassConfig().getTypedScopeCreator(); } @Override IncrementalScopeCreator getScopeCreator() { return this.scopeCreator; } @Override void putScopeCreator(IncrementalScopeCreator creator) { this.scopeCreator = creator; } @SuppressWarnings("unchecked") 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()); MemoizedTypedScopeCreator 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); 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 Iterable getTypeMismatches() { switch (this.mostRecentTypechecker) { case OTI: return getTypeValidator().getMismatches(); case NTI: return getGlobalTypeInfo().getMismatches(); default: throw new RuntimeException("Can't ask for type mismatches before type checking."); } } @Override Iterable getImplicitInterfaceUses() { switch (this.mostRecentTypechecker) { case OTI: return getTypeValidator().getImplicitInterfaceUses(); case NTI: return getGlobalTypeInfo().getImplicitInterfaceUses(); default: throw new RuntimeException("Can't ask for type mismatches before type checking."); } } @Override GlobalTypeInfo getGlobalTypeInfo() { if (this.globalTypeInfo == null) { this.globalTypeInfo = new GlobalTypeInfo(this, forwardDeclaredTypes); } return this.globalTypeInfo; } public void maybeSetTracker() { if (options.getTracerMode().isOn()) { PrintStream tracerOutput = options.getTracerOutput() == null ? this.outStream : options.getTracerOutput(); tracker = new PerformanceTracker(externsRoot, jsRoot, options.getTracerMode(), tracerOutput); 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(); 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 = input.getAstRoot(this); if (hasErrors()) { return null; } externsRoot.addChildToBack(n); } if (options.transformAMDToCJSModules) { processAMDModules(); } if (options.getLanguageIn().toFeatureSet().has(FeatureSet.Feature.MODULES) || options.processCommonJSModules) { this.moduleLoader = new ModuleLoader( null, options.moduleRoots, inputs, ModuleLoader.PathResolver.RELATIVE, options.moduleResolutionMode, inputPathByWebpackId); if (options.moduleResolutionMode == ModuleLoader.ResolutionMode.NODE) { // processJsonInputs requires a module loader to already be defined // so we redefine it afterwards with the package.json inputs this.moduleLoader = new ModuleLoader( null, options.moduleRoots, inputs, ModuleLoader.PathResolver.RELATIVE, options.moduleResolutionMode, processJsonInputs(inputs)); } } else { // Use an empty module loader if we're not actually dealing with modules. this.moduleLoader = ModuleLoader.EMPTY; } if (options.getDependencyOptions().needsManagement()) { findDependenciesFromEntryPoints( 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(inputs); } // 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()) { 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 : inputs) { for (String require : input.getKnownRequires()) { 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(inputs); } for (CompilerInput input : inputs) { Node n = input.getAstRoot(this); if (n == null) { continue; } 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.traverseEs6(this, n, sia); } if (NodeUtil.isFromTypeSummary(n)) { input.setIsExtern(true); externsRoot.addChildToBack(n); } else { jsRoot.addChildToBack(n); } } if (hasErrors()) { return null; } return externAndJsRoot; } finally { afterPass(PassNames.PARSE_INPUTS); stopTracer(tracer, PassNames.PARSE_INPUTS); } } 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() { hoistExterns(); // 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())); } } hoistNoCompileFiles(); if (staleInputs) { repartitionInputs(); } } /** * Find dependencies by recursively traversing each dependency of an input starting with the entry * points. Causes a full parse of each file, but since the file is reachable by walking the graph, * this would be required in later compilation passes regardless. * *

Inputs which are not reachable during graph traversal will be dropped. * *

If the dependency mode is set to LOOSE, inputs for which the deps package did not find a * provide statement or detect as a module will be treated as entry points. */ void findDependenciesFromEntryPoints(boolean supportEs6Modules, boolean supportCommonJSModules) { hoistExterns(); List entryPoints = new ArrayList<>(); Map inputsByProvide = new HashMap<>(); Map inputsByIdentifier = new HashMap<>(); for (CompilerInput input : inputs) { if (!options.getDependencyOptions().shouldDropMoochers() && input.getProvides().isEmpty()) { entryPoints.add(input); } inputsByIdentifier.put( ModuleIdentifier.forFile(input.getPath().toString()).toString(), input); for (String provide : input.getProvides()) { if (!provide.startsWith("module$")) { 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 = new HashSet<>(inputs); List orderedInputs = new ArrayList<>(); for (CompilerInput entryPoint : entryPoints) { orderedInputs.addAll( depthFirstDependenciesFromInput( entryPoint, /* wasImportedByModule = */ false, workingInputSet, inputsByIdentifier, inputsByProvide, supportEs6Modules, supportCommonJSModules)); } } /** For a given input, order it's dependencies in a depth first traversal */ private List depthFirstDependenciesFromInput( CompilerInput input, boolean wasImportedByModule, Set inputs, Map inputsByIdentifier, Map inputsByProvide, boolean supportEs6Modules, boolean supportCommonJSModules) { List orderedInputs = new ArrayList<>(); 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 orderedInputs; } FindModuleDependencies findDeps = new FindModuleDependencies( this, supportEs6Modules, supportCommonJSModules, inputPathByWebpackId); findDeps.process(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()); ArrayList allDeps = new ArrayList<>(); allDeps.addAll(input.getRequires()); allDeps.addAll(input.getDynamicRequires()); 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) { orderedInputs.addAll( depthFirstDependenciesFromInput( requiredInput, requiredByModuleImport, inputs, inputsByIdentifier, inputsByProvide, supportEs6Modules, supportCommonJSModules)); } } orderedInputs.add(input); return orderedInputs; } /** * Hoists inputs with the @externs annotation into the externs list. */ void hoistExterns() { boolean staleInputs = false; for (CompilerInput input : inputs) { // TODO(b/65450037): Remove this if. All @externs annotated files should be hoisted. 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 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 move it out of the main // JS root and put it with the other externs. 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; } input.setJsModuleType(CompilerInput.ModuleType.JSON); rewriteJson.process(null, root); } return rewriteJson.getPackageJsonMainEntries(); } private List parsePotentialModules(List inputsToProcess) { List filteredInputs = new ArrayList<>(); for (CompilerInput input : inputsToProcess) { // Only process files that are detected as ES6 modules if (!options.dependencyOptions.shouldPruneDependencies() || !JsFileParser.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() { for (CompilerInput input : inputs) { input.setCompiler(this); Node root = input.getAstRoot(this); if (root == null) { continue; } 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)); } private Node parseCodeHelper(SourceFile src) { CompilerInput input = new CompilerInput(src); 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; 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(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(); } }); } /** * 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 } 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(getTypeIRegistry()); builder.setCompilerOptions(options); builder.setSourceMap(sourceMap); builder.setTagAsExterns(firstOutput && n.isFromExterns()); builder.setTagAsTypeSummary( firstOutput && !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(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 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; } }); } /** * 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); } void recordFunctionInformation() { logger.fine("Recording function information"); startPass("recordFunctionInformation"); RecordFunctionInformation recordFunctionInfoPass = new RecordFunctionInformation(this, this.functionNames); process(recordFunctionInfoPass); functionInformationMap = recordFunctionInfoPass.getMap(); endPass("recordFunctionInformation"); } 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}"; 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 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_NEXT: return Config.LanguageMode.ES_NEXT; 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. */ 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) { 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() && 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(); } @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); } @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; } // The sourcemap will return a path relative to the sourcemap's file. // Translate it to one relative to our base directory. SourceFile source = SourceMapResolver.getRelativePath(sourceMap.getOriginalPath(), result.getOriginalFile()); if (source == null) { return null; } String originalPath = source.getOriginalPath(); sourceMapOriginalSources.putIfAbsent(originalPath, source); return result .toBuilder() .setOriginalFile(originalPath) .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(@Nullable 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; } /** * 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; /** Fully qualified function names and globally unique ids */ private FunctionNames functionNames = 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 setFunctionNames(FunctionNames functionNames) { this.functionNames = functionNames; } @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; } @Override public FunctionNames getFunctionNames() { return functionNames; } VariableMap getStringMap() { return this.stringMap; } @Override public void addExportedNames(Set exportedNames) { this.exportedNames.addAll(exportedNames); } @Override public Set getExportedNames() { return exportedNames; } @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(new CompilerOptions()); } return errorManager; } @Override List getInputsInOrder() { return Collections.unmodifiableList(inputs); } @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 (inputs != null) ? inputs.size() : 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 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) { 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 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) { setFeatureSet(options.getLanguageIn().toFeatureSet()); Node js = ast.getAstRoot(this); 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(); 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 ModuleLoader getModuleLoader() { return moduleLoader; } private void addFilesToSourceMap(Iterable files) { if (getOptions().sourceMapIncludeSourcesContent && getSourceMap() != null) { for (SourceFile file : files) { getSourceMap().addSourceFile(file); } } } 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; } 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 List inputs; private final Map inputsById; private final JSTypeRegistry typeRegistry; private final TypeValidator typeValidator; private final MostRecentTypechecker mostRecentTypeChecker; private final CompilerInput synthesizedExternsInput; private final CompilerInput synthesizedExternsInputAtEnd; private final Map injectedLibraries; private final Node lastInjectedLibrary; private final GlobalTypeInfo globalTypeInfo; private final boolean hasRegExpGlobalReferences; private final LifeCycleStage lifeCycleStage; private final Set externProperties; private final JSError[] errors; private final JSError[] warnings; private final JSModuleGraph moduleGraph; private final List modules; private final int uniqueNameId; private final Set exportedNames; private final Map cssNames; private final VariableMap variableMap; private final VariableMap propertyMap; private final VariableMap anonymousFunctionaMap; private final FunctionNames functioNames; 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.inputs = checkNotNull(compiler.inputs); this.inputsById = checkNotNull(compiler.inputsById); this.mostRecentTypeChecker = compiler.mostRecentTypechecker; this.synthesizedExternsInput = compiler.synthesizedExternsInput; this.synthesizedExternsInputAtEnd = compiler.synthesizedExternsInputAtEnd; this.injectedLibraries = compiler.injectedLibraries; this.lastInjectedLibrary = compiler.lastInjectedLibrary; this.globalTypeInfo = compiler.globalTypeInfo; 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.modules = compiler.modules; this.uniqueNameId = compiler.uniqueNameId; this.exportedNames = compiler.exportedNames; this.cssNames = compiler.cssNames; this.variableMap = compiler.variableMap; this.propertyMap = compiler.propertyMap; this.anonymousFunctionaMap = compiler.anonymousFunctionNameMap; this.functioNames = compiler.functionNames; 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(new Callable() { @Override public Void call() throws Exception { 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(); List newModules = modules; 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); CompilerState compilerState = (CompilerState) objectInputStream.readObject(); if (compilerState.typeRegistry != null) { compilerState.typeRegistry.restoreContents(objectInputStream); } stopTracer(tracer, PassNames.DESERIALIZE_COMPILER_STATE); return compilerState; } }); featureSet = compilerState.featureSet; externs = compilerState.externs; inputs = compilerState.inputs; inputsById.clear(); inputsById.putAll(compilerState.inputsById); typeRegistry = compilerState.typeRegistry; externAndJsRoot = compilerState.externAndJsRoot; externsRoot = compilerState.externsRoot; jsRoot = compilerState.jsRoot; mostRecentTypechecker = compilerState.mostRecentTypeChecker; synthesizedExternsInput = compilerState.synthesizedExternsInput; synthesizedExternsInputAtEnd = compilerState.synthesizedExternsInputAtEnd; injectedLibraries.clear(); injectedLibraries.putAll(compilerState.injectedLibraries); lastInjectedLibrary = compilerState.lastInjectedLibrary; globalTypeInfo = compilerState.globalTypeInfo; hasRegExpGlobalReferences = compilerState.hasRegExpGlobalReferences; typeValidator = compilerState.typeValidator; setLifeCycleStage(compilerState.lifeCycleStage); externProperties = compilerState.externProperties; moduleGraph = compilerState.moduleGraph; modules = compilerState.modules; uniqueNameId = compilerState.uniqueNameId; 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; functionNames = compilerState.functioNames; defaultDefineValues = checkNotNull(compilerState.defaultDefineValues); annotationMap = checkNotNull(compilerState.annotationMap); inputSourceMaps = compilerState.inputSourceMaps; changeStamp = compilerState.changeStamp; // Reapply module names to deserialized modules renameModules(newModules, modules); // 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); } } public void resetCompilerInput() { for (JSModule module : this.modules) { for (CompilerInput input : module.getInputs()) { input.reset(); } } for (CompilerInput input : this.externs) { input.reset(); } } /** Returns the module type for the provided namespace. */ @Override @Nullable CompilerInput.ModuleType getModuleTypeByName(String moduleName) { return moduleTypesByName.get(moduleName); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy