always keep declarations found in unreachable code
a few more tests and some cleanups.
This commit is contained in:
145
lib/compress.js
145
lib/compress.js
@@ -63,7 +63,7 @@ function Compressor(options, false_by_default) {
|
|||||||
comparations : !false_by_default,
|
comparations : !false_by_default,
|
||||||
evaluate : !false_by_default,
|
evaluate : !false_by_default,
|
||||||
booleans : !false_by_default,
|
booleans : !false_by_default,
|
||||||
dwloops : !false_by_default,
|
loops : !false_by_default,
|
||||||
hoist_funs : !false_by_default,
|
hoist_funs : !false_by_default,
|
||||||
hoist_vars : !false_by_default,
|
hoist_vars : !false_by_default,
|
||||||
|
|
||||||
@@ -129,24 +129,20 @@ function Compressor(options, false_by_default) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function do_list(array, compressor) {
|
function do_list(array, compressor, splice_blocks) {
|
||||||
return MAP(array, function(node){
|
return MAP(array, function(node){
|
||||||
return node.squeeze(compressor);
|
node = node.squeeze(compressor);
|
||||||
|
if (splice_blocks) {
|
||||||
|
if (node instanceof AST_BlockStatement) {
|
||||||
|
return MAP.splice(eliminate_spurious_blocks(node.body));
|
||||||
|
}
|
||||||
|
if (node instanceof AST_EmptyStatement)
|
||||||
|
return MAP.skip;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function tighten_body(statements, compressor) {
|
|
||||||
statements = do_list(statements, compressor);
|
|
||||||
statements = eliminate_spurious_blocks(statements);
|
|
||||||
if (compressor.option("dead_code")) {
|
|
||||||
statements = eliminate_dead_code(statements, compressor);
|
|
||||||
}
|
|
||||||
if (compressor.option("sequences")) {
|
|
||||||
statements = sequencesize(statements, compressor);
|
|
||||||
}
|
|
||||||
return statements;
|
|
||||||
};
|
|
||||||
|
|
||||||
function eliminate_spurious_blocks(statements) {
|
function eliminate_spurious_blocks(statements) {
|
||||||
return statements.reduce(function(a, stat){
|
return statements.reduce(function(a, stat){
|
||||||
if (stat instanceof AST_BlockStatement) {
|
if (stat instanceof AST_BlockStatement) {
|
||||||
@@ -158,24 +154,28 @@ function Compressor(options, false_by_default) {
|
|||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
function eliminate_dead_code(statements, compressor) {
|
function tighten_body(statements, compressor) {
|
||||||
var has_quit = false;
|
statements = do_list(statements, compressor, true);
|
||||||
return statements.reduce(function(a, stat){
|
if (compressor.option("dead_code")) {
|
||||||
if (has_quit) {
|
statements = eliminate_dead_code(statements, compressor);
|
||||||
if (stat instanceof AST_Defun) {
|
|
||||||
a.push(stat);
|
|
||||||
}
|
}
|
||||||
else {
|
if (compressor.option("sequences")) {
|
||||||
|
statements = sequencesize(statements, compressor);
|
||||||
|
}
|
||||||
|
return statements;
|
||||||
|
};
|
||||||
|
|
||||||
|
function extract_declarations_from_unreachable_code(compressor, stat, target) {
|
||||||
stat.walk(new TreeWalker(function(node){
|
stat.walk(new TreeWalker(function(node){
|
||||||
if (node instanceof AST_Definitions || node instanceof AST_Defun) {
|
if (node instanceof AST_Definitions || node instanceof AST_Defun) {
|
||||||
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
|
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
|
||||||
if (node instanceof AST_Definitions) {
|
if (node instanceof AST_Definitions) {
|
||||||
node = node.clone();
|
node = node.clone();
|
||||||
node.remove_initializers();
|
node.remove_initializers();
|
||||||
a.push(node);
|
target.push(node);
|
||||||
}
|
}
|
||||||
else if (node instanceof AST_Defun) {
|
else if (node instanceof AST_Defun) {
|
||||||
a.push(node);
|
target.push(node);
|
||||||
}
|
}
|
||||||
return true; // no point to descend
|
return true; // no point to descend
|
||||||
}
|
}
|
||||||
@@ -185,6 +185,17 @@ function Compressor(options, false_by_default) {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function eliminate_dead_code(statements, compressor) {
|
||||||
|
var has_quit = false;
|
||||||
|
return statements.reduce(function(a, stat){
|
||||||
|
if (has_quit) {
|
||||||
|
if (stat instanceof AST_Defun) {
|
||||||
|
a.push(stat);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
extract_declarations_from_unreachable_code(compressor, stat, a);
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
a.push(stat);
|
a.push(stat);
|
||||||
if (stat instanceof AST_Jump) {
|
if (stat instanceof AST_Jump) {
|
||||||
@@ -552,18 +563,27 @@ function Compressor(options, false_by_default) {
|
|||||||
return self.optimize(compressor);
|
return self.optimize(compressor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function warn_dead_code(node) {
|
||||||
|
AST_Node.warn("Dropping unreachable code [{line},{col}]", node.start);
|
||||||
|
};
|
||||||
|
|
||||||
AST_DWLoop.DEFMETHOD("optimize", function(compressor){
|
AST_DWLoop.DEFMETHOD("optimize", function(compressor){
|
||||||
var self = this;
|
var self = this;
|
||||||
if (!compressor.option("dwloops")) return self;
|
|
||||||
var cond = self.condition.evaluate(compressor);
|
var cond = self.condition.evaluate(compressor);
|
||||||
|
self.condition = cond[0];
|
||||||
|
if (!compressor.option("loops")) return self;
|
||||||
if (cond.length == 2) {
|
if (cond.length == 2) {
|
||||||
if (cond[1]) {
|
if (cond[1]) {
|
||||||
return make_node(AST_For, self, {
|
return make_node(AST_For, self, {
|
||||||
body: self.body
|
body: self.body
|
||||||
});
|
});
|
||||||
} else if (self instanceof AST_While) {
|
} else if (self instanceof AST_While) {
|
||||||
AST_Node.warn("Unreachable code [{line},{col}]", self.start);
|
if (compressor.option("dead_code")) {
|
||||||
return make_node(AST_EmptyStatement, self);
|
warn_dead_code(self);
|
||||||
|
var a = [];
|
||||||
|
extract_declarations_from_unreachable_code(compressor, self.body, a);
|
||||||
|
return make_node(AST_BlockStatement, self, { body: a });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
@@ -575,7 +595,34 @@ function Compressor(options, false_by_default) {
|
|||||||
if (self.condition) self.condition = self.condition.squeeze(compressor);
|
if (self.condition) self.condition = self.condition.squeeze(compressor);
|
||||||
if (self.step) self.step = self.step.squeeze(compressor);
|
if (self.step) self.step = self.step.squeeze(compressor);
|
||||||
self.body = self.body.squeeze(compressor);
|
self.body = self.body.squeeze(compressor);
|
||||||
return self;
|
return self.optimize(compressor);
|
||||||
|
});
|
||||||
|
|
||||||
|
AST_For.DEFMETHOD("optimize", function(compressor){
|
||||||
|
var cond = this.condition;
|
||||||
|
if (cond) {
|
||||||
|
cond = cond.evaluate(compressor);
|
||||||
|
this.condition = cond[0];
|
||||||
|
}
|
||||||
|
if (!compressor.option("loops")) return this;
|
||||||
|
if (this.condition) {
|
||||||
|
var cond = this.condition.evaluate(compressor);
|
||||||
|
if (cond.length == 2 && !cond[1]) {
|
||||||
|
if (compressor.option("dead_code")) {
|
||||||
|
warn_dead_code(this.body);
|
||||||
|
var a = [];
|
||||||
|
if (this.init instanceof AST_Statement) a.push(this.init);
|
||||||
|
else if (this.init) a.push(make_node(AST_SimpleStatement, this.init, {
|
||||||
|
body: this.init
|
||||||
|
}));
|
||||||
|
extract_declarations_from_unreachable_code(compressor, this.body, a);
|
||||||
|
return make_node(AST_BlockStatement, this, {
|
||||||
|
body: a
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
});
|
});
|
||||||
|
|
||||||
SQUEEZE(AST_ForIn, function(self, compressor){
|
SQUEEZE(AST_ForIn, function(self, compressor){
|
||||||
@@ -625,10 +672,24 @@ function Compressor(options, false_by_default) {
|
|||||||
if (cond.length == 2) {
|
if (cond.length == 2) {
|
||||||
if (cond[1]) {
|
if (cond[1]) {
|
||||||
AST_Node.warn("Condition always true [{line},{col}]", self.condition.start);
|
AST_Node.warn("Condition always true [{line},{col}]", self.condition.start);
|
||||||
return self.body;
|
if (compressor.option("dead_code")) {
|
||||||
|
var a = [];
|
||||||
|
if (self.alternative) {
|
||||||
|
warn_dead_code(self.alternative);
|
||||||
|
extract_declarations_from_unreachable_code(compressor, self.alternative, a);
|
||||||
|
}
|
||||||
|
a.push(self.body);
|
||||||
|
return make_node(AST_BlockStatement, self, { body: a });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
AST_Node.warn("Condition always false [{line},{col}]", self.condition.start);
|
AST_Node.warn("Condition always false [{line},{col}]", self.condition.start);
|
||||||
return self.alternative || make_node(AST_EmptyStatement, self);
|
if (compressor.option("dead_code")) {
|
||||||
|
warn_dead_code(self.body);
|
||||||
|
var a = [];
|
||||||
|
extract_declarations_from_unreachable_code(compressor, self.body, a);
|
||||||
|
if (self.alternative) a.push(self.alternative);
|
||||||
|
return make_node(AST_BlockStatement, self, { body: a });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (self.condition instanceof AST_UnaryPrefix
|
if (self.condition instanceof AST_UnaryPrefix
|
||||||
@@ -638,6 +699,12 @@ function Compressor(options, false_by_default) {
|
|||||||
self.body = self.alternative || make_node(AST_EmptyStatement, self);
|
self.body = self.alternative || make_node(AST_EmptyStatement, self);
|
||||||
self.alternative = tmp;
|
self.alternative = tmp;
|
||||||
}
|
}
|
||||||
|
if (self.body instanceof AST_EmptyStatement
|
||||||
|
&& self.alternative instanceof AST_EmptyStatement) {
|
||||||
|
return make_node(AST_SimpleStatement, self.condition, {
|
||||||
|
body: self.condition
|
||||||
|
});
|
||||||
|
}
|
||||||
if (self.body instanceof AST_SimpleStatement
|
if (self.body instanceof AST_SimpleStatement
|
||||||
&& self.alternative instanceof AST_SimpleStatement) {
|
&& self.alternative instanceof AST_SimpleStatement) {
|
||||||
return make_node(AST_SimpleStatement, self, {
|
return make_node(AST_SimpleStatement, self, {
|
||||||
@@ -814,11 +881,15 @@ function Compressor(options, false_by_default) {
|
|||||||
|
|
||||||
SQUEEZE(AST_UnaryPrefix, function(self, compressor){
|
SQUEEZE(AST_UnaryPrefix, function(self, compressor){
|
||||||
// need to determine the context before cloning the node
|
// need to determine the context before cloning the node
|
||||||
var bool = compressor.in_boolean_context();
|
|
||||||
self = self.clone();
|
self = self.clone();
|
||||||
var e = self.expression = self.expression.squeeze(compressor);
|
self.expression = self.expression.squeeze(compressor);
|
||||||
if (compressor.option("booleans") && bool) {
|
return self.optimize(compressor);
|
||||||
switch (self.operator) {
|
});
|
||||||
|
|
||||||
|
AST_UnaryPrefix.DEFMETHOD("optimize", function(compressor){
|
||||||
|
if (compressor.option("booleans") && compressor.in_boolean_context()) {
|
||||||
|
var e = this.expression;
|
||||||
|
switch (this.operator) {
|
||||||
case "!":
|
case "!":
|
||||||
if (e instanceof AST_UnaryPrefix && e.operator == "!") {
|
if (e instanceof AST_UnaryPrefix && e.operator == "!") {
|
||||||
// !!foo ==> foo, if we're in boolean context
|
// !!foo ==> foo, if we're in boolean context
|
||||||
@@ -828,11 +899,11 @@ function Compressor(options, false_by_default) {
|
|||||||
case "typeof":
|
case "typeof":
|
||||||
// typeof always returns a non-empty string, thus it's
|
// typeof always returns a non-empty string, thus it's
|
||||||
// always true in booleans
|
// always true in booleans
|
||||||
AST_Node.warn("Boolean expression always true [{line},{col}]", self.start);
|
AST_Node.warn("Boolean expression always true [{line},{col}]", this.start);
|
||||||
return make_node(AST_True, self).optimize(compressor);
|
return make_node(AST_True, this).optimize(compressor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.evaluate(compressor)[0];
|
return this.evaluate(compressor)[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
SQUEEZE(AST_Binary, function(self, compressor){
|
SQUEEZE(AST_Binary, function(self, compressor){
|
||||||
|
|||||||
74
test/compress/conditionals.js
Normal file
74
test/compress/conditionals.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
ifs_1: {
|
||||||
|
options = {
|
||||||
|
conditionals: true
|
||||||
|
};
|
||||||
|
input: {
|
||||||
|
if (foo) bar();
|
||||||
|
if (!foo); else bar();
|
||||||
|
if (foo); else bar();
|
||||||
|
if (foo); else;
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
foo&&bar();
|
||||||
|
foo&&bar();
|
||||||
|
foo||bar();
|
||||||
|
foo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ifs_2: {
|
||||||
|
options = {
|
||||||
|
conditionals: true
|
||||||
|
};
|
||||||
|
input: {
|
||||||
|
if (foo) {
|
||||||
|
x();
|
||||||
|
} else if (bar) {
|
||||||
|
y();
|
||||||
|
} else if (baz) {
|
||||||
|
z();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foo) {
|
||||||
|
x();
|
||||||
|
} else if (bar) {
|
||||||
|
y();
|
||||||
|
} else if (baz) {
|
||||||
|
z();
|
||||||
|
} else {
|
||||||
|
t();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
foo ? x() : bar ? y() : baz && z();
|
||||||
|
foo ? x() : bar ? y() : baz ? z() : t();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ifs_3_should_warn: {
|
||||||
|
options = {
|
||||||
|
conditionals : true,
|
||||||
|
dead_code : true,
|
||||||
|
evaluate : true,
|
||||||
|
booleans : true
|
||||||
|
};
|
||||||
|
input: {
|
||||||
|
if (x && !(x + "1") && y) { // 1
|
||||||
|
var qq;
|
||||||
|
foo();
|
||||||
|
} else {
|
||||||
|
bar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x || !!(x + "1") || y) { // 2
|
||||||
|
foo();
|
||||||
|
} else {
|
||||||
|
var jj;
|
||||||
|
bar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var qq; bar(); // 1
|
||||||
|
var jj; foo(); // 2
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,3 +57,33 @@ dead_code_2_should_warn: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dead_code_constant_boolean_should_warn_more: {
|
||||||
|
options = {
|
||||||
|
dead_code : true,
|
||||||
|
loops : true,
|
||||||
|
booleans : true,
|
||||||
|
conditionals : true,
|
||||||
|
evaluate : true
|
||||||
|
};
|
||||||
|
input: {
|
||||||
|
while (!((foo && bar) || (x + "0"))) {
|
||||||
|
console.log("unreachable");
|
||||||
|
var foo;
|
||||||
|
function bar() {}
|
||||||
|
}
|
||||||
|
for (var x = 10; x && (y || x) && (!typeof x); ++x) {
|
||||||
|
asdf();
|
||||||
|
foo();
|
||||||
|
var moo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var foo;
|
||||||
|
function bar() {}
|
||||||
|
// nothing for the while
|
||||||
|
// as for the for, it should keep:
|
||||||
|
var x = 10;
|
||||||
|
var moo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -119,8 +119,10 @@ function parse_test(file) {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
var stat = node.body;
|
var stat = node.body;
|
||||||
if (stat instanceof U.AST_BlockStatement && stat.body.length == 1)
|
if (stat instanceof U.AST_BlockStatement) {
|
||||||
stat = stat.body[0];
|
if (stat.body.length == 1) stat = stat.body[0];
|
||||||
|
else if (stat.body.length == 0) stat = new U.AST_EmptyStatement();
|
||||||
|
}
|
||||||
test[node.label.name] = stat;
|
test[node.label.name] = stat;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ load_global("../lib/compress.js");
|
|||||||
load_global("../lib/sourcemap.js");
|
load_global("../lib/sourcemap.js");
|
||||||
|
|
||||||
UglifyJS.AST_Node.warn_function = function(txt) {
|
UglifyJS.AST_Node.warn_function = function(txt) {
|
||||||
sys.debug(txt);
|
sys.error("WARN: " + txt);
|
||||||
};
|
};
|
||||||
|
|
||||||
// XXX: perhaps we shouldn't export everything but heck, I'm lazy.
|
// XXX: perhaps we shouldn't export everything but heck, I'm lazy.
|
||||||
|
|||||||
Reference in New Issue
Block a user