From: Alex Lam S.L Date: Sun, 16 Apr 2017 17:36:50 +0000 (+0800) Subject: enhance `reduce_vars` (#1814) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=1a498db2d3e520d1711144c423312c62a2673115;p=UglifyJS.git enhance `reduce_vars` (#1814) - allow immediate assignment after declaration of variable - relax modification rule for immutable value - fix order of visit for TreeWalker - remove extraneous code --- diff --git a/lib/ast.js b/lib/ast.js index 0fa051b8..739c21c2 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -182,21 +182,13 @@ var AST_BlockStatement = DEFNODE("BlockStatement", null, { }, AST_Block); var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { - $documentation: "The empty statement (empty block or simply a semicolon)", - _walk: function(visitor) { - return visitor._visit(this); - } + $documentation: "The empty statement (empty block or simply a semicolon)" }, AST_Statement); var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", $propdoc: { body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" - }, - _walk: function(visitor) { - return visitor._visit(this, function(){ - this.body._walk(visitor); - }); } }, AST_Statement); @@ -551,11 +543,11 @@ var AST_Call = DEFNODE("Call", "expression args", { }, _walk: function(visitor) { return visitor._visit(this, function(){ - this.expression._walk(visitor); var args = this.args; for (var i = 0, len = args.length; i < len; i++) { args[i]._walk(visitor); } + this.expression._walk(visitor); }); } }); diff --git a/lib/compress.js b/lib/compress.js index 6062be54..640fcecc 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -119,7 +119,7 @@ merge(Compressor.prototype, { option: function(key) { return this.options[key] }, compress: function(node) { if (this.option("expression")) { - node = node.process_expression(true); + node.process_expression(true); } var passes = +this.options.passes || 1; for (var pass = 0; pass < passes && pass < 3; ++pass) { @@ -128,7 +128,7 @@ merge(Compressor.prototype, { node = node.transform(this); } if (this.option("expression")) { - node = node.process_expression(false); + node.process_expression(false); } return node; }, @@ -200,7 +200,7 @@ merge(Compressor.prototype, { return this.TYPE == node.TYPE && this.print_to_string() == node.print_to_string(); }); - AST_Node.DEFMETHOD("process_expression", function(insert, compressor) { + AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) { var self = this; var tt = new TreeTransformer(function(node) { if (insert && node instanceof AST_SimpleStatement) { @@ -244,10 +244,10 @@ merge(Compressor.prototype, { } return node; }); - return self.transform(tt); + self.transform(tt); }); - AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){ + AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan) { var reduce_vars = rescan && compressor.option("reduce_vars"); var toplevel = compressor.option("toplevel"); var safe_ids = Object.create(null); @@ -258,7 +258,7 @@ merge(Compressor.prototype, { d.fixed = false; } }); - var tw = new TreeWalker(function(node, descend){ + var tw = new TreeWalker(function(node, descend) { node._squeezed = false; node._optimized = false; if (reduce_vars) { @@ -268,7 +268,7 @@ merge(Compressor.prototype, { var d = node.definition(); d.references.push(node); if (d.fixed === undefined || !is_safe(d) - || is_modified(node, 0, node.fixed_value() instanceof AST_Lambda)) { + || is_modified(node, 0, is_immutable(node.fixed_value()))) { d.fixed = false; } } @@ -293,6 +293,20 @@ merge(Compressor.prototype, { d.fixed = false; } } + if (node instanceof AST_Assign + && node.operator == "=" + && node.left instanceof AST_SymbolRef) { + var d = node.left.definition(); + if (HOP(safe_ids, d.id) && d.fixed == null) { + d.fixed = function() { + return node.right; + }; + mark(d, false); + node.right.walk(tw); + mark(d, true); + return true; + } + } if (node instanceof AST_Defun) { var d = node.name.definition(); if (!toplevel && d.global || is_safe(d)) { @@ -309,21 +323,24 @@ merge(Compressor.prototype, { } var iife; if (node instanceof AST_Function - && !node.name && (iife = tw.parent()) instanceof AST_Call && iife.expression === node) { - // Virtually turn IIFE parameters into variable definitions: - // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})() - // So existing transformation rules can work on them. - node.argnames.forEach(function(arg, i) { - var d = arg.definition(); - d.fixed = function() { - return iife.args[i] || make_node(AST_Undefined, iife); - }; - mark(d, true); - }); + if (node.name) { + node.name.definition().fixed = node; + } else { + // Virtually turn IIFE parameters into variable definitions: + // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})() + // So existing transformation rules can work on them. + node.argnames.forEach(function(arg, i) { + var d = arg.definition(); + d.fixed = function() { + return iife.args[i] || make_node(AST_Undefined, iife); + }; + mark(d, true); + }); + } } - if (node instanceof AST_If || node instanceof AST_DWLoop) { + if (node instanceof AST_If) { node.condition.walk(tw); push(); node.body.walk(tw); @@ -335,6 +352,13 @@ merge(Compressor.prototype, { } return true; } + if (node instanceof AST_DWLoop) { + push(); + node.condition.walk(tw); + node.body.walk(tw); + pop(); + return true; + } if (node instanceof AST_LabeledStatement) { push(); node.body.walk(tw); @@ -401,13 +425,17 @@ merge(Compressor.prototype, { def.should_replace = undefined; } - function is_modified(node, level, func) { + function is_immutable(value) { + return value && value.is_constant() || value instanceof AST_Lambda; + } + + function is_modified(node, level, immutable) { var parent = tw.parent(level); if (is_lhs(node, parent) - || !func && parent instanceof AST_Call && parent.expression === node) { + || !immutable && parent instanceof AST_Call && parent.expression === node) { return true; } else if (parent instanceof AST_PropAccess && parent.expression === node) { - return !func && is_modified(parent, level + 1); + return !immutable && is_modified(parent, level + 1); } } }); @@ -2167,7 +2195,7 @@ merge(Compressor.prototype, { if (this.expression instanceof AST_Function && (!this.expression.name || !this.expression.name.definition().references.length)) { var node = this.clone(); - node.expression = node.expression.process_expression(false, compressor); + node.expression.process_expression(false, compressor); return node; } return this; diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 7621dd4a..6e079c1a 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1996,6 +1996,146 @@ catch_var: { expect_stdout: "true" } +var_assign_1: { + options = { + evaluate: true, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + !function() { + var a; + a = 2; + console.log(a); + }(); + } + expect: { + !function() { + console.log(2); + }(); + } + expect_stdout: "2" +} + +var_assign_2: { + options = { + evaluate: true, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + !function() { + var a; + if (a = 2) console.log(a); + }(); + } + expect: { + !function() { + if (2) console.log(2); + }(); + } + expect_stdout: "2" +} + +var_assign_3: { + options = { + evaluate: true, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + !function() { + var a; + while (a = 2); + console.log(a); + }(); + } + expect: { + !function() { + var a; + while (a = 2); + console.log(a); + }(); + } +} + +var_assign_4: { + options = { + evaluate: true, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + !function a() { + a = 2; + console.log(a); + }(); + } + expect: { + !function a() { + a = 2, + console.log(a); + }(); + } +} + +var_assign_5: { + options = { + evaluate: true, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + !function() { + var a; + !function(b) { + a = 2; + console.log(a, b); + }(a); + }(); + } + expect: { + !function() { + var a; + !function(b) { + a = 2, + console.log(a, b); + }(a); + }(); + } + expect_stdout: "2 undefined" +} + +immutable: { + options = { + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + !function() { + var a = "test"; + console.log(a.indexOf("e")); + }(); + } + expect: { + !function() { + console.log("test".indexOf("e")); + }(); + } + expect_stdout: "1" +} + issue_1814_1: { options = { evaluate: true,