diff options
-rw-r--r-- | lib/compress.js | 308 | ||||
-rw-r--r-- | test/compress/default-values.js | 10 | ||||
-rw-r--r-- | test/compress/destructured.js | 138 | ||||
-rw-r--r-- | test/compress/evaluate.js | 59 | ||||
-rw-r--r-- | test/compress/exports.js | 2 | ||||
-rw-r--r-- | test/compress/rests.js | 94 | ||||
-rw-r--r-- | test/compress/spreads.js | 72 |
7 files changed, 497 insertions, 186 deletions
diff --git a/lib/compress.js b/lib/compress.js index 453396e7..4f9892fe 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -6368,10 +6368,11 @@ merge(Compressor.prototype, { var assign = props.assign.drop_side_effect_free(compressor); if (assign) props.unshift(assign); } - if (parent instanceof AST_Sequence && parent.tail_node() !== node) { - value = value.drop_side_effect_free(compressor); + if (!(parent instanceof AST_Sequence) + || parent.tail_node() === node + || value.has_side_effects(compressor)) { + props.push(value); } - if (value) props.push(value); switch (props.length) { case 0: return List.skip; @@ -6491,18 +6492,19 @@ merge(Compressor.prototype, { if (def.value) def.value = def.value.transform(tt); var value = def.value; if (def.name instanceof AST_Destructured) { - var name = trim_destructured(def.name, value, function(node) { + var trimmed = trim_destructured(def.name, value, function(node) { if (!drop_vars) return node; if (node.definition().id in in_use_ids) return node; if (is_catch(node)) return node; if (is_var && !can_drop_symbol(node)) return node; return null; - }); - if (name) { + }, true); + if (trimmed.name) { + def.name = trimmed.name; + def.value = value = trimmed.value; flush(); - } else { - value = value.drop_side_effect_free(compressor); - if (value) side_effects.push(value); + } else if (trimmed.value) { + side_effects.push(trimmed.value); } return; } @@ -6648,7 +6650,7 @@ merge(Compressor.prototype, { if (side_effects.length > 0) { if (tail.length == 0) { body.push(make_node(AST_SimpleStatement, node, { - body: make_sequence(node, side_effects) + body: make_sequence(node, side_effects), })); } else if (value) { side_effects.push(value); @@ -6656,7 +6658,7 @@ merge(Compressor.prototype, { } else { def.value = make_node(AST_UnaryPrefix, def, { operator: "void", - expression: make_sequence(def, side_effects) + expression: make_sequence(def, side_effects), }); } side_effects = []; @@ -6708,11 +6710,16 @@ merge(Compressor.prototype, { if (node instanceof AST_Assign) { descend(node, tt); if (node.left instanceof AST_Destructured) { - var lhs = trim_destructured(node.left, node.right, function(node) { + var trimmed = trim_destructured(node.left, node.right, function(node) { return node; - }); - if (!lhs) return node.right; - node.left = lhs; + }, node.write_only); + if (!trimmed.name) { + if (trimmed.value) return trimmed.value; + if (parent instanceof AST_Sequence && parent.tail_node() !== node) return List.skip; + return make_node(AST_Number, node, { value: 0 }); + } + node.left = trimmed.name; + node.right = trimmed.value; } return node; } @@ -7023,111 +7030,240 @@ merge(Compressor.prototype, { return node; } - function trim_destructured(node, value, process) { + function trim_destructured(node, value, process, drop) { var trimmer = new TreeTransformer(function(node) { if (node instanceof AST_DefaultValue) { if (compressor.option("default_values") && value && value.is_defined(compressor)) { node = node.name; } else { - return trim_default(trimmer, node); + var trimmed = trim_default(trimmer, node); + if (!trimmed && drop && value) value = value.drop_side_effect_free(compressor); + return trimmed; } } if (node instanceof AST_DestructuredArray) { - var save = value; - if (value instanceof AST_SymbolRef) value = value.fixed_value(); + var save_drop = drop; + var save_value = value; + if (value instanceof AST_SymbolRef) { + drop = false; + value = value.fixed_value(); + } var values = value instanceof AST_Array && value.elements; - var elements = []; + var elements = [], newValues = [], pos = 0; node.elements.forEach(function(element, index) { value = values && values[index]; - if (value instanceof AST_Spread) value = values = null; - if (element instanceof AST_Hole) return; + if (value instanceof AST_Hole) { + value = null; + } else if (value instanceof AST_Spread) { + newValues.length = pos; + fill_holes(save_value, newValues); + [].push.apply(newValues, values.slice(index)); + save_value.elements = newValues; + value = values = false; + } element = element.transform(trimmer); - if (element) elements[index] = element; + if (element) elements[pos] = element; + if (value) newValues[pos] = value; + if (element || value || !drop || !values) pos++; + }); + value = values && make_node(AST_Array, save_value, { + elements: values.slice(node.elements.length), }); if (node.rest) { - if (compressor.option("rests")) { - value = values && make_node(AST_Array, save, { - elements: values.slice(node.elements.length), - }); - node.rest = node.rest.transform(trimmer); - } else { - node.rest = node.rest.transform(tt); - } - } - value = save; - if (node.rest) { - elements.length = node.elements.length; - } else if (values && elements.length == 0) { + var was_drop = drop; + drop = false; + node.rest = node.rest.transform(compressor.option("rests") ? trimmer : tt); + drop = was_drop; + if (node.rest) elements.length = pos; + } + if (drop && value && !node.rest) value = value.drop_side_effect_free(compressor); + if (value instanceof AST_Array) { + value = value.elements; + } else if (value instanceof AST_Sequence) { + value = value.expressions; + } else if (value) { + value = [ value ]; + } + if (value && value.length) { + newValues.length = pos; + [].push.apply(newValues, value); + } + value = save_value; + drop = save_drop; + if (values && value instanceof AST_Array) { + fill_holes(value, newValues); + value.elements = newValues; + } + if (!node.rest && (value instanceof AST_Array + || value && value.is_string(compressor))) switch (elements.length) { + case 0: + if (drop) value = value.drop_side_effect_free(compressor); return null; + case 1: + if (!drop) break; + var sym = elements[0]; + if (!(sym instanceof AST_Symbol)) break; + value = make_node(AST_Sub, node, { + expression: value, + property: make_node(AST_Number, node, { value: 0 }), + }); + return sym; } fill_holes(node, elements); node.elements = elements; return node; } if (node instanceof AST_DestructuredObject) { - var save = value; - if (value instanceof AST_SymbolRef) value = value.fixed_value(); - var values; + var save_drop = drop; + var save_value = value; + if (value instanceof AST_SymbolRef) { + drop = false; + value = value.fixed_value(); + } + var prop_keys, prop_map; if (value instanceof AST_Object) { - values = Object.create(null); - for (var i = 0; i < value.properties.length; i++) { - var prop = value.properties[i]; - if (typeof prop.key != "string") { - values = null; - break; + prop_keys = []; + prop_map = Object.create(null); + value.properties.forEach(function(prop, index) { + if (prop instanceof AST_ObjectSetter) return; + if (prop instanceof AST_Spread) return prop_map = false; + var key = prop.key; + if (key instanceof AST_Node) key = key.evaluate(compressor, true); + if (key instanceof AST_Node) { + prop_map = false; + } else if (prop_map) { + prop_map[key] = prop; } - values[prop.key] = prop.value; - } + prop_keys[index] = key; + }); } + if (node.rest) { + value = false; + node.rest = node.rest.transform(compressor.option("rests") ? trimmer : tt); + } + var can_drop = Object.create(null); + var drop_keys = drop && Object.create(null); var properties = []; - node.properties.forEach(function(prop) { - var retain; - if (prop.key instanceof AST_Node) { - prop.key = prop.key.transform(tt); - value = null; - retain = prop.key.has_side_effects(compressor); + node.properties.map(function(prop) { + var key = prop.key; + if (key instanceof AST_Node) { + prop.key = key = key.transform(tt); + key = key.evaluate(compressor, true); + } + if (key instanceof AST_Node) { + drop_keys = false; } else { - value = values && values[prop.key]; - retain = false; + can_drop[key] = !(key in can_drop); } - if ((retain || node.rest) && is_decl(prop.value)) { - prop.value = prop.value.transform(tt); - properties.push(prop); + return key; + }).forEach(function(key, index) { + var prop = node.properties[index], trimmed; + if (key instanceof AST_Node) { + drop = false; + value = false; + trimmed = prop.value.transform(trimmer) || retain_lhs(prop.value); } else { - var newValue = prop.value.transform(trimmer); - if (!newValue && node.rest) { - if (prop.value instanceof AST_DestructuredArray) { - newValue = make_node(AST_DestructuredArray, prop.value, { elements: [] }); - } else { - newValue = make_node(AST_DestructuredObject, prop.value, { properties: [] }); - } + drop = drop_keys && can_drop[key]; + var mapped = prop_map && prop_map[key]; + if (mapped) { + value = mapped.value; + if (value instanceof AST_Accessor) value = false; + } else { + value = false; } - if (newValue) { - prop.value = newValue; - properties.push(prop); + trimmed = prop.value.transform(trimmer); + if (!trimmed) { + if (node.rest || prop.key instanceof AST_Node) trimmed = retain_lhs(prop.value); + if (drop_keys && !(key in drop_keys)) { + if (mapped) { + drop_keys[key] = mapped; + if (value === null) prop_map[key] = false; + } else { + drop_keys[key] = true; + } + } + } else if (drop_keys) { + drop_keys[key] = false; } + if (value) mapped.value = value; + } + if (trimmed) { + prop.value = trimmed; + properties.push(prop); } }); - if (node.rest) { - if (compressor.option("rests")) { - value = values && make_node(AST_Object, save, { - properties: [], - }); - node.rest = node.rest.transform(trimmer); - } else { - node.rest = node.rest.transform(tt); + value = save_value; + drop = save_drop; + if (drop_keys && prop_keys) value.properties = value.properties.filter(function(prop, index) { + if (prop instanceof AST_ObjectSetter) return false; + if (prop instanceof AST_Spread) return true; + var key = prop_keys[index]; + if (key instanceof AST_Node) return true; + if (key in drop_keys) { + var mapped = drop_keys[key]; + if (!mapped) return true; + if (mapped === prop) return prop_map[key]; + } else if (node.rest) { + return true; } - } - value = save; - if (properties.length == 0 && !node.rest && value && !value.may_throw_on_access(compressor)) { + var trimmed = prop.value.drop_side_effect_free(compressor); + if (!trimmed) { + if (!(prop.key instanceof AST_Node)) return false; + trimmed = make_node(AST_Number, prop, { value: 0 }); + } + prop.value = trimmed; + return true; + }); + if (value && !node.rest) switch (properties.length) { + case 0: + if (value.may_throw_on_access(compressor, true)) break; + if (drop) value = value.drop_side_effect_free(compressor); return null; + case 1: + if (!drop) break; + var prop = properties[0]; + if (prop.key instanceof AST_Node) break; + if (!(prop.value instanceof AST_Symbol)) break; + value = make_node(AST_Sub, node, { + expression: value, + property: make_node_from_constant(prop.key, prop), + }); + return prop.value; } node.properties = properties; return node; } - return process(node); + if (node instanceof AST_Hole) { + node = null; + } else { + node = process(node); + } + if (!node && drop && value) value = value.drop_side_effect_free(compressor); + return node; }); - return node.transform(trimmer); + return { + name: node.transform(trimmer), + value: value, + }; + + function retain_lhs(node) { + if (node instanceof AST_Destructured) { + if (value === null) { + value = make_node(AST_Number, node, { value: 0 }); + } else if (value) { + if (value.may_throw_on_access(compressor, true)) value = make_sequence(node, [ + value, + make_node(AST_Number, node, { value: 0 }), + ]); + } else if (node instanceof AST_DestructuredArray) { + return make_node(AST_DestructuredArray, node, { elements: [] }); + } + return make_node(AST_DestructuredObject, node, { properties: [] }); + } + if (node instanceof AST_DefaultValue) node = node.name; + node.__unused = null; + return node; + } } }); @@ -11831,15 +11967,15 @@ merge(Compressor.prototype, { }); function safe_to_flatten(value, compressor) { - if (value instanceof AST_SymbolRef) { - value = value.fixed_value(); - } if (!value) return false; - if (!(value instanceof AST_Lambda)) return true; var parent = compressor.parent(); if (parent.TYPE != "Call") return true; if (parent.expression !== compressor.self()) return true; - return !value.contains_this(); + if (value instanceof AST_SymbolRef) { + value = value.fixed_value(); + if (!value) return false; + } + return value instanceof AST_Lambda && !value.contains_this(); } OPT(AST_Sub, function(self, compressor) { diff --git a/test/compress/default-values.js b/test/compress/default-values.js index de5374fa..7b67ed17 100644 --- a/test/compress/default-values.js +++ b/test/compress/default-values.js @@ -279,7 +279,7 @@ reduce_array: { console.log(a, b, c); } expect: { - var [ , , c = "baz" ] = [ void 0, null ]; + var [ c = "baz" ] = []; console.log("foo", null, c); } expect_stdout: "foo null baz" @@ -299,7 +299,7 @@ reduce_object: { console.log(a, b, c); } expect: { - var { c = "baz" } = { a: void 0, b: null }; + var { c = "baz" } = {}; console.log("foo", null, c); } expect_stdout: "foo null baz" @@ -719,7 +719,7 @@ unused_value_var_2: { console.log(a); } expect: { - var [ a ] = [ "PASS" ]; + var a = [ "PASS" ][0]; console.log(a); } expect_stdout: "PASS" @@ -1300,7 +1300,7 @@ issue_4468: { expect: { (function() { var { - [console.log("PASS")]: b = 0, + [console.log("PASS")]: b, } = 0; })(); } @@ -1743,7 +1743,7 @@ issue_4854: { }()); } expect: { - console.log(void ([] = "foo")); + console.log(void 0); } expect_stdout: "undefined" node_version: ">=6" diff --git a/test/compress/destructured.js b/test/compress/destructured.js index 140d48e1..7db2d238 100644 --- a/test/compress/destructured.js +++ b/test/compress/destructured.js @@ -1105,7 +1105,7 @@ drop_unused_1: { try { throw 42; } catch (a) { - var [ a ] = []; + var a = [][0]; } } } @@ -1138,6 +1138,142 @@ drop_unused_2: { node_version: ">=6" } +drop_hole: { + options = { + unused: true, + } + input: { + var [ a ] = [ , ]; + console.log(a); + } + expect: { + var a = [][0]; + console.log(a); + } + expect_stdout: "undefined" + node_version: ">=6" +} + +keep_key: { + options = { + evaluate: true, + side_effects: true, + unused: true, + } + input: { + ({} = { + [(console.log("PASS"), 42)]: null, + }); + } + expect: { + ({} = { + [(console.log("PASS"), 42)]: 0, + }); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +keep_reference: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = [ {}, 42 ]; + var [ b, c ] = a; + console.log(a[0] === b ? "PASS" : "FAIL"); + } + expect: { + var a = [ {}, 42 ]; + var [ b ] = a; + console.log(a[0] === b ? "PASS" : "FAIL"); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +maintain_position_assign: { + options = { + unused: true, + } + input: { + console.log(([ , ] = [ , "PASS" ])[1]); + } + expect: { + console.log([ , "PASS" ][1]); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +maintain_position_var: { + options = { + toplevel: true, + unused: true, + } + input: { + A = "FAIL"; + var [ a, b ] = [ A ]; + console.log(b || "PASS"); + } + expect: { + A = "FAIL"; + var [ , b ] = [ A ]; + console.log(b || "PASS"); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +side_effects_array: { + options = { + unused: true, + } + input: { + try { + var [ a ] = 42; + } catch (e) { + console.log("PASS"); + } + } + expect: { + try { + var [ a ] = 42; + } catch (e) { + console.log("PASS"); + } + } + expect_stdout: "PASS" + node_version: ">=6" +} + +side_effects_object: { + options = { + toplevel: true, + unused: true, + } + input: { + var a = null, b = console, { c } = 42; + try { + c[a = "PASS"]; + } catch (e) { + console.log(a); + } + } + expect: { + var a = null, c = (console, 42["c"]); + try { + c[a = "PASS"]; + } catch (e) { + console.log(a); + } + } + expect_stdout: "PASS" + node_version: ">=6" +} + join_vars: { options = { conditionals: true, diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 98ce60e8..ed58ce6f 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -684,26 +684,47 @@ prototype_function: { side_effects: true, } input: { - var a = ({valueOf: 0}) < 1; - var b = ({toString: 0}) < 1; - var c = ({valueOf: 0}) + ""; - var d = ({toString: 0}) + ""; - var e = (({valueOf: 0}) + "")[2]; - var f = (({toString: 0}) + "")[2]; - var g = ({valueOf: 0}).valueOf(); - var h = ({toString: 0}).toString(); - } - expect: { - var a = ({valueOf: 0}) < 1; - var b = ({toString: 0}) < 1; - var c = ({valueOf: 0}) + ""; - var d = ({toString: 0}) + ""; - var e = (({valueOf: 0}) + "")[2]; - var f = (({toString: 0}) + "")[2]; - var g = 0(); - var h = 0(); + function v() { + return this.valueOf === v ? "PASS" : "FAIL"; + } + console.log(({ valueOf: v }) < 1); + console.log(({ valueOf: v }) + ""); + console.log((( {valueOf: v }) + "")[2]); + console.log(({ valueOf: v }).valueOf()); + function t() { + return this.toString === t ? "PASS" : "FAIL"; + } + console.log(({ toString: t }) < 1); + console.log(({ toString: t }) + ""); + console.log((( {toString: t }) + "")[2]); + console.log(({ toString: t }).toString()); } - expect_stdout: true + expect: { + function v() { + return this.valueOf === v ? "PASS" : "FAIL"; + } + console.log(({ valueOf: v }) < 1); + console.log(({ valueOf: v }) + ""); + console.log((( {valueOf: v }) + "")[2]); + console.log(({ valueOf: v }).valueOf()); + function t() { + return this.toString === t ? "PASS" : "FAIL"; + } + console.log(({ toString: t }) < 1); + console.log(({ toString: t }) + ""); + console.log((( {toString: t }) + "")[2]); + console.log(({ toString: t }).toString()); + } + expect_stdout: [ + "false", + "PASS", + "S", + "PASS", + "false", + "PASS", + "S", + "PASS", + ] } call_args: { diff --git a/test/compress/exports.js b/test/compress/exports.js index 0917648b..005a93e6 100644 --- a/test/compress/exports.js +++ b/test/compress/exports.js @@ -249,7 +249,7 @@ hoist_exports_2: { } } expect: { - let e, { foo: a } = 42; + let e, a = 42["foo"]; function f(t, { [e]: o }) { t(o, f); } diff --git a/test/compress/rests.js b/test/compress/rests.js index a22a6b21..6293d58d 100644 --- a/test/compress/rests.js +++ b/test/compress/rests.js @@ -244,7 +244,7 @@ retain_destructured_array: { console.log.apply(console, b); } expect: { - var [ , ...b ] = [ "FAIL", "PASS", 42 ]; + var [ ...b ] = [ "PASS", 42 ]; console.log.apply(console, b); } expect_stdout: "PASS 42" @@ -284,7 +284,7 @@ retain_destructured_object_2: { console.log(k, b[k]); } expect: { - var { foo: [], ...b } = { foo: [ "FAIL" ], bar: "PASS", baz: 42 }; + var { foo: {}, ...b } = { foo: 0, bar: "PASS", baz: 42 }; for (var k in b) console.log(k, b[k]); } @@ -407,6 +407,24 @@ drop_unused_call_args_2: { node_version: ">=6" } +maintain_position: { + options = { + unused: true, + } + input: { + A = "FAIL"; + var [ , ...a ] = [ A, "PASS" ]; + console.log(a[0]); + } + expect: { + A = "FAIL"; + var [ , ...a ] = [ A, "PASS" ]; + console.log(a[0]); + } + expect_stdout: "PASS" + node_version: ">=6" +} + merge_funarg: { options = { merge_vars: true, @@ -723,6 +741,78 @@ issue_4544_2: { node_version: ">=6" } +issue_4560_1: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + } + input: { + var a = 0; + (function(...{ + [a++]: {}, + }) {})(2); + console.log(a); + } + expect: { + var a = 0; + (function(...{ + [a++]: {}, + }) {})(2); + console.log(a); + } + expect_stdout: "1" + node_version: ">=6" +} + +issue_4560_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = 0; + (function(...{ + [a++]: {}, + }) {})(2); + console.log(a); + } + expect: { + var a = 0; + (function(...{ + [a++]: {}, + }) {})(2); + console.log(a); + } + expect_stdout: "1" + node_version: ">=6" +} + +issue_4560_3: { + options = { + collapse_vars: true, + reduce_vars: true, + toplevel: true, + } + input: { + var a = 0, b; + [ ...{ + [a++]: b, + } ] = [ "PASS" ]; + console.log(b); + } + expect: { + var a = 0, b; + [ ...{ + [a++]: b, + } ] = [ "PASS" ]; + console.log(b); + } + expect_stdout: "PASS" + node_version: ">=6" +} + issue_4562: { options = { evaluate: true, diff --git a/test/compress/spreads.js b/test/compress/spreads.js index 7ef118e1..3b1ec461 100644 --- a/test/compress/spreads.js +++ b/test/compress/spreads.js @@ -945,78 +945,6 @@ issue_4556: { node_version: ">=6" } -issue_4560_1: { - options = { - evaluate: true, - reduce_vars: true, - toplevel: true, - } - input: { - var a = 0; - (function(...{ - [a++]: {}, - }) {})(2); - console.log(a); - } - expect: { - var a = 0; - (function(...{ - [a++]: {}, - }) {})(2); - console.log(a); - } - expect_stdout: "1" - node_version: ">=6" -} - -issue_4560_2: { - options = { - reduce_vars: true, - toplevel: true, - unused: true, - } - input: { - var a = 0; - (function(...{ - [a++]: {}, - }) {})(2); - console.log(a); - } - expect: { - var a = 0; - (function(...{ - [a++]: {}, - }) {})(2); - console.log(a); - } - expect_stdout: "1" - node_version: ">=6" -} - -issue_4560_3: { - options = { - collapse_vars: true, - reduce_vars: true, - toplevel: true, - } - input: { - var a = 0, b; - [ ...{ - [a++]: b, - } ] = [ "PASS" ]; - console.log(b); - } - expect: { - var a = 0, b; - [ ...{ - [a++]: b, - } ] = [ "PASS" ]; - console.log(b); - } - expect_stdout: "PASS" - node_version: ">=6" -} - issue_4614: { options = { pure_getters: "strict", |