20
20
import static com .google .javascript .jscomp .AstFactory .type ;
21
21
22
22
import com .google .common .base .Predicate ;
23
- import com .google .common .collect .HashBasedTable ;
24
23
import com .google .common .collect .HashMultimap ;
25
24
import com .google .common .collect .LinkedHashMultimap ;
26
25
import com .google .common .collect .SetMultimap ;
27
- import com .google .common .collect .Table ;
28
26
import com .google .javascript .jscomp .NodeTraversal .AbstractPostOrderCallback ;
29
27
import com .google .javascript .jscomp .NodeTraversal .AbstractPreOrderCallback ;
30
28
import com .google .javascript .jscomp .colors .StandardColors ;
45
43
*
46
44
* <p>Note that this must run after Es6RewriteDestructuring, since it does not process destructuring
47
45
* let/const declarations at all.
48
- *
49
- * <p>TODO(moz): Try to use MakeDeclaredNamesUnique
50
46
*/
51
47
public final class Es6RewriteBlockScopedDeclaration extends AbstractPostOrderCallback
52
48
implements CompilerPass {
53
49
54
50
private final AbstractCompiler compiler ;
55
51
private final AstFactory astFactory ;
56
- private final Table <Node , String , String > renameTable = HashBasedTable .create ();
57
52
private final Set <Node > letConsts = new LinkedHashSet <>();
58
- private final Set <String > undeclaredNames = new LinkedHashSet <>();
59
- private final Set <String > externNames = new LinkedHashSet <>();
60
53
private static final FeatureSet transpiledFeatures =
61
54
FeatureSet .BARE_MINIMUM .with (Feature .LET_DECLARATIONS , Feature .CONST_DECLARATIONS );
62
55
private final UniqueIdSupplier uniqueIdSupplier ;
63
- private final boolean astMayHaveUndeclaredVariables ;
64
56
65
57
public Es6RewriteBlockScopedDeclaration (AbstractCompiler compiler ) {
66
58
this .compiler = compiler ;
67
59
this .uniqueIdSupplier = compiler .getUniqueIdSupplier ();
68
- this .astMayHaveUndeclaredVariables = compiler .getOptions ().skipNonTranspilationPasses ;
69
60
this .astFactory = compiler .createAstFactory ();
70
61
}
71
62
@@ -82,25 +73,24 @@ public void visit(NodeTraversal t, Node n, Node parent) {
82
73
}
83
74
if (NodeUtil .isNameDeclaration (n )) {
84
75
for (Node nameNode = n .getFirstChild (); nameNode != null ; nameNode = nameNode .getNext ()) {
85
- visitBlockScopedName ( t , n , nameNode );
76
+ visitBlockScopedNameDeclaration ( n , nameNode );
86
77
}
87
78
} else {
88
79
// NOTE: This pass depends on class declarations having been transpiled away
89
80
checkState (n .isFunction () || n .isCatch (), "Unexpected declaration node: %s" , n );
90
- visitBlockScopedName ( t , n , n .getFirstChild ());
81
+ visitBlockScopedNameDeclaration ( n , n .getFirstChild ());
91
82
}
92
83
}
93
84
94
85
@ Override
95
86
public void process (Node externs , Node root ) {
96
- if ( this . astMayHaveUndeclaredVariables ) {
97
- // If we are only transpiling, we may have undefined variables in the code .
98
- NodeTraversal . traverse ( compiler , root , new CollectUndeclaredNames () );
99
- }
100
- // Record names declared in externs to prevent collisions when declaring vars from let/ const.
101
- this . externNames . addAll ( NodeUtil . collectExternVariableNames ( compiler , externs ));
87
+ // Make all declared names unique, so we can safely just switch 'let' or 'const' to 'var' in all
88
+ // non-loop cases .
89
+ MakeDeclaredNamesUnique renamer = MakeDeclaredNamesUnique . builder (). build ( );
90
+ NodeTraversal . traverseRoots ( compiler , renamer , externs , root );
91
+ // - Gather a list of let & const variables
92
+ // - Also add `= void 0` to any that are not initialized.
102
93
NodeTraversal .traverse (compiler , root , this );
103
- NodeTraversal .traverse (compiler , root , new Es6RenameReferences (renameTable ));
104
94
LoopClosureTransformer transformer = new LoopClosureTransformer ();
105
95
NodeTraversal .traverse (compiler , root , transformer );
106
96
transformer .transformLoopClosure ();
@@ -113,8 +103,7 @@ public void process(Node externs, Node root) {
113
103
*
114
104
* <p>Also normalizes declarations with no initializer in a loop to be initialized to undefined.
115
105
*/
116
- private void visitBlockScopedName (NodeTraversal t , Node decl , Node nameNode ) {
117
- Scope scope = t .getScope ();
106
+ private void visitBlockScopedNameDeclaration (Node decl , Node nameNode ) {
118
107
Node parent = decl .getParent ();
119
108
// Normalize "let x;" to "let x = undefined;" if in a loop, since we later convert x
120
109
// to be $jscomp$loop$0.x and want to reset the property to undefined every loop iteration.
@@ -126,26 +115,6 @@ && inLoop(decl)) {
126
115
nameNode .addChildToFront (undefined );
127
116
compiler .reportChangeToEnclosingScope (undefined );
128
117
}
129
-
130
- String oldName = nameNode .getString ();
131
- Scope hoistScope = scope .getClosestHoistScope ();
132
- if (scope != hoistScope ) {
133
- String newName = oldName ;
134
- if (hoistScope .hasSlot (oldName )
135
- || undeclaredNames .contains (oldName )
136
- || externNames .contains (oldName )) {
137
- do {
138
- newName = oldName + "$" + uniqueIdSupplier .getUniqueId (t .getInput ());
139
- } while (hoistScope .hasSlot (newName ));
140
- nameNode .setString (newName );
141
- compiler .reportChangeToEnclosingScope (nameNode );
142
- Node scopeRoot = scope .getRootNode ();
143
- renameTable .put (scopeRoot , oldName , newName );
144
- }
145
- Var oldVar = scope .getVar (oldName );
146
- scope .undeclare (oldVar );
147
- hoistScope .declare (newName , nameNode , oldVar .getInput ());
148
- }
149
118
}
150
119
151
120
/**
@@ -219,35 +188,17 @@ private void rewriteDeclsToVars() {
219
188
}
220
189
}
221
190
222
- /**
223
- * Records undeclared names and aggressively rename possible references to them. Eg: In "{ let
224
- * inner; } use(inner);", we rename the let declared variable.
225
- */
226
- private class CollectUndeclaredNames extends AbstractPostOrderCallback {
227
-
228
- @ Override
229
- public void visit (NodeTraversal t , Node n , Node parent ) {
230
- if (n .isName () && !t .getScope ().hasSlot (n .getString ())) {
231
- undeclaredNames .add (n .getString ());
232
- }
233
- }
234
- }
235
-
236
191
/** Transforms let/const declarations captured by loop closures. */
237
192
private class LoopClosureTransformer extends AbstractPreOrderCallback {
238
193
239
194
private static final String LOOP_OBJECT_NAME = "$jscomp$loop" ;
240
- private static final String LOOP_OBJECT_PROPERTY_NAME = "$jscomp$loop$prop$" ;
241
195
private final Map <Node , LoopObject > loopObjectMap = new LinkedHashMap <>();
242
196
243
197
private final SetMultimap <Node , LoopObject > nodesRequiringLoopObjectsClosureMap =
244
198
LinkedHashMultimap .create ();
245
199
private final SetMultimap <Node , String > nodesHandledForLoopObjectClosure =
246
200
HashMultimap .create ();
247
201
private final SetMultimap <Var , Node > referenceMap = LinkedHashMultimap .create ();
248
- // Maps from a var to a unique property name for that var
249
- // e.g. 'i' -> '$jscomp$loop$prop$i$0'
250
- private final Map <Var , String > propertyNameMap = new LinkedHashMap <>();
251
202
252
203
@ Override
253
204
public boolean shouldTraverse (NodeTraversal t , Node n , Node parent ) {
@@ -322,9 +273,7 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
322
273
LoopObject object =
323
274
loopObjectMap .computeIfAbsent (
324
275
loopNode , (Node k ) -> new LoopObject (createUniqueObjectName (t .getInput ())));
325
- String newPropertyName = createUniquePropertyName (var );
326
276
object .vars .add (var );
327
- propertyNameMap .put (var , newPropertyName );
328
277
nodesRequiringLoopObjectsClosureMap .put (nodeToWrapInClosure , object );
329
278
}
330
279
return true ;
@@ -334,13 +283,11 @@ private String createUniqueObjectName(CompilerInput input) {
334
283
return LOOP_OBJECT_NAME + "$" + uniqueIdSupplier .getUniqueId (input );
335
284
}
336
285
337
- // Normalization guarantees unique variable names so generating a unique ID is necessary
338
- // because this pass runs after normalization.
339
- private String createUniquePropertyName (Var var ) {
340
- return LOOP_OBJECT_PROPERTY_NAME
341
- + var .getName ()
342
- + "$"
343
- + uniqueIdSupplier .getUniqueId (var .getInput ());
286
+ private String getLoopObjPropName (Var var ) {
287
+ // NOTE: var.getName() would be wrong here, because it will still contain the original
288
+ // and possibly non-unique name for the variable. However, the name node itself will already
289
+ // have the new and guaranteed-globally-unique name.
290
+ return var .getNameNode ().getString ();
344
291
}
345
292
346
293
private void transformLoopClosure () {
@@ -445,7 +392,7 @@ private void changeVanillaForLoopHeader(Node loopNode, Node updateLoopObject) {
445
392
*/
446
393
private void changeLoopLocalVariablesToProperties (Node loopNode , LoopObject loopObject ) {
447
394
for (Var var : loopObject .vars ) {
448
- String newPropertyName = propertyNameMap . get (var );
395
+ String newPropertyName = getLoopObjPropName (var );
449
396
for (Node reference : referenceMap .get (var )) {
450
397
// for-of loops are transpiled away before this pass runs
451
398
checkState (!loopNode .isForOf (), loopNode );
@@ -546,7 +493,7 @@ private void assignLoopVarToLoopObjectProperty(
546
493
/** Rename all variables in the loop object to properties */
547
494
private void renameVarsToProperties (LoopObject loopObject , Node objectLitNextIteration ) {
548
495
for (Var var : loopObject .vars ) {
549
- String newPropertyName = propertyNameMap . get (var );
496
+ String newPropertyName = getLoopObjPropName (var );
550
497
objectLitNextIteration .addChildToBack (
551
498
astFactory .createStringKey (
552
499
newPropertyName ,
0 commit comments