|
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; |
35 | 34 | import java.util.LinkedHashMap;
|
36 | 35 | import java.util.LinkedHashSet;
|
37 | 36 | import java.util.Map;
|
@@ -154,36 +153,109 @@ private static void maybeAddConstJSDoc(Node srcDeclaration, Node srcName, Node d
|
154 | 153 | }
|
155 | 154 | }
|
156 | 155 |
|
| 156 | + /** |
| 157 | + * Given a declarationList of let/const declarations, convert all declared names to their own, |
| 158 | + * separate (normalized) var declarations. |
| 159 | + * |
| 160 | + * <p>"const i = 0, j = 0;" becomes "/\*\* @const *\/ var i = 0; /\** @const *\/ var j = 0;" |
| 161 | + * |
| 162 | + * <p>If the declarationList of let/const declarations is in a FOR intializer, moves those into |
| 163 | + * separate declarations outside the FOR loop (for maintaining normalization). |
| 164 | + * |
| 165 | + * <p>TODO: b/197349249 - We won't have any declaration lists here when this pass runs post |
| 166 | + * normalize. |
| 167 | + */ |
157 | 168 | private void handleDeclarationList(Node declarationList, Node parent) {
|
158 |
| - // Normalize: "const i = 0, j = 0;" becomes "/** @const */ var i = 0; /** @const */ var j = 0;" |
159 |
| - while (declarationList.hasMoreThanOneChild()) { |
| 169 | + if (declarationList.isVar() && declarationList.hasOneChild()) { |
| 170 | + // This declaration list is already handled and we can safely return. This happens because |
| 171 | + // this method is also called by {@code replaceDeclarationWithProperty} where it gets |
| 172 | + // repeatedly called for each name in the declarationList. After the first call |
| 173 | + // to this (for the first name), this declarationList would no longer be {@code Token.CONST} |
| 174 | + // (i.e. it would've become a separate var with an {@code /** const */} annotation). |
| 175 | + return; |
| 176 | + } |
| 177 | + if (parent.isVanillaFor()) { |
| 178 | + handleDeclarationListInForInitializer(declarationList, parent); |
| 179 | + return; |
| 180 | + } |
| 181 | + // convert all names to their own, separate (normalized) declarations |
| 182 | + while (declarationList.hasChildren()) { |
160 | 183 | Node name = declarationList.getLastChild();
|
161 | 184 | Node newDeclaration = IR.var(name.detach()).srcref(declarationList);
|
| 185 | + /* |
| 186 | + * This method gets called multiple times by {@code replaceDeclarationWithProperty}. In the |
| 187 | + * first call, it splits the declarationList into individual /\** @const *\/ var declarations. |
| 188 | + * i.e. `const a,b` --> `/\** @const *\/ var a; /\** @const *\/ var b;` |
| 189 | + * |
| 190 | + * <p>Then it gets invoked for each individual var declaration. once for each name in the |
| 191 | + * original declarationList). However, after the first call to this (for the first name), this |
| 192 | + * declarationList would no longer be {@code Token.CONST} (i.e. it would've become a separate |
| 193 | + * var with an {@code /*\* const *\/} annotation). |
| 194 | + * |
| 195 | + * <p>Previously, this method rewrote those individual var declarations again into new var |
| 196 | + * declarations. Without copying over the JSDoc, those newly created var declarations did not |
| 197 | + * contain `@const`, and produce this: |
| 198 | + * |
| 199 | + * <p>`/\** @const *\/ var a; /\** @const *\/ var b;` -->`var a; var b;` |
| 200 | + * |
| 201 | + * <p>Now, even though the "redundant rewriting" is handled by early-returning from this |
| 202 | + * method whenever this method gets called with a non-declaration var list, it's still |
| 203 | + * important that the individual var declarations created by the first rewriting contain the |
| 204 | + * annotations from original declaration list. Hence we propagate JSDoc from declarationList |
| 205 | + * into the individual var declarations. |
| 206 | + */ |
| 207 | + extractInlineJSDoc(declarationList, name, newDeclaration); |
162 | 208 | maybeAddConstJSDoc(declarationList, name, newDeclaration);
|
163 | 209 | newDeclaration.insertAfter(declarationList);
|
164 | 210 | compiler.reportChangeToEnclosingScope(parent);
|
165 | 211 | }
|
166 |
| - maybeAddConstJSDoc(declarationList, declarationList.getFirstChild(), declarationList); |
167 |
| - declarationList.setToken(Token.VAR); |
| 212 | + |
| 213 | + // declarationList has no children left. Remove. |
| 214 | + declarationList.detach(); |
| 215 | + compiler.reportChangeToEnclosingScope(parent); |
| 216 | + } |
| 217 | + |
| 218 | + private void handleDeclarationListInForInitializer(Node declarationList, Node parent) { |
| 219 | + checkState(parent.isVanillaFor()); |
| 220 | + // if the declarationList is in a FOR initializer, move it outside |
| 221 | + Node insertSpot = getInsertSpotBeforeLoop(parent); |
| 222 | + // convert all names to their own, separate (normalized) declarations |
| 223 | + while (declarationList.hasChildren()) { |
| 224 | + Node name = declarationList.getLastChild(); |
| 225 | + Node newDeclaration = IR.var(name.detach()).srcref(declarationList); |
| 226 | + extractInlineJSDoc(declarationList, name, newDeclaration); |
| 227 | + maybeAddConstJSDoc(declarationList, name, newDeclaration); |
| 228 | + // generate normalized var initializer (i.e. outside FOR) |
| 229 | + newDeclaration.insertBefore(insertSpot); |
| 230 | + insertSpot = newDeclaration; |
| 231 | + compiler.reportChangeToEnclosingScope(parent); |
| 232 | + } |
| 233 | + // make FOR initializer empty |
| 234 | + Node empty = astFactory.createEmpty().srcref(declarationList); |
| 235 | + declarationList.replaceWith(empty); |
| 236 | + compiler.reportChangeToEnclosingScope(empty); |
168 | 237 | }
|
169 | 238 |
|
170 | 239 | private void addNodeBeforeLoop(Node newNode, Node loopNode) {
|
| 240 | + Node insertSpot = getInsertSpotBeforeLoop(loopNode); |
| 241 | + newNode.insertBefore(insertSpot); |
| 242 | + compiler.reportChangeToEnclosingScope(newNode); |
| 243 | + } |
| 244 | + |
| 245 | + private Node getInsertSpotBeforeLoop(Node loopNode) { |
171 | 246 | Node insertSpot = loopNode;
|
172 | 247 | while (insertSpot.getParent().isLabel()) {
|
173 | 248 | insertSpot = insertSpot.getParent();
|
174 | 249 | }
|
175 |
| - newNode.insertBefore(insertSpot); |
176 |
| - compiler.reportChangeToEnclosingScope(newNode); |
| 250 | + return insertSpot; |
177 | 251 | }
|
178 | 252 |
|
179 | 253 | private void rewriteDeclsToVars() {
|
180 | 254 | if (!letConsts.isEmpty()) {
|
181 | 255 | for (Node n : letConsts) {
|
182 |
| - if (n.isConst()) { |
183 |
| - handleDeclarationList(n, n.getParent()); |
184 |
| - } |
185 |
| - n.setToken(Token.VAR); |
186 |
| - compiler.reportChangeToEnclosingScope(n); |
| 256 | + // for both lets and consts we want to split the declaration lists when converting them to |
| 257 | + // vars (to maintain normalization) |
| 258 | + handleDeclarationList(n, n.getParent()); |
187 | 259 | }
|
188 | 260 | }
|
189 | 261 | }
|
@@ -416,11 +488,14 @@ private void replaceDeclarationWithProperty(
|
416 | 488 | LoopObject loopObject, String newPropertyName, Node reference) {
|
417 | 489 | Node declaration = reference.getParent();
|
418 | 490 | Node grandParent = declaration.getParent();
|
419 |
| - // If the declaration contains multiple declared variables, split it apart. |
420 |
| - handleDeclarationList(declaration, grandParent); |
421 |
| - // Record that the let / const declaration statement has been turned into one or more |
| 491 | + // Record that the let / const declaration statement will get turned into one or more |
422 | 492 | // var statements by handleDeclarationList(), so we won't try to change it again later.
|
423 | 493 | letConsts.remove(declaration);
|
| 494 | + // If the declaration contains multiple declared variables, split it apart. |
| 495 | + // NOTE: This call could be made for each declarationList once, rather than each name in that |
| 496 | + // list |
| 497 | + handleDeclarationList(declaration, grandParent); |
| 498 | + |
424 | 499 | // The variable we're working with may have been moved to a new var statement.
|
425 | 500 | declaration = reference.getParent();
|
426 | 501 | if (reference.hasChildren()) {
|
|
0 commit comments