support spread syntax (#4328)
This commit is contained in:
32
lib/ast.js
32
lib/ast.js
@@ -209,6 +209,8 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
|
|||||||
|
|
||||||
function must_be_expression(node, prop) {
|
function must_be_expression(node, prop) {
|
||||||
if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node");
|
if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node");
|
||||||
|
if (node[prop] instanceof AST_Hole) throw new Error(prop + " cannot be AST_Hole");
|
||||||
|
if (node[prop] instanceof AST_Spread) throw new Error(prop + " cannot be AST_Spread");
|
||||||
if (node[prop] instanceof AST_Statement && !(node[prop] instanceof AST_Function)) {
|
if (node[prop] instanceof AST_Statement && !(node[prop] instanceof AST_Function)) {
|
||||||
throw new Error(prop + " cannot be AST_Statement");
|
throw new Error(prop + " cannot be AST_Statement");
|
||||||
}
|
}
|
||||||
@@ -817,9 +819,11 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
|
|||||||
|
|
||||||
/* -----[ OTHER ]----- */
|
/* -----[ OTHER ]----- */
|
||||||
|
|
||||||
function must_be_expressions(node, prop) {
|
function must_be_expressions(node, prop, allow_spread, allow_hole) {
|
||||||
node[prop].forEach(function(node) {
|
node[prop].forEach(function(node) {
|
||||||
if (!(node instanceof AST_Node)) throw new Error(prop + " must be AST_Node[]");
|
if (!(node instanceof AST_Node)) throw new Error(prop + " must be AST_Node[]");
|
||||||
|
if (!allow_hole && node instanceof AST_Hole) throw new Error(prop + " cannot be AST_Hole");
|
||||||
|
if (!allow_spread && node instanceof AST_Spread) throw new Error(prop + " cannot be AST_Spread");
|
||||||
if (node instanceof AST_Statement && !(node instanceof AST_Function)) {
|
if (node instanceof AST_Statement && !(node instanceof AST_Function)) {
|
||||||
throw new Error(prop + " cannot contain AST_Statement");
|
throw new Error(prop + " cannot contain AST_Statement");
|
||||||
}
|
}
|
||||||
@@ -843,7 +847,7 @@ var AST_Call = DEFNODE("Call", "expression args pure", {
|
|||||||
},
|
},
|
||||||
_validate: function() {
|
_validate: function() {
|
||||||
must_be_expression(this, "expression");
|
must_be_expression(this, "expression");
|
||||||
must_be_expressions(this, "args");
|
must_be_expressions(this, "args", true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -920,6 +924,22 @@ var AST_Sub = DEFNODE("Sub", null, {
|
|||||||
},
|
},
|
||||||
}, AST_PropAccess);
|
}, AST_PropAccess);
|
||||||
|
|
||||||
|
var AST_Spread = DEFNODE("Spread", "expression", {
|
||||||
|
$documentation: "Spread expression in array/object literals or function calls",
|
||||||
|
$propdoc: {
|
||||||
|
expression: "[AST_Node] expression to be expanded",
|
||||||
|
},
|
||||||
|
walk: function(visitor) {
|
||||||
|
var node = this;
|
||||||
|
visitor.visit(node, function() {
|
||||||
|
node.expression.walk(visitor);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_validate: function() {
|
||||||
|
must_be_expression(this, "expression");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
var AST_Unary = DEFNODE("Unary", "operator expression", {
|
var AST_Unary = DEFNODE("Unary", "operator expression", {
|
||||||
$documentation: "Base class for unary expressions",
|
$documentation: "Base class for unary expressions",
|
||||||
$propdoc: {
|
$propdoc: {
|
||||||
@@ -1020,7 +1040,7 @@ var AST_Array = DEFNODE("Array", "elements", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
_validate: function() {
|
_validate: function() {
|
||||||
must_be_expressions(this, "elements");
|
must_be_expressions(this, "elements", true, true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1098,7 +1118,7 @@ var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", {
|
|||||||
var AST_Object = DEFNODE("Object", "properties", {
|
var AST_Object = DEFNODE("Object", "properties", {
|
||||||
$documentation: "An object literal",
|
$documentation: "An object literal",
|
||||||
$propdoc: {
|
$propdoc: {
|
||||||
properties: "[AST_ObjectProperty*] array of properties"
|
properties: "[(AST_ObjectProperty|AST_Spread)*] array of properties"
|
||||||
},
|
},
|
||||||
walk: function(visitor) {
|
walk: function(visitor) {
|
||||||
var node = this;
|
var node = this;
|
||||||
@@ -1110,7 +1130,9 @@ var AST_Object = DEFNODE("Object", "properties", {
|
|||||||
},
|
},
|
||||||
_validate: function() {
|
_validate: function() {
|
||||||
this.properties.forEach(function(node) {
|
this.properties.forEach(function(node) {
|
||||||
if (!(node instanceof AST_ObjectProperty)) throw new Error("properties must be AST_ObjectProperty[]");
|
if (!(node instanceof AST_ObjectProperty || node instanceof AST_Spread)) {
|
||||||
|
throw new Error("properties must contain AST_ObjectProperty and/or AST_Spread only");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
173
lib/compress.js
173
lib/compress.js
@@ -866,7 +866,12 @@ merge(Compressor.prototype, {
|
|||||||
var fn = this;
|
var fn = this;
|
||||||
fn.inlined = false;
|
fn.inlined = false;
|
||||||
var iife;
|
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 hit = false;
|
||||||
var aborts = false;
|
var aborts = false;
|
||||||
fn.walk(new TreeWalker(function(node) {
|
fn.walk(new TreeWalker(function(node) {
|
||||||
@@ -1786,6 +1791,7 @@ merge(Compressor.prototype, {
|
|||||||
if (node instanceof AST_PropAccess) {
|
if (node instanceof AST_PropAccess) {
|
||||||
return side_effects || !value_def && node.expression.may_throw_on_access(compressor);
|
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 (node instanceof AST_SymbolRef) {
|
||||||
if (symbol_in_lvalues(node, parent)) {
|
if (symbol_in_lvalues(node, parent)) {
|
||||||
return !(parent instanceof AST_Assign && parent.operator == "=" && parent.left === node);
|
return !(parent instanceof AST_Assign && parent.operator == "=" && parent.left === node);
|
||||||
@@ -1819,7 +1825,10 @@ merge(Compressor.prototype, {
|
|||||||
&& !fn.uses_arguments
|
&& !fn.uses_arguments
|
||||||
&& !fn.pinned()
|
&& !fn.pinned()
|
||||||
&& (iife = compressor.parent()) instanceof AST_Call
|
&& (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");
|
var fn_strict = compressor.has_directive("use strict");
|
||||||
if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
|
if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
|
||||||
var len = fn.argnames.length;
|
var len = fn.argnames.length;
|
||||||
@@ -1932,6 +1941,8 @@ merge(Compressor.prototype, {
|
|||||||
expr.expressions.forEach(extract_candidates);
|
expr.expressions.forEach(extract_candidates);
|
||||||
} else if (expr instanceof AST_SimpleStatement) {
|
} else if (expr instanceof AST_SimpleStatement) {
|
||||||
extract_candidates(expr.body);
|
extract_candidates(expr.body);
|
||||||
|
} else if (expr instanceof AST_Spread) {
|
||||||
|
extract_candidates(expr.expression);
|
||||||
} else if (expr instanceof AST_Sub) {
|
} else if (expr instanceof AST_Sub) {
|
||||||
extract_candidates(expr.expression);
|
extract_candidates(expr.expression);
|
||||||
extract_candidates(expr.property);
|
extract_candidates(expr.property);
|
||||||
@@ -1978,6 +1989,7 @@ merge(Compressor.prototype, {
|
|||||||
return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1);
|
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_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_Switch) return node;
|
||||||
if (parent instanceof AST_Unary) return node;
|
if (parent instanceof AST_Unary) return node;
|
||||||
if (parent instanceof AST_VarDef) 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_Sequence) return find_stop_unused(parent, level + 1);
|
||||||
if (parent instanceof AST_SimpleStatement) 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_Switch) return find_stop_unused(parent, level + 1);
|
||||||
if (parent instanceof AST_Unary) return find_stop_unused(parent, level + 1);
|
if (parent instanceof AST_Unary) return find_stop_unused(parent, level + 1);
|
||||||
if (parent instanceof AST_VarDef) {
|
if (parent instanceof AST_VarDef) {
|
||||||
@@ -4224,15 +4237,19 @@ merge(Compressor.prototype, {
|
|||||||
|
|
||||||
// determine if expression has side effects
|
// determine if expression has side effects
|
||||||
(function(def) {
|
(function(def) {
|
||||||
function any(list, compressor) {
|
function any(list, compressor, spread) {
|
||||||
for (var i = list.length; --i >= 0;)
|
return !all(list, spread ? function(node) {
|
||||||
if (list[i].has_side_effects(compressor))
|
return node instanceof AST_Spread ? !spread(node, compressor) : !node.has_side_effects(compressor);
|
||||||
return true;
|
} : function(node) {
|
||||||
return false;
|
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_Node, return_true);
|
||||||
def(AST_Array, function(compressor) {
|
def(AST_Array, function(compressor) {
|
||||||
return any(this.elements, compressor);
|
return any(this.elements, compressor, array_spread);
|
||||||
});
|
});
|
||||||
def(AST_Assign, function(compressor) {
|
def(AST_Assign, function(compressor) {
|
||||||
var lhs = this.left;
|
var lhs = this.left;
|
||||||
@@ -4256,7 +4273,7 @@ merge(Compressor.prototype, {
|
|||||||
&& (!this.is_call_pure(compressor) || this.expression.has_side_effects(compressor))) {
|
&& (!this.is_call_pure(compressor) || this.expression.has_side_effects(compressor))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return any(this.args, compressor);
|
return any(this.args, compressor, array_spread);
|
||||||
});
|
});
|
||||||
def(AST_Case, function(compressor) {
|
def(AST_Case, function(compressor) {
|
||||||
return this.expression.has_side_effects(compressor)
|
return this.expression.has_side_effects(compressor)
|
||||||
@@ -4296,23 +4313,38 @@ merge(Compressor.prototype, {
|
|||||||
});
|
});
|
||||||
def(AST_Lambda, return_false);
|
def(AST_Lambda, return_false);
|
||||||
def(AST_Object, function(compressor) {
|
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) {
|
def(AST_ObjectProperty, function(compressor) {
|
||||||
return this.key instanceof AST_Node && this.key.has_side_effects(compressor)
|
return this.key instanceof AST_Node && this.key.has_side_effects(compressor)
|
||||||
|| this.value.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) {
|
def(AST_Sequence, function(compressor) {
|
||||||
return any(this.expressions, compressor);
|
return any(this.expressions, compressor);
|
||||||
});
|
});
|
||||||
def(AST_SimpleStatement, function(compressor) {
|
def(AST_SimpleStatement, function(compressor) {
|
||||||
return this.body.has_side_effects(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) {
|
def(AST_Switch, function(compressor) {
|
||||||
return this.expression.has_side_effects(compressor)
|
return this.expression.has_side_effects(compressor)
|
||||||
|| any(this.body, compressor);
|
|| any(this.body, compressor);
|
||||||
@@ -4612,6 +4644,9 @@ merge(Compressor.prototype, {
|
|||||||
if (!all(self.argnames, function(argname) {
|
if (!all(self.argnames, function(argname) {
|
||||||
return argname instanceof AST_SymbolFunarg;
|
return argname instanceof AST_SymbolFunarg;
|
||||||
})) break;
|
})) break;
|
||||||
|
if (!all(call.args, function(arg) {
|
||||||
|
return !(arg instanceof AST_Spread);
|
||||||
|
})) break;
|
||||||
for (var j = 0; j < len; j++) {
|
for (var j = 0; j < len; j++) {
|
||||||
var arg = call.args[j];
|
var arg = call.args[j];
|
||||||
if (!(arg instanceof AST_SymbolRef)) break;
|
if (!(arg instanceof AST_SymbolRef)) break;
|
||||||
@@ -6171,26 +6206,43 @@ merge(Compressor.prototype, {
|
|||||||
// Returns an array of expressions with side-effects or null
|
// Returns an array of expressions with side-effects or null
|
||||||
// if all elements were dropped. Note: original array may be
|
// if all elements were dropped. Note: original array may be
|
||||||
// returned if nothing changed.
|
// returned if nothing changed.
|
||||||
function trim(nodes, compressor, first_in_statement) {
|
function trim(nodes, compressor, first_in_statement, spread) {
|
||||||
var len = nodes.length;
|
var len = nodes.length;
|
||||||
if (!len) return null;
|
if (!len) return null;
|
||||||
var ret = [], changed = false;
|
var ret = [], changed = false;
|
||||||
for (var i = 0; i < len; i++) {
|
for (var i = 0; i < len; i++) {
|
||||||
var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
|
var node = nodes[i];
|
||||||
changed |= node !== nodes[i];
|
var trimmed;
|
||||||
if (node) {
|
if (spread && node instanceof AST_Spread) {
|
||||||
ret.push(node);
|
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;
|
first_in_statement = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed ? ret.length ? ret : null : nodes;
|
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_Node, return_this);
|
||||||
def(AST_Accessor, return_null);
|
def(AST_Accessor, return_null);
|
||||||
def(AST_Array, function(compressor, first_in_statement) {
|
def(AST_Array, function(compressor, first_in_statement) {
|
||||||
var values = trim(this.elements, compressor, first_in_statement);
|
var values = trim(this.elements, compressor, first_in_statement, array_spread);
|
||||||
return values && make_sequence(this, values);
|
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) {
|
def(AST_Assign, function(compressor) {
|
||||||
var left = this.left;
|
var left = this.left;
|
||||||
@@ -6241,14 +6293,14 @@ merge(Compressor.prototype, {
|
|||||||
var self = this;
|
var self = this;
|
||||||
if (self.is_expr_pure(compressor)) {
|
if (self.is_expr_pure(compressor)) {
|
||||||
if (self.pure) AST_Node.warn("Dropping __PURE__ call [{file}:{line},{col}]", self.start);
|
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);
|
return args && make_sequence(self, args);
|
||||||
}
|
}
|
||||||
var exp = self.expression;
|
var exp = self.expression;
|
||||||
if (self.is_call_pure(compressor)) {
|
if (self.is_call_pure(compressor)) {
|
||||||
var exprs = self.args.slice();
|
var exprs = self.args.slice();
|
||||||
exprs.unshift(exp.expression);
|
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);
|
return exprs && make_sequence(self, exprs);
|
||||||
}
|
}
|
||||||
var def;
|
var def;
|
||||||
@@ -6279,7 +6331,7 @@ merge(Compressor.prototype, {
|
|||||||
if (assign_this_only) {
|
if (assign_this_only) {
|
||||||
var exprs = self.args.slice();
|
var exprs = self.args.slice();
|
||||||
exprs.unshift(exp);
|
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);
|
return exprs && make_sequence(self, exprs);
|
||||||
}
|
}
|
||||||
if (!fn.contains_this()) return make_node(AST_Call, self, self);
|
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) {
|
def(AST_Object, function(compressor, first_in_statement) {
|
||||||
var exprs = [];
|
var exprs = [];
|
||||||
this.properties.forEach(function(prop) {
|
this.properties.forEach(function(prop) {
|
||||||
|
if (prop instanceof AST_Spread) {
|
||||||
|
exprs.push(prop);
|
||||||
|
} else {
|
||||||
if (prop.key instanceof AST_Node) exprs.push(prop.key);
|
if (prop.key instanceof AST_Node) exprs.push(prop.key);
|
||||||
exprs.push(prop.value);
|
exprs.push(prop.value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
var values = trim(exprs, compressor, first_in_statement);
|
var values = trim(exprs, compressor, first_in_statement, function(node, compressor, first_in_statement) {
|
||||||
return values && make_sequence(this, values);
|
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) {
|
def(AST_Sequence, function(compressor, first_in_statement) {
|
||||||
var expressions = trim(this.expressions, 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 (fn.pinned()) return;
|
||||||
if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return;
|
if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return;
|
||||||
var args = call.args;
|
var args = call.args;
|
||||||
|
if (!all(args, function(arg) {
|
||||||
|
return !(arg instanceof AST_Spread);
|
||||||
|
})) return;
|
||||||
var pos = 0, last = 0;
|
var pos = 0, last = 0;
|
||||||
var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call) ? function(argname, arg) {
|
var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call) ? function(argname, arg) {
|
||||||
if (!argname) return true;
|
if (!argname) return true;
|
||||||
@@ -7566,6 +7648,9 @@ merge(Compressor.prototype, {
|
|||||||
&& !self.is_expr_pure(compressor)
|
&& !self.is_expr_pure(compressor)
|
||||||
&& all(fn.argnames, function(argname) {
|
&& all(fn.argnames, function(argname) {
|
||||||
return !(argname instanceof AST_Destructured);
|
return !(argname instanceof AST_Destructured);
|
||||||
|
})
|
||||||
|
&& all(self.args, function(arg) {
|
||||||
|
return !(arg instanceof AST_Spread);
|
||||||
});
|
});
|
||||||
if (can_inline && stat instanceof AST_Return) {
|
if (can_inline && stat instanceof AST_Return) {
|
||||||
var value = stat.value;
|
var value = stat.value;
|
||||||
@@ -7630,7 +7715,12 @@ merge(Compressor.prototype, {
|
|||||||
return !(argname instanceof AST_Destructured);
|
return !(argname instanceof AST_Destructured);
|
||||||
})
|
})
|
||||||
&& (fn !== exp || fn_name_unused(fn, compressor))) {
|
&& (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);
|
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) {
|
function safe_to_flatten(value, compressor) {
|
||||||
if (value instanceof AST_SymbolRef) {
|
if (value instanceof AST_SymbolRef) {
|
||||||
value = value.fixed_value();
|
value = value.fixed_value();
|
||||||
@@ -9694,10 +9798,15 @@ merge(Compressor.prototype, {
|
|||||||
prop = self.property = sub.property;
|
prop = self.property = sub.property;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (compressor.option("properties") && compressor.option("side_effects")
|
var elements;
|
||||||
&& prop instanceof AST_Number && expr instanceof AST_Array) {
|
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 index = prop.value;
|
||||||
var elements = expr.elements;
|
|
||||||
var retValue = elements[index];
|
var retValue = elements[index];
|
||||||
if (safe_to_flatten(retValue, compressor)) {
|
if (safe_to_flatten(retValue, compressor)) {
|
||||||
var is_hole = retValue instanceof AST_Hole;
|
var is_hole = retValue instanceof AST_Hole;
|
||||||
|
|||||||
@@ -702,6 +702,8 @@ function OutputStream(options) {
|
|||||||
|| p instanceof AST_ObjectProperty
|
|| p instanceof AST_ObjectProperty
|
||||||
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|
||||||
|| p instanceof AST_PropAccess && p.expression === this
|
|| p instanceof AST_PropAccess && p.expression === this
|
||||||
|
// ...(foo, bar, baz)
|
||||||
|
|| p instanceof AST_Spread
|
||||||
// !(foo, bar, baz)
|
// !(foo, bar, baz)
|
||||||
|| p instanceof AST_Unary
|
|| p instanceof AST_Unary
|
||||||
// var a = (1, 2), b = a + a; ==> b == 4
|
// var a = (1, 2), b = a + a; ==> b == 4
|
||||||
@@ -1231,6 +1233,10 @@ function OutputStream(options) {
|
|||||||
this.property.print(output);
|
this.property.print(output);
|
||||||
output.print("]");
|
output.print("]");
|
||||||
});
|
});
|
||||||
|
DEFPRINT(AST_Spread, function(output) {
|
||||||
|
output.print("...");
|
||||||
|
this.expression.print(output);
|
||||||
|
});
|
||||||
DEFPRINT(AST_UnaryPrefix, function(output) {
|
DEFPRINT(AST_UnaryPrefix, function(output) {
|
||||||
var op = this.operator;
|
var op = this.operator;
|
||||||
var exp = this.expression;
|
var exp = this.expression;
|
||||||
|
|||||||
26
lib/parse.js
26
lib/parse.js
@@ -501,7 +501,16 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||||||
|
|
||||||
function handle_dot() {
|
function handle_dot() {
|
||||||
next();
|
next();
|
||||||
return is_digit(peek().charCodeAt(0)) ? read_num(".") : token("punc", ".");
|
var ch = peek();
|
||||||
|
if (ch == ".") {
|
||||||
|
var op = ".";
|
||||||
|
do {
|
||||||
|
op += ".";
|
||||||
|
next();
|
||||||
|
} while (peek() == ".");
|
||||||
|
return token("operator", op);
|
||||||
|
}
|
||||||
|
return is_digit(ch.charCodeAt(0)) ? read_num(".") : token("punc", ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
function read_word() {
|
function read_word() {
|
||||||
@@ -1316,6 +1325,12 @@ function parse($TEXT, options) {
|
|||||||
if (allow_trailing_comma && is("punc", closing)) break;
|
if (allow_trailing_comma && is("punc", closing)) break;
|
||||||
if (is("punc", ",") && allow_empty) {
|
if (is("punc", ",") && allow_empty) {
|
||||||
a.push(new AST_Hole({ start: S.token, end: S.token }));
|
a.push(new AST_Hole({ start: S.token, end: S.token }));
|
||||||
|
} else if (is("operator", "...") && parser === expression) {
|
||||||
|
a.push(new AST_Spread({
|
||||||
|
start: S.token,
|
||||||
|
expression: (next(), parser()),
|
||||||
|
end: prev(),
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
a.push(parser());
|
a.push(parser());
|
||||||
}
|
}
|
||||||
@@ -1343,6 +1358,15 @@ function parse($TEXT, options) {
|
|||||||
// allow trailing comma
|
// allow trailing comma
|
||||||
if (!options.strict && is("punc", "}")) break;
|
if (!options.strict && is("punc", "}")) break;
|
||||||
var start = S.token;
|
var start = S.token;
|
||||||
|
if (is("operator", "...")) {
|
||||||
|
next();
|
||||||
|
a.push(new AST_Spread({
|
||||||
|
start: start,
|
||||||
|
expression: expression(false),
|
||||||
|
end: prev(),
|
||||||
|
}));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var key = as_property_key();
|
var key = as_property_key();
|
||||||
if (is("punc", "(")) {
|
if (is("punc", "(")) {
|
||||||
var func_start = S.token;
|
var func_start = S.token;
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ TreeTransformer.prototype = new TreeWalker;
|
|||||||
self.expression = self.expression.transform(tw);
|
self.expression = self.expression.transform(tw);
|
||||||
self.property = self.property.transform(tw);
|
self.property = self.property.transform(tw);
|
||||||
});
|
});
|
||||||
|
DEF(AST_Spread, function(self, tw) {
|
||||||
|
self.expression = self.expression.transform(tw);
|
||||||
|
});
|
||||||
DEF(AST_Unary, function(self, tw) {
|
DEF(AST_Unary, function(self, tw) {
|
||||||
self.expression = self.expression.transform(tw);
|
self.expression = self.expression.transform(tw);
|
||||||
});
|
});
|
||||||
|
|||||||
371
test/compress/spread.js
Normal file
371
test/compress/spread.js
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
collapse_vars_1: {
|
||||||
|
options = {
|
||||||
|
collapse_vars: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var a;
|
||||||
|
[ ...a = "PASS", "PASS"].slice();
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var a;
|
||||||
|
[ ...a = "PASS", "PASS"].slice();
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
collapse_vars_2: {
|
||||||
|
options = {
|
||||||
|
collapse_vars: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var a = "FAIL";
|
||||||
|
try {
|
||||||
|
a = "PASS";
|
||||||
|
[ ...42, "PASS"].slice();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var a = "FAIL";
|
||||||
|
try {
|
||||||
|
a = "PASS";
|
||||||
|
[ ...42, "PASS"].slice();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
collapse_vars_3: {
|
||||||
|
options = {
|
||||||
|
collapse_vars: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var a = "FAIL";
|
||||||
|
try {
|
||||||
|
[ ...(a = "PASS", 42), "PASS"].slice();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var a = "FAIL";
|
||||||
|
try {
|
||||||
|
[ ...(a = "PASS", 42), "PASS"].slice();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
collapse_vars_4: {
|
||||||
|
options = {
|
||||||
|
collapse_vars: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
console.log(function(a) {
|
||||||
|
return a;
|
||||||
|
}(...[ "PASS", "FAIL" ]));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(function(a) {
|
||||||
|
return a;
|
||||||
|
}(...[ "PASS", "FAIL" ]));
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
dont_inline: {
|
||||||
|
options = {
|
||||||
|
inline: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
console.log(function(a) {
|
||||||
|
return a;
|
||||||
|
}(...[ "PASS", "FAIL" ]));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(function(a) {
|
||||||
|
return a;
|
||||||
|
}(...[ "PASS", "FAIL" ]));
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_inline: {
|
||||||
|
options = {
|
||||||
|
properties: true,
|
||||||
|
inline: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
console.log(function(a) {
|
||||||
|
return a;
|
||||||
|
}(...[ "PASS", "FAIL" ]));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(("FAIL", "PASS"));
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
drop_empty_call_1: {
|
||||||
|
options = {
|
||||||
|
side_effects: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
try {
|
||||||
|
(function() {})(...null);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("PASS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
try {
|
||||||
|
[ ...null ];
|
||||||
|
} catch (e) {
|
||||||
|
console.log("PASS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
drop_empty_call_2: {
|
||||||
|
options = {
|
||||||
|
properties: true,
|
||||||
|
side_effects: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
(function() {})(...[ console.log("PASS") ]);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log("PASS");
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_hole: {
|
||||||
|
options = {
|
||||||
|
properties: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
console.log(...[ "PASS", , 42 ]);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log("PASS", void 0, 42);
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS undefined 42"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_property_access: {
|
||||||
|
options = {
|
||||||
|
properties: true,
|
||||||
|
side_effects: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
console.log(function() {
|
||||||
|
return [ ..."foo" ][0];
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(function() {
|
||||||
|
return [ ..."foo" ][0];
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
expect_stdout: "f"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_fargs: {
|
||||||
|
options = {
|
||||||
|
keep_fargs: "strict",
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var a = [ "PASS" ];
|
||||||
|
(function(b, c) {
|
||||||
|
console.log(c);
|
||||||
|
})(console, ...a);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var a = [ "PASS" ];
|
||||||
|
(function(b, c) {
|
||||||
|
console.log(c);
|
||||||
|
})(console, ...a);
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
reduce_vars_1: {
|
||||||
|
options = {
|
||||||
|
reduce_vars: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
console.log(function(b, c) {
|
||||||
|
return c ? "PASS" : "FAIL";
|
||||||
|
}(..."foo"));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(function(b, c) {
|
||||||
|
return c ? "PASS" : "FAIL";
|
||||||
|
}(..."foo"));
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
reduce_vars_2: {
|
||||||
|
options = {
|
||||||
|
conditionals: true,
|
||||||
|
evaluate: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
console.log(function(b, c) {
|
||||||
|
return c ? "PASS" : "FAIL";
|
||||||
|
}(..."foo"));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(function(b, c) {
|
||||||
|
return c ? "PASS" : "FAIL";
|
||||||
|
}(..."foo"));
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=6"
|
||||||
|
}
|
||||||
|
|
||||||
|
drop_object: {
|
||||||
|
options = {
|
||||||
|
side_effects: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
({ ...console.log("PASS") });
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log("PASS");
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=8"
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_getter: {
|
||||||
|
options = {
|
||||||
|
side_effects: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
({
|
||||||
|
...{
|
||||||
|
get p() {
|
||||||
|
console.log("PASS");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
get q() {
|
||||||
|
console.log("FAIL");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
({
|
||||||
|
...{
|
||||||
|
get p() {
|
||||||
|
console.log("PASS");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=8"
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_accessor: {
|
||||||
|
options = {
|
||||||
|
properties: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var o = {
|
||||||
|
...{
|
||||||
|
get p() {
|
||||||
|
console.log("GET");
|
||||||
|
return this.r;
|
||||||
|
},
|
||||||
|
set q(v) {
|
||||||
|
console.log("SET", v);
|
||||||
|
},
|
||||||
|
r: 42,
|
||||||
|
},
|
||||||
|
r: null,
|
||||||
|
};
|
||||||
|
for (var k in o)
|
||||||
|
console.log(k, o[k]);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var o = {
|
||||||
|
...{
|
||||||
|
get p() {
|
||||||
|
console.log("GET");
|
||||||
|
return this.r;
|
||||||
|
},
|
||||||
|
set q(v) {
|
||||||
|
console.log("SET", v);
|
||||||
|
},
|
||||||
|
r: 42,
|
||||||
|
},
|
||||||
|
r: null,
|
||||||
|
};
|
||||||
|
for (var k in o)
|
||||||
|
console.log(k, o[k]);
|
||||||
|
}
|
||||||
|
expect_stdout: [
|
||||||
|
"GET",
|
||||||
|
"p 42",
|
||||||
|
"q undefined",
|
||||||
|
"r null",
|
||||||
|
]
|
||||||
|
node_version: ">=8"
|
||||||
|
}
|
||||||
|
|
||||||
|
unused_var_side_effects: {
|
||||||
|
options = {
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
(function f(a) {
|
||||||
|
var b = {
|
||||||
|
...a,
|
||||||
|
};
|
||||||
|
})({
|
||||||
|
get p() {
|
||||||
|
console.log("PASS");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
(function(a) {
|
||||||
|
({
|
||||||
|
...a,
|
||||||
|
});
|
||||||
|
})({
|
||||||
|
get p() {
|
||||||
|
console.log("PASS");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
node_version: ">=8"
|
||||||
|
}
|
||||||
@@ -135,7 +135,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
|||||||
if (expr && !(expr instanceof U.AST_Hole)) {
|
if (expr && !(expr instanceof U.AST_Hole)) {
|
||||||
node.start._permute++;
|
node.start._permute++;
|
||||||
CHANGED = true;
|
CHANGED = true;
|
||||||
return expr;
|
return expr instanceof U.AST_Spread ? expr.expression : expr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (node instanceof U.AST_Binary) {
|
else if (node instanceof U.AST_Binary) {
|
||||||
@@ -164,7 +164,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
|||||||
][ ((node.start._permute += step) * steps | 0) % 3 ];
|
][ ((node.start._permute += step) * steps | 0) % 3 ];
|
||||||
if (expr) {
|
if (expr) {
|
||||||
CHANGED = true;
|
CHANGED = true;
|
||||||
return expr;
|
return expr instanceof U.AST_Spread ? expr.expression : expr;
|
||||||
}
|
}
|
||||||
if (node.expression instanceof U.AST_Function) {
|
if (node.expression instanceof U.AST_Function) {
|
||||||
// hoist and return expressions from the IIFE function expression
|
// hoist and return expressions from the IIFE function expression
|
||||||
@@ -381,9 +381,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (in_list) {
|
if (in_list) {
|
||||||
// special case to drop object properties and switch branches
|
// drop switch branches
|
||||||
if (parent instanceof U.AST_Object
|
if (parent instanceof U.AST_Switch && parent.expression != node) {
|
||||||
|| parent instanceof U.AST_Switch && parent.expression != node) {
|
|
||||||
node.start._permute++;
|
node.start._permute++;
|
||||||
CHANGED = true;
|
CHANGED = true;
|
||||||
return List.skip;
|
return List.skip;
|
||||||
@@ -404,7 +403,9 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skip element/property from (destructured) array/object
|
// skip element/property from (destructured) array/object
|
||||||
if (parent instanceof U.AST_Array || parent instanceof U.AST_Destructured || parent instanceof AST_Object) {
|
if (parent instanceof U.AST_Array
|
||||||
|
|| parent instanceof U.AST_Destructured
|
||||||
|
|| parent instanceof U.AST_Object) {
|
||||||
node.start._permute++;
|
node.start._permute++;
|
||||||
CHANGED = true;
|
CHANGED = true;
|
||||||
return List.skip;
|
return List.skip;
|
||||||
|
|||||||
@@ -371,9 +371,24 @@ function createParams(noDuplicate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createArgs(recurmax, stmtDepth, canThrow) {
|
function createArgs(recurmax, stmtDepth, canThrow) {
|
||||||
|
recurmax--;
|
||||||
var args = [];
|
var args = [];
|
||||||
for (var n = rng(4); --n >= 0;) {
|
for (var n = rng(4); --n >= 0;) switch (rng(50)) {
|
||||||
args.push(rng(2) ? createValue() : createExpression(recurmax - 1, COMMA_OK, stmtDepth, canThrow));
|
case 0:
|
||||||
|
case 1:
|
||||||
|
var name = getVarName();
|
||||||
|
if (canThrow && rng(8) === 0) {
|
||||||
|
args.push("..." + name);
|
||||||
|
} else {
|
||||||
|
args.push('...("" + ' + name + ")");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
args.push("..." + createArrayLiteral(recurmax, stmtDepth, canThrow));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
args.push(rng(2) ? createValue() : createExpression(recurmax, COMMA_OK, stmtDepth, canThrow));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return args.join(", ");
|
return args.join(", ");
|
||||||
}
|
}
|
||||||
@@ -1044,13 +1059,30 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
|
|||||||
|
|
||||||
function createArrayLiteral(recurmax, stmtDepth, canThrow) {
|
function createArrayLiteral(recurmax, stmtDepth, canThrow) {
|
||||||
recurmax--;
|
recurmax--;
|
||||||
var arr = "[";
|
var arr = [];
|
||||||
for (var i = rng(6); --i >= 0;) {
|
for (var i = rng(6); --i >= 0;) switch (rng(50)) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
// in rare cases produce an array hole element
|
// in rare cases produce an array hole element
|
||||||
var element = rng(20) ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) : "";
|
arr.push("");
|
||||||
arr += element + ", ";
|
break;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
var name = getVarName();
|
||||||
|
if (canThrow && rng(8) === 0) {
|
||||||
|
arr.push("..." + name);
|
||||||
|
} else {
|
||||||
|
arr.push('...("" + ' + name + ")");
|
||||||
}
|
}
|
||||||
return arr + "]";
|
break;
|
||||||
|
case 4:
|
||||||
|
arr.push("..." + createArrayLiteral(recurmax, stmtDepth, canThrow));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
arr.push(createExpression(recurmax, COMMA_OK, stmtDepth, canThrow));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "[" + arr.join(", ") + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
var SAFE_KEYS = [
|
var SAFE_KEYS = [
|
||||||
@@ -1135,13 +1167,20 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
|
|||||||
function createObjectLiteral(recurmax, stmtDepth, canThrow) {
|
function createObjectLiteral(recurmax, stmtDepth, canThrow) {
|
||||||
recurmax--;
|
recurmax--;
|
||||||
var obj = ["({"];
|
var obj = ["({"];
|
||||||
for (var i = rng(6); --i >= 0;) switch (rng(30)) {
|
for (var i = rng(6); --i >= 0;) switch (rng(50)) {
|
||||||
case 0:
|
case 0:
|
||||||
obj.push(createObjectFunction(recurmax, stmtDepth, canThrow));
|
|
||||||
break;
|
|
||||||
case 1:
|
case 1:
|
||||||
obj.push(getVarName() + ",");
|
obj.push(getVarName() + ",");
|
||||||
break;
|
break;
|
||||||
|
case 2:
|
||||||
|
obj.push(createObjectFunction(recurmax, stmtDepth, canThrow));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
obj.push("..." + getVarName() + ",");
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
obj.push("..." + createObjectLiteral(recurmax, stmtDepth, canThrow) + ",");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "),");
|
obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "),");
|
||||||
break;
|
break;
|
||||||
@@ -1591,6 +1630,9 @@ function patch_try_catch(orig, toplevel) {
|
|||||||
} else if (result.name == "TypeError" && /'in'/.test(result.message)) {
|
} else if (result.name == "TypeError" && /'in'/.test(result.message)) {
|
||||||
index = result.ufuzz_catch;
|
index = result.ufuzz_catch;
|
||||||
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("invalid `in`");' + orig.slice(index);
|
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("invalid `in`");' + orig.slice(index);
|
||||||
|
} else if (result.name == "TypeError" && /not iterable/.test(result.message)) {
|
||||||
|
index = result.ufuzz_catch;
|
||||||
|
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("spread not iterable");' + orig.slice(index);
|
||||||
} else if (result.name == "RangeError" && result.message == "Maximum call stack size exceeded") {
|
} else if (result.name == "RangeError" && result.message == "Maximum call stack size exceeded") {
|
||||||
index = result.ufuzz_try;
|
index = result.ufuzz_try;
|
||||||
return orig.slice(0, index) + 'throw new Error("skipping infinite recursion");' + orig.slice(index);
|
return orig.slice(0, index) + 'throw new Error("skipping infinite recursion");' + orig.slice(index);
|
||||||
@@ -1656,6 +1698,7 @@ for (var round = 1; round <= num_iterations; round++) {
|
|||||||
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
|
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
|
||||||
}
|
}
|
||||||
// ignore difference in error message caused by `in`
|
// ignore difference in error message caused by `in`
|
||||||
|
// ignore difference in error message caused by spread syntax
|
||||||
// ignore difference in depth of termination caused by infinite recursion
|
// ignore difference in depth of termination caused by infinite recursion
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
var orig_skipped = patch_try_catch(original_code, toplevel);
|
var orig_skipped = patch_try_catch(original_code, toplevel);
|
||||||
|
|||||||
Reference in New Issue
Block a user