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'
This commit is contained in:
Dan Wolff
2013-09-19 10:58:50 +02:00
committed by Mihai Bazon
parent 83ba338bd0
commit 8d14efe818
2 changed files with 81 additions and 40 deletions

View File

@@ -636,7 +636,8 @@ merge(Compressor.prototype, {
AST_Node.DEFMETHOD("evaluate", function(compressor){ AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return [ this ]; if (!compressor.option("evaluate")) return [ this ];
try { 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 ]; return [ best_of(ast, this), val ];
} catch(ex) { } catch(ex) {
if (ex !== def) throw ex; if (ex !== def) throw ex;
@@ -653,8 +654,8 @@ merge(Compressor.prototype, {
// places too. :-( Wish JS had multiple inheritance. // places too. :-( Wish JS had multiple inheritance.
throw def; throw def;
}); });
function ev(node) { function ev(node, compressor) {
return node._eval(); return node._eval(compressor);
}; };
def(AST_Node, function(){ def(AST_Node, function(){
throw def; // not constant throw def; // not constant
@@ -662,69 +663,87 @@ merge(Compressor.prototype, {
def(AST_Constant, function(){ def(AST_Constant, function(){
return this.getValue(); return this.getValue();
}); });
def(AST_UnaryPrefix, function(){ def(AST_UnaryPrefix, function(compressor){
var e = this.expression; var e = this.expression;
switch (this.operator) { switch (this.operator) {
case "!": return !ev(e); case "!": return !ev(e, compressor);
case "typeof": case "typeof":
// Function would be evaluated to an array and so typeof would // Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case. // incorrectly return 'object'. Hence making is a special case.
if (e instanceof AST_Function) return typeof function(){}; if (e instanceof AST_Function) return typeof function(){};
e = ev(e); e = ev(e, compressor);
// typeof <RegExp> returns "object" or "function" on different platforms // typeof <RegExp> returns "object" or "function" on different platforms
// so cannot evaluate reliably // so cannot evaluate reliably
if (e instanceof RegExp) throw def; if (e instanceof RegExp) throw def;
return typeof e; return typeof e;
case "void": return void ev(e); case "void": return void ev(e, compressor);
case "~": return ~ev(e); case "~": return ~ev(e, compressor);
case "-": case "-":
e = ev(e); e = ev(e, compressor);
if (e === 0) throw def; if (e === 0) throw def;
return -e; return -e;
case "+": return +ev(e); case "+": return +ev(e, compressor);
} }
throw def; throw def;
}); });
def(AST_Binary, function(){ def(AST_Binary, function(c){
var left = this.left, right = this.right; var left = this.left, right = this.right;
switch (this.operator) { switch (this.operator) {
case "&&" : return ev(left) && ev(right); case "&&" : return ev(left, c) && ev(right, c);
case "||" : return ev(left) || ev(right); case "||" : return ev(left, c) || ev(right, c);
case "|" : return ev(left) | ev(right); case "|" : return ev(left, c) | ev(right, c);
case "&" : return ev(left) & ev(right); case "&" : return ev(left, c) & ev(right, c);
case "^" : return ev(left) ^ ev(right); case "^" : return ev(left, c) ^ ev(right, c);
case "+" : return ev(left) + ev(right); case "+" :
case "*" : return ev(left) * ev(right); // handle concatenating strings even if the left part cannot
case "/" : return ev(left) / ev(right); // be evaluated, e.g. (variable + "str") + "str"
case "%" : return ev(left) % ev(right); if (!(c instanceof Compressor)) {
case "-" : return ev(left) - ev(right); throw new Error("Compressor must be passed!!");
case "<<" : return ev(left) << ev(right); }
case ">>" : return ev(left) >> ev(right); if (left instanceof AST_Binary && left.operator == "+" && left.is_string(c)) {
case ">>>" : return ev(left) >>> ev(right); return make_node(AST_Binary, this, {
case "==" : return ev(left) == ev(right); operator: "+",
case "===" : return ev(left) === ev(right); left: left.left,
case "!=" : return ev(left) != ev(right); right: make_node(AST_String, null, {
case "!==" : return ev(left) !== ev(right); value : "" + ev(left.right, c) + ev(right, c),
case "<" : return ev(left) < ev(right); start : left.right.start,
case "<=" : return ev(left) <= ev(right); end : right.end
case ">" : return ev(left) > ev(right); })
case ">=" : return ev(left) >= ev(right); });
case "in" : return ev(left) in ev(right); } else {
case "instanceof" : return ev(left) instanceof ev(right); 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; throw def;
}); });
def(AST_Conditional, function(){ def(AST_Conditional, function(compressor){
return ev(this.condition) return ev(this.condition, compressor)
? ev(this.consequent) ? ev(this.consequent, compressor)
: ev(this.alternative); : ev(this.alternative, compressor);
}); });
def(AST_SymbolRef, function(){ def(AST_SymbolRef, function(compressor){
var d = this.definition(); 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; throw def;
}); });
})(function(node, func){ })(function(node, func){

View File

@@ -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");
}
}