com.google.javascript.jscomp.Scope Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler Show documentation
Show all versions of closure-compiler 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 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 com.google.common.base.Preconditions;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticScope;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Scope contains information about a variable scope in JavaScript.
* Scopes can be nested, a scope points back to its parent scope.
* A Scope contains information about variables defined in that scope.
*
* @see NodeTraversal
*
*/
public class Scope implements StaticScope {
private final Map vars = new LinkedHashMap<>();
private final Scope parent;
protected final int depth;
protected final Node rootNode;
private Var arguments;
/**
* Creates a Scope given the parent Scope and the root node of the scope.
* @param parent The parent Scope. Cannot be null.
* @param rootNode Typically the FUNCTION node.
*/
Scope(Scope parent, Node rootNode) {
Preconditions.checkNotNull(parent);
Preconditions.checkNotNull(rootNode);
Preconditions.checkArgument(
rootNode != parent.rootNode,
"Root node: %s\nParent's root node: %s", rootNode, parent.rootNode);
this.parent = parent;
this.rootNode = rootNode;
this.depth = parent.depth + 1;
}
protected Scope(Node rootNode) {
Preconditions.checkNotNull(rootNode);
this.parent = null;
this.rootNode = rootNode;
this.depth = 0;
}
@Override
public String toString() {
return "Scope@" + rootNode;
}
static Scope createGlobalScope(Node rootNode) {
return new Scope(rootNode);
}
/** The depth of the scope. The global scope has depth 0. */
public int getDepth() {
return depth;
}
/**
* Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node.
*/
@Override
public Node getRootNode() {
return rootNode;
}
public Scope getParent() {
return parent;
}
Scope getGlobalScope() {
Scope result = this;
while (result.getParent() != null) {
result = result.getParent();
}
return result;
}
@Override
public StaticScope getParentScope() {
return parent;
}
/**
* Declares a variable.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param input the input in which this variable is defined.
*/
Var declare(String name, Node nameNode, CompilerInput input) {
Preconditions.checkState(name != null && !name.isEmpty());
// Make sure that it's declared only once
Preconditions.checkState(vars.get(name) == null);
Var var = new Var(name, nameNode, this, vars.size(), input);
vars.put(name, var);
return var;
}
/**
* Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope.
*/
void undeclare(Var var) {
Preconditions.checkState(var.scope == this);
Preconditions.checkState(vars.get(var.name) == var);
vars.remove(var.name);
}
@Override
public Var getSlot(String name) {
return getVar(name);
}
@Override
public Var getOwnSlot(String name) {
return vars.get(name);
}
/**
* Returns the variable, may be null
*/
public Var getVar(String name) {
Scope scope = this;
while (scope != null) {
Var var = scope.vars.get(name);
if (var != null) {
return var;
}
if ("arguments".equals(name) && NodeUtil.isVanillaFunction(scope.getRootNode())) {
return scope.getArgumentsVar();
}
// Recurse up the parent Scope
scope = scope.parent;
}
return null;
}
/**
* Get a unique VAR object to represents "arguments" within this scope
*/
public Var getArgumentsVar() {
if (arguments == null) {
arguments = Var.makeArgumentsVar(this);
}
return arguments;
}
/**
* Returns true if a variable is declared.
*/
public boolean isDeclared(String name, boolean recurse) {
Scope scope = this;
while (true) {
if (scope.vars.containsKey(name)) {
return true;
}
// In ES6, we create a separate "function parameter scope" above the function block scope to
// handle default parameters. Since nothing in the function block scope is allowed to shadow
// the variables in the function scope, we treat the two scopes as one in this method.
if (scope.isFunctionBlockScope() || (scope.parent != null && recurse)) {
scope = scope.parent;
continue;
}
return false;
}
}
/**
* Return an iterable over all of the variables declared in this scope
* (except the special 'arguments' variable).
*/
public Iterable getVarIterable() {
return vars.values();
}
public Iterable getAllSymbols() {
return Collections.unmodifiableCollection(vars.values());
}
/**
* Returns number of variables in this scope (excluding the special 'arguments' variable)
*/
public int getVarCount() {
return vars.size();
}
/**
* Returns whether this is the global scope.
*/
public boolean isGlobal() {
return parent == null;
}
/**
* Returns whether this is a local scope (i.e. not the global scope).
*/
public boolean isLocal() {
return parent != null;
}
public boolean isBlockScope() {
return NodeUtil.createsBlockScope(rootNode);
}
public boolean isFunctionBlockScope() {
return isBlockScope() && parent != null && parent.getRootNode().isFunction();
}
public boolean isFunctionScope() {
return getRootNode().isFunction();
}
public boolean isModuleScope() {
return getRootNode().isModuleBody();
}
/**
* If a var were declared in this scope, return the scope it would be hoisted to.
*
* For function scopes, we return back the scope itself, since even though there is no way
* to declare a var inside function parameters, it would make even less sense to say that
* such declarations would be "hoisted" somewhere else.
*/
public Scope getClosestHoistScope() {
Scope current = this;
while (current != null) {
if (current.isFunctionScope()
|| current.isFunctionBlockScope()
|| current.isGlobal()
|| current.isModuleScope()) {
return current;
}
current = current.parent;
}
return null;
}
}