aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2020-12-07 22:51:20 +0000
committerGitHub <noreply@github.com>2020-12-08 06:51:20 +0800
commitc587d7917d445e97ed8580251547870256237b63 (patch)
tree689b1c396045071e8e94e1ef3b68a798b7437551
parent336336f53f7f1ba161abc7834da190504afeb27b (diff)
downloadtracifyjs-c587d7917d445e97ed8580251547870256237b63.tar.gz
tracifyjs-c587d7917d445e97ed8580251547870256237b63.zip
introduce `spread` (#4346)
fixes #4345
-rw-r--r--README.md2
-rw-r--r--lib/compress.js87
-rw-r--r--test/compress/objects.js10
-rw-r--r--test/compress/spread.js44
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"
+}