Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

babel-plugin-minify-mangle-names

Package Overview
Dependencies
Maintainers
3
Versions
87
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

babel-plugin-minify-mangle-names - npm Package Compare versions

Comparing version 0.0.8 to 0.1.0

lib/bfs-traverse.js

623

lib/index.js

@@ -5,4 +5,12 @@ "use strict";

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Charset = require("./charset");
var ScopeTracker = require("./scope-tracker");
var isLabelIdentifier = require("./is-label-identifier");
var bfsTraverseCreator = require("./bfs-traverse");
var fixupVarScoping = require("./fixup-var-scoping");
var _require = require("babel-helper-mark-eval-scopes"),

@@ -13,8 +21,9 @@ markEvalScopes = _require.markEvalScopes,

var PATH_RENAME_MARKER = Symbol("PATH_RENAME_MARKER");
var newIssueUrl = "https://github.com/babel/babili/issues/new";
module.exports = function (_ref) {
var t = _ref.types,
traverse = _ref.traverse;
module.exports = function (babel) {
var t = babel.types,
traverse = babel.traverse;
var bfsTraverse = bfsTraverseCreator(babel);
var hop = Object.prototype.hasOwnProperty;

@@ -24,13 +33,13 @@

function Mangler(charset, program) {
var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
_ref2$blacklist = _ref2.blacklist,
blacklist = _ref2$blacklist === undefined ? {} : _ref2$blacklist,
_ref2$keepFnName = _ref2.keepFnName,
keepFnName = _ref2$keepFnName === undefined ? false : _ref2$keepFnName,
_ref2$eval = _ref2.eval,
_eval = _ref2$eval === undefined ? false : _ref2$eval,
_ref2$topLevel = _ref2.topLevel,
topLevel = _ref2$topLevel === undefined ? false : _ref2$topLevel,
_ref2$keepClassName = _ref2.keepClassName,
keepClassName = _ref2$keepClassName === undefined ? false : _ref2$keepClassName;
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
_ref$blacklist = _ref.blacklist,
blacklist = _ref$blacklist === undefined ? {} : _ref$blacklist,
_ref$keepFnName = _ref.keepFnName,
keepFnName = _ref$keepFnName === undefined ? false : _ref$keepFnName,
_ref$keepClassName = _ref.keepClassName,
keepClassName = _ref$keepClassName === undefined ? false : _ref$keepClassName,
_ref$eval = _ref.eval,
_eval = _ref$eval === undefined ? false : _ref$eval,
_ref$topLevel = _ref.topLevel,
topLevel = _ref$topLevel === undefined ? false : _ref$topLevel;

@@ -41,29 +50,37 @@ _classCallCheck(this, Mangler);

this.program = program;
// user passed options
this.blacklist = toObject(blacklist);
this.keepFnName = keepFnName;
this.keepClassName = keepClassName;
this.topLevel = topLevel;
this.eval = _eval;
this.topLevel = topLevel;
this.unsafeScopes = new Set();
// tracking
this.visitedScopes = new Set();
this.referencesToUpdate = new Map();
this.scopeTracker = new ScopeTracker();
this.renamedNodes = new Set();
}
/**
* Run the mangler
*/
_createClass(Mangler, [{
key: "run",
value: function run() {
this.cleanup();
this.crawlScope();
this.collect();
this.fixup();
this.charset.sort();
this.mangle();
}
/**
* Tells if a variable name is blacklisted
* @param {String} name
*/
}, {
key: "cleanup",
value: function cleanup() {
traverse.clearCache();
this.program.scope.crawl();
}
}, {
key: "isBlacklist",

@@ -73,15 +90,59 @@ value: function isBlacklist(name) {

}
/**
* 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
*/
}, {
key: "markUnsafeScopes",
value: function markUnsafeScopes(scope) {
var evalScope = scope;
do {
this.unsafeScopes.add(evalScope);
} while (evalScope = evalScope.parent);
key: "crawlScope",
value: function 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
*/
}, {
key: "fixup",
value: function 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
*/
}, {
key: "collect",
value: function collect() {
var mangler = this;
var scopeTracker = mangler.scopeTracker;
scopeTracker.addScope(this.program.scope);
/**
* Same usage as in DCE, whichever runs first
*/
if (!isEvalScopesMarked(mangler.program.scope)) {

@@ -91,32 +152,191 @@ markEvalScopes(mangler.program);

if (this.charset.shouldConsider) {
var collectVisitor = {
Identifier(path) {
var node = path.node;
/**
* 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
*/
var collectVisitor = {
Scopable(_ref2) {
var scope = _ref2.scope;
scopeTracker.addScope(scope);
if (path.parentPath.isMemberExpression({ property: node }) || path.parentPath.isObjectProperty({ key: node })) {
mangler.charset.consider(node.name);
// Collect bindings defined in the scope
Object.keys(scope.bindings).forEach(function (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;
var scope = path.scope,
name = path.node.name;
var 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;
var scope = path.scope,
name = path.node.name;
var 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.isExportSpecifier() && path.parentKey === "exported") {
return;
}
},
Literal(_ref3) {
var node = _ref3.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);
}
/**
* 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) {
var node = path.node;
// 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(_ref3) {
var node = _ref3.node;
mangler.program.traverse(collectVisitor);
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
*/
}, {
key: "isExportedWithName",
value: function isExportedWithName(binding) {
// short circuit
if (!this.topLevel) {
return false;
}
var refs = binding.referencePaths;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = refs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var ref = _step.value;
if (ref.isExportNamedDeclaration()) {
return true;
}
}
// default
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return false;
}
/**
* Mangle the scope
* @param {Scope} scope
*/
}, {
key: "mangleScope",
value: function mangleScope(scope) {
var mangler = this;
var scopeTracker = mangler.scopeTracker;
// 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
var i = 0;

@@ -126,42 +346,36 @@ function getNext() {

}
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;
// }
var bindings = scopeTracker.bindings.get(scope);
var names = [].concat(_toConsumableArray(bindings.keys()));
var bindings = scope.getAllBindings();
var names = Object.keys(bindings);
/**
* 1. Iterate through the list of BindingIdentifiers
* 2. Rename each of them in-place
* 3. Update the scope tree.
*/
for (var _i = 0; _i < names.length; _i++) {
var oldName = names[_i];
var binding = bindings[oldName];
var binding = bindings.get(oldName);
// Names which should NOT be mangled
if (
// arguments
oldName === "arguments"
// other scope bindings
|| !scope.hasOwnBinding(oldName)
// arguments - for non-strict mode
oldName === "arguments" ||
// labels
|| binding.path.isLabeledStatement()
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
binding.path.isClassDeclaration() && binding.path === scope.path ||
// blacklisted
|| mangler.isBlacklist(oldName)
mangler.isBlacklist(oldName) || (
// function names
|| (mangler.keepFnName ? isFunction(binding.path) : false)
mangler.keepFnName ? isFunction(binding.path) : false) || (
// class names
|| (mangler.keepClassName ? isClass(binding.path) : false)) {
mangler.keepClassName ? isClass(binding.path) : false) ||
// named export
mangler.isExportedWithName(binding)) {
continue;

@@ -173,10 +387,18 @@ }

next = getNext();
} while (!t.isValidIdentifier(next) || hop.call(bindings, next) || scope.hasGlobal(next) || scope.hasReference(next));
} while (!t.isValidIdentifier(next) || scopeTracker.hasBinding(scope, next) || scope.hasGlobal(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
*/
}, {

@@ -187,49 +409,57 @@ key: "mangle",

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);
}
});
}
/**
* 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
*/
}, {
key: "rename",
value: function rename(scope, oldName, newName) {
var binding = scope.getBinding(oldName);
key: "renameBindingIds",
value: function renameBindingIds(path, oldName, newName) {
var predicate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () {
return true;
};
// rename at the declaration level
var bindingPaths = binding.path.getBindingIdentifierPaths(true, false);
// we traverse through all bindingPaths because,
// there is no binding.identifierPath in babel
for (var name in bindingPaths) {
var bindingIds = path.getBindingIdentifierPaths(true, false);
for (var name in bindingIds) {
if (name !== oldName) continue;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator = bindingPaths[name][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var idPath = _step.value;
for (var _iterator2 = bindingIds[name][Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var idPath = _step2.value;
if (binding.identifier === idPath.node) {
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);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
if (_didIteratorError2) {
throw _iteratorError2;
}

@@ -239,27 +469,42 @@ }

}
}
var bindings = scope.bindings;
/**
* 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
*/
bindings[newName] = binding;
delete bindings[oldName];
}, {
key: "rename",
value: function rename(scope, binding, oldName, newName) {
var mangler = this;
var scopeTracker = mangler.scopeTracker;
// update all constant violations & redeclarations
var violations = binding.constantViolations;
// rename at the declaration level
var _loop = function _loop(i) {
if (violations[i].isLabeledStatement()) return "continue";
this.renameBindingIds(binding.path, oldName, newName, function (idPath) {
return idPath.node === binding.identifier;
});
var bindings = violations[i].getBindingIdentifierPaths();
Object.keys(bindings).map(function (b) {
if (b === oldName && !bindings[b][PATH_RENAME_MARKER]) {
bindings[b].replaceWith(t.identifier(newName));
bindings[b][PATH_RENAME_MARKER] = true;
}
});
};
// update mangler's ScopeTracker
scopeTracker.renameBinding(scope, oldName, newName);
// update all constant violations
var violations = binding.constantViolations;
for (var i = 0; i < violations.length; i++) {
var _ret = _loop(i);
if (violations[i].isLabeledStatement()) continue;
if (_ret === "continue") continue;
this.renameBindingIds(violations[i], oldName, newName);
scopeTracker.updateReference(violations[i].scope, binding, oldName, newName);
}

@@ -269,8 +514,8 @@

var refs = binding.referencePaths;
for (var i = 0; i < refs.length; i++) {
var path = refs[i];
if (path[PATH_RENAME_MARKER]) continue;
for (var _i2 = 0; _i2 < refs.length; _i2++) {
var path = refs[_i2];
var node = path.node;
if (!path.isIdentifier()) {

@@ -287,11 +532,38 @@ // Ideally, this should not happen

ReferencedIdentifier(refPath) {
if (refPath.node.name === oldName && refPath.scope === scope && !refPath[PATH_RENAME_MARKER]) {
refPath.node.name = newName;
if (refPath.node.name !== oldName) {
return;
}
var 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
var bindings = scope.bindings;
bindings[newName] = binding;
delete bindings[oldName];
}

@@ -306,13 +578,18 @@ }]);

visitor: {
Program(path) {
// If the source code is small then we're going to assume that the user
// is running on this on single files before bundling. Therefore we
// need to achieve as much determinisim and we will not do any frequency
// sorting on the character set. Currently the number is pretty arbitrary.
var shouldConsiderSource = path.getSource().length > 70000;
/**
* 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
// is running on this on single files before bundling. Therefore we
// need to achieve as much determinisim and we will not do any frequency
// sorting on the character set. Currently the number is pretty arbitrary.
var shouldConsiderSource = path.getSource().length > 70000;
var charset = new Charset(shouldConsiderSource);
var charset = new Charset(shouldConsiderSource);
var mangler = new Mangler(charset, path, this.opts);
mangler.run();
var mangler = new Mangler(charset, path, this.opts);
mangler.run();
}
}

@@ -323,71 +600,3 @@ }

var CHARSET = ("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_").split("");
var Charset = function () {
function Charset(shouldConsider) {
var _this = this;
_classCallCheck(this, Charset);
this.shouldConsider = shouldConsider;
this.chars = CHARSET.slice();
this.frequency = {};
this.chars.forEach(function (c) {
_this.frequency[c] = 0;
});
this.finalized = false;
}
_createClass(Charset, [{
key: "consider",
value: function consider(str) {
var _this2 = this;
if (!this.shouldConsider) {
return;
}
str.split("").forEach(function (c) {
if (_this2.frequency[c] != null) {
_this2.frequency[c]++;
}
});
}
}, {
key: "sort",
value: function sort() {
var _this3 = this;
if (this.shouldConsider) {
this.chars = this.chars.sort(function (a, b) {
return _this3.frequency[b] - _this3.frequency[a];
});
}
this.finalized = true;
}
}, {
key: "getIdentifier",
value: function getIdentifier(num) {
if (!this.finalized) {
throw new Error("Should sort first");
}
var ret = "";
num++;
do {
num--;
ret += this.chars[num % this.chars.length];
num = Math.floor(num / this.chars.length);
} while (num > 0);
return ret;
}
}]);
return Charset;
}();
// convert value to object
function toObject(value) {

@@ -413,8 +622,2 @@ if (!Array.isArray(value)) {

return path.isClassExpression() || path.isClassDeclaration();
}
function isLabelIdentifier(path) {
var node = path.node;
return path.parentPath.isLabeledStatement({ label: node }) || path.parentPath.isBreakStatement({ label: node }) || path.parentPath.isContinueStatement({ label: node });
}
{
"name": "babel-plugin-minify-mangle-names",
"version": "0.0.8",
"version": "0.1.0",
"description": "",

@@ -15,5 +15,5 @@ "homepage": "https://github.com/babel/babili#readme",

"dependencies": {
"babel-helper-mark-eval-scopes": "^0.0.3"
"babel-helper-mark-eval-scopes": "^0.1.0"
},
"devDependencies": {}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc