Skip to content

Commit

Permalink
BFS traverse
Browse files Browse the repository at this point in the history
  • Loading branch information
boopathi committed May 18, 2017
1 parent 8f96264 commit 4104411
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 84 deletions.
48 changes: 48 additions & 0 deletions packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js
Original file line number Diff line number Diff line change
@@ -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; // current depth

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);
}
}
}
};
};
138 changes: 70 additions & 68 deletions packages/babel-plugin-minify-mangle-names/src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
const Charset = require("./charset");
const ScopeTracker = require("./scope-tracker");
const isLabelIdentifier = require("./is-label-identifier");
const bfsTraverseCreator = require("./bfs-traverse");

const {
markEvalScopes,
isMarked: isEvalScopesMarked,
hasEval
} = require("babel-helper-mark-eval-scopes");

module.exports = ({ types: t, traverse }) => {
module.exports = babel => {
const { types: t, traverse } = babel;
const bfsTraverse = bfsTraverseCreator(babel);
const hop = Object.prototype.hasOwnProperty;

class Mangler {
Expand All @@ -20,8 +23,7 @@ module.exports = ({ types: t, traverse }) => {
keepFnName = false,
keepClassName = false,
eval: _eval = false,
topLevel = false,
reuse = true
topLevel = false
} = {}
) {
this.charset = charset;
Expand All @@ -31,15 +33,15 @@ module.exports = ({ types: t, traverse }) => {
this.keepClassName = keepClassName;
this.topLevel = topLevel;
this.eval = _eval;
this.reuse = reuse;

this.visitedScopes = new Set();
this.scopeTracker = new ScopeTracker({ reuse });
this.scopeTracker = new ScopeTracker();
this.renamedNodes = new Set();
}

run() {
this.crawlScope();
this.fixup();
this.collect();
this.charset.sort();
this.mangle();
Expand All @@ -54,6 +56,49 @@ module.exports = ({ types: t, traverse }) => {
this.program.scope.crawl();
}

fixup() {
const mangler = this;
this.program.traverse({
// 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
VariableDeclaration(path) {
if (path.node.kind !== "var") {
return;
}
const ids = path.getOuterBindingIdentifiers();
const fnScope = path.scope.getFunctionParent();
Object.keys(ids).forEach(id => {
const binding = path.scope.getBinding(id);

if (binding.scope !== fnScope) {
const existingBinding = fnScope.bindings[id];
if (!existingBinding) {
// move binding to the function scope
fnScope.bindings[id] = binding;
binding.scope = fnScope;
delete binding.scope.bindings[id];
} else {
// we need a new binding that's valid in both the scopes
// binding.scope and fnScope
const newName = fnScope.generateUid(
binding.scope.generateUid(id)
);

// rename binding in the original scope
mangler.rename(binding.scope, binding, id, newName);

// move binding to fnScope as newName
fnScope.bindings[newName] = binding;
binding.scope = fnScope;
delete binding.scope.bindings[newName];
}
}
});
}
});
}

collect() {
const mangler = this;
const { scopeTracker } = mangler;
Expand All @@ -77,62 +122,23 @@ module.exports = ({ types: t, traverse }) => {
const binding = scope.getBinding(name);
scopeTracker.addReference(scope, binding, name);
},
// this fixes a bug where converting let to var
// doesn't change the binding's scope to function scope
VariableDeclaration: {
enter(path) {
if (path.node.kind !== "var") {

BindingIdentifier(path) {
if (isLabelIdentifier(path)) return;

const { scope, node: { name } } = path;
const binding = scope.getBinding(name);
if (!binding) {
if (scope.hasGlobal(name)) return;
if (
path.parentPath.isExportSpecifier() &&
path.parentKey === "exported"
) {
return;
}
const ids = path.getOuterBindingIdentifiers();
const fnScope = path.scope.getFunctionParent();
Object.keys(ids).forEach(id => {
const binding = path.scope.getBinding(id);

if (binding.scope !== fnScope) {
const existingBinding = fnScope.bindings[id];
if (!existingBinding) {
// move binding to the function scope
fnScope.bindings[id] = binding;
binding.scope = fnScope;
delete binding.scope.bindings[id];
} else {
// we need a new binding that's valid in both the scopes
// binding.scope and fnScope
const newName = fnScope.generateUid(
binding.scope.generateUid(id)
);

// rename binding in the original scope
mangler.rename(binding.scope, binding, id, newName);

// move binding to fnScope as newName
fnScope.bindings[newName] = binding;
binding.scope = fnScope;
delete binding.scope.bindings[newName];
}
}
});
}
},
BindingIdentifier: {
exit(path) {
if (isLabelIdentifier(path)) return;
const { scope, node: { name } } = path;
const binding = scope.getBinding(name);
if (!binding) {
if (scope.hasGlobal(name)) return;
if (
path.parentPath.isExportSpecifier() &&
path.parentKey === "exported"
) {
return;
}
console.log(scope.globals);
throw new Error("binding not found " + name);
}
scopeTracker.addBinding(binding);
throw new Error("binding not found " + name);
}
scopeTracker.addBinding(binding);
}
};

Expand All @@ -152,7 +158,7 @@ module.exports = ({ types: t, traverse }) => {
};
}

mangler.program.traverse(collectVisitor);
bfsTraverse(mangler.program, collectVisitor);
}

isExportedWithName(binding) {
Expand Down Expand Up @@ -237,23 +243,19 @@ module.exports = ({ types: t, traverse }) => {
!scopeTracker.canUseInReferencedScopes(binding, next)
);

if (mangler.reuse) {
resetNext();
}
resetNext();

mangler.rename(scope, binding, oldName, next);
}
}

mangle() {
const mangler = this;

if (mangler.topLevel) {
mangler.mangleScope(this.program.scope);
}

this.program.traverse({
bfsTraverse(this.program, {
Scopable(path) {
mangler.mangleScope(path.scope);
if (!path.isProgram() || mangler.topLevel)
mangler.mangleScope(path.scope);
}
});
}
Expand Down
28 changes: 12 additions & 16 deletions packages/babel-plugin-minify-mangle-names/src/scope-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ const isLabelIdentifier = require("./is-label-identifier");
* Scope - References, Bindings
*/
module.exports = class ScopeTracker {
constructor({ reuse }) {
this.references = new Map;
this.bindings = new Map;

this.reuse = reuse;
constructor() {
this.references = new Map();
this.bindings = new Map();
}

// Register a new Scope and initiliaze it with empty sets
addScope(scope) {
if (!this.references.has(scope)) {
this.references.set(scope, new CountedSet);
this.references.set(scope, new CountedSet());
}
if (!this.bindings.has(scope)) {
this.bindings.set(scope, new Map);
this.bindings.set(scope, new Map());
}
}

Expand All @@ -36,13 +34,10 @@ module.exports = class ScopeTracker {
if (binding && binding.scope === parent) {
break;
}
} while (parent = parent.parent);
} while ((parent = parent.parent));
}

hasReference(scope, name) {
if (!this.reuse) {
return scope.hasReference(name);
}
if (!this.references.has(scope)) {
this.addScope(scope);
this.updateScope(scope);
Expand Down Expand Up @@ -108,7 +103,7 @@ module.exports = class ScopeTracker {
if (binding.scope === parent) {
break;
}
} while (parent = parent.parent);
} while ((parent = parent.parent));
}

addBinding(binding) {
Expand All @@ -122,9 +117,6 @@ module.exports = class ScopeTracker {
}

hasBinding(scope, name) {
if (!this.reuse) {
return scope.hasBinding(name);
}
return this.bindings.get(scope).has(name);
}

Expand All @@ -142,7 +134,10 @@ module.exports = class ScopeTracker {
// with a throw statement. This helps in understanding where it
// happens to debug it.
updateScope(scope) {
throw new Error("Tracker received a scope it doesn't know about yet. Please report this - https://github.com/babel/babili/issues/new");
/* eslint-disable no-unreachable */
throw new Error(
"Tracker received a scope it doesn't know about yet. Please report this - https://github.com/babel/babili/issues/new"
);

const tracker = this;
scope.path.traverse({
Expand All @@ -153,5 +148,6 @@ module.exports = class ScopeTracker {
}
}
});
/* eslint-enable */
}
};

0 comments on commit 4104411

Please sign in to comment.