From 1e4985ed9e0822118ad01313af09008af1e9f036 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 5 Dec 2020 21:19:31 +0000 Subject: support spread syntax (#4328) --- lib/compress.js | 177 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 143 insertions(+), 34 deletions(-) (limited to 'lib/compress.js') diff --git a/lib/compress.js b/lib/compress.js index 7434a20d..a78cf6ef 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -866,7 +866,12 @@ merge(Compressor.prototype, { var fn = this; fn.inlined = false; var iife; - if (!fn.name && (iife = tw.parent()) instanceof AST_Call && iife.expression === fn) { + if (!fn.name + && (iife = tw.parent()) instanceof AST_Call + && iife.expression === fn + && all(iife.args, function(arg) { + return !(arg instanceof AST_Spread); + })) { var hit = false; var aborts = false; fn.walk(new TreeWalker(function(node) { @@ -1786,6 +1791,7 @@ merge(Compressor.prototype, { if (node instanceof AST_PropAccess) { return side_effects || !value_def && node.expression.may_throw_on_access(compressor); } + if (node instanceof AST_Spread) return true; if (node instanceof AST_SymbolRef) { if (symbol_in_lvalues(node, parent)) { return !(parent instanceof AST_Assign && parent.operator == "=" && parent.left === node); @@ -1819,7 +1825,10 @@ merge(Compressor.prototype, { && !fn.uses_arguments && !fn.pinned() && (iife = compressor.parent()) instanceof AST_Call - && iife.expression === fn) { + && iife.expression === fn + && all(iife.args, function(arg) { + return !(arg instanceof AST_Spread); + })) { var fn_strict = compressor.has_directive("use strict"); if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false; var len = fn.argnames.length; @@ -1932,6 +1941,8 @@ merge(Compressor.prototype, { expr.expressions.forEach(extract_candidates); } else if (expr instanceof AST_SimpleStatement) { extract_candidates(expr.body); + } else if (expr instanceof AST_Spread) { + extract_candidates(expr.expression); } else if (expr instanceof AST_Sub) { extract_candidates(expr.expression); extract_candidates(expr.property); @@ -1978,6 +1989,7 @@ merge(Compressor.prototype, { return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1); } if (parent instanceof AST_SimpleStatement) return find_stop_unused(parent, level + 1); + if (parent instanceof AST_Spread) return node; if (parent instanceof AST_Switch) return node; if (parent instanceof AST_Unary) return node; if (parent instanceof AST_VarDef) return node; @@ -2087,6 +2099,7 @@ merge(Compressor.prototype, { } if (parent instanceof AST_Sequence) return find_stop_unused(parent, level + 1); if (parent instanceof AST_SimpleStatement) return find_stop_unused(parent, level + 1); + if (parent instanceof AST_Spread) return node; if (parent instanceof AST_Switch) return find_stop_unused(parent, level + 1); if (parent instanceof AST_Unary) return find_stop_unused(parent, level + 1); if (parent instanceof AST_VarDef) { @@ -4224,15 +4237,19 @@ merge(Compressor.prototype, { // determine if expression has side effects (function(def) { - function any(list, compressor) { - for (var i = list.length; --i >= 0;) - if (list[i].has_side_effects(compressor)) - return true; - return false; + function any(list, compressor, spread) { + return !all(list, spread ? function(node) { + return node instanceof AST_Spread ? !spread(node, compressor) : !node.has_side_effects(compressor); + } : function(node) { + return !node.has_side_effects(compressor); + }); + } + function array_spread(node, compressor) { + return !node.expression.is_string(compressor) || node.expression.has_side_effects(compressor); } def(AST_Node, return_true); def(AST_Array, function(compressor) { - return any(this.elements, compressor); + return any(this.elements, compressor, array_spread); }); def(AST_Assign, function(compressor) { var lhs = this.left; @@ -4256,7 +4273,7 @@ merge(Compressor.prototype, { && (!this.is_call_pure(compressor) || this.expression.has_side_effects(compressor))) { return true; } - return any(this.args, compressor); + return any(this.args, compressor, array_spread); }); def(AST_Case, function(compressor) { return this.expression.has_side_effects(compressor) @@ -4296,23 +4313,38 @@ merge(Compressor.prototype, { }); def(AST_Lambda, return_false); def(AST_Object, function(compressor) { - return any(this.properties, compressor); + return any(this.properties, compressor, function(node, compressor) { + var exp = node.expression; + if (exp instanceof AST_Object) return true; + if (exp instanceof AST_PropAccess) return true; + if (exp instanceof AST_SymbolRef) { + exp = exp.fixed_value(); + if (!exp) return true; + if (exp instanceof AST_SymbolRef) return true; + if (exp instanceof AST_PropAccess) return true; + if (!(exp instanceof AST_Object)) return false; + return !all(exp.properties, function(prop) { + return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread); + }); + } + return exp.has_side_effects(compressor); + }); }); def(AST_ObjectProperty, function(compressor) { return this.key instanceof AST_Node && this.key.has_side_effects(compressor) || this.value.has_side_effects(compressor); }); - def(AST_Sub, function(compressor) { - return this.expression.may_throw_on_access(compressor) - || this.expression.has_side_effects(compressor) - || this.property.has_side_effects(compressor); - }); def(AST_Sequence, function(compressor) { return any(this.expressions, compressor); }); def(AST_SimpleStatement, function(compressor) { return this.body.has_side_effects(compressor); }); + def(AST_Sub, function(compressor) { + return this.expression.may_throw_on_access(compressor) + || this.expression.has_side_effects(compressor) + || this.property.has_side_effects(compressor); + }); def(AST_Switch, function(compressor) { return this.expression.has_side_effects(compressor) || any(this.body, compressor); @@ -4612,6 +4644,9 @@ merge(Compressor.prototype, { if (!all(self.argnames, function(argname) { return argname instanceof AST_SymbolFunarg; })) break; + if (!all(call.args, function(arg) { + return !(arg instanceof AST_Spread); + })) break; for (var j = 0; j < len; j++) { var arg = call.args[j]; if (!(arg instanceof AST_SymbolRef)) break; @@ -6171,26 +6206,43 @@ merge(Compressor.prototype, { // Returns an array of expressions with side-effects or null // if all elements were dropped. Note: original array may be // returned if nothing changed. - function trim(nodes, compressor, first_in_statement) { + function trim(nodes, compressor, first_in_statement, spread) { var len = nodes.length; if (!len) return null; var ret = [], changed = false; for (var i = 0; i < len; i++) { - var node = nodes[i].drop_side_effect_free(compressor, first_in_statement); - changed |= node !== nodes[i]; - if (node) { - ret.push(node); + var node = nodes[i]; + var trimmed; + if (spread && node instanceof AST_Spread) { + trimmed = spread(node, compressor, first_in_statement); + } else { + trimmed = node.drop_side_effect_free(compressor, first_in_statement); + } + if (trimmed !== node) changed = true; + if (trimmed) { + ret.push(trimmed); first_in_statement = false; } } return changed ? ret.length ? ret : null : nodes; } - + function array_spread(node, compressor, first_in_statement) { + var exp = node.expression; + if (!exp.is_string(compressor)) return node; + return exp.drop_side_effect_free(compressor, first_in_statement); + } def(AST_Node, return_this); def(AST_Accessor, return_null); def(AST_Array, function(compressor, first_in_statement) { - var values = trim(this.elements, compressor, first_in_statement); - return values && make_sequence(this, values); + var values = trim(this.elements, compressor, first_in_statement, array_spread); + if (!values) return null; + if (all(values, function(node) { + return !(node instanceof AST_Spread); + })) return make_sequence(this, values); + if (values === this.elements) return this; + var node = this.clone(); + node.elements = values; + return node; }); def(AST_Assign, function(compressor) { var left = this.left; @@ -6241,14 +6293,14 @@ merge(Compressor.prototype, { var self = this; if (self.is_expr_pure(compressor)) { if (self.pure) AST_Node.warn("Dropping __PURE__ call [{file}:{line},{col}]", self.start); - var args = trim(self.args, compressor, first_in_statement); + var args = trim(self.args, compressor, first_in_statement, array_spread); return args && make_sequence(self, args); } var exp = self.expression; if (self.is_call_pure(compressor)) { var exprs = self.args.slice(); exprs.unshift(exp.expression); - exprs = trim(exprs, compressor, first_in_statement); + exprs = trim(exprs, compressor, first_in_statement, array_spread); return exprs && make_sequence(self, exprs); } var def; @@ -6279,7 +6331,7 @@ merge(Compressor.prototype, { if (assign_this_only) { var exprs = self.args.slice(); exprs.unshift(exp); - exprs = trim(exprs, compressor, first_in_statement); + exprs = trim(exprs, compressor, first_in_statement, array_spread); return exprs && make_sequence(self, exprs); } if (!fn.contains_this()) return make_node(AST_Call, self, self); @@ -6337,11 +6389,38 @@ merge(Compressor.prototype, { def(AST_Object, function(compressor, first_in_statement) { var exprs = []; this.properties.forEach(function(prop) { - if (prop.key instanceof AST_Node) exprs.push(prop.key); - exprs.push(prop.value); + if (prop instanceof AST_Spread) { + exprs.push(prop); + } else { + if (prop.key instanceof AST_Node) exprs.push(prop.key); + exprs.push(prop.value); + } + }); + var values = trim(exprs, compressor, first_in_statement, function(node, compressor, first_in_statement) { + var exp = node.expression; + if (exp instanceof AST_Object) return node; + if (exp instanceof AST_PropAccess) return node; + if (exp instanceof AST_SymbolRef) { + exp = exp.fixed_value(); + if (!exp) return node; + if (exp instanceof AST_SymbolRef) return node; + if (exp instanceof AST_PropAccess) return node; + if (!(exp instanceof AST_Object)) return null; + return all(exp.properties, function(prop) { + return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread); + }) ? null : node; + } + return exp.drop_side_effect_free(compressor, first_in_statement); }); - var values = trim(exprs, compressor, first_in_statement); - return values && make_sequence(this, values); + if (!values) return null; + if (values === exprs && !all(values, function(node) { + return !(node instanceof AST_Spread); + })) return this; + return make_sequence(this, values.map(function(node) { + return node instanceof AST_Spread ? make_node(AST_Object, node, { + properties: [ node ], + }) : node; + })); }); def(AST_Sequence, function(compressor, first_in_statement) { var expressions = trim(this.expressions, compressor, first_in_statement); @@ -7213,6 +7292,9 @@ merge(Compressor.prototype, { if (fn.pinned()) return; if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return; var args = call.args; + if (!all(args, function(arg) { + return !(arg instanceof AST_Spread); + })) return; var pos = 0, last = 0; var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call) ? function(argname, arg) { if (!argname) return true; @@ -7566,6 +7648,9 @@ merge(Compressor.prototype, { && !self.is_expr_pure(compressor) && all(fn.argnames, function(argname) { return !(argname instanceof AST_Destructured); + }) + && all(self.args, function(arg) { + return !(arg instanceof AST_Spread); }); if (can_inline && stat instanceof AST_Return) { var value = stat.value; @@ -7630,7 +7715,12 @@ merge(Compressor.prototype, { return !(argname instanceof AST_Destructured); }) && (fn !== exp || fn_name_unused(fn, compressor))) { - var args = self.args.concat(make_node(AST_Undefined, self)); + var args = self.args.map(function(arg) { + return arg instanceof AST_Spread ? make_node(AST_Array, arg, { + elements: [ arg ], + }) : arg; + }); + args.push(make_node(AST_Undefined, self)); return make_sequence(self, args).optimize(compressor); } } @@ -9599,6 +9689,20 @@ merge(Compressor.prototype, { }); }); + 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); + } else if (exp instanceof AST_Array) return List.splice(exp.elements.map(function(node) { + return node instanceof AST_Hole ? make_node(AST_Undefined, node).optimize(compressor) : node; + })); + } + return self; + }); + function safe_to_flatten(value, compressor) { if (value instanceof AST_SymbolRef) { value = value.fixed_value(); @@ -9694,10 +9798,15 @@ merge(Compressor.prototype, { prop = self.property = sub.property; } } - if (compressor.option("properties") && compressor.option("side_effects") - && prop instanceof AST_Number && expr instanceof AST_Array) { + var elements; + if (compressor.option("properties") + && compressor.option("side_effects") + && prop instanceof AST_Number + && expr instanceof AST_Array + && all(elements = expr.elements, function(value) { + return !(value instanceof AST_Spread); + })) { var index = prop.value; - var elements = expr.elements; var retValue = elements[index]; if (safe_to_flatten(retValue, compressor)) { var is_hole = retValue instanceof AST_Hole; -- cgit v1.2.3