Concatenate strings also on the right-hand side of an expression that cannot be evalu...
authorDan Wolff <dan.d.wolff@gmail.com>
Thu, 19 Sep 2013 08:58:50 +0000 (10:58 +0200)
committerMihai Bazon <mihai@bazon.net>
Thu, 19 Sep 2013 10:03:03 +0000 (13:03 +0300)
E.g. converts:
  a+'Hello'+'World'
to
  a+'HelloWorld'

lib/compress.js
test/compress/issue-126.js [new file with mode: 0644]

index 0d2053e..8bd58bb 100644 (file)
@@ -636,7 +636,8 @@ merge(Compressor.prototype, {
         AST_Node.DEFMETHOD("evaluate", function(compressor){
             if (!compressor.option("evaluate")) return [ this ];
             try {
-                var val = this._eval(), ast = make_node_from_constant(compressor, val, this);
+                var val = this._eval(compressor);
+                var ast = val instanceof AST_Binary ? val : make_node_from_constant(compressor, val, this);
                 return [ best_of(ast, this), val ];
             } catch(ex) {
                 if (ex !== def) throw ex;
@@ -653,8 +654,8 @@ merge(Compressor.prototype, {
             // places too. :-( Wish JS had multiple inheritance.
             throw def;
         });
-        function ev(node) {
-            return node._eval();
+        function ev(node, compressor) {
+            return node._eval(compressor);
         };
         def(AST_Node, function(){
             throw def;          // not constant
@@ -662,69 +663,87 @@ merge(Compressor.prototype, {
         def(AST_Constant, function(){
             return this.getValue();
         });
-        def(AST_UnaryPrefix, function(){
+        def(AST_UnaryPrefix, function(compressor){
             var e = this.expression;
             switch (this.operator) {
-              case "!": return !ev(e);
+              case "!": return !ev(e, compressor);
               case "typeof":
                 // Function would be evaluated to an array and so typeof would
                 // incorrectly return 'object'. Hence making is a special case.
                 if (e instanceof AST_Function) return typeof function(){};
 
-                e = ev(e);
+                e = ev(e, compressor);
 
                 // typeof <RegExp> returns "object" or "function" on different platforms
                 // so cannot evaluate reliably
                 if (e instanceof RegExp) throw def;
 
                 return typeof e;
-              case "void": return void ev(e);
-              case "~": return ~ev(e);
+              case "void": return void ev(e, compressor);
+              case "~": return ~ev(e, compressor);
               case "-":
-                e = ev(e);
+                e = ev(e, compressor);
                 if (e === 0) throw def;
                 return -e;
-              case "+": return +ev(e);
+              case "+": return +ev(e, compressor);
             }
             throw def;
         });
-        def(AST_Binary, function(){
+        def(AST_Binary, function(c){
             var left = this.left, right = this.right;
             switch (this.operator) {
-              case "&&"         : return ev(left) &&         ev(right);
-              case "||"         : return ev(left) ||         ev(right);
-              case "|"          : return ev(left) |          ev(right);
-              case "&"          : return ev(left) &          ev(right);
-              case "^"          : return ev(left) ^          ev(right);
-              case "+"          : return ev(left) +          ev(right);
-              case "*"          : return ev(left) *          ev(right);
-              case "/"          : return ev(left) /          ev(right);
-              case "%"          : return ev(left) %          ev(right);
-              case "-"          : return ev(left) -          ev(right);
-              case "<<"         : return ev(left) <<         ev(right);
-              case ">>"         : return ev(left) >>         ev(right);
-              case ">>>"        : return ev(left) >>>        ev(right);
-              case "=="         : return ev(left) ==         ev(right);
-              case "==="        : return ev(left) ===        ev(right);
-              case "!="         : return ev(left) !=         ev(right);
-              case "!=="        : return ev(left) !==        ev(right);
-              case "<"          : return ev(left) <          ev(right);
-              case "<="         : return ev(left) <=         ev(right);
-              case ">"          : return ev(left) >          ev(right);
-              case ">="         : return ev(left) >=         ev(right);
-              case "in"         : return ev(left) in         ev(right);
-              case "instanceof" : return ev(left) instanceof ev(right);
+              case "&&"         : return ev(left, c) &&         ev(right, c);
+              case "||"         : return ev(left, c) ||         ev(right, c);
+              case "|"          : return ev(left, c) |          ev(right, c);
+              case "&"          : return ev(left, c) &          ev(right, c);
+              case "^"          : return ev(left, c) ^          ev(right, c);
+              case "+"          :
+                // handle concatenating strings even if the left part cannot
+                // be evaluated, e.g. (variable + "str") + "str"
+                if (!(c instanceof Compressor)) {
+                    throw new Error("Compressor must be passed!!");
+                }
+                if (left instanceof AST_Binary && left.operator == "+" && left.is_string(c)) {
+                    return make_node(AST_Binary, this, {
+                        operator: "+",
+                        left: left.left,
+                        right: make_node(AST_String, null, {
+                            value : "" + ev(left.right, c) + ev(right, c),
+                            start : left.right.start,
+                            end   : right.end
+                        })
+                    });
+                } else {
+                    return ev(left, c) + ev(right, c);
+                }
+              case "*"          : return ev(left, c) *          ev(right, c);
+              case "/"          : return ev(left, c) /          ev(right, c);
+              case "%"          : return ev(left, c) %          ev(right, c);
+              case "-"          : return ev(left, c) -          ev(right, c);
+              case "<<"         : return ev(left, c) <<         ev(right, c);
+              case ">>"         : return ev(left, c) >>         ev(right, c);
+              case ">>>"        : return ev(left, c) >>>        ev(right, c);
+              case "=="         : return ev(left, c) ==         ev(right, c);
+              case "==="        : return ev(left, c) ===        ev(right, c);
+              case "!="         : return ev(left, c) !=         ev(right, c);
+              case "!=="        : return ev(left, c) !==        ev(right, c);
+              case "<"          : return ev(left, c) <          ev(right, c);
+              case "<="         : return ev(left, c) <=         ev(right, c);
+              case ">"          : return ev(left, c) >          ev(right, c);
+              case ">="         : return ev(left, c) >=         ev(right, c);
+              case "in"         : return ev(left, c) in         ev(right, c);
+              case "instanceof" : return ev(left, c) instanceof ev(right, c);
             }
             throw def;
         });
-        def(AST_Conditional, function(){
-            return ev(this.condition)
-                ? ev(this.consequent)
-                : ev(this.alternative);
+        def(AST_Conditional, function(compressor){
+            return ev(this.condition, compressor)
+                ? ev(this.consequent, compressor)
+                : ev(this.alternative, compressor);
         });
-        def(AST_SymbolRef, function(){
+        def(AST_SymbolRef, function(compressor){
             var d = this.definition();
-            if (d && d.constant && d.init) return ev(d.init);
+            if (d && d.constant && d.init) return ev(d.init, compressor);
             throw def;
         });
     })(function(node, func){
diff --git a/test/compress/issue-126.js b/test/compress/issue-126.js
new file mode 100644 (file)
index 0000000..1e65494
--- /dev/null
@@ -0,0 +1,22 @@
+concatenate_rhs_strings: {
+    options = {
+        evaluate: true,
+        unsafe: true,
+    }
+    input: {
+        foo(bar() + 123 + "Hello" + "World");
+        foo(bar() + (123 + "Hello") + "World");
+        foo((bar() + 123) + "Hello" + "World");
+        foo(bar() + 123 + "Hello" + "World" + ("Foo" + "Bar"));
+        foo("Foo" + "Bar" + bar() + 123 + "Hello" + "World" + ("Foo" + "Bar"));
+        foo("Hello" + bar() + 123 + "World");
+    }
+    expect: {
+        foo(bar() + 123 + "HelloWorld");
+        foo(bar() + "123HelloWorld");
+        foo((bar() + 123) + "HelloWorld");
+        foo(bar() + 123 + "HelloWorldFooBar");
+        foo("FooBar" + bar() + "123HelloWorldFooBar");
+        foo("Hello" + bar() + "123World");
+    }
+}