@Override public void process(Node externs, Node root) { assignmentLog = new StringBuilder(); // Do variable reference counting. NodeTraversal.traverse(compiler, externs, new ProcessVars(true)); NodeTraversal.traverse(compiler, root, new ProcessVars(false)); // Make sure that new names don't overlap with extern names. reservedNames.addAll(externNames); // Rename vars, sorted by frequency of occurrence to minimize code size. SortedSet<Assignment> varsByFrequency = new TreeSet<Assignment>(FREQUENCY_COMPARATOR); varsByFrequency.addAll(assignments.values()); if (shouldShadow) { new ShadowVariables(compiler, assignments, varsByFrequency, pseudoNameMap) .process(externs, root); } // First try to reuse names from an earlier compilation. if (prevUsedRenameMap != null) { reusePreviouslyUsedVariableMap(); } // Assign names, sorted by descending frequency to minimize code size. assignNames(varsByFrequency); boolean changed = false; // Rename the globals! for (Node n : globalNameNodes) { String newName = getNewGlobalName(n); // Note: if newName is null, then oldName is an extern. if (newName != null) { n.setString(newName); changed = true; } } // Rename the locals! int count = 0; for (Node n : localNameNodes) { String newName = getNewLocalName(n); if (newName != null) { n.setString(newName); changed = true; } count++; } if (changed) { compiler.reportCodeChange(); } // Lastly, write the name assignments to the debug log. compiler.addToDebugLog("JS var assignments:\n" + assignmentLog); assignmentLog = null; }
/** * Rename or remove labels. * * @param node The label node. * @param parent The parent of the label node. */ private void visitLabel(Node node, Node parent) { Node nameNode = node.getFirstChild(); Preconditions.checkState(nameNode != null); String name = nameNode.getString(); LabelInfo li = getLabelInfo(name); // This is a label... if (li.referenced || !removeUnused) { String newName = getNameForId(li.id); if (!name.equals(newName)) { // ... and it is used, give it the short name. nameNode.setString(newName); compiler.reportCodeChange(); } } else { // ... and it is not referenced, just remove it. Node newChild = node.getLastChild(); node.removeChild(newChild); parent.replaceChild(node, newChild); if (newChild.isBlock()) { NodeUtil.tryMergeBlock(newChild); } compiler.reportCodeChange(); } // Remove the label from the current stack of labels. namespaceStack.peek().renameMap.remove(name); }
/** * Duplicates the PolymerElement externs with a different element base class if needed. For * example, if the base class is HTMLInputElement, then a class PolymerInputElement will be added. * If the element does not extend a native HTML element, this method is a no-op. */ private void appendPolymerElementExterns(final ClassDefinition cls) { if (!nativeExternsAdded.add(cls.nativeBaseElement)) { return; } Node block = IR.block(); Node baseExterns = polymerElementExterns.cloneTree(); String polymerElementType = getPolymerElementType(cls); baseExterns.getFirstChild().setString(polymerElementType); String elementType = tagNameMap.get(cls.nativeBaseElement); JSTypeExpression elementBaseType = new JSTypeExpression(new Node(Token.BANG, IR.string(elementType)), VIRTUAL_FILE); JSDocInfoBuilder baseDocs = JSDocInfoBuilder.copyFrom(baseExterns.getJSDocInfo()); baseDocs.changeBaseType(elementBaseType); baseExterns.setJSDocInfo(baseDocs.build()); block.addChildToBack(baseExterns); for (Node baseProp : polymerElementProps) { Node newProp = baseProp.cloneTree(); Node newPropRootName = NodeUtil.getRootOfQualifiedName(newProp.getFirstChild().getFirstChild()); newPropRootName.setString(polymerElementType); block.addChildToBack(newProp); } Node parent = polymerElementExterns.getParent(); Node stmts = block.removeChildren(); parent.addChildrenAfter(stmts, polymerElementExterns); compiler.reportCodeChange(); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (!NodeUtil.isReferenceName(n)) { return; } Scope referencedIn = t.getScope(); String oldName = n.getString(); Scope current = referencedIn; boolean doRename = false; String newName = null; while (current != null) { Map<String, String> renamesAtCurrentLevel = renameMap.get(current.getRootNode()); if (current.isDeclared(oldName, false)) { return; } else if (renamesAtCurrentLevel != null && renamesAtCurrentLevel.containsKey(oldName)) { doRename = true; newName = renamesAtCurrentLevel.get(oldName); break; } else { current = current.getParent(); } } if (doRename) { n.setString(newName); t.getCompiler().reportCodeChange(); } }
private void fixJsdocType(Scope scope, Node node) { if (node.isString()) { Polyfill polyfill = polyfills.statics.get(node.getString()); // Note: all classes are unqualified names, so we don't need to deal with dots if (polyfill != null && scope.getVar(node.getString()) == null && !languageOutIsAtLeast(polyfill.nativeVersion)) { node.setString(polyfill.rewrite); } } for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { fixJsdocType(scope, child); } }
/** Renames all properties with references on more than one type. */ void renameProperties() { int propsRenamed = 0, propsSkipped = 0, instancesRenamed = 0, instancesSkipped = 0, singleTypeProps = 0; for (Property prop : properties.values()) { if (prop.shouldRename()) { Map<T, String> propNames = buildPropNames(prop.getTypes(), prop.name); ++propsRenamed; prop.expandTypesToSkip(); UnionFind<T> types = prop.getTypes(); for (Node node : prop.renameNodes) { T rootType = prop.rootTypes.get(node); if (prop.shouldRename(rootType)) { String newName = propNames.get(rootType); node.setString(newName); compiler.reportCodeChange(); ++instancesRenamed; } else { ++instancesSkipped; } } } else { if (prop.skipRenaming) { ++propsSkipped; } else { ++singleTypeProps; } } } logger.info("Renamed " + instancesRenamed + " instances of " + propsRenamed + " properties."); logger.info( "Skipped renaming " + instancesSkipped + " invalidated " + "properties, " + propsSkipped + " instances of properties " + "that were skipped for specific types and " + singleTypeProps + " properties that were referenced from only one type."); }
/** * Rename label references in breaks and continues. * * @param node The break or continue node. */ private void visitBreakOrContinue(Node node) { Node nameNode = node.getFirstChild(); if (nameNode != null) { // This is a named break or continue; String name = nameNode.getString(); Preconditions.checkState(!name.isEmpty()); LabelInfo li = getLabelInfo(name); if (li != null) { String newName = getNameForId(li.id); // Mark the label as referenced so it isn't removed. li.referenced = true; if (!name.equals(newName)) { // Give it the short name. nameNode.setString(newName); compiler.reportCodeChange(); } } } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { // TODO(moz): Add support for renaming classes. if (!n.isLet() && !n.isConst() && !NodeUtil.isBlockScopedFunctionDeclaration(n)) { return; } Scope scope = t.getScope(); Node nameNode = n.getFirstChild(); if (!n.isFunction() && !nameNode.hasChildren() && (parent == null || !NodeUtil.isEnhancedFor(parent))) { nameNode.addChildToFront(IR.name("undefined").useSourceInfoIfMissingFrom(nameNode)); } String oldName = nameNode.getString(); if (n.isLet() || n.isConst()) { blockScopedDeclarations.add(n); } Scope hoistScope = scope.getClosestHoistScope(); boolean doRename = false; if (scope != hoistScope) { doRename = hoistScope.isDeclared(oldName, true) || undeclaredNames.contains(oldName); String newName = doRename ? oldName + "$" + compiler.getUniqueNameIdSupplier().get() : oldName; Var oldVar = scope.getVar(oldName); scope.undeclare(oldVar); hoistScope.declare(newName, nameNode, oldVar.input); if (doRename) { nameNode.setString(newName); Node scopeRoot = scope.getRootNode(); if (!renameMap.containsKey(scopeRoot)) { renameMap.put(scopeRoot, new HashMap<String, String>()); } renameMap.get(scopeRoot).put(oldName, newName); } } if (doRename) { t.getCompiler().reportCodeChange(); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isName()) { return; } String name = n.getString(); // Ignore anonymous functions if (name.length() == 0) { return; } // Is this local or Global? // Bleeding functions should be treated as part of their outer // scope, because IE has bugs in how it handles bleeding // functions. Scope.Var var = t.getScope().getVar(name); boolean local = (var != null) && var.isLocal() && (!var.scope.getParent().isGlobal() || !var.isBleedingFunction()); // Are we renaming global variables? if (!local && localRenamingOnly) { reservedNames.add(name); return; } // Are we renaming function expression names? if (preserveFunctionExpressionNames && var != null && NodeUtil.isFunctionExpression(var.getParentNode())) { reservedNames.add(name); return; } // Check if we can rename this. if (!okToRenameVar(name, local)) { if (local) { // Blindly de-uniquify for the Prototype library for issue 103. String newName = MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName(name); if (!newName.equals(name)) { n.setString(newName); } } return; } if (isExternsPass_) { // Keep track of extern globals. if (!local) { externNames.add(name); } return; } if (pseudoNameMap != null) { recordPseudoName(n); } if (local) { // Local var: assign a new name String tempName = LOCAL_VAR_PREFIX + getLocalVarIndex(var); incCount(tempName); localNameNodes.add(n); n.setString(tempName); } else if (var != null) { // Not an extern // If it's global, increment global count incCount(name); globalNameNodes.add(n); } }