diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2020-01-28 07:33:11 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-28 07:33:11 +0800 |
commit | e9e76dcf040d5076b6046d7273c3509df106fb84 (patch) | |
tree | afe9ded11df559969c75abcb106f6bc040f3a306 | |
parent | 0dcedad2d5a6b670ecd5aef3cf18d5e511af6e91 (diff) | |
download | tracifyjs-e9e76dcf040d5076b6046d7273c3509df106fb84.tar.gz tracifyjs-e9e76dcf040d5076b6046d7273c3509df106fb84.zip |
fix corner case in string concatenations (#3692)
- migrate de-facto compression to `conditionals` & `strings`
fixes #3689
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | lib/compress.js | 54 | ||||
-rw-r--r-- | test/compress/arrays.js | 5 | ||||
-rw-r--r-- | test/compress/concat-strings.js | 43 | ||||
-rw-r--r-- | test/compress/dead-code.js | 1 | ||||
-rw-r--r-- | test/compress/issue-269.js | 157 |
6 files changed, 164 insertions, 98 deletions
@@ -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 diff --git a/lib/compress.js b/lib/compress.js index 4e967aef..e14d9d06 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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"]), @@ -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() @@ -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 @@ -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; @@ -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) { diff --git a/test/compress/arrays.js b/test/compress/arrays.js index 497e96c4..9569c4fa 100644 --- a/test/compress/arrays.js +++ b/test/compress/arrays.js @@ -16,6 +16,7 @@ holes_and_undefined: { constant_join: { options = { evaluate: true, + strings: true, unsafe: true, } input: { @@ -65,6 +66,7 @@ constant_join: { constant_join_2: { options = { evaluate: true, + strings: true, unsafe: true, } input: { @@ -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(); @@ -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"; diff --git a/test/compress/concat-strings.js b/test/compress/concat-strings.js index a9957d3d..5e8c512a 100644 --- a/test/compress/concat-strings.js +++ b/test/compress/concat-strings.js @@ -26,7 +26,9 @@ concat_1: { } concat_2: { - options = {} + options = { + strings: true, + } input: { console.log( 1 + (2 + 3), @@ -55,7 +57,9 @@ concat_2: { } concat_3: { - options = {} + options = { + strings: true, + } input: { console.log( 1 + 2 + (3 + 4 + 5), @@ -84,7 +88,9 @@ concat_3: { } concat_4: { - options = {} + options = { + strings: true, + } input: { console.log( 1 + "2" + (3 + 4 + 5), @@ -113,7 +119,9 @@ concat_4: { } concat_5: { - options = {} + options = { + strings: true, + } input: { console.log( "1" + 2 + (3 + 4 + 5), @@ -142,7 +150,9 @@ concat_5: { } concat_6: { - options = {} + options = { + strings: true, + } input: { console.log( "1" + "2" + (3 + 4 + 5), @@ -171,6 +181,9 @@ concat_6: { } concat_7: { + options = { + strings: true, + } input: { console.log( "" + 1, @@ -197,6 +210,9 @@ concat_7: { } concat_8: { + options = { + strings: true, + } input: { console.log( 1 + "", @@ -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" +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 024d710b..344e1ff9 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -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); diff --git a/test/compress/issue-269.js b/test/compress/issue-269.js index 20b11b0a..2adc5017 100644 --- a/test/compress/issue-269.js +++ b/test/compress/issue-269.js @@ -1,98 +1,111 @@ issue_269_1: { - options = { + options = { unsafe: true, } - input: { - f( - String(x), - Number(x), - Boolean(x), + input: { + var x = {}; + console.log( + String(x), + Number(x), + Boolean(x), - String(), - Number(), - Boolean() - ); - } - expect: { - f( - x + '', +x, !!x, - '', 0, false - ); - } + String(), + Number(), + Boolean() + ); + } + expect: { + var x = {}; + console.log( + x + "", +x, !!x, + "", 0, false + ); + } + expect_stdout: true } issue_269_dangers: { - options = { + options = { unsafe: true, } - input: { - f( - String(x, x), - Number(x, x), - Boolean(x, x) - ); - } - expect: { - f(String(x, x), Number(x, x), Boolean(x, x)); - } + input: { + var x = {}; + console.log( + String(x, x), + Number(x, x), + Boolean(x, x) + ); + } + expect: { + var x = {}; + console.log(String(x, x), Number(x, x), Boolean(x, x)); + } + expect_stdout: true } issue_269_in_scope: { - options = { + options = { unsafe: true, } - input: { - var String, Number, Boolean; - f( - String(x), - Number(x, x), - Boolean(x) - ); - } - expect: { - var String, Number, Boolean; - f(String(x), Number(x, x), Boolean(x)); - } + input: { + var String, Number, Boolean; + var x = {}; + console.log( + String(x), + Number(x, x), + Boolean(x) + ); + } + expect: { + var String, Number, Boolean; + var x = {}; + console.log(String(x), Number(x, x), Boolean(x)); + } + expect_stdout: true } strings_concat: { - options = { + options = { + strings: true, unsafe: true, } - input: { - f( - String(x + 'str'), - String('str' + x) - ); - } - expect: { - f( - x + 'str', - 'str' + x - ); - } + input: { + var x = {}; + console.log( + String(x + "str"), + String("str" + x) + ); + } + expect: { + var x = {}; + console.log( + x + "str", + "str" + x + ); + } + expect_stdout: true } regexp: { - options = { + options = { evaluate: true, unsafe: true, } - input: { - RegExp("foo"); - RegExp("bar", "ig"); - RegExp(foo); - RegExp("bar", ig); - RegExp("should", "fail"); - } - expect: { - /foo/; - /bar/ig; - RegExp(foo); - RegExp("bar", ig); - RegExp("should", "fail"); - } - expect_warnings: [ - 'WARN: Error converting RegExp("should","fail") [test/compress/issue-269.js:5,2]', - ] + input: { + RegExp("foo"); + RegExp("bar", "ig"); + RegExp(foo); + RegExp("bar", ig); + RegExp("should", "fail"); + } + expect: { + /foo/; + /bar/ig; + RegExp(foo); + RegExp("bar", ig); + RegExp("should", "fail"); + } + expect_warnings: [ + 'WARN: Error converting RegExp("should","fail") [test/compress/issue-269.js:5,8]', + ] } |