enhance booleans (#5365)
This commit is contained in:
53
lib/ast.js
53
lib/ast.js
@@ -2085,33 +2085,40 @@ TreeWalker.prototype = {
|
||||
}
|
||||
},
|
||||
in_boolean_context: function() {
|
||||
var self = this.self();
|
||||
for (var i = 0, p; p = this.parent(i); i++) {
|
||||
if (p instanceof AST_Conditional && p.condition === self
|
||||
|| p instanceof AST_DWLoop && p.condition === self
|
||||
|| p instanceof AST_For && p.condition === self
|
||||
|| p instanceof AST_If && p.condition === self
|
||||
|| p instanceof AST_Return && p.in_bool
|
||||
|| p instanceof AST_Sequence && p.tail_node() !== self
|
||||
|| p instanceof AST_SimpleStatement
|
||||
|| p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
|
||||
return true;
|
||||
}
|
||||
if (p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||")
|
||||
|| p instanceof AST_Conditional
|
||||
|| p.tail_node() === self) {
|
||||
self = p;
|
||||
} else if (p instanceof AST_Return) {
|
||||
for (var call, fn = p; call = this.parent(++i); fn = call) {
|
||||
if (call.TYPE == "Call") {
|
||||
if (!(fn instanceof AST_Lambda) || fn.name) return false;
|
||||
} else if (fn instanceof AST_Lambda) {
|
||||
for (var drop = true, level = 0, parent, self = this.self(); parent = this.parent(level++); self = parent) {
|
||||
if (parent instanceof AST_Binary) switch (parent.operator) {
|
||||
case "&&":
|
||||
case "||":
|
||||
if (parent.left === self) drop = false;
|
||||
continue;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (parent instanceof AST_Conditional) {
|
||||
if (parent.condition === self) return true;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (parent instanceof AST_DWLoop) return parent.condition === self;
|
||||
if (parent instanceof AST_For) return parent.condition === self;
|
||||
if (parent instanceof AST_If) return parent.condition === self;
|
||||
if (parent instanceof AST_Return) {
|
||||
if (parent.in_bool) return true;
|
||||
while (parent = this.parent(level++)) {
|
||||
if (parent instanceof AST_Lambda) {
|
||||
if (parent.name) return false;
|
||||
parent = this.parent(level++);
|
||||
if (parent.TYPE != "Call") return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent instanceof AST_Sequence) {
|
||||
if (parent.tail_node() === self) continue;
|
||||
return drop ? "d" : true;
|
||||
}
|
||||
if (parent instanceof AST_SimpleStatement) return drop ? "d" : true;
|
||||
if (parent instanceof AST_UnaryPrefix) return parent.operator == "!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
327
lib/compress.js
327
lib/compress.js
@@ -189,7 +189,7 @@ Compressor.prototype = new TreeTransformer(function(node, descend, in_list) {
|
||||
if (is_scope) {
|
||||
node.hoist_properties(this);
|
||||
node.hoist_declarations(this);
|
||||
node.process_boolean_returns(this);
|
||||
node.process_returns(this);
|
||||
}
|
||||
// Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize()
|
||||
// would call AST_Node.transform() if a different instance of AST_Node is
|
||||
@@ -475,7 +475,8 @@ Compressor.prototype.compress = function(node) {
|
||||
|
||||
function reset_def(tw, compressor, def) {
|
||||
def.assignments = 0;
|
||||
def.bool_fn = 0;
|
||||
def.bool_return = 0;
|
||||
def.drop_return = 0;
|
||||
def.cross_loop = false;
|
||||
def.direct_access = false;
|
||||
def.escaped = [];
|
||||
@@ -1057,12 +1058,11 @@ Compressor.prototype.compress = function(node) {
|
||||
if (iife) delete exp.reduce_vars;
|
||||
return true;
|
||||
}
|
||||
if (node.TYPE == "Call" && tw.in_boolean_context()) {
|
||||
if (exp instanceof AST_SymbolRef) {
|
||||
exp.definition().bool_fn++;
|
||||
} else if (exp instanceof AST_Assign && exp.operator == "=" && exp.left instanceof AST_SymbolRef) {
|
||||
exp.left.definition().bool_fn++;
|
||||
}
|
||||
if (node.TYPE == "Call") switch (tw.in_boolean_context()) {
|
||||
case "d":
|
||||
var drop = true;
|
||||
case true:
|
||||
mark_refs(exp, drop);
|
||||
}
|
||||
exp.walk(tw);
|
||||
var optional = node.optional;
|
||||
@@ -1078,6 +1078,25 @@ Compressor.prototype.compress = function(node) {
|
||||
tw.find_parent(AST_Scope).may_call_this();
|
||||
}
|
||||
return true;
|
||||
|
||||
function mark_refs(node, drop) {
|
||||
if (node instanceof AST_Assign) {
|
||||
if (node.operator != "=") return;
|
||||
mark_refs(node.left, drop);
|
||||
mark_refs(node.right, drop);
|
||||
} else if (node instanceof AST_Binary) {
|
||||
if (!lazy_op[node.operator]) return;
|
||||
mark_refs(node.left, drop);
|
||||
mark_refs(node.right, drop);
|
||||
} else if (node instanceof AST_Conditional) {
|
||||
mark_refs(node.consequent, drop);
|
||||
mark_refs(node.alternative, drop);
|
||||
} else if (node instanceof AST_SymbolRef) {
|
||||
var def = node.definition();
|
||||
def.bool_return++;
|
||||
if (drop) def.drop_return++;
|
||||
}
|
||||
}
|
||||
});
|
||||
def(AST_Class, function(tw, descend, compressor) {
|
||||
var node = this;
|
||||
@@ -7923,7 +7942,7 @@ Compressor.prototype.compress = function(node) {
|
||||
}));
|
||||
}
|
||||
|
||||
function map_bool_returns(fn) {
|
||||
function map_self_returns(fn) {
|
||||
var map = Object.create(null);
|
||||
scan_local_returns(fn, function(node) {
|
||||
var value = node.value;
|
||||
@@ -7936,9 +7955,14 @@ Compressor.prototype.compress = function(node) {
|
||||
return map;
|
||||
}
|
||||
|
||||
function all_bool(def, bool_returns, compressor) {
|
||||
return def.bool_fn + (bool_returns[def.id] || 0) === def.references.length - def.replaced
|
||||
&& !compressor.exposed(def);
|
||||
function can_trim_returns(def, self_returns, compressor) {
|
||||
if (compressor.exposed(def)) return false;
|
||||
switch (def.references.length - def.replaced - (self_returns[def.id] || 0)) {
|
||||
case def.drop_return:
|
||||
return "d";
|
||||
case def.bool_return:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function process_boolean_returns(fn, compressor) {
|
||||
@@ -7964,39 +7988,74 @@ Compressor.prototype.compress = function(node) {
|
||||
});
|
||||
}
|
||||
|
||||
AST_Scope.DEFMETHOD("process_boolean_returns", noop);
|
||||
AST_Defun.DEFMETHOD("process_boolean_returns", function(compressor) {
|
||||
AST_Scope.DEFMETHOD("process_returns", noop);
|
||||
AST_Defun.DEFMETHOD("process_returns", function(compressor) {
|
||||
if (!compressor.option("booleans")) return;
|
||||
var bool_returns = map_bool_returns(this);
|
||||
if (!all_bool(this.name.definition(), bool_returns, compressor)) return;
|
||||
if (compressor.parent() instanceof AST_ExportDefault) return;
|
||||
switch (can_trim_returns(this.name.definition(), map_self_returns(this), compressor)) {
|
||||
case "d":
|
||||
drop_returns(compressor, this, true);
|
||||
break;
|
||||
case true:
|
||||
process_boolean_returns(this, compressor);
|
||||
break;
|
||||
}
|
||||
});
|
||||
AST_Function.DEFMETHOD("process_boolean_returns", function(compressor) {
|
||||
AST_Function.DEFMETHOD("process_returns", function(compressor) {
|
||||
if (!compressor.option("booleans")) return;
|
||||
var bool_returns = map_bool_returns(this);
|
||||
if (this.name && !all_bool(this.name.definition(), bool_returns, compressor)) return;
|
||||
var drop = true;
|
||||
var self_returns = map_self_returns(this);
|
||||
if (this.name && !can_trim(this.name.definition())) return;
|
||||
var parent = compressor.parent();
|
||||
if (parent instanceof AST_Assign) {
|
||||
if (parent.operator != "=") return;
|
||||
var sym = parent.left;
|
||||
if (!(sym instanceof AST_SymbolRef)) return;
|
||||
if (!all_bool(sym.definition(), bool_returns, compressor)) return;
|
||||
if (!can_trim(sym.definition())) return;
|
||||
} else if (parent instanceof AST_Call && parent.expression !== this) {
|
||||
var exp = parent.expression;
|
||||
if (exp instanceof AST_SymbolRef) exp = exp.fixed_value();
|
||||
if (!(exp instanceof AST_Lambda)) return;
|
||||
if (exp.uses_arguments || exp.pinned()) return;
|
||||
var sym = exp.argnames[parent.args.indexOf(this)];
|
||||
var args = parent.args, sym;
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
var arg = args[i];
|
||||
if (arg === this) {
|
||||
sym = exp.argnames[i];
|
||||
if (!sym && exp.rest) return;
|
||||
break;
|
||||
}
|
||||
if (arg instanceof AST_Spread) return;
|
||||
}
|
||||
if (sym instanceof AST_DefaultValue) sym = sym.name;
|
||||
if (sym instanceof AST_SymbolFunarg && !all_bool(sym.definition(), bool_returns, compressor)) return;
|
||||
if (sym instanceof AST_SymbolFunarg && !can_trim(sym.definition())) return;
|
||||
} else if (parent.TYPE == "Call") {
|
||||
compressor.pop();
|
||||
var in_bool = compressor.in_boolean_context();
|
||||
compressor.push(this);
|
||||
if (!in_bool) return;
|
||||
switch (in_bool) {
|
||||
case true:
|
||||
drop = false;
|
||||
case "d":
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else return;
|
||||
if (drop) {
|
||||
drop_returns(compressor, this, true);
|
||||
} else {
|
||||
process_boolean_returns(this, compressor);
|
||||
}
|
||||
|
||||
function can_trim(def) {
|
||||
switch (can_trim_returns(def, self_returns, compressor)) {
|
||||
case true:
|
||||
drop = false;
|
||||
case "d":
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AST_BlockScope.DEFMETHOD("var_names", function() {
|
||||
@@ -8155,6 +8214,118 @@ Compressor.prototype.compress = function(node) {
|
||||
});
|
||||
}
|
||||
|
||||
function drop_returns(compressor, exp, ignore_name) {
|
||||
if (!(exp instanceof AST_Lambda)) return;
|
||||
var arrow = is_arrow(exp);
|
||||
var async = is_async(exp);
|
||||
var changed = false;
|
||||
var drop_body = false;
|
||||
if (arrow && compressor.option("arrows")) {
|
||||
if (!exp.value) {
|
||||
drop_body = true;
|
||||
} else if (!async || is_primitive(compressor, exp.value)) {
|
||||
var dropped = exp.value.drop_side_effect_free(compressor);
|
||||
if (dropped !== exp.value) {
|
||||
changed = true;
|
||||
exp.value = dropped;
|
||||
}
|
||||
}
|
||||
} else if (!is_generator(exp)) {
|
||||
if (!ignore_name && exp.name) {
|
||||
var def = exp.name.definition();
|
||||
drop_body = def.references.length == def.replaced;
|
||||
} else {
|
||||
drop_body = true;
|
||||
}
|
||||
}
|
||||
if (drop_body) {
|
||||
exp.process_expression(false, function(node) {
|
||||
var value = node.value;
|
||||
if (value) {
|
||||
if (async && !is_primitive(compressor, value)) return node;
|
||||
value = value.drop_side_effect_free(compressor, true);
|
||||
}
|
||||
changed = true;
|
||||
if (!value) return make_node(AST_EmptyStatement, node);
|
||||
return make_node(AST_SimpleStatement, node, { body: value });
|
||||
});
|
||||
scan_local_returns(exp, function(node) {
|
||||
var value = node.value;
|
||||
if (value) {
|
||||
if (async && !is_primitive(compressor, value)) return;
|
||||
var dropped = value.drop_side_effect_free(compressor);
|
||||
if (dropped !== value) {
|
||||
changed = true;
|
||||
node.value = dropped;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (async && compressor.option("awaits")) {
|
||||
if (drop_body) exp.process_expression("awaits", function(node) {
|
||||
var body = node.body;
|
||||
if (body instanceof AST_Await) {
|
||||
if (is_primitive(compressor, body.expression)) {
|
||||
changed = true;
|
||||
body = body.expression.drop_side_effect_free(compressor, true);
|
||||
if (!body) return make_node(AST_EmptyStatement, node);
|
||||
node.body = body;
|
||||
}
|
||||
} else if (body instanceof AST_Sequence) {
|
||||
var exprs = body.expressions;
|
||||
for (var i = exprs.length; --i >= 0;) {
|
||||
var tail = exprs[i];
|
||||
if (!(tail instanceof AST_Await)) break;
|
||||
var value = tail.expression;
|
||||
if (!is_primitive(compressor, value)) break;
|
||||
changed = true;
|
||||
if (exprs[i] = value.drop_side_effect_free(compressor)) break;
|
||||
}
|
||||
switch (i) {
|
||||
case -1:
|
||||
return make_node(AST_EmptyStatement, node);
|
||||
case 0:
|
||||
node.body = exprs[0];
|
||||
break;
|
||||
default:
|
||||
exprs.length = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
});
|
||||
var abort = !drop_body && exp.name || arrow && exp.value && !is_primitive(compressor, exp.value);
|
||||
var tw = new TreeWalker(function(node) {
|
||||
if (abort) return true;
|
||||
if (tw.parent() === exp && node.may_throw(compressor)) return abort = true;
|
||||
if (node instanceof AST_Await) return abort = true;
|
||||
if (node instanceof AST_ForAwaitOf) return abort = true;
|
||||
if (node instanceof AST_Return) {
|
||||
if (node.value && !is_primitive(compressor, node.value)) return abort = true;
|
||||
return;
|
||||
}
|
||||
if (node instanceof AST_Scope && node !== exp) return true;
|
||||
});
|
||||
exp.walk(tw);
|
||||
if (!abort) {
|
||||
var ctor;
|
||||
switch (exp.CTOR) {
|
||||
case AST_AsyncArrow:
|
||||
ctor = AST_Arrow;
|
||||
break;
|
||||
case AST_AsyncFunction:
|
||||
ctor = AST_Function;
|
||||
break;
|
||||
case AST_AsyncGeneratorFunction:
|
||||
ctor = AST_GeneratorFunction;
|
||||
break;
|
||||
}
|
||||
return make_node(ctor, exp, exp);
|
||||
}
|
||||
}
|
||||
return changed && exp.clone();
|
||||
}
|
||||
|
||||
// drop_side_effect_free()
|
||||
// remove side-effect-free parts which only affects return value
|
||||
(function(def) {
|
||||
@@ -8261,116 +8432,6 @@ Compressor.prototype.compress = function(node) {
|
||||
if (!rhs) return lhs;
|
||||
return make_sequence(this, [ lhs, rhs ]);
|
||||
});
|
||||
function drop_returns(compressor, exp) {
|
||||
var arrow = is_arrow(exp);
|
||||
var async = is_async(exp);
|
||||
var changed = false;
|
||||
var drop_body = false;
|
||||
if (arrow && compressor.option("arrows")) {
|
||||
if (!exp.value) {
|
||||
drop_body = true;
|
||||
} else if (!async || is_primitive(compressor, exp.value)) {
|
||||
var dropped = exp.value.drop_side_effect_free(compressor);
|
||||
if (dropped !== exp.value) {
|
||||
changed = true;
|
||||
exp.value = dropped;
|
||||
}
|
||||
}
|
||||
} else if (exp instanceof AST_AsyncFunction || exp instanceof AST_Function) {
|
||||
if (exp.name) {
|
||||
var def = exp.name.definition();
|
||||
drop_body = def.references.length == def.replaced;
|
||||
} else {
|
||||
drop_body = true;
|
||||
}
|
||||
}
|
||||
if (drop_body) {
|
||||
exp.process_expression(false, function(node) {
|
||||
var value = node.value;
|
||||
if (value) {
|
||||
if (async && !is_primitive(compressor, value)) return node;
|
||||
value = value.drop_side_effect_free(compressor, true);
|
||||
}
|
||||
changed = true;
|
||||
if (!value) return make_node(AST_EmptyStatement, node);
|
||||
return make_node(AST_SimpleStatement, node, { body: value });
|
||||
});
|
||||
scan_local_returns(exp, function(node) {
|
||||
var value = node.value;
|
||||
if (value) {
|
||||
if (async && !is_primitive(compressor, value)) return;
|
||||
var dropped = value.drop_side_effect_free(compressor);
|
||||
if (dropped !== value) {
|
||||
changed = true;
|
||||
node.value = dropped;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (async && compressor.option("awaits")) {
|
||||
if (drop_body) exp.process_expression("awaits", function(node) {
|
||||
var body = node.body;
|
||||
if (body instanceof AST_Await) {
|
||||
if (is_primitive(compressor, body.expression)) {
|
||||
changed = true;
|
||||
body = body.expression.drop_side_effect_free(compressor, true);
|
||||
if (!body) return make_node(AST_EmptyStatement, node);
|
||||
node.body = body;
|
||||
}
|
||||
} else if (body instanceof AST_Sequence) {
|
||||
var exprs = body.expressions;
|
||||
for (var i = exprs.length; --i >= 0;) {
|
||||
var tail = exprs[i];
|
||||
if (!(tail instanceof AST_Await)) break;
|
||||
var value = tail.expression;
|
||||
if (!is_primitive(compressor, value)) break;
|
||||
changed = true;
|
||||
if (exprs[i] = value.drop_side_effect_free(compressor)) break;
|
||||
}
|
||||
switch (i) {
|
||||
case -1:
|
||||
return make_node(AST_EmptyStatement, node);
|
||||
case 0:
|
||||
node.body = exprs[0];
|
||||
break;
|
||||
default:
|
||||
exprs.length = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
});
|
||||
var abort = !drop_body && exp.name || arrow && exp.value && !is_primitive(compressor, exp.value);
|
||||
var tw = new TreeWalker(function(node) {
|
||||
if (abort) return true;
|
||||
if (tw.parent() === exp && node.may_throw(compressor)) return abort = true;
|
||||
if (node instanceof AST_Await) return abort = true;
|
||||
if (node instanceof AST_ForAwaitOf) return abort = true;
|
||||
if (node instanceof AST_Return) {
|
||||
if (node.value && !is_primitive(compressor, node.value)) return abort = true;
|
||||
return;
|
||||
}
|
||||
if (node instanceof AST_Scope && node !== exp) return true;
|
||||
});
|
||||
exp.walk(tw);
|
||||
if (!abort) {
|
||||
var ctor;
|
||||
switch (exp.CTOR) {
|
||||
case AST_AsyncArrow:
|
||||
ctor = AST_Arrow;
|
||||
break;
|
||||
case AST_AsyncFunction:
|
||||
ctor = AST_Function;
|
||||
break;
|
||||
case AST_AsyncGeneratorFunction:
|
||||
ctor = AST_GeneratorFunction;
|
||||
break;
|
||||
}
|
||||
return make_node(ctor, exp, exp);
|
||||
}
|
||||
}
|
||||
return changed && exp.clone();
|
||||
}
|
||||
function assign_this_only(fn, compressor) {
|
||||
fn.new = true;
|
||||
var result = all(fn.body, function(stat) {
|
||||
|
||||
@@ -448,6 +448,23 @@ concat_truthy: {
|
||||
]
|
||||
}
|
||||
|
||||
process_returns: {
|
||||
options = {
|
||||
booleans: true,
|
||||
}
|
||||
input: {
|
||||
(function() {
|
||||
return 42;
|
||||
})() && console.log("PASS");
|
||||
}
|
||||
expect: {
|
||||
(function() {
|
||||
return 42;
|
||||
})() && console.log("PASS");
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_3465_1: {
|
||||
options = {
|
||||
booleans: true,
|
||||
|
||||
@@ -692,7 +692,7 @@ funarg_inline: {
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
process_boolean_returns: {
|
||||
process_returns: {
|
||||
options = {
|
||||
booleans: true,
|
||||
}
|
||||
@@ -706,9 +706,7 @@ process_boolean_returns: {
|
||||
expect: {
|
||||
console.log(function({ length }) {
|
||||
return length ? "FAIL" : "PASS";
|
||||
}(function() {
|
||||
return 42;
|
||||
}));
|
||||
}(function() {}));
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
node_version: ">=6"
|
||||
|
||||
@@ -6341,7 +6341,7 @@ issue_4612_4: {
|
||||
expect: {
|
||||
console.log(function() {
|
||||
function f() {
|
||||
return h();
|
||||
h();
|
||||
}
|
||||
function g() {
|
||||
return h();
|
||||
|
||||
@@ -1144,7 +1144,7 @@ conditional_assignments_3: {
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_3856: {
|
||||
issue_3856_1: {
|
||||
options = {
|
||||
booleans: true,
|
||||
conditionals: true,
|
||||
@@ -1169,9 +1169,46 @@ issue_3856: {
|
||||
console.log(function() {
|
||||
(function() {
|
||||
var a, b;
|
||||
if (a) return a, 1;
|
||||
for (a = 0; !console;);
|
||||
if (a) a;
|
||||
else {
|
||||
a = 0;
|
||||
for (; !console;);
|
||||
}
|
||||
})();
|
||||
}());
|
||||
}
|
||||
expect_stdout: "undefined"
|
||||
}
|
||||
|
||||
issue_3856_2: {
|
||||
options = {
|
||||
booleans: true,
|
||||
conditionals: true,
|
||||
if_return: true,
|
||||
join_vars: true,
|
||||
passes: 2,
|
||||
sequences: true,
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function() {
|
||||
(function() {
|
||||
var a;
|
||||
if (!a) {
|
||||
a = 0;
|
||||
for (var b; !console;);
|
||||
return 0;
|
||||
}
|
||||
if (a) return 1;
|
||||
})();
|
||||
}());
|
||||
}
|
||||
expect: {
|
||||
console.log(function() {
|
||||
(function() {
|
||||
var a, b;
|
||||
if (!a)
|
||||
for (a = 0; !console;);
|
||||
})();
|
||||
}());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user