
com.google.javascript.jscomp.HoistVarsOutOfBlocks Maven / Gradle / Ivy
/*
* Copyright 2017 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.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
/**
* Moves vars in blocks to the top of function scopes, if they are used outside of the block
* they're in. For instance
*
*
*
* if (someCondition) {
* var a = 1;
* }
* var b = a || 2;
*
*
*
* becomes
*
*
* var a;
* if (someCondition) {
* a = 1;
* }
* var b = a || 2;
*
*
* This runs just before the Normalize pass to simplify some of the logic in Normalize. Note that
* vars in for loops (e.g. {@code for (var x = 0; ; ) {} alert(x); }) are not hoisted because they
* don't cause any issues for the Normalize pass.
*/
class HoistVarsOutOfBlocks extends AbstractPostOrderCallback
implements ReferenceCollectingCallback.Behavior, CompilerPass {
private final AbstractCompiler compiler;
private ReferenceMap refMap;
private final ScopeCreator scopeCreator;
HoistVarsOutOfBlocks(AbstractCompiler compiler) {
this.compiler = compiler;
this.scopeCreator = new Es6SyntacticScopeCreator(compiler);
}
@Override
public void process(Node externs, Node root) {
ReferenceCollectingCallback rcc = new ReferenceCollectingCallback(compiler, this, scopeCreator);
rcc.process(root);
}
/**
* Users of this class call {@link #process(Node, Node)} on it. That method executes a {@link
* ReferenceCollectingCallback}. At the end of every scope, this method is executed and causes
* another traversal of the scope, which does the hoisting.
*/
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap refMap) {
// TODO(tbreisacher): Avoid calling t.getScope() here, so that we aren't creating scopes we
// don't need to.
if (!t.getScope().isHoistScope()) {
return;
}
this.refMap = refMap;
(new NodeTraversal(compiler, this, scopeCreator)).traverseAtScope(t.getScope());
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isVar() && t.getScope() != t.getClosestHoistScope()) {
hoistVarIfNeeded(t, n, parent);
}
}
private void hoistVarIfNeeded(NodeTraversal t, Node varNode, Node parent) {
if (NodeUtil.isAnyFor(parent)) {
// These don't need to be hoisted.
return;
}
Node block = t.getScopeRoot();
for (Node lhs : varNode.children()) {
if (!lhs.isName()) {
continue;
}
Var var = t.getScope().getVar(lhs.getString());
ReferenceCollection refs = this.refMap.getReferences(var);
if (refs == null) {
continue;
}
boolean hoist = false;
for (Reference r : refs) {
if (!r.getNode().isDescendantOf(block)) {
hoist = true;
break;
}
}
if (!hoist) {
continue;
}
Node rhs = lhs.removeFirstChild();
Node hoistRoot = t.getClosestHoistScope().getRootNode();
if (hoistRoot.isRoot()) {
hoistRoot = NodeUtil.getEnclosingScript(varNode);
}
if (rhs == null) {
// Note that lhs.getParent() may not be varNode, because of the way replaceDeclarationChild
// splits var nodes up.
NodeUtil.removeChild(lhs.getParent(), lhs);
} else {
Node exprAssign = IR.exprResult(IR.assign(lhs.cloneNode(), rhs));
exprAssign.useSourceInfoIfMissingFromForTree(varNode);
NodeUtil.replaceDeclarationChild(lhs, exprAssign);
}
hoistRoot.addChildToFront(IR.var(lhs.cloneNode()).useSourceInfoIfMissingFromForTree(lhs));
t.reportCodeChange();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy