From c587d7917d445e97ed8580251547870256237b63 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 7 Dec 2020 22:51:20 +0000 Subject: [PATCH] introduce `spread` (#4346) fixes #4345 --- README.md | 2 + lib/compress.js | 87 +++++++++++++++++++++++----------------- test/compress/objects.js | 10 ++--- test/compress/spread.js | 44 +++++++++++++++++--- 4 files changed, 95 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index bda1f02b..5718014d 100644 --- a/README.md +++ b/README.md @@ -751,6 +751,8 @@ to be `false` and all symbol names will be omitted. annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For example: `/*@__PURE__*/foo();` +- `spread` (default: `true`) -- flatten spread expressions. + - `strings` (default: `true`) -- compact string concatenations. - `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches diff --git a/lib/compress.js b/lib/compress.js index 21ecc5e5..1b5b8be6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -84,6 +84,7 @@ function Compressor(options, false_by_default) { reduce_vars : !false_by_default, sequences : !false_by_default, side_effects : !false_by_default, + spread : !false_by_default, strings : !false_by_default, switches : !false_by_default, top_retain : null, @@ -9724,27 +9725,10 @@ merge(Compressor.prototype, { }); }); - function is_integer(key) { - return /^[0-9]+$/.test(key); - } - OPT(AST_Spread, function(self, compressor) { - if (compressor.option("properties")) { - var exp = self.expression; - if (compressor.parent() instanceof AST_Object) { - if (exp instanceof AST_Object && all(exp.properties, function(node) { - return node instanceof AST_ObjectKeyVal; - })) return List.splice(exp.properties.map(function(node) { - var key = node.key; - if (!(key instanceof AST_Node) && is_integer(key)) { - node = node.clone(); - node.key = make_node(AST_Number, node, { - value: +key - }); - } - return node; - })); - } else if (exp instanceof AST_Array) return List.splice(exp.elements.map(function(node) { + var exp = self.expression; + if (compressor.option("spread") && exp instanceof AST_Array && !(compressor.parent() instanceof AST_Object)) { + return List.splice(exp.elements.map(function(node) { return node instanceof AST_Hole ? make_node(AST_Undefined, node).optimize(compressor) : node; })); } @@ -10014,40 +9998,69 @@ merge(Compressor.prototype, { }); OPT(AST_Object, function(self, compressor) { - if (!compressor.option("objects") || compressor.has_directive("use strict")) return self; - for (var i = self.properties.length; --i >= 0;) { - var prop = self.properties[i]; - var key = prop.key; - if (key instanceof AST_Node) key = key.evaluate(compressor); - if (is_integer(key)) break; - if (key !== prop.key) prop.key = "" + key; - } + if (!compressor.option("objects")) return self; + var changed = false; + var computed_int = false; + var has_computed = false; + var keep_duplicate = compressor.has_directive("use strict"); var keys = new Dictionary(); var values = []; self.properties.forEach(function(prop) { - if (prop instanceof AST_ObjectKeyVal && typeof prop.key == "string") { - if (prop.value.has_side_effects(compressor)) flush(); - keys.add(prop.key, prop.value); + if (!(prop instanceof AST_Spread)) return process(prop); + var exp = prop.expression; + if (exp instanceof AST_Object && all(exp.properties, function(node) { + return node instanceof AST_ObjectKeyVal; + })) { + changed = true; + has_computed = true; + exp.properties.forEach(process); } else { flush(); values.push(prop); } }); flush(); - if (self.properties.length != values.length) return make_node(AST_Object, self, { + return changed ? make_node(AST_Object, self, { properties: values - }); - return self; + }) : self; function flush() { - keys.each(function(expressions, key) { + keys.each(function(props) { + if (props.length == 1) return values.push(props[0]); + changed = true; + var tail = keep_duplicate && props.pop(); values.push(make_node(AST_ObjectKeyVal, self, { - key: key, - value: make_sequence(self, expressions) + key: props[0].key, + value: make_sequence(self, props.map(function(prop) { + return prop.value; + })) })); + if (tail) values.push(tail); }); keys = new Dictionary(); } + + function process(prop) { + var key = prop.key; + if (key instanceof AST_Node) { + has_computed = true; + key = key.evaluate(compressor); + if (key !== prop.key) key = prop.key = "" + key; + } + if (prop instanceof AST_ObjectKeyVal && typeof key == "string") { + if (prop.value.has_side_effects(compressor)) flush(); + keys.add(key, prop); + } else { + flush(); + values.push(prop); + } + if (has_computed && !computed_int && typeof key == "string" && /^[0-9]+$/.test(key)) { + computed_int = true; + prop.key = make_node(AST_Number, prop, { + value: +key + }); + } + } }); OPT(AST_Return, function(self, compressor) { diff --git a/test/compress/objects.js b/test/compress/objects.js index 51b7bcc4..5d3653d0 100644 --- a/test/compress/objects.js +++ b/test/compress/objects.js @@ -45,8 +45,8 @@ duplicate_key_strict: { "use strict"; var o = { a: 1, - b: 2, a: 3, + b: 2, }; for (var k in o) console.log(k, o[k]); @@ -323,8 +323,8 @@ issue_4269_3: { } expect: { console.log({ - ["foo"]: "bar", - get 42() { + foo: "bar", + get [42]() { return "FAIL"; }, 42: "PASS", @@ -353,8 +353,8 @@ issue_4269_4: { get 42() { return "FAIL"; }, - ["foo"]: "bar", - 42: "PASS", + foo: "bar", + [42]: "PASS", }[42]); } expect_stdout: "PASS" diff --git a/test/compress/spread.js b/test/compress/spread.js index 6763d4f7..aaa8a7f7 100644 --- a/test/compress/spread.js +++ b/test/compress/spread.js @@ -105,8 +105,8 @@ dont_inline: { do_inline: { options = { - properties: true, inline: true, + spread: true, } input: { console.log(function(a) { @@ -144,8 +144,8 @@ drop_empty_call_1: { drop_empty_call_2: { options = { - properties: true, side_effects: true, + spread: true, } input: { (function() {})(...[ console.log("PASS") ]); @@ -159,7 +159,7 @@ drop_empty_call_2: { convert_hole: { options = { - properties: true, + spread: true, } input: { console.log(...[ "PASS", , 42 ]); @@ -295,7 +295,8 @@ keep_getter: { keep_accessor: { options = { - properties: true, + objects: true, + spread: true, } input: { var o = { @@ -372,7 +373,8 @@ unused_var_side_effects: { issue_4329: { options = { - properties: true, + objects: true, + spread: true, } input: { console.log({ @@ -423,7 +425,7 @@ issue_4331: { console.log(b); } expect_stdout: "PASS" - node_version: ">=8" + node_version: ">=6" } issue_4342: { @@ -447,3 +449,33 @@ issue_4342: { expect_stdout: "PASS" node_version: ">=6" } + +issue_4345: { + options = { + objects: true, + spread: true, + } + input: { + console.log({ + ...{ + get 42() { + return "FAIL"; + }, + ...{}, + 42: "PASS", + }, + }[42]); + } + expect: { + console.log({ + ...{ + get 42() { + return "FAIL"; + }, + [42]: "PASS", + }, + }[42]); + } + expect_stdout: "PASS" + node_version: ">=8" +} -- 2.34.1