add clean_getters compressor option (default false)

allows one to specify if `foo.bar` is considered to have side effects.
This commit is contained in:
Mihai Bazon
2013-10-02 19:33:45 +03:00
parent 88fb83aa81
commit 8cc86fee60
2 changed files with 55 additions and 49 deletions

View File

@@ -212,6 +212,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `negate_iife` -- negate "Immediately-Called Function Expressions" - `negate_iife` -- negate "Immediately-Called Function Expressions"
where the return value is discarded, to avoid the parens that the where the return value is discarded, to avoid the parens that the
code generator would insert. code generator would insert.
- `clean_getters` -- the default is `false`. If you pass `true` for
this, UglifyJS will assume that object property access
(e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects.
### The `unsafe` option ### The `unsafe` option

View File

@@ -66,6 +66,7 @@ function Compressor(options, false_by_default) {
join_vars : !false_by_default, join_vars : !false_by_default,
cascade : !false_by_default, cascade : !false_by_default,
side_effects : !false_by_default, side_effects : !false_by_default,
clean_getters : false,
negate_iife : !false_by_default, negate_iife : !false_by_default,
screw_ie8 : false, screw_ie8 : false,
@@ -802,70 +803,72 @@ merge(Compressor.prototype, {
// determine if expression has side effects // determine if expression has side effects
(function(def){ (function(def){
def(AST_Node, function(){ return true }); def(AST_Node, function(compressor){ return true });
def(AST_EmptyStatement, function(){ return false }); def(AST_EmptyStatement, function(compressor){ return false });
def(AST_Constant, function(){ return false }); def(AST_Constant, function(compressor){ return false });
def(AST_This, function(){ return false }); def(AST_This, function(compressor){ return false });
def(AST_Block, function(){ def(AST_Block, function(compressor){
for (var i = this.body.length; --i >= 0;) { for (var i = this.body.length; --i >= 0;) {
if (this.body[i].has_side_effects()) if (this.body[i].has_side_effects(compressor))
return true; return true;
} }
return false; return false;
}); });
def(AST_SimpleStatement, function(){ def(AST_SimpleStatement, function(compressor){
return this.body.has_side_effects(); return this.body.has_side_effects(compressor);
}); });
def(AST_Defun, function(){ return true }); def(AST_Defun, function(compressor){ return true });
def(AST_Function, function(){ return false }); def(AST_Function, function(compressor){ return false });
def(AST_Binary, function(){ def(AST_Binary, function(compressor){
return this.left.has_side_effects() return this.left.has_side_effects(compressor)
|| this.right.has_side_effects(); || this.right.has_side_effects(compressor);
}); });
def(AST_Assign, function(){ return true }); def(AST_Assign, function(compressor){ return true });
def(AST_Conditional, function(){ def(AST_Conditional, function(compressor){
return this.condition.has_side_effects() return this.condition.has_side_effects(compressor)
|| this.consequent.has_side_effects() || this.consequent.has_side_effects(compressor)
|| this.alternative.has_side_effects(); || this.alternative.has_side_effects(compressor);
}); });
def(AST_Unary, function(){ def(AST_Unary, function(compressor){
return this.operator == "delete" return this.operator == "delete"
|| this.operator == "++" || this.operator == "++"
|| this.operator == "--" || this.operator == "--"
|| this.expression.has_side_effects(); || this.expression.has_side_effects(compressor);
}); });
def(AST_SymbolRef, function(){ return false }); def(AST_SymbolRef, function(compressor){ return false });
def(AST_Object, function(){ def(AST_Object, function(compressor){
for (var i = this.properties.length; --i >= 0;) for (var i = this.properties.length; --i >= 0;)
if (this.properties[i].has_side_effects()) if (this.properties[i].has_side_effects(compressor))
return true; return true;
return false; return false;
}); });
def(AST_ObjectProperty, function(){ def(AST_ObjectProperty, function(compressor){
return this.value.has_side_effects(); return this.value.has_side_effects(compressor);
}); });
def(AST_Array, function(){ def(AST_Array, function(compressor){
for (var i = this.elements.length; --i >= 0;) for (var i = this.elements.length; --i >= 0;)
if (this.elements[i].has_side_effects()) if (this.elements[i].has_side_effects(compressor))
return true; return true;
return false; return false;
}); });
// def(AST_Dot, function(){ def(AST_Dot, function(compressor){
// return this.expression.has_side_effects(); if (!compressor.option("clean_getters")) return true;
// }); return this.expression.has_side_effects(compressor);
// def(AST_Sub, function(){
// return this.expression.has_side_effects()
// || this.property.has_side_effects();
// });
def(AST_PropAccess, function(){
return true;
}); });
def(AST_Seq, function(){ def(AST_Sub, function(compressor){
return this.car.has_side_effects() if (!compressor.option("clean_getters")) return true;
|| this.cdr.has_side_effects(); return this.expression.has_side_effects(compressor)
|| this.property.has_side_effects(compressor);
});
def(AST_PropAccess, function(compressor){
return !compressor.option("clean_getters");
});
def(AST_Seq, function(compressor){
return this.car.has_side_effects(compressor)
|| this.cdr.has_side_effects(compressor);
}); });
})(function(node, func){ })(function(node, func){
node.DEFMETHOD("has_side_effects", func); node.DEFMETHOD("has_side_effects", func);
@@ -949,7 +952,7 @@ merge(Compressor.prototype, {
node.definitions.forEach(function(def){ node.definitions.forEach(function(def){
if (def.value) { if (def.value) {
initializations.add(def.name.name, def.value); initializations.add(def.name.name, def.value);
if (def.value.has_side_effects()) { if (def.value.has_side_effects(compressor)) {
def.value.walk(tw); def.value.walk(tw);
} }
} }
@@ -1026,7 +1029,7 @@ merge(Compressor.prototype, {
line : def.name.start.line, line : def.name.start.line,
col : def.name.start.col col : def.name.start.col
}; };
if (def.value && def.value.has_side_effects()) { if (def.value && def.value.has_side_effects(compressor)) {
def._unused_side_effects = true; def._unused_side_effects = true;
compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w); compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w);
return true; return true;
@@ -1228,7 +1231,7 @@ merge(Compressor.prototype, {
OPT(AST_SimpleStatement, function(self, compressor){ OPT(AST_SimpleStatement, function(self, compressor){
if (compressor.option("side_effects")) { if (compressor.option("side_effects")) {
if (!self.body.has_side_effects()) { if (!self.body.has_side_effects(compressor)) {
compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start); compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
return make_node(AST_EmptyStatement, self); return make_node(AST_EmptyStatement, self);
} }
@@ -1741,7 +1744,7 @@ merge(Compressor.prototype, {
if (compressor.option("side_effects")) { if (compressor.option("side_effects")) {
if (self.expression instanceof AST_Function if (self.expression instanceof AST_Function
&& self.args.length == 0 && self.args.length == 0
&& !AST_Block.prototype.has_side_effects.call(self.expression)) { && !AST_Block.prototype.has_side_effects.call(self.expression, compressor)) {
return make_node(AST_Undefined, self).transform(compressor); return make_node(AST_Undefined, self).transform(compressor);
} }
} }
@@ -1768,7 +1771,7 @@ merge(Compressor.prototype, {
OPT(AST_Seq, function(self, compressor){ OPT(AST_Seq, function(self, compressor){
if (!compressor.option("side_effects")) if (!compressor.option("side_effects"))
return self; return self;
if (!self.car.has_side_effects()) { if (!self.car.has_side_effects(compressor)) {
// we shouldn't compress (1,eval)(something) to // we shouldn't compress (1,eval)(something) to
// eval(something) because that changes the meaning of // eval(something) because that changes the meaning of
// eval (becomes lexical instead of global). // eval (becomes lexical instead of global).
@@ -1783,12 +1786,12 @@ merge(Compressor.prototype, {
} }
if (compressor.option("cascade")) { if (compressor.option("cascade")) {
if (self.car instanceof AST_Assign if (self.car instanceof AST_Assign
&& !self.car.left.has_side_effects() && !self.car.left.has_side_effects(compressor)
&& self.car.left.equivalent_to(self.cdr)) { && self.car.left.equivalent_to(self.cdr)) {
return self.car; return self.car;
} }
if (!self.car.has_side_effects() if (!self.car.has_side_effects(compressor)
&& !self.cdr.has_side_effects() && !self.cdr.has_side_effects(compressor)
&& self.car.equivalent_to(self.cdr)) { && self.car.equivalent_to(self.cdr)) {
return self.car; return self.car;
} }
@@ -1850,7 +1853,7 @@ merge(Compressor.prototype, {
} }
if (this.right instanceof AST_Seq if (this.right instanceof AST_Seq
&& !(this.operator == "||" || this.operator == "&&") && !(this.operator == "||" || this.operator == "&&")
&& !this.left.has_side_effects()) { && !this.left.has_side_effects(compressor)) {
var seq = this.right; var seq = this.right;
var x = seq.to_array(); var x = seq.to_array();
this.right = x.pop(); this.right = x.pop();
@@ -1867,7 +1870,7 @@ merge(Compressor.prototype, {
OPT(AST_Binary, function(self, compressor){ OPT(AST_Binary, function(self, compressor){
var reverse = compressor.has_directive("use asm") ? noop var reverse = compressor.has_directive("use asm") ? noop
: function(op, force) { : function(op, force) {
if (force || !(self.left.has_side_effects() || self.right.has_side_effects())) { if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) {
if (op) self.operator = op; if (op) self.operator = op;
var tmp = self.left; var tmp = self.left;
self.left = self.right; self.left = self.right;