diff --git a/lib/compress.js b/lib/compress.js index f7232e9a..3f2e1ec6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -185,90 +185,89 @@ function Compressor(options, false_by_default) { }; } -Compressor.prototype = new TreeTransformer; -merge(Compressor.prototype, { - option: function(key) { return this.options[key] }, - exposed: function(def) { - if (def.exported) return true; - if (def.undeclared) return true; - if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false; - var toplevel = this.toplevel; - return !all(def.orig, function(sym) { - return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"]; - }); - }, - compress: function(node) { - node = node.resolve_defines(this); - node.hoist_exports(this); - if (this.option("expression")) { - node.process_expression(true); - } - var merge_vars = this.options.merge_vars; - var passes = +this.options.passes || 1; - var min_count = 1 / 0; - var stopping = false; - var mangle = { ie: this.option("ie") }; - for (var pass = 0; pass < passes; pass++) { - node.figure_out_scope(mangle); - if (pass > 0 || this.option("reduce_vars")) - node.reset_opt_flags(this); - this.options.merge_vars = merge_vars && (stopping || pass == passes - 1); - node = node.transform(this); - if (passes > 1) { - var count = 0; - node.walk(new TreeWalker(function() { - count++; - })); - AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", { - pass: pass, - min_count: min_count, - count: count, - }); - if (count < min_count) { - min_count = count; - stopping = false; - } else if (stopping) { - break; - } else { - stopping = true; - } +Compressor.prototype = new TreeTransformer(function(node, descend, in_list) { + if (node._squeezed) return node; + var is_scope = node instanceof AST_Scope; + if (is_scope) { + node.hoist_properties(this); + node.hoist_declarations(this); + node.process_boolean_returns(this); + } + // Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize() + // would call AST_Node.transform() if a different instance of AST_Node is + // produced after OPT(). + // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction. + // Migrate and defer all children's AST_Node.transform() to below, which + // will now happen after this parent AST_Node has been properly substituted + // thus gives a consistent AST snapshot. + descend(node, this); + // Existing code relies on how AST_Node.optimize() worked, and omitting the + // following replacement call would result in degraded efficiency of both + // output and performance. + descend(node, this); + var opt = node.optimize(this); + if (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) { + opt.drop_unused(this); + if (opt.merge_variables(this)) opt.drop_unused(this); + descend(opt, this); + } + if (opt === node) opt._squeezed = true; + return opt; +}); +Compressor.prototype.option = function(key) { + return this.options[key]; +}; +Compressor.prototype.exposed = function(def) { + if (def.exported) return true; + if (def.undeclared) return true; + if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false; + var toplevel = this.toplevel; + return !all(def.orig, function(sym) { + return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"]; + }); +}; +Compressor.prototype.compress = function(node) { + node = node.resolve_defines(this); + node.hoist_exports(this); + if (this.option("expression")) { + node.process_expression(true); + } + var merge_vars = this.options.merge_vars; + var passes = +this.options.passes || 1; + var min_count = 1 / 0; + var stopping = false; + var mangle = { ie: this.option("ie") }; + for (var pass = 0; pass < passes; pass++) { + node.figure_out_scope(mangle); + if (pass > 0 || this.option("reduce_vars")) + node.reset_opt_flags(this); + this.options.merge_vars = merge_vars && (stopping || pass == passes - 1); + node = node.transform(this); + if (passes > 1) { + var count = 0; + node.walk(new TreeWalker(function() { + count++; + })); + AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", { + pass: pass, + min_count: min_count, + count: count, + }); + if (count < min_count) { + min_count = count; + stopping = false; + } else if (stopping) { + break; + } else { + stopping = true; } } - if (this.option("expression")) { - node.process_expression(false); - } - return node; - }, - before: function(node, descend, in_list) { - if (node._squeezed) return node; - var is_scope = node instanceof AST_Scope; - if (is_scope) { - node.hoist_properties(this); - node.hoist_declarations(this); - node.process_boolean_returns(this); - } - // Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize() - // would call AST_Node.transform() if a different instance of AST_Node is - // produced after OPT(). - // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction. - // Migrate and defer all children's AST_Node.transform() to below, which - // will now happen after this parent AST_Node has been properly substituted - // thus gives a consistent AST snapshot. - descend(node, this); - // Existing code relies on how AST_Node.optimize() worked, and omitting the - // following replacement call would result in degraded efficiency of both - // output and performance. - descend(node, this); - var opt = node.optimize(this); - if (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) { - opt.drop_unused(this); - if (opt.merge_variables(this)) opt.drop_unused(this); - descend(opt, this); - } - if (opt === node) opt._squeezed = true; - return opt; } -}); + if (this.option("expression")) { + node.process_expression(false); + } + return node; +}; (function(OPT) { OPT(AST_Node, function(self, compressor) { diff --git a/lib/utils.js b/lib/utils.js index 69c2dcd1..4c465844 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -96,15 +96,6 @@ function defaults(args, defs, croak) { return defs; } -function merge(obj, ext) { - var count = 0; - for (var i in ext) if (HOP(ext, i)) { - obj[i] = ext[i]; - count++; - } - return count; -} - function noop() {} function return_false() { return false; } function return_true() { return true; } @@ -171,63 +162,80 @@ function all(array, predicate) { } function Dictionary() { - this._values = Object.create(null); - this._size = 0; + this.values = Object.create(null); } Dictionary.prototype = { set: function(key, val) { - if (!this.has(key)) ++this._size; - this._values["$" + key] = val; + if (key == "__proto__") { + this.proto_value = val; + } else { + this.values[key] = val; + } return this; }, add: function(key, val) { - if (this.has(key)) { - this.get(key).push(val); + var list = this.get(key); + if (list) { + list.push(val); } else { this.set(key, [ val ]); } return this; }, - get: function(key) { return this._values["$" + key] }, + get: function(key) { + return key == "__proto__" ? this.proto_value : this.values[key]; + }, del: function(key) { - if (this.has(key)) { - --this._size; - delete this._values["$" + key]; + if (key == "__proto__") { + delete this.proto_value; + } else { + delete this.values[key]; } return this; }, - has: function(key) { return ("$" + key) in this._values }, + has: function(key) { + return key == "__proto__" ? "proto_value" in this : key in this.values; + }, all: function(predicate) { - for (var i in this._values) - if (!predicate(this._values[i], i.substr(1))) - return false; + for (var i in this.values) + if (!predicate(this.values[i], i)) return false; + if ("proto_value" in this && !predicate(this.proto_value, "__proto__")) return false; return true; }, each: function(f) { - for (var i in this._values) - f(this._values[i], i.substr(1)); + for (var i in this.values) + f(this.values[i], i); + if ("proto_value" in this) f(this.proto_value, "__proto__"); }, size: function() { - return this._size; + return Object.keys(this.values).length + ("proto_value" in this); }, map: function(f) { var ret = []; - for (var i in this._values) - ret.push(f(this._values[i], i.substr(1))); + for (var i in this.values) + ret.push(f(this.values[i], i)); + if ("proto_value" in this) ret.push(f(this.proto_value, "__proto__")); return ret; }, clone: function() { var ret = new Dictionary(); - for (var i in this._values) - ret._values[i] = this._values[i]; - ret._size = this._size; + this.each(function(value, i) { + ret.set(i, value); + }); return ret; }, - toObject: function() { return this._values } + toObject: function() { + var obj = {}; + this.each(function(value, i) { + obj["$" + i] = value; + }); + return obj; + }, }; Dictionary.fromObject = function(obj) { var dict = new Dictionary(); - dict._size = merge(dict._values, obj); + for (var i in obj) + if (HOP(obj, i)) dict.set(i.slice(1), obj[i]); return dict; }; diff --git a/test/compress/objects.js b/test/compress/objects.js index 3c7f2d18..beec4bfd 100644 --- a/test/compress/objects.js +++ b/test/compress/objects.js @@ -198,9 +198,9 @@ numeric_literal: { expect_exact: [ 'var obj = {', ' 0: 0,', - ' "-0": 1,', - ' 42: 3,', ' 37: 4,', + ' 42: 3,', + ' "-0": 1,', ' o: 5,', ' 1e42: 8,', ' b: 7',