com.google.javascript.jscomp.AbstractCompiler Maven / Gradle / Ivy
/*
* 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.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.MustBeClosed;
import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper;
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.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.jstype.JSTypeRegistry;
import java.io.Serializable;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.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}");
protected Map annotationMap = new HashMap<>();
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.
/**
* Looks up an input (possibly an externs input) by input id.
* May return null.
*/
public abstract CompilerInput getInput(InputId inputId);
/** Looks up a source file by name. May return null. */
@Nullable
abstract SourceFile getSourceFileByName(String sourceName);
@Nullable
public abstract Node getScriptNode(String filename);
/** Gets the module graph. */
@Nullable
abstract JSModuleGraph 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(Map 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 the type-checking pass that ran most recently.
*/
abstract void setTypeCheckingHasRun(boolean hasRun);
/** Gets the type-checking pass that ran most recently. */
abstract boolean hasTypeCheckingRun();
/**
* Gets a central registry of type information from the compiled JS.
*/
public abstract JSTypeRegistry getTypeRegistry();
public abstract void clearJSTypeRegistry();
abstract void forwardDeclareType(String typeName);
/**
* Gets a memoized scope creator with type information. Only used by jsdev.
*/
abstract ScopeCreator getTypedScopeCreator();
/**
* Gets the top scope.
*/
public abstract TypedScope getTopScope();
/**
* Gets a memoized scope creator without type information, used by the checks and optimization
* passes to avoid continuously recreating the entire scope.
*/
abstract IncrementalScopeCreator getScopeCreator();
/**
* Stores a memoized scope creator without type information, used by the checks and optimization
* passes to avoid continuously recreating the entire scope.
*/
abstract void putScopeCreator(IncrementalScopeCreator creator);
/**
* 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);
/**
* Sets the CssRenamingMap.
*/
abstract void setCssRenamingMap(CssRenamingMap map);
/**
* Gets the CssRenamingMap.
*/
abstract CssRenamingMap getCssRenamingMap();
/**
* 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 JSModule module);
abstract TypeValidator getTypeValidator();
/** Gets the central registry of type violations. */
public abstract Iterable getTypeMismatches();
/**
* Gets all types that are used implicitly as a matching interface type. These are recorded as
* TypeMismatchs only for convenience
*/
public abstract Iterable getImplicitInterfaceUses();
abstract void setExternExports(String externExports);
/**
* Parses code for injecting.
*/
abstract Node parseSyntheticCode(String code);
/**
* Parses code for injecting, and associate it with a given source file.
*/
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);
/**
* 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);
}
/**
* 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);
/**
* An accumulation of deleted scope nodes since the last time the given pass was run. A returned
* null or empty list means no scope nodes have been deleted since the last run or this is the
* first time the pass has run.
*/
abstract List getDeletedScopeNodesForPass(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);
/**
* Normalizes the types of AST nodes in the given tree, and
* annotates any nodes to which the coding convention applies so that passes
* can read the annotations instead of using the coding convention.
*/
abstract void prepareAst(Node root);
/**
* 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 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;
}
}
/**
* 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);
// TODO(bashir) It would be good to extract a single dumb data object with
// only getters and setters that keeps all global information we keep for a
// compiler instance. Then move some of the functions of this class there.
/**
* Updates the list of references for variables in global scope.
*
* @param refMapPatch Maps each variable to all of its references; may contain
* references collected from the whole AST or only a SCRIPT sub-tree.
* @param collectionRoot The root of sub-tree in which reference collection
* has been done. This should either be a SCRIPT node (if collection is
* done on a single file) or it is assumed that collection is on full AST.
*/
abstract void updateGlobalVarReferences(Map
refMapPatch, Node collectionRoot);
/**
* This can be used to get the list of all references to all global variables
* based on all previous calls to {@code updateGlobalVarReferences}.
*
* @return The reference collection map associated to global scope variable.
*/
abstract GlobalVarReferenceMap getGlobalVarReferences();
/**
* @return a CompilerInput that can be modified to add additional extern
* definitions to the beginning of the externs AST
*/
abstract CompilerInput getSynthesizedExternsInput();
/**
* @return a CompilerInput that can be modified to add additional extern
* definitions to the end of the externs AST
*/
abstract CompilerInput getSynthesizedExternsInputAtEnd();
/**
* @return a number in [0,1] range indicating an approximate progress of the
* last compile. Note this should only be used as a hint and no assumptions
* should be made on accuracy, even a completed compile may choose not to set
* this to 1.0 at the end.
*/
public abstract double getProgress();
/**
* Gets the last pass name set by setProgress.
*/
abstract String getLastPassName();
/**
* Sets the progress percentage as well as the name of the last pass that
* ran (if available).
* @param progress A percentage expressed as a double in the range [0, 1].
* Use -1 if you just want to set the last pass name.
*/
abstract void setProgress(double progress, @Nullable String lastPassName);
/**
* 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(Set externProperties);
/**
* Gets the names of the properties defined in externs or null if GatherExternProperties pass was
* not run yet.
*/
abstract Set 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);
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. */
abstract void setAccessorSummary(AccessorSummary summary);
/**
* Returns all the comments from the given file.
*/
abstract List getComments(String filename);
/**
* Stores a map of default @define values. These values
* can be overridden by values specifically set in the CompilerOptions.
*/
abstract void setDefaultDefineValues(ImmutableMap values);
/**
* Gets a map of default @define values. These values
* can be overridden by values specifically set in the CompilerOptions.
*/
abstract ImmutableMap getDefaultDefineValues();
/**
* Gets the module loader.
*/
abstract ModuleLoader getModuleLoader();
/** Lookup the type of a module from its name. */
abstract CompilerInput.ModuleType getModuleTypeByName(String moduleName);
/**
* Sets an annotation for the given key.
*
* @param key the annotation key
* @param object the object to store as the annotation
*/
void setAnnotation(String key, Object object) {
checkArgument(object != null, "The stored annotation value cannot be null.");
Preconditions.checkArgument(
!annotationMap.containsKey(key), "Cannot overwrite the existing annotation '%s'.", key);
annotationMap.put(key, object);
}
/**
* Gets the annotation for the given key.
*
* @param key the annotation key
* @return the annotation object for the given key if it has been set, or null
*/
@Nullable
Object getAnnotation(String key) {
return annotationMap.get(key);
}
/**
* Returns a new AstFactory that will add type information to the nodes it creates if and only if
* type type checking has already happened.
*/
public AstFactory createAstFactory() {
return hasTypeCheckingRun()
? AstFactory.createFactoryWithTypes(getTypeRegistry())
: 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 abstract ModuleMetadataMap getModuleMetadataMap();
public abstract void setModuleMetadataMap(ModuleMetadataMap moduleMetadataMap);
public abstract ModuleMap getModuleMap();
public abstract void setModuleMap(ModuleMap moduleMap);
/** Provides logging access to a file with the specified name. */
@MustBeClosed
public final LogFile createOrReopenLog(
Class> owner, String firstNamePart, String... restNameParts) {
@Nullable Path dir = getOptions().getDebugLogDirectory();
if (dir == null) {
return LogFile.createNoOp();
}
Path relativeParts = Paths.get(firstNamePart, restNameParts);
Path file = dir.resolve(owner.getSimpleName()).resolve(relativeParts);
return LogFile.createOrReopen(file);
}
/**
* 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);
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();
}