aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2020-12-23 22:22:55 +0000
committerGitHub <noreply@github.com>2020-12-24 06:22:55 +0800
commit2390fae5c4b008aa1028ffdddaa071e4084ef8ac (patch)
tree962a3258aa0d53d66dda3c6736a3750b46f3f0a3 /lib
parent56fce2131c86ed58ac00bb27b308054b1c2a669f (diff)
downloadtracifyjs-2390fae5c4b008aa1028ffdddaa071e4084ef8ac.tar.gz
tracifyjs-2390fae5c4b008aa1028ffdddaa071e4084ef8ac.zip
support default values (#4442)
Diffstat (limited to 'lib')
-rw-r--r--lib/ast.js68
-rw-r--r--lib/compress.js260
-rw-r--r--lib/output.js11
-rw-r--r--lib/parse.js109
-rw-r--r--lib/transform.js4
5 files changed, 347 insertions, 105 deletions
diff --git a/lib/ast.js b/lib/ast.js
index f319a42d..60e57385 100644
--- a/lib/ast.js
+++ b/lib/ast.js
@@ -207,15 +207,25 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)"
}, AST_Statement);
-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_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 && !is_function(node[prop])) {
- throw new Error(prop + " cannot be AST_Statement");
+function validate_expression(value, prop, multiple, allow_spread, allow_hole) {
+ multiple = multiple ? "contain" : "be";
+ if (!(value instanceof AST_Node)) throw new Error(prop + " must " + multiple + " AST_Node");
+ if (value instanceof AST_DefaultValue) throw new Error(prop + " cannot " + multiple + " AST_DefaultValue");
+ if (value instanceof AST_Destructured) throw new Error(prop + " cannot " + multiple + " AST_Destructured");
+ 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", {
$documentation: "A statement consisting of an expression, i.e. a = 1 + 2",
$propdoc: {
@@ -534,7 +544,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", {
this.argnames.forEach(function(node) {
validate_destructured(node, function(node) {
if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
- });
+ }, true);
});
},
}, AST_Scope);
@@ -838,7 +848,6 @@ var AST_Const = DEFNODE("Const", null, {
validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
});
- if (node.value != null) must_be_expression(node, "value");
});
},
}, AST_Definitions);
@@ -851,7 +860,6 @@ var AST_Let = DEFNODE("Let", null, {
validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
});
- if (node.value != null) must_be_expression(node, "value");
});
},
}, AST_Definitions);
@@ -864,7 +872,6 @@ var AST_Var = DEFNODE("Var", null, {
validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
});
- if (node.value != null) must_be_expression(node, "value");
});
},
}, AST_Definitions);
@@ -873,7 +880,7 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
$documentation: "A variable declaration; only appears in a AST_Definitions node",
$propdoc: {
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) {
var node = this;
@@ -882,18 +889,34 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
if (node.value) node.value.walk(visitor);
});
},
+ _validate: function() {
+ if (this.value != null) must_be_expression(this, "value");
+ },
});
/* -----[ 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) {
node[prop].forEach(function(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 && !is_function(node)) {
- throw new Error(prop + " cannot contain AST_Statement");
- }
+ validate_expression(node, prop, true, allow_spread, allow_hole);
});
}
@@ -1048,7 +1071,7 @@ var AST_Binary = DEFNODE("Binary", "operator left right", {
});
},
_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");
must_be_expression(this, "right");
},
@@ -1131,12 +1154,13 @@ var AST_Destructured = DEFNODE("Destructured", null, {
$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_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) {
- validate_destructured(prop.value, check);
+ validate_destructured(prop.value, check, true);
});
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");
must_be_expression(this, "key");
}
- must_be_expression(this, "value");
+ if (!(this.value instanceof AST_Node)) throw new Error("value must be AST_Node");
},
});
diff --git a/lib/compress.js b/lib/compress.js
index 13b7306b..23ce712d 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -56,6 +56,7 @@ function Compressor(options, false_by_default) {
comparisons : !false_by_default,
conditionals : !false_by_default,
dead_code : !false_by_default,
+ default_values : !false_by_default,
directives : !false_by_default,
drop_console : false,
drop_debugger : !false_by_default,
@@ -607,6 +608,20 @@ merge(Compressor.prototype, {
function scan_declaration(tw, lhs, fixed, visit) {
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) {
reset_flags(node);
var save = fixed;
@@ -1184,6 +1199,11 @@ merge(Compressor.prototype, {
AST_Node.DEFMETHOD("convert_symbol", noop);
AST_Destructured.DEFMETHOD("convert_symbol", function(type, process) {
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) {
node = node.clone();
descend(node, this);
@@ -1205,8 +1225,13 @@ merge(Compressor.prototype, {
AST_SymbolDeclaration.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) {
+ if (node instanceof AST_DefaultValue) {
+ node.value.walk(tw);
+ node.name.walk(marker);
+ return true;
+ }
if (node instanceof AST_DestructuredKeyVal) {
if (node.key instanceof AST_Node) node.key.walk(tw);
node.value.walk(marker);
@@ -1215,7 +1240,9 @@ merge(Compressor.prototype, {
return process(node);
});
this.walk(marker);
- });
+ }
+ AST_DefaultValue.DEFMETHOD("mark_symbol", mark_destructured);
+ AST_Destructured.DEFMETHOD("mark_symbol", mark_destructured);
function mark_symbol(process) {
return process(this);
}
@@ -1229,6 +1256,10 @@ merge(Compressor.prototype, {
var found = false;
var tw = new TreeWalker(function(node) {
if (found) return true;
+ if (node instanceof AST_DefaultValue) {
+ node.name.walk(tw);
+ return true;
+ }
if (node instanceof AST_DestructuredKeyVal) {
if (!allow_computed_keys && node.key instanceof AST_Node) return found = true;
node.value.walk(tw);
@@ -1658,7 +1689,7 @@ merge(Compressor.prototype, {
var assign_used = false;
var can_replace = !args || !hit;
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);
}
can_replace = true;
@@ -1895,9 +1926,14 @@ merge(Compressor.prototype, {
for (var i = len; --i >= 0;) {
var sym = fn.argnames[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, {
name: sym,
- value: arg
+ value: value ? arg ? make_sequence(iife, [ arg, value ]) : value : arg,
}));
if (sym instanceof AST_Destructured) {
if (!sym.match_symbol(return_false)) continue;
@@ -1906,17 +1942,21 @@ merge(Compressor.prototype, {
}
if (sym.name in names) continue;
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);
} else if (arg instanceof AST_Lambda && arg.pinned()) {
arg = null;
- } else {
+ } else if (arg) {
arg.walk(tw);
}
- if (arg) candidates.unshift([ make_node(AST_VarDef, sym, {
+ if (!arg) continue;
+ var candidate = make_node(AST_VarDef, sym, {
name: sym,
value: arg
- }) ]);
+ });
+ candidate.index = i;
+ candidates.unshift([ candidate ]);
}
}
}
@@ -2310,14 +2350,22 @@ merge(Compressor.prototype, {
}
function remove_candidate(expr) {
- if (expr.name instanceof AST_SymbolFunarg) {
- var index = compressor.self().argnames.indexOf(expr.name);
- var args = compressor.parent().args;
- if (args[index]) {
- args[index] = make_node(AST_Number, args[index], {
+ var index = expr.index;
+ if (index >= 0) {
+ var argname = scope.argnames[index];
+ if (argname instanceof AST_DefaultValue) {
+ argname.value = make_node(AST_Number, argname, {
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;
}
@@ -3097,7 +3145,7 @@ merge(Compressor.prototype, {
|| node instanceof AST_Undefined
|| node instanceof AST_UnaryPrefix
&& node.operator == "void"
- && !node.expression.has_side_effects(compressor);
+ && !(compressor && node.expression.has_side_effects(compressor));
}
// is_truthy()
@@ -4077,10 +4125,18 @@ merge(Compressor.prototype, {
if (fn.evaluating) return this;
if (fn.name && fn.name.definition().recursive_refs > 0) 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 this;
- var args = eval_args(this.args);
if (!args && !ignore_side_effects) return this;
var stat = fn.first_statement();
if (!(stat instanceof AST_Return)) {
@@ -4104,9 +4160,10 @@ merge(Compressor.prototype, {
if (!val) return;
var cached_args = [];
if (!args || all(fn.argnames, function(sym, i) {
- var value = args[i];
+ if (sym instanceof AST_DefaultValue) sym = sym.name;
var def = sym.definition();
if (def.orig[def.orig.length - 1] !== sym) return false;
+ var value = args[i];
def.references.forEach(function(node) {
node._eval = function() {
return value;
@@ -5340,32 +5397,35 @@ merge(Compressor.prototype, {
var calls_to_drop_args = [];
var fns_with_marked_args = [];
var trimmer = new TreeTransformer(function(node) {
+ if (node instanceof AST_DefaultValue) return trim_default(tt, trimmer, node);
if (node instanceof AST_DestructuredArray) {
var trim = true;
for (var i = node.elements.length; --i >= 0;) {
- var sym = node.elements[i];
- if (!(sym instanceof AST_SymbolDeclaration)) {
- node.elements[i] = sym.transform(trimmer);
- trim = false;
- } else if (sym.definition().id in in_use_ids) {
+ var element = node.elements[i].transform(trimmer);
+ if (element) {
+ node.elements[i] = element;
trim = false;
} else if (trim) {
node.elements.pop();
} else {
- node.elements[i] = make_node(AST_Hole, sym);
+ node.elements[i] = make_node(AST_Hole, node.elements[i]);
}
}
return node;
}
if (node instanceof AST_DestructuredKeyVal) {
- if (!(node.value instanceof AST_SymbolDeclaration)) {
- node.value = node.value.transform(trimmer);
- return node;
- }
- if (typeof node.key != "string") return node;
- if (node.value.definition().id in in_use_ids) return node;
- return List.skip;
+ var retain = false;
+ if (node.key instanceof AST_Node) {
+ node.key = node.key.transform(tt);
+ retain = node.key.has_side_effects(compressor);
+ }
+ if (retain && is_decl(node.value)) return node;
+ var value = node.value.transform(trimmer);
+ 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 parent = tt.parent();
@@ -5432,21 +5492,28 @@ merge(Compressor.prototype, {
var trim = compressor.drop_fargs(node, parent);
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
- if (sym instanceof AST_Destructured) {
- sym.transform(trimmer);
- trim = false;
+ if (!(sym instanceof AST_SymbolFunarg)) {
+ var arg = sym.transform(trimmer);
+ 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;
}
var def = sym.definition();
if (def.id in in_use_ids) {
trim = false;
if (indexOf_assign(def, sym) < 0) sym.__unused = null;
+ } else if (trim) {
+ log(sym, "Dropping unused function argument {name}");
+ a.pop();
} else {
sym.__unused = true;
- if (trim) {
- log(sym, "Dropping unused function argument {name}");
- a.pop();
- }
}
}
fns_with_marked_args.push(node);
@@ -5469,6 +5536,7 @@ merge(Compressor.prototype, {
if (def.name instanceof AST_Destructured) {
var value = def.value;
var trimmer = new TreeTransformer(function(node) {
+ if (node instanceof AST_DefaultValue) return trim_default(tt, trimmer, node);
if (node instanceof AST_DestructuredArray) {
var save = value;
if (value instanceof AST_SymbolRef) value = value.fixed_value();
@@ -5514,7 +5582,7 @@ merge(Compressor.prototype, {
value = values && values[prop.key];
retain = false;
}
- if (retain && prop.value instanceof AST_SymbolDeclaration) {
+ if (retain && is_decl(prop.value)) {
properties.push(prop);
} else {
var newValue = prop.value.transform(trimmer);
@@ -5962,6 +6030,38 @@ merge(Compressor.prototype, {
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) {
@@ -6168,7 +6268,8 @@ merge(Compressor.prototype, {
if (!(exp instanceof AST_Lambda)) return;
if (exp.uses_arguments || exp.pinned()) return;
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") {
compressor.pop();
var in_bool = compressor.in_boolean_context();
@@ -7447,6 +7548,11 @@ merge(Compressor.prototype, {
var side_effects = [];
for (var i = 0; i < args.length; 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) {
var node = args[i].drop_side_effect_free(compressor);
if (drop_fargs(argname)) {
@@ -7779,22 +7885,31 @@ merge(Compressor.prototype, {
}
}
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 can_inline = is_func
- && compressor.option("inline")
- && !self.is_expr_pure(compressor)
- && all(fn.argnames, function(argname) {
- return !(argname instanceof AST_Destructured);
- })
- && all(self.args, function(arg) {
- return !(arg instanceof AST_Spread);
- });
+ var has_default = false;
+ var can_drop = is_func && all(fn.argnames, function(argname, index) {
+ if (argname instanceof AST_DefaultValue) {
+ has_default = true;
+ var arg = self.args[index];
+ if (arg && !is_undefined(arg)) return false;
+ var abort = false;
+ 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) {
var value = stat.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, args).optimize(compressor);
+ return make_sequence(self, convert_args(value)).optimize(compressor);
}
}
if (is_func) {
@@ -7805,6 +7920,9 @@ merge(Compressor.prototype, {
&& !(fn.name && fn instanceof AST_Function)
&& (exp === fn || !recursive_ref(compressor, def = exp.definition())
&& fn.is_constant_expression(find_scope(compressor)))
+ && all(self.args, function(arg) {
+ return !(arg instanceof AST_Spread);
+ })
&& (value = can_flatten_body(stat))
&& !fn.contains_this()) {
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")
+ && can_drop
&& all(fn.body, is_empty)
&& (fn !== exp || fn_name_unused(fn, compressor))
- && !(fn instanceof AST_Arrow && fn.value)
- && all(fn.argnames, function(argname) {
- 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);
+ && !(fn instanceof AST_Arrow && fn.value)) {
+ return make_sequence(self, convert_args()).optimize(compressor);
}
}
if (compressor.option("drop_console")) {
@@ -7881,6 +7991,19 @@ merge(Compressor.prototype, {
}
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) {
if (!is_async(scope || compressor.find_parent(AST_Scope))) return true;
var safe = true;
@@ -7948,6 +8071,7 @@ merge(Compressor.prototype, {
}
function can_substitute_directly() {
+ if (has_default) return;
if (var_assigned) return;
if (compressor.option("inline") < 2 && fn.argnames.length) return;
if (!fn.variables.all(function(def) {
@@ -8017,6 +8141,7 @@ merge(Compressor.prototype, {
for (var i = 0; i < fn.argnames.length; i++) {
var arg = fn.argnames[i];
if (arg.__unused) continue;
+ if (arg instanceof AST_DefaultValue) arg = arg.name;
if (!safe_to_inject || var_exists(defined, arg.name)) return false;
used[arg.name] = true;
if (in_loop) in_loop.push(arg.definition());
@@ -8115,6 +8240,10 @@ merge(Compressor.prototype, {
for (i = len; --i >= 0;) {
var name = fn.argnames[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 (value) expressions.push(value);
} else {
@@ -8148,6 +8277,7 @@ merge(Compressor.prototype, {
}
append_var(decls, expressions, name, var_def.value);
if (in_loop && all(fn.argnames, function(argname) {
+ if (argname instanceof AST_DefaultValue) argname = argname.name;
return argname.name != name.name;
})) {
var def = fn.variables.get(name.name);
@@ -9936,13 +10066,13 @@ merge(Compressor.prototype, {
var argname = fn.argnames[index];
if (def.deleted && def.deleted[index]) {
argname = null;
- } else if (argname instanceof AST_Destructured) {
+ } else if (argname && !(argname instanceof AST_SymbolFunarg)) {
argname = null;
} else if (argname && (compressor.has_directive("use strict")
|| fn.name
|| !(fn_parent instanceof AST_Call && index < fn_parent.args.length)
|| !all(fn.argnames, function(argname) {
- return !(argname instanceof AST_Destructured);
+ return argname instanceof AST_SymbolFunarg;
}))) {
var arg_def = argname.definition();
if (!compressor.option("reduce_vars")
diff --git a/lib/output.js b/lib/output.js
index 3d8d74c9..cd36f74a 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -702,6 +702,8 @@ function OutputStream(options) {
// (false, true) ? (a = 10, b = 20) : (c = 30)
// ==> 20 (side effect, set a := 10 and b := 20)
|| p instanceof AST_Conditional
+ // [ a = (1, 2) ] = [] ==> a == 2
+ || p instanceof AST_DefaultValue
// { [(1, 2)]: 3 }[2] ==> 3
// { foo: (1, 2) }.foo ==> 2
|| 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 ]----- */
function print_call_args(self, output) {
if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) {
diff --git a/lib/parse.js b/lib/parse.js
index 87293cab..c8ad531f 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -1041,11 +1041,30 @@ function parse($TEXT, options) {
function to_funarg(node) {
if (node instanceof AST_Array) return new AST_DestructuredArray({
start: node.start,
- elements: node.elements.map(function(node) {
- return node instanceof AST_Hole ? node : to_funarg(node);
- }),
+ elements: node.elements.map(to_funarg),
+ 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({
start: node.start,
properties: node.properties.map(function(prop) {
@@ -1122,7 +1141,7 @@ function parse($TEXT, options) {
var was_funarg = S.in_funarg;
S.in_funarg = S.in_function;
var argnames = expr_list(")", !options.strict, false, function() {
- return maybe_destructured(AST_SymbolFunarg);
+ return maybe_default(AST_SymbolFunarg);
});
S.in_funarg = was_funarg;
var loop = S.in_loop;
@@ -1468,6 +1487,32 @@ function parse($TEXT, options) {
}));
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();
if (is("punc", "(")) {
var func_start = S.token;
@@ -1492,15 +1537,6 @@ function parse($TEXT, options) {
}));
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) {
case "async":
key = as_property_key();
@@ -1601,7 +1637,7 @@ function parse($TEXT, options) {
return new AST_DestructuredArray({
start: start,
elements: expr_list("]", !options.strict, true, function() {
- return maybe_destructured(type);
+ return maybe_default(type);
}),
end: prev(),
});
@@ -1620,15 +1656,25 @@ function parse($TEXT, options) {
a.push(new AST_DestructuredKeyVal({
start: key_start,
key: key,
- value: maybe_destructured(type),
+ value: maybe_default(type),
end: prev(),
}));
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({
start: key_start,
key: key_start.value,
- value: as_symbol(type),
+ value: name,
end: prev(),
}));
}
@@ -1642,6 +1688,19 @@ function parse($TEXT, options) {
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) {
var start = call.start;
var comments = start.comments_before;
@@ -1788,20 +1847,34 @@ function parse($TEXT, options) {
if (node instanceof AST_Array) {
var elements = node.elements.map(to_destructured);
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({
start: node.start,
elements: elements,
end: node.end,
}) : 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;
var props = [];
for (var i = 0; i < node.properties.length; i++) {
var prop = node.properties[i];
if (!(prop instanceof AST_ObjectKeyVal)) return node;
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({
start: prop.start,
key: prop.key,
diff --git a/lib/transform.js b/lib/transform.js
index 5372cc59..0b92f8b5 100644
--- a/lib/transform.js
+++ b/lib/transform.js
@@ -126,6 +126,10 @@ TreeTransformer.prototype = new TreeWalker;
self.name = self.name.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) {
if (self.name) self.name = self.name.transform(tw);
self.argnames = do_list(self.argnames, tw);