fix pure_funcs & improve side_effects
- only drops side-effect-free arguments - drop side-effect-free parts with discarded value from `AST_Seq` & `AST_SimpleStatement` closes #1494
This commit is contained in:
178
lib/compress.js
178
lib/compress.js
@@ -83,6 +83,14 @@ function Compressor(options, false_by_default) {
|
||||
global_defs : {},
|
||||
passes : 1,
|
||||
}, true);
|
||||
var pure_funcs = this.options["pure_funcs"];
|
||||
if (typeof pure_funcs == "function") {
|
||||
this.pure_funcs = pure_funcs;
|
||||
} else {
|
||||
this.pure_funcs = pure_funcs ? function(node) {
|
||||
return pure_funcs.indexOf(node.expression.print_to_string()) < 0;
|
||||
} : return_true;
|
||||
}
|
||||
var top_retain = this.options["top_retain"];
|
||||
if (top_retain instanceof RegExp) {
|
||||
this.top_retain = function(def) {
|
||||
@@ -304,6 +312,13 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function tighten_body(statements, compressor) {
|
||||
var CHANGED, max_iter = 10;
|
||||
do {
|
||||
@@ -1354,10 +1369,12 @@ merge(Compressor.prototype, {
|
||||
def(AST_This, return_false);
|
||||
|
||||
def(AST_Call, function(compressor){
|
||||
var pure = compressor.option("pure_funcs");
|
||||
if (!pure) return true;
|
||||
if (typeof pure == "function") return pure(this);
|
||||
return pure.indexOf(this.expression.print_to_string()) < 0;
|
||||
if (compressor.pure_funcs(this)) return true;
|
||||
for (var i = this.args.length; --i >= 0;) {
|
||||
if (this.args[i].has_side_effects(compressor))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
def(AST_Block, function(compressor){
|
||||
@@ -1855,12 +1872,151 @@ merge(Compressor.prototype, {
|
||||
return self;
|
||||
});
|
||||
|
||||
// drop_side_effect_free()
|
||||
// remove side-effect-free parts which only affects return value
|
||||
(function(def){
|
||||
function return_this() {
|
||||
return this;
|
||||
}
|
||||
|
||||
function return_null() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Drop side-effect-free elements from an array of expressions.
|
||||
// Returns an array of expressions with side-effects or null
|
||||
// if all elements were dropped. Note: original array may be
|
||||
// returned if nothing changed.
|
||||
function trim(nodes, compressor, first_in_statement) {
|
||||
var ret = [], changed = false;
|
||||
for (var i = 0, ii = nodes.length; i < ii; i++) {
|
||||
var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
|
||||
changed |= node !== nodes[i];
|
||||
if (node) {
|
||||
ret.push(node);
|
||||
first_in_statement = false;
|
||||
}
|
||||
}
|
||||
return changed ? ret.length ? ret : null : nodes;
|
||||
}
|
||||
|
||||
def(AST_Node, return_this);
|
||||
def(AST_Constant, return_null);
|
||||
def(AST_This, return_null);
|
||||
def(AST_Call, function(compressor, first_in_statement){
|
||||
if (compressor.pure_funcs(this)) return this;
|
||||
var args = trim(this.args, compressor, first_in_statement);
|
||||
return args && AST_Seq.from_array(args);
|
||||
});
|
||||
def(AST_Function, return_null);
|
||||
def(AST_Binary, function(compressor, first_in_statement){
|
||||
var right = this.right.drop_side_effect_free(compressor);
|
||||
if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement);
|
||||
switch (this.operator) {
|
||||
case "&&":
|
||||
case "||":
|
||||
var node = this.clone();
|
||||
node.right = right;
|
||||
return node;
|
||||
default:
|
||||
var left = this.left.drop_side_effect_free(compressor, first_in_statement);
|
||||
if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement);
|
||||
return make_node(AST_Seq, this, {
|
||||
car: left,
|
||||
cdr: right
|
||||
});
|
||||
}
|
||||
});
|
||||
def(AST_Assign, return_this);
|
||||
def(AST_Conditional, function(compressor){
|
||||
var consequent = this.consequent.drop_side_effect_free(compressor);
|
||||
var alternative = this.alternative.drop_side_effect_free(compressor);
|
||||
if (consequent === this.consequent && alternative === this.alternative) return this;
|
||||
if (!consequent) return alternative ? make_node(AST_Binary, this, {
|
||||
operator: "||",
|
||||
left: this.condition,
|
||||
right: alternative
|
||||
}) : this.condition.drop_side_effect_free(compressor);
|
||||
if (!alternative) return make_node(AST_Binary, this, {
|
||||
operator: "&&",
|
||||
left: this.condition,
|
||||
right: consequent
|
||||
});
|
||||
var node = this.clone();
|
||||
node.consequent = consequent;
|
||||
node.alternative = alternative;
|
||||
return node;
|
||||
});
|
||||
def(AST_Unary, function(compressor, first_in_statement){
|
||||
switch (this.operator) {
|
||||
case "delete":
|
||||
case "++":
|
||||
case "--":
|
||||
return this;
|
||||
case "typeof":
|
||||
if (this.expression instanceof AST_SymbolRef) return null;
|
||||
default:
|
||||
if (first_in_statement && is_iife_call(this.expression)) return this;
|
||||
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
||||
}
|
||||
});
|
||||
def(AST_SymbolRef, function() {
|
||||
return this.undeclared() ? this : null;
|
||||
});
|
||||
def(AST_Object, function(compressor, first_in_statement){
|
||||
var values = trim(this.properties, compressor, first_in_statement);
|
||||
return values && AST_Seq.from_array(values);
|
||||
});
|
||||
def(AST_ObjectProperty, function(compressor, first_in_statement){
|
||||
return this.value.drop_side_effect_free(compressor, first_in_statement);
|
||||
});
|
||||
def(AST_Array, function(compressor, first_in_statement){
|
||||
var values = trim(this.elements, compressor, first_in_statement);
|
||||
return values && AST_Seq.from_array(values);
|
||||
});
|
||||
def(AST_Dot, function(compressor, first_in_statement){
|
||||
if (!compressor.option("pure_getters")) return this;
|
||||
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
||||
});
|
||||
def(AST_Sub, function(compressor, first_in_statement){
|
||||
if (!compressor.option("pure_getters")) return this;
|
||||
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
|
||||
if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
|
||||
var property = this.property.drop_side_effect_free(compressor);
|
||||
if (!property) return expression;
|
||||
return make_node(AST_Seq, this, {
|
||||
car: expression,
|
||||
cdr: property
|
||||
});
|
||||
});
|
||||
def(AST_Seq, function(compressor){
|
||||
var cdr = this.cdr.drop_side_effect_free(compressor);
|
||||
if (cdr === this.cdr) return this;
|
||||
if (!cdr) return this.car;
|
||||
return make_node(AST_Seq, this, {
|
||||
car: this.car,
|
||||
cdr: cdr
|
||||
});
|
||||
});
|
||||
})(function(node, func){
|
||||
node.DEFMETHOD("drop_side_effect_free", func);
|
||||
});
|
||||
|
||||
OPT(AST_SimpleStatement, function(self, compressor){
|
||||
if (compressor.option("side_effects")) {
|
||||
if (!self.body.has_side_effects(compressor)) {
|
||||
var body = self.body;
|
||||
if (!body.has_side_effects(compressor)) {
|
||||
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
|
||||
return make_node(AST_EmptyStatement, self);
|
||||
}
|
||||
var node = body.drop_side_effect_free(compressor, true);
|
||||
if (!node) {
|
||||
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
|
||||
return make_node(AST_EmptyStatement, self);
|
||||
}
|
||||
if (node !== body) {
|
||||
return make_node(AST_SimpleStatement, self, { body: node });
|
||||
}
|
||||
}
|
||||
return self;
|
||||
});
|
||||
@@ -2435,13 +2591,6 @@ merge(Compressor.prototype, {
|
||||
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){
|
||||
@@ -2464,9 +2613,8 @@ merge(Compressor.prototype, {
|
||||
OPT(AST_Seq, function(self, compressor){
|
||||
if (!compressor.option("side_effects"))
|
||||
return self;
|
||||
if (!self.car.has_side_effects(compressor)) {
|
||||
return maintain_this_binding(compressor.parent(), self, self.cdr);
|
||||
}
|
||||
self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor));
|
||||
if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr);
|
||||
if (compressor.option("cascade")) {
|
||||
if (self.car instanceof AST_Assign
|
||||
&& !self.car.left.has_side_effects(compressor)) {
|
||||
|
||||
@@ -590,3 +590,61 @@ drop_fnames: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global_var: {
|
||||
options = {
|
||||
side_effects: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a;
|
||||
function foo(b) {
|
||||
a;
|
||||
b;
|
||||
c;
|
||||
typeof c === "undefined";
|
||||
c + b + a;
|
||||
b && b.ar();
|
||||
return b;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var a;
|
||||
function foo(b) {
|
||||
c;
|
||||
c;
|
||||
b && b.ar();
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iife: {
|
||||
options = {
|
||||
side_effects: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
var a;
|
||||
~function() {}(b);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f() {
|
||||
~function() {}(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop_value: {
|
||||
options = {
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
(1, [2, foo()], 3, {a:1, b:bar()});
|
||||
}
|
||||
expect: {
|
||||
foo(), bar();
|
||||
}
|
||||
}
|
||||
|
||||
295
test/compress/pure_funcs.js
Normal file
295
test/compress/pure_funcs.js
Normal file
@@ -0,0 +1,295 @@
|
||||
array: {
|
||||
options = {
|
||||
pure_funcs: [ "Math.floor" ],
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
var a;
|
||||
function f(b) {
|
||||
Math.floor(a / b);
|
||||
Math.floor(c / b);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var a;
|
||||
function f(b) {
|
||||
c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func: {
|
||||
options = {
|
||||
pure_funcs: function(node) {
|
||||
return !~node.args[0].print_to_string().indexOf("a");
|
||||
},
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
function f(a, b) {
|
||||
Math.floor(a / b);
|
||||
Math.floor(c / b);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f(a, b) {
|
||||
Math.floor(c / b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
side_effects: {
|
||||
options = {
|
||||
pure_funcs: [ "console.log" ],
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
function f(a, b) {
|
||||
console.log(a());
|
||||
console.log(b);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f(a, b) {
|
||||
a();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unused: {
|
||||
options = {
|
||||
pure_funcs: [ "pure" ],
|
||||
side_effects: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function foo() {
|
||||
var u = pure(1);
|
||||
var x = pure(2);
|
||||
var y = pure(x);
|
||||
var z = pure(pure(side_effects()));
|
||||
return pure(3);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function foo() {
|
||||
side_effects();
|
||||
return pure(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
babel: {
|
||||
options = {
|
||||
pure_funcs: [ "_classCallCheck" ],
|
||||
side_effects: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor))
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
var Foo = function Foo() {
|
||||
_classCallCheck(this, Foo);
|
||||
};
|
||||
}
|
||||
expect: {
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor))
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
var Foo = function() {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
conditional: {
|
||||
options = {
|
||||
pure_funcs: [ "pure" ],
|
||||
side_effects: true,
|
||||
}
|
||||
input: {
|
||||
pure(1 | a() ? 2 & b() : 7 ^ c());
|
||||
pure(1 | a() ? 2 & b() : 5);
|
||||
pure(1 | a() ? 4 : 7 ^ c());
|
||||
pure(1 | a() ? 4 : 5);
|
||||
pure(3 ? 2 & b() : 7 ^ c());
|
||||
pure(3 ? 2 & b() : 5);
|
||||
pure(3 ? 4 : 7 ^ c());
|
||||
pure(3 ? 4 : 5);
|
||||
}
|
||||
expect: {
|
||||
1 | a() ? b() : c();
|
||||
1 | a() && b();
|
||||
1 | a() || c();
|
||||
a();
|
||||
3 ? b() : c();
|
||||
3 && b();
|
||||
3 || c();
|
||||
}
|
||||
}
|
||||
|
||||
relational: {
|
||||
options = {
|
||||
pure_funcs: [ "foo" ],
|
||||
side_effects :true,
|
||||
}
|
||||
input: {
|
||||
foo() in foo();
|
||||
foo() instanceof bar();
|
||||
foo() < "bar";
|
||||
bar() > foo();
|
||||
bar() != bar();
|
||||
bar() !== "bar";
|
||||
"bar" == foo();
|
||||
"bar" === bar();
|
||||
"bar" >= "bar";
|
||||
}
|
||||
expect: {
|
||||
bar();
|
||||
bar();
|
||||
bar(), bar();
|
||||
bar();
|
||||
bar();
|
||||
}
|
||||
}
|
||||
|
||||
arithmetic: {
|
||||
options = {
|
||||
pure_funcs: [ "foo" ],
|
||||
side_effects :true,
|
||||
}
|
||||
input: {
|
||||
foo() + foo();
|
||||
foo() - bar();
|
||||
foo() * "bar";
|
||||
bar() / foo();
|
||||
bar() & bar();
|
||||
bar() | "bar";
|
||||
"bar" >> foo();
|
||||
"bar" << bar();
|
||||
"bar" >>> "bar";
|
||||
}
|
||||
expect: {
|
||||
bar();
|
||||
bar();
|
||||
bar(), bar();
|
||||
bar();
|
||||
bar();
|
||||
}
|
||||
}
|
||||
|
||||
boolean_and: {
|
||||
options = {
|
||||
pure_funcs: [ "foo" ],
|
||||
side_effects :true,
|
||||
}
|
||||
input: {
|
||||
foo() && foo();
|
||||
foo() && bar();
|
||||
foo() && "bar";
|
||||
bar() && foo();
|
||||
bar() && bar();
|
||||
bar() && "bar";
|
||||
"bar" && foo();
|
||||
"bar" && bar();
|
||||
"bar" && "bar";
|
||||
}
|
||||
expect: {
|
||||
foo() && bar();
|
||||
bar();
|
||||
bar() && bar();
|
||||
bar();
|
||||
"bar" && bar();
|
||||
}
|
||||
}
|
||||
|
||||
boolean_or: {
|
||||
options = {
|
||||
pure_funcs: [ "foo" ],
|
||||
side_effects :true,
|
||||
}
|
||||
input: {
|
||||
foo() || foo();
|
||||
foo() || bar();
|
||||
foo() || "bar";
|
||||
bar() || foo();
|
||||
bar() || bar();
|
||||
bar() || "bar";
|
||||
"bar" || foo();
|
||||
"bar" || bar();
|
||||
"bar" || "bar";
|
||||
}
|
||||
expect: {
|
||||
foo() || bar();
|
||||
bar();
|
||||
bar() || bar();
|
||||
bar();
|
||||
"bar" || bar();
|
||||
}
|
||||
}
|
||||
|
||||
assign: {
|
||||
options = {
|
||||
pure_funcs: [ "foo" ],
|
||||
side_effects :true,
|
||||
}
|
||||
input: {
|
||||
var a;
|
||||
function f(b) {
|
||||
a = foo();
|
||||
b *= 4 + foo();
|
||||
c >>= 0 | foo();
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var a;
|
||||
function f(b) {
|
||||
a = foo();
|
||||
b *= 4 + foo();
|
||||
c >>= 0 | foo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unary: {
|
||||
options = {
|
||||
pure_funcs: [ "foo" ],
|
||||
side_effects :true,
|
||||
}
|
||||
input: {
|
||||
typeof foo();
|
||||
typeof bar();
|
||||
typeof "bar";
|
||||
void foo();
|
||||
void bar();
|
||||
void "bar";
|
||||
delete a[foo()];
|
||||
delete a[bar()];
|
||||
delete a["bar"];
|
||||
a[foo()]++;
|
||||
a[bar()]++;
|
||||
a["bar"]++;
|
||||
--a[foo()];
|
||||
--a[bar()];
|
||||
--a["bar"];
|
||||
~foo();
|
||||
~bar();
|
||||
~"bar";
|
||||
}
|
||||
expect: {
|
||||
bar();
|
||||
bar();
|
||||
delete a[foo()];
|
||||
delete a[bar()];
|
||||
delete a["bar"];
|
||||
a[foo()]++;
|
||||
a[bar()]++;
|
||||
a["bar"]++;
|
||||
--a[foo()];
|
||||
--a[bar()];
|
||||
--a["bar"];
|
||||
bar();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user