com.google.javascript.jscomp.DefinitionsRemover 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 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.ImmutableSet;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
/**
* Models an assignment that defines a variable and the removal of it.
*
*/
class DefinitionsRemover {
/**
* This logic must match {@link isDefinitionNode}.
*
* @return an {@link Definition} object if the node contains a definition or {@code null}
* otherwise.
*/
static Definition getDefinition(Node n, boolean isExtern) {
Node parent = n.getParent();
if (parent == null) {
return null;
}
if (NodeUtil.isVarDeclaration(n) && (isExtern || n.hasChildren())) {
return new VarDefinition(n, isExtern);
} else if (parent.isFunction() && parent.getFirstChild() == n) {
if (!NodeUtil.isFunctionExpression(parent)) {
return new NamedFunctionDefinition(parent, isExtern);
} else if (!n.getString().isEmpty()) {
return new FunctionExpressionDefinition(parent, isExtern);
}
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return new AssignmentDefinition(parent, isExtern);
} else if (NodeUtil.isObjectLitKey(n)) {
return new ObjectLiteralPropertyDefinition(parent, n, n.getFirstChild(), isExtern);
} else if (parent.isParamList()) {
Node function = parent.getParent();
return new FunctionArgumentDefinition(function, n, isExtern);
} else if (parent.getToken() == Token.COLON && parent.getFirstChild() == n && isExtern) {
Node grandparent = parent.getParent();
Preconditions.checkState(grandparent.getToken() == Token.LB);
Preconditions.checkState(grandparent.getParent().getToken() == Token.LC);
return new RecordTypePropertyDefinition(n);
} else if (isExtern && n.isGetProp() && parent.isExprResult() && n.isQualifiedName()) {
return new ExternalNameOnlyDefinition(n);
}
return null;
}
/**
* This logic must match {@link getDefinition}.
*
* @return Whether a definition object can be created.
*/
static boolean isDefinitionNode(Node n) {
Node parent = n.getParent();
if (parent == null) {
return false;
}
if (NodeUtil.isVarDeclaration(n) && (n.isFromExterns() || n.hasChildren())) {
return true;
} else if (parent.isFunction() && parent.getFirstChild() == n) {
if (!NodeUtil.isFunctionExpression(parent)) {
return true;
} else if (!n.getString().isEmpty()) {
return true;
}
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return true;
} else if (NodeUtil.isObjectLitKey(n)) {
return true;
} else if (parent.isParamList()) {
return true;
} else if (parent.getToken() == Token.COLON
&& parent.getFirstChild() == n
&& n.isFromExterns()) {
Node grandparent = parent.getParent();
Preconditions.checkState(grandparent.getToken() == Token.LB);
Preconditions.checkState(grandparent.getParent().getToken() == Token.LC);
return true;
} else if (n.isFromExterns() && parent.isExprResult() && n.isGetProp() && n.isQualifiedName()) {
return true;
}
return false;
}
abstract static class Definition {
private final boolean isExtern;
Definition(boolean isExtern) {
this.isExtern = isExtern;
}
/**
* Removes this definition from the AST if it is not an extern.
*
* This method should not be called on a definition for which isExtern()
* is true.
*/
public void remove() {
if (!isExtern) {
performRemove();
} else {
throw new IllegalStateException("Attempt to remove() an extern" +
" definition.");
}
}
/**
* Subclasses should override to remove the definition from the AST.
*/
protected abstract void performRemove();
/**
* Variable or property name represented by this definition.
* For example, in the case of assignments this method would
* return the NAME, GETPROP or GETELEM expression that acts as the
* assignment left hand side.
*
* @return the L-Value associated with this definition.
* The node's type is always NAME, GETPROP or GETELEM.
*/
public abstract Node getLValue();
/**
* Value expression that acts as the right hand side of the
* definition statement.
*/
public abstract Node getRValue();
/**
* Returns true if the definition is an extern.
*/
public boolean isExtern() {
return isExtern;
}
@Override
public String toString() {
return getLValue().getQualifiedName() + " = " + getRValue();
}
}
/**
* Represents an name-only external definition. The definition's
* RHS is missing.
*/
abstract static class IncompleteDefinition extends Definition {
private static final Set ALLOWED_TYPES =
ImmutableSet.of(Token.NAME, Token.GETPROP, Token.GETELEM);
private final Node lValue;
IncompleteDefinition(Node lValue, boolean inExterns) {
super(inExterns);
Preconditions.checkNotNull(lValue);
Preconditions.checkArgument(
ALLOWED_TYPES.contains(lValue.getToken()),
"Unexpected lValue type %s",
lValue.getToken());
this.lValue = lValue;
}
@Override
public Node getLValue() {
return lValue;
}
@Override
public Node getRValue() {
return null;
}
}
/**
* Represents an unknown definition.
*/
static final class UnknownDefinition extends IncompleteDefinition {
UnknownDefinition(Node lValue, boolean inExterns) {
super(lValue, inExterns);
}
@Override
public void performRemove() {
throw new IllegalArgumentException("Can't remove an UnknownDefinition");
}
}
/**
* Represents an name-only external definition. The definition's
* RHS is missing.
*/
static final class ExternalNameOnlyDefinition extends IncompleteDefinition {
ExternalNameOnlyDefinition(Node lValue) {
super(lValue, true);
}
@Override
public void performRemove() {
throw new IllegalArgumentException(
"Can't remove external name-only definition");
}
}
/**
* Represents a function formal parameter. The definition's RHS is missing.
*/
static final class FunctionArgumentDefinition extends IncompleteDefinition {
FunctionArgumentDefinition(Node function,
Node argumentName,
boolean inExterns) {
super(argumentName, inExterns);
Preconditions.checkArgument(function.isFunction());
Preconditions.checkArgument(argumentName.isName());
}
@Override
public void performRemove() {
throw new IllegalArgumentException(
"Can't remove a FunctionArgumentDefinition");
}
}
/**
* Represents a function declaration or function expression.
*/
abstract static class FunctionDefinition extends Definition {
protected final Node function;
FunctionDefinition(Node node, boolean inExterns) {
super(inExterns);
Preconditions.checkArgument(node.isFunction());
function = node;
}
@Override
public Node getLValue() {
return function.getFirstChild();
}
@Override
public Node getRValue() {
return function;
}
}
/**
* Represents a function declaration without assignment node such as
* {@code function foo()}.
*/
static final class NamedFunctionDefinition extends FunctionDefinition {
NamedFunctionDefinition(Node node, boolean inExterns) {
super(node, inExterns);
}
@Override
public void performRemove() {
function.detach();
}
}
/**
* Represents a function expression that acts as a RHS. The defined
* name is only reachable from within the function.
*/
static final class FunctionExpressionDefinition extends FunctionDefinition {
FunctionExpressionDefinition(Node node, boolean inExterns) {
super(node, inExterns);
Preconditions.checkArgument(
NodeUtil.isFunctionExpression(node));
}
@Override
public void performRemove() {
// replace internal name with ""
function.replaceChild(function.getFirstChild(), IR.name(""));
}
}
/**
* Represents a declaration within an assignment.
*/
static final class AssignmentDefinition extends Definition {
private final Node assignment;
AssignmentDefinition(Node node, boolean inExterns) {
super(inExterns);
Preconditions.checkArgument(node.isAssign());
assignment = node;
}
@Override
public void performRemove() {
// A simple assignment. foo = bar() -> bar();
Node parent = assignment.getParent();
Node last = assignment.getLastChild();
assignment.removeChild(last);
parent.replaceChild(assignment, last);
}
@Override
public Node getLValue() {
return assignment.getFirstChild();
}
@Override
public Node getRValue() {
return assignment.getLastChild();
}
}
/**
* Represents member declarations using a record type from externs.
* Example: /** @typedef {{prop: number}} *\/ var typdef;
*/
static final class RecordTypePropertyDefinition extends IncompleteDefinition {
RecordTypePropertyDefinition(Node name) {
super(IR.getprop(IR.objectlit(), name.cloneNode()),
/** isExtern */ true);
Preconditions.checkArgument(name.isString());
}
@Override
public void performRemove() {
throw new UnsupportedOperationException("Can't remove RecordType def");
}
}
/**
* Represents member declarations using a object literal.
* Example: var x = { e : function() { } };
*/
static final class ObjectLiteralPropertyDefinition extends Definition {
private final Node literal;
private final Node name;
private final Node value;
ObjectLiteralPropertyDefinition(Node lit, Node name, Node value,
boolean isExtern) {
super(isExtern);
this.literal = lit;
this.name = name;
this.value = value;
}
@Override
public void performRemove() {
literal.removeChild(name);
}
@Override
public Node getLValue() {
// TODO(user) revisit: object literal definitions are an example
// of definitions whose LHS doesn't correspond to a node that
// exists in the AST. We will have to change the return type of
// getLValue sooner or later in order to provide this added
// flexibility.
switch (name.getToken()) {
case SETTER_DEF:
case GETTER_DEF:
case STRING_KEY:
// TODO(johnlenz): return a GETELEM for quoted strings.
return IR.getprop(
IR.objectlit(),
IR.string(name.getString()));
default:
throw new IllegalStateException("unexpected");
}
}
@Override
public Node getRValue() {
return value;
}
}
/**
* Represents a VAR declaration with an assignment.
*/
static final class VarDefinition extends Definition {
private final Node name;
VarDefinition(Node node, boolean inExterns) {
super(inExterns);
Preconditions.checkArgument(NodeUtil.isVarDeclaration(node));
Preconditions.checkArgument(inExterns || node.hasChildren(),
"VAR Declaration of %s must be assigned a value.", node.getString());
name = node;
}
@Override
public void performRemove() {
Node var = name.getParent();
Preconditions.checkState(var.getFirstChild() == var.getLastChild(),
"AST should be normalized first");
Node parent = var.getParent();
Node rValue = name.removeFirstChild();
Preconditions.checkState(!NodeUtil.isLoopStructure(parent));
parent.replaceChild(var, NodeUtil.newExpr(rValue));
}
@Override
public Node getLValue() {
return name;
}
@Override
public Node getRValue() {
return name.getFirstChild();
}
}
}