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

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

Go to download

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

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2009 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.checkState;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.MustBeClosed;
import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper;
import com.google.javascript.jscomp.ExpressionDecomposer.Workaround;
import com.google.javascript.jscomp.colors.ColorRegistry;
import com.google.javascript.jscomp.deps.ModuleLoader;
import com.google.javascript.jscomp.diagnostic.LogFile;
import com.google.javascript.jscomp.modules.ModuleMap;
import com.google.javascript.jscomp.modules.ModuleMetadataMap;
import com.google.javascript.jscomp.parsing.Config;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.trees.Comment;
import com.google.javascript.jscomp.serialization.ColorPool;
import com.google.javascript.jscomp.type.ReverseAbstractInterpreter;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticScope;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import java.io.Serializable;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import org.jspecify.nullness.Nullable;

/**
 * An abstract compiler, to help remove the circular dependency of passes on JSCompiler.
 *
 * 

This is an abstract class, so that we can make the methods package-private. */ public abstract class AbstractCompiler implements SourceExcerptProvider, CompilerInputProvider { static final DiagnosticType READ_ERROR = DiagnosticType.error("JSC_READ_ERROR", "Cannot read file {0}: {1}"); private int currentPassIndex = -1; /** Will be called before each pass runs. */ @OverridingMethodsMustInvokeSuper void beforePass(String passName) { this.currentPassIndex++; } /** Will be called after each pass finishes. */ @OverridingMethodsMustInvokeSuper void afterPass(String passName) {} private LifeCycleStage stage = LifeCycleStage.RAW; // TODO(nicksantos): Decide if all of these are really necessary. // Many of them are just accessors that should be passed to the // CompilerPass's constructor. abstract java.util.function.Supplier getTypedAstDeserializer(SourceFile file); /** Looks up an input (possibly an externs input) by input id. May return null. */ @Override public abstract CompilerInput getInput(InputId inputId); /** Looks up a source file by name. May return null. */ abstract @Nullable SourceFile getSourceFileByName(String sourceName); public abstract @Nullable Node getScriptNode(String filename); /** Gets the module graph. */ abstract @Nullable JSChunkGraph getModuleGraph(); /** * Gets the inputs in the order in which they are being processed. Only for use by {@code * AbstractCompilerRunner}. */ abstract Iterable getInputsInOrder(); /** * Gets the total number of inputs. * *

This can be useful as a guide for the initial allocated size for data structures. */ abstract int getNumberOfInputs(); // // Intermediate state and results produced and needed by particular passes. // TODO(rluble): move these into the general structure for keeping state between pass runs. // /** Adds exported names to keep track. */ public abstract void addExportedNames(Set exportedVariableNames); /** Gets the names that have been exported. */ public abstract Set getExportedNames(); /** Sets the variable renaming map */ public abstract void setVariableMap(VariableMap variableMap); /** Sets the property renaming map */ public abstract void setPropertyMap(VariableMap propertyMap); /** Sets the string replacement map */ public abstract void setStringMap(VariableMap stringMap); /** Sets the css names found during compilation. */ public abstract void setCssNames(LinkedHashMap newCssNames); /** Sets the mapping for instrumentation parameter encoding. */ public abstract void setInstrumentationMapping(VariableMap instrumentationMapping); /** Sets the id generator for cross-module motion. */ public abstract void setIdGeneratorMap(String serializedIdMappings); /** Gets the id generator for cross-module motion. */ public abstract IdGenerator getCrossModuleIdGenerator(); /** Sets the naming map for anonymous functions */ public abstract void setAnonymousFunctionNameMap(VariableMap functionMap); // // End of intermediate state needed by passes. // /** Sets whether the typechecking passes have run. */ abstract void setTypeCheckingHasRun(boolean hasRun); /** Returns whether the typechecking passes have run */ public abstract boolean hasTypeCheckingRun(); /** Whether the AST has been annotated with optimization colors. */ public abstract boolean hasOptimizationColors(); /** * Returns `true` when type checking has run, but the type registry has been cleared. * *

See also `clearJSTypeRegistry()`. */ public abstract boolean isTypeRegistryCleared(); /** Gets a central registry of type information from the compiled JS. */ public abstract JSTypeRegistry getTypeRegistry(); public abstract void clearJSTypeRegistry(); /** Gets a central registry of colors from deserialized JS types. */ public abstract ColorRegistry getColorRegistry(); /** Sets the color registry */ public abstract void setColorRegistry(ColorRegistry registry); abstract void forwardDeclareType(String typeName); /** Gets a memoized scope creator with type information. */ abstract ScopeCreator getTypedScopeCreator(); abstract void clearTypedScopeCreator(); /** Gets the top scope. */ public abstract TypedScope getTopScope(); /** Sets the top scope. */ abstract void setTopScope(TypedScope x); /** * Returns a scope containing only externs and synthetic code or other code in the first script. * *

Intended for transpilation passes to look up types when synthesizing new code. */ abstract StaticScope getTranspilationNamespace(); /** Report an error or warning. */ public abstract void report(JSError error); /** Report an internal error. */ abstract void throwInternalError(String msg, Throwable cause); /** Gets the current coding convention. */ public abstract CodingConvention getCodingConvention(); /** * Passes that make modifications in a scope that is different than the Compiler.currentScope use * this (eg, InlineVariables and many others) */ public abstract void reportChangeToEnclosingScope(Node n); /** * Mark modifications in a scope that is different than the Compiler.currentScope use this (eg, * InlineVariables and many others) */ public abstract void reportChangeToChangeScope(Node changeScopeRoot); /** * Mark a specific function node as known to be deleted. Is part of having accurate change * tracking which is necessary to streamline optimizations. */ abstract void reportFunctionDeleted(Node node); /** * Gets a suitable SCRIPT node to serve as a parent for code insertion. If {@code module} contains * any inputs, the returned node will be the SCRIPT node corresponding to its first input. If * {@code module} is empty, on the other hand, then the returned node will be the first SCRIPT * node in a non-empty module that {@code module} depends on (the deepest one possible). * * @param module A module. If null, will return the first SCRIPT node of all modules. * @return A SCRIPT node (never null). */ abstract Node getNodeForCodeInsertion(@Nullable JSChunk module); abstract TypeValidator getTypeValidator(); /** Gets the central registry of type violations. */ public abstract Iterable getTypeMismatches(); abstract void setExternExports(String externExports); /** Parses code for injecting, and associate it with a given source file. */ public abstract Node parseSyntheticCode(String filename, String code); /** Parses code for testing. */ @VisibleForTesting abstract Node parseTestCode(String code); /** Parses code for testing. */ @VisibleForTesting abstract Node parseTestCode(ImmutableList code); /** Prints a node to source code. */ public abstract String toSource(); /** Prints a node to source code. */ public abstract String toSource(Node root); /** * Whether to prefer using {@link JSFileRegexParser} when calculating {@link DependencyInfo} as * opposed to triggering an AST-based parse. * *

This is primarily for performance reasons. The two should produce the same results except in * some edge cases. */ abstract boolean preferRegexParser(); /** Gets a default error reporter for injecting into Rhino. */ abstract ErrorReporter getDefaultErrorReporter(); /** Get an interpreter for type analysis. */ public abstract ReverseAbstractInterpreter getReverseAbstractInterpreter(); /** Returns the current life-cycle stage of the AST we're working on. */ public LifeCycleStage getLifeCycleStage() { return stage; } private static final String FILL_FILE_SUFFIX = "$fillFile"; /** Empty modules get an empty "fill" file, so that we can move code into an empty module. */ static String createFillFileName(String moduleName) { return moduleName + FILL_FILE_SUFFIX; } /** Returns whether a file name was created by {@link createFillFileName}. */ public static boolean isFillFileName(String fileName) { return fileName.endsWith(FILL_FILE_SUFFIX); } /** * Deserialize runtime libraries from a TypedAST packaged as a JAR resource and reconcile their * Colors with the current inputs. * *

This method must be called anywhere that Colors are reconciled for application to the AST. * Otherwise Color information won't be consistent. `colorPoolBuilder` must be the same builder as * used for the other inputs, and the caller retains ownership. * * @param colorPoolBuilder if present, includes inferred optimization colors on the deserialized * ASTs. If absent, does not include colors. */ public void initRuntimeLibraryTypedAsts(Optional colorPoolBuilder) { throw new UnsupportedOperationException( "Implementation in Compiler.java is not J2CL compatible."); } /** * Generates unique String Ids when requested via a compiler instance. * *

This supplier provides Ids that are deterministic and unique across all input files given to * the compiler. The generated ID format is: uniqueId = "fileHashCode$counterForThisFile" */ abstract UniqueIdSupplier getUniqueIdSupplier(); /** * Generates unique ids. * * @deprecated because the generated names during transpilation are not unique across all input * files. Use the new supplier by calling {@code getUniqueIdSupplier()}. */ @Deprecated abstract Supplier getUniqueNameIdSupplier(); /** * @return Whether any errors have been encountered that should stop the compilation process. */ abstract boolean hasHaltingErrors(); /** Register a listener for code change events. */ abstract void addChangeHandler(CodeChangeHandler handler); /** Remove a listener for code change events. */ abstract void removeChangeHandler(CodeChangeHandler handler); /** Register a provider for some type of index. */ abstract void addIndexProvider(IndexProvider indexProvider); /** * Returns, from a provider, the desired index of type T, otherwise null if no provider is * registered for the given type. */ abstract T getIndex(Class type); /** A monotonically increasing value to identify a change */ abstract int getChangeStamp(); /** * An accumulation of changed scope nodes since the last time the given pass was run. A returned * empty list means no scope nodes have changed since the last run and a returned null means this * is the first time the pass has run. */ abstract List getChangedScopeNodesForPass(String passName); /** Called to indicate that the current change stamp has been used */ abstract void incrementChangeStamp(); /** Returns the root of the source tree, ignoring externs */ abstract Node getJsRoot(); /** True iff a function changed since the last time a pass was run */ abstract boolean hasScopeChanged(Node n); /** * Represents the different contexts for which the compiler could have distinct configurations. */ static enum ConfigContext { /** Normal JavaScript. */ DEFAULT, /** Externs files. */ EXTERNS } /** Returns the parser configuration for the specified context. */ abstract Config getParserConfig(ConfigContext context); /** Gets the error manager. */ public abstract ErrorManager getErrorManager(); /** Set the current life-cycle state. */ void setLifeCycleStage(LifeCycleStage stage) { this.stage = stage; } /** * Are the nodes equal for the purpose of inlining? If type aware optimizations are on, type * equality is checked. */ abstract boolean areNodesEqualForInlining(Node n1, Node n2); /** * Set if RegExp global properties are used. * * @param references Whether there are references to the RegExp global object properties. */ abstract void setHasRegExpGlobalReferences(boolean references); /** * @return Whether the AST contains references to the RegExp global object properties. */ abstract boolean hasRegExpGlobalReferences(); /** * @return The error level the given error object will be reported at. */ abstract CheckLevel getErrorLevel(JSError error); /** What point in optimizations we're in. For use by compiler passes */ public static enum LifeCycleStage implements Serializable { RAW, // See constraints put on the AST by either running the ConvertTypesToColors.java pass /or/ by // having created the AST via serialization/TypedAstDeserializer.java // NORMALIZED implies this constraint COLORS_AND_SIMPLIFIED_JSDOC, // See constraints put on the tree by Normalize.java NORMALIZED, // The normalize pass has put constraints on the tree, // but variables and properties have been renamed so // coding conventions no longer apply. NORMALIZED_OBFUSCATED; public boolean isNormalized() { return this == NORMALIZED || this == NORMALIZED_OBFUSCATED; } public boolean isNormalizedUnobfuscated() { return this == NORMALIZED; } public boolean isNormalizedObfuscated() { return this == NORMALIZED_OBFUSCATED; } public boolean hasColorAndSimplifiedJSDoc() { return this != RAW; } } /** * Runs a given compiler-pass by calling its {@code process()} method. * * @param pass The pass to be run. */ abstract void process(CompilerPass pass); /** Returns the root node of the AST, which includes both externs and source. */ public abstract Node getRoot(); abstract CompilerOptions getOptions(); /** * The set of features defined by the input language mode that have not (yet) been transpiled * away. * *

This is **not** the exact set of all features currently in the AST, but rather an improper * super set. This set starts out as the set of features from the language input mode specified by * the options, which is verified to be a super set of what appears in the input code (if the * input code contains a feature outside the language input mode it is an error). As the compiler * transpiles code any features that are transpiled away from the AST are removed from this set. * *

The feature set is computed this way, rather than using the detected features in the AST, so * that the set of passes that run is determined purely by input flags. Otherwise, introducing a * previously unused feature in any of the transitive deps of compilation target could effect the * build behaviour. */ abstract FeatureSet getFeatureSet(); abstract void setFeatureSet(FeatureSet fs); /** * @return a CompilerInput that can be modified to add additional extern definitions to the * beginning of the externs AST */ abstract CompilerInput getSynthesizedExternsInput(); /** Gets the last pass name set by setProgress. */ abstract String getLastPassName(); static final String RUNTIME_LIB_DIR = "src/com/google/javascript/jscomp/js/"; /** * The subdir js/ contains libraries of code that we inject at compile-time only if requested by * this function. * *

Notice that these libraries will almost always create global symbols. * * @param resourceName The name of the library. For example, if "base" is is specified, then we * load js/base.js * @param force Inject the library even if compiler options say not to. * @return The last node of the most-recently-injected runtime library. If new code was injected, * this will be the last expression node of the library. If the caller needs to add additional * code, they should add it as the next sibling of this node. If no runtime libraries have * been injected, then null is returned. */ abstract Node ensureLibraryInjected(String resourceName, boolean force); /** * Sets the names of the properties defined in externs. * * @param externProperties The set of property names defined in externs. */ abstract void setExternProperties(ImmutableSet externProperties); /** * Gets the names of the properties defined in externs or null if GatherExternProperties pass was * not run yet. */ public abstract ImmutableSet getExternProperties(); /** * Adds a {@link SourceMapInput} for the given {@code sourceFileName}, to be used for error * reporting and source map combining. */ public abstract void addInputSourceMap(String name, SourceMapInput sourceMap); public abstract String getBase64SourceMapContents(String sourceFileName); abstract void addComments(String filename, List comments); /** * Returns a summary an entry for every property name found in the AST with a getter and / or * setter defined. * *

Property names for which there are no getters or setters will not be in the map. */ abstract AccessorSummary getAccessorSummary(); /** Sets the summary of properties with getters and setters. */ public abstract void setAccessorSummary(AccessorSummary summary); /** Returns all the comments from the given file. */ abstract List getComments(String filename); /** Gets the module loader. */ abstract ModuleLoader getModuleLoader(); /** Lookup the type of a module from its name. */ abstract CompilerInput.ModuleType getModuleTypeByName(String moduleName); /** Set whether J2CL passes should run */ abstract void setRunJ2clPasses(boolean runJ2clPasses); /** Whether J2CL passes should run */ abstract boolean runJ2clPasses(); /** * Returns a new AstFactory that will add type information to the nodes it creates if and only if * type checking has already happened and types have not been converted into colors. * *

Note that the AstFactory will /not/ add colors to the AST if types have been converted into * colors. The AstFactory does not understand colors, although color support could certainly be * added if it proves useful. */ public final AstFactory createAstFactory() { return hasTypeCheckingRun() ? (hasOptimizationColors() ? AstFactory.createFactoryWithColors(getColorRegistry()) : AstFactory.createFactoryWithTypes(getTypeRegistry())) : AstFactory.createFactoryWithoutTypes(); } /** * Returns a new AstFactory that will not add type information, regardless of whether type * checking has already happened. */ public final AstFactory createAstFactoryWithoutTypes() { return AstFactory.createFactoryWithoutTypes(); } /** * Returns a new AstAnalyzer configured correctly to answer questions about Nodes in the AST * currently being compiled. */ public AstAnalyzer getAstAnalyzer() { return new AstAnalyzer(this, getOptions().getAssumeGettersArePure()); } public ExpressionDecomposer createDefaultExpressionDecomposer() { return createExpressionDecomposer( this.getUniqueNameIdSupplier(), ImmutableSet.of(), Scope.createGlobalScope(new Node(Token.SCRIPT))); } public ExpressionDecomposer createExpressionDecomposer( Supplier uniqueNameIdSupplier, ImmutableSet knownConstantFunctions, Scope scope) { // If the output is ES5, then it may end up running on IE11, so enable a workaround // for one of its bugs. final EnumSet enabledWorkarounds = FeatureSet.ES5.contains(getOptions().getOutputFeatureSet()) ? EnumSet.of(Workaround.BROKEN_IE11_LOCATION_ASSIGN) : EnumSet.noneOf(Workaround.class); return new ExpressionDecomposer( this, uniqueNameIdSupplier, knownConstantFunctions, scope, enabledWorkarounds); } public abstract ModuleMetadataMap getModuleMetadataMap(); public abstract void setModuleMetadataMap(ModuleMetadataMap moduleMetadataMap); public abstract ModuleMap getModuleMap(); public abstract void setModuleMap(ModuleMap moduleMap); public final boolean isDebugLoggingEnabled() { return this.getOptions().getDebugLogDirectory() != null; } public final List getDebugLogFilterList() { if (this.getOptions().getDebugLogFilter() == null) { return new ArrayList<>(); } return Splitter.on(',').omitEmptyStrings().splitToList(this.getOptions().getDebugLogFilter()); } /** Provides logging access to a file with the specified name. */ @MustBeClosed public final LogFile createOrReopenLog( Class owner, String firstNamePart, String... restNameParts) { if (!this.isDebugLoggingEnabled()) { return LogFile.createNoOp(); } Path dir = getOptions().getDebugLogDirectory(); Path relativeParts = Paths.get(firstNamePart, restNameParts); Path file = dir.resolve(owner.getSimpleName()).resolve(relativeParts); // If a filter list for log file names was provided, only create a log file if any // of the filter strings matches. List filters = getDebugLogFilterList(); if (filters.isEmpty()) { return LogFile.createOrReopen(file); } for (String filter : filters) { if (file.toString().contains(filter)) { return LogFile.createOrReopen(file); } } return LogFile.createNoOp(); } /** * Provides logging access to a file with the specified name, differentiated by the index of the * current pass. * *

Indexing helps in separating logs from different pass loops. The filename pattern is * "[debug_log_directory]/[owner_name]/([name_part[i]]/){0,n-1}[pass_index]_[name_part[n]]". */ @MustBeClosed public final LogFile createOrReopenIndexedLog( Class owner, String firstNamePart, String... restNameParts) { checkState(this.currentPassIndex >= 0, this.currentPassIndex); String index = Strings.padStart(Integer.toString(this.currentPassIndex), 3, '0'); int length = restNameParts.length; if (length == 0) { firstNamePart = index + "_" + firstNamePart; } else { restNameParts[length - 1] = index + "_" + restNameParts[length - 1]; } return this.createOrReopenLog(owner, firstNamePart, restNameParts); } /** Returns the InputId of the synthetic code input (even if it is not initialized yet). */ abstract InputId getSyntheticCodeInputId(); /** * Adds a synthetic script to the front of the AST * *

Useful to allow inserting code into the global scope, earlier than any of the user-provided * code, in the case that the first user-provided input is a module. */ abstract void initializeSyntheticCodeInput(); /** Removes the script added by {@link #initializeSyntheticCodeInput} */ abstract void removeSyntheticCodeInput(); /** * Merges all code in the script added by {@link #initializeSyntheticCodeInput} into the first * non-synthetic script. Will crash if the first non-synthetic script is a module and module * rewriting has not occurred. */ abstract void mergeSyntheticCodeInput(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy