31
31
import com .google .javascript .rhino .IR ;
32
32
import com .google .javascript .rhino .JSDocInfo ;
33
33
import com .google .javascript .rhino .Node ;
34
+ import com .google .javascript .rhino .Token ;
34
35
import java .util .LinkedHashMap ;
35
36
import java .util .LinkedHashSet ;
36
37
import java .util .Map ;
@@ -174,10 +175,42 @@ private void handleDeclarationList(Node declarationList, Node parent) {
174
175
// (i.e. it would've become a separate var with an {@code /** const */} annotation).
175
176
return ;
176
177
}
178
+
177
179
if (parent .isVanillaFor ()) {
178
- handleDeclarationListInForInitializer (declarationList , parent );
180
+ /*
181
+ * This is needed to handle the case where we get:
182
+ *
183
+ * <ol>
184
+ * <li>`for (let x = ...)` or
185
+ * <li>`for (const x = ...`. <
186
+ * </ol>
187
+ *
188
+ * <p>Normalize only moves "var" outside the for initializer, it allows let/const within the
189
+ * initializer. So both #1 and #2 can exist regardless if this pass runs before and after
190
+ * normalize.
191
+ */
192
+ handleDeclarationListInVanillaForInitializer (declarationList , parent );
193
+ return ;
194
+ }
195
+
196
+ if (parent .isForIn ()) {
197
+ /*
198
+ * This is needed to handle the case where we get:
199
+ *
200
+ * <ol>
201
+ * <li>`for (let x in ...)` or
202
+ * <li>`for (const x in ...`. <
203
+ * </ol>
204
+ *
205
+ *
206
+ * <p>Normalize only moves "var" outside the for initializer, it allows let/const within the
207
+ * initializer. So both #1 and #2 can exist regardless if this pass runs before and after
208
+ * normalize.
209
+ */
210
+ handleDeclarationListInForInInitializer (declarationList , parent );
179
211
return ;
180
212
}
213
+
181
214
// convert all names to their own, separate (normalized) declarations
182
215
while (declarationList .hasChildren ()) {
183
216
Node name = declarationList .getLastChild ();
@@ -215,7 +248,74 @@ private void handleDeclarationList(Node declarationList, Node parent) {
215
248
compiler .reportChangeToEnclosingScope (parent );
216
249
}
217
250
218
- private void handleDeclarationListInForInitializer (Node declarationList , Node parent ) {
251
+ /**
252
+ * TODO: b/197349249 - We won't have any declaration lists in the FOR_IN initializer here when
253
+ * this pass runs post normalize. After that, all lets/consts can be directly converted to vars.
254
+ */
255
+ private void handleDeclarationListInForInInitializer (Node declarationList , Node parent ) {
256
+ checkState (parent .isForIn ());
257
+ Node first = declarationList ;
258
+ Node lhs = first .getFirstChild ();
259
+ Node loopNode = parent ;
260
+ if (lhs .isDestructuringLhs ()) {
261
+ // This pass relies on destructuring syntax being already removed. Hence we must not enter
262
+ // this case.
263
+ // TODO: b/279640656 Enable this code path once this pass runs unconditionally in stage3.
264
+ checkState (
265
+ false , "Destructuring syntax is unsupported in ES6RewriteBlockScopedDeclarations pass" );
266
+ // Transform:
267
+ // `for (let [a, b = 3] in c) {}` or `for (const [a, b = 3] in c) {}`
268
+ // to:
269
+ // <pre>
270
+ // `var a; var b; for ([a, b = 3] in c) {}`
271
+ // </pre>
272
+ // respectively
273
+ NodeUtil .visitLhsNodesInNode (
274
+ lhs ,
275
+ (name ) -> {
276
+ // Add a declaration outside the for loop for the given name.
277
+ checkState (
278
+ name .isName (),
279
+ "lhs in destructuring declaration should be a simple name. (%s)" ,
280
+ name );
281
+ Node newName = IR .name (name .getString ()).srcref (name );
282
+ Node newVar = IR .var (newName ).srcref (name );
283
+ extractInlineJSDoc (declarationList , name , newVar );
284
+ // if the initializer name was a const, the newName must no longer be a const.
285
+ addNodeBeforeLoop (newVar , loopNode );
286
+ });
287
+
288
+ // Transform `for (var [a, b]... )` to `for ([a, b]...`
289
+ Node destructuringPattern = lhs .removeFirstChild ();
290
+ first .replaceWith (destructuringPattern );
291
+ } else {
292
+ // Transform:
293
+ // for (let a in b) {}
294
+ // to:
295
+ // var a; for (a in b) {};
296
+ // and:
297
+ // for (const a in b) {}
298
+ // to:
299
+ // var a; for (a in b) {};
300
+ Node newStatement = first .cloneTree ();
301
+ Node name = newStatement .getFirstChild ().cloneNode ();
302
+ // cloning also copies over any properties
303
+ if (name .getBooleanProp (Node .IS_CONSTANT_NAME )) {
304
+ // if the initializer name was a const, it must no longer be a const var.
305
+ name .putProp (Node .IS_CONSTANT_NAME , false );
306
+ }
307
+ newStatement .setToken (Token .VAR );
308
+ extractInlineJSDoc (first , first .getFirstChild (), newStatement );
309
+ first .replaceWith (name );
310
+ addNodeBeforeLoop (newStatement , loopNode );
311
+ }
312
+ }
313
+
314
+ /**
315
+ * TODO: b/197349249 - We won't have any declaration lists in the FOR initializer here when this
316
+ * pass runs post normalize. After that, all lets/consts can be directly converted to vars.
317
+ */
318
+ private void handleDeclarationListInVanillaForInitializer (Node declarationList , Node parent ) {
219
319
checkState (parent .isVanillaFor ());
220
320
// if the declarationList is in a FOR initializer, move it outside
221
321
Node insertSpot = getInsertSpotBeforeLoop (parent );
@@ -224,13 +324,16 @@ private void handleDeclarationListInForInitializer(Node declarationList, Node pa
224
324
Node name = declarationList .getLastChild ();
225
325
Node newDeclaration = IR .var (name .detach ()).srcref (declarationList );
226
326
extractInlineJSDoc (declarationList , name , newDeclaration );
227
- maybeAddConstJSDoc (declarationList , name , newDeclaration );
327
+ if (name .getBooleanProp (Node .IS_CONSTANT_NAME )) {
328
+ // if the initializer name was a const, it must no longer be a const var after rewriting.
329
+ name .putProp (Node .IS_CONSTANT_NAME , false );
330
+ }
228
331
// generate normalized var initializer (i.e. outside FOR)
229
332
newDeclaration .insertBefore (insertSpot );
230
333
insertSpot = newDeclaration ;
231
334
compiler .reportChangeToEnclosingScope (parent );
232
335
}
233
- // make FOR initializer empty
336
+ // make FOR initializer empty `for (; cond; incr)`
234
337
Node empty = astFactory .createEmpty ().srcref (declarationList );
235
338
declarationList .replaceWith (empty );
236
339
compiler .reportChangeToEnclosingScope (empty );
0 commit comments