diff --git a/.gitignore b/.gitignore
index 012bda1ffd..f1ac2c5c93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,10 +8,10 @@ test/core/tmp
/browser.js
/browser-polyfill.js
/runtime.js
-coverage
-dist
-.package.json
-packages/babel-runtime/core-js
-packages/babel-runtime/helpers/*.js
-packages/babel-runtime/regenerator/*.js
-lib
+/coverage
+/dist
+/.package.json
+/packages/babel-runtime/core-js
+/packages/babel-runtime/helpers/*.js
+/packages/babel-runtime/regenerator/*.js
+/lib
diff --git a/src/babel/traversal/path/lib/hoister.js b/src/babel/traversal/path/lib/hoister.js
new file mode 100644
index 0000000000..5964e6c088
--- /dev/null
+++ b/src/babel/traversal/path/lib/hoister.js
@@ -0,0 +1,130 @@
+import * as react from "../../../transformation/helpers/react";
+import * as t from "../../../types";
+
+var referenceVisitor = {
+ ReferencedIdentifier(node, parent, scope, state) {
+ if (this.isJSXIdentifier() && react.isCompatTag(node.name)) {
+ return;
+ }
+
+ // direct references that we need to track to hoist this to the highest scope we can
+ var bindingInfo = scope.getBinding(node.name);
+ if (!bindingInfo) return;
+
+ // this binding isn't accessible from the parent scope so we can safely ignore it
+ // eg. it's in a closure etc
+ if (bindingInfo !== state.scope.getBinding(node.name)) return;
+
+ if (bindingInfo.constant) {
+ state.bindings[node.name] = bindingInfo;
+ } else {
+ for (var violationPath of (bindingInfo.constantViolations: Array)) {
+ state.breakOnScopePaths.push(violationPath.scope.path);
+ }
+ }
+ }
+};
+
+export default class PathHoister {
+ constructor(path, scope) {
+ this.breakOnScopePaths = [];
+ this.bindings = {};
+ this.scopes = [];
+ this.scope = scope;
+ this.path = path;
+ }
+
+ isCompatibleScope(scope) {
+ for (var key in this.bindings) {
+ var binding = this.bindings[key];
+ if (!scope.bindingIdentifierEquals(key, binding.identifier)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ getCompatibleScopes() {
+ var scope = this.path.scope;
+ do {
+ if (this.isCompatibleScope(scope)) {
+ this.scopes.push(scope);
+ } else {
+ break;
+ }
+
+ if (this.breakOnScopePaths.indexOf(scope.path) >= 0) {
+ break;
+ }
+ } while(scope = scope.parent);
+ }
+
+ getAttachmentPath() {
+ var scopes = this.scopes;
+
+ var scope = scopes.pop();
+ if (!scope) return;
+
+ if (scope.path.isFunction()) {
+ if (this.hasOwnParamBindings(scope)) {
+ // should ignore this scope since it's ourselves
+ if (this.scope.is(scope)) return;
+
+ // needs to be attached to the body
+ return scope.path.get("body").get("body")[0];
+ } else {
+ // doesn't need to be be attached to this scope
+ return this.getNextScopeStatementParent();
+ }
+ } else if (scope.path.isProgram()) {
+ return this.getNextScopeStatementParent();
+ }
+ }
+
+ getNextScopeStatementParent() {
+ var scope = this.scopes.pop();
+ if (scope) return scope.path.getStatementParent();
+ }
+
+ hasOwnParamBindings(scope) {
+ for (var name in this.bindings) {
+ if (!scope.hasOwnBinding(name)) continue;
+
+ var binding = this.bindings[name];
+ if (binding.kind === "param") return true;
+ }
+ return false;
+ }
+
+ run() {
+ var node = this.path.node;
+ if (node._hoisted) return;
+ node._hoisted = true;
+
+ this.path.traverse(referenceVisitor, this);
+
+ this.getCompatibleScopes();
+
+ var path = this.getAttachmentPath();
+ if (!path) return;
+
+ var uid = path.scope.generateUidIdentifier("ref");
+
+ path.insertBefore([
+ t.variableDeclaration("var", [
+ t.variableDeclarator(uid, this.path.node)
+ ])
+ ]);
+
+ var parent = this.path.parentPath;
+
+ if (parent.isJSXElement() && this.path.container === parent.node.children) {
+ // turning the `span` in `
` to an expression so we need to wrap it with
+ // an expression container
+ uid = t.jSXExpressionContainer(uid);
+ }
+
+ this.path.replaceWith(uid);
+ }
+}
diff --git a/src/babel/traversal/path/lib/removal-hooks.js b/src/babel/traversal/path/lib/removal-hooks.js
new file mode 100644
index 0000000000..fc6e60ae9b
--- /dev/null
+++ b/src/babel/traversal/path/lib/removal-hooks.js
@@ -0,0 +1,71 @@
+// This file contains all the cases where we have to perform additional logic when performing
+// specific operations in order to retain correct JavaScript semantics.
+
+import * as t from "../../../types";
+
+// pre hooks should be used for either rejecting removal or delegating removal to a replacement
+export var pre = [
+ function (self, parent) {
+ if (self.key === "body" && self.isBlockStatement() && parent.isFunction()) {
+ // lol nah, you'll break stuff
+ return true;
+ }
+ },
+
+ function (self, parent) {
+ // attempting to remove body of an arrow function so we just replace it with undefined
+ if (self.key === "body" && parent.isArrowFunctionExpression()) {
+ self.replaceWith(t.identifier("undefined"));
+ return true;
+ }
+ }
+];
+
+// post hooks should be used for cleaning up parents
+export var post = [
+ function (self, parent) {
+ // just remove a declaration for an export so this is no longer valid
+ if (self.key === "declaration" && parent.isExportDeclaration()) {
+ parent.remove();
+ return true;
+ }
+ },
+
+ function (self, parent) {
+ // we've just removed the last declarator of a variable declaration so there's no point in
+ // keeping it
+ if (parent.isVariableDeclaration() && parent.node.declarations.length === 0) {
+ parent.remove();
+ return true;
+ }
+ },
+
+ function (self, parent) {
+ // we're the child of an expression statement so we should remove the parent
+ if (parent.isExpressionStatement()) {
+ parent.remove();
+ return true;
+ }
+ },
+
+ function (self, parent) {
+ // we've just removed the second element of a sequence expression so let's turn that sequence
+ // expression into a regular expression
+ if (parent.isSequenceExpression() && parent.node.expressions.length === 1) {
+ parent.replaceWith(parent.node.expressions[0]);
+ return true;
+ }
+ },
+
+ function (self, parent) {
+ // we're in a binary expression, better remove it and replace it with the last expression
+ if (parent.isBinary()) {
+ if (self.key === "left") {
+ parent.replaceWith(parent.node.right);
+ } else { // key === "right"
+ parent.replaceWith(parent.node.left);
+ }
+ return true;
+ }
+ }
+];
diff --git a/src/babel/traversal/path/lib/virtual-types.js b/src/babel/traversal/path/lib/virtual-types.js
new file mode 100644
index 0000000000..7f6ba09015
--- /dev/null
+++ b/src/babel/traversal/path/lib/virtual-types.js
@@ -0,0 +1,34 @@
+import * as t from "../../../types";
+
+export var ReferencedIdentifier = {
+ types: ["Identifier", "JSXIdentifier"],
+ checkPath(path, opts) {
+ return t.isReferencedIdentifier(path.node, path.parent, opts);
+ }
+};
+
+export var Scope = {
+ types: ["Scopable"],
+ checkPath(path) {
+ return t.isScope(path.node, path.parent);
+ }
+};
+
+export var Referenced = {
+ checkPath(path) {
+ return t.isReferenced(path.node, path.parent);
+ }
+};
+
+export var BlockScoped = {
+ checkPath(path) {
+ return t.isBlockScoped(path.node);
+ }
+};
+
+export var Var = {
+ types: ["VariableDeclaration"],
+ checkPath(path) {
+ return t.isVar(path.node);
+ }
+};
diff --git a/test/core/fixtures/bin/babel/--ignore/out-files/lib/bar/index.js b/test/core/fixtures/bin/babel/--ignore/out-files/lib/bar/index.js
new file mode 100644
index 0000000000..0ed075a193
--- /dev/null
+++ b/test/core/fixtures/bin/babel/--ignore/out-files/lib/bar/index.js
@@ -0,0 +1,3 @@
+"use strict";
+
+bar;
diff --git a/test/core/fixtures/bin/babel/--only/out-files/lib/bar/index.js b/test/core/fixtures/bin/babel/--only/out-files/lib/bar/index.js
new file mode 100644
index 0000000000..0ed075a193
--- /dev/null
+++ b/test/core/fixtures/bin/babel/--only/out-files/lib/bar/index.js
@@ -0,0 +1,3 @@
+"use strict";
+
+bar;
diff --git a/test/core/fixtures/bin/babel/dir --out-dir --source-maps inline/out-files/lib/bar/bar.js b/test/core/fixtures/bin/babel/dir --out-dir --source-maps inline/out-files/lib/bar/bar.js
new file mode 100644
index 0000000000..7c44c97342
--- /dev/null
+++ b/test/core/fixtures/bin/babel/dir --out-dir --source-maps inline/out-files/lib/bar/bar.js
@@ -0,0 +1,8 @@
+"use strict";
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Test = function Test() {
+ _classCallCheck(this, Test);
+};
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9iYXIvYmFyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7SUFBTSxJQUFJLFlBQUosSUFBSTt3QkFBSixJQUFJIiwiZmlsZSI6ImJhci5qcyIsInNvdXJjZXNDb250ZW50IjpbImNsYXNzIFRlc3Qge1xuXG59Il19
diff --git a/test/core/fixtures/bin/babel/dir --out-dir --source-maps inline/out-files/lib/foo.js b/test/core/fixtures/bin/babel/dir --out-dir --source-maps inline/out-files/lib/foo.js
new file mode 100644
index 0000000000..3537587fad
--- /dev/null
+++ b/test/core/fixtures/bin/babel/dir --out-dir --source-maps inline/out-files/lib/foo.js
@@ -0,0 +1,6 @@
+"use strict";
+
+arr.map(function (x) {
+ return x * MULTIPLIER;
+});
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9mb28uanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxHQUFHLENBQUMsR0FBRyxDQUFDLFVBQUEsQ0FBQztTQUFJLENBQUMsR0FBRyxVQUFVO0NBQUEsQ0FBQyxDQUFDIiwiZmlsZSI6ImZvby5qcyIsInNvdXJjZXNDb250ZW50IjpbImFyci5tYXAoeCA9PiB4ICogTVVMVElQTElFUik7Il19
diff --git a/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/bar/bar.js b/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/bar/bar.js
new file mode 100644
index 0000000000..85d52f9265
--- /dev/null
+++ b/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/bar/bar.js
@@ -0,0 +1,8 @@
+"use strict";
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Test = function Test() {
+ _classCallCheck(this, Test);
+};
+//# sourceMappingURL=bar.js.map
diff --git a/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/bar/bar.js.map b/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/bar/bar.js.map
new file mode 100644
index 0000000000..d2f66bb31f
--- /dev/null
+++ b/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/bar/bar.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/bar/bar.js"],"names":[],"mappings":";;;;IAAM,IAAI,YAAJ,IAAI;wBAAJ,IAAI","file":"bar.js","sourcesContent":["class Test {\n\n}"]}
diff --git a/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/foo.js b/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/foo.js
new file mode 100644
index 0000000000..7a5ff48137
--- /dev/null
+++ b/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/foo.js
@@ -0,0 +1,6 @@
+"use strict";
+
+arr.map(function (x) {
+ return x * MULTIPLIER;
+});
+//# sourceMappingURL=foo.js.map
diff --git a/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/foo.js.map b/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/foo.js.map
new file mode 100644
index 0000000000..77300ca29a
--- /dev/null
+++ b/test/core/fixtures/bin/babel/dir --out-dir --source-maps/out-files/lib/foo.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../src/foo.js"],"names":[],"mappings":";;AAAA,GAAG,CAAC,GAAG,CAAC,UAAA,CAAC;SAAI,CAAC,GAAG,UAAU;CAAA,CAAC,CAAC","file":"foo.js","sourcesContent":["arr.map(x => x * MULTIPLIER);"]}
diff --git a/test/core/fixtures/bin/babel/dir --out-dir/out-files/lib/bar/bar.js b/test/core/fixtures/bin/babel/dir --out-dir/out-files/lib/bar/bar.js
new file mode 100644
index 0000000000..7c09ea8b39
--- /dev/null
+++ b/test/core/fixtures/bin/babel/dir --out-dir/out-files/lib/bar/bar.js
@@ -0,0 +1,7 @@
+"use strict";
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Test = function Test() {
+ _classCallCheck(this, Test);
+};
diff --git a/test/core/fixtures/bin/babel/dir --out-dir/out-files/lib/foo.js b/test/core/fixtures/bin/babel/dir --out-dir/out-files/lib/foo.js
new file mode 100644
index 0000000000..ae4557e57b
--- /dev/null
+++ b/test/core/fixtures/bin/babel/dir --out-dir/out-files/lib/foo.js
@@ -0,0 +1,5 @@
+"use strict";
+
+arr.map(function (x) {
+ return x * MULTIPLIER;
+});