From: Alex Lam S.L Date: Fri, 3 Mar 2017 10:13:07 +0000 (+0800) Subject: process code with implicit return statement (#1522) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=07accd2fbb78ddbdb427774b3b5287a16fa95b5f;p=UglifyJS.git process code with implicit return statement (#1522) Bookmarklet for instance implicitedly assumes a "completion value" without using `return`. The `expression` option now supports such use cases. Optimisations on IIFEs also enhanced. fixes #354 fixes #543 fixes #625 fixes #628 fixes #640 closes #1293 --- diff --git a/README.md b/README.md index 628bcdec..0b532a83 100644 --- a/README.md +++ b/README.md @@ -426,6 +426,9 @@ to set `true`; it's effectively a shortcut for `foo=true`). such as `console.info` and/or retain side effects from function arguments after dropping the function call then use `pure_funcs` instead. +- `expression` -- default `false`. Pass `true` to preserve completion values + from terminal statements without `return`, e.g. in bookmarklets. + - `keep_fargs` -- default `true`. Prevents the compressor from discarding unused function arguments. You need this for code which relies on `Function.length`. diff --git a/lib/compress.js b/lib/compress.js index ec1e7174..2cd79128 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -80,6 +80,7 @@ function Compressor(options, false_by_default) { screw_ie8 : true, drop_console : false, angular : false, + expression : false, warnings : true, global_defs : {}, passes : 1, @@ -116,12 +117,18 @@ Compressor.prototype = new TreeTransformer; merge(Compressor.prototype, { option: function(key) { return this.options[key] }, compress: function(node) { + if (this.option("expression")) { + node = node.process_expression(true); + } var passes = +this.options.passes || 1; for (var pass = 0; pass < passes && pass < 3; ++pass) { if (pass > 0 || this.option("reduce_vars")) node.reset_opt_flags(this, true); node = node.transform(this); } + if (this.option("expression")) { + node = node.process_expression(false); + } return node; }, warn: function(text, props) { @@ -178,6 +185,42 @@ merge(Compressor.prototype, { return this.print_to_string() == node.print_to_string(); }); + AST_Node.DEFMETHOD("process_expression", function(insert) { + var self = this; + var tt = new TreeTransformer(function(node) { + if (insert && node instanceof AST_SimpleStatement) { + return make_node(AST_Return, node, { + value: node.body + }); + } + if (!insert && node instanceof AST_Return) { + return make_node(AST_SimpleStatement, node, { + body: node.value || make_node(AST_Undefined, node) + }); + } + if (node instanceof AST_Lambda && node !== self) { + return node; + } + if (node instanceof AST_Block) { + var index = node.body.length - 1; + if (index >= 0) { + node.body[index] = node.body[index].transform(tt); + } + } + if (node instanceof AST_If) { + node.body = node.body.transform(tt); + if (node.alternative) { + node.alternative = node.alternative.transform(tt); + } + } + if (node instanceof AST_With) { + node.body = node.body.transform(tt); + } + return node; + }); + return self.transform(tt); + }); + AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){ var reduce_vars = rescan && compressor.option("reduce_vars"); var safe_ids = []; @@ -2030,7 +2073,14 @@ merge(Compressor.prototype, { def(AST_Constant, return_null); def(AST_This, return_null); def(AST_Call, function(compressor, first_in_statement){ - if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this; + if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) { + if (this.expression instanceof AST_Function) { + var node = this.clone(); + node.expression = node.expression.process_expression(false); + return node; + } + return this; + } if (this.pure) { compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' '); @@ -2522,12 +2572,13 @@ merge(Compressor.prototype, { }); OPT(AST_Call, function(self, compressor){ + var exp = self.expression; if (compressor.option("unused") - && self.expression instanceof AST_Function - && !self.expression.uses_arguments - && !self.expression.uses_eval - && self.args.length > self.expression.argnames.length) { - var end = self.expression.argnames.length; + && exp instanceof AST_Function + && !exp.uses_arguments + && !exp.uses_eval + && self.args.length > exp.argnames.length) { + var end = exp.argnames.length; for (var i = end, len = self.args.length; i < len; i++) { var node = self.args[i].drop_side_effect_free(compressor); if (node) { @@ -2537,7 +2588,6 @@ merge(Compressor.prototype, { self.args.length = end; } if (compressor.option("unsafe")) { - var exp = self.expression; if (exp instanceof AST_SymbolRef && exp.undeclared()) { switch (exp.name) { case "Array": @@ -2711,16 +2761,22 @@ merge(Compressor.prototype, { return best_of(self, node); } } - if (compressor.option("side_effects")) { - if (self.expression instanceof AST_Function - && self.args.length == 0 - && !AST_Block.prototype.has_side_effects.call(self.expression, compressor)) { - return make_node(AST_Undefined, self).transform(compressor); + if (exp instanceof AST_Function) { + if (exp.body[0] instanceof AST_Return + && exp.body[0].value.is_constant()) { + var args = self.args.concat(exp.body[0].value); + return AST_Seq.from_array(args).transform(compressor); + } + if (compressor.option("side_effects")) { + if (!AST_Block.prototype.has_side_effects.call(exp, compressor)) { + var args = self.args.concat(make_node(AST_Undefined, self)); + return AST_Seq.from_array(args).transform(compressor); + } } } if (compressor.option("drop_console")) { - if (self.expression instanceof AST_PropAccess) { - var name = self.expression.expression; + if (exp instanceof AST_PropAccess) { + var name = exp.expression; while (name.expression) { name = name.expression; } @@ -2731,12 +2787,6 @@ merge(Compressor.prototype, { } } } - if (self.args.length == 0 - && self.expression instanceof AST_Function - && self.expression.body[0] instanceof AST_Return - && self.expression.body[0].value.is_constant()) { - return self.expression.body[0].value; - } if (compressor.option("negate_iife") && compressor.parent() instanceof AST_SimpleStatement && is_iife_call(self)) { diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 4b613181..20dab3b9 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -632,7 +632,7 @@ iife: { } expect: { function f() { - ~function() {}(b); + b; } } } diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 26b6e489..68739503 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -640,9 +640,7 @@ call_args: { expect: { const a = 1; console.log(1); - +function(a) { - return 1; - }(1); + +(1, 1); } } @@ -663,9 +661,7 @@ call_args_drop_param: { expect: { const a = 1; console.log(1); - +function() { - return 1; - }(b); + +(b, 1); } } diff --git a/test/compress/functions.js b/test/compress/functions.js index d3d99b57..18505a18 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -35,9 +35,9 @@ iifes_returning_constants_keep_fargs_true: { console.log("okay"); console.log(123); console.log(void 0); - console.log(function(x,y,z){return 2}(1,2,3)); - console.log(function(x,y){return 6}(2,3)); - console.log(function(x, y){return 6}(2,3,a(),b())); + console.log(2); + console.log(6); + console.log((a(), b(), 6)); } } @@ -71,6 +71,6 @@ iifes_returning_constants_keep_fargs_false: { console.log(void 0); console.log(2); console.log(6); - console.log(function(){return 6}(a(),b())); + console.log((a(), b(), 6)); } } diff --git a/test/compress/issue-640.js b/test/compress/issue-640.js new file mode 100644 index 00000000..dd3f3f21 --- /dev/null +++ b/test/compress/issue-640.js @@ -0,0 +1,317 @@ +cond_5: { + options = { + conditionals: true, + expression: true, + } + input: { + if (some_condition()) { + if (some_other_condition()) { + do_something(); + } else { + alternate(); + } + } else { + alternate(); + } + + if (some_condition()) { + if (some_other_condition()) { + do_something(); + } + } + } + expect: { + some_condition() && some_other_condition() ? do_something() : alternate(); + if (some_condition() && some_other_condition()) do_something(); + } +} + +dead_code_const_annotation_regex: { + options = { + booleans : true, + conditionals : true, + dead_code : true, + evaluate : true, + expression : true, + loops : true, + } + input: { + var unused; + // @constraint this shouldn't be a constant + var CONST_FOO_ANN = false; + if (CONST_FOO_ANN) { + console.log("reachable"); + } + } + expect: { + var unused; + var CONST_FOO_ANN = !1; + if (CONST_FOO_ANN) console.log('reachable'); + } +} + +drop_console_2: { + options = { + drop_console: true, + expression: true, + } + input: { + console.log('foo'); + console.log.apply(console, arguments); + } + expect: { + // with regular compression these will be stripped out as well + void 0; + void 0; + } +} + +drop_value: { + options = { + expression: true, + side_effects: true, + } + input: { + (1, [2, foo()], 3, {a:1, b:bar()}); + } + expect: { + foo(), {a:1, b:bar()}; + } +} + +wrongly_optimized: { + options = { + conditionals: true, + booleans: true, + evaluate: true, + expression: true, + } + input: { + function func() { + foo(); + } + if (func() || true) { + bar(); + } + } + expect: { + function func() { + foo(); + } + // TODO: optimize to `func(), bar()` + if (func(), !0) bar(); + } +} + +negate_iife_1: { + options = { + expression: true, + negate_iife: true, + } + input: { + (function(){ stuff() })(); + } + expect: { + (function(){ stuff() })(); + } +} + +negate_iife_3: { + options = { + conditionals: true, + expression: true, + negate_iife: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + (function(){ return t })() ? console.log(true) : console.log(false); + } +} + +negate_iife_3_off: { + options = { + conditionals: true, + expression: true, + negate_iife: false, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + (function(){ return t })() ? console.log(true) : console.log(false); + } +} + +negate_iife_4: { + options = { + conditionals: true, + expression: true, + negate_iife: true, + sequences: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + (function(){ + console.log("something"); + })(); + } + expect: { + (function(){ return t })() ? console.log(true) : console.log(false), function(){ + console.log("something"); + }(); + } +} + +negate_iife_5: { + options = { + conditionals: true, + expression: true, + negate_iife: true, + sequences: true, + } + input: { + if ((function(){ return t })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + (function(){ return t })() ? foo(true) : bar(false), function(){ + console.log("something"); + }(); + } +} + +negate_iife_5_off: { + options = { + conditionals: true, + expression: true, + negate_iife: false, + sequences: true, + }; + input: { + if ((function(){ return t })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + (function(){ return t })() ? foo(true) : bar(false), function(){ + console.log("something"); + }(); + } +} + +issue_1254_negate_iife_true: { + options = { + expression: true, + negate_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()(); + } + expect_exact: '(function(){return function(){console.log("test")}})()();' +} + +issue_1254_negate_iife_nested: { + options = { + expression: true, + negate_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()()()()(); + } + expect_exact: '(function(){return function(){console.log("test")}})()()()()();' +} + +conditional: { + options = { + expression: true, + pure_funcs: [ "pure" ], + side_effects: true, + } + input: { + pure(1 | a() ? 2 & b() : 7 ^ c()); + pure(1 | a() ? 2 & b() : 5); + pure(1 | a() ? 4 : 7 ^ c()); + pure(1 | a() ? 4 : 5); + pure(3 ? 2 & b() : 7 ^ c()); + pure(3 ? 2 & b() : 5); + pure(3 ? 4 : 7 ^ c()); + pure(3 ? 4 : 5); + } + expect: { + 1 | a() ? b() : c(); + 1 | a() && b(); + 1 | a() || c(); + a(); + 3 ? b() : c(); + 3 && b(); + 3 || c(); + pure(3 ? 4 : 5); + } +} + +limit_1: { + options = { + expression: true, + sequences: 3, + } + input: { + a; + b; + c; + d; + e; + f; + g; + h; + i; + j; + k; + } + expect: { + // Turned into a single return statement + // so it can no longer be split into lines + a,b,c,d,e,f,g,h,i,j,k; + } +} + +iife: { + options = { + expression: true, + sequences: true, + } + input: { + x = 42; + (function a() {})(); + !function b() {}(); + ~function c() {}(); + +function d() {}(); + -function e() {}(); + void function f() {}(); + typeof function g() {}(); + } + expect: { + x = 42, function a() {}(), function b() {}(), function c() {}(), + function d() {}(), function e() {}(), function f() {}(), typeof function g() {}(); + } +} diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index f17ae206..9a0b5a46 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -32,6 +32,19 @@ negate_iife_2: { } } +negate_iife_2_side_effects: { + options = { + negate_iife: true, + side_effects: true, + } + input: { + (function(){ return {} })().x = 10; // should not transform this one + } + expect: { + (function(){ return {} })().x = 10; + } +} + negate_iife_3: { options = { negate_iife: true, @@ -45,6 +58,34 @@ negate_iife_3: { } } +negate_iife_3_evaluate: { + options = { + conditionals: true, + evaluate: true, + negate_iife: true, + } + input: { + (function(){ return true })() ? console.log(true) : console.log(false); + } + expect: { + console.log(true); + } +} + +negate_iife_3_side_effects: { + options = { + conditionals: true, + negate_iife: true, + side_effects: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + !function(){ return t }() ? console.log(false) : console.log(true); + } +} + negate_iife_3_off: { options = { negate_iife: false, @@ -58,6 +99,20 @@ negate_iife_3_off: { } } +negate_iife_3_off_evaluate: { + options = { + conditionals: true, + evaluate: true, + negate_iife: false, + } + input: { + (function(){ return true })() ? console.log(true) : console.log(false); + } + expect: { + console.log(true); + } +} + negate_iife_4: { options = { negate_iife: true, @@ -320,3 +375,35 @@ issue_1288: { }(0); } } + +issue_1288_side_effects: { + options = { + conditionals: true, + negate_iife: true, + side_effects: true, + } + input: { + if (w) ; + else { + (function f() {})(); + } + if (!x) { + (function() { + x = {}; + })(); + } + if (y) + (function() {})(); + else + (function(z) { + return z; + })(0); + } + expect: { + w; + x || function() { + x = {}; + }(); + y; + } +} diff --git a/test/compress/sequences.js b/test/compress/sequences.js index d93f5237..7bb274cb 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -248,6 +248,6 @@ iife: { } expect: { x = 42, function a() {}(), function b() {}(), function c() {}(), - function d() {}(), function e() {}(), function f() {}(), function g() {}() + function d() {}(), function e() {}(), function f() {}(), function g() {}(); } }