/** * Declares global variables to serve as aliases for the values in an object literal, optionally * removing all of the object literal's keys and values. * * @param alias The object literal's flattened name (e.g. "a$b$c") * @param objlit The OBJLIT node * @param varNode The VAR node to which new global variables should be added as children * @param nameToAddAfter The child of {@code varNode} after which new variables should be added * (may be null) * @param varParent {@code varNode}'s parent * @return The number of variables added */ private int declareVarsForObjLitValues( Name objlitName, String alias, Node objlit, Node varNode, Node nameToAddAfter, Node varParent) { int numVars = 0; int arbitraryNameCounter = 0; boolean discardKeys = !objlitName.shouldKeepKeys(); for (Node key = objlit.getFirstChild(), nextKey; key != null; key = nextKey) { Node value = key.getFirstChild(); nextKey = key.getNext(); // A get or a set can not be rewritten as a VAR. if (key.isGetterDef() || key.isSetterDef()) { continue; } // We generate arbitrary names for keys that aren't valid JavaScript // identifiers, since those keys are never referenced. (If they were, // this object literal's child names wouldn't be collapsible.) The only // reason that we don't eliminate them entirely is the off chance that // their values are expressions that have side effects. boolean isJsIdentifier = !key.isNumber() && TokenStream.isJSIdentifier(key.getString()); String propName = isJsIdentifier ? key.getString() : String.valueOf(++arbitraryNameCounter); // If the name cannot be collapsed, skip it. String qName = objlitName.getFullName() + '.' + propName; Name p = nameMap.get(qName); if (p != null && !p.canCollapse()) { continue; } String propAlias = appendPropForAlias(alias, propName); Node refNode = null; if (discardKeys) { objlit.removeChild(key); value.detachFromParent(); } else { // Substitute a reference for the value. refNode = IR.name(propAlias); if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) { refNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } key.replaceChild(value, refNode); } // Declare the collapsed name as a variable with the original value. Node nameNode = IR.name(propAlias); nameNode.addChildToFront(value); if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } Node newVar = IR.var(nameNode).useSourceInfoIfMissingFromForTree(key); if (nameToAddAfter != null) { varParent.addChildAfter(newVar, nameToAddAfter); } else { varParent.addChildBefore(newVar, varNode); } compiler.reportCodeChange(); nameToAddAfter = newVar; // Update the global name's node ancestry if it hasn't already been // done. (Duplicate keys in an object literal can bring us here twice // for the same global name.) if (isJsIdentifier && p != null) { if (!discardKeys) { Ref newAlias = p.getDeclaration().cloneAndReclassify(Ref.Type.ALIASING_GET); newAlias.node = refNode; p.addRef(newAlias); } p.getDeclaration().node = nameNode; if (value.isFunction()) { checkForHosedThisReferences(value, key.getJSDocInfo(), p); } } numVars++; } return numVars; }