com.google.javascript.jscomp.GlobalVarReferenceMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler-unshaded Show documentation
Show all versions of closure-compiler-unshaded Show documentation
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.
/*
* Copyright 2011 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 com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* An implementation for {@code ReferenceMap} that is specific to global scope
* and can be used in different passes. In other words instead of relying on
* Var object it relies on the name of the variable. It also supports hot-swap
* update of reference map for a specific script.
*
* @see ReferenceCollectingCallback#exitScope(NodeTraversal)
*/
class GlobalVarReferenceMap implements ReferenceMap {
private Map refMap = null;
private final Map inputOrder;
/** @param inputs The ordered list of all inputs for the compiler. */
GlobalVarReferenceMap(Iterable inputs, Iterable externs) {
inputOrder = new HashMap<>();
int ind = 0;
for (CompilerInput extern : externs) {
inputOrder.put(extern.getInputId(), ind);
ind++;
}
for (CompilerInput input : inputs) {
inputOrder.put(input.getInputId(), ind);
ind++;
}
}
@Override
public ReferenceCollection getReferences(Var var) {
if (!var.isGlobal()) {
return null;
}
return refMap.get(var.getName());
}
/**
* Resets global var reference map with the new provide map.
*
* @param globalRefMap The reference map result of a
* {@link ReferenceCollectingCallback} pass collected from the whole AST.
*/
private void resetGlobalVarReferences(
Map globalRefMap) {
refMap = new LinkedHashMap<>();
for (Entry entry : globalRefMap.entrySet()) {
Var var = entry.getKey();
if (var.isGlobal()) {
refMap.put(var.getName(), entry.getValue());
}
}
}
/**
* Updates the internal reference map based on the provided parameters. If
* {@code scriptRoot} is not SCRIPT, it basically replaces the internal map
* with the new one, otherwise it replaces all the information associated to
* the given script.
*
* @param refMapPatch The reference map result of a
* {@link ReferenceCollectingCallback} pass which might be collected from
* the whole AST or just a sub-tree associated to a SCRIPT node.
* @param root AST sub-tree root on which reference collection was done.
*/
void updateGlobalVarReferences(Map
refMapPatch, Node root) {
if (refMap == null || !root.isScript()) {
resetGlobalVarReferences(refMapPatch);
return;
}
InputId inputId = root.getInputId();
checkNotNull(inputId);
// Note there are two assumptions here (i) the order of compiler inputs
// has not changed and (ii) all references are in the order they appear
// in AST (this is enforced in ReferenceCollectionCallback).
removeScriptReferences(inputId);
for (Entry entry : refMapPatch.entrySet()) {
Var var = entry.getKey();
if (var.isGlobal()) {
replaceReferences(var.getName(), inputId, entry.getValue());
}
}
}
private void removeScriptReferences(InputId inputId) {
checkNotNull(inputId);
if (!inputOrder.containsKey(inputId)) {
return; // Input did not exist when last computed, so skip
}
// TODO(bashir): If this is too slow it is not too difficult to make it
// faster with keeping an index for variables accessed in sourceName.
for (ReferenceCollection collection : refMap.values()) {
if (collection == null) {
continue;
}
List oldRefs = collection.references;
SourceRefRange range = findSourceRefRange(oldRefs, inputId);
List newRefs = new ArrayList<>(range.refsBefore());
newRefs.addAll(range.refsAfter());
collection.references = newRefs;
}
}
private void replaceReferences(String varName, InputId inputId,
ReferenceCollection newSourceCollection) {
ReferenceCollection combined = new ReferenceCollection();
List combinedRefs = combined.references;
ReferenceCollection oldCollection = refMap.get(varName);
refMap.put(varName, combined);
if (oldCollection == null) {
combinedRefs.addAll(newSourceCollection.references);
return;
}
// otherwise replace previous references that are from sourceName
SourceRefRange range = findSourceRefRange(oldCollection.references,
inputId);
combinedRefs.addAll(range.refsBefore());
combinedRefs.addAll(newSourceCollection.references);
combinedRefs.addAll(range.refsAfter());
}
/**
* Finds the range of references associated to {@code sourceName}. Note that
* even if there is no sourceName references the returned information can be
* used to decide where to insert new sourceName refs.
*/
private SourceRefRange findSourceRefRange(List refList,
InputId inputId) {
checkNotNull(inputId);
// TODO(bashir): We can do binary search here, but since this is fast enough
// right now, we just do a linear search for simplicity.
int lastBefore = -1;
int firstAfter = refList.size();
int index = 0;
checkState(inputOrder.containsKey(inputId), inputId.getIdName());
int sourceInputOrder = inputOrder.get(inputId);
for (Reference ref : refList) {
checkNotNull(ref.getInputId());
int order = inputOrder.get(ref.getInputId());
if (order < sourceInputOrder) {
lastBefore = index;
} else if (order > sourceInputOrder) {
firstAfter = index;
break;
}
index++;
}
return new SourceRefRange(refList, lastBefore, firstAfter);
}
private static class SourceRefRange {
private final int lastBefore;
private final int firstAfter;
private final List refList;
SourceRefRange(List refList, int lastBefore,
int firstAfter) {
this.lastBefore = Math.max(lastBefore, -1);
this.firstAfter = Math.min(firstAfter, refList.size());
this.refList = refList;
}
/** Note that the returned list is backed by {@code refList}! */
List refsBefore() {
return refList.subList(0, lastBefore + 1);
}
/** Note that the returned list is backed by {@code refList}! */
List refsAfter() {
return refList.subList(firstAfter, refList.size());
}
}
/**
* @param globalScope a new Global Scope to replace the scope of references
* with.
*/
public void updateReferencesWithGlobalScope(Scope globalScope) {
for (ReferenceCollection collection : refMap.values()) {
List newRefs = new ArrayList<>(collection.references.size());
for (Reference ref : collection) {
// This is only ever called with compiler.getTopScope() in production, which is
// a TypedScope. But References are only ever created with
if (ref.getScope() != globalScope) {
newRefs.add(ref.cloneWithNewScope(globalScope));
} else {
newRefs.add(ref);
}
}
collection.references = newRefs;
}
}
/**
* A CleanupPass implementation that will replace references to old Syntactic
* Global Scopes generated in previous compile runs with references to the
* Global Typed Scope.
*/
static class GlobalVarRefCleanupPass implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
public GlobalVarRefCleanupPass(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
GlobalVarReferenceMap refMap = compiler.getGlobalVarReferences();
if (refMap != null) {
// We don't have a suitable untyped scope to use, but the actual scope probably doesn't
// matter, so long as it's a global scope and doesn't persist references to things that
// need to be cleaned up. So we just generate a new simple root scope.
refMap.updateReferencesWithGlobalScope(
Scope.createGlobalScope(compiler.getTopScope().getRootNode()));
}
}
@Override
public void process(Node externs, Node root) {
// GlobalVarRefCleanupPass should not do work during process.
}
}
}