diff --git a/lib/minify.js b/lib/minify.js index a68cbf3a..ce0954c4 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -75,6 +75,7 @@ function minify(files, options) { options.mangle = defaults(options.mangle, { cache: options.nameCache && (options.nameCache.vars || {}), eval: false, + group_voids: false, ie8: false, keep_fnames: false, properties: false, diff --git a/lib/scope.js b/lib/scope.js index 6c883c66..f9880a4f 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -400,6 +400,7 @@ AST_Symbol.DEFMETHOD("global", function(){ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options) { options = defaults(options, { eval : false, + group_voids : false, ie8 : false, keep_fnames : false, reserved : [], @@ -457,6 +458,18 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ this.walk(tw); to_mangle.forEach(function(def){ def.mangle(options) }); + if (options.group_voids) { + base54.reset(); + base54.sort(); + if (options.toplevel) this.group_voids(options); + else this.walk(new TreeWalker(function(node) { + if (node instanceof AST_Scope && !(node instanceof AST_Toplevel)) { + node.group_voids(options); + return true; + } + })); + } + function collect(symbol) { if (!member(symbol.name, options.reserved)) { to_mangle.push(symbol); @@ -464,11 +477,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ } }); -AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) { +AST_Scope.DEFMETHOD("find_colliding_names", function(options, all) { var cache = options.cache && options.cache.props; var avoid = Object.create(null); options.reserved.forEach(to_avoid); - this.globals.each(add_def); + if (this.globals) this.globals.each(add_def); this.walk(new TreeWalker(function(node) { if (node instanceof AST_Scope) node.variables.each(add_def); if (node instanceof AST_SymbolCatch) add_def(node.definition()); @@ -480,9 +493,9 @@ AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) { } function add_def(def) { - var name = def.name; + var name = def.mangled_name || def.name; if (def.global && cache && cache.has(name)) name = cache.get(name); - else if (!def.unmangleable(options)) return; + else if (!all && !def.unmangleable(options)) return; to_avoid(name); } }); @@ -522,6 +535,56 @@ AST_Toplevel.DEFMETHOD("expand_names", function(options) { } }); +AST_Scope.DEFMETHOD("group_voids", function(options) { + var avoid = this.find_colliding_names(options, true); + var cname = 0; + var name; + do { + name = base54(cname++); + } while (avoid[name] || !is_identifier(name)); + var count = 0; + this.transform(new TreeTransformer(function(node) { + if (node instanceof AST_Undefined + || node instanceof AST_UnaryPrefix + && node.operator == "void" + && node.expression.is_constant()) { + count++; + return new AST_SymbolRef({ + name: name, + start: node.start, + end: node.end + }); + } + })); + if (count) { + for (var i = 0, len = this.body.length; i < len; i++) { + var stat = this.body[i]; + if (stat instanceof AST_Var) { + stat.definitions.push(make_var_def(stat)); + return; + } + } + this.body.push(new AST_Var({ + definitions: [ make_var_def(this) ], + start: this.start, + end: this.end + })); + } + + function make_var_def(node) { + return new AST_VarDef({ + name: new AST_SymbolVar({ + name: name, + start: node.start, + end: node.end + }), + value: null, + start: node.start, + end: node.end + }); + } +}); + AST_Node.DEFMETHOD("tail_node", return_this); AST_Sequence.DEFMETHOD("tail_node", function() { return this.expressions[this.expressions.length - 1]; diff --git a/test/compress/group_voids.js b/test/compress/group_voids.js new file mode 100644 index 00000000..2d960079 --- /dev/null +++ b/test/compress/group_voids.js @@ -0,0 +1,198 @@ +group_voids: { + options = { + } + mangle = { + group_voids: true, + toplevel: false, + } + input: { + var a = 0; + x = void 0; + if (void 0 === b) + c = void 0; + function f1() { + var a = 1; + console.log(void 0); + } + function f2(undefined) { + var a = 2; + console.log(void 0); + } + function f3() { + var undefined = 3; + console.log(void 0); + } + function f4() { + console.log(void 0); + for (var a = 4;;); + var b = 4; + function f5() { + var c = 5; + var d = 5; + console.log(void 0); + } + } + function f6() { + try { + var a = 6; + console.log(void 0); + } catch (e) { + console.log(void 0); + } + } + } + expect: { + var a = 0; + x = void 0; + if (void 0 === b) + c = void 0; + function f1() { + var o = 1, a; + console.log(a); + } + function f2(o) { + var n = 2, a; + console.log(a); + } + function f3() { + var o = 3, a; + console.log(a); + } + function f4() { + console.log(a); + for(var o = 4;;); + var n = 4, a; + function v() { + var o = 5; + var n = 5; + console.log(a); + } + } + function f6() { + try { + var o = 6; + console.log(a); + } catch (o) { + console.log(a); + } + var a; + } + } +} + +group_voids_toplevel: { + options = { + } + mangle = { + group_voids: true, + toplevel: true, + } + input: { + var a = 0; + x = void 0; + if (void 0 === b) + c = void 0; + function f1() { + var a = 1; + console.log(void 0); + } + function f2(undefined) { + var a = 2; + console.log(void 0); + } + function f3() { + var undefined = 3; + console.log(void 0); + } + function f4() { + console.log(void 0); + for (var a = 4;;); + var b = 4; + function f5() { + var c = 5; + var d = 5; + console.log(void 0); + } + } + function f6() { + try { + var a = 6; + console.log(void 0); + } catch (e) { + console.log(void 0); + } + } + } + expect: { + var o = 0, a; + x = a; + if (a === b) + c = a; + function n() { + var o = 1; + console.log(a); + } + function v(o) { + var n = 2; + console.log(a); + } + function i() { + var o = 3; + console.log(a); + } + function l() { + console.log(a); + for(var o = 4;;); + var n = 4; + function v() { + var o = 5; + var n = 5; + console.log(a); + } + } + function r() { + try { + var o = 6; + console.log(a); + } catch (o) { + console.log(a); + } + } + } +} + +group_voids_catch: { + options = { + } + mangle = { + group_voids: true, + } + input: { + f(); + function f() { + var a = 1; + console.log(void 0); + try { + throw "FAIL"; + } catch (undefined) { + console.log(void 0); + } + } + } + expect: { + f(); + function f() { + var o = 1, a; + console.log(a); + try { + throw "FAIL"; + } catch (o) { + console.log(a); + } + } + } + expect_stdout: [ + "undefined", + "undefined", + ] +}