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"
where the return value is discarded, to avoid the parens that the
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

View File

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