final class NameAnalyzer extends java.lang.Object implements CompilerPass
a
) or
qualified (e.g. a.b.c
), and the dependencies between them, then
removes code associated with unreferenced names. It starts by assuming that
only externally accessible names (e.g. window
) are referenced,
then iteratively marks additional names as referenced (e.g. Foo
in window['foo'] = new Foo();
). This makes it possible to
eliminate code containing circular references.
Qualified names can be defined using dotted or object literal syntax
(a.b.c = x;
or a.b = {c: x};
, respectively).
Removal of prototype classes is currently all or nothing. In other words, prototype properties and methods are never individually removed.
Optionally generates pretty HTML output of data so that it is easy to analyze dependencies.
Only operates on names defined in the global scope, but it would be easy
to extend the pass to names defined in local scopes.
TODO(nicksantos): In the initial implementation of this pass, it was
important to understand namespaced names (e.g., that a.b is distinct from
a.b.c). Now that this pass comes after CollapseProperties, this is no longer
necessary. For now, I've changed so that referenceParentNames
creates a two-way reference between a.b and a.b.c, so that they're
effectively the same name. When someone has the time, we should completely
rip out all the logic that understands namespaces.
Modifier and Type | Class and Description |
---|---|
private static class |
NameAnalyzer.AliasSet
All the aliases in a program form a graph, where each global name is
a node in the graph, and two names are connected if one directly aliases
the other.
|
private class |
NameAnalyzer.ClassDefiningFunctionNode
Class for nodes that are function calls that may change a function's
prototype
|
private class |
NameAnalyzer.FindDeclarationsAndSetters
Identifies all declarations of global names and setter statements
affecting global symbols (assignments to global names).
|
private class |
NameAnalyzer.FindDependencyScopes
Identifies all dependency scopes.
|
private class |
NameAnalyzer.FindReferences
Identifies all references between global names.
|
private class |
NameAnalyzer.HoistVariableAndFunctionDeclarations
Create JsName objects for variable and function declarations in
the global scope before computing name references.
|
private class |
NameAnalyzer.InstanceOfCheckNode
Class for nodes that check instanceof
|
private static class |
NameAnalyzer.JsName
Struct to hold information about a fully qualified JS name
|
private class |
NameAnalyzer.JsNameRefNode
Class for nodes that reference a fully-qualified JS name.
|
private static class |
NameAnalyzer.NameInformation
Class to hold information that can be determined from a node tree about a
given name
|
private class |
NameAnalyzer.ProcessExternals
Walk through externs and mark nodes as externally declared if declared
|
private class |
NameAnalyzer.PrototypeSetNode
Class for nodes that set prototype properties or methods.
|
(package private) static interface |
NameAnalyzer.RefNode
Interface to get information about and remove unreferenced names.
|
private static class |
NameAnalyzer.RefType
Relationship between the two names.
|
private class |
NameAnalyzer.RemoveListener |
private static class |
NameAnalyzer.SpecialReferenceNode
Base class for special reference nodes.
|
private static class |
NameAnalyzer.TriState
Enum for saying a value can be true, false, or either (cleaner than using a
Boolean with null)
|
Modifier and Type | Field and Description |
---|---|
private java.util.Map<java.lang.String,NameAnalyzer.AliasSet> |
aliases
When multiple names in the global scope point to the same object, we
call them aliases.
|
private java.util.Map<java.lang.String,NameAnalyzer.JsName> |
allNames
Map of all JS names found
|
private AstChangeProxy |
changeProxy
Ast change helper
|
private AbstractCompiler |
compiler
Reference to the JS compiler
|
(package private) static java.util.Set<java.lang.String> |
DEFAULT_GLOBAL_NAMES
All of these refer to global scope.
|
private java.util.Set<java.lang.String> |
externalNames
Names that are externally defined
|
private static java.lang.String |
FUNCTION
Function class name
|
private java.util.Set<java.lang.String> |
globalNames
Names that refer to the global scope
|
private static com.google.common.base.Predicate<Node> |
NON_LOCAL_RESULT_PREDICATE |
private static java.lang.String |
PROTOTYPE_SUBSTRING
Used to parse prototype names
|
private static int |
PROTOTYPE_SUBSTRING_LEN |
private static int |
PROTOTYPE_SUFFIX_LEN |
private LinkedDirectedGraph<NameAnalyzer.JsName,NameAnalyzer.RefType> |
referenceGraph
Reference dependency graph
|
private java.util.List<NameAnalyzer.RefNode> |
refNodes
Name declarations or assignments, in post-order traversal order
|
private boolean |
removeUnreferenced
Whether to remove unreferenced variables in main pass
|
(package private) static DiagnosticType |
REPORT_PATH_IO_ERROR |
private java.lang.String |
reportPath
The path of the report file
|
private com.google.common.collect.ListMultimap<Node,NameAnalyzer.NameInformation> |
scopes
Map of name scopes - all children of the Node key have a dependency on the
name value.
|
private static java.lang.String |
WINDOW
Window root
|
Constructor and Description |
---|
NameAnalyzer(AbstractCompiler compiler,
boolean removeUnreferenced,
java.lang.String reportPath)
Creates a name analyzer, with option to remove unreferenced variables when
calling process().
|
Modifier and Type | Method and Description |
---|---|
private static void |
appendListItem(java.lang.StringBuilder sb,
java.lang.String text) |
private void |
calculateReferences()
Propagate "referenced" property down the graph.
|
private static Node |
collapseReplacements(java.util.List<Node> replacements)
Merge a list of nodes into a single expression.
|
private int |
countOf(NameAnalyzer.TriState isClass,
NameAnalyzer.TriState referenced)
Gets the count of nodes matching the criteria
|
(package private) static void |
createEmptyReport(AbstractCompiler compiler,
java.lang.String reportPath) |
private void |
createName(java.lang.String name)
Creates a
NameAnalyzer.JsName for the given name if it doesn't already
exist. |
private NameAnalyzer.NameInformation |
createNameInformation(NodeTraversal t,
Node n)
Creates name information for the current node during a traversal.
|
private NameAnalyzer.NameInformation |
createNameInformation(java.lang.String name,
Scope scope,
Node rootNameNode)
Creates name information for a particular qualified name that occurs in a
particular scope.
|
private java.util.List<NameAnalyzer.NameInformation> |
getDependencyScope(Node n)
Gets the nearest enclosing dependency scope, or null if there isn't one.
|
private java.util.List<NameAnalyzer.NameInformation> |
getEnclosingFunctionDependencyScope(NodeTraversal t)
Get dependency scope defined by the enclosing function, or null.
|
private DiGraph.DiGraphNode<NameAnalyzer.JsName,NameAnalyzer.RefType> |
getGraphNode(NameAnalyzer.JsName name) |
(package private) java.lang.String |
getHtmlReport()
Generates an HTML report
|
private NameAnalyzer.JsName |
getName(java.lang.String name,
boolean canCreate)
Looks up a
NameAnalyzer.JsName by name, optionally creating one if it doesn't
already exist. |
private static java.util.List<Node> |
getRhsSubexpressions(Node n)
Extract a list of subexpressions that act as right hand sides.
|
private java.util.List<Node> |
getSideEffectNodes(Node n)
Extract a list of replacement nodes to use.
|
private static boolean |
isAnalyzableObjectDefinePropertiesDefinition(Node n)
Check if
n is an Object.defineProperties definition
that is static enough for this pass to understand and remove. |
private boolean |
isExternallyReferenceable(Scope scope,
java.lang.String name)
Checks whether a name can be referenced outside of the compiled code.
|
private static java.lang.String |
nameAnchor(java.lang.String name) |
private static java.lang.String |
nameLink(java.lang.String name) |
void |
process(Node externs,
Node root)
Process the JS with root node root.
|
private void |
propagateReference(NameAnalyzer.JsName... names) |
private void |
recordAlias(java.lang.String fromName,
java.lang.String toName)
Records an alias of one name to another name.
|
private void |
recordReference(DiGraph.DiGraphNode<NameAnalyzer.JsName,NameAnalyzer.RefType> from,
DiGraph.DiGraphNode<NameAnalyzer.JsName,NameAnalyzer.RefType> to,
NameAnalyzer.RefType depType)
Records a reference from one name to another name.
|
private void |
recordReference(java.lang.String fromName,
java.lang.String toName,
NameAnalyzer.RefType depType)
Records a reference from one name to another name.
|
private void |
referenceAliases()
The NameAnalyzer algorithm works best when all objects have a canonical
name in the global scope.
|
private void |
referenceParentNames()
Adds mutual references between all known global names and their parent
names.
|
(package private) void |
removeUnreferenced()
Removes all unreferenced variables.
|
private void |
replaceTopLevelExpressionWithRhs(Node parent,
Node n)
Simplify a toplevel expression, while preserving program
behavior.
|
private void |
replaceWithRhs(Node parent,
Node n)
Replace n with a simpler expression, while preserving program
behavior.
|
private static boolean |
valueConsumedByParent(Node n,
Node parent)
Determine if the parent reads the value of a child expression
directly.
|
private final AbstractCompiler compiler
private final java.util.Map<java.lang.String,NameAnalyzer.JsName> allNames
private LinkedDirectedGraph<NameAnalyzer.JsName,NameAnalyzer.RefType> referenceGraph
private final com.google.common.collect.ListMultimap<Node,NameAnalyzer.NameInformation> scopes
private static final java.lang.String PROTOTYPE_SUBSTRING
private static final int PROTOTYPE_SUBSTRING_LEN
private static final int PROTOTYPE_SUFFIX_LEN
private static final java.lang.String WINDOW
private static final java.lang.String FUNCTION
static final java.util.Set<java.lang.String> DEFAULT_GLOBAL_NAMES
private final boolean removeUnreferenced
private final java.lang.String reportPath
private final java.util.Set<java.lang.String> globalNames
private final AstChangeProxy changeProxy
private final java.util.Set<java.lang.String> externalNames
private final java.util.List<NameAnalyzer.RefNode> refNodes
private final java.util.Map<java.lang.String,NameAnalyzer.AliasSet> aliases
static final DiagnosticType REPORT_PATH_IO_ERROR
private static final com.google.common.base.Predicate<Node> NON_LOCAL_RESULT_PREDICATE
NameAnalyzer(AbstractCompiler compiler, boolean removeUnreferenced, java.lang.String reportPath)
compiler
- The AbstractCompilerremoveUnreferenced
- If true, remove unreferenced variables during
process()static void createEmptyReport(AbstractCompiler compiler, java.lang.String reportPath)
public void process(Node externs, Node root)
CompilerPass
process
in interface CompilerPass
externs
- Top of external JS treeroot
- Top of JS treeprivate void recordAlias(java.lang.String fromName, java.lang.String toName)
private void recordReference(java.lang.String fromName, java.lang.String toName, NameAnalyzer.RefType depType)
private void recordReference(DiGraph.DiGraphNode<NameAnalyzer.JsName,NameAnalyzer.RefType> from, DiGraph.DiGraphNode<NameAnalyzer.JsName,NameAnalyzer.RefType> to, NameAnalyzer.RefType depType)
void removeUnreferenced()
java.lang.String getHtmlReport()
private static void appendListItem(java.lang.StringBuilder sb, java.lang.String text)
private static java.lang.String nameLink(java.lang.String name)
private static java.lang.String nameAnchor(java.lang.String name)
private NameAnalyzer.JsName getName(java.lang.String name, boolean canCreate)
NameAnalyzer.JsName
by name, optionally creating one if it doesn't
already exist.name
- A fully qualified namecanCreate
- Whether to create the object if necessaryJsName
object, or null if one can't be found and
can't be created.private void createName(java.lang.String name)
NameAnalyzer.JsName
for the given name if it doesn't already
exist.name
- A fully qualified nameprivate void referenceAliases()
var a = {};
var b = a;
a.foo = 3;
alert(b.foo);
then a.foo and b.foo are the same name, even though NameAnalyzer doesn't
represent them as such.
To handle this case, we look at all the aliases in the program.
If descendant properties of that alias are assigned, then we create a
directional reference from the original name to the alias. For example,
in this case, the assign to a.foo
triggers a reference from
b
to a
, but NOT from a to b.
Similarly, "instanceof" checks do not prevent the removal
of a unaliased name but an instanceof check on an alias can only be removed
if the other aliases are also removed, so we add a connection here.private DiGraph.DiGraphNode<NameAnalyzer.JsName,NameAnalyzer.RefType> getGraphNode(NameAnalyzer.JsName name)
private void referenceParentNames()
a.b.c
and a.b
).private NameAnalyzer.NameInformation createNameInformation(NodeTraversal t, Node n)
t
- The node traversaln
- The current nodeprivate NameAnalyzer.NameInformation createNameInformation(java.lang.String name, Scope scope, Node rootNameNode)
name
- A qualified name (e.g. "x" or "a.b.c")scope
- The scope in which name
occursrootNameNode
- The NAME node for the first token of name
private boolean isExternallyReferenceable(Scope scope, java.lang.String name)
scope
- The current variable scopename
- The nameprivate java.util.List<NameAnalyzer.NameInformation> getDependencyScope(Node n)
private java.util.List<NameAnalyzer.NameInformation> getEnclosingFunctionDependencyScope(NodeTraversal t)
private void calculateReferences()
private void propagateReference(NameAnalyzer.JsName... names)
private int countOf(NameAnalyzer.TriState isClass, NameAnalyzer.TriState referenced)
isClass
- Whether the node is a classreferenced
- Whether the node is referencedprivate java.util.List<Node> getSideEffectNodes(Node n)
private void replaceWithRhs(Node parent, Node n)
private void replaceTopLevelExpressionWithRhs(Node parent, Node n)
private static boolean valueConsumedByParent(Node n, Node parent)
private static Node collapseReplacements(java.util.List<Node> replacements)
private static java.util.List<Node> getRhsSubexpressions(Node n)
private static boolean isAnalyzableObjectDefinePropertiesDefinition(Node n)
n
is an Object.defineProperties definition
that is static enough for this pass to understand and remove.