com.google.javascript.jscomp.CollapseVariableDeclarations Maven / Gradle / Ivy
Show all versions of closure-compiler-linter Show documentation
/*
* Copyright 2006 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.checkState;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Collapses multiple variable declarations into a single one. i.e the
* following:
*
*
* var a;
* var b = 1;
* var c = 2;
*
*
* becomes:
*
* var a, b = 1, c = 2;
*
* This reduces the generated code size. More optimizations are possible:
* Group all variable declarations inside a function into one such variable.
* declaration block.
* Re-use variables instead of declaring a new one if they are used for
* only part of a function.
*
* Similarly, also collapses assigns like:
*
*
* a = true;
* b = true;
* var c = true;
*
*
* becomes:
*
* var c = b = a = true;
*/
class CollapseVariableDeclarations implements CompilerPass {
/** Reference to JS Compiler */
private final AbstractCompiler compiler;
/** Encapsulation of information about a variable declaration collapse */
private static class Collapse {
/**
* Variable declaration that any following var nodes should be
* collapsed into
*/
final Node startNode;
/** Parent of the nodes to the collapse */
final Node parent;
Collapse(Node startNode, Node endNode, Node parent) {
this.startNode = startNode;
this.parent = parent;
}
}
/**
* Collapses to do in this pass.
*/
private final List collapses = new ArrayList<>();
/**
* Nodes we've already looked at for collapsing, so that we don't look at them
* again (we look ahead when examining what nodes can be collapsed, and the
* node traversal may give them to us again)
*/
private final Set nodesToCollapse = new HashSet<>();
CollapseVariableDeclarations(AbstractCompiler compiler) {
checkState(!compiler.getLifeCycleStage().isNormalized());
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
collapses.clear();
nodesToCollapse.clear();
NodeTraversal.traverse(compiler, root, new GatherCollapses());
if (!collapses.isEmpty()) {
applyCollapses();
}
}
/**
* Gathers all of the variable declarations that should be collapsed into one.
*
* We do not do the collapsing as we go since node traversal would be affected by the changes
* we are making to the parse tree.
*/
private class GatherCollapses extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// If we've already looked at this node, skip it
if (nodesToCollapse.contains(n)) {
return;
}
if (!NodeUtil.isNameDeclaration(n)) {
return;
}
// Adjacent VAR children of an IF node are the if and else parts and can't
// be collapsed
if (parent.isIf()) {
return;
}
Node varNode = n;
Token nType = n.getToken();
// Find variable declarations that follow this one (if any)
n = n.getNext();
boolean hasNodesToCollapse = false;
// we only want to collapse lets with lets, vars with vars, and consts with consts so we
// check to make sure the declaration types match
while (n != null && nType == n.getToken()) {
nodesToCollapse.add(n);
hasNodesToCollapse = true;
n = n.getNext();
}
if (hasNodesToCollapse) {
nodesToCollapse.add(varNode);
collapses.add(new Collapse(varNode, n, parent));
}
}
}
private void applyCollapses() {
for (Collapse collapse : collapses) {
Node var = collapse.startNode;
compiler.reportChangeToEnclosingScope(var);
while (var.getNext() != null
&& (var.getNext().getToken() == var.getToken())) {
Node next = collapse.parent.removeChildAfter(var);
// Move all children of the next var node into the first one.
var.addChildrenToBack(next.removeChildren());
}
}
}
}