diff --git a/lib/compress.js b/lib/compress.js index 57554fa5..366a2616 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -59,6 +59,8 @@ function Compressor(options, false_by_default) { evaluate : !false_by_default, booleans : !false_by_default, loops : !false_by_default, + inlineconst : false, + inlinestr : false, unused : !false_by_default, hoist_funs : !false_by_default, hoist_vars : false, @@ -83,6 +85,7 @@ merge(Compressor.prototype, { before: function(node, descend, in_list) { if (node._squeezed) return node; if (node instanceof AST_Scope) { + node.inline_const_primitive(this); node.drop_unused(this); node = node.hoist_declarations(this); } @@ -165,7 +168,9 @@ merge(Compressor.prototype, { return make_node(AST_Null, orig).optimize(compressor); } if (val instanceof RegExp) { - return make_node(AST_RegExp, orig).optimize(compressor); + return make_node(AST_RegExp, orig, { + value: val + }).optimize(compressor); } throw new Error(string_template("Can't handle constant of type: {type}", { type: typeof val @@ -593,8 +598,8 @@ merge(Compressor.prototype, { // of the array is always an AST_Node descendant; when // evaluation was successful it's a node that represents the // constant; otherwise it's the original node. - AST_Node.DEFMETHOD("evaluate", function(compressor){ - if (!compressor.option("evaluate")) return [ this ]; + AST_Node.DEFMETHOD("evaluate", function(compressor, ignore_setting){ + if (!compressor.option("evaluate") && !ignore_setting) return [ this ]; try { var val = this._eval(), ast = make_node_from_constant(compressor, val, this); return [ best_of(ast, this), val ]; @@ -886,6 +891,68 @@ merge(Compressor.prototype, { return self; }); + AST_Scope.DEFMETHOD("inline_const_primitive", function(compressor){ + var self = this; + var inlineStringThreshold = compressor.option("inlinestr"); + if (compressor.option("inlineconst") + && !(self instanceof AST_Toplevel) + && !self.uses_eval + ) { + var in_use = []; + var definitions = {}; + // pass 1: find out symbols defined for only once and has + // could be evaluate as number or bool constant. + var scope = this; + var tw = new TreeWalker(function(node, descend){ + if (node !== self) { + if (node instanceof AST_Definitions && scope === self) { + node.definitions.forEach(function(def){ + if (def.value) { + var value = def.value.evaluate(compressor, true); + def.value = value[0]; + if (value.length > 1) { + if (typeof value[1] === 'string' && value[1].length < inlineStringThreshold + || typeof value[1] === 'number' + || typeof value[1] === 'boolean' + || value[1] === null + ) + definitions[def.name.name] = value[1]; + } + } + }); + return true; + } + if (node instanceof AST_Assign) { + if (definitions.hasOwnProperty(node.left.name)) + delete definitions[node.left.name]; + return true; + } + if (node instanceof AST_Scope) { + var save_scope = scope; + scope = node; + descend(); + scope = save_scope; + return true; + } + } + }); + self.walk(tw); + + // pass 2: we should replace symbols refer to constants + // with corresponding values. + var tt = new TreeTransformer( + function before(node, descend, in_list) { + if (node instanceof AST_SymbolRef) { + if (definitions.hasOwnProperty(node.name)) { + return make_node_from_constant(compressor, definitions[node.name], node.orig); + } + } + } + ); + self.transform(tt); + } + }); + AST_Scope.DEFMETHOD("drop_unused", function(compressor){ var self = this; if (compressor.option("unused") diff --git a/test/compress/inlineconst.js b/test/compress/inlineconst.js new file mode 100644 index 00000000..d9cc3bd9 --- /dev/null +++ b/test/compress/inlineconst.js @@ -0,0 +1,67 @@ +inlineconst_number: { + options = { inlineconst: true }; + input: { + function f(c) { + var a = 5, b = 10; + return a + b + c; + } + } + expect: { + function f(c) { + var a = 5, b = 10; + return 5 + 10 + c; + } + } +} + +inlineconst_bool_and_null: { + options = { inlineconst: true }; + input: { + function f(c) { + var a = true, b = false, n = null; + return (n || b || a) ^ c; + } + } + expect: { + function f(c) { + var a = true, b = false, n = null; + return (null || false || true) ^ c; + } + } +} + +inlineconst_string: { + options = { inlineconst: true, inlinestr: 2 }; + input: { + function f() { + var s1 = "1", s2 = "12"; + return s1 + s2; + } + } + expect: { + function f() { + var s1 = "1", s2 = "12"; + return "1" + s2; + } + } +} + +inlineconst_in_nested_function: { + options = { inlineconst: true }; + input: { + function f() { + var x = 1; + return function t() { + return x + 1; + }; + } + } + expect: { + function f() { + var x = 1; + return function t() { + return 1 + 1; + }; + } + } +}