diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2020-12-23 22:22:55 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-24 06:22:55 +0800 |
commit | 2390fae5c4b008aa1028ffdddaa071e4084ef8ac (patch) | |
tree | 962a3258aa0d53d66dda3c6736a3750b46f3f0a3 /lib | |
parent | 56fce2131c86ed58ac00bb27b308054b1c2a669f (diff) | |
download | tracifyjs-2390fae5c4b008aa1028ffdddaa071e4084ef8ac.tar.gz tracifyjs-2390fae5c4b008aa1028ffdddaa071e4084ef8ac.zip |
support default values (#4442)
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ast.js | 68 | ||||
-rw-r--r-- | lib/compress.js | 260 | ||||
-rw-r--r-- | lib/output.js | 11 | ||||
-rw-r--r-- | lib/parse.js | 109 | ||||
-rw-r--r-- | lib/transform.js | 4 |
5 files changed, 347 insertions, 105 deletions
@@ -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); |