Skip to content

Commit 960adb8

Browse files
committed
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 mishoo#1288 closes mishoo#1451
1 parent dbea93e commit 960adb8

File tree

5 files changed

+277
-99
lines changed

5 files changed

+277
-99
lines changed

Diff for: lib/compress.js

+68-74
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,15 @@ merge(Compressor.prototype, {
278278
return x;
279279
};
280280

281+
var readOnlyPrefix = makePredicate("! ~ + - void typeof");
282+
function statement_to_expression(stat) {
283+
if (stat.body instanceof AST_UnaryPrefix && readOnlyPrefix(stat.body.operator)) {
284+
return stat.body.expression;
285+
} else {
286+
return stat.body;
287+
}
288+
}
289+
281290
function tighten_body(statements, compressor) {
282291
var CHANGED, max_iter = 10;
283292
do {
@@ -303,10 +312,6 @@ merge(Compressor.prototype, {
303312
}
304313
} while (CHANGED && max_iter-- > 0);
305314

306-
if (compressor.option("negate_iife")) {
307-
negate_iifes(statements, compressor);
308-
}
309-
310315
return statements;
311316

312317
function collapse_single_use_vars(statements, compressor) {
@@ -753,7 +758,7 @@ merge(Compressor.prototype, {
753758
if (seqLength(seq) >= compressor.sequences_limit) {
754759
push_seq();
755760
}
756-
seq.push(stat.body);
761+
seq.push(seq.length > 0 ? statement_to_expression(stat) : stat.body);
757762
} else {
758763
push_seq();
759764
ret.push(stat);
@@ -802,7 +807,7 @@ merge(Compressor.prototype, {
802807
stat.init = cons_seq(stat.init);
803808
}
804809
else if (!stat.init) {
805-
stat.init = prev.body;
810+
stat.init = statement_to_expression(prev);
806811
ret.pop();
807812
}
808813
} catch(ex) {
@@ -859,50 +864,6 @@ merge(Compressor.prototype, {
859864
}, []);
860865
};
861866

862-
function negate_iifes(statements, compressor) {
863-
function is_iife_call(node) {
864-
if (node instanceof AST_Call) {
865-
return node.expression instanceof AST_Function || is_iife_call(node.expression);
866-
}
867-
return false;
868-
}
869-
870-
statements.forEach(function(stat){
871-
if (stat instanceof AST_SimpleStatement) {
872-
stat.body = (function transform(thing) {
873-
return thing.transform(new TreeTransformer(function(node){
874-
if (node instanceof AST_New) {
875-
return node;
876-
}
877-
if (is_iife_call(node)) {
878-
return make_node(AST_UnaryPrefix, node, {
879-
operator: "!",
880-
expression: node
881-
});
882-
}
883-
else if (node instanceof AST_Call) {
884-
node.expression = transform(node.expression);
885-
}
886-
else if (node instanceof AST_Seq) {
887-
node.car = transform(node.car);
888-
}
889-
else if (node instanceof AST_Conditional) {
890-
var expr = transform(node.condition);
891-
if (expr !== node.condition) {
892-
// it has been negated, reverse
893-
node.condition = expr;
894-
var tmp = node.consequent;
895-
node.consequent = node.alternative;
896-
node.alternative = tmp;
897-
}
898-
}
899-
return node;
900-
}));
901-
})(stat.body);
902-
}
903-
});
904-
};
905-
906867
};
907868

908869
function extract_functions_from_statement_array(statements) {
@@ -1007,7 +968,15 @@ merge(Compressor.prototype, {
1007968
return ast1.print_to_string().length >
1008969
ast2.print_to_string().length
1009970
? ast2 : ast1;
1010-
};
971+
}
972+
973+
function best_of_statement(ast1, ast2) {
974+
return best_of(make_node(AST_SimpleStatement, ast1, {
975+
body: ast1
976+
}), make_node(AST_SimpleStatement, ast2, {
977+
body: ast2
978+
})).body;
979+
}
1011980

1012981
// methods to evaluate a constant expression
1013982
(function (def){
@@ -1227,7 +1196,17 @@ merge(Compressor.prototype, {
12271196
operator: "!",
12281197
expression: exp
12291198
});
1230-
};
1199+
}
1200+
function best(orig, alt, first_in_statement) {
1201+
var negated = basic_negation(orig);
1202+
if (first_in_statement) {
1203+
var stat = make_node(AST_SimpleStatement, alt, {
1204+
body: alt
1205+
});
1206+
return best_of(negated, stat) === stat ? alt : negated;
1207+
}
1208+
return best_of(negated, alt);
1209+
}
12311210
def(AST_Node, function(){
12321211
return basic_negation(this);
12331212
});
@@ -1247,13 +1226,13 @@ merge(Compressor.prototype, {
12471226
self.cdr = self.cdr.negate(compressor);
12481227
return self;
12491228
});
1250-
def(AST_Conditional, function(compressor){
1229+
def(AST_Conditional, function(compressor, first_in_statement){
12511230
var self = this.clone();
12521231
self.consequent = self.consequent.negate(compressor);
12531232
self.alternative = self.alternative.negate(compressor);
1254-
return best_of(basic_negation(this), self);
1233+
return best(this, self, first_in_statement);
12551234
});
1256-
def(AST_Binary, function(compressor){
1235+
def(AST_Binary, function(compressor, first_in_statement){
12571236
var self = this.clone(), op = this.operator;
12581237
if (compressor.option("unsafe_comps")) {
12591238
switch (op) {
@@ -1270,20 +1249,20 @@ merge(Compressor.prototype, {
12701249
case "!==": self.operator = "==="; return self;
12711250
case "&&":
12721251
self.operator = "||";
1273-
self.left = self.left.negate(compressor);
1252+
self.left = self.left.negate(compressor, first_in_statement);
12741253
self.right = self.right.negate(compressor);
1275-
return best_of(basic_negation(this), self);
1254+
return best(this, self, first_in_statement);
12761255
case "||":
12771256
self.operator = "&&";
1278-
self.left = self.left.negate(compressor);
1257+
self.left = self.left.negate(compressor, first_in_statement);
12791258
self.right = self.right.negate(compressor);
1280-
return best_of(basic_negation(this), self);
1259+
return best(this, self, first_in_statement);
12811260
}
12821261
return basic_negation(this);
12831262
});
12841263
})(function(node, func){
1285-
node.DEFMETHOD("negate", function(compressor){
1286-
return func.call(this, compressor);
1264+
node.DEFMETHOD("negate", function(compressor, first_in_statement){
1265+
return func.call(this, compressor, first_in_statement);
12871266
});
12881267
});
12891268

@@ -1968,8 +1947,8 @@ merge(Compressor.prototype, {
19681947
return make_node(AST_SimpleStatement, self, {
19691948
body: make_node(AST_Conditional, self, {
19701949
condition : self.condition,
1971-
consequent : self.body.body,
1972-
alternative : self.alternative.body
1950+
consequent : statement_to_expression(self.body),
1951+
alternative : statement_to_expression(self.alternative)
19731952
})
19741953
}).transform(compressor);
19751954
}
@@ -1985,14 +1964,14 @@ merge(Compressor.prototype, {
19851964
body: make_node(AST_Binary, self, {
19861965
operator : "||",
19871966
left : negated,
1988-
right : self.body.body
1967+
right : statement_to_expression(self.body)
19891968
})
19901969
}).transform(compressor);
19911970
return make_node(AST_SimpleStatement, self, {
19921971
body: make_node(AST_Binary, self, {
19931972
operator : "&&",
19941973
left : self.condition,
1995-
right : self.body.body
1974+
right : statement_to_expression(self.body)
19961975
})
19971976
}).transform(compressor);
19981977
}
@@ -2003,7 +1982,7 @@ merge(Compressor.prototype, {
20031982
body: make_node(AST_Binary, self, {
20041983
operator : "||",
20051984
left : self.condition,
2006-
right : self.alternative.body
1985+
right : statement_to_expression(self.alternative)
20071986
})
20081987
}).transform(compressor);
20091988
}
@@ -2386,7 +2365,19 @@ merge(Compressor.prototype, {
23862365
}
23872366
}
23882367
}
2389-
return self.evaluate(compressor)[0];
2368+
if (compressor.option("negate_iife")
2369+
&& compressor.parent() instanceof AST_SimpleStatement
2370+
&& is_iife_call(self)) {
2371+
return self.negate(compressor, true);
2372+
}
2373+
return self;
2374+
2375+
function is_iife_call(node) {
2376+
if (node instanceof AST_Call && !(node instanceof AST_New)) {
2377+
return node.expression instanceof AST_Function || is_iife_call(node.expression);
2378+
}
2379+
return false;
2380+
}
23902381
});
23912382

23922383
OPT(AST_New, function(self, compressor){
@@ -2473,6 +2464,10 @@ merge(Compressor.prototype, {
24732464
// !!foo ==> foo, if we're in boolean context
24742465
return e.expression;
24752466
}
2467+
if (e instanceof AST_Binary) {
2468+
var statement = first_in_statement(compressor);
2469+
self = (statement ? best_of_statement : best_of)(self, e.negate(compressor, statement));
2470+
}
24762471
break;
24772472
case "typeof":
24782473
// typeof always returns a non-empty string, thus it's
@@ -2486,9 +2481,6 @@ merge(Compressor.prototype, {
24862481
}
24872482
return make_node(AST_True, self);
24882483
}
2489-
if (e instanceof AST_Binary && self.operator == "!") {
2490-
self = best_of(self, e.negate(compressor));
2491-
}
24922484
}
24932485
return self.evaluate(compressor)[0];
24942486
});
@@ -2665,11 +2657,12 @@ merge(Compressor.prototype, {
26652657
if (compressor.option("comparisons") && self.is_boolean()) {
26662658
if (!(compressor.parent() instanceof AST_Binary)
26672659
|| compressor.parent() instanceof AST_Assign) {
2660+
var statement = first_in_statement(compressor);
26682661
var negated = make_node(AST_UnaryPrefix, self, {
26692662
operator: "!",
2670-
expression: self.negate(compressor)
2663+
expression: self.negate(compressor, statement)
26712664
});
2672-
self = best_of(self, negated);
2665+
self = (statement ? best_of_statement : best_of)(self, negated);
26732666
}
26742667
if (compressor.option("unsafe_comps")) {
26752668
switch (self.operator) {
@@ -2873,8 +2866,9 @@ merge(Compressor.prototype, {
28732866
return maintain_this_binding(compressor.parent(), self, self.alternative);
28742867
}
28752868
}
2876-
var negated = cond[0].negate(compressor);
2877-
if (best_of(cond[0], negated) === negated) {
2869+
var statement = first_in_statement(compressor);
2870+
var negated = cond[0].negate(compressor, statement);
2871+
if ((statement ? best_of_statement : best_of)(cond[0], negated) === negated) {
28782872
self = make_node(AST_Conditional, self, {
28792873
condition: negated,
28802874
consequent: self.alternative,

Diff for: lib/output.js

-25
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,6 @@ function OutputStream(options) {
425425
pos : function() { return current_pos },
426426
push_node : function(node) { stack.push(node) },
427427
pop_node : function() { return stack.pop() },
428-
stack : function() { return stack },
429428
parent : function(n) {
430429
return stack[stack.length - 2 - (n || 0)];
431430
}
@@ -1334,30 +1333,6 @@ function OutputStream(options) {
13341333
}
13351334
};
13361335

1337-
// return true if the node at the top of the stack (that means the
1338-
// innermost node in the current output) is lexically the first in
1339-
// a statement.
1340-
function first_in_statement(output) {
1341-
var a = output.stack(), i = a.length, node = a[--i], p = a[--i];
1342-
while (i > 0) {
1343-
if (p instanceof AST_Statement && p.body === node)
1344-
return true;
1345-
if ((p instanceof AST_Seq && p.car === node ) ||
1346-
(p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) ||
1347-
(p instanceof AST_Dot && p.expression === node ) ||
1348-
(p instanceof AST_Sub && p.expression === node ) ||
1349-
(p instanceof AST_Conditional && p.condition === node ) ||
1350-
(p instanceof AST_Binary && p.left === node ) ||
1351-
(p instanceof AST_UnaryPostfix && p.expression === node ))
1352-
{
1353-
node = p;
1354-
p = a[--i];
1355-
} else {
1356-
return false;
1357-
}
1358-
}
1359-
};
1360-
13611336
// self should be AST_New. decide if we want to show parens or not.
13621337
function need_constructor_parens(self, output) {
13631338
// Always print parentheses with arguments

Diff for: lib/utils.js

+23
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,26 @@ Dictionary.fromObject = function(obj) {
320320
function HOP(obj, prop) {
321321
return Object.prototype.hasOwnProperty.call(obj, prop);
322322
}
323+
324+
// return true if the node at the top of the stack (that means the
325+
// innermost node in the current output) is lexically the first in
326+
// a statement.
327+
function first_in_statement(stack) {
328+
var node = stack.parent(-1);
329+
for (var i = 0, p; p = stack.parent(i); i++) {
330+
if (p instanceof AST_Statement && p.body === node)
331+
return true;
332+
if ((p instanceof AST_Seq && p.car === node ) ||
333+
(p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) ||
334+
(p instanceof AST_Dot && p.expression === node ) ||
335+
(p instanceof AST_Sub && p.expression === node ) ||
336+
(p instanceof AST_Conditional && p.condition === node ) ||
337+
(p instanceof AST_Binary && p.left === node ) ||
338+
(p instanceof AST_UnaryPostfix && p.expression === node ))
339+
{
340+
node = p;
341+
} else {
342+
return false;
343+
}
344+
}
345+
}

0 commit comments

Comments
 (0)