add `clean_getters` compressor option (default `false`)
authorMihai Bazon <mihai@bazon.net>
Wed, 2 Oct 2013 16:33:45 +0000 (19:33 +0300)
committerMihai Bazon <mihai@bazon.net>
Wed, 2 Oct 2013 16:38:01 +0000 (19:38 +0300)
allows one to specify if `foo.bar` is considered to have side effects.

README.md
lib/compress.js

index 4356d4f..6980878 100644 (file)
--- a/README.md
+++ b/README.md
@@ -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
 
index 93f0148..76cbc74 100644 (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;