copy and rename RenameProperties from Google Closure Compiler and adjust for source-compatibility
diff --git a/compiler-jx/src/main/java/com/google/javascript/jscomp/RenamePropertiesWithModuleSupport.java b/compiler-jx/src/main/java/com/google/javascript/jscomp/RenamePropertiesWithModuleSupport.java
new file mode 100644
index 0000000..81e7d04
--- /dev/null
+++ b/compiler-jx/src/main/java/com/google/javascript/jscomp/RenamePropertiesWithModuleSupport.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright 2004 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.checkState;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.javascript.jscomp.AbstractCompiler.LifeCycleStage;
+import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
+import com.google.javascript.rhino.IR;
+import com.google.javascript.rhino.Node;
+import com.google.javascript.rhino.TokenStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.annotation.Nullable;
+
+/**
+ * RenameProperties renames properties (including methods) of all JavaScript
+ * objects. This includes prototypes, functions, object literals, etc.
+ *
+ * <p> If provided a VariableMap of previously used names, it tries to reuse
+ * those names.
+ *
+ * <p> To prevent a property from getting renamed you may extern it (add it to
+ * your externs file) or put it in quotes.
+ *
+ * <p> To avoid run-time JavaScript errors, use quotes when accessing properties
+ * that are defined using quotes.
+ *
+ * <pre>
+ *   var a = {'myprop': 0}, b = a['myprop'];  // correct
+ *   var x = {'myprop': 0}, y = x.myprop;     // incorrect
+ * </pre>
+ *
+ * This pass also recognizes and replaces special renaming functions. They supply
+ * a property name as the string literal for the first argument.
+ * This pass will replace them as though they were JS property
+ * references. Here are two examples:
+ *    JSCompiler_renameProperty('propertyName') -> 'jYq'
+ *    JSCompiler_renameProperty('myProp.nestedProp.innerProp') -> 'e4.sW.C$'
+ *
+ */
+class RenamePropertiesWithModuleSupport implements CompilerPass {
+  private static final Splitter DOT_SPLITTER = Splitter.on('.');
+
+  private final AbstractCompiler compiler;
+  private final boolean generatePseudoNames;
+
+  /** Property renaming map from a previous compilation. */
+  private final VariableMap prevUsedPropertyMap;
+
+  private final List<Node> toRemove = new ArrayList<Node>();
+  private final List<Node> stringNodesToRename = new ArrayList<Node>();
+  private final Map<Node, Node> callNodeToParentMap =
+      new LinkedHashMap<Node, Node>();
+  private final char[] reservedFirstCharacters;
+  private final char[] reservedNonFirstCharacters;
+
+  // Map from property name to Property object
+  private final Map<String, Property> propertyMap = new LinkedHashMap<String, Property>();
+
+  // Property names that don't get renamed
+  private final Set<String> externedNames = new LinkedHashSet<String>(
+      Arrays.asList("prototype"));
+
+  // Names to which properties shouldn't be renamed, to avoid name conflicts
+  private final Set<String> quotedNames = new LinkedHashSet<String>();
+
+  // Shared name generator
+  private final NameGenerator nameGenerator;
+
+  private static final Comparator<Property> FREQUENCY_COMPARATOR =
+    new Comparator<Property>() {
+      @Override
+      public int compare(Property p1, Property p2) {
+
+        /**
+         * First a frequently used names would always be picked first.
+         */
+        if (p1.numOccurrences != p2.numOccurrences) {
+          return p2.numOccurrences - p1.numOccurrences;
+        }
+
+        /**
+         * Finally, for determinism, we compare them based on the old name.
+         */
+        return p1.oldName.compareTo(p2.oldName);
+       }
+    };
+
+  static final DiagnosticType BAD_CALL = DiagnosticType.error(
+      "JSC_BAD_RENAME_PROPERTY_FUNCTION_NAME_CALL",
+      "Bad {0} call - the first argument must be a string literal");
+
+  static final DiagnosticType BAD_ARG = DiagnosticType.error(
+      "JSC_BAD_RENAME_PROPERTY_FUNCTION_NAME_ARG",
+      "Bad {0} argument - ''{1}'' is not a valid JavaScript identifier");
+
+  /**
+   * Creates an instance.
+   *
+   * @param compiler The JSCompiler
+   * @param generatePseudoNames Generate pseudo names. e.g foo -> $foo$ instead
+   *        of compact obfuscated names. This is used for debugging.
+   * @param nameGenerator a shared NameGenerator that this instance can use;
+   *        the instance may reset or reconfigure it, so the caller should
+   *        not expect any state to be preserved
+   */
+  RenamePropertiesWithModuleSupport(AbstractCompiler compiler, boolean generatePseudoNames,
+      NameGenerator nameGenerator) {
+    this(compiler, generatePseudoNames, null, null, null, nameGenerator);
+  }
+
+  /**
+   * Creates an instance.
+   *
+   * @param compiler The JSCompiler.
+   * @param generatePseudoNames Generate pseudo names. e.g foo -> $foo$ instead
+   *        of compact obfuscated names. This is used for debugging.
+   * @param prevUsedPropertyMap The property renaming map used in a previous
+   *        compilation.
+   * @param nameGenerator a shared NameGenerator that this instance can use;
+   *        the instance may reset or reconfigure it, so the caller should
+   *        not expect any state to be preserved
+   */
+  RenamePropertiesWithModuleSupport(AbstractCompiler compiler,
+      boolean generatePseudoNames, VariableMap prevUsedPropertyMap,
+      NameGenerator nameGenerator) {
+    this(compiler, generatePseudoNames, prevUsedPropertyMap, null, null, nameGenerator);
+  }
+
+  /**
+   * Creates an instance.
+   *
+   * @param compiler The JSCompiler.
+   * @param generatePseudoNames Generate pseudo names. e.g foo -> $foo$ instead of compact
+   *     obfuscated names. This is used for debugging.
+   * @param prevUsedPropertyMap The property renaming map used in a previous compilation.
+   * @param reservedFirstCharacters If specified these characters won't be used in generated names
+   *     for the first character
+   * @param reservedNonFirstCharacters If specified these characters won't be used in generated
+   *     names for characters after the first
+   * @param nameGenerator a shared NameGenerator that this instance can use; the instance may reset
+   *     or reconfigure it, so the caller should not expect any state to be preserved
+   */
+  RenamePropertiesWithModuleSupport(
+      AbstractCompiler compiler,
+      boolean generatePseudoNames,
+      VariableMap prevUsedPropertyMap,
+      @Nullable char[] reservedFirstCharacters,
+      @Nullable char[] reservedNonFirstCharacters,
+      NameGenerator nameGenerator) {
+    this.compiler = compiler;
+    this.generatePseudoNames = generatePseudoNames;
+    this.prevUsedPropertyMap = prevUsedPropertyMap;
+    this.reservedFirstCharacters = reservedFirstCharacters;
+    this.reservedNonFirstCharacters = reservedNonFirstCharacters;
+    this.nameGenerator = nameGenerator;
+    externedNames.addAll(compiler.getExternProperties());
+  }
+
+  @Override
+  public void process(Node externs, Node root) {
+    checkState(compiler.getLifeCycleStage().isNormalized());
+
+    NodeTraversal.traverse(compiler, root, new ProcessProperties());
+
+    Set<String> reservedNames =
+        Sets.newHashSetWithExpectedSize(externedNames.size() + quotedNames.size());
+    reservedNames.addAll(externedNames);
+    reservedNames.addAll(quotedNames);
+
+    // Assign names, sorted by descending frequency to minimize code size.
+    Set<Property> propsByFreq = new TreeSet<Property>(FREQUENCY_COMPARATOR);
+    propsByFreq.addAll(propertyMap.values());
+
+    // First, try and reuse as many property names from the previous compilation
+    // as possible.
+    if (prevUsedPropertyMap != null) {
+      reusePropertyNames(reservedNames, propsByFreq);
+    }
+
+    generateNames(propsByFreq, reservedNames);
+
+    // Update the string nodes.
+    for (Node n : stringNodesToRename) {
+      String oldName = n.getString();
+      Property p = propertyMap.get(oldName);
+      if (p != null && p.newName != null) {
+        checkState(oldName.equals(p.oldName));
+        n.setString(p.newName);
+        if (!p.newName.equals(oldName)) {
+          compiler.reportChangeToEnclosingScope(n);
+        }
+      }
+    }
+
+    // Update the call nodes.
+    for (Map.Entry<Node, Node> nodeEntry : callNodeToParentMap.entrySet()) {
+      Node parent = nodeEntry.getValue();
+      Node firstArg = nodeEntry.getKey().getSecondChild();
+      StringBuilder sb = new StringBuilder();
+      for (String oldName : DOT_SPLITTER.split(firstArg.getString())) {
+        Property p = propertyMap.get(oldName);
+        String replacement;
+        if (p != null && p.newName != null) {
+          checkState(oldName.equals(p.oldName));
+          replacement = p.newName;
+        } else {
+          replacement = oldName;
+        }
+        if (sb.length() > 0) {
+          sb.append('.');
+        }
+        sb.append(replacement);
+      }
+      parent.replaceChild(nodeEntry.getKey(), IR.string(sb.toString()));
+      compiler.reportChangeToEnclosingScope(parent);
+    }
+
+    // Complete queued removals.
+    for (Node n : toRemove) {
+      Node parent = n.getParent();
+      compiler.reportChangeToEnclosingScope(n);
+      n.detach();
+      NodeUtil.markFunctionsDeleted(n, compiler);
+      if (!parent.hasChildren() && !parent.isScript()) {
+        parent.detach();
+      }
+    }
+
+    compiler.setLifeCycleStage(LifeCycleStage.NORMALIZED_OBFUSCATED);
+    // This pass may rename getter or setter properties
+    GatherGettersAndSetterProperties.update(compiler, externs, root);
+  }
+
+  /**
+   * Runs through the list of properties and renames as many as possible with
+   * names from the previous compilation. Also, updates reservedNames with the
+   * set of reused names.
+   * @param reservedNames Reserved names to use during renaming.
+   * @param allProps Properties to rename.
+   */
+  private void reusePropertyNames(Set<String> reservedNames,
+                                  Collection<Property> allProps) {
+    for (Property prop : allProps) {
+      // Check if this node can reuse a name from a previous compilation - if
+      // it can set the newName for the property too.
+      String prevName = prevUsedPropertyMap.lookupNewName(prop.oldName);
+      if (!generatePseudoNames && prevName != null) {
+        // We can reuse prevName if it's not reserved.
+        if (reservedNames.contains(prevName)) {
+          continue;
+        }
+
+        prop.newName = prevName;
+        reservedNames.add(prevName);
+      }
+    }
+  }
+
+  /**
+   * Generates new names for properties.
+   *
+   * @param props Properties to generate new names for
+   * @param reservedNames A set of names to which properties should not be
+   *     renamed
+   */
+  private void generateNames(Set<Property> props, Set<String> reservedNames) {
+    nameGenerator.reset(reservedNames, "", reservedFirstCharacters, reservedNonFirstCharacters);
+    for (Property p : props) {
+      if (generatePseudoNames) {
+        p.newName = "$" + p.oldName + "$";
+      } else {
+        // If we haven't already given this property a reusable name.
+        if (p.newName == null) {
+          p.newName = nameGenerator.generateNextName();
+        }
+      }
+      reservedNames.add(p.newName);
+    }
+  }
+
+  /**
+   * Gets the property renaming map (the "answer key").
+   *
+   * @return A mapping from original names to new names
+   */
+  VariableMap getPropertyMap() {
+    ImmutableMap.Builder<String, String> map = ImmutableMap.builder();
+    for (Property p : propertyMap.values()) {
+      if (p.newName != null) {
+        map.put(p.oldName, p.newName);
+      }
+    }
+    return new VariableMap(map.build());
+  }
+
+
+  // -------------------------------------------------------------------------
+
+  /**
+   * A traversal callback that collects property names and counts how
+   * frequently each property name occurs.
+   */
+  private class ProcessProperties extends AbstractPostOrderCallback {
+
+    @Override
+    public void visit(NodeTraversal t, Node n, Node parent) {
+      switch (n.getToken()) {
+        case COMPUTED_PROP:
+          break;
+        case GETPROP:
+          Node propNode = n.getSecondChild();
+          if (propNode.isString()) {
+            if (compiler.getCodingConvention().blockRenamingForProperty(
+                propNode.getString())) {
+              externedNames.add(propNode.getString());
+              break;
+            }
+            maybeMarkCandidate(propNode);
+          }
+          break;
+        case OBJECTLIT:
+          for (Node key = n.getFirstChild(); key != null; key = key.getNext()) {
+            if (key.isComputedProp()) {
+              // We don't want to rename computed properties
+              continue;
+            } else if (key.isQuotedString()) {
+              // Ensure that we never rename some other property in a way
+              // that could conflict with this quoted key.
+              quotedNames.add(key.getString());
+            } else if (compiler.getCodingConvention().blockRenamingForProperty(key.getString())) {
+              externedNames.add(key.getString());
+            } else {
+              maybeMarkCandidate(key);
+            }
+          }
+          break;
+        case OBJECT_PATTERN:
+          // Iterate through all the nodes in the object pattern
+          for (Node key = n.getFirstChild(); key != null; key = key.getNext()) {
+            if (key.isComputedProp()) {
+              // We don't want to rename computed properties
+              continue;
+            } else if (key.isQuotedString()) {
+              // Ensure that we never rename some other property in a way
+              // that could conflict with this quoted key.
+              quotedNames.add(key.getString());
+            } else if (compiler.getCodingConvention().blockRenamingForProperty(key.getString())) {
+              externedNames.add(key.getString());
+            } else {
+              maybeMarkCandidate(key);
+            }
+          }
+          break;
+        case GETELEM:
+          // If this is a quoted property access (e.g. x['myprop']), we need to
+          // ensure that we never rename some other property in a way that
+          // could conflict with this quoted name.
+          Node child = n.getLastChild();
+          if (child != null && child.isString()) {
+            quotedNames.add(child.getString());
+          }
+          break;
+        case CALL: {
+          // We replace property renaming function calls with a string
+          // containing the renamed property.
+          Node fnName = n.getFirstChild();
+          if (compiler
+              .getCodingConvention()
+              .isPropertyRenameFunction(fnName.getOriginalQualifiedName())) {
+            callNodeToParentMap.put(n, parent);
+            countCallCandidates(t, n);
+          }
+          break;
+        }
+        case CLASS_MEMBERS:
+          {
+            // Replace function names defined in a class scope
+            for (Node key = n.getFirstChild(); key != null; key = key.getNext()) {
+              if (key.isComputedProp()) {
+                // We don't want to rename computed properties.
+                continue;
+              } else {
+                Node member = key.getFirstChild();
+
+                String memberDefName = key.getString();
+                if (member.isFunction()) {
+                  Node fnName = member.getFirstChild();
+                  if (compiler.getCodingConvention().blockRenamingForProperty(memberDefName)) {
+                    externedNames.add(fnName.getString());
+                  } else if (memberDefName.equals("constructor")
+                      || memberDefName.equals("superClass_")) {
+                    // TODO (simarora) is there a better way to identify these externs?
+                    externedNames.add(fnName.getString());
+                  } else {
+                    maybeMarkCandidate(key);
+                  }
+                }
+              }
+            }
+            break;
+          }
+        case FUNCTION:
+          {
+            // We eliminate any stub implementations of JSCompiler_renameProperty
+            // that we encounter.
+            if (NodeUtil.isFunctionDeclaration(n)) {
+              String name = n.getFirstChild().getString();
+              if (NodeUtil.JSC_PROPERTY_NAME_FN.equals(name)) {
+                toRemove.add(n);
+              }
+            } else if (parent.isName()
+                && NodeUtil.JSC_PROPERTY_NAME_FN.equals(parent.getString())) {
+              Node varNode = parent.getParent();
+              if (varNode.isVar()) {
+                toRemove.add(parent);
+              }
+            } else if (NodeUtil.isFunctionExpression(n)
+                && parent.isAssign()
+                && parent.getFirstChild().isGetProp()
+                && compiler
+                    .getCodingConvention()
+                    .isPropertyRenameFunction(parent.getFirstChild().getOriginalQualifiedName())) {
+              Node exprResult = parent.getParent();
+              if (exprResult.isExprResult()
+                  && NodeUtil.isStatementBlock(exprResult.getParent())
+                  && exprResult.getFirstChild().isAssign()) {
+                toRemove.add(exprResult);
+              }
+            }
+            break;
+          }
+        default:
+          break;
+      }
+    }
+
+    /**
+     * If a property node is eligible for renaming, stashes a reference to it
+     * and increments the property name's access count.
+     *
+     * @param n The STRING node for a property
+     */
+    private void maybeMarkCandidate(Node n) {
+      String name = n.getString();
+      if (!externedNames.contains(name)) {
+        stringNodesToRename.add(n);
+        countPropertyOccurrence(name);
+      }
+    }
+
+    /**
+     * Counts references to property names that occur in a special function
+     * call.
+     *
+     * @param callNode The CALL node for a property
+     * @param t The traversal
+     */
+    private void countCallCandidates(NodeTraversal t, Node callNode) {
+      String fnName = callNode.getFirstChild().getOriginalName();
+      if (fnName == null) {
+        fnName = callNode.getFirstChild().getString();
+      }
+      Node firstArg = callNode.getSecondChild();
+      if (!firstArg.isString()) {
+        t.report(callNode, BAD_CALL, fnName);
+        return;
+      }
+
+      for (String name : DOT_SPLITTER.split(firstArg.getString())) {
+        if (!TokenStream.isJSIdentifier(name)) {
+          t.report(callNode, BAD_ARG, fnName);
+          continue;
+        }
+        if (!externedNames.contains(name)) {
+          countPropertyOccurrence(name);
+        }
+      }
+    }
+
+    /**
+     * Increments the occurrence count for a property name.
+     *
+     * @param name The property name
+     */
+    private void countPropertyOccurrence(String name) {
+      Property prop = propertyMap.get(name);
+      if (prop == null) {
+        prop = new Property(name);
+        propertyMap.put(name, prop);
+      }
+      prop.numOccurrences++;
+    }
+  }
+
+  // -------------------------------------------------------------------------
+
+  /**
+   * Encapsulates the information needed for renaming a property.
+   */
+  private static class Property {
+    final String oldName;
+    String newName;
+    int numOccurrences;
+
+    Property(String name) {
+      this.oldName = name;
+    }
+  }
+}