From: Dan Wolff Date: Thu, 19 Sep 2013 08:58:50 +0000 (+0200) Subject: Concatenate strings also on the right-hand side of an expression that cannot be evalu... X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=8d14efe;p=UglifyJS.git Concatenate strings also on the right-hand side of an expression that cannot be evaluated. Fix #126 E.g. converts: a+'Hello'+'World' to a+'HelloWorld' --- diff --git a/lib/compress.js b/lib/compress.js index 0d2053e7..8bd58bb1 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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 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 index 00000000..1e654941 --- /dev/null +++ b/test/compress/issue-126.js @@ -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"); + } +}