Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.javascript.jscomp.NodeTraversal Maven / Gradle / Ivy
/*
* Copyright 2004 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.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.nullToEmpty;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* NodeTraversal allows an iteration through the nodes in the parse tree,
* and facilitates the optimizations on the parse tree.
*
*/
public class NodeTraversal {
private final AbstractCompiler compiler;
private final Callback callback;
/** Contains the current node*/
private Node curNode;
/** The change scope for the current node being visiteds */
private Node currentChangeScope;
/**
* Stack containing the Scopes that have been created. The Scope objects
* are lazily created; so the {@code scopeRoots} stack contains the
* Nodes for all Scopes that have not been created yet.
*/
private final Deque> scopes = new ArrayDeque<>();
/**
* A stack of scope roots. See #scopes.
*/
private final List scopeRoots = new ArrayList<>();
/**
* Stack containing the control flow graphs (CFG) that have been created. There are fewer CFGs
* than scopes, since block-level scopes are not valid CFG roots. The CFG objects are lazily
* populated: elements are simply the CFG root node until requested by {@link
* #getControlFlowGraph()}.
*/
private final Deque cfgs = new ArrayDeque<>();
/** The current source file name */
private String sourceName;
/** The current input */
private InputId inputId;
private CompilerInput compilerInput;
/** The scope creator */
private final ScopeCreator scopeCreator;
private final boolean useBlockScope;
/** Possible callback for scope entry and exist **/
private ScopedCallback scopeCallback;
/** Callback for passes that iterate over a list of change scope roots (FUNCTIONs and SCRIPTs) */
public interface ChangeScopeRootCallback {
void enterChangeScopeRoot(AbstractCompiler compiler, Node root);
}
/**
* Callback for tree-based traversals
*/
public interface Callback {
/**
* Visits a node in pre order (before visiting its children) and decides
* whether this node's children should be traversed. If children are
* traversed, they will be visited by
* {@link #visit(NodeTraversal, Node, Node)} in postorder.
* Implementations can have side effects (e.g. modifying the parse
* tree).
* @return whether the children of this node should be visited
*/
boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent);
/**
* Visits a node in postorder (after its children have been visited).
* A node is visited only if all its parents should be traversed
* ({@link #shouldTraverse(NodeTraversal, Node, Node)}).
* Implementations can have side effects (e.g. modifying the parse
* tree).
*/
void visit(NodeTraversal t, Node n, Node parent);
}
/**
* Callback that also knows about scope changes
*/
public interface ScopedCallback extends Callback {
/**
* Called immediately after entering a new scope. The new scope can
* be accessed through t.getScope()
*/
void enterScope(NodeTraversal t);
/**
* Called immediately before exiting a scope. The ending scope can
* be accessed through t.getScope()
*/
void exitScope(NodeTraversal t);
}
/**
* Abstract callback to visit all nodes in postorder.
*/
public abstract static class AbstractPostOrderCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
return true;
}
}
/** Abstract callback to visit all nodes in preorder. */
public abstract static class AbstractPreOrderCallback implements Callback {
@Override
public final void visit(NodeTraversal t, Node n, Node parent) {}
}
/** Abstract scoped callback to visit all nodes in postorder. */
public abstract static class AbstractScopedCallback implements ScopedCallback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {}
@Override
public void exitScope(NodeTraversal t) {}
}
/**
* Abstract callback to visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || !parent.isFunction() || n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't traverse into
* functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
return parent == null
|| NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback that knows when goog.modules (and in the future ES6 modules) are entered
* and exited. This includes both whole file modules and bundled modules.
*/
public abstract static class AbstractModuleCallback implements ScopedCallback {
/**
* Called immediately after entering a module.
*/
public abstract void enterModule(NodeTraversal t, Node scopeRoot);
/**
* Called immediately before exiting a module.
*/
public abstract void exitModule(NodeTraversal t, Node scopeRoot);
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public final void enterScope(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
if (NodeUtil.isModuleScopeRoot(scopeRoot)) {
enterModule(t, scopeRoot);
}
}
@Override
public final void exitScope(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
if (NodeUtil.isModuleScopeRoot(scopeRoot)) {
exitModule(t, scopeRoot);
}
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set nodeTypes, boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
return include == nodeTypes.contains(n.getToken());
}
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.scopeCreator = scopeCreator;
this.useBlockScope = scopeCreator.hasBlockScope();
}
private void throwUnexpectedException(Throwable unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (inputId != null) {
message =
unexpectedException.getMessage() + "\n"
+ formatNodeContext("Node", curNode)
+ (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
initTraversal(root);
curNode = root;
pushScope(root);
// null parent ensures that the shallow callbacks will traverse root
traverseBranch(root, null);
popScope();
} catch (Error | Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
void traverseRoots(Node externs, Node root) {
try {
Node scopeRoot = externs.getParent();
checkNotNull(scopeRoot);
initTraversal(scopeRoot);
curNode = scopeRoot;
pushScope(scopeRoot);
traverseBranch(externs, scopeRoot);
checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
popScope();
} catch (Error | Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
String sourceFileName = getBestSourceFileName(n);
if (sourceFileName == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceFileName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceFileName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope or module scopes. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, AbstractScope, ?> s) {
checkState(s.isGlobal() || s.isModuleScope(), s);
try {
initTraversal(root);
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
} catch (Error | Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(AbstractScope, ?> s) {
Node n = s.getRootNode();
initTraversal(n);
curNode = n;
Deque> parentScopes = new ArrayDeque<>();
AbstractScope, ?> temp = s.getParent();
while (temp != null) {
parentScopes.push(temp);
temp = temp.getParent();
}
while (!parentScopes.isEmpty()) {
pushScope(parentScopes.pop(), true);
}
if (n.isFunction()) {
pushScope(s);
Node args = n.getSecondChild();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else if (n.isNormalBlock()) {
pushScope(s);
// traverseBranch is not called here to avoid re-creating the block scope.
traverseChildren(n);
popScope();
} else if (NodeUtil.isAnyFor(n)) {
// ES6 Creates a separate for scope and for-body scope
checkState(scopeCreator.hasBlockScope());
pushScope(s);
Node forAssignmentParam = n.getFirstChild();
Node forIterableParam = forAssignmentParam.getNext();
Node forBodyScope = forIterableParam.getNext();
traverseBranch(forAssignmentParam, n);
traverseBranch(forIterableParam, n);
traverseBranch(forBodyScope, n);
popScope();
} else if (n.isSwitch()) {
// ES6 creates a separate switch scope with cases
checkState(scopeCreator.hasBlockScope());
pushScope(s);
traverseChildren(n);
popScope();
} else {
checkState(s.isGlobal() || s.isModuleScope(), "Expected global or module scope. Got:", s);
traverseWithScope(n, s);
}
}
private void traverseScopeRoot(Node scopeRoot) {
try {
initTraversal(scopeRoot);
curNode = scopeRoot;
initScopeRoots(scopeRoot.getParent());
traverseBranch(scopeRoot, scopeRoot.getParent());
} catch (Error | Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
/**
* Traverses *just* the contents of provided scope nodes (and optionally scopes nested within
* them) but will fall back on traversing the entire AST from root if a null scope nodes list is
* provided.
* @param root If scopeNodes is null, this method will just traverse 'root' instead. If scopeNodes
* is not null, this parameter is ignored.
*/
public static void traverseEs6ScopeRoots(
AbstractCompiler compiler,
@Nullable Node root,
@Nullable List scopeNodes,
final Callback cb,
final boolean traverseNested) {
traverseEs6ScopeRoots(compiler, root, scopeNodes, cb, null, traverseNested);
}
/**
* Traverses *just* the contents of provided scope nodes (and optionally scopes nested within
* them) but will fall back on traversing the entire AST from root if a null scope nodes list is
* provided. Also allows for a callback to notify when starting on one of the provided scope
* nodes.
* @param root If scopeNodes is null, this method will just traverse 'root' instead. If scopeNodes
* is not null, this parameter is ignored.
*/
public static void traverseEs6ScopeRoots(
AbstractCompiler compiler,
@Nullable Node root,
@Nullable List scopeNodes,
final Callback cb,
@Nullable final ChangeScopeRootCallback changeCallback,
final boolean traverseNested) {
if (scopeNodes == null) {
NodeTraversal.traverseEs6(compiler, root, cb);
} else {
MemoizedScopeCreator scopeCreator =
new MemoizedScopeCreator(new Es6SyntacticScopeCreator(compiler));
for (final Node scopeNode : scopeNodes) {
traverseSingleEs6ScopeRoot(
compiler, cb, changeCallback, traverseNested, scopeCreator, scopeNode);
}
}
}
private static void traverseSingleEs6ScopeRoot(
AbstractCompiler compiler,
final Callback cb,
@Nullable ChangeScopeRootCallback changeCallback,
final boolean traverseNested,
MemoizedScopeCreator scopeCreator,
final Node scopeNode) {
if (changeCallback != null) {
changeCallback.enterChangeScopeRoot(compiler, scopeNode);
}
ScopedCallback scb = new ScopedCallback() {
boolean insideScopeNode = false;
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (scopeNode == n) {
insideScopeNode = true;
}
return (traverseNested || scopeNode == n || !NodeUtil.isChangeScopeRoot(n))
&& cb.shouldTraverse(t, n, parent);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (scopeNode == n) {
insideScopeNode = false;
}
cb.visit(t, n, parent);
}
@Override
public void enterScope(NodeTraversal t) {
if (insideScopeNode && cb instanceof ScopedCallback) {
((ScopedCallback) cb).enterScope(t);
}
}
@Override
public void exitScope(NodeTraversal t) {
if (insideScopeNode && cb instanceof ScopedCallback) {
((ScopedCallback) cb).exitScope(t);
}
}
};
NodeTraversal.traverseEs6ScopeRoot(compiler, scopeNode, scb, scopeCreator);
}
/**
* Traverse a function out-of-band of normal traversal.
*
* @param node The function node.
* @param scope The scope the function is contained in. Does not fire enter/exit
* callback events for this scope.
*/
public void traverseFunctionOutOfBand(Node node, AbstractScope, ?> scope) {
checkNotNull(scope);
checkState(node.isFunction(), node);
checkNotNull(scope.getRootNode());
initTraversal(node);
curNode = node.getParent();
pushScope(scope, true /* quietly */);
traverseBranch(node, curNode);
popScope(true /* quietly */);
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
void traverseInnerNode(Node node, Node parent, AbstractScope, ?> refinedScope) {
checkNotNull(parent);
initTraversal(node);
if (refinedScope != null && getAbstractScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
public AbstractCompiler getCompiler() {
return compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >= 0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current char number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getCharno() {
Node cur = curNode;
while (cur != null) {
int line = cur.getCharno();
if (line >= 0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
*/
public CompilerInput getInput() {
if (compilerInput == null && inputId != null) {
compilerInput = compiler.getInput(inputId);
}
return compilerInput;
}
/**
* Gets the current input module.
*/
public JSModule getModule() {
CompilerInput input = getInput();
return input == null ? null : input.getModule();
}
/** Returns the node currently being traversed. */
public Node getCurrentNode() {
return curNode;
}
/**
* Traversal for passes that work only on changed functions.
* Suppose a loopable pass P1 uses this traversal.
* Then, if a function doesn't change between two runs of P1, it won't look at
* the function the second time.
* (We're assuming that P1 runs to a fixpoint, o/w we may miss optimizations.)
*
* Most changes are reported with calls to Compiler.reportCodeChange(), which
* doesn't know which scope changed. We keep track of the current scope by
* calling Compiler.setScope inside pushScope and popScope.
* The automatic tracking can be wrong in rare cases when a pass changes scope
* w/out causing a call to pushScope or popScope.
*
* Passes that do cross-scope modifications call
* Compiler.reportChangeToEnclosingScope(Node n).
*/
public static void traverseChangedFunctions(
final AbstractCompiler compiler, final ChangeScopeRootCallback callback) {
final Node jsRoot = compiler.getJsRoot();
NodeTraversal.traverseEs6(compiler, jsRoot,
new AbstractPreOrderCallback() {
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isChangeScopeRoot(n) && compiler.hasScopeChanged(n)) {
callback.enterChangeScopeRoot(compiler, n);
}
return true;
}
});
}
/**
* Traverses using the ES6SyntacticScopeCreator
*/
// TODO (stephshi): rename to "traverse" when the old traverse method is no longer used
public static void traverseEs6(AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb, new Es6SyntacticScopeCreator(compiler));
t.traverse(root);
}
/** Traverses from a particular scope node using the ES6SyntacticScopeCreator */
private static void traverseEs6ScopeRoot(
AbstractCompiler compiler, Node scopeNode, Callback cb, MemoizedScopeCreator scopeCreator) {
NodeTraversal t = new NodeTraversal(compiler, cb, scopeCreator);
t.traverseScopeRoot(scopeNode);
}
/**
* @deprecated Use the ES6SyntacticScopeCreator instead.
*/
@Deprecated
public static void traverseTyped(AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb, SyntacticScopeCreator.makeTyped(compiler));
t.traverse(root);
}
public static void traverseRootsEs6(
AbstractCompiler compiler, Callback cb, Node externs, Node root) {
NodeTraversal t = new NodeTraversal(compiler, cb, new Es6SyntacticScopeCreator(compiler));
t.traverseRoots(externs, root);
}
/**
* @deprecated Use the ES6SyntacticScopeCreator instead.
*/
@Deprecated
public static void traverseRootsTyped(
AbstractCompiler compiler, Callback cb, Node externs, Node root) {
NodeTraversal t = new NodeTraversal(compiler, cb, SyntacticScopeCreator.makeTyped(compiler));
t.traverseRoots(externs, root);
}
private void handleScript(Node n, Node parent) {
if (Thread.interrupted()) {
throw new RuntimeException(new InterruptedException());
}
setChangeScope(n);
setInputId(n.getInputId(), getSourceName(n));
curNode = n;
if (callback.shouldTraverse(this, n, parent)) {
traverseChildren(n);
curNode = n;
callback.visit(this, n, parent);
}
setChangeScope(null);
}
private void handleFunction(Node n, Node parent) {
Node changeScope = this.currentChangeScope;
setChangeScope(n);
curNode = n;
if (callback.shouldTraverse(this, n, parent)) {
traverseFunction(n, parent);
curNode = n;
callback.visit(this, n, parent);
}
setChangeScope(changeScope);
}
/**
* Traverses a branch.
*/
private void traverseBranch(Node n, Node parent) {
Token type = n.getToken();
if (type == Token.SCRIPT) {
handleScript(n, parent);
return;
} else if (type == Token.FUNCTION) {
handleFunction(n, parent);
return;
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) {
return;
}
if (type == Token.CLASS) {
traverseClass(n);
} else if (type == Token.MODULE_BODY) {
traverseModule(n);
} else if (useBlockScope && NodeUtil.createsBlockScope(n)) {
traverseBlockScope(n);
} else {
traverseChildren(n);
}
curNode = n;
callback.visit(this, n, parent);
}
/** Traverses a function. */
private void traverseFunction(Node n, Node parent) {
final Node fnName = n.getFirstChild();
boolean isFunctionDeclaration = parent != null && NodeUtil.isFunctionDeclaration(n);
if (isFunctionDeclaration) {
// Function declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (!isFunctionDeclaration) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
// ES6 "arrow" function may not have a block as a body.
traverseBranch(body, n);
popScope();
}
/** Traverses a class. */
private void traverseClass(Node n) {
final Node className = n.getFirstChild();
boolean isClassExpression = NodeUtil.isClassExpression(n);
if (!isClassExpression) {
// Class declarations are in the scope containing the declaration.
traverseBranch(className, n);
}
curNode = n;
pushScope(n);
if (isClassExpression) {
// Class expression names are only accessible within the function
// scope.
traverseBranch(className, n);
}
final Node extendsClause = n.getSecondChild();
final Node body = extendsClause.getNext();
// Extends
traverseBranch(extendsClause, n);
// Body
traverseBranch(body, n);
popScope();
}
private void traverseChildren(Node n) {
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
}
/** Traverses a module. */
private void traverseModule(Node n) {
pushScope(n);
traverseChildren(n);
popScope();
}
/** Traverses a non-function block. */
private void traverseBlockScope(Node n) {
pushScope(n);
traverseChildren(n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. When possible, prefer
* this method over NodeUtil.getEnclosingFunction() because this in general looks at less nodes.
*/
public Node getEnclosingFunction() {
Node root = getCfgRoot();
return root.isFunction() ? root : null;
}
/** Sets the given node as the current scope and pushes the relevant frames on the CFG stacks. */
private void recordScopeRoot(Node node) {
if (NodeUtil.isValidCfgRoot(node)) {
cfgs.push(node);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
checkNotNull(curNode);
checkNotNull(node);
scopeRoots.add(node);
recordScopeRoot(node);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(AbstractScope, ?> s) {
pushScope(s, false);
}
/**
* Creates a new scope (e.g. when entering a function).
* @param quietly Don't fire an enterScope callback.
*/
private void pushScope(AbstractScope, ?> s, boolean quietly) {
checkNotNull(curNode);
scopes.push(s);
recordScopeRoot(s.getRootNode());
if (!quietly && scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
private void popScope() {
popScope(false);
}
/**
* Pops back to the previous scope (e.g. when leaving a function).
* @param quietly Don't fire the exitScope callback.
*/
private void popScope(boolean quietly) {
if (!quietly && scopeCallback != null) {
scopeCallback.exitScope(this);
}
Node scopeRoot;
int roots = scopeRoots.size();
if (roots > 0) {
scopeRoot = scopeRoots.remove(roots - 1);
} else {
scopeRoot = scopes.pop().getRootNode();
}
if (NodeUtil.isValidCfgRoot(scopeRoot)) {
cfgs.pop();
}
}
/** Gets the current scope. */
public AbstractScope, ?> getAbstractScope() {
AbstractScope, ?> scope = scopes.peek();
for (int i = 0; i < scopeRoots.size(); i++) {
scope = scopeCreator.createScope(scopeRoots.get(i), scope);
scopes.push(scope);
}
scopeRoots.clear();
return scope;
}
private AbstractScope, ?> instantiateScopes(int count) {
checkArgument(count <= scopeRoots.size());
AbstractScope, ?> scope = scopes.peek();
for (int i = 0; i < count; i++) {
scope = scopeCreator.createScope(scopeRoots.get(i), scope);
scopes.push(scope);
}
scopeRoots.subList(0, count).clear();
return scope;
}
public boolean isHoistScope() {
return isHoistScopeRootNode(getScopeRoot());
}
public Node getClosestHoistScopeRoot() {
int roots = scopeRoots.size();
for (int i = roots; i > 0; i--) {
Node rootNode = scopeRoots.get(i - 1);
if (isHoistScopeRootNode(rootNode)) {
return rootNode;
}
}
return scopes.peek().getClosestHoistScope().getRootNode();
}
public AbstractScope, ?> getClosestHoistScope() {
for (int i = scopeRoots.size(); i > 0; i--) {
if (isHoistScopeRootNode(scopeRoots.get(i - 1))) {
return instantiateScopes(i);
}
}
// Note: this cast is required to make j2cl output work.
return (AbstractScope, ?>) scopes.peek().getClosestHoistScope();
}
private static boolean isHoistScopeRootNode(Node n) {
switch (n.getToken()) {
case FUNCTION:
case MODULE_BODY:
case ROOT:
case SCRIPT:
return true;
default:
return NodeUtil.isFunctionBlock(n);
}
}
public Scope getScope() {
return getAbstractScope().untyped();
}
public TypedScope getTypedScope() {
return getAbstractScope().typed();
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph getControlFlowGraph() {
ControlFlowGraph result;
Object o = cfgs.peek();
if (o instanceof Node) {
Node cfgRoot = (Node) o;
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
cfa.process(null, cfgRoot);
result = cfa.getCfg();
cfgs.pop();
cfgs.push(result);
} else {
result = (ControlFlowGraph) o;
}
return result;
}
/** Returns the current scope's root. */
public Node getScopeRoot() {
int roots = scopeRoots.size();
if (roots > 0) {
return scopeRoots.get(roots - 1);
} else {
AbstractScope, ?> s = scopes.peek();
return s != null ? s.getRootNode() : null;
}
}
private Node getCfgRoot() {
Node result;
Object o = cfgs.peek();
if (o instanceof Node) {
result = (Node) o;
} else {
result = ((ControlFlowGraph) o).getEntry().getValue();
}
return result;
}
public ScopeCreator getScopeCreator() {
return scopeCreator;
}
/**
* Determines whether the traversal is currently in the global scope. Note that this returns false
* in a global block scope.
*/
public boolean inGlobalScope() {
return getScopeDepth() == 0;
}
/** Determines whether the traversal is currently in the scope of the block of a function. */
public boolean inFunctionBlockScope() {
Node scopeRoot = getScopeRoot();
return scopeRoot.isNormalBlock() && scopeRoot.getParent().isFunction();
}
/**
* Determines whether the hoist scope of the current traversal is global.
*/
public boolean inGlobalHoistScope() {
Node cfgRoot = getCfgRoot();
checkState(
cfgRoot.isScript()
|| cfgRoot.isRoot()
|| cfgRoot.isNormalBlock()
|| cfgRoot.isFunction()
|| cfgRoot.isModuleBody(),
cfgRoot);
return cfgRoot.isScript() || cfgRoot.isRoot() || cfgRoot.isNormalBlock();
}
/**
* Determines whether the traversal is currently in the global scope. Note that this returns false
* in a global block scope.
*/
public boolean inModuleScope() {
return NodeUtil.isModuleScopeRoot(getScopeRoot());
}
/**
* Determines whether the hoist scope of the current traversal is global.
*/
public boolean inModuleHoistScope() {
Node moduleRoot = getCfgRoot();
if (moduleRoot.isFunction()) {
// For wrapped modules, the function block is the module scope root.
moduleRoot = moduleRoot.getLastChild();
}
return NodeUtil.isModuleScopeRoot(moduleRoot);
}
int getScopeDepth() {
int sum = scopes.size() + scopeRoots.size();
checkState(sum > 0);
return sum - 1; // Use 0-based scope depth to be consistent within the compiler
}
/** Reports a diagnostic (error or warning) */
public void report(Node n, DiagnosticType diagnosticType,
String... arguments) {
JSError error = JSError.make(n, diagnosticType, arguments);
compiler.report(error);
}
public void reportCodeChange() {
Node changeScope = this.currentChangeScope;
checkNotNull(changeScope);
checkState(NodeUtil.isChangeScopeRoot(changeScope), changeScope);
compiler.reportChangeToChangeScope(changeScope);
}
public void reportCodeChange(Node n) {
compiler.reportChangeToEnclosingScope(n);
}
private static String getSourceName(Node n) {
String name = n.getSourceFileName();
return nullToEmpty(name);
}
/**
* @param n The current change scope, should be null when the traversal is complete.
*/
private void setChangeScope(Node n) {
this.currentChangeScope = n;
}
private Node getEnclosingScript(Node n) {
while (n != null && !n.isScript()) {
n = n.getParent();
}
return n;
}
private void initTraversal(Node traversalRoot) {
if (Thread.interrupted()) {
throw new RuntimeException(new InterruptedException());
}
Node changeScope = NodeUtil.getEnclosingChangeScopeRoot(traversalRoot);
setChangeScope(changeScope);
Node script = getEnclosingScript(changeScope);
if (script != null) {
setInputId(script.getInputId(), script.getSourceFileName());
} else {
setInputId(null, "");
}
}
/**
* Prefills the scopeRoots stack up to a given spot in the AST. Allows for starting traversal at
* any spot while still having correct scope state.
*/
private void initScopeRoots(Node n) {
Deque queuedScopeRoots = new ArrayDeque<>();
while (n != null) {
if (isScopeRoot(n)) {
queuedScopeRoots.addFirst(n);
}
n = n.getParent();
}
for (Node queuedScopeRoot : queuedScopeRoots) {
pushScope(queuedScopeRoot);
}
}
private boolean isScopeRoot(Node n) {
if (n.isRoot() && n.getParent() == null) {
return true;
} else if (n.isFunction()) {
return true;
} else if (useBlockScope && NodeUtil.createsBlockScope(n)) {
return true;
}
return false;
}
private void setInputId(InputId id, String sourceName) {
inputId = id;
this.sourceName = sourceName;
compilerInput = null;
}
InputId getInputId() {
return inputId;
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, CheckLevel level, DiagnosticType type,
String... arguments) {
return JSError.make(n, level, type, arguments);
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, DiagnosticType type, String... arguments) {
return JSError.make(n, type, arguments);
}
private String getBestSourceFileName(Node n) {
return n == null ? sourceName : n.getSourceFileName();
}
}