All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.Es6RewriteBlockScopedDeclaration Maven / Gradle / Ivy

/*
 * Copyright 2014 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 static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.rhino.jstype.JSTypeNative.OBJECT_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.STRING_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.UNKNOWN_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.VOID_TYPE;

import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Rewrite "let"s and "const"s as "var"s. Rename block-scoped declarations and their references when
 * necessary.
 *
 * 

Note that this must run after Es6RewriteDestructuring, since it does not process destructuring * let/const declarations at all. * *

TODO(moz): Try to use MakeDeclaredNamesUnique */ public final class Es6RewriteBlockScopedDeclaration extends AbstractPostOrderCallback implements HotSwapCompilerPass { private final AbstractCompiler compiler; private final Table renameTable = HashBasedTable.create(); private final Set letConsts = new HashSet<>(); private final Set undeclaredNames = new HashSet<>(); private static final FeatureSet transpiledFeatures = FeatureSet.BARE_MINIMUM.with(Feature.LET_DECLARATIONS, Feature.CONST_DECLARATIONS); private final Supplier uniqueNameIdSupplier; /** Should we generate type information for newly generated AST nodes? */ private boolean shouldAddTypesOnNewAstNodes; public Es6RewriteBlockScopedDeclaration(AbstractCompiler compiler) { this.compiler = compiler; this.uniqueNameIdSupplier = compiler.getUniqueNameIdSupplier(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.hasChildren() || !NodeUtil.isBlockScopedDeclaration(n.getFirstChild())) { return; } // NOTE: This pass depends on for-of being transpiled away before it runs. checkState(parent == null || !parent.isForOf(), parent); if (n.isLet() || n.isConst()) { letConsts.add(n); } if (NodeUtil.isNameDeclaration(n)) { for (Node nameNode : n.children()) { visitBlockScopedName(t, n, nameNode); } } else { // NOTE: This pass depends on class declarations having been transpiled away checkState(n.isFunction() || n.isCatch(), "Unexpected declaration node: %s", n); visitBlockScopedName(t, n, n.getFirstChild()); } } @Override public void process(Node externs, Node root) { shouldAddTypesOnNewAstNodes = getShouldAddTypesOnNewAstNodes(); NodeTraversal.traverse(compiler, root, new CollectUndeclaredNames()); NodeTraversal.traverse(compiler, root, this); // Needed for let / const declarations in .d.ts externs. TranspilationPasses.processTranspile(compiler, externs, transpiledFeatures, this); NodeTraversal.traverse(compiler, root, new Es6RenameReferences(renameTable)); LoopClosureTransformer transformer = new LoopClosureTransformer(); NodeTraversal.traverse(compiler, root, transformer); transformer.transformLoopClosure(); rewriteDeclsToVars(); TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { shouldAddTypesOnNewAstNodes = getShouldAddTypesOnNewAstNodes(); NodeTraversal.traverse(compiler, scriptRoot, new CollectUndeclaredNames()); NodeTraversal.traverse(compiler, scriptRoot, this); NodeTraversal.traverse(compiler, scriptRoot, new Es6RenameReferences(renameTable)); LoopClosureTransformer transformer = new LoopClosureTransformer(); NodeTraversal.traverse(compiler, scriptRoot, transformer); transformer.transformLoopClosure(); rewriteDeclsToVars(); TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures); } private boolean getShouldAddTypesOnNewAstNodes() { return compiler.hasTypeCheckingRun(); } /** * Renames block-scoped declarations that shadow a variable in an outer scope * *

Also normalizes declarations with no initializer in a loop to be initialized to undefined. */ private void visitBlockScopedName(NodeTraversal t, Node decl, Node nameNode) { Scope scope = t.getScope(); Node parent = decl.getParent(); // Normalize "let x;" to "let x = undefined;" if in a loop, since we later convert x // to be $jscomp$loop$0.x and want to reset the property to undefined every loop iteration. if ((decl.isLet() || decl.isConst()) && !nameNode.hasChildren() && (parent == null || !parent.isForIn()) && inLoop(decl)) { Node undefined = createUndefinedNode().srcref(nameNode); nameNode.addChildToFront(undefined); compiler.reportChangeToEnclosingScope(undefined); } String oldName = nameNode.getString(); Scope hoistScope = scope.getClosestHoistScope(); if (scope != hoistScope) { String newName = oldName; if (hoistScope.hasSlot(oldName) || undeclaredNames.contains(oldName)) { do { newName = oldName + "$" + compiler.getUniqueNameIdSupplier().get(); } while (hoistScope.hasSlot(newName)); nameNode.setString(newName); compiler.reportChangeToEnclosingScope(nameNode); Node scopeRoot = scope.getRootNode(); renameTable.put(scopeRoot, oldName, newName); } Var oldVar = scope.getVar(oldName); scope.undeclare(oldVar); hoistScope.declare(newName, nameNode, oldVar.getInput()); } } /** * Whether n is inside a loop. If n is inside a function which is inside a loop, we do not * consider it to be inside a loop. */ private boolean inLoop(Node n) { Node enclosingNode = NodeUtil.getEnclosingNode(n, isLoopOrFunction); return enclosingNode != null && !enclosingNode.isFunction(); } private static final Predicate isLoopOrFunction = new Predicate() { @Override public boolean apply(Node n) { return n.isFunction() || NodeUtil.isLoopStructure(n); } }; private static void extractInlineJSDoc(Node srcDeclaration, Node srcName, Node destDeclaration) { JSDocInfo existingInfo = srcDeclaration.getJSDocInfo(); if (existingInfo == null) { // Extract inline JSDoc from "src" and add it to the "dest" node. existingInfo = srcName.getJSDocInfo(); srcName.setJSDocInfo(null); } JSDocInfoBuilder builder = JSDocInfoBuilder.maybeCopyFrom(existingInfo); destDeclaration.setJSDocInfo(builder.build()); } private static void maybeAddConstJSDoc(Node srcDeclaration, Node srcParent, Node srcName, Node destDeclaration) { if (srcDeclaration.isConst() // Don't add @const for the left side of a for/in. If we do we get warnings from the NTI. // TODO(lharker): Check if this condition is still necessary, since NTI is deleted && !(srcParent.isForIn() && srcDeclaration == srcParent.getFirstChild())) { extractInlineJSDoc(srcDeclaration, srcName, destDeclaration); JSDocInfoBuilder builder = JSDocInfoBuilder.maybeCopyFrom(destDeclaration.getJSDocInfo()); builder.recordConstancy(); destDeclaration.setJSDocInfo(builder.build()); } } private void handleDeclarationList(Node declarationList, Node parent) { // Normalize: "const i = 0, j = 0;" becomes "/** @const */ var i = 0; /** @const */ var j = 0;" while (declarationList.hasMoreThanOneChild()) { Node name = declarationList.getLastChild(); Node newDeclaration = IR.var(name.detach()).useSourceInfoFrom(declarationList); maybeAddConstJSDoc(declarationList, parent, name, newDeclaration); parent.addChildAfter(newDeclaration, declarationList); compiler.reportChangeToEnclosingScope(parent); } maybeAddConstJSDoc(declarationList, parent, declarationList.getFirstChild(), declarationList); declarationList.setToken(Token.VAR); } private void addNodeBeforeLoop(Node newNode, Node loopNode) { Node insertSpot = loopNode; while (insertSpot.getParent().isLabel()) { insertSpot = insertSpot.getParent(); } insertSpot.getParent().addChildBefore(newNode, insertSpot); compiler.reportChangeToEnclosingScope(newNode); } private void rewriteDeclsToVars() { if (!letConsts.isEmpty()) { for (Node n : letConsts) { if (n.isConst()) { handleDeclarationList(n, n.getParent()); } n.setToken(Token.VAR); compiler.reportChangeToEnclosingScope(n); } } } /** * Records undeclared names and aggressively rename possible references to them. * Eg: In "{ let inner; } use(inner);", we rename the let declared variable. */ private class CollectUndeclaredNames extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName() && !t.getScope().hasSlot(n.getString())) { undeclaredNames.add(n.getString()); } } } /** * Transforms let/const declarations captured by loop closures. */ private class LoopClosureTransformer extends AbstractPostOrderCallback { private static final String LOOP_OBJECT_NAME = "$jscomp$loop"; private static final String LOOP_OBJECT_PROPERTY_NAME = "$jscomp$loop$prop$"; private final Map loopObjectMap = new LinkedHashMap<>(); private final Multimap functionLoopObjectsMap = LinkedHashMultimap.create(); private final Multimap functionHandledMap = HashMultimap.create(); private final Multimap referenceMap = LinkedHashMultimap.create(); // Maps from a var to a unique property name for that var // e.g. 'i' -> '$jscomp$loop$prop$i$0' private final Map propertyNameMap = new LinkedHashMap<>(); @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!NodeUtil.isReferenceName(n)) { return; } String name = n.getString(); Scope referencedIn = t.getScope(); Var var = referencedIn.getVar(name); if (var == null) { return; } if (!var.isLet() && !var.isConst()) { return; } if (n.getParent().isLet() || n.getParent().isConst()) { letConsts.add(n.getParent()); } // Traverse nodes up from let/const declaration: // If we hit a function or the root before a loop - Not a loop closure. // if we hit a loop first - maybe loop closure. Scope declaredIn = var.getScope(); Node loopNode = null; for (Scope s = declaredIn;; s = s.getParent()) { Node scopeRoot = s.getRootNode(); if (NodeUtil.isLoopStructure(scopeRoot)) { loopNode = scopeRoot; break; } else if (scopeRoot.getParent() != null && NodeUtil.isLoopStructure(scopeRoot.getParent())) { loopNode = scopeRoot.getParent(); break; } else if (s.isFunctionBlockScope() || s.isGlobal()) { return; } } referenceMap.put(var, n); // Traverse scopes from reference scope to declaration scope. // If we hit a function - loop closure detected. Scope outerMostFunctionScope = null; for (Scope s = referencedIn; s != declaredIn && s.getRootNode() != loopNode; s = s.getParent()) { if (s.isFunctionScope()) { outerMostFunctionScope = s; } } if (outerMostFunctionScope != null) { Node function = outerMostFunctionScope.getRootNode(); if (functionHandledMap.containsEntry(function, name)) { return; } functionHandledMap.put(function, name); LoopObject object = loopObjectMap.computeIfAbsent( loopNode, (Node k) -> new LoopObject( LOOP_OBJECT_NAME + "$" + compiler.getUniqueNameIdSupplier().get())); String newPropertyName = createUniquePropertyName(var); object.vars.add(var); propertyNameMap.put(var, newPropertyName); functionLoopObjectsMap.put(function, object); } } private String createUniquePropertyName(Var var) { return LOOP_OBJECT_PROPERTY_NAME + var.getName() + "$" + uniqueNameIdSupplier.get(); } private void transformLoopClosure() { if (loopObjectMap.isEmpty()) { return; } for (Node loopNode : loopObjectMap.keySet()) { // Introduce objects to reflect the captured scope variables. // Fields are initially left as undefined to avoid cases like: // var $jscomp$loop$0 = {$jscomp$loop$prop$i: 0, $jscomp$loop$prop$j: $jscomp$loop$0.i} // They are initialized lazily by changing declarations into assignments // later. LoopObject loopObject = loopObjectMap.get(loopNode); Node objectLitNextIteration = createObjectLit(); for (Var var : loopObject.vars) { String newPropertyName = propertyNameMap.get(var); objectLitNextIteration.addChildToBack( IR.stringKey( newPropertyName, createLoopVarReferenceReplacement( loopObject, var.getNameNode(), newPropertyName))); } Node updateLoopObject = createAssignNode(createLoopObjectNameNode(loopObject), objectLitNextIteration); Node objectLit = IR.var(createLoopObjectNameNode(loopObject), createObjectLit()) .useSourceInfoFromForTree(loopNode); addNodeBeforeLoop(objectLit, loopNode); if (loopNode.isVanillaFor()) { // For // The initializer is pulled out and placed prior to the loop. Node initializer = loopNode.getFirstChild(); loopNode.replaceChild(initializer, IR.empty()); if (!initializer.isEmpty()) { if (!NodeUtil.isNameDeclaration(initializer)) { initializer = IR.exprResult(initializer).useSourceInfoFrom(initializer); } addNodeBeforeLoop(initializer, loopNode); } Node increment = loopNode.getChildAtIndex(2); if (increment.isEmpty()) { loopNode.replaceChild( increment, updateLoopObject.useSourceInfoIfMissingFromForTree(loopNode)); } else { Node placeHolder = IR.empty(); loopNode.replaceChild(increment, placeHolder); loopNode.replaceChild( placeHolder, createCommaNode(updateLoopObject, increment) .useSourceInfoIfMissingFromForTree(loopNode)); } } else { // We need to make sure the loop object update happens on every loop iteration. // We want to keep it at the end of the loop, because that makes it easier to reason // about the types. // // TODO(bradfordcsmith): Maybe move the update to the start of the loop when this pass // is moved after the type checking passes. // // A finally block would do it, but would have more runtime cost, so instead, if we find // that there are continue statements referring to the loop we will do this. // // originalLoopLabel: while (condition) { // $jscomp$loop$0: { // // original loop body here // // with continue statements converted to `break $jscomp$loop$0;` // // If originalLoopLabel exists, we'll also need to traverse into innner loops // // and convert `continue originalLoopLabel;`. // } // $jscomp$loop$0 = { var1: $jscomp$loop$0.var1, var2: $jscomp$loop$0.var2, ... }; // } // We're intentionally using the same name for the inner loop label and the loop variable // object. Label names and variables are different namespaces, so they do not conflict. String innerBlockLabel = loopObject.name; Node loopBody = NodeUtil.getLoopCodeBlock(loopNode); if (maybeUpdateContinueStatements(loopNode, innerBlockLabel)) { Node innerBlock = IR.block().srcref(loopBody); innerBlock.addChildrenToFront(loopBody.removeChildren()); loopBody.addChildToFront( IR.label(IR.labelName(innerBlockLabel).srcref(loopBody), innerBlock) .srcref(loopBody)); } loopBody.addChildToBack( IR.exprResult(updateLoopObject).useSourceInfoIfMissingFromForTree(loopNode)); } compiler.reportChangeToEnclosingScope(loopNode); // For captured variables, change declarations to assignments on the // corresponding field of the introduced object. Rename all references // accordingly. for (Var var : loopObject.vars) { String newPropertyName = propertyNameMap.get(var); for (Node reference : referenceMap.get(var)) { // for-of loops are transpiled away before this pass runs checkState(!loopNode.isForOf(), loopNode); // For-of and for-in declarations are not altered, since they are // used as temporary variables for assignment. if (loopNode.isForIn() && loopNode.getFirstChild() == reference.getParent()) { // reference is the node loopVar in a for-in or for-of that looks like this: // `for (const loopVar of list) {` checkState(reference == var.getNameNode(), reference); Node referenceParent = reference.getParent(); checkState(NodeUtil.isNameDeclaration(referenceParent), referenceParent); checkState(reference.isName(), reference); // Start transpiled form of // `for (const p in obj) { ... }` // with this statement to copy the loop variable into the corresponding loop object // property. // `$jscomp$loop$0.$jscomp$loop$prop$0$p = p;` Node loopVarReference = reference.cloneNode(); if (shouldAddTypesOnNewAstNodes) { // Note that name nodes in declarations are not given types by the type checker // passes. // Luckily we know that for-in loops over string property names. loopVarReference.setJSType(getNativeType(STRING_TYPE)); } loopNode .getLastChild() .addChildToFront( IR.exprResult( createAssignNode( createLoopVarReferenceReplacement( loopObject, reference, newPropertyName), loopVarReference)) .useSourceInfoIfMissingFromForTree(reference)); } else { if (NodeUtil.isNameDeclaration(reference.getParent())) { Node declaration = reference.getParent(); Node grandParent = declaration.getParent(); handleDeclarationList(declaration, grandParent); declaration = reference.getParent(); // Might have changed after normalization. // Change declaration to assignment, or just drop it if there's // no initial value. if (reference.hasChildren()) { Node newReference = cloneWithType(reference); Node assign = createAssignNode(newReference, reference.removeFirstChild()); extractInlineJSDoc(declaration, reference, declaration); maybeAddConstJSDoc(declaration, grandParent, reference, declaration); assign.setJSDocInfo(declaration.getJSDocInfo()); Node replacement = IR.exprResult(assign) .useSourceInfoIfMissingFromForTree(declaration); grandParent.replaceChild(declaration, replacement); reference = newReference; } else { grandParent.removeChild(declaration); } letConsts.remove(declaration); compiler.reportChangeToEnclosingScope(grandParent); } if (reference.getParent().isCall() && reference.getParent().getFirstChild() == reference) { reference.getParent().putBooleanProp(Node.FREE_CALL, false); } // Change reference to GETPROP. Node changeScope = NodeUtil.getEnclosingChangeScopeRoot(reference); reference.replaceWith( createLoopVarReferenceReplacement(loopObject, reference, newPropertyName)); // TODO(johnlenz): Don't work on detached nodes. if (changeScope != null) { compiler.reportChangeToChangeScope(changeScope); } } } } } // Create wrapper functions and call them. for (Node function : functionLoopObjectsMap.keySet()) { Node returnNode = IR.returnNode(); Collection objects = functionLoopObjectsMap.get(function); Node[] objectNames = new Node[objects.size()]; Node[] objectNamesForCall = new Node[objects.size()]; int i = 0; JSType[] objectTypes = new JSType[objects.size()]; for (LoopObject object : objects) { Node paramObjectName = createLoopObjectNameNode(object); objectNames[i] = paramObjectName; if (shouldAddTypesOnNewAstNodes) { objectTypes[i] = checkNotNull(paramObjectName.getJSType()); } objectNamesForCall[i] = createLoopObjectNameNode(object); i++; } Node iife = IR.function(IR.name(""), IR.paramList(objectNames), IR.block(returnNode)); if (shouldAddTypesOnNewAstNodes) { FunctionType iifeType = FunctionType.builder(compiler.getTypeRegistry()) .withName("") .withSourceNode(iife) .withParameters(compiler.getTypeRegistry().createParameters(objectTypes)) .withReturnType(function.getJSType()) .buildAndResolve(); iife.setJSType(iifeType); } compiler.reportChangeToChangeScope(iife); Node call = IR.call(iife, objectNamesForCall); if (shouldAddTypesOnNewAstNodes) { call.setJSType(function.getJSType()); } call.putBooleanProp(Node.FREE_CALL, true); Node replacement; if (NodeUtil.isFunctionDeclaration(function)) { replacement = IR.var(IR.name(function.getFirstChild().getString()), call) .useSourceInfoIfMissingFromForTree(function); } else { replacement = call.useSourceInfoIfMissingFromForTree(function); } function.replaceWith(replacement); returnNode.addChildToFront(function); compiler.reportChangeToEnclosingScope(replacement); } } /** * Creates a `$jscomp$loop$0.$jscomp$loop$prop$varName$1` replacement for a reference to * `varName`. */ private Node createLoopVarReferenceReplacement( LoopObject loopObject, Node reference, String propertyName) { Node replacement = IR.getprop(createLoopObjectNameNode(loopObject), propertyName); if (shouldAddTypesOnNewAstNodes) { // If the reference is the name node in a declaration (e.g. `x` in `let x;`), // it won't have a type, so just use unknown. JSType jsType = reference.getJSType(); if (jsType == null) { jsType = getNativeType(UNKNOWN_TYPE); } replacement.setJSType(jsType); } replacement.useSourceInfoFromForTree(reference); return replacement; } private Node createLoopObjectNameNode(LoopObject loopObject) { Node loopObjectNameNode = IR.name(loopObject.name); if (shouldAddTypesOnNewAstNodes) { loopObjectNameNode.setJSType(getNativeType(OBJECT_TYPE)); } return loopObjectNameNode; } /** * Converts all continue statements referring to the given loop to `break $jscomp$loop$0;` where * `$jscomp$loop$0` is the label on the block containing the original loop body. * *

If this method returns {@code true}, then we must wrap the original loop body in a block * labeled with the name from the loopObject. * * @return True if at least one continue statement was found and replaced. */ private boolean maybeUpdateContinueStatements(Node loopNode, String breakLabel) { Node loopParent = loopNode.getParent(); final String originalLoopLabel = loopParent.isLabel() ? loopParent.getFirstChild().getString() : null; ContinueStatementUpdater continueStatementUpdater = new ContinueStatementUpdater(breakLabel, originalLoopLabel); NodeTraversal.traverse( compiler, NodeUtil.getLoopCodeBlock(loopNode), continueStatementUpdater); return continueStatementUpdater.replacedAContinueStatement; } /** * Converts all continue statements referring to the given loop to `break $jscomp$loop$0;` where * `$jscomp$loop$0` is the label on the block containing the original loop body. */ private class ContinueStatementUpdater implements NodeTraversal.Callback { // label to put on break statements created that replace continue statements. private final String breakLabel; @Nullable private final String originalLoopLabel; // Track how many levels of loops deep we go below this one. int loopDepth = 0; // Set to true if a continue statement is found boolean replacedAContinueStatement = false; public ContinueStatementUpdater(String breakLabel, @Nullable String originalLoopLabel) { this.breakLabel = breakLabel; this.originalLoopLabel = originalLoopLabel; } @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // NOTE: This pass runs after ES6 classes have already been transpiled away. checkState(!n.isClass(), n); if (n.isFunction()) { return false; } else if (NodeUtil.isLoopStructure(n)) { if (originalLoopLabel == null) { // If this loop has no label, there cannot be any continue statements referring to it // in inner loops. return false; } else { loopDepth++; return true; } } else { return true; } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isLoopStructure(n)) { loopDepth--; } else if (n.isContinue()) { if (loopDepth == 0 && !n.hasChildren()) { replaceWithBreak(n); } else if (originalLoopLabel != null && n.hasChildren() && originalLoopLabel.equals(n.getOnlyChild().getString())) { replaceWithBreak(n); } // else continue belongs to some other loop } // else nothing to do } private void replaceWithBreak(Node continueNode) { Node labelName = IR.labelName(breakLabel).srcref(continueNode); Node breakNode = IR.breakNode(labelName).srcref(continueNode); continueNode.replaceWith(breakNode); replacedAContinueStatement = true; } } private class LoopObject { /** * The name of the variable having the loop's internal variables as properties, and the label * applied to the block containing the original loop body in cases where these are needed. */ private final String name; private final Set vars = new LinkedHashSet<>(); private LoopObject(String name) { this.name = name; } } } private Node cloneWithType(Node node) { Node clone = node.cloneNode(); if (shouldAddTypesOnNewAstNodes) { clone.setJSType(node.getJSType()); } return clone; } /** Creates an ASSIGN node with type information matching its RHS. */ private Node createAssignNode(Node lhs, Node rhs) { Node assignNode = IR.assign(lhs, rhs); if (shouldAddTypesOnNewAstNodes) { assignNode.setJSType(rhs.getJSType()); } return assignNode; } /** Creates a COMMA node with type information matching its second argument. */ private Node createCommaNode(Node expr1, Node expr2) { Node commaNode = IR.comma(expr1, expr2); if (shouldAddTypesOnNewAstNodes) { commaNode.setJSType(expr2.getJSType()); } return commaNode; } private Node createObjectLit() { Node objectlit = IR.objectlit(); if (shouldAddTypesOnNewAstNodes) { objectlit.setJSType(getNativeType(OBJECT_TYPE)); } return objectlit; } private Node createUndefinedNode() { Node undefined = IR.name("undefined"); if (shouldAddTypesOnNewAstNodes) { undefined.setJSType(getNativeType(VOID_TYPE)); } return undefined; } private JSType getNativeType(JSTypeNative jsNativeType) { return compiler.getTypeRegistry().getNativeType(jsNativeType); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy