Repository: babel/babili
Pull request #395 (2017-01-31 00:27:53)

Reuse removed vars in mangler
Duplicate of #284. Rebased with current master.

Changes added to master / Issues reported for master and propagated to this branch:

+ [x] Destructuring bug fix (#390)
+ [x] recrawl scope (#381)
+ [x] topLevel option (#210)
+ [x] evalScopes helper (#371)
+ [x] #394 
+ [x] #365, #383 - Class Declaration Binding
+ [x] #400 Accept array as blacklist
+ [x] #414 renaming binding identifier with outer-inner collisions in bindingIdentifiers

kangax: @boopathi this looks great. The only thing I don't like is the "tracker" name since it doesn't really explain what it is or what it tracks and it might make reading code that uses it harder. Wouldn't "Scope" or "ScopeChain" be a better name?
boopathi: It is not `Scope`. It is basically -

```js
type Tracker {
  references: Map<Scope, CountedSet<String> >
  bindings: Map<Scope, Map<String, Binding> >
}
```

It counts references and maintains bindings.

Maybe call it `ScopeTracker` ?
kangax: Sure, that would be better.

On Thursday, February 2, 2017, Boopathi Rajaa <notifications@github.com>
wrote:

> It is not Scope. It is basically -
>
> type Tracker {
>   references: Map<Scope, CountedSet<String> >
>   bindings: Map<Scope, Map<String, Binding> >
> }
>
> It counts references and maintains bindings.
>
> Maybe call it ScopeTracker ?
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/babel/babili/pull/395#issuecomment-276932674>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AAABf1LiX5MEHBmmz-Wsom6fzPRVQ7f5ks5rYb03gaJpZM4LyGJw>
> .
>

codecov[bot]: # [Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=h1) Report
> Merging [#395](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=desc) into [master](https://codecov.io/gh/babel/babili/commit/673c6176615146289f80a13d362fc8719d9ac19b?src=pr&el=desc) will **decrease** coverage by `0.71%`.
> The diff coverage is `68.75%`.

[![Impacted file tree graph](https://codecov.io/gh/babel/babili/pull/395/graphs/tree.svg?token=yjfidKkIaL&src=pr&height=150&width=650)](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #395      +/-   ##
==========================================
- Coverage   83.22%   82.51%   -0.72%     
==========================================
  Files          34       38       +4     
  Lines        2539     2693     +154     
  Branches      908      943      +35     
==========================================
+ Hits         2113     2222     +109     
- Misses        255      291      +36     
- Partials      171      180       +9
```


| [Impacted Files](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...gin-minify-mangle-names/src/is-label-identifier.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2lzLWxhYmVsLWlkZW50aWZpZXIuanM=) | `100% <100%> (ø)` | |
| [...el-plugin-minify-mangle-names/src/scope-tracker.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL3Njb3BlLXRyYWNrZXIuanM=) | `60.49% <60.49%> (ø)` | |
| [...es/babel-plugin-minify-mangle-names/src/charset.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2NoYXJzZXQuanM=) | `62.06% <62.06%> (ø)` | |
| [...ages/babel-plugin-minify-mangle-names/src/index.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2luZGV4Lmpz) | `80.1% <72.89%> (+2.45%)` | :arrow_up: |
| [...abel-plugin-minify-mangle-names/src/counted-set.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2NvdW50ZWQtc2V0Lmpz) | `82.35% <82.35%> (ø)` | |
| [packages/babel-plugin-minify-simplify/src/index.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1zaW1wbGlmeS9zcmMvaW5kZXguanM=) | `80.74% <0%> (+0.12%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=footer). Last update [673c617...fe9a683](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov[bot]: # [Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=h1) Report
> Merging [#395](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=desc) into [master](https://codecov.io/gh/babel/babili/commit/673c6176615146289f80a13d362fc8719d9ac19b?src=pr&el=desc) will **decrease** coverage by `0.71%`.
> The diff coverage is `68.75%`.

[![Impacted file tree graph](https://codecov.io/gh/babel/babili/pull/395/graphs/tree.svg?token=yjfidKkIaL&src=pr&height=150&width=650)](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #395      +/-   ##
==========================================
- Coverage   83.22%   82.51%   -0.72%     
==========================================
  Files          34       38       +4     
  Lines        2539     2693     +154     
  Branches      908      943      +35     
==========================================
+ Hits         2113     2222     +109     
- Misses        255      291      +36     
- Partials      171      180       +9
```


| [Impacted Files](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...gin-minify-mangle-names/src/is-label-identifier.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2lzLWxhYmVsLWlkZW50aWZpZXIuanM=) | `100% <100%> (ø)` | |
| [...el-plugin-minify-mangle-names/src/scope-tracker.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL3Njb3BlLXRyYWNrZXIuanM=) | `60.49% <60.49%> (ø)` | |
| [...es/babel-plugin-minify-mangle-names/src/charset.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2NoYXJzZXQuanM=) | `62.06% <62.06%> (ø)` | |
| [...ages/babel-plugin-minify-mangle-names/src/index.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2luZGV4Lmpz) | `80.1% <72.89%> (+2.45%)` | :arrow_up: |
| [...abel-plugin-minify-mangle-names/src/counted-set.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2NvdW50ZWQtc2V0Lmpz) | `82.35% <82.35%> (ø)` | |
| [packages/babel-plugin-minify-simplify/src/index.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1zaW1wbGlmeS9zcmMvaW5kZXguanM=) | `80.74% <0%> (+0.12%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=footer). Last update [673c617...fe9a683](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov[bot]: # [Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=h1) Report
> Merging [#395](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=desc) into [master](https://codecov.io/gh/babel/babili/commit/e2710cf0630d19a8ec8d0f052d1f92cbc5078bb6?src=pr&el=desc) will **decrease** coverage by `0.75%`.
> The diff coverage is `68.75%`.

[![Impacted file tree graph](https://codecov.io/gh/babel/babili/pull/395/graphs/tree.svg?width=650&height=150&token=yjfidKkIaL&src=pr)](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #395      +/-   ##
==========================================
- Coverage   83.26%   82.51%   -0.76%     
==========================================
  Files          34       38       +4     
  Lines        2539     2693     +154     
  Branches      908      943      +35     
==========================================
+ Hits         2114     2222     +108     
- Misses        254      291      +37     
- Partials      171      180       +9
```


| [Impacted Files](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...gin-minify-mangle-names/src/is-label-identifier.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2lzLWxhYmVsLWlkZW50aWZpZXIuanM=) | `100% <100%> (ø)` | |
| [...el-plugin-minify-mangle-names/src/scope-tracker.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL3Njb3BlLXRyYWNrZXIuanM=) | `60.49% <60.49%> (ø)` | |
| [...es/babel-plugin-minify-mangle-names/src/charset.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2NoYXJzZXQuanM=) | `62.06% <62.06%> (ø)` | |
| [...ages/babel-plugin-minify-mangle-names/src/index.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2luZGV4Lmpz) | `80.1% <72.89%> (+2.45%)` | :arrow_up: |
| [...abel-plugin-minify-mangle-names/src/counted-set.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2NvdW50ZWQtc2V0Lmpz) | `82.35% <82.35%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=footer). Last update [e2710cf...0d29d98](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov[bot]: # [Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=h1) Report
> Merging [#395](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=desc) into [master](https://codecov.io/gh/babel/babili/commit/e2710cf0630d19a8ec8d0f052d1f92cbc5078bb6?src=pr&el=desc) will **decrease** coverage by `0.75%`.
> The diff coverage is `68.75%`.

[![Impacted file tree graph](https://codecov.io/gh/babel/babili/pull/395/graphs/tree.svg?height=150&width=650&token=yjfidKkIaL&src=pr)](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #395      +/-   ##
==========================================
- Coverage   83.26%   82.51%   -0.76%     
==========================================
  Files          34       38       +4     
  Lines        2539     2693     +154     
  Branches      908      943      +35     
==========================================
+ Hits         2114     2222     +108     
- Misses        254      291      +37     
- Partials      171      180       +9
```


| [Impacted Files](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...gin-minify-mangle-names/src/is-label-identifier.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2lzLWxhYmVsLWlkZW50aWZpZXIuanM=) | `100% <100%> (ø)` | |
| [...el-plugin-minify-mangle-names/src/scope-tracker.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL3Njb3BlLXRyYWNrZXIuanM=) | `60.49% <60.49%> (ø)` | |
| [...es/babel-plugin-minify-mangle-names/src/charset.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2NoYXJzZXQuanM=) | `62.06% <62.06%> (ø)` | |
| [...ages/babel-plugin-minify-mangle-names/src/index.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2luZGV4Lmpz) | `80.1% <72.89%> (+2.45%)` | :arrow_up: |
| [...abel-plugin-minify-mangle-names/src/counted-set.js](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=tree#diff-cGFja2FnZXMvYmFiZWwtcGx1Z2luLW1pbmlmeS1tYW5nbGUtbmFtZXMvc3JjL2NvdW50ZWQtc2V0Lmpz) | `82.35% <82.35%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=footer). Last update [e2710cf...0d29d98](https://codecov.io/gh/babel/babili/pull/395?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js b/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js
new file mode 100644
index 0000000..0bff09d
--- /dev/null
+++ b/packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js
@@ -0,0 +1,48 @@
+"use strict";
+
+module.exports = function bfsTraverseCreator({ types: t, traverse }) {
+  function getFields(path) {
+    return t.VISITOR_KEYS[path.type];
+  }
+
+  return function bfsTraverse(path, _visitor) {
+    if (!path.node) {
+      throw new Error("Not a valid path");
+    }
+    const visitor = traverse.explode(_visitor);
+
+    const queue = [path];
+    let current;
+
+    while (queue.length > 0) {
+      current = queue.shift();
+
+      // call
+      if (
+        visitor &&
+        visitor[current.type] &&
+        Array.isArray(visitor[current.type].enter)
+      ) {
+        const fns = visitor[current.type].enter;
+        for (const fn of fns) {
+          if (typeof fn === "function") fn(current);
+        }
+      }
+
+      const fields = getFields(current);
+
+      for (const field of fields) {
+        const child = current.get(field);
+
+        if (Array.isArray(child)) {
+          // visit container left to right
+          for (const c of child) {
+            if (c.node) queue.push(c);
+          }
+        } else {
+          if (child.node) queue.push(child);
+        }
+      }
+    }
+  };
+};
diff --git a/packages/babel-plugin-minify-mangle-names/src/charset.js b/packages/babel-plugin-minify-mangle-names/src/charset.js
new file mode 100644
index 0000000..43646c6
--- /dev/null
+++ b/packages/babel-plugin-minify-mangle-names/src/charset.js
@@ -0,0 +1,53 @@
+"use strict";
+
+const CHARSET = ("abcdefghijklmnopqrstuvwxyz" +
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_").split("");
+
+module.exports = class Charset {
+  constructor(shouldConsider) {
+    this.shouldConsider = shouldConsider;
+    this.chars = CHARSET.slice();
+    this.frequency = {};
+    this.chars.forEach(c => {
+      this.frequency[c] = 0;
+    });
+    this.finalized = false;
+  }
+
+  consider(str) {
+    if (!this.shouldConsider) {
+      return;
+    }
+
+    str.split("").forEach(c => {
+      if (this.frequency[c] != null) {
+        this.frequency[c]++;
+      }
+    });
+  }
+
+  sort() {
+    if (this.shouldConsider) {
+      this.chars = this.chars.sort(
+        (a, b) => this.frequency[b] - this.frequency[a]
+      );
+    }
+
+    this.finalized = true;
+  }
+
+  getIdentifier(num) {
+    if (!this.finalized) {
+      throw new Error("Should sort first");
+    }
+
+    let ret = "";
+    num++;
+    do {
+      num--;
+      ret += this.chars[num % this.chars.length];
+      num = Math.floor(num / this.chars.length);
+    } while (num > 0);
+    return ret;
+  }
+};
diff --git a/packages/babel-plugin-minify-mangle-names/src/counted-set.js b/packages/babel-plugin-minify-mangle-names/src/counted-set.js
new file mode 100644
index 0000000..034912c
--- /dev/null
+++ b/packages/babel-plugin-minify-mangle-names/src/counted-set.js
@@ -0,0 +1,28 @@
+// Set that counts
+module.exports = class CountedSet {
+  constructor() {
+    // because you can't simply extend Builtins yet
+    this.map = new Map();
+  }
+  keys() {
+    return [...this.map.keys()];
+  }
+  has(value) {
+    return this.map.has(value);
+  }
+  add(value) {
+    if (!this.has(value)) {
+      this.map.set(value, 0);
+    }
+    this.map.set(value, this.map.get(value) + 1);
+  }
+  delete(value) {
+    if (!this.has(value)) return;
+    const count = this.map.get(value);
+    if (count <= 1) {
+      this.map.delete(value);
+    } else {
+      this.map.set(value, count - 1);
+    }
+  }
+};
diff --git a/packages/babel-plugin-minify-mangle-names/src/fixup-var-scoping.js b/packages/babel-plugin-minify-mangle-names/src/fixup-var-scoping.js
new file mode 100644
index 0000000..61adfdf
--- /dev/null
+++ b/packages/babel-plugin-minify-mangle-names/src/fixup-var-scoping.js
@@ -0,0 +1,55 @@
+// this fixes a bug where converting let to var
+// doesn't change the binding's scope to function scope
+// https://github.com/babel/babel/issues/4818
+module.exports = function(mangler) {
+  mangler.program.traverse({
+    VariableDeclaration(path) {
+      if (path.node.kind !== "var") {
+        return;
+      }
+      const fnScope = path.scope.getFunctionParent();
+      const bindingIds = path.getOuterBindingIdentifierPaths();
+
+      for (const name in bindingIds) {
+        const binding = path.scope.getBinding(name);
+
+        // var isn't hoisted to fnScope
+        if (binding.scope !== fnScope) {
+          const existingBinding = fnScope.bindings[name];
+          // make sure we are clear that the fnScope doesn't already have
+          // an existing binding
+          if (!existingBinding) {
+            // move binding to the function scope
+
+            // update our scopeTracker first before
+            // we mutate the scope
+            mangler.scopeTracker.moveBinding(binding, fnScope);
+
+            fnScope.bindings[name] = binding;
+            binding.scope = fnScope;
+            delete binding.scope.bindings[name];
+          } else {
+            // we need a new binding that's valid in both the scopes
+            // binding.scope and fnScope
+            const newName = fnScope.generateUid(
+              binding.scope.generateUid(name)
+            );
+
+            // rename binding in the original scope
+            mangler.rename(binding.scope, binding, name, newName);
+
+            // move binding to fnScope as newName
+
+            // update our scopeTracker first before
+            // we mutate the scope
+            mangler.scopeTracker.moveBinding(binding, fnScope);
+
+            fnScope.bindings[newName] = binding;
+            binding.scope = fnScope;
+            delete binding.scope.bindings[newName];
+          }
+        }
+      }
+    }
+  });
+};
diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js
index 77e1082..66da547 100644
--- a/packages/babel-plugin-minify-mangle-names/src/index.js
+++ b/packages/babel-plugin-minify-mangle-names/src/index.js
@@ -1,14 +1,22 @@
 "use strict";
 
+const Charset = require("./charset");
+const ScopeTracker = require("./scope-tracker");
+const isLabelIdentifier = require("./is-label-identifier");
+const bfsTraverseCreator = require("./bfs-traverse");
+const fixupVarScoping = require("./fixup-var-scoping");
+
 const {
   markEvalScopes,
   isMarked: isEvalScopesMarked,
   hasEval
 } = require("babel-helper-mark-eval-scopes");
 
-const PATH_RENAME_MARKER = Symbol("PATH_RENAME_MARKER");
+const newIssueUrl = "https://github.com/babel/babili/issues/new";
 
-module.exports = ({ types: t, traverse }) => {
+module.exports = babel => {
+  const { types: t, traverse } = babel;
+  const bfsTraverse = bfsTraverseCreator(babel);
   const hop = Object.prototype.hasOwnProperty;
 
   class Mangler {
@@ -18,76 +26,225 @@ module.exports = ({ types: t, traverse }) => {
       {
         blacklist = {},
         keepFnName = false,
+        keepClassName = false,
         eval: _eval = false,
-        topLevel = false,
-        keepClassName = false
+        topLevel = false
       } = {}
     ) {
       this.charset = charset;
       this.program = program;
+
+      // user passed options
       this.blacklist = toObject(blacklist);
       this.keepFnName = keepFnName;
       this.keepClassName = keepClassName;
-      this.eval = _eval;
       this.topLevel = topLevel;
+      this.eval = _eval;
 
-      this.unsafeScopes = new Set();
+      // tracking
       this.visitedScopes = new Set();
-
-      this.referencesToUpdate = new Map();
+      this.scopeTracker = new ScopeTracker();
+      this.renamedNodes = new Set();
     }
 
+    /**
+     * Run the mangler
+     */
     run() {
-      this.cleanup();
+      this.crawlScope();
       this.collect();
+      this.fixup();
       this.charset.sort();
       this.mangle();
     }
 
-    cleanup() {
-      traverse.clearCache();
-      this.program.scope.crawl();
-    }
-
+    /**
+     * Tells if a variable name is blacklisted
+     * @param {String} name
+     */
     isBlacklist(name) {
       return hop.call(this.blacklist, name) && this.blacklist[name];
     }
 
-    markUnsafeScopes(scope) {
-      let evalScope = scope;
-      do {
-        this.unsafeScopes.add(evalScope);
-      } while ((evalScope = evalScope.parent));
+    /**
+     * Clears traverse cache and recrawls the AST
+     *
+     * to recompute the bindings, references, other scope information
+     * and paths because the other transformations in the same pipeline
+     * (other plugins and presets) changes the AST and does NOT update
+     * the scope objects
+     */
+    crawlScope() {
+      traverse.clearCache();
+      this.program.scope.crawl();
+    }
+
+    /**
+     * Re-crawling comes with a side-effect that let->var conversion
+     * reverts the update of the binding information (block to fn scope).
+     * This function takes care of it by updating it again.
+     *
+     * TODO: This is unnecessary work and needs to be fixed in babel.
+     * https://github.com/babel/babel/issues/4818
+     *
+     * When this is removed, remember to remove fixup's dependency in
+     * ScopeTracker
+     */
+    fixup() {
+      fixupVarScoping(this);
     }
 
+    /**
+     * A single pass through the AST to collect info for
+     *
+     * 1. Scope Tracker
+     * 2. Unsafe Scopes (direct eval scopes)
+     * 3. Charset considerations for better gzip compression
+     *
+     * Traversed in the same fashion(BFS) the mangling is done
+     */
     collect() {
       const mangler = this;
+      const { scopeTracker } = mangler;
 
+      scopeTracker.addScope(this.program.scope);
+
+      /**
+       * Same usage as in DCE, whichever runs first
+       */
       if (!isEvalScopesMarked(mangler.program.scope)) {
         markEvalScopes(mangler.program);
       }
 
-      if (this.charset.shouldConsider) {
-        const collectVisitor = {
-          Identifier(path) {
-            const { node } = path;
+      /**
+       * The visitors to be used in traversal.
+       *
+       * Note: BFS traversal supports only the `enter` handlers, `exit`
+       * handlers are simply dropped without Errors
+       *
+       * Collects items defined in the ScopeTracker
+       */
+      const collectVisitor = {
+        Scopable({ scope }) {
+          scopeTracker.addScope(scope);
+
+          // Collect bindings defined in the scope
+          Object.keys(scope.bindings).forEach(name => {
+            scopeTracker.addBinding(scope.bindings[name]);
+          });
+        },
+        /**
+         * This is necessary because, in Babel, the scope.references
+         * does NOT contain the references in that scope. Only the program
+         * scope (top most level) contains all the references.
+         *
+         * We collect the references in a fashion where all the scopes between
+         * and including the referenced scope and scope where it is declared
+         * is considered as scope referencing that identifier
+         */
+        ReferencedIdentifier(path) {
+          if (isLabelIdentifier(path)) return;
+          const { scope, node: { name } } = path;
+          const binding = scope.getBinding(name);
+          if (!binding) {
+            // Do not collect globals as they are already available via
+            // babel's API
+            if (scope.hasGlobal(name)) return;
+            // This should NOT happen ultimately. Panic if this code block is
+            // reached
+            throw new Error(
+              "Binding not found for ReferencedIdentifier. " +
+                name +
+                "Please report this at " +
+                newIssueUrl
+            );
+          } else {
+            // Add it to our scope tracker if everything is fine
+            scopeTracker.addReference(scope, binding, name);
+          }
+        },
+
+        /**
+         * This is useful to detect binding ids and add them to the
+         * scopeTracker's bindings
+         */
+        BindingIdentifier(path) {
+          if (isLabelIdentifier(path)) return;
+
+          const { scope, node: { name } } = path;
+          const binding = scope.getBinding(name);
 
+          if (!binding) {
+            // ignore the globals as it's available via Babel's API
+            if (scope.hasGlobal(name)) return;
+
+            // Ignore the NamedExports as they should NOT be mangled
             if (
-              path.parentPath.isMemberExpression({ property: node }) ||
-              path.parentPath.isObjectProperty({ key: node })
+              path.parentPath.isExportSpecifier() &&
+              path.parentKey === "exported"
             ) {
-              mangler.charset.consider(node.name);
+              return;
             }
-          },
-          Literal({ node }) {
-            mangler.charset.consider(String(node.value));
+
+            // This should NOT happen ultimately. Panic if this code is reached
+            throw new Error(
+              "Binding not found for BindingIdentifier. " +
+                name +
+                "Please report this at " +
+                newIssueUrl
+            );
           }
-        };
 
-        mangler.program.traverse(collectVisitor);
+          /**
+           * Detect constant violations
+           *
+           * If it's a constant violation, then add the Identifier Path as
+           * a Reference instead of Binding - This is because the ScopeTracker
+           * tracks these Re-declaration and mutation of variables as References
+           * as it is simple to rename them
+           */
+          if (binding.identifier === path.node) {
+            scopeTracker.addBinding(binding);
+          } else {
+            // constant violation
+            scopeTracker.addReference(scope, binding, name);
+          }
+        }
+      };
+
+      /**
+       * These visitors are for collecting the Characters used in the program
+       * to measure the frequency and generate variable names for mangling so
+       * as to improve the gzip compression - as gzip likes repetition
+       */
+      if (this.charset.shouldConsider) {
+        collectVisitor.Identifier = function Identifer(path) {
+          const { node } = path;
+
+          // We don't mangle properties, so we collect them as they contribute
+          // to the frequency of characters
+          if (
+            path.parentPath.isMemberExpression({ property: node }) ||
+            path.parentPath.isObjectProperty({ key: node })
+          ) {
+            mangler.charset.consider(node.name);
+          }
+        };
+        collectVisitor.Literal = function Literal({ node }) {
+          mangler.charset.consider(String(node.value));
+        };
       }
+
+      // Traverse the AST
+      bfsTraverse(mangler.program, collectVisitor);
     }
 
+    /**
+     * Tells if a binding is exported as a NamedExport - so as to NOT mangle
+     *
+     * Babel treats NamedExports as a binding referenced by this NamedExport decl
+     * @param {Binding} binding
+     */
     isExportedWithName(binding) {
       // short circuit
       if (!this.topLevel) {
@@ -106,51 +263,55 @@ module.exports = ({ types: t, traverse }) => {
       return false;
     }
 
+    /**
+     * Mangle the scope
+     * @param {Scope} scope
+     */
     mangleScope(scope) {
       const mangler = this;
+      const { scopeTracker } = mangler;
 
+      // Unsafe Scope
       if (!mangler.eval && hasEval(scope)) return;
 
+      // Already visited
+      // This is because for a function, in Babel, the function and
+      // the function body's BlockStatement has the same scope, and will
+      // be visited twice by the Scopable handler, and we want to mangle
+      // it only once
       if (mangler.visitedScopes.has(scope)) return;
       mangler.visitedScopes.add(scope);
 
+      // Helpers to generate names
       let i = 0;
       function getNext() {
         return mangler.charset.getIdentifier(i++);
       }
+      function resetNext() {
+        i = 0;
+      }
 
-      // This is useful when we have vars of single character
-      // => var a, ...z, A, ...Z, $, _;
-      // to
-      // => var aa, a, b ,c;
-      // instead of
-      // => var aa, ab, ...;
-      // TODO:
-      // Re-enable after enabling this feature
-      // This doesn't work right now as we are concentrating
-      // on performance improvements
-      // function resetNext() {
-      //   i = 0;
-      // }
-
-      const bindings = scope.getAllBindings();
-      const names = Object.keys(bindings);
+      const bindings = scopeTracker.bindings.get(scope);
+      const names = [...bindings.keys()];
 
+      /**
+       * 1. Iterate through the list of BindingIdentifiers
+       * 2. Rename each of them in-place
+       * 3. Update the scope tree.
+       */
       for (let i = 0; i < names.length; i++) {
         const oldName = names[i];
-        const binding = bindings[oldName];
+        const binding = bindings.get(oldName);
 
+        // Names which should NOT be mangled
         if (
-          // arguments
+          // arguments - for non-strict mode
           oldName === "arguments" ||
-          // other scope bindings
-          !scope.hasOwnBinding(oldName) ||
           // labels
           binding.path.isLabeledStatement() ||
           // ClassDeclaration has binding in two scopes
           //   1. The scope in which it is declared
           //   2. The class's own scope
-          // - https://github.com/babel/babel/issues/5156
           (binding.path.isClassDeclaration() && binding.path === scope.path) ||
           // blacklisted
           mangler.isBlacklist(oldName) ||
@@ -169,76 +330,113 @@ module.exports = ({ types: t, traverse }) => {
           next = getNext();
         } while (
           !t.isValidIdentifier(next) ||
-          hop.call(bindings, next) ||
+          scopeTracker.hasBinding(scope, next) ||
           scope.hasGlobal(next) ||
-          scope.hasReference(next)
+          scopeTracker.hasReference(scope, next) ||
+          !scopeTracker.canUseInReferencedScopes(binding, next)
         );
 
-        // TODO:
-        // re-enable this - check above
-        // resetNext();
-        mangler.rename(scope, oldName, next);
+        // Reset so variables which are removed can be reused
+        resetNext();
+
+        // Once we detected a valid `next` Identifier which could be used,
+        // call the renamer
+        mangler.rename(scope, binding, oldName, next);
       }
     }
 
+    /**
+     * The mangle function that traverses through all the Scopes in a BFS
+     * fashion - calls mangleScope
+     */
     mangle() {
       const mangler = this;
 
-      if (mangler.topLevel) {
-        mangler.mangleScope(mangler.program.scope);
-      }
-
-      this.program.traverse({
+      bfsTraverse(this.program, {
         Scopable(path) {
-          mangler.mangleScope(path.scope);
+          if (!path.isProgram() || mangler.topLevel)
+            mangler.mangleScope(path.scope);
         }
       });
     }
 
-    rename(scope, oldName, newName) {
-      const binding = scope.getBinding(oldName);
-
-      // rename at the declaration level
-      const bindingPaths = binding.path.getBindingIdentifierPaths(true, false);
-
-      // we traverse through all bindingPaths because,
-      // there is no binding.identifierPath in babel
-      for (const name in bindingPaths) {
+    /**
+     * Given a NodePath, collects all the Identifiers which are BindingIdentifiers
+     * and replaces them with the new name
+     *
+     * For example,
+     *   var a = 1, { b } = c; // a and b are BindingIdentifiers
+     *
+     * @param {NodePath} path
+     * @param {String} oldName
+     * @param {String} newName
+     * @param {Function} predicate
+     */
+    renameBindingIds(path, oldName, newName, predicate = () => true) {
+      const bindingIds = path.getBindingIdentifierPaths(true, false);
+      for (const name in bindingIds) {
         if (name !== oldName) continue;
-        for (const idPath of bindingPaths[name]) {
-          if (binding.identifier === idPath.node) {
+        for (const idPath of bindingIds[name]) {
+          if (predicate(idPath)) {
+            this.renamedNodes.add(idPath.node);
             idPath.replaceWith(t.identifier(newName));
-            binding.identifier = idPath.node;
-            idPath[PATH_RENAME_MARKER] = true;
+            this.renamedNodes.add(idPath.node);
           }
         }
       }
+    }
 
-      const { bindings } = scope;
-      bindings[newName] = binding;
-      delete bindings[oldName];
+    /**
+     * The Renamer:
+     * Renames the following for one Binding in a Scope
+     *
+     * 1. Binding in that Scope
+     * 2. All the Binding's constant violations
+     * 3. All its References
+     * 4. Updates mangler.scopeTracker
+     * 5. Updates Babel's Scope tracking
+     *
+     * @param {Scope} scope
+     * @param {Binding} binding
+     * @param {String} oldName
+     * @param {String} newName
+     */
+    rename(scope, binding, oldName, newName) {
+      const mangler = this;
+      const { scopeTracker } = mangler;
 
-      // update all constant violations & redeclarations
+      // rename at the declaration level
+      this.renameBindingIds(
+        binding.path,
+        oldName,
+        newName,
+        idPath => idPath.node === binding.identifier
+      );
+
+      // update mangler's ScopeTracker
+      scopeTracker.renameBinding(scope, oldName, newName);
+
+      // update all constant violations
       const violations = binding.constantViolations;
       for (let i = 0; i < violations.length; i++) {
         if (violations[i].isLabeledStatement()) continue;
 
-        const bindings = violations[i].getBindingIdentifierPaths();
-        Object.keys(bindings).map(b => {
-          if (b === oldName && !bindings[b][PATH_RENAME_MARKER]) {
-            bindings[b].replaceWith(t.identifier(newName));
-            bindings[b][PATH_RENAME_MARKER] = true;
-          }
-        });
+        this.renameBindingIds(violations[i], oldName, newName);
+        scopeTracker.updateReference(
+          violations[i].scope,
+          binding,
+          oldName,
+          newName
+        );
       }
 
       // update all referenced places
       const refs = binding.referencePaths;
       for (let i = 0; i < refs.length; i++) {
         const path = refs[i];
-        if (path[PATH_RENAME_MARKER]) continue;
 
         const { node } = path;
+
         if (!path.isIdentifier()) {
           // Ideally, this should not happen
           // it happens in these places now -
@@ -250,25 +448,59 @@ module.exports = ({ types: t, traverse }) => {
           // replacement in dce from `x` to `!x` gives referencePath as `!x`
           path.traverse({
             ReferencedIdentifier(refPath) {
-              if (
-                refPath.node.name === oldName &&
-                refPath.scope === scope &&
-                !refPath[PATH_RENAME_MARKER]
-              ) {
-                refPath.node.name = newName;
+              if (refPath.node.name !== oldName) {
+                return;
+              }
+              const actualBinding = refPath.scope.getBinding(oldName);
+              if (actualBinding !== binding) {
+                return;
               }
+              mangler.renamedNodes.add(refPath.node);
+              refPath.replaceWith(t.identifier(newName));
+              mangler.renamedNodes.add(refPath.node);
+
+              scopeTracker.updateReference(
+                refPath.scope,
+                binding,
+                oldName,
+                newName
+              );
             }
           });
         } else if (!isLabelIdentifier(path)) {
-          node.name = newName;
+          if (path.node.name === oldName) {
+            mangler.renamedNodes.add(path.node);
+            path.replaceWith(t.identifier(newName));
+            mangler.renamedNodes.add(path.node);
+            scopeTracker.updateReference(path.scope, binding, oldName, newName);
+          } else if (mangler.renamedNodes.has(path.node)) {
+            // already renamed,
+            // just update the references
+            scopeTracker.updateReference(path.scope, binding, oldName, newName);
+          } else {
+            throw new Error(
+              `Unexpected Rename Error: ` +
+                `Trying to replace ${node.name}: from ${oldName} to ${newName}` +
+                `Please report it at ${newIssueUrl}`
+            );
+          }
         }
+        // else label identifier - silently ignore
       }
+
+      // update babel's scope tracking
+      const { bindings } = scope;
+      bindings[newName] = binding;
+      delete bindings[oldName];
     }
   }
 
   return {
     name: "minify-mangle-names",
     visitor: {
+      /**
+       * Mangler is run as a single pass. It's the same pattern as used in DCE
+       */
       Program: {
         exit(path) {
           // If the source code is small then we're going to assume that the user
@@ -287,58 +519,6 @@ module.exports = ({ types: t, traverse }) => {
   };
 };
 
-const CHARSET = ("abcdefghijklmnopqrstuvwxyz" +
-  "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_").split("");
-
-class Charset {
-  constructor(shouldConsider) {
-    this.shouldConsider = shouldConsider;
-    this.chars = CHARSET.slice();
-    this.frequency = {};
-    this.chars.forEach(c => {
-      this.frequency[c] = 0;
-    });
-    this.finalized = false;
-  }
-
-  consider(str) {
-    if (!this.shouldConsider) {
-      return;
-    }
-
-    str.split("").forEach(c => {
-      if (this.frequency[c] != null) {
-        this.frequency[c]++;
-      }
-    });
-  }
-
-  sort() {
-    if (this.shouldConsider) {
-      this.chars = this.chars.sort(
-        (a, b) => this.frequency[b] - this.frequency[a]
-      );
-    }
-
-    this.finalized = true;
-  }
-
-  getIdentifier(num) {
-    if (!this.finalized) {
-      throw new Error("Should sort first");
-    }
-
-    let ret = "";
-    num++;
-    do {
-      num--;
-      ret += this.chars[num % this.chars.length];
-      num = Math.floor(num / this.chars.length);
-    } while (num > 0);
-    return ret;
-  }
-}
-
 // convert value to object
 function toObject(value) {
   if (!Array.isArray(value)) {
@@ -361,12 +541,3 @@ function isFunction(path) {
 function isClass(path) {
   return path.isClassExpression() || path.isClassDeclaration();
 }
-
-function isLabelIdentifier(path) {
-  const { node } = path;
-  return (
-    path.parentPath.isLabeledStatement({ label: node }) ||
-    path.parentPath.isBreakStatement({ label: node }) ||
-    path.parentPath.isContinueStatement({ label: node })
-  );
-}
diff --git a/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js b/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js
new file mode 100644
index 0000000..4cc15cf
--- /dev/null
+++ b/packages/babel-plugin-minify-mangle-names/src/is-label-identifier.js
@@ -0,0 +1,10 @@
+module.exports = isLabelIdentifier;
+
+function isLabelIdentifier(path) {
+  const { node } = path;
+  return (
+    path.parentPath.isLabeledStatement({ label: node }) ||
+    path.parentPath.isBreakStatement({ label: node }) ||
+    path.parentPath.isContinueStatement({ label: node })
+  );
+}
diff --git a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js
new file mode 100644
index 0000000..1e0c8be
--- /dev/null
+++ b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js
@@ -0,0 +1,214 @@
+"use strict";
+
+const CountedSet = require("./counted-set");
+const isLabelIdentifier = require("./is-label-identifier");
+
+const newIssueUrl = "https://github.com/babel/babili/issues/new";
+
+/**
+ * ScopeTracker
+ *   references: Map<Scope, CountedSet<String> >
+ *   bindings: Map<Scope, Map<String, Binding> >
+ */
+module.exports = class ScopeTracker {
+  constructor() {
+    this.references = new Map();
+    this.bindings = new Map();
+  }
+
+  /**
+   * Register a new Scope and initiliaze it with empty sets
+   * @param {Scope} scope
+   */
+  addScope(scope) {
+    if (!this.references.has(scope)) {
+      this.references.set(scope, new CountedSet());
+    }
+    if (!this.bindings.has(scope)) {
+      this.bindings.set(scope, new Map());
+    }
+  }
+
+  /**
+   * Add reference to all Scopes between and including the ReferencedScope
+   * and Binding's Scope
+   * @param {Scope} scope
+   * @param {Binding} binding
+   * @param {String} name
+   */
+  addReference(scope, binding, name) {
+    let parent = scope;
+    do {
+      this.references.get(parent).add(name);
+      if (!binding) {
+        throw new Error(
+          `Binding Not Found for ${name} during scopeTracker.addRefernce: ` +
+            `Please report at ${newIssueUrl}`
+        );
+      }
+      if (binding.scope === parent) break;
+    } while ((parent = parent.parent));
+  }
+
+  /**
+   * has a Reference in the given {Scope} or a child Scope
+   *
+   * Refer {addReference} to know why the following call will be valid
+   * for detecting references in child Scopes
+   *
+   * @param {Scope} scope
+   * @param {String} name
+   */
+  hasReference(scope, name) {
+    return this.references.get(scope).has(name);
+  }
+
+  /**
+   * Update reference count in all scopes between and including the
+   * Referenced Scope and the Binding's Scope
+   *
+   * @param {Scope} scope
+   * @param {Binding} binding
+   * @param {String} oldName
+   * @param {String} newName
+   */
+  updateReference(scope, binding, oldName, newName) {
+    let parent = scope;
+    do {
+      const ref = this.references.get(parent);
+
+      ref.delete(oldName);
+      ref.add(newName);
+
+      if (!binding) {
+        // Something went wrong - panic
+        throw new Error(
+          "Binding Not Found during scopeTracker.updateRefernce: " +
+            `Updating "${oldName}" to "${newName}"` +
+            `Please report at ${newIssueUrl}`
+        );
+      }
+
+      if (binding.scope === parent) break;
+    } while ((parent = parent.parent));
+  }
+
+  /**
+   * has either a Binding or a Reference
+   * @param {Scope} scope
+   * @param {Binding} binding
+   * @param {String} name
+   */
+  hasBindingOrReference(scope, binding, name) {
+    return this.hasReference(scope, name) || this.hasBinding(scope, name);
+  }
+
+  /**
+   * For a Binding visit all places where the Binding is used and detect
+   * if the newName {next} can be used in all these places
+   *
+   * 1. binding's own scope
+   * 2. constant violations' scopes
+   * 3. referencePaths' scopes
+   *
+   * @param {Binding} binding
+   * @param {String} next
+   */
+  canUseInReferencedScopes(binding, next) {
+    const tracker = this;
+
+    if (tracker.hasBindingOrReference(binding.scope, binding, next)) {
+      return false;
+    }
+
+    for (let i = 0; i < binding.constantViolations.length; i++) {
+      const violation = binding.constantViolations[i];
+      if (tracker.hasBindingOrReference(violation.scope, binding, next)) {
+        return false;
+      }
+    }
+
+    for (let i = 0; i < binding.referencePaths.length; i++) {
+      const ref = binding.referencePaths[i];
+      if (!ref.isIdentifier()) {
+        let canUse = true;
+        ref.traverse({
+          ReferencedIdentifier(path) {
+            if (path.node.name !== next) return;
+            if (tracker.hasBindingOrReference(path.scope, binding, next)) {
+              canUse = false;
+            }
+          }
+        });
+        if (!canUse) {
+          return canUse;
+        }
+      } else if (!isLabelIdentifier(ref)) {
+        if (tracker.hasBindingOrReference(ref.scope, binding, next)) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Add a binding to Tracker in binding's own Scope
+   * @param {Binding} binding
+   */
+  addBinding(binding) {
+    if (!binding) {
+      return;
+    }
+
+    const bindings = this.bindings.get(binding.scope);
+    const existingBinding = bindings.get(binding.identifier.name);
+
+    if (existingBinding && existingBinding !== binding) {
+      throw new Error(
+        "Binding " +
+          existingBinding.identifier.name +
+          "already exists. " +
+          "Trying to add " +
+          binding.identifier.name
+      );
+    }
+
+    bindings.set(binding.identifier.name, binding);
+  }
+
+  /**
+   * Moves Binding from it's own Scope to {toScope}
+   *
+   * required for fixup-var-scope
+   *
+   * @param {Binding} binding
+   * @param {Scope} toScope
+   */
+  moveBinding(binding, toScope) {
+    this.bindings.get(binding.scope).delete(binding.identifier.name);
+    this.bindings.get(toScope).set(binding.identifier.name, binding);
+  }
+
+  /**
+   * has a Binding in the current {Scope}
+   * @param {Scope} scope
+   * @param {String} name
+   */
+  hasBinding(scope, name) {
+    return this.bindings.get(scope).has(name);
+  }
+
+  /**
+   * Update the ScopeTracker on rename
+   * @param {Scope} scope
+   * @param {String} oldName
+   * @param {String} newName
+   */
+  renameBinding(scope, oldName, newName) {
+    const bindings = this.bindings.get(scope);
+    bindings.set(newName, bindings.get(oldName));
+    bindings.delete(oldName);
+  }
+};


Test patch:
diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js
index 2299ee5..c1d8ab5 100644
--- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js
+++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js
@@ -11,50 +11,51 @@ function transform(code, options = {}, sourceType = "script") {
   }).code;
 }
 
+function transformWithSimplify(code, options = {}, sourceType = "script") {
+  return babel.transform(code, {
+    sourceType,
+    plugins: [
+      require("../../babel-plugin-minify-simplify/src/index"),
+      [require("../src/index"), options]
+    ]
+  }).code;
+}
+
 describe("mangle-names", () => {
   it("should not mangle names in the global namespace", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       var Foo = 1;
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       var Foo = 1;
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should mangle names", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         var xxx = 1;
         if (xxx) {
           console.log(xxx);
         }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo() {
         var a = 1;
         if (a) {
           console.log(a);
         }
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should handle name collisions", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         var x = 2;
         var xxx = 1;
@@ -62,10 +63,8 @@ describe("mangle-names", () => {
           console.log(xxx + x);
         }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo() {
         var a = 2;
         var b = 1;
@@ -73,15 +72,13 @@ describe("mangle-names", () => {
           console.log(b + a);
         }
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should be fine with shadowing", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       var a = 1;
       function foo() {
         var xxx = 1;
@@ -89,127 +86,105 @@ describe("mangle-names", () => {
           console.log(xxx);
         }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       var a = 1;
       function foo() {
-        var b = 1;
-        if (b) {
-          console.log(b);
+        var a = 1;
+        if (a) {
+          console.log(a);
         }
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should not shadow outer references", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function bar() {
         function foo(a, b, c) {
           lol(a,b,c);
         }
-
         function lol() {}
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function bar() {
-        function d(f, g, h) {
-          e(f, g, h);
+        function a(e, a, b) {
+          d(e, a, b);
         }
-
-        function e() {}
+        function d() {}
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should mangle args", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo(xxx) {
         if (xxx) {
           console.log(xxx);
         }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo(a) {
         if (a) {
           console.log(a);
         }
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should ignore labels", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         meh: for (;;) {
           continue meh;
         }
       }
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       function foo() {
         meh: for (;;) {
           continue meh;
         }
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should not have labels conflicting with bindings", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         meh: for (;;) {
           var meh;
           break meh;
         }
       }
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       function foo() {
         meh: for (;;) {
           var a;
           break meh;
         }
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   // https://phabricator.babeljs.io/T6957
   it("labels should not shadow bindings", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         var meh;
         meh: for (;;) {
@@ -217,11 +192,9 @@ describe("mangle-names", () => {
         }
         return meh;
       }
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       function foo() {
         var a;
         meh: for (;;) {
@@ -229,15 +202,13 @@ describe("mangle-names", () => {
         }
         return a;
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("labels should not shadow bindings 2", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function f(a) {
         try {
           a: {
@@ -245,25 +216,21 @@ describe("mangle-names", () => {
           }
         } catch ($a) { }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function f(b) {
         try {
           a: {
             console.log(b);
           }
-        } catch (c) {}
+        } catch (a) {}
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should be order independent", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         function bar(aaa, bbb, ccc) {
           baz(aaa, bbb, ccc);
@@ -274,30 +241,26 @@ describe("mangle-names", () => {
         }
         bar();
       }
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       function foo() {
-        function a(c, d, e) {
-          b(c, d, e);
+        function a(a, c, d) {
+          b(a, c, d);
         }
         function b() {
-          var c = who();
-          c.bam();
+          var a = who();
+          a.bam();
         }
         a();
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should be order independent 2", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         (function bar() {
           bar();
@@ -307,29 +270,25 @@ describe("mangle-names", () => {
           };
         })();
       }
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       function foo() {
         (function a() {
           a();
           return function () {
-            var b = wow();
-            b.woo();
+            var a = wow();
+            a.woo();
           };
         })();
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
-  it("should handle only think in function scopes", () => {
-    const source = unpad(
-      `
+  it("should handle only thunk in function scopes", () => {
+    const source = unpad(`
       function foo() {
         function xx(bar, baz) {
           if (1) {
@@ -338,27 +297,23 @@ describe("mangle-names", () => {
         }
         function yy(){}
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo() {
-        function a(c, d) {
+        function a(a, c) {
           if (1) {
-            b(c, d);
+            b(a, c);
           }
         }
         function b() {}
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should be fine with shadowing 2", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         function xx(bar, baz) {
           return function(boo, foo) {
@@ -367,27 +322,23 @@ describe("mangle-names", () => {
         }
         function yy(){}
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo() {
-        function a(c, d) {
-          return function (e, f) {
-            c(e, f);
+        function a(a, b) {
+          return function (b, c) {
+            a(b, c);
           };
         }
         function b() {}
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should not be confused by scopes", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         function bar() {
           var baz;
@@ -397,28 +348,24 @@ describe("mangle-names", () => {
         }
         function bam() {}
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo() {
         function a() {
-          var c;
-          if (c) {
+          var a;
+          if (a) {
             b();
           }
         }
         function b() {}
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should not be confused by scopes (closures)", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         function bar(baz) {
           return function() {
@@ -427,100 +374,85 @@ describe("mangle-names", () => {
         }
         function bam() {}
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo() {
-        function a(c) {
+        function a(a) {
           return function () {
             b();
           };
         }
         function b() {}
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should handle recursion", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function bar() {
         function foo(a, b, c) {
           foo(a,b,c);
         }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function bar() {
-        function d(e, f, g) {
-          d(e, f, g);
+        function d(e, a, b) {
+          d(e, a, b);
         }
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should handle global name conflict", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function e() {
         function foo() {
           b = bar();
         }
         function bar() {}
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function e() {
         function a() {
           b = c();
         }
         function c() {}
       }
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should handle global name", () => {
-    const source = unpad(
-      `
-      function Foo() {
-        var foo = 3;
+    const source = unpad(`
+      function foo() {
         var bar = 1;
         var baz = 2;
       }
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
-      function Foo() {
-        var foo = 3;
-        var a = 1;
-        var b = 2;
+    const expected = unpad(`
+      function a() {
+        var bar = 1;
+        var a = 2;
       }
-    `
-    );
-    expect(transform(source, { blacklist: { foo: true, bar: false } })).toBe(
-      expected
-    );
+    `);
+    expect(
+      transform(source, {
+        blacklist: { foo: false, bar: true },
+        topLevel: true
+      })
+    ).toBe(expected);
   });
 
   it("should handle deeply nested paths with no bindings", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function xoo() {
         function foo(zz, xx, yy) {
           function bar(zip, zap, zop) {
@@ -533,52 +465,44 @@ describe("mangle-names", () => {
           }
         }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function xoo() {
-        function a(b, c, d) {
-          function e(f, g, h) {
-            return function (i) {
-              g();
+        function a(a, b, c) {
+          function d(a, b, c) {
+            return function (c) {
+              b();
               return function () {
-                f();
+                a();
               };
             };
           }
         }
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should handle try/catch", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function xoo() {
         var e;
         try {} catch (e) {
 
         }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function xoo() {
         var a;
-        try {} catch (b) {}
+        try {} catch (a) {}
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should not mangle vars in scope with eval", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         var inScopeOuter = 1;
         (function () {
@@ -589,10 +513,8 @@ describe("mangle-names", () => {
           })();
         })();
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo() {
         var inScopeOuter = 1;
         (function () {
@@ -603,36 +525,30 @@ describe("mangle-names", () => {
           })();
         })();
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should mangle names with local eval bindings", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function eval() {}
       function foo() {
         var bar = 1;
         eval('...');
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function eval() {}
       function foo() {
         var a = 1;
         eval('...');
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should mangle names with option eval = true", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         var inScopeOuter = 1;
         (function () {
@@ -643,28 +559,24 @@ describe("mangle-names", () => {
           })();
         })();
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function foo() {
         var a = 1;
         (function () {
-          var b = 2;
+          var a = 2;
           eval("...");
           (function () {
-            var c = 1;
+            var a = 1;
           })();
         })();
       }
-    `
-    );
+    `);
     expect(transform(source, { eval: true })).toBe(expected);
   });
 
   it("should integrate with block scoping plugin", () => {
-    const srcTxt = unpad(
-      `
+    const srcTxt = unpad(`
       function f(x) {
         for (let i = 0; i; i++) {
           let n;
@@ -674,8 +586,7 @@ describe("mangle-names", () => {
           g(() => n);
         }
       }
-    `
-    );
+    `);
 
     const first = babel.transform(srcTxt, {
       plugins: ["transform-es2015-block-scoping"],
@@ -688,17 +599,16 @@ describe("mangle-names", () => {
       plugins: [require("../src/index")]
     }).code;
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       function f(a) {
-        var b = function (d) {
-          var e = void 0;
-          if (e) {
+        var b = function (a) {
+          var b = void 0;
+          if (b) {
             return {
               v: void 0
             };
           }
-          g(() => e);
+          g(() => b);
         };
 
         for (var d = 0; d; d++) {
@@ -706,15 +616,13 @@ describe("mangle-names", () => {
           if (typeof c === "object") return c.v;
         }
       }
-    `
-    );
+    `);
 
     expect(actual).toBe(expected);
   });
 
   it("should integrate with block scoping plugin 2", () => {
-    const srcTxt = unpad(
-      `
+    const srcTxt = unpad(`
       (function () {
         function bar() {
           if (smth) {
@@ -726,8 +634,7 @@ describe("mangle-names", () => {
         function foo() { }
         module.exports = { bar };
       })();
-    `
-    );
+    `);
 
     const first = babel.transform(srcTxt, {
       plugins: ["transform-es2015-block-scoping"],
@@ -740,28 +647,25 @@ describe("mangle-names", () => {
       plugins: [require("../src/index")]
     }).code;
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       (function () {
         function a() {
           if (smth) {
-            var c = blah();
-            c();
+            var a = blah();
+            a();
           }
           b();
         }
         function b() {}
         module.exports = { bar: a };
       })();
-    `
-    );
+    `);
 
     expect(actual).toBe(expected);
   });
 
   it("should keep mangled named consistent across scopes when defined later on", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       (function() {
         function foo() {
           {
@@ -775,15 +679,13 @@ describe("mangle-names", () => {
 
         function bar() {}
       }());
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       (function () {
         function a() {
           {
-            var c = true;
+            var a = true;
 
             {
               b();
@@ -793,15 +695,13 @@ describe("mangle-names", () => {
 
         function b() {}
       })();
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   it("should correctly mangle in nested loops", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       (function () {
         for (let x in foo) {
           for (let y in foo[x]) {
@@ -809,11 +709,9 @@ describe("mangle-names", () => {
           }
         }
       })();
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       (function () {
         for (let a in foo) {
           for (let b in foo[a]) {
@@ -821,16 +719,14 @@ describe("mangle-names", () => {
           }
         }
       })();
-    `
-    );
+    `);
 
     expect(transform(source)).toBe(expected);
   });
 
   // #issue55, #issue57
   it("should correctly mangle function declarations in different order", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       (function(){
         (function() {
           for (let x in y) y[x];
@@ -838,8 +734,7 @@ describe("mangle-names", () => {
         })();
         function g() {}
       })();
-    `
-    );
+    `);
 
     const ast = babel.transform(source, {
       presets: ["env"],
@@ -854,8 +749,7 @@ describe("mangle-names", () => {
       plugins: [require("../src/index")]
     }).code;
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       "use strict";
 
       (function () {
@@ -868,15 +762,13 @@ describe("mangle-names", () => {
         })();
         function a() {}
       })();
-    `
-    );
+    `);
 
     expect(actual).toBe(expected);
   });
 
   it("should NOT mangle functions when keepFnName is true", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       (function() {
         var foo = function foo() {
           foo();
@@ -888,10 +780,8 @@ describe("mangle-names", () => {
         var baz = foo;
         baz();
       })();
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       (function () {
         var a = function foo() {
           foo();
@@ -903,14 +793,12 @@ describe("mangle-names", () => {
         var b = a;
         b();
       })();
-    `
-    );
+    `);
     expect(transform(source, { keepFnName: true })).toBe(expected);
   });
 
   it("should NOT mangle classes when keepClassName is true", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       (function() {
         class Foo {}
         const Bar = class Bar extends Foo {}
@@ -920,10 +808,8 @@ describe("mangle-names", () => {
         }
         bar();
       })();
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       (function () {
         class Foo {}
         const b = class Bar extends Foo {};
@@ -933,38 +819,32 @@ describe("mangle-names", () => {
         }
         a();
       })();
-    `
-    );
+    `);
     expect(transform(source, { keepClassName: true })).toBe(expected);
   });
 
   it("should mangle variable re-declaration / K violations", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       !function () {
         var foo = 1;
         foo++;
         var foo = 2;
         foo++;
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       !function () {
         var a = 1;
         a++;
         var a = 2;
         a++;
       };
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should handle K violations - 2", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       !function () {
         var bar = 1;
         bar--;
@@ -977,96 +857,122 @@ describe("mangle-names", () => {
           foo(foo);
         }
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       !function () {
         var b = 1;
         b--;
         var b = 10;
         a(b);
         function a() {
-          var c = 10;
-          c++;
-          var c = 20;
-          c(c);
+          var a = 10;
+          a++;
+          var a = 20;
+          a(a);
         }
       };
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should work with redeclarations", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       (function () {
         var x = y;
         x = z;
         x;
       })();
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       (function () {
         var a = y;
         a = z;
         a;
       })();
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should reuse removed vars", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function Foo() {
         var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
         var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z;
         var $, _;
+        a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
+        A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z;
+        $, _;
+        function Foo() {
+          var a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
+          var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z;
+          var $, _;
+          a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
+          A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z;
+          $, _;
+          function Foo() {
+            var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
+            var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z;
+            var $, _;
+            a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
+            A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z;
+            $, _;
+          }
+          Foo();
+        }
+        Foo();
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       function Foo() {
-        var aa, ba, ca, da, ea, fa, ga, ha, ia, ja, ka, la, ma, na, oa, pa, qa, ra, sa, ta, ua, va, wa, xa, ya, za;
-        var Aa, Ba, Ca, Da, Ea, Fa, Ga, Ha, Ia, Ja, Ka, La, Ma, Na, Oa, Pa, Qa, Ra, Sa, Ta, Ua, Va, Wa, Xa, Ya, Za;
-        var $a, _a;
+        var ba, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y;
+        var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y;
+        var Z, $;
+        ba, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y;
+        z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y;
+        Z, $;
+        function aa() {
+          var aa, a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y;
+          var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y;
+          var Z, $;
+          aa, a, b, c, d, e, f, g, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y;
+          z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y;
+          Z, $;
+          function h() {
+            var aa, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y;
+            var z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y;
+            var Z, $;
+            aa, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y;
+            z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y;
+            Z, $;
+          }
+          h();
+        }
+        aa();
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should mangle both referenced and binding identifiers with K violations", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       (function () {
         var foo = bar,
             foo = baz;
         foo;
       })();
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       (function () {
         var a = bar,
             a = baz;
         a;
       })();
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should handle export declarations", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       const foo = 1;
       export { foo };
       export const bar = 2;
@@ -1078,10 +984,8 @@ describe("mangle-names", () => {
         bar();
         baz();
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       const foo = 1;
       export { foo };
       export const bar = 2;
@@ -1093,14 +997,12 @@ describe("mangle-names", () => {
         a();
         b();
       }
-    `
-    );
+    `);
     expect(transform(source, {}, "module")).toBe(expected);
   });
 
   it("should find global scope properly", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       class A {}
       class B extends A {}
       (function () {
@@ -1112,10 +1014,8 @@ describe("mangle-names", () => {
           }
         }
       })();
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       class A {}
       class B extends A {}
       (function () {
@@ -1127,14 +1027,12 @@ describe("mangle-names", () => {
           }
         }
       })();
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should mangle classes properly", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       class A {}
       class B {}
       new A();
@@ -1145,54 +1043,46 @@ describe("mangle-names", () => {
         new A();
         new B();
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       class A {}
       class B {}
       new A();
       new B();
       function a() {
+        class a {}
         class b {}
-        class c {}
+        new a();
         new b();
-        new c();
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   // https://github.com/babel/babili/issues/138
   it("should handle class exports in modules - issue#138", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       export class App extends Object {};
-    `
-    );
+    `);
     const expected = source;
     expect(transform(source, {}, "module")).toBe(expected);
   });
 
   it("should not mangle the name arguments", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       (function () {
         var arguments = void 0;
         (function () {
           console.log(arguments);
         })("argument");
       })();
-    `
-    );
+    `);
     const expected = source;
     expect(transform(source)).toBe(expected);
   });
 
   it("should mangle topLevel when topLevel option is true", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         if (FOO_ENV === "production") {
           HELLO_WORLD.call();
@@ -1207,11 +1097,9 @@ describe("mangle-names", () => {
       };
       class AbstractClass {}
       foo();
-    `
-    );
+    `);
 
-    const expected = unpad(
-      `
+    const expected = unpad(`
       function a() {
         if (b === "production") {
           c.call();
@@ -1226,15 +1114,13 @@ describe("mangle-names", () => {
       };
       class d {}
       a();
-    `
-    );
+    `);
 
     expect(transform(source, { topLevel: true })).toBe(expected);
   });
 
   it("should fix #326, #369 - destructuring", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       // issue#326
       function a() {
         let foo, bar, baz;
@@ -1250,33 +1136,29 @@ describe("mangle-names", () => {
         [, namespace, name, value] = message.split(',') || [];
         console.log(name);
       }
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       // issue#326
       function a() {
-        let b, c, d;
-        ({ foo: b, bar: c, baz: d } = {});
-        return { foo: b, bar: c, baz: d };
+        let a, b, c;
+        ({ foo: a, bar: b, baz: c } = {});
+        return { foo: a, bar: b, baz: c };
       }
       // issue#369
-      function decodeMessage(b) {
+      function decodeMessage(a) {
+        let b;
         let c;
-        let d;
-        let e = null;
+        let d = null;
 
-        [, c, d, e] = b.split(\',\') || [];
-        console.log(d);
+        [, b, c, d] = a.split(',') || [];
+        console.log(c);
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should rename binding.identifier - issue#411", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       !function () {
         function e(e) {
           foo(e);
@@ -1285,85 +1167,118 @@ describe("mangle-names", () => {
           return e();
         };
       }();
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       !function () {
-        function a(b) {
-          foo(b);
+        function a(a) {
+          foo(a);
         }
         return function () {
           return a();
         };
       }();
-    `
-    );
+    `);
     expect(transform(source)).toBe(expected);
   });
 
   it("should fix issue#365 - classDeclaration with unsafe parent scope", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       function foo() {
         eval("");
         class A {}
         class B {}
       }
-    `
-    );
+    `);
     expect(transform(source)).toBe(source);
   });
 
   it("should fix classDeclaration with unsafe program scope", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       class A {}
       class B {}
       eval("");
-    `
-    );
+    `);
     expect(transform(source, { topLevel: true })).toBe(source);
   });
 
+  it("should handle constant violations across multiple blocks", () => {
+    const source = unpad(`
+      function foo() {
+        var x;x;x;
+        {
+          var x;x;x;
+          function y() {
+            var x;x;x;
+            {
+              var x;x;x;
+            }
+          }
+        }
+      }
+    `);
+    const expected = unpad(`
+      function foo() {
+        var a;a;a;
+        {
+          var a;a;a;
+          function b() {
+            var a;a;a;
+            {
+              var a;a;a;
+            }
+          }
+        }
+      }
+    `);
+    expect(transform(source)).toBe(expected);
+  });
+
+  it("should work with if_return optimization changing fn scope", () => {
+    const source = unpad(`
+      function foo() {
+        if (x)
+          return;
+        function bar() {}
+        bar(a);
+      }
+    `);
+    const expected = unpad(`
+      function foo() {
+        function b() {}
+        x || b(a);
+      }
+    `);
+    expect(transformWithSimplify(source)).toBe(expected);
+  });
+
   it("should not mangle named exports - 1", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       export const Foo = foo;
-    `
-    );
+    `);
     expect(transform(source, { topLevel: true }, "module")).toBe(source);
   });
 
   it("should not mangle named exports - 2", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       const Foo = a;
       export {Foo};
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       const b = a;
       export { b as Foo };
-    `
-    );
+    `);
     expect(transform(source, { topLevel: true }, "module")).toBe(expected);
   });
 
   it("should not mangle named exports - 3", () => {
-    const source = unpad(
-      `
+    const source = unpad(`
       const Foo = a;
       export {Foo as Bar};
-    `
-    );
-    const expected = unpad(
-      `
+    `);
+    const expected = unpad(`
       const b = a;
       export { b as Bar };
-    `
-    );
+    `);
     expect(transform(source, { topLevel: true }, "module")).toBe(expected);
   });
 });
diff --git a/packages/babel-preset-babili/__tests__/preset-tests.js b/packages/babel-preset-babili/__tests__/preset-tests.js
index cb90a9e..9694a49 100644
--- a/packages/babel-preset-babili/__tests__/preset-tests.js
+++ b/packages/babel-preset-babili/__tests__/preset-tests.js
@@ -7,7 +7,7 @@ function transform(code, options = {}, sourceType = "script") {
   return babel.transform(code, {
     sourceType,
     minified: false,
-    presets: [require("../src/index")]
+    presets: [[require("../src/index"), options]]
   }).code;
 }
 
@@ -34,8 +34,8 @@ describe("preset", () => {
     const expected = unpad(
       `
       function foo() {
-        var d, e, f;
-        d ? e && f : e || f;
+        var d, a, b;
+        d ? a && b : a || b;
       }
     `
     );
@@ -129,12 +129,30 @@ describe("preset", () => {
     const expected = unpad(
       `
       function a() {
-        var b = Math.floor,
-            c = Math.max;
-        c(foo, bar);
+        var a = Math.floor,
+            b = Math.max;
+        b(foo, bar);
       }
     `
     );
     expect(transform(source)).toBe(expected);
   });
+
+  it("should fix bug#326 - object destructuring", () => {
+    const source = unpad(`
+      function a() {
+        let foo, bar, baz;
+        ({foo, bar, baz} = {});
+        return {foo, bar, baz};
+      }
+    `);
+    const expected = unpad(`
+      function a() {
+        let a, b, c;
+
+        return ({ foo: a, bar: b, baz: c } = {}), { foo: a, bar: b, baz: c };
+      }
+    `);
+    expect(transform(source)).toBe(expected);
+  });
 });