More dirty handling of [ ... ].join() in unsafe mode

Close #300
This commit is contained in:
Mihai Bazon
2013-09-22 13:12:34 +03:00
parent e8158279ff
commit 583fac0a0f
2 changed files with 99 additions and 14 deletions

View File

@@ -630,15 +630,20 @@ merge(Compressor.prototype, {
// elements. If the node has been successfully reduced to a // elements. If the node has been successfully reduced to a
// constant, then the second element tells us the value; // constant, then the second element tells us the value;
// otherwise the second element is missing. The first element // otherwise the second element is missing. The first element
// of the array is always an AST_Node descendant; when // of the array is always an AST_Node descendant; if
// evaluation was successful it's a node that represents the // evaluation was successful it's a node that represents the
// constant; otherwise it's the original node. // constant; otherwise it's the original or a replacement node.
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(compressor); var val = this._eval(compressor);
var ast = val instanceof AST_Binary ? val : make_node_from_constant(compressor, val, this); if (val instanceof AST_Node) {
return [ best_of(ast, this), val ]; // we didn't really reduce it to a constant, so
// the return array should contain only one
// node; but perhaps it's a better one.
return [ best_of(val, this) ];
}
return [ best_of(make_node_from_constant(compressor, val, this), this), val ];
} catch(ex) { } catch(ex) {
if (ex !== def) throw ex; if (ex !== def) throw ex;
return [ this ]; return [ this ];
@@ -750,10 +755,56 @@ merge(Compressor.prototype, {
if (this.expression instanceof AST_Dot if (this.expression instanceof AST_Dot
&& this.expression.expression instanceof AST_Array && this.expression.expression instanceof AST_Array
&& this.expression.property == "join") { && this.expression.property == "join") {
var x = this.expression.expression.elements.map(function(el){ var separator = this.args.length == 0 ? "," : ev(this.args[0], compressor);
return ev(el, compressor); if (separator instanceof AST_Node) throw def; // not a constant
var elements = this.expression.expression.elements.map(function(el){
return el.evaluate(compressor); // doesn't throw.
}); });
return x.join(ev(this.args[0], compressor)); elements = elements.reduce(function(a, el){
if (a.length == 0 || el.length == 1) {
a.push(el);
} else {
var last = a[a.length - 1];
if (last.length == 2) {
// it's a constant
var val = "" + last[1] + separator + el[1];
a[a.length - 1] = [
make_node_from_constant(compressor, val, last[0]),
val
];
} else {
a.push(el);
}
}
return a;
}, []);
if (elements.length == 0) return "";
if (elements.length == 1) return elements[0][0];
if (separator == "") {
var first;
if (elements[0][0] instanceof AST_String
|| elements[1][0] instanceof AST_String) {
first = elements.shift()[0];
} else {
first = make_node(AST_String, this, { value: "" });
}
return elements.reduce(function(prev, el){
return make_node(AST_Binary, el[0], {
operator : "+",
left : prev,
right : el[0],
});
}, first);
}
// need this awkward cloning to not affect original element
// best_of will decide which one to get through.
var node = this.clone();
node.expression = node.expression.clone();
node.expression.expression = node.expression.expression.clone();
node.expression.expression.elements = elements.map(function(el){
return el[0];
});
return node;
} }
} }
throw def; throw def;
@@ -1918,11 +1969,6 @@ merge(Compressor.prototype, {
} }
break; break;
} }
var exp = self.evaluate(compressor);
if (exp.length > 1) {
if (best_of(exp[0], self) !== self)
return exp[0];
}
if (compressor.option("comparisons")) { if (compressor.option("comparisons")) {
if (!(compressor.parent() instanceof AST_Binary) if (!(compressor.parent() instanceof AST_Binary)
|| compressor.parent() instanceof AST_Assign) { || compressor.parent() instanceof AST_Assign) {
@@ -1942,7 +1988,7 @@ merge(Compressor.prototype, {
&& self.left.operator == "+" && self.left.is_string(compressor)) { && self.left.operator == "+" && self.left.is_string(compressor)) {
return self.left; return self.left;
} }
return self; return self.evaluate(compressor)[0];
}); });
OPT(AST_SymbolRef, function(self, compressor){ OPT(AST_SymbolRef, function(self, compressor){

View File

@@ -20,16 +20,55 @@ constant_join: {
}; };
input: { input: {
var a = [ "foo", "bar", "baz" ].join(""); var a = [ "foo", "bar", "baz" ].join("");
var a1 = [ "foo", "bar", "baz" ].join();
var b = [ "foo", 1, 2, 3, "bar" ].join(""); var b = [ "foo", 1, 2, 3, "bar" ].join("");
var c = [ boo(), "foo", 1, 2, 3, "bar", bar() ].join(""); var c = [ boo(), "foo", 1, 2, 3, "bar", bar() ].join("");
var c1 = [ boo(), bar(), "foo", 1, 2, 3, "bar", bar() ].join("");
var c2 = [ 1, 2, "foo", "bar", baz() ].join("");
var d = [ "foo", 1 + 2 + "bar", "baz" ].join("-"); var d = [ "foo", 1 + 2 + "bar", "baz" ].join("-");
var e = [].join(foo + bar); var e = [].join(foo + bar);
var f = [].join("");
var g = [].join("foo");
} }
expect: { expect: {
var a = "foobarbaz"; var a = "foobarbaz";
var a1 = "foo,bar,baz";
var b = "foo123bar"; var b = "foo123bar";
var c = [ boo(), "foo", 1, 2, 3, "bar", bar() ].join(""); // we could still shorten this one, but oh well. var c = boo() + "foo123bar" + bar();
var c1 = "" + boo() + bar() + "foo123bar" + bar();
var c2 = "12foobar" + baz();
var d = "foo-3bar-baz"; var d = "foo-3bar-baz";
var e = [].join(foo + bar); var e = [].join(foo + bar);
var f = "";
var g = "";
}
}
constant_join_2: {
options = {
unsafe : true,
evaluate : true
};
input: {
var a = [ "foo", "bar", boo(), "baz", "x", "y" ].join("");
var b = [ "foo", "bar", boo(), "baz", "x", "y" ].join("-");
var c = [ "foo", "bar", boo(), "baz", "x", "y" ].join("really-long-separator");
var d = [ "foo", "bar", boo(),
[ "foo", 1, 2, 3, "bar" ].join("+"),
"baz", "x", "y" ].join("-");
var e = [ "foo", "bar", boo(),
[ "foo", 1, 2, 3, "bar" ].join("+"),
"baz", "x", "y" ].join("really-long-separator");
var f = [ "str", "str" + variable, "foo", "bar", "moo" + foo ].join("");
}
expect: {
var a = "foobar" + boo() + "bazxy";
var b = [ "foo-bar", boo(), "baz-x-y" ].join("-");
var c = [ "foo", "bar", boo(), "baz", "x", "y" ].join("really-long-separator");
var d = [ "foo-bar", boo(), "foo+1+2+3+bar-baz-x-y" ].join("-");
var e = [ "foo", "bar", boo(),
"foo+1+2+3+bar",
"baz", "x", "y" ].join("really-long-separator");
var f = "str" + ("str" + variable) + "foobar" + ("moo" + foo);
} }
} }