Skip to content

Commit

Permalink
fix corner case in string concatenations (#3692)
Browse files Browse the repository at this point in the history
- migrate de-facto compression to `conditionals` & `strings`

fixes #3689
  • Loading branch information
alexlamsl committed Jan 27, 2020
1 parent 0dcedad commit e9e76dc
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 98 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -736,6 +736,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
example: `/*@__PURE__*/foo();`

- `strings` (default: `true`) -- compact string concatenations.

- `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches

- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or
Expand Down
54 changes: 33 additions & 21 deletions lib/compress.js
Expand Up @@ -83,6 +83,7 @@ function Compressor(options, false_by_default) {
reduce_vars : !false_by_default,
sequences : !false_by_default,
side_effects : !false_by_default,
strings : !false_by_default,
switches : !false_by_default,
top_retain : null,
toplevel : !!(options && options["top_retain"]),
Expand Down Expand Up @@ -6188,6 +6189,18 @@ merge(Compressor.prototype, {
self.right = tmp;
}
}
function swap_chain() {
var rhs = self.right;
self.left = make_node(AST_Binary, self, {
operator: self.operator,
left: self.left,
right: rhs.left,
start: self.left.start,
end: rhs.left.end
});
self.right = rhs.right;
self.left = self.left.transform(compressor);
}
if (commutativeOperators[self.operator]
&& self.right.is_constant()
&& !self.left.is_constant()
Expand Down Expand Up @@ -6338,17 +6351,28 @@ merge(Compressor.prototype, {
case ">=": reverse("<="); break;
}
}
if (self.operator == "+") {
// x && (y && z) => x && y && z
// x || (y || z) => x || y || z
if (compressor.option("conditionals")
&& lazy_op[self.operator]
&& self.right instanceof AST_Binary
&& self.operator == self.right.operator) {
swap_chain();
}
if (compressor.option("strings") && self.operator == "+") {
// "foo" + 42 + "" => "foo" + 42
if (self.right instanceof AST_String
&& self.right.value == ""
&& self.left.is_string(compressor)) {
return self.left.optimize(compressor);
}
// "" + ("foo" + 42) => "foo" + 42
if (self.left instanceof AST_String
&& self.left.value == ""
&& self.right.is_string(compressor)) {
return self.right.optimize(compressor);
}
// "" + 42 + "foo" => 42 + "foo"
if (self.left instanceof AST_Binary
&& self.left.operator == "+"
&& self.left.left instanceof AST_String
Expand All @@ -6357,6 +6381,14 @@ merge(Compressor.prototype, {
self.left = self.left.right;
return self.optimize(compressor);
}
// "x" + (y + "z") => "x" + y + "z"
// x + ("y" + z) => x + "y" + z
if (self.right instanceof AST_Binary
&& self.operator == self.right.operator
&& (self.left.is_string(compressor) && self.right.is_string(compressor)
|| self.right.left.is_string(compressor) && !self.right.right.has_side_effects(compressor))) {
swap_chain();
}
}
if (compressor.option("evaluate")) {
var associative = true;
Expand Down Expand Up @@ -6727,26 +6759,6 @@ merge(Compressor.prototype, {
return node.optimize(compressor);
}
}
// x && (y && z) => x && y && z
// x || (y || z) => x || y || z
// x + ("y" + z) => x + "y" + z
// "x" + (y + "z") => "x" + y + "z"
if (self.right instanceof AST_Binary
&& self.right.operator == self.operator
&& (lazy_op[self.operator]
|| (self.operator == "+"
&& (self.right.left.is_string(compressor)
|| (self.left.is_string(compressor)
&& self.right.right.is_string(compressor))))))
{
self.left = make_node(AST_Binary, self.left, {
operator : self.operator,
left : self.left,
right : self.right.left
});
self.right = self.right.right;
return self.transform(compressor);
}
return try_evaluate(compressor, self);

function align(ref, op) {
Expand Down
5 changes: 5 additions & 0 deletions test/compress/arrays.js
Expand Up @@ -16,6 +16,7 @@ holes_and_undefined: {
constant_join: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
Expand Down Expand Up @@ -65,6 +66,7 @@ constant_join: {
constant_join_2: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
Expand Down Expand Up @@ -94,9 +96,11 @@ constant_join_2: {
constant_join_3: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
var foo, bar, baz;
var a = [ null ].join();
var b = [ , ].join();
var c = [ , 1, , 3 ].join();
Expand All @@ -111,6 +115,7 @@ constant_join_3: {
var l = [ foo, bar + "baz" ].join("");
}
expect: {
var foo, bar, baz;
var a = "";
var b = "";
var c = ",1,,3";
Expand Down
43 changes: 38 additions & 5 deletions test/compress/concat-strings.js
Expand Up @@ -26,7 +26,9 @@ concat_1: {
}

concat_2: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + (2 + 3),
Expand Down Expand Up @@ -55,7 +57,9 @@ concat_2: {
}

concat_3: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + 2 + (3 + 4 + 5),
Expand Down Expand Up @@ -84,7 +88,9 @@ concat_3: {
}

concat_4: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + "2" + (3 + 4 + 5),
Expand Down Expand Up @@ -113,7 +119,9 @@ concat_4: {
}

concat_5: {
options = {}
options = {
strings: true,
}
input: {
console.log(
"1" + 2 + (3 + 4 + 5),
Expand Down Expand Up @@ -142,7 +150,9 @@ concat_5: {
}

concat_6: {
options = {}
options = {
strings: true,
}
input: {
console.log(
"1" + "2" + (3 + 4 + 5),
Expand Down Expand Up @@ -171,6 +181,9 @@ concat_6: {
}

concat_7: {
options = {
strings: true,
}
input: {
console.log(
"" + 1,
Expand All @@ -197,6 +210,9 @@ concat_7: {
}

concat_8: {
options = {
strings: true,
}
input: {
console.log(
1 + "",
Expand All @@ -221,3 +237,20 @@ concat_8: {
}
expect_stdout: true
}

issue_3689: {
options = {
strings: true,
}
input: {
console.log(function(a) {
return a + ("" + (a[0] = 0));
}([]));
}
expect: {
console.log(function(a) {
return a + ("" + (a[0] = 0));
}([]));
}
expect_stdout: "00"
}
1 change: 1 addition & 0 deletions test/compress/dead-code.js
Expand Up @@ -830,6 +830,7 @@ issue_3552: {
unreachable_assign: {
options = {
dead_code: true,
strings: true,
}
input: {
console.log(A = "P" + (A = "A" + (B = "S" + (A = B = "S"))), A, B);
Expand Down

0 comments on commit e9e76dc

Please sign in to comment.