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
This commit is contained in:
alexlamsl
2017-02-18 19:11:57 +08:00
parent 6b3c49e458
commit f0ff6189be
5 changed files with 277 additions and 99 deletions

View File

@@ -278,6 +278,15 @@ merge(Compressor.prototype, {
return x; 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) { function tighten_body(statements, compressor) {
var CHANGED, max_iter = 10; var CHANGED, max_iter = 10;
do { do {
@@ -303,10 +312,6 @@ merge(Compressor.prototype, {
} }
} while (CHANGED && max_iter-- > 0); } while (CHANGED && max_iter-- > 0);
if (compressor.option("negate_iife")) {
negate_iifes(statements, compressor);
}
return statements; return statements;
function collapse_single_use_vars(statements, compressor) { function collapse_single_use_vars(statements, compressor) {
@@ -753,7 +758,7 @@ merge(Compressor.prototype, {
if (seqLength(seq) >= compressor.sequences_limit) { if (seqLength(seq) >= compressor.sequences_limit) {
push_seq(); push_seq();
} }
seq.push(stat.body); seq.push(seq.length > 0 ? statement_to_expression(stat) : stat.body);
} else { } else {
push_seq(); push_seq();
ret.push(stat); ret.push(stat);
@@ -802,7 +807,7 @@ merge(Compressor.prototype, {
stat.init = cons_seq(stat.init); stat.init = cons_seq(stat.init);
} }
else if (!stat.init) { else if (!stat.init) {
stat.init = prev.body; stat.init = statement_to_expression(prev);
ret.pop(); ret.pop();
} }
} catch(ex) { } 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) { function extract_functions_from_statement_array(statements) {
@@ -1007,7 +968,15 @@ merge(Compressor.prototype, {
return ast1.print_to_string().length > return ast1.print_to_string().length >
ast2.print_to_string().length ast2.print_to_string().length
? ast2 : ast1; ? 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 // methods to evaluate a constant expression
(function (def){ (function (def){
@@ -1227,7 +1196,17 @@ merge(Compressor.prototype, {
operator: "!", operator: "!",
expression: exp 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(){ def(AST_Node, function(){
return basic_negation(this); return basic_negation(this);
}); });
@@ -1247,13 +1226,13 @@ merge(Compressor.prototype, {
self.cdr = self.cdr.negate(compressor); self.cdr = self.cdr.negate(compressor);
return self; return self;
}); });
def(AST_Conditional, function(compressor){ def(AST_Conditional, function(compressor, first_in_statement){
var self = this.clone(); var self = this.clone();
self.consequent = self.consequent.negate(compressor); self.consequent = self.consequent.negate(compressor);
self.alternative = self.alternative.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; var self = this.clone(), op = this.operator;
if (compressor.option("unsafe_comps")) { if (compressor.option("unsafe_comps")) {
switch (op) { switch (op) {
@@ -1270,20 +1249,20 @@ merge(Compressor.prototype, {
case "!==": self.operator = "==="; return self; case "!==": self.operator = "==="; return self;
case "&&": case "&&":
self.operator = "||"; self.operator = "||";
self.left = self.left.negate(compressor); self.left = self.left.negate(compressor, first_in_statement);
self.right = self.right.negate(compressor); self.right = self.right.negate(compressor);
return best_of(basic_negation(this), self); return best(this, self, first_in_statement);
case "||": case "||":
self.operator = "&&"; self.operator = "&&";
self.left = self.left.negate(compressor); self.left = self.left.negate(compressor, first_in_statement);
self.right = self.right.negate(compressor); self.right = self.right.negate(compressor);
return best_of(basic_negation(this), self); return best(this, self, first_in_statement);
} }
return basic_negation(this); return basic_negation(this);
}); });
})(function(node, func){ })(function(node, func){
node.DEFMETHOD("negate", function(compressor){ node.DEFMETHOD("negate", function(compressor, first_in_statement){
return func.call(this, compressor); return func.call(this, compressor, first_in_statement);
}); });
}); });
@@ -1954,8 +1933,8 @@ merge(Compressor.prototype, {
return make_node(AST_SimpleStatement, self, { return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Conditional, self, { body: make_node(AST_Conditional, self, {
condition : self.condition, condition : self.condition,
consequent : self.body.body, consequent : statement_to_expression(self.body),
alternative : self.alternative.body alternative : statement_to_expression(self.alternative)
}) })
}).transform(compressor); }).transform(compressor);
} }
@@ -1971,14 +1950,14 @@ merge(Compressor.prototype, {
body: make_node(AST_Binary, self, { body: make_node(AST_Binary, self, {
operator : "||", operator : "||",
left : negated, left : negated,
right : self.body.body right : statement_to_expression(self.body)
}) })
}).transform(compressor); }).transform(compressor);
return make_node(AST_SimpleStatement, self, { return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, { body: make_node(AST_Binary, self, {
operator : "&&", operator : "&&",
left : self.condition, left : self.condition,
right : self.body.body right : statement_to_expression(self.body)
}) })
}).transform(compressor); }).transform(compressor);
} }
@@ -1989,7 +1968,7 @@ merge(Compressor.prototype, {
body: make_node(AST_Binary, self, { body: make_node(AST_Binary, self, {
operator : "||", operator : "||",
left : self.condition, left : self.condition,
right : self.alternative.body right : statement_to_expression(self.alternative)
}) })
}).transform(compressor); }).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){ OPT(AST_New, function(self, compressor){
@@ -2459,6 +2450,10 @@ merge(Compressor.prototype, {
// !!foo ==> foo, if we're in boolean context // !!foo ==> foo, if we're in boolean context
return e.expression; 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; break;
case "typeof": case "typeof":
// typeof always returns a non-empty string, thus it's // typeof always returns a non-empty string, thus it's
@@ -2472,9 +2467,6 @@ merge(Compressor.prototype, {
} }
return make_node(AST_True, self); 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]; return self.evaluate(compressor)[0];
}); });
@@ -2651,11 +2643,12 @@ merge(Compressor.prototype, {
if (compressor.option("comparisons") && self.is_boolean()) { if (compressor.option("comparisons") && self.is_boolean()) {
if (!(compressor.parent() instanceof AST_Binary) if (!(compressor.parent() instanceof AST_Binary)
|| compressor.parent() instanceof AST_Assign) { || compressor.parent() instanceof AST_Assign) {
var statement = first_in_statement(compressor);
var negated = make_node(AST_UnaryPrefix, self, { var negated = make_node(AST_UnaryPrefix, self, {
operator: "!", 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")) { if (compressor.option("unsafe_comps")) {
switch (self.operator) { switch (self.operator) {
@@ -2859,8 +2852,9 @@ merge(Compressor.prototype, {
return maintain_this_binding(compressor.parent(), self, self.alternative); return maintain_this_binding(compressor.parent(), self, self.alternative);
} }
} }
var negated = cond[0].negate(compressor); var statement = first_in_statement(compressor);
if (best_of(cond[0], negated) === negated) { var negated = cond[0].negate(compressor, statement);
if ((statement ? best_of_statement : best_of)(cond[0], negated) === negated) {
self = make_node(AST_Conditional, self, { self = make_node(AST_Conditional, self, {
condition: negated, condition: negated,
consequent: self.alternative, consequent: self.alternative,

View File

@@ -425,7 +425,6 @@ function OutputStream(options) {
pos : function() { return current_pos }, pos : function() { return current_pos },
push_node : function(node) { stack.push(node) }, push_node : function(node) { stack.push(node) },
pop_node : function() { return stack.pop() }, pop_node : function() { return stack.pop() },
stack : function() { return stack },
parent : function(n) { parent : function(n) {
return stack[stack.length - 2 - (n || 0)]; 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. // self should be AST_New. decide if we want to show parens or not.
function need_constructor_parens(self, output) { function need_constructor_parens(self, output) {
// Always print parentheses with arguments // Always print parentheses with arguments

View File

@@ -320,3 +320,26 @@ Dictionary.fromObject = function(obj) {
function HOP(obj, prop) { function HOP(obj, prop) {
return Object.prototype.hasOwnProperty.call(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;
}
}
}

View File

@@ -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: { negate_iife_2: {
options = { options = {
negate_iife: true negate_iife: true
@@ -25,6 +35,20 @@ negate_iife_2: {
negate_iife_3: { negate_iife_3: {
options = { options = {
negate_iife: true, 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: { input: {
(function(){ return true })() ? console.log(true) : console.log(false); (function(){ return true })() ? console.log(true) : console.log(false);
@@ -37,6 +61,7 @@ negate_iife_3: {
negate_iife_3: { negate_iife_3: {
options = { options = {
negate_iife: true, negate_iife: true,
conditionals: true,
sequences: true sequences: true
}; };
input: { 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: { negate_iife_4: {
options = { options = {
negate_iife: true, 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: { negate_iife_nested: {
options = { options = {
negate_iife: true, 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: { negate_iife_issue_1073: {
options = { options = {
negate_iife: true, negate_iife: true,
@@ -172,3 +287,36 @@ issue_1254_negate_iife_nested: {
} }
expect_exact: '!function(){return function(){console.log("test")}}()()()()();' 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);
}
}

View File

@@ -213,3 +213,41 @@ limit_2: {
i, j, k; 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() {}()
}
}