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..09807d57 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,15 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ this.walk(tw); to_mangle.forEach(function(def){ def.mangle(options) }); + if (!options.group_voids) return; + 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); @@ -522,6 +532,73 @@ AST_Toplevel.DEFMETHOD("expand_names", function(options) { } }); +AST_Scope.DEFMETHOD("group_voids", function(options) { + var self = this, enclosed = []; + var scopes = [], count = 0; + self.walk(new TreeWalker(function(node, descend) { + if (node instanceof AST_Scope && node !== self) { + scopes.unshift(node); + descend(); + if (scopes[0] === node) scopes.shift(); + return true; + } + if (count > 0 && scopes.length == 0) return; + if (is_undefined(node)) { + count++; + var scope; + while (scope = scopes.shift()) { + scope.enclosed.forEach(function(def) { + push_uniq(enclosed, def); + }); + } + } + })); + if (count < 1) return; + var save = self.enclosed; + self.enclosed = enclosed; + var name = next_mangled(self, options); + self.enclosed = save; + this.transform(new TreeTransformer(function(node) { + if (is_undefined(node)) return new AST_SymbolRef({ + name: name, + start: node.start, + end: node.end + }); + })); + 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 is_undefined(node) { + return node instanceof AST_Undefined + || node instanceof AST_UnaryPrefix + && node.operator == "void" + && node.expression.is_constant(); + } + + 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..1eedcfa9 --- /dev/null +++ b/test/compress/group_voids.js @@ -0,0 +1,196 @@ +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 g() { + var c = 5; + var d = 5; + console.log(void 0); + } + } + function f5() { + 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, n; + console.log(n); + } + function f2(o) { + var n = 2, v; + console.log(v); + } + function f3() { + var o = 3, n; + console.log(n); + } + function f4() { + console.log(i); + for(var o = 4;;); + var n = 4, i; + function v() { + var o = 5; + var n = 5; + console.log(i); + } + } + function f5() { + try { + var o = 6; + console.log(n); + } catch (o) { + console.log(n); + } + var n; + } + } +} + +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 g() { + var c = 5; + var d = 5; + console.log(void 0); + } + } + function f5() { + 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: { + (function() { + var a = 1; + console.log(void 0); + try { + throw "FAIL"; + } catch (undefined) { + console.log(void 0); + } + })(); + } + expect: { + (function() { + var o = 1, c; + console.log(c); + try { + throw "FAIL"; + } catch (o) { + console.log(c); + } + })(); + } + expect_stdout: [ + "undefined", + "undefined", + ] +}