support default values (#4442)

This commit is contained in:
Alex Lam S.L
2020-12-23 22:22:55 +00:00
committed by GitHub
parent 56fce2131c
commit 2390fae5c4
10 changed files with 1430 additions and 208 deletions

View File

@@ -207,13 +207,23 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)" $documentation: "The empty statement (empty block or simply a semicolon)"
}, AST_Statement); }, AST_Statement);
function must_be_expression(node, prop) { function validate_expression(value, prop, multiple, allow_spread, allow_hole) {
if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node"); multiple = multiple ? "contain" : "be";
if (node[prop] instanceof AST_Hole) throw new Error(prop + " cannot be AST_Hole"); if (!(value instanceof AST_Node)) throw new Error(prop + " must " + multiple + " AST_Node");
if (node[prop] instanceof AST_Spread) throw new Error(prop + " cannot be AST_Spread"); if (value instanceof AST_DefaultValue) throw new Error(prop + " cannot " + multiple + " AST_DefaultValue");
if (node[prop] instanceof AST_Statement && !is_function(node[prop])) { if (value instanceof AST_Destructured) throw new Error(prop + " cannot " + multiple + " AST_Destructured");
throw new Error(prop + " cannot be AST_Statement"); if (value instanceof AST_Hole && !allow_hole) throw new Error(prop + " cannot " + multiple + " AST_Hole");
if (value instanceof AST_Spread && !allow_spread) throw new Error(prop + " cannot " + multiple + " AST_Spread");
if (value instanceof AST_Statement && !is_function(value)) {
throw new Error(prop + " cannot " + multiple + " AST_Statement");
} }
if (value instanceof AST_SymbolDeclaration) {
throw new Error(prop + " cannot " + multiple + " AST_SymbolDeclaration");
}
}
function must_be_expression(node, prop) {
validate_expression(node[prop], prop);
} }
var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
@@ -534,7 +544,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", {
this.argnames.forEach(function(node) { this.argnames.forEach(function(node) {
validate_destructured(node, function(node) { validate_destructured(node, function(node) {
if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]"); if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
}); }, true);
}); });
}, },
}, AST_Scope); }, AST_Scope);
@@ -838,7 +848,6 @@ var AST_Const = DEFNODE("Const", null, {
validate_destructured(node.name, function(node) { validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst"); if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
}); });
if (node.value != null) must_be_expression(node, "value");
}); });
}, },
}, AST_Definitions); }, AST_Definitions);
@@ -851,7 +860,6 @@ var AST_Let = DEFNODE("Let", null, {
validate_destructured(node.name, function(node) { validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet"); if (!(node instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
}); });
if (node.value != null) must_be_expression(node, "value");
}); });
}, },
}, AST_Definitions); }, AST_Definitions);
@@ -864,7 +872,6 @@ var AST_Var = DEFNODE("Var", null, {
validate_destructured(node.name, function(node) { validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar"); if (!(node instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
}); });
if (node.value != null) must_be_expression(node, "value");
}); });
}, },
}, AST_Definitions); }, AST_Definitions);
@@ -873,7 +880,7 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
$documentation: "A variable declaration; only appears in a AST_Definitions node", $documentation: "A variable declaration; only appears in a AST_Definitions node",
$propdoc: { $propdoc: {
name: "[AST_Destructured|AST_SymbolVar] name of the variable", name: "[AST_Destructured|AST_SymbolVar] name of the variable",
value: "[AST_Node?] initializer, or null of there's no initializer" value: "[AST_Node?] initializer, or null of there's no initializer",
}, },
walk: function(visitor) { walk: function(visitor) {
var node = this; var node = this;
@@ -882,18 +889,34 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
if (node.value) node.value.walk(visitor); if (node.value) node.value.walk(visitor);
}); });
}, },
_validate: function() {
if (this.value != null) must_be_expression(this, "value");
},
}); });
/* -----[ OTHER ]----- */ /* -----[ OTHER ]----- */
var AST_DefaultValue = DEFNODE("DefaultValue", "name value", {
$documentation: "A default value declaration",
$propdoc: {
name: "[AST_Destructured|AST_SymbolDeclaration] name of the variable",
value: "[AST_Node] value to assign if variable is `undefined`",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
node.name.walk(visitor);
node.value.walk(visitor);
});
},
_validate: function() {
must_be_expression(this, "value");
},
});
function must_be_expressions(node, prop, allow_spread, allow_hole) { 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[]"); validate_expression(node, prop, true, allow_spread, allow_hole);
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 && !is_function(node)) {
throw new Error(prop + " cannot contain AST_Statement");
}
}); });
} }
@@ -1048,7 +1071,7 @@ var AST_Binary = DEFNODE("Binary", "operator left right", {
}); });
}, },
_validate: function() { _validate: function() {
must_be_expression(this, "left"); if (!(this instanceof AST_Assign)) must_be_expression(this, "left");
if (typeof this.operator != "string") throw new Error("operator must be string"); if (typeof this.operator != "string") throw new Error("operator must be string");
must_be_expression(this, "right"); must_be_expression(this, "right");
}, },
@@ -1131,12 +1154,13 @@ var AST_Destructured = DEFNODE("Destructured", null, {
$documentation: "Base class for destructured literal", $documentation: "Base class for destructured literal",
}); });
function validate_destructured(node, check) { function validate_destructured(node, check, allow_default) {
if (node instanceof AST_DefaultValue && allow_default) return validate_destructured(node.name, check);
if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) { if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) {
if (!(node instanceof AST_Hole)) validate_destructured(node, check); if (!(node instanceof AST_Hole)) validate_destructured(node, check, true);
}); });
if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) { if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) {
validate_destructured(prop.value, check); validate_destructured(prop.value, check, true);
}); });
check(node); check(node);
} }
@@ -1174,7 +1198,7 @@ var AST_DestructuredKeyVal = DEFNODE("DestructuredKeyVal", "key value", {
if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node"); if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
must_be_expression(this, "key"); must_be_expression(this, "key");
} }
must_be_expression(this, "value"); if (!(this.value instanceof AST_Node)) throw new Error("value must be AST_Node");
}, },
}); });

View File

@@ -56,6 +56,7 @@ function Compressor(options, false_by_default) {
comparisons : !false_by_default, comparisons : !false_by_default,
conditionals : !false_by_default, conditionals : !false_by_default,
dead_code : !false_by_default, dead_code : !false_by_default,
default_values : !false_by_default,
directives : !false_by_default, directives : !false_by_default,
drop_console : false, drop_console : false,
drop_debugger : !false_by_default, drop_debugger : !false_by_default,
@@ -607,6 +608,20 @@ merge(Compressor.prototype, {
function scan_declaration(tw, lhs, fixed, visit) { function scan_declaration(tw, lhs, fixed, visit) {
var scanner = new TreeWalker(function(node) { var scanner = new TreeWalker(function(node) {
if (node instanceof AST_DefaultValue) {
reset_flags(node);
push(tw);
node.value.walk(tw);
pop(tw);
var save = fixed;
fixed = function() {
var value = save();
return is_undefined(value) ? make_sequence(node, [ value, node.value ]) : node.name;
};
node.name.walk(scanner);
fixed = save;
return true;
}
if (node instanceof AST_DestructuredArray) { if (node instanceof AST_DestructuredArray) {
reset_flags(node); reset_flags(node);
var save = fixed; var save = fixed;
@@ -1184,6 +1199,11 @@ merge(Compressor.prototype, {
AST_Node.DEFMETHOD("convert_symbol", noop); AST_Node.DEFMETHOD("convert_symbol", noop);
AST_Destructured.DEFMETHOD("convert_symbol", function(type, process) { AST_Destructured.DEFMETHOD("convert_symbol", function(type, process) {
return this.transform(new TreeTransformer(function(node, descend) { return this.transform(new TreeTransformer(function(node, descend) {
if (node instanceof AST_DefaultValue) {
node = node.clone();
node.name = node.name.transform(this);
return node;
}
if (node instanceof AST_Destructured) { if (node instanceof AST_Destructured) {
node = node.clone(); node = node.clone();
descend(node, this); descend(node, this);
@@ -1205,8 +1225,13 @@ merge(Compressor.prototype, {
AST_SymbolDeclaration.DEFMETHOD("convert_symbol", convert_symbol); AST_SymbolDeclaration.DEFMETHOD("convert_symbol", convert_symbol);
AST_SymbolRef.DEFMETHOD("convert_symbol", convert_symbol); AST_SymbolRef.DEFMETHOD("convert_symbol", convert_symbol);
AST_Destructured.DEFMETHOD("mark_symbol", function(process, tw) { function mark_destructured(process, tw) {
var marker = new TreeWalker(function(node) { var marker = new TreeWalker(function(node) {
if (node instanceof AST_DefaultValue) {
node.value.walk(tw);
node.name.walk(marker);
return true;
}
if (node instanceof AST_DestructuredKeyVal) { if (node instanceof AST_DestructuredKeyVal) {
if (node.key instanceof AST_Node) node.key.walk(tw); if (node.key instanceof AST_Node) node.key.walk(tw);
node.value.walk(marker); node.value.walk(marker);
@@ -1215,7 +1240,9 @@ merge(Compressor.prototype, {
return process(node); return process(node);
}); });
this.walk(marker); this.walk(marker);
}); }
AST_DefaultValue.DEFMETHOD("mark_symbol", mark_destructured);
AST_Destructured.DEFMETHOD("mark_symbol", mark_destructured);
function mark_symbol(process) { function mark_symbol(process) {
return process(this); return process(this);
} }
@@ -1229,6 +1256,10 @@ merge(Compressor.prototype, {
var found = false; var found = false;
var tw = new TreeWalker(function(node) { var tw = new TreeWalker(function(node) {
if (found) return true; if (found) return true;
if (node instanceof AST_DefaultValue) {
node.name.walk(tw);
return true;
}
if (node instanceof AST_DestructuredKeyVal) { if (node instanceof AST_DestructuredKeyVal) {
if (!allow_computed_keys && node.key instanceof AST_Node) return found = true; if (!allow_computed_keys && node.key instanceof AST_Node) return found = true;
node.value.walk(tw); node.value.walk(tw);
@@ -1658,7 +1689,7 @@ merge(Compressor.prototype, {
var assign_used = false; var assign_used = false;
var can_replace = !args || !hit; var can_replace = !args || !hit;
if (!can_replace) { if (!can_replace) {
for (var j = scope.argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) { for (var j = candidate.index + 1; !abort && j < args.length; j++) {
args[j].transform(scanner); args[j].transform(scanner);
} }
can_replace = true; can_replace = true;
@@ -1895,9 +1926,14 @@ merge(Compressor.prototype, {
for (var i = len; --i >= 0;) { for (var i = len; --i >= 0;) {
var sym = fn.argnames[i]; var sym = fn.argnames[i];
var arg = iife.args[i]; var arg = iife.args[i];
var value;
if (sym instanceof AST_DefaultValue) {
value = sym.value;
sym = sym.name;
}
args.unshift(make_node(AST_VarDef, sym, { args.unshift(make_node(AST_VarDef, sym, {
name: sym, name: sym,
value: arg value: value ? arg ? make_sequence(iife, [ arg, value ]) : value : arg,
})); }));
if (sym instanceof AST_Destructured) { if (sym instanceof AST_Destructured) {
if (!sym.match_symbol(return_false)) continue; if (!sym.match_symbol(return_false)) continue;
@@ -1906,17 +1942,21 @@ merge(Compressor.prototype, {
} }
if (sym.name in names) continue; if (sym.name in names) continue;
names[sym.name] = true; names[sym.name] = true;
if (!arg) { if (value) arg = !arg || is_undefined(arg) ? value : null;
if (!arg && !value) {
arg = make_node(AST_Undefined, sym).transform(compressor); arg = make_node(AST_Undefined, sym).transform(compressor);
} else if (arg instanceof AST_Lambda && arg.pinned()) { } else if (arg instanceof AST_Lambda && arg.pinned()) {
arg = null; arg = null;
} else { } else if (arg) {
arg.walk(tw); arg.walk(tw);
} }
if (arg) candidates.unshift([ make_node(AST_VarDef, sym, { if (!arg) continue;
var candidate = make_node(AST_VarDef, sym, {
name: sym, name: sym,
value: arg value: arg
}) ]); });
candidate.index = i;
candidates.unshift([ candidate ]);
} }
} }
} }
@@ -2310,14 +2350,22 @@ merge(Compressor.prototype, {
} }
function remove_candidate(expr) { function remove_candidate(expr) {
if (expr.name instanceof AST_SymbolFunarg) { var index = expr.index;
var index = compressor.self().argnames.indexOf(expr.name); if (index >= 0) {
var args = compressor.parent().args; var argname = scope.argnames[index];
if (args[index]) { if (argname instanceof AST_DefaultValue) {
args[index] = make_node(AST_Number, args[index], { argname.value = make_node(AST_Number, argname, {
value: 0 value: 0
}); });
expr.name.definition().fixed = false; argname.name.definition().fixed = false;
} else {
var args = compressor.parent().args;
if (args[index]) {
args[index] = make_node(AST_Number, args[index], {
value: 0
});
argname.definition().fixed = false;
}
} }
return true; return true;
} }
@@ -3097,7 +3145,7 @@ merge(Compressor.prototype, {
|| node instanceof AST_Undefined || node instanceof AST_Undefined
|| node instanceof AST_UnaryPrefix || node instanceof AST_UnaryPrefix
&& node.operator == "void" && node.operator == "void"
&& !node.expression.has_side_effects(compressor); && !(compressor && node.expression.has_side_effects(compressor));
} }
// is_truthy() // is_truthy()
@@ -4077,10 +4125,18 @@ merge(Compressor.prototype, {
if (fn.evaluating) return this; if (fn.evaluating) return this;
if (fn.name && fn.name.definition().recursive_refs > 0) return this; if (fn.name && fn.name.definition().recursive_refs > 0) return this;
if (this.is_expr_pure(compressor)) return this; if (this.is_expr_pure(compressor)) return this;
if (!all(fn.argnames, function(sym) { var args = eval_args(this.args);
if (!all(fn.argnames, function(sym, index) {
if (sym instanceof AST_DefaultValue) {
if (!args) return false;
if (args[index] !== undefined) return false;
var value = sym.value._eval(compressor, ignore_side_effects, cached, depth);
if (value === sym.value) return false;
args[index] = value;
sym = sym.name;
}
return !(sym instanceof AST_Destructured); return !(sym instanceof AST_Destructured);
})) return this; })) return this;
var args = eval_args(this.args);
if (!args && !ignore_side_effects) return this; if (!args && !ignore_side_effects) return this;
var stat = fn.first_statement(); var stat = fn.first_statement();
if (!(stat instanceof AST_Return)) { if (!(stat instanceof AST_Return)) {
@@ -4104,9 +4160,10 @@ merge(Compressor.prototype, {
if (!val) return; if (!val) return;
var cached_args = []; var cached_args = [];
if (!args || all(fn.argnames, function(sym, i) { if (!args || all(fn.argnames, function(sym, i) {
var value = args[i]; if (sym instanceof AST_DefaultValue) sym = sym.name;
var def = sym.definition(); var def = sym.definition();
if (def.orig[def.orig.length - 1] !== sym) return false; if (def.orig[def.orig.length - 1] !== sym) return false;
var value = args[i];
def.references.forEach(function(node) { def.references.forEach(function(node) {
node._eval = function() { node._eval = function() {
return value; return value;
@@ -5340,32 +5397,35 @@ merge(Compressor.prototype, {
var calls_to_drop_args = []; var calls_to_drop_args = [];
var fns_with_marked_args = []; var fns_with_marked_args = [];
var trimmer = new TreeTransformer(function(node) { var trimmer = new TreeTransformer(function(node) {
if (node instanceof AST_DefaultValue) return trim_default(tt, trimmer, node);
if (node instanceof AST_DestructuredArray) { if (node instanceof AST_DestructuredArray) {
var trim = true; var trim = true;
for (var i = node.elements.length; --i >= 0;) { for (var i = node.elements.length; --i >= 0;) {
var sym = node.elements[i]; var element = node.elements[i].transform(trimmer);
if (!(sym instanceof AST_SymbolDeclaration)) { if (element) {
node.elements[i] = sym.transform(trimmer); node.elements[i] = element;
trim = false;
} else if (sym.definition().id in in_use_ids) {
trim = false; trim = false;
} else if (trim) { } else if (trim) {
node.elements.pop(); node.elements.pop();
} else { } else {
node.elements[i] = make_node(AST_Hole, sym); node.elements[i] = make_node(AST_Hole, node.elements[i]);
} }
} }
return node; return node;
} }
if (node instanceof AST_DestructuredKeyVal) { if (node instanceof AST_DestructuredKeyVal) {
if (!(node.value instanceof AST_SymbolDeclaration)) { var retain = false;
node.value = node.value.transform(trimmer); if (node.key instanceof AST_Node) {
return node; node.key = node.key.transform(tt);
retain = node.key.has_side_effects(compressor);
} }
if (typeof node.key != "string") return node; if (retain && is_decl(node.value)) return node;
if (node.value.definition().id in in_use_ids) return node; var value = node.value.transform(trimmer);
return List.skip; if (!value) return List.skip;
node.value = value;
return node;
} }
if (node instanceof AST_SymbolDeclaration) return node.definition().id in in_use_ids ? node : null;
}); });
var tt = new TreeTransformer(function(node, descend, in_list) { var tt = new TreeTransformer(function(node, descend, in_list) {
var parent = tt.parent(); var parent = tt.parent();
@@ -5432,21 +5492,28 @@ merge(Compressor.prototype, {
var trim = compressor.drop_fargs(node, parent); var trim = compressor.drop_fargs(node, parent);
for (var a = node.argnames, i = a.length; --i >= 0;) { for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i]; var sym = a[i];
if (sym instanceof AST_Destructured) { if (!(sym instanceof AST_SymbolFunarg)) {
sym.transform(trimmer); var arg = sym.transform(trimmer);
trim = false; if (arg) {
trim = false;
} else if (trim) {
log(sym.name, "Dropping unused function argument {name}");
a.pop();
} else {
sym.name.__unused = true;
a[i] = sym.name;
}
continue; continue;
} }
var def = sym.definition(); var def = sym.definition();
if (def.id in in_use_ids) { if (def.id in in_use_ids) {
trim = false; trim = false;
if (indexOf_assign(def, sym) < 0) sym.__unused = null; if (indexOf_assign(def, sym) < 0) sym.__unused = null;
} else if (trim) {
log(sym, "Dropping unused function argument {name}");
a.pop();
} else { } else {
sym.__unused = true; sym.__unused = true;
if (trim) {
log(sym, "Dropping unused function argument {name}");
a.pop();
}
} }
} }
fns_with_marked_args.push(node); fns_with_marked_args.push(node);
@@ -5469,6 +5536,7 @@ merge(Compressor.prototype, {
if (def.name instanceof AST_Destructured) { if (def.name instanceof AST_Destructured) {
var value = def.value; var value = def.value;
var trimmer = new TreeTransformer(function(node) { var trimmer = new TreeTransformer(function(node) {
if (node instanceof AST_DefaultValue) return trim_default(tt, trimmer, node);
if (node instanceof AST_DestructuredArray) { if (node instanceof AST_DestructuredArray) {
var save = value; var save = value;
if (value instanceof AST_SymbolRef) value = value.fixed_value(); if (value instanceof AST_SymbolRef) value = value.fixed_value();
@@ -5514,7 +5582,7 @@ merge(Compressor.prototype, {
value = values && values[prop.key]; value = values && values[prop.key];
retain = false; retain = false;
} }
if (retain && prop.value instanceof AST_SymbolDeclaration) { if (retain && is_decl(prop.value)) {
properties.push(prop); properties.push(prop);
} else { } else {
var newValue = prop.value.transform(trimmer); var newValue = prop.value.transform(trimmer);
@@ -5962,6 +6030,38 @@ merge(Compressor.prototype, {
return true; return true;
} }
} }
function is_decl(node) {
return (node instanceof AST_DefaultValue ? node.name : node) instanceof AST_SymbolDeclaration;
}
function trim_default(tt, trimmer, node) {
node.value = node.value.transform(tt);
var name = node.name.transform(trimmer);
if (!name) {
var value = node.value.drop_side_effect_free(compressor);
if (!value) return null;
name = node.name;
if (name instanceof AST_Destructured) {
name = name.clone();
name[name instanceof AST_DestructuredArray ? "elements" : "properties"] = [];
if (!(value instanceof AST_Array || value.is_string(compressor)
|| name instanceof AST_DestructuredObject
&& (value instanceof AST_Object
|| value.is_boolean(compressor)
|| value.is_number(compressor)))) {
value = make_node(AST_Array, value, {
elements: [ value ],
});
}
node.name = name;
} else {
log(name, "Side effects in default value of unused variable {name}");
}
node.value = value;
}
return node;
}
}); });
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) { AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) {
@@ -6168,7 +6268,8 @@ merge(Compressor.prototype, {
if (!(exp instanceof AST_Lambda)) return; if (!(exp instanceof AST_Lambda)) return;
if (exp.uses_arguments || exp.pinned()) return; if (exp.uses_arguments || exp.pinned()) return;
var sym = exp.argnames[parent.args.indexOf(this)]; var sym = exp.argnames[parent.args.indexOf(this)];
if (sym && !all_bool(sym.definition(), bool_returns, compressor)) return; if (sym instanceof AST_DefaultValue) sym = sym.name;
if (sym instanceof AST_SymbolFunarg && !all_bool(sym.definition(), bool_returns, compressor)) return;
} else if (parent.TYPE == "Call") { } else if (parent.TYPE == "Call") {
compressor.pop(); compressor.pop();
var in_bool = compressor.in_boolean_context(); var in_bool = compressor.in_boolean_context();
@@ -7447,6 +7548,11 @@ merge(Compressor.prototype, {
var side_effects = []; var side_effects = [];
for (var i = 0; i < args.length; i++) { for (var i = 0; i < args.length; i++) {
var argname = fn.argnames[i]; var argname = fn.argnames[i];
if (compressor.option("default_values")
&& argname instanceof AST_DefaultValue
&& args[i].is_defined(compressor)) {
fn.argnames[i] = argname = argname.name;
}
if (!argname || "__unused" in argname) { if (!argname || "__unused" in argname) {
var node = args[i].drop_side_effect_free(compressor); var node = args[i].drop_side_effect_free(compressor);
if (drop_fargs(argname)) { if (drop_fargs(argname)) {
@@ -7779,22 +7885,31 @@ merge(Compressor.prototype, {
} }
} }
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp; var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
var is_func = fn instanceof AST_Arrow || fn instanceof AST_Defun || fn instanceof AST_Function; var is_func = fn instanceof AST_Arrow || fn instanceof AST_Defun || fn instanceof AST_Function;
var stat = is_func && fn.first_statement(); var stat = is_func && fn.first_statement();
var can_inline = is_func var has_default = false;
&& compressor.option("inline") var can_drop = is_func && all(fn.argnames, function(argname, index) {
&& !self.is_expr_pure(compressor) if (argname instanceof AST_DefaultValue) {
&& all(fn.argnames, function(argname) { has_default = true;
return !(argname instanceof AST_Destructured); var arg = self.args[index];
}) if (arg && !is_undefined(arg)) return false;
&& all(self.args, function(arg) { var abort = false;
return !(arg instanceof AST_Spread); argname.value.walk(new TreeWalker(function(node) {
}); if (abort) return true;
if (node instanceof AST_SymbolRef && fn.find_variable(node.name) === node.definition()) {
return abort = true;
}
}));
if (abort) return false;
argname = argname.name;
}
return !(argname instanceof AST_Destructured);
});
var can_inline = can_drop && compressor.option("inline") && !self.is_expr_pure(compressor);
if (can_inline && stat instanceof AST_Return) { if (can_inline && stat instanceof AST_Return) {
var value = stat.value; var value = stat.value;
if (exp === fn && (!value || value.is_constant_expression() && safe_from_await(value))) { if (exp === fn && (!value || value.is_constant_expression() && safe_from_await(value))) {
var args = self.args.concat(value || make_node(AST_Undefined, self)); return make_sequence(self, convert_args(value)).optimize(compressor);
return make_sequence(self, args).optimize(compressor);
} }
} }
if (is_func) { if (is_func) {
@@ -7805,6 +7920,9 @@ merge(Compressor.prototype, {
&& !(fn.name && fn instanceof AST_Function) && !(fn.name && fn instanceof AST_Function)
&& (exp === fn || !recursive_ref(compressor, def = exp.definition()) && (exp === fn || !recursive_ref(compressor, def = exp.definition())
&& fn.is_constant_expression(find_scope(compressor))) && fn.is_constant_expression(find_scope(compressor)))
&& all(self.args, function(arg) {
return !(arg instanceof AST_Spread);
})
&& (value = can_flatten_body(stat)) && (value = can_flatten_body(stat))
&& !fn.contains_this()) { && !fn.contains_this()) {
var replacing = exp === fn || def.single_use && def.references.length - def.replaced == 1; var replacing = exp === fn || def.single_use && def.references.length - def.replaced == 1;
@@ -7848,19 +7966,11 @@ merge(Compressor.prototype, {
} }
} }
if (compressor.option("side_effects") if (compressor.option("side_effects")
&& can_drop
&& all(fn.body, is_empty) && all(fn.body, is_empty)
&& (fn !== exp || fn_name_unused(fn, compressor)) && (fn !== exp || fn_name_unused(fn, compressor))
&& !(fn instanceof AST_Arrow && fn.value) && !(fn instanceof AST_Arrow && fn.value)) {
&& all(fn.argnames, function(argname) { return make_sequence(self, convert_args()).optimize(compressor);
return !(argname instanceof AST_Destructured);
})) {
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);
} }
} }
if (compressor.option("drop_console")) { if (compressor.option("drop_console")) {
@@ -7881,6 +7991,19 @@ merge(Compressor.prototype, {
} }
return try_evaluate(compressor, self); return try_evaluate(compressor, self);
function convert_args(value) {
var args = self.args.map(function(arg) {
return arg instanceof AST_Spread ? make_node(AST_Array, arg, {
elements: [ arg ],
}) : arg;
});
fn.argnames.forEach(function(argname, index) {
if (argname instanceof AST_DefaultValue) args.push(argname.value);
});
args.push(value || make_node(AST_Undefined, self));
return args;
}
function safe_from_await(node) { function safe_from_await(node) {
if (!is_async(scope || compressor.find_parent(AST_Scope))) return true; if (!is_async(scope || compressor.find_parent(AST_Scope))) return true;
var safe = true; var safe = true;
@@ -7948,6 +8071,7 @@ merge(Compressor.prototype, {
} }
function can_substitute_directly() { function can_substitute_directly() {
if (has_default) return;
if (var_assigned) return; if (var_assigned) return;
if (compressor.option("inline") < 2 && fn.argnames.length) return; if (compressor.option("inline") < 2 && fn.argnames.length) return;
if (!fn.variables.all(function(def) { if (!fn.variables.all(function(def) {
@@ -8017,6 +8141,7 @@ merge(Compressor.prototype, {
for (var i = 0; i < fn.argnames.length; i++) { for (var i = 0; i < fn.argnames.length; i++) {
var arg = fn.argnames[i]; var arg = fn.argnames[i];
if (arg.__unused) continue; if (arg.__unused) continue;
if (arg instanceof AST_DefaultValue) arg = arg.name;
if (!safe_to_inject || var_exists(defined, arg.name)) return false; if (!safe_to_inject || var_exists(defined, arg.name)) return false;
used[arg.name] = true; used[arg.name] = true;
if (in_loop) in_loop.push(arg.definition()); if (in_loop) in_loop.push(arg.definition());
@@ -8115,6 +8240,10 @@ merge(Compressor.prototype, {
for (i = len; --i >= 0;) { for (i = len; --i >= 0;) {
var name = fn.argnames[i]; var name = fn.argnames[i];
var value = self.args[i]; var value = self.args[i];
if (name instanceof AST_DefaultValue) {
value = value ? make_sequence(self, [ value, name.value ]) : name.value;
name = name.name;
}
if (name.__unused || scope.var_names()[name.name]) { if (name.__unused || scope.var_names()[name.name]) {
if (value) expressions.push(value); if (value) expressions.push(value);
} else { } else {
@@ -8148,6 +8277,7 @@ merge(Compressor.prototype, {
} }
append_var(decls, expressions, name, var_def.value); append_var(decls, expressions, name, var_def.value);
if (in_loop && all(fn.argnames, function(argname) { if (in_loop && all(fn.argnames, function(argname) {
if (argname instanceof AST_DefaultValue) argname = argname.name;
return argname.name != name.name; return argname.name != name.name;
})) { })) {
var def = fn.variables.get(name.name); var def = fn.variables.get(name.name);
@@ -9936,13 +10066,13 @@ merge(Compressor.prototype, {
var argname = fn.argnames[index]; var argname = fn.argnames[index];
if (def.deleted && def.deleted[index]) { if (def.deleted && def.deleted[index]) {
argname = null; argname = null;
} else if (argname instanceof AST_Destructured) { } else if (argname && !(argname instanceof AST_SymbolFunarg)) {
argname = null; argname = null;
} else if (argname && (compressor.has_directive("use strict") } else if (argname && (compressor.has_directive("use strict")
|| fn.name || fn.name
|| !(fn_parent instanceof AST_Call && index < fn_parent.args.length) || !(fn_parent instanceof AST_Call && index < fn_parent.args.length)
|| !all(fn.argnames, function(argname) { || !all(fn.argnames, function(argname) {
return !(argname instanceof AST_Destructured); return argname instanceof AST_SymbolFunarg;
}))) { }))) {
var arg_def = argname.definition(); var arg_def = argname.definition();
if (!compressor.option("reduce_vars") if (!compressor.option("reduce_vars")

View File

@@ -702,6 +702,8 @@ function OutputStream(options) {
// (false, true) ? (a = 10, b = 20) : (c = 30) // (false, true) ? (a = 10, b = 20) : (c = 30)
// ==> 20 (side effect, set a := 10 and b := 20) // ==> 20 (side effect, set a := 10 and b := 20)
|| p instanceof AST_Conditional || p instanceof AST_Conditional
// [ a = (1, 2) ] = [] ==> a == 2
|| p instanceof AST_DefaultValue
// { [(1, 2)]: 3 }[2] ==> 3 // { [(1, 2)]: 3 }[2] ==> 3
// { foo: (1, 2) }.foo ==> 2 // { foo: (1, 2) }.foo ==> 2
|| p instanceof AST_DestructuredKeyVal || p instanceof AST_DestructuredKeyVal
@@ -1218,6 +1220,15 @@ function OutputStream(options) {
} }
}); });
DEFPRINT(AST_DefaultValue, function(output) {
var self = this;
self.name.print(output);
output.space();
output.print("=");
output.space();
self.value.print(output);
});
/* -----[ other expressions ]----- */ /* -----[ other expressions ]----- */
function print_call_args(self, output) { function print_call_args(self, output) {
if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) { if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) {

View File

@@ -1041,11 +1041,30 @@ function parse($TEXT, options) {
function to_funarg(node) { function to_funarg(node) {
if (node instanceof AST_Array) return new AST_DestructuredArray({ if (node instanceof AST_Array) return new AST_DestructuredArray({
start: node.start, start: node.start,
elements: node.elements.map(function(node) { elements: node.elements.map(to_funarg),
return node instanceof AST_Hole ? node : to_funarg(node);
}),
end: node.end, end: node.end,
}); });
if (node instanceof AST_Assign) return new AST_DefaultValue({
start: node.start,
name: to_funarg(node.left),
value: node.right,
end: node.end,
});
if (node instanceof AST_DefaultValue) {
node.name = to_funarg(node.name);
return node;
}
if (node instanceof AST_DestructuredArray) {
node.elements = node.elements.map(to_funarg);
return node;
}
if (node instanceof AST_DestructuredObject) {
node.properties.forEach(function(prop) {
prop.value = to_funarg(prop.value);
});
return node;
}
if (node instanceof AST_Hole) return node;
if (node instanceof AST_Object) return new AST_DestructuredObject({ if (node instanceof AST_Object) return new AST_DestructuredObject({
start: node.start, start: node.start,
properties: node.properties.map(function(prop) { properties: node.properties.map(function(prop) {
@@ -1122,7 +1141,7 @@ function parse($TEXT, options) {
var was_funarg = S.in_funarg; var was_funarg = S.in_funarg;
S.in_funarg = S.in_function; S.in_funarg = S.in_function;
var argnames = expr_list(")", !options.strict, false, function() { var argnames = expr_list(")", !options.strict, false, function() {
return maybe_destructured(AST_SymbolFunarg); return maybe_default(AST_SymbolFunarg);
}); });
S.in_funarg = was_funarg; S.in_funarg = was_funarg;
var loop = S.in_loop; var loop = S.in_loop;
@@ -1468,6 +1487,32 @@ function parse($TEXT, options) {
})); }));
continue; continue;
} }
if (is_token(peek(), "operator", "=")) {
var name = as_symbol(AST_SymbolRef);
next();
a.push(new AST_ObjectKeyVal({
start: start,
key: start.value,
value: new AST_Assign({
start: start,
left: name,
operator: "=",
right: maybe_assign(),
end: prev(),
}),
end: prev(),
}));
continue;
}
if (is_token(peek(), "punc", ",") || is_token(peek(), "punc", "}")) {
a.push(new AST_ObjectKeyVal({
start: start,
key: start.value,
value: as_symbol(AST_SymbolRef),
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;
@@ -1492,15 +1537,6 @@ function parse($TEXT, options) {
})); }));
continue; continue;
} }
if (is("punc", ",") || is("punc", "}")) {
a.push(new AST_ObjectKeyVal({
start: start,
key: key,
value: _make_symbol(AST_SymbolRef, start),
end: prev(),
}));
continue;
}
if (start.type == "name") switch (key) { if (start.type == "name") switch (key) {
case "async": case "async":
key = as_property_key(); key = as_property_key();
@@ -1601,7 +1637,7 @@ function parse($TEXT, options) {
return new AST_DestructuredArray({ return new AST_DestructuredArray({
start: start, start: start,
elements: expr_list("]", !options.strict, true, function() { elements: expr_list("]", !options.strict, true, function() {
return maybe_destructured(type); return maybe_default(type);
}), }),
end: prev(), end: prev(),
}); });
@@ -1620,15 +1656,25 @@ function parse($TEXT, options) {
a.push(new AST_DestructuredKeyVal({ a.push(new AST_DestructuredKeyVal({
start: key_start, start: key_start,
key: key, key: key,
value: maybe_destructured(type), value: maybe_default(type),
end: prev(), end: prev(),
})); }));
continue; continue;
} }
var name = as_symbol(type);
if (is("operator", "=")) {
next();
name = new AST_DefaultValue({
start: name.start,
name: name,
value: maybe_assign(),
end: prev(),
});
}
a.push(new AST_DestructuredKeyVal({ a.push(new AST_DestructuredKeyVal({
start: key_start, start: key_start,
key: key_start.value, key: key_start.value,
value: as_symbol(type), value: name,
end: prev(), end: prev(),
})); }));
} }
@@ -1642,6 +1688,19 @@ function parse($TEXT, options) {
return as_symbol(type); return as_symbol(type);
} }
function maybe_default(type) {
var start = S.token;
var name = maybe_destructured(type);
if (!is("operator", "=")) return name;
next();
return new AST_DefaultValue({
start: start,
name: name,
value: maybe_assign(),
end: prev(),
});
}
function mark_pure(call) { function mark_pure(call) {
var start = call.start; var start = call.start;
var comments = start.comments_before; var comments = start.comments_before;
@@ -1788,20 +1847,34 @@ function parse($TEXT, options) {
if (node instanceof AST_Array) { if (node instanceof AST_Array) {
var elements = node.elements.map(to_destructured); var elements = node.elements.map(to_destructured);
return all(elements, function(node) { return all(elements, function(node) {
return node instanceof AST_Destructured || node instanceof AST_Hole || is_assignable(node); return node instanceof AST_DefaultValue
|| node instanceof AST_Destructured
|| node instanceof AST_Hole
|| is_assignable(node);
}) ? new AST_DestructuredArray({ }) ? new AST_DestructuredArray({
start: node.start, start: node.start,
elements: elements, elements: elements,
end: node.end, end: node.end,
}) : node; }) : node;
} }
if (node instanceof AST_Assign) {
var name = to_destructured(node.left);
return name instanceof AST_Destructured || is_assignable(name) ? new AST_DefaultValue({
start: node.start,
name: name,
value: node.right,
end: node.end,
}) : node;
}
if (!(node instanceof AST_Object)) return node; if (!(node instanceof AST_Object)) return node;
var props = []; var props = [];
for (var i = 0; i < node.properties.length; i++) { for (var i = 0; i < node.properties.length; i++) {
var prop = node.properties[i]; var prop = node.properties[i];
if (!(prop instanceof AST_ObjectKeyVal)) return node; if (!(prop instanceof AST_ObjectKeyVal)) return node;
var value = to_destructured(prop.value); var value = to_destructured(prop.value);
if (!(value instanceof AST_Destructured || is_assignable(value))) return node; if (!(value instanceof AST_DefaultValue || value instanceof AST_Destructured || is_assignable(value))) {
return node;
}
props.push(new AST_DestructuredKeyVal({ props.push(new AST_DestructuredKeyVal({
start: prop.start, start: prop.start,
key: prop.key, key: prop.key,

View File

@@ -126,6 +126,10 @@ TreeTransformer.prototype = new TreeWalker;
self.name = self.name.transform(tw); self.name = self.name.transform(tw);
if (self.value) self.value = self.value.transform(tw); if (self.value) self.value = self.value.transform(tw);
}); });
DEF(AST_DefaultValue, function(self, tw) {
self.name = self.name.transform(tw);
self.value = self.value.transform(tw);
});
DEF(AST_Lambda, function(self, tw) { DEF(AST_Lambda, function(self, tw) {
if (self.name) self.name = self.name.transform(tw); if (self.name) self.name = self.name.transform(tw);
self.argnames = do_list(self.argnames, tw); self.argnames = do_list(self.argnames, tw);

View File

@@ -0,0 +1,952 @@
arrow_1: {
input: {
console.log(((a = "PASS") => a)());
}
expect_exact: 'console.log(((a="PASS")=>a)());'
expect_stdout: "PASS"
node_version: ">=6"
}
arrow_2: {
input: {
console.log((([ a = "FAIL" ]) => a)([ "PASS" ]));
}
expect_exact: 'console.log((([a="FAIL"])=>a)(["PASS"]));'
expect_stdout: "PASS"
node_version: ">=6"
}
arrow_3: {
input: {
(([ a = console ] = null) => a.log("PASS"))("");
}
expect_exact: '(([a=console]=null)=>a.log("PASS"))("");'
expect_stdout: "PASS"
node_version: ">=6"
}
assign: {
input: {
[ a = "PASS" ] = [];
console.log(a);
}
expect_exact: '[a="PASS"]=[];console.log(a);'
expect_stdout: "PASS"
node_version: ">=6"
}
declaration_var: {
input: {
var [ a = "PASS" ] = [ , ];
console.log(a);
}
expect_exact: 'var[a="PASS"]=[,];console.log(a);'
expect_stdout: "PASS"
node_version: ">=6"
}
declaration_const: {
input: {
const [ a = "FAIL" ] = [ "PASS" ];
console.log(a);
}
expect_exact: 'const[a="FAIL"]=["PASS"];console.log(a);'
expect_stdout: "PASS"
node_version: ">=6"
}
declaration_let: {
input: {
let [ a = "PASS" ] = [ void 42 ];
console.log(a);
}
expect_exact: 'let[a="PASS"]=[void 42];console.log(a);'
expect_stdout: "PASS"
node_version: ">=6"
}
object_shorthand_assign: {
input: {
({ a = "PASS" } = 42);
console.log(a);
}
expect_exact: '({a:a="PASS"}=42);console.log(a);'
expect_stdout: "PASS"
node_version: ">=6"
}
object_shorthand_declaration: {
input: {
var { a = "PASS" } = 42;
console.log(a);
}
expect_exact: 'var{a:a="PASS"}=42;console.log(a);'
expect_stdout: "PASS"
node_version: ">=6"
}
object_shorthand_function: {
input: {
(function({ a = "PASS" }) {
console.log(a);
})(42);
}
expect_exact: '(function({a:a="PASS"}){console.log(a)})(42);'
expect_stdout: "PASS"
node_version: ">=6"
}
retain_arguments_1: {
options = {
arguments: true,
}
input: {
console.log(function(a = "FAIL") {
return arguments[0];
}() || "PASS");
}
expect: {
console.log(function(a = "FAIL") {
return arguments[0];
}() || "PASS");
}
expect_stdout: "PASS"
node_version: ">=6"
}
retain_arguments_2: {
options = {
arguments: true,
}
input: {
console.log(function(a, b = null) {
a = "FAIL";
return arguments[0];
}("PASS", 42));
}
expect: {
console.log(function(a, b = null) {
a = "FAIL";
return arguments[0];
}("PASS", 42));
}
expect_stdout: "PASS"
node_version: ">=6"
}
process_boolean_returns: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
console.log(function(a = console.log("FAIL 1")) {
return a() ? "PASS" : "FAIL 2";
}(function() {
return 42;
}));
}
expect: {
console.log(function(a = console.log("FAIL 1")) {
return a() ? "PASS" : "FAIL 2";
}(function() {
return 1;
}));
}
expect_stdout: "PASS"
node_version: ">=6"
}
collapse_value_1: {
options = {
collapse_vars: true,
unused: true,
}
input: {
console.log(function(a = "PASS") {
return a;
}());
}
expect: {
console.log(function(a) {
return "PASS";
}());
}
expect_stdout: "PASS"
node_version: ">=6"
}
collapse_value_2: {
options = {
collapse_vars: true,
unused: true,
}
input: {
(function(a = console) {
return a;
})().log("PASS");
}
expect: {
(function(a) {
return console;
})().log("PASS");
}
expect_stdout: "PASS"
node_version: ">=6"
}
flatten_if: {
options = {
conditionals: true,
}
input: {
if (console.log("PASS")) {
var [
a = function b() {
for (c in b);
},
] = 0;
}
}
expect: {
var a;
console.log("PASS") && ([
a = function b() {
for (c in b);
},
] = 0);
}
expect_stdout: "PASS"
node_version: ">=6"
}
maintain_if: {
options = {
conditionals: true,
}
input: {
if (a)
for (;;);
else
var [ a = "PASS" ] = [];
console.log(a);
}
expect: {
if (a)
for (;;);
else
var [ a = "PASS" ] = [];
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=6"
}
reduce_value: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function(a = "PASS") {
return a;
}());
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=6"
}
evaluate_iife: {
options = {
evaluate: true,
}
input: {
console.log(function(a = "PASS") {
return a;
}());
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=6"
}
unsafe_evaluate_iife_1: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(function([ a ] = []) {
return "PASS";
}());
}
expect: {
console.log(function([ a ] = []) {
return "PASS";
}());
}
expect_stdout: "PASS"
node_version: ">=6"
}
unsafe_evaluate_iife_2: {
options = {
evaluate: true,
reduce_vars: true,
unsafe: true,
}
input: {
console.log(function([ a ] = []) {
return a[0];
}([ [ "PASS" ] ]));
}
expect: {
console.log(function([ a ] = []) {
return a[0];
}([ [ "PASS" ] ]));
}
expect_stdout: "PASS"
node_version: ">=6"
}
inline_direct: {
options = {
default_values: true,
inline: true,
unused: true,
}
input: {
console.log(function(a = "FAIL") {
return a;
}("PASS"));
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=6"
}
inline_constant: {
options = {
inline: true,
}
input: {
console.log(function(a = console.log("foo")) {
return "bar";
}(void console.log("baz")));
}
expect: {
console.log((void console.log("baz"), console.log("foo"), "bar"));
}
expect_stdout: [
"baz",
"foo",
"bar",
]
node_version: ">=6"
}
inline_function: {
options = {
default_values: true,
inline: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
(function(a = console.log("foo"), b = console.log("bar")) {
console.log("baz");
}(void console.log("moo"), 42));
}
expect: {
console.log("moo"),
console.log("foo"),
console.log("baz");
}
expect_stdout: [
"moo",
"foo",
"baz",
]
node_version: ">=6"
}
inline_loop_1: {
options = {
inline: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
while (function f(a = "PASS") {
console.log(a);
}());
}
expect: {
while (a = "PASS", void console.log(a));
var a;
}
expect_stdout: "PASS"
node_version: ">=6"
}
inline_loop_2: {
options = {
inline: true,
toplevel: true,
}
input: {
while (function(a = [ "PASS" ]) {
var a = function f(b) {
console.log(a[b]);
}(0);
}());
}
expect: {
while (a = [ "PASS" ], a = function f(b) {
console.log(a[b]);
}(0), void 0) ;
var a;
}
expect_stdout: "PASS"
node_version: ">=6"
}
drop_empty_iife: {
options = {
side_effects: true,
}
input: {
console.log(function(a = console.log("foo")) {}(void console.log("baz")));
}
expect: {
console.log((console.log("baz"), void console.log("foo")));
}
expect_stdout: [
"baz",
"foo",
"undefined",
]
node_version: ">=6"
}
retain_empty_iife: {
options = {
side_effects: true,
}
input: {
var a;
try {
(function(a = a) {})();
} catch (e) {
console.log("PASS");
}
}
expect: {
var a;
try {
(function(a = a) {})();
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=6"
}
retain_fargs: {
options = {
unused: true,
}
input: {
(function([ a = console.log("PASS") ]) {})([]);
}
expect: {
(function([ a = console.log("PASS") ]) {})([]);
}
expect_stdout: "PASS"
node_version: ">=6"
}
drop_fargs: {
options = {
keep_fargs: "strict",
unused: true,
}
input: {
console.log(function(a = 42, b = console.log("foo"), c = true) {
return "bar";
}(console.log("baz"), "moo", false));
}
expect: {
console.log(function(b = console.log("foo")) {
return "bar";
}((console.log("baz"), "moo")));
}
expect_stdout: [
"baz",
"bar",
]
expect_warnings: [
"WARN: Dropping unused function argument c [test/compress/default-values.js:1,61]",
"WARN: Side effects in default value of unused variable b [test/compress/default-values.js:1,37]",
]
node_version: ">=6"
}
unused_var_1: {
options = {
toplevel: true,
unused: true,
}
input: {
var [ a = 42 ] = [ console.log("PASS") ];
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=6"
}
unused_var_2: {
options = {
toplevel: true,
unused: true,
}
input: {
var {
p: [ a ] = "" + console.log("FAIL"),
} = {
p: [ console.log("PASS") ],
};
}
expect: {
var {
p: [] = [ console.log("FAIL") ],
} = {
p: [ console.log("PASS") ],
};
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_var_1: {
mangle = {
toplevel: false,
}
input: {
var N = 1, [ {
pname: p = "x",
i: n = N,
}, {
[p + n]: v,
} ] = [ {}, {
x1: "PASS",
} ];
console.log(v);
}
expect: {
var N = 1, [ {
pname: p = "x",
i: n = N,
}, {
[p + n]: v,
} ] = [ {}, {
x1: "PASS",
} ];
console.log(v);
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_var_1_toplevel: {
mangle = {
toplevel: true,
}
input: {
var N = 1, [ {
pname: p = "x",
i: n = N,
}, {
[p + n]: v,
} ] = [ {}, {
x1: "PASS",
} ];
console.log(v);
}
expect: {
var o = 1, [ {
pname: a = "x",
i: e = o,
}, {
[a + e]: l,
} ] = [ {}, {
x1: "PASS",
} ];
console.log(l);
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_var_2: {
mangle = {
toplevel: false,
}
input: {
var N = 1, [ {
pname: p = "x",
i: n = N,
} = {}, {
[p + n]: v,
} ] = [ , {
x1: "PASS",
} ];
console.log(v);
}
expect: {
var N = 1, [ {
pname: p = "x",
i: n = N,
} = {}, {
[p + n]: v,
} ] = [ , {
x1: "PASS",
} ];
console.log(v);
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_var_2_toplevel: {
mangle = {
toplevel: true,
}
input: {
var N = 1, [ {
pname: p = "x",
i: n = N,
} = {}, {
[p + n]: v,
} ] = [ , {
x1: "PASS",
} ];
console.log(v);
}
expect: {
var o = 1, [ {
pname: a = "x",
i: e = o,
} = {}, {
[a + e]: l,
} ] = [ , {
x1: "PASS",
} ];
console.log(l);
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_function_1: {
mangle = {
toplevel: false,
}
input: {
var N = 1;
(function(o, {
pname: p,
} = o, {
[p + N]: v,
} = o) {
let N;
console.log(v);
})({
pname: "x",
x1: "PASS",
});
}
expect: {
var N = 1;
(function(n, {
pname: e,
} = n, {
[e + N]: o,
} = n) {
let a;
console.log(o);
})({
pname: "x",
x1: "PASS",
});
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_function_1_toplevel: {
mangle = {
toplevel: true,
}
input: {
var N = 1;
(function(o, {
pname: p,
} = o, {
[p + N]: v,
} = o) {
let N;
console.log(v);
})({
pname: "x",
x1: "PASS",
});
}
expect: {
var l = 1;
(function(n, {
pname: e,
} = n, {
[e + l]: o,
} = n) {
let a;
console.log(o);
})({
pname: "x",
x1: "PASS",
});
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_function_2: {
mangle = {
toplevel: false,
}
input: {
var N = 1;
(function({
pname: p = "x",
i: n = N,
}, {
[p + n]: v,
}) {
let N;
console.log(v);
})({}, {
x1: "PASS",
});
}
expect: {
var N = 1;
(function({
pname: n = "x",
i: o = N,
}, {
[n + o]: e,
}) {
let l;
console.log(e);
})({}, {
x1: "PASS",
});
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_function_2_toplevel: {
mangle = {
toplevel: true,
}
input: {
var N = 1;
(function({
pname: p = "x",
i: n = N,
}, {
[p + n]: v,
}) {
let N;
console.log(v);
})({}, {
x1: "PASS",
});
}
expect: {
var a = 1;
(function({
pname: n = "x",
i: o = a,
}, {
[n + o]: e,
}) {
let l;
console.log(e);
})({}, {
x1: "PASS",
});
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_arrow_1: {
mangle = {
toplevel: false,
}
input: {
var N = 1;
((o, {
pname: p,
} = o, {
[p + N]: v,
} = o) => {
let N;
console.log(v);
})({
pname: "x",
x1: "PASS",
});
}
expect: {
var N = 1;
((e, {
pname: a,
} = e, {
[a + N]: l,
} = e) => {
let n;
console.log(l);
})({
pname: "x",
x1: "PASS",
});
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_arrow_1_toplevel: {
mangle = {
toplevel: true,
}
input: {
var N = 1;
((o, {
pname: p,
} = o, {
[p + N]: v,
} = o) => {
let N;
console.log(v);
})({
pname: "x",
x1: "PASS",
});
}
expect: {
var o = 1;
((e, {
pname: a,
} = e, {
[a + o]: l,
} = e) => {
let n;
console.log(l);
})({
pname: "x",
x1: "PASS",
});
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_arrow_2: {
mangle = {
toplevel: false,
}
input: {
var N = 1;
(({
pname: p = "x",
i: n = N,
}, {
[p + n]: v,
}) => {
let N;
console.log(v);
})({}, {
x1: "PASS",
});
}
expect: {
var N = 1;
(({
pname: e = "x",
i: l = N,
}, {
[e + l]: o,
}) => {
let a;
console.log(o);
})({}, {
x1: "PASS",
});
}
expect_stdout: "PASS"
node_version: ">=6"
}
mangle_arrow_2_toplevel: {
mangle = {
toplevel: true,
}
input: {
var N = 1;
(({
pname: p = "x",
i: n = N,
}, {
[p + n]: v,
}) => {
let N;
console.log(v);
})({}, {
x1: "PASS",
});
}
expect: {
var n = 1;
(({
pname: e = "x",
i: l = n,
}, {
[e + l]: o,
}) => {
let a;
console.log(o);
})({}, {
x1: "PASS",
});
}
expect_stdout: "PASS"
node_version: ">=6"
}

View File

@@ -691,6 +691,28 @@ funarg_inline: {
node_version: ">=6" node_version: ">=6"
} }
process_boolean_returns: {
options = {
booleans: true,
}
input: {
console.log(function({ length }) {
return length ? "FAIL" : "PASS";
}(function() {
return 42;
}));
}
expect: {
console.log(function({ length }) {
return length ? "FAIL" : "PASS";
}(function() {
return 42;
}));
}
expect_stdout: "PASS"
node_version: ">=6"
}
simple_const: { simple_const: {
options = { options = {
evaluate: true, evaluate: true,

View File

@@ -3,7 +3,7 @@ var UglifyJS = require("../node");
describe("Getters and setters", function() { describe("Getters and setters", function() {
it("Should not accept operator symbols as getter/setter name", function() { it("Should not accept operator symbols as getter/setter name", function() {
var illegalOperators = [ [
"++", "++",
"--", "--",
"+", "+",
@@ -42,43 +42,26 @@ describe("Getters and setters", function() {
"&=", "&=",
"&&", "&&",
"||" "||"
]; ].reduce(function(tests, illegalOperator) {
var generator = function() { tests.push({
var results = []; code: "var obj = { get " + illegalOperator + "() { return test; }};",
operator: illegalOperator,
for (var i in illegalOperators) { });
results.push({ tests.push({
code: "var obj = { get " + illegalOperators[i] + "() { return test; }};", code: "var obj = { set " + illegalOperator + "(value) { test = value; }};",
operator: illegalOperators[i], operator: illegalOperator,
method: "get" });
}); return tests;
results.push({ }, []).forEach(function(test) {
code: "var obj = { set " + illegalOperators[i] + "(value) { test = value}};", assert.throws(function() {
operator: illegalOperators[i], UglifyJS.parse(test.code);
method: "set" }, test.operator == "=" ? function(e) {
});
}
return results;
};
var testCase = function(data) {
return function() {
UglifyJS.parse(data.code);
};
};
var fail = function(data) {
return function(e) {
return e instanceof UglifyJS.JS_Parse_Error return e instanceof UglifyJS.JS_Parse_Error
&& e.message === "Unexpected token: operator «" + data.operator + "»"; && /^Unexpected token: punc «{», expected: punc «.*?»$/.test(e.message);
}; } : function(e) {
}; return e instanceof UglifyJS.JS_Parse_Error
var errorMessage = function(data) { && e.message === "Unexpected token: operator «" + test.operator + "»";
return "Expected but didn't get a syntax error while parsing following line:\n" + data.code; }, "Expected but didn't get a syntax error while parsing following line:\n" + test.code);
}; });
var tests = generator();
for (var i = 0; i < tests.length; i++) {
var test = tests[i];
assert.throws(testCase(test), fail(test), errorMessage(test));
}
}); });
}); });

View File

@@ -211,6 +211,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
node.alternative, node.alternative,
][ ((node.start._permute += step) * steps | 0) % 3 ]; ][ ((node.start._permute += step) * steps | 0) % 3 ];
} }
else if (node instanceof U.AST_DefaultValue) {
node.start._permute++;
CHANGED = true;
return node.name;
}
else if (node instanceof U.AST_Defun) { else if (node instanceof U.AST_Defun) {
switch (((node.start._permute += step) * steps | 0) % 2) { switch (((node.start._permute += step) * steps | 0) % 2) {
case 0: case 0:

View File

@@ -137,6 +137,7 @@ var SUPPORT = function(matrix) {
catch_omit_var: "try {} catch {}", catch_omit_var: "try {} catch {}",
computed_key: "({[0]: 0});", computed_key: "({[0]: 0});",
const_block: "var a; { const a = 0; }", const_block: "var a; { const a = 0; }",
default_value: "[ a = 0 ] = [];",
destructuring: "[] = [];", destructuring: "[] = [];",
let: "let a;", let: "let a;",
spread: "[...[]];", spread: "[...[]];",
@@ -425,18 +426,35 @@ function createArgs(recurmax, stmtDepth, canThrow) {
function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was_async) { function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was_async) {
var avoid = []; var avoid = [];
var len = unique_vars.length; var len = unique_vars.length;
var pairs = createPairs(recurmax); var pairs = createPairs(recurmax, !nameLenBefore);
unique_vars.length = len; unique_vars.length = len;
return pairs; return pairs;
function createAssignmentValue(recurmax) { function fill(nameFn, valueFn) {
var save_async = async; var save_async = async;
if (was_async != null) async = was_async; if (was_async != null) {
async = false;
if (save_async || was_async) addAvoidVar("await");
}
avoid.forEach(addAvoidVar);
var save_vars = nameLenBefore && VAR_NAMES.splice(nameLenBefore); var save_vars = nameLenBefore && VAR_NAMES.splice(nameLenBefore);
var value = nameLenBefore && rng(2) ? createValue() : createExpression(recurmax, NO_COMMA, stmtDepth, canThrow); if (nameFn) nameFn();
if (was_async != null) {
async = was_async;
if (save_async || was_async) removeAvoidVar("await");
}
if (valueFn) valueFn();
if (save_vars) [].push.apply(VAR_NAMES, save_vars); if (save_vars) [].push.apply(VAR_NAMES, save_vars);
avoid.forEach(removeAvoidVar);
async = save_async; async = save_async;
return value; }
function createAssignmentValue(recurmax) {
return nameLenBefore && rng(2) ? createValue() : createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
}
function createDefaultValue(recurmax, noDefault) {
return !noDefault && SUPPORT.default_value && rng(20) == 0 ? " = " + createAssignmentValue(recurmax) : "";
} }
function createKey(recurmax, keys) { function createKey(recurmax, keys) {
@@ -459,20 +477,22 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
return name; return name;
} }
function createPairs(recurmax) { function createPairs(recurmax, noDefault) {
var names = [], values = []; var names = [], values = [];
var m = rng(4), n = rng(4); var m = rng(4), n = rng(4);
if (!nameLenBefore) m = Math.max(m, n, 1); if (!nameLenBefore) m = Math.max(m, n, 1);
for (var i = Math.max(m, n); --i >= 0;) { for (var i = Math.max(m, n); --i >= 0;) {
if (i < m && i < n) { if (i < m && i < n) {
createDestructured(recurmax, names, values); createDestructured(recurmax, noDefault, names, values);
continue; } else if (i < m) {
} var name = createName();
if (i < m) { fill(function() {
names.unshift(createName()); names.unshift(name + createDefaultValue(recurmax, noDefault));
} });
if (i < n) { } else {
values.unshift(createAssignmentValue(recurmax)); fill(null, function() {
values.unshift(createAssignmentValue(recurmax));
});
} }
} }
return { return {
@@ -481,7 +501,7 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
}; };
} }
function createDestructured(recurmax, names, values) { function createDestructured(recurmax, noDefault, names, values) {
switch (rng(20)) { switch (rng(20)) {
case 0: case 0:
if (--recurmax < 0) { if (--recurmax < 0) {
@@ -489,20 +509,25 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
values.unshift('""'); values.unshift('""');
} else { } else {
var pairs = createPairs(recurmax); var pairs = createPairs(recurmax);
while (!rng(10)) { var default_value;
var index = rng(pairs.names.length + 1); fill(function() {
pairs.names.splice(index, 0, ""); default_value = createDefaultValue(recurmax, noDefault);
if (index < pairs.values.length) { }, function() {
pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : ""); while (!rng(10)) {
} else switch (rng(5)) { var index = rng(pairs.names.length + 1);
case 0: pairs.names.splice(index, 0, "");
pairs.values[index] = createAssignmentValue(recurmax); if (index < pairs.values.length) {
case 1: pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
pairs.values.length = index + 1; } else switch (rng(5)) {
case 0:
pairs.values[index] = createAssignmentValue(recurmax);
case 1:
pairs.values.length = index + 1;
}
} }
} names.unshift("[ " + pairs.names.join(", ") + " ]" + default_value);
names.unshift("[ " + pairs.names.join(", ") + " ]"); values.unshift("[ " + pairs.values.join(", ") + " ]");
values.unshift("[ " + pairs.values.join(", ") + " ]"); });
} }
break; break;
case 1: case 1:
@@ -521,33 +546,26 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
keys[index] = key; keys[index] = key;
} }
}); });
var save_async = async; fill(function() {
if (was_async != null) { names.unshift("{ " + addTrailingComma(pairs.names.map(function(name, index) {
async = false; var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys);
if (save_async || was_async) avoid.push("await"); return key ? key + ": " + name : name;
} }).join(", ")) + " }" + createDefaultValue(recurmax, noDefault));
addAvoidVars(avoid); }, function() {
var save_vars = nameLenBefore && VAR_NAMES.splice(nameLenBefore); values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) {
names.unshift("{ " + addTrailingComma(pairs.names.map(function(name, index) { var key = index in keys ? keys[index] : createKey(recurmax, keys);
var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys); return key + ": " + value;
return key ? key + ": " + name : name; }).join(", ")) + " }");
}).join(", ")) + " }"); });
if (was_async != null) {
async = was_async;
if (save_async || was_async) removeAvoidVars([ avoid.pop() ]);
}
values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) {
var key = index in keys ? keys[index] : createKey(recurmax, keys);
return key + ": " + value;
}).join(", ")) + " }");
if (save_vars) [].push.apply(VAR_NAMES, save_vars);
removeAvoidVars(avoid);
async = save_async;
} }
break; break;
default: default:
names.unshift(createName()); var name = createName();
values.unshift(createAssignmentValue(recurmax)); fill(function() {
names.unshift(name + createDefaultValue(recurmax, noDefault));
}, function() {
values.unshift(createAssignmentValue(recurmax));
});
break; break;
} }
} }
@@ -575,8 +593,8 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
} }
unique_vars.length -= 6; unique_vars.length -= 6;
fn(function() { fn(function() {
addAvoidVars(consts); consts.forEach(addAvoidVar);
addAvoidVars(lets); lets.forEach(addAvoidVar);
if (rng(2)) { if (rng(2)) {
return createDefinitions("const", consts) + "\n" + createDefinitions("let", lets) + "\n"; return createDefinitions("const", consts) + "\n" + createDefinitions("let", lets) + "\n";
} else { } else {
@@ -610,17 +628,17 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
default: default:
s += names.map(function(name) { s += names.map(function(name) {
if (type == "let" && !rng(10)) { if (type == "let" && !rng(10)) {
removeAvoidVars([ name ]); removeAvoidVar(name);
return name; return name;
} }
var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow); var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
removeAvoidVars([ name ]); removeAvoidVar(name);
return name + " = " + value; return name + " = " + value;
}).join(", ") + ";"; }).join(", ") + ";";
names.length = 0; names.length = 0;
break; break;
} }
removeAvoidVars(names); names.forEach(removeAvoidVar);
return s; return s;
} }
} }
@@ -877,9 +895,9 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
unique_vars.length -= 6; unique_vars.length -= 6;
if (SUPPORT.computed_key && rng(10) == 0) { if (SUPPORT.computed_key && rng(10) == 0) {
s += " catch ({ message: " + message + ", "; s += " catch ({ message: " + message + ", ";
addAvoidVars([ name ]); addAvoidVar(name);
s += "[" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + "]: " + name; s += "[" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + "]: " + name;
removeAvoidVars([ name ]); removeAvoidVar(name);
s += " }) { "; s += " }) { ";
} else { } else {
s += " catch ({ name: " + name + ", message: " + message + " }) { "; s += " catch ({ name: " + name + ", message: " + message + " }) { ";
@@ -1483,15 +1501,13 @@ function createUnaryPostfix() {
return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)]; return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)];
} }
function addAvoidVars(names) { function addAvoidVar(name) {
avoid_vars = avoid_vars.concat(names); avoid_vars.push(name);
} }
function removeAvoidVars(names) { function removeAvoidVar(name) {
names.forEach(function(name) { var index = avoid_vars.lastIndexOf(name);
var index = avoid_vars.lastIndexOf(name); if (index >= 0) avoid_vars.splice(index, 1);
if (index >= 0) avoid_vars.splice(index, 1);
});
} }
function getVarName(noConst) { function getVarName(noConst) {
@@ -1799,6 +1815,8 @@ for (var round = 1; round <= num_iterations; round++) {
var orig_result = [ sandbox.run_code(original_code), sandbox.run_code(original_code, true) ]; var orig_result = [ sandbox.run_code(original_code), sandbox.run_code(original_code, true) ];
errored = typeof orig_result[0] != "string"; errored = typeof orig_result[0] != "string";
if (errored) { if (errored) {
println();
println();
println("//============================================================="); println("//=============================================================");
println("// original code"); println("// original code");
try_beautify(original_code, false, orig_result[0], println); try_beautify(original_code, false, orig_result[0], println);