-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
HoistVarsOutOfBlocks.java
138 lines (126 loc) · 4.28 KB
/
HoistVarsOutOfBlocks.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
* 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.jscomp.ReferenceCollectingCallback.ReferenceMap;
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
*
* <pre>
*
* if (someCondition) {
* var a = 1;
* }
* var b = a || 2;
*
* </pre>
*
* becomes
*
* <pre>
* var a;
* if (someCondition) {
* a = 1;
* }
* var b = a || 2;
* <pre>
*
* 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.getScope().getRootNode();
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.getFirstChild();
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.cloneTree()));
exprAssign.useSourceInfoIfMissingFromForTree(varNode);
NodeUtil.replaceDeclarationChild(lhs, exprAssign);
}
hoistRoot.addChildToFront(IR.var(lhs.cloneNode()).useSourceInfoIfMissingFromForTree(lhs));
t.reportCodeChange();
}
}
}