From: alexlamsl Date: Sat, 18 Feb 2017 11:11:57 +0000 (+0800) Subject: clean up `negate_iife` X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=f0ff6189be3a75cd4ccb1c38051ec27f9b30d67f;p=UglifyJS.git clean up `negate_iife` - remove extra tree scanning phase for `negate_iife` - `negate_iife` now only deals with the narrowest form, i.e. IIFE sitting directly under `AST_SimpleStatement` - `booleans`, `conditionals` etc. will now take care the rest via more accurate accounting - `a(); void b();` => `a(); b();` fixes #1288 closes #1451 --- diff --git a/lib/compress.js b/lib/compress.js index 536b7518..b66c5582 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -278,6 +278,15 @@ merge(Compressor.prototype, { return x; }; + var readOnlyPrefix = makePredicate("! ~ + - void typeof"); + function statement_to_expression(stat) { + if (stat.body instanceof AST_UnaryPrefix && readOnlyPrefix(stat.body.operator)) { + return stat.body.expression; + } else { + return stat.body; + } + } + function tighten_body(statements, compressor) { var CHANGED, max_iter = 10; do { @@ -303,10 +312,6 @@ merge(Compressor.prototype, { } } while (CHANGED && max_iter-- > 0); - if (compressor.option("negate_iife")) { - negate_iifes(statements, compressor); - } - return statements; function collapse_single_use_vars(statements, compressor) { @@ -753,7 +758,7 @@ merge(Compressor.prototype, { if (seqLength(seq) >= compressor.sequences_limit) { push_seq(); } - seq.push(stat.body); + seq.push(seq.length > 0 ? statement_to_expression(stat) : stat.body); } else { push_seq(); ret.push(stat); @@ -802,7 +807,7 @@ merge(Compressor.prototype, { stat.init = cons_seq(stat.init); } else if (!stat.init) { - stat.init = prev.body; + stat.init = statement_to_expression(prev); ret.pop(); } } catch(ex) { @@ -859,50 +864,6 @@ merge(Compressor.prototype, { }, []); }; - function negate_iifes(statements, compressor) { - function is_iife_call(node) { - if (node instanceof AST_Call) { - return node.expression instanceof AST_Function || is_iife_call(node.expression); - } - return false; - } - - statements.forEach(function(stat){ - if (stat instanceof AST_SimpleStatement) { - stat.body = (function transform(thing) { - return thing.transform(new TreeTransformer(function(node){ - if (node instanceof AST_New) { - return node; - } - if (is_iife_call(node)) { - return make_node(AST_UnaryPrefix, node, { - operator: "!", - expression: node - }); - } - else if (node instanceof AST_Call) { - node.expression = transform(node.expression); - } - else if (node instanceof AST_Seq) { - node.car = transform(node.car); - } - else if (node instanceof AST_Conditional) { - var expr = transform(node.condition); - if (expr !== node.condition) { - // it has been negated, reverse - node.condition = expr; - var tmp = node.consequent; - node.consequent = node.alternative; - node.alternative = tmp; - } - } - return node; - })); - })(stat.body); - } - }); - }; - }; function extract_functions_from_statement_array(statements) { @@ -1007,7 +968,15 @@ merge(Compressor.prototype, { return ast1.print_to_string().length > ast2.print_to_string().length ? ast2 : ast1; - }; + } + + function best_of_statement(ast1, ast2) { + return best_of(make_node(AST_SimpleStatement, ast1, { + body: ast1 + }), make_node(AST_SimpleStatement, ast2, { + body: ast2 + })).body; + } // methods to evaluate a constant expression (function (def){ @@ -1227,7 +1196,17 @@ merge(Compressor.prototype, { operator: "!", expression: exp }); - }; + } + function best(orig, alt, first_in_statement) { + var negated = basic_negation(orig); + if (first_in_statement) { + var stat = make_node(AST_SimpleStatement, alt, { + body: alt + }); + return best_of(negated, stat) === stat ? alt : negated; + } + return best_of(negated, alt); + } def(AST_Node, function(){ return basic_negation(this); }); @@ -1247,13 +1226,13 @@ merge(Compressor.prototype, { self.cdr = self.cdr.negate(compressor); return self; }); - def(AST_Conditional, function(compressor){ + def(AST_Conditional, function(compressor, first_in_statement){ var self = this.clone(); self.consequent = self.consequent.negate(compressor); self.alternative = self.alternative.negate(compressor); - return best_of(basic_negation(this), self); + return best(this, self, first_in_statement); }); - def(AST_Binary, function(compressor){ + def(AST_Binary, function(compressor, first_in_statement){ var self = this.clone(), op = this.operator; if (compressor.option("unsafe_comps")) { switch (op) { @@ -1270,20 +1249,20 @@ merge(Compressor.prototype, { case "!==": self.operator = "==="; return self; case "&&": self.operator = "||"; - self.left = self.left.negate(compressor); + self.left = self.left.negate(compressor, first_in_statement); self.right = self.right.negate(compressor); - return best_of(basic_negation(this), self); + return best(this, self, first_in_statement); case "||": self.operator = "&&"; - self.left = self.left.negate(compressor); + self.left = self.left.negate(compressor, first_in_statement); self.right = self.right.negate(compressor); - return best_of(basic_negation(this), self); + return best(this, self, first_in_statement); } return basic_negation(this); }); })(function(node, func){ - node.DEFMETHOD("negate", function(compressor){ - return func.call(this, compressor); + node.DEFMETHOD("negate", function(compressor, first_in_statement){ + return func.call(this, compressor, first_in_statement); }); }); @@ -1954,8 +1933,8 @@ merge(Compressor.prototype, { return make_node(AST_SimpleStatement, self, { body: make_node(AST_Conditional, self, { condition : self.condition, - consequent : self.body.body, - alternative : self.alternative.body + consequent : statement_to_expression(self.body), + alternative : statement_to_expression(self.alternative) }) }).transform(compressor); } @@ -1971,14 +1950,14 @@ merge(Compressor.prototype, { body: make_node(AST_Binary, self, { operator : "||", left : negated, - right : self.body.body + right : statement_to_expression(self.body) }) }).transform(compressor); return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "&&", left : self.condition, - right : self.body.body + right : statement_to_expression(self.body) }) }).transform(compressor); } @@ -1989,7 +1968,7 @@ merge(Compressor.prototype, { body: make_node(AST_Binary, self, { operator : "||", left : self.condition, - right : self.alternative.body + right : statement_to_expression(self.alternative) }) }).transform(compressor); } @@ -2372,7 +2351,19 @@ merge(Compressor.prototype, { } } } - return self.evaluate(compressor)[0]; + if (compressor.option("negate_iife") + && compressor.parent() instanceof AST_SimpleStatement + && is_iife_call(self)) { + return self.negate(compressor, true); + } + return self; + + function is_iife_call(node) { + if (node instanceof AST_Call && !(node instanceof AST_New)) { + return node.expression instanceof AST_Function || is_iife_call(node.expression); + } + return false; + } }); OPT(AST_New, function(self, compressor){ @@ -2459,6 +2450,10 @@ merge(Compressor.prototype, { // !!foo ==> foo, if we're in boolean context return e.expression; } + if (e instanceof AST_Binary) { + var statement = first_in_statement(compressor); + self = (statement ? best_of_statement : best_of)(self, e.negate(compressor, statement)); + } break; case "typeof": // typeof always returns a non-empty string, thus it's @@ -2472,9 +2467,6 @@ merge(Compressor.prototype, { } return make_node(AST_True, self); } - if (e instanceof AST_Binary && self.operator == "!") { - self = best_of(self, e.negate(compressor)); - } } return self.evaluate(compressor)[0]; }); @@ -2651,11 +2643,12 @@ merge(Compressor.prototype, { if (compressor.option("comparisons") && self.is_boolean()) { if (!(compressor.parent() instanceof AST_Binary) || compressor.parent() instanceof AST_Assign) { + var statement = first_in_statement(compressor); var negated = make_node(AST_UnaryPrefix, self, { operator: "!", - expression: self.negate(compressor) + expression: self.negate(compressor, statement) }); - self = best_of(self, negated); + self = (statement ? best_of_statement : best_of)(self, negated); } if (compressor.option("unsafe_comps")) { switch (self.operator) { @@ -2859,8 +2852,9 @@ merge(Compressor.prototype, { return maintain_this_binding(compressor.parent(), self, self.alternative); } } - var negated = cond[0].negate(compressor); - if (best_of(cond[0], negated) === negated) { + var statement = first_in_statement(compressor); + var negated = cond[0].negate(compressor, statement); + if ((statement ? best_of_statement : best_of)(cond[0], negated) === negated) { self = make_node(AST_Conditional, self, { condition: negated, consequent: self.alternative, diff --git a/lib/output.js b/lib/output.js index 50e5aa43..b6f0a873 100644 --- a/lib/output.js +++ b/lib/output.js @@ -425,7 +425,6 @@ function OutputStream(options) { pos : function() { return current_pos }, push_node : function(node) { stack.push(node) }, pop_node : function() { return stack.pop() }, - stack : function() { return stack }, parent : function(n) { return stack[stack.length - 2 - (n || 0)]; } @@ -1334,30 +1333,6 @@ function OutputStream(options) { } }; - // return true if the node at the top of the stack (that means the - // innermost node in the current output) is lexically the first in - // a statement. - function first_in_statement(output) { - var a = output.stack(), i = a.length, node = a[--i], p = a[--i]; - while (i > 0) { - if (p instanceof AST_Statement && p.body === node) - return true; - if ((p instanceof AST_Seq && p.car === node ) || - (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || - (p instanceof AST_Dot && p.expression === node ) || - (p instanceof AST_Sub && p.expression === node ) || - (p instanceof AST_Conditional && p.condition === node ) || - (p instanceof AST_Binary && p.left === node ) || - (p instanceof AST_UnaryPostfix && p.expression === node )) - { - node = p; - p = a[--i]; - } else { - return false; - } - } - }; - // self should be AST_New. decide if we want to show parens or not. function need_constructor_parens(self, output) { // Always print parentheses with arguments diff --git a/lib/utils.js b/lib/utils.js index d0a21ac9..a0571d65 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -320,3 +320,26 @@ Dictionary.fromObject = function(obj) { function HOP(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } + +// return true if the node at the top of the stack (that means the +// innermost node in the current output) is lexically the first in +// a statement. +function first_in_statement(stack) { + var node = stack.parent(-1); + for (var i = 0, p; p = stack.parent(i); i++) { + if (p instanceof AST_Statement && p.body === node) + return true; + if ((p instanceof AST_Seq && p.car === node ) || + (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || + (p instanceof AST_Dot && p.expression === node ) || + (p instanceof AST_Sub && p.expression === node ) || + (p instanceof AST_Conditional && p.condition === node ) || + (p instanceof AST_Binary && p.left === node ) || + (p instanceof AST_UnaryPostfix && p.expression === node )) + { + node = p; + } else { + return false; + } + } +} diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index 0c111604..312e0f28 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -10,6 +10,16 @@ negate_iife_1: { } } +negate_iife_1_off: { + options = { + negate_iife: false, + }; + input: { + (function(){ stuff() })(); + } + expect_exact: '(function(){stuff()})();' +} + negate_iife_2: { options = { negate_iife: true @@ -25,6 +35,20 @@ negate_iife_2: { negate_iife_3: { options = { negate_iife: true, + conditionals: true + }; + input: { + (function(){ return true })() ? console.log(true) : console.log(false); + } + expect: { + !function(){ return true }() ? console.log(false) : console.log(true); + } +} + +negate_iife_3_off: { + options = { + negate_iife: false, + conditionals: true, }; input: { (function(){ return true })() ? console.log(true) : console.log(false); @@ -37,6 +61,7 @@ negate_iife_3: { negate_iife_3: { options = { negate_iife: true, + conditionals: true, sequences: true }; input: { @@ -52,6 +77,41 @@ negate_iife_3: { } } +sequence_off: { + options = { + negate_iife: false, + conditionals: true, + sequences: true, + passes: 2, + }; + input: { + function f() { + (function(){ return true })() ? console.log(true) : console.log(false); + (function(){ + console.log("something"); + })(); + } + function g() { + (function(){ + console.log("something"); + })(); + (function(){ return true })() ? console.log(true) : console.log(false); + } + } + expect: { + function f() { + !function(){ return true }() ? console.log(false) : console.log(true), function(){ + console.log("something"); + }(); + } + function g() { + (function(){ + console.log("something"); + })(), function(){ return true }() ? console.log(true) : console.log(false); + } + } +} + negate_iife_4: { options = { negate_iife: true, @@ -75,6 +135,29 @@ negate_iife_4: { } } +negate_iife_4_off: { + options = { + negate_iife: false, + sequences: true, + conditionals: true, + }; + input: { + if ((function(){ return true })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + !function(){ return true }() ? bar(false) : foo(true), function(){ + console.log("something"); + }(); + } +} + negate_iife_nested: { options = { negate_iife: true, @@ -107,6 +190,38 @@ negate_iife_nested: { } } +negate_iife_nested_off: { + options = { + negate_iife: false, + sequences: true, + conditionals: true, + }; + input: { + function Foo(f) { + this.f = f; + } + new Foo(function() { + (function(x) { + (function(y) { + console.log(y); + })(x); + })(7); + }).f(); + } + expect: { + function Foo(f) { + this.f = f; + } + new Foo(function() { + (function(x) { + (function(y) { + console.log(y); + })(x); + })(7); + }).f(); + } +} + negate_iife_issue_1073: { options = { negate_iife: true, @@ -172,3 +287,36 @@ issue_1254_negate_iife_nested: { } expect_exact: '!function(){return function(){console.log("test")}}()()()()();' } + +issue_1288: { + options = { + negate_iife: true, + conditionals: true, + }; + input: { + if (w) ; + else { + (function f() {})(); + } + if (!x) { + (function() { + x = {}; + })(); + } + if (y) + (function() {})(); + else + (function(z) { + return z; + })(0); + } + expect: { + w || function f() {}(); + x || function() { + x = {}; + }(); + y ? function() {}() : function(z) { + return z; + }(0); + } +} diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 8a3ffe89..d93f5237 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -213,3 +213,41 @@ limit_2: { i, j, k; } } + +negate_iife_for: { + options = { + sequences: true, + negate_iife: true, + }; + input: { + (function() {})(); + for (i = 0; i < 5; i++) console.log(i); + + (function() {})(); + for (; i < 5; i++) console.log(i); + } + expect: { + for (!function() {}(), i = 0; i < 5; i++) console.log(i); + for (function() {}(); i < 5; i++) console.log(i); + } +} + +iife: { + options = { + 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() {}(), function g() {}() + } +}