support spread syntax (#4328)
This commit is contained in:
177
lib/compress.js
177
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);
|
||||
return values && make_sequence(this, values);
|
||||
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);
|
||||
});
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user