
com.google.javascript.jscomp.NameReferenceGraph 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 com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.javascript.jscomp.DefinitionsRemover.AssignmentDefinition;
import com.google.javascript.jscomp.DefinitionsRemover.Definition;
import com.google.javascript.jscomp.DefinitionsRemover.NamedFunctionDefinition;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* A graph represents all the referencing of global names in the program. In
* other words, it is a call and variable-name graph.
*
* The NameReferenceGraph G for a program P is a directed graph G = (V, E)
* where:
*
*
V ({@link Name}) represents all global names in P and E = (v, v'), v and
* v' in V ({@link Reference} represents a reference use or definition from the
* name v to v' in P.
*
*
There are two core results we are trying to compute. The first being able
* to precisely identify the function body at any given call site with
* {@link #getReferencesAt(Node)}.
*
*
The second result come directly from the previous one. The directed edge
* provides us with dependency information. If A->B, B might be needed (in this
* module) if A is needed (in this module). The converse of the this result is
* more useful. B is not needed if A is not needed.
*
*/
class NameReferenceGraph extends
LinkedDirectedGraph
implements DefinitionProvider {
// This is the key result of the name graph. Given a node in the AST, this map
// will give us the Reference edges. For example a CALL node will map to a
// list of possible call edge destinations.
private final Multimap
referenceMap = HashMultimap.create();
// Given a qualified name, provides the Name object.
private Map nameMap = new HashMap<>();
// The following are some implicit nodes of the graph.
// If we have a call site that we absolutely have no idea what variable it
// it calls or reference, we'd point it to UNKNOWN.
final Name unknown;
// Represents the "main" global block as well as externs.
final Name main;
// The implicit "window" object.
final Name window;
final AbstractCompiler compiler;
public NameReferenceGraph(AbstractCompiler compiler) {
super(true, true);
this.compiler = compiler;
// Initialize builtins.
unknown = new Name("{UNKNOWN}", true);
unknown.isAliased = true;
unknown.type = compiler.getTypeIRegistry().getNativeType(JSTypeNative.NO_TYPE);
this.createNode(unknown);
main = new Name("{Global Main}", true);
this.createNode(main);
window = new Name("window", true);
this.createNode(window);
}
public Name defineNameIfNotExists(String name, boolean isExtern) {
Name symbol = null;
if (nameMap.containsKey(name)) {
// This is a re-declaration.
symbol = nameMap.get(name);
} else {
symbol = new Name(name, isExtern);
nameMap.put(name, symbol);
createNode(symbol);
}
return symbol;
}
/**
* Retrieves a list of all possible Names that this site is referring to.
*/
public List getReferencesAt(Node site) {
Preconditions.checkArgument(
site.isGetProp() || site.isName());
List result = new ArrayList<>();
result.addAll(referenceMap.get(site));
return result;
}
@Override
public Collection getDefinitionsReferencedAt(Node useSite) {
List nameRefs = getReferencesAt(useSite);
if (nameRefs.isEmpty()) {
return null;
}
List result = new ArrayList<>();
for (Name nameRef : nameRefs) {
List decls = nameRef.getDeclarations();
if (!decls.isEmpty()) {
result.addAll(decls);
}
}
if (!result.isEmpty()) {
return result;
} else {
return null;
}
}
public Name getSymbol(String name) {
return nameMap.get(name);
}
@Override
public GraphNode createNode(Name value) {
nameMap.put(value.qName, value);
return super.createNode(value);
}
@Override
public void connect(Name src, Reference ref, Name dest) {
super.connect(src, ref, dest);
referenceMap.put(ref.site, dest);
}
/**
* Represents function or variable names that can be referenced globally.
*/
class Name {
// Full name
private final String qName;
private JSType type;
// A list (re)declarations
private List declarations = new LinkedList<>();
final boolean isExtern;
private boolean isExported = false;
private boolean isAliased = false;
// Function invocations that use ".call" and ".apply" syntax may prevent
// several of the possible optimizations. We keep track of all functions
// invoked in this way so those passes can exclude them.
// Ex:
// some_func.call(some_obj, 1, 2 , 3);
// The name graph does not currently recognize this as a call to some_func.
// This Set is meant to keep track of such occurrence until the name graph
// becomes aware of those cases.
private boolean exposedToCallOrApply = false;
public Name(String qName, boolean isExtern) {
this.qName = qName;
this.isExtern = isExtern;
int lastDot = qName.lastIndexOf('.');
String name = (lastDot == -1) ? qName : qName.substring(lastDot + 1);
this.isExported = compiler.getCodingConvention().isExported(name);
this.type = compiler.getTypeIRegistry().getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
public JSType getType() {
return type;
}
public void setType(JSType type) {
this.type = type;
}
public List getDeclarations() {
return declarations;
}
public void addAssignmentDeclaration(Node node) {
declarations.add(new AssignmentDefinition(node, isExtern));
}
public void addFunctionDeclaration(Node node) {
declarations.add(new NamedFunctionDefinition(node, isExtern));
}
public void markExported() {
this.isExported = true;
}
public boolean isExported() {
return isExported;
}
/**
* @return {@code} True if this name has been dereferenced. Removing from
* the program or the module is no longer safe unless further analysis
* can prove otherwise.
*/
public boolean isAliased() {
return isAliased;
}
public void setAliased(boolean isAliased) {
this.isAliased = isAliased;
}
public String getQualifiedName() {
return qName;
}
/**
* @return The short property name of this object if it is a property, else
* {@code null}.
*/
public String getPropertyName() {
int lastIndexOfDot = qName.lastIndexOf('.');
if (lastIndexOfDot == -1) {
return null;
} else {
return qName.substring(lastIndexOfDot + 1);
}
}
public boolean exposedToCallOrApply() {
return exposedToCallOrApply;
}
public void markExposedToCallOrApply() {
exposedToCallOrApply = true;
}
@Override
public String toString() {
return qName + " : " + type;
}
@Override
public int hashCode() {
return qName.hashCode();
}
}
/**
* A reference site for a function or a variable reference. It can be a
* reference use or an assignment to that name.
*/
static class Reference {
// The node that references the name.
public final Node site;
public Reference(Node site) {
this.site = site;
}
/**
* Get accessor for retrieving the actual node corresponding to the
* reference.
*
* @return node representing the access/reference/call site
*/
public Node getSite() {
return site;
}
}
}