diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2020-11-17 00:01:24 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-17 08:01:24 +0800 |
commit | e5f80afc53e7df3b0b57931a03dc3481ddd9c30e (patch) | |
tree | 2c3686f36caccfd0a642a344eb6bcde728d80f6a /lib/compress.js | |
parent | 42e34c870ad5979135ab7407ab62abce9507fc28 (diff) | |
download | tracifyjs-e5f80afc53e7df3b0b57931a03dc3481ddd9c30e.tar.gz tracifyjs-e5f80afc53e7df3b0b57931a03dc3481ddd9c30e.zip |
support destructured literals (#4278)
Diffstat (limited to 'lib/compress.js')
-rw-r--r-- | lib/compress.js | 798 |
1 files changed, 568 insertions, 230 deletions
diff --git a/lib/compress.js b/lib/compress.js index 9eb4a9e8..d9446e87 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -368,6 +368,15 @@ merge(Compressor.prototype, { } while (sym = sym.parent_scope); } + function can_drop_symbol(tw, ref, keep_lambda) { + var orig = ref.definition().orig; + if (ref.in_arg && (orig[0] instanceof AST_SymbolFunarg || orig[1] instanceof AST_SymbolFunarg)) return false; + return all(orig, function(sym) { + return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet + || keep_lambda && sym instanceof AST_SymbolLambda); + }); + } + (function(def) { def(AST_Node, noop); @@ -585,6 +594,47 @@ merge(Compressor.prototype, { if (is_arguments(def) && node.property instanceof AST_Number) def.reassigned = true; } + function scan_declaration(tw, lhs, fixed, visit) { + var scanner = new TreeWalker(function(node) { + if (node instanceof AST_DestructuredArray) { + var save = fixed; + node.elements.forEach(function(node, index) { + if (node instanceof AST_Hole) return; + fixed = function() { + return make_node(AST_Sub, node, { + expression: save(), + property: make_node(AST_Number, node, { + value: index + }) + }); + }; + node.walk(scanner); + }); + fixed = save; + return true; + } + if (node instanceof AST_DestructuredObject) { + var save = fixed; + node.properties.forEach(function(node) { + if (node.key instanceof AST_Node) node.key.walk(tw); + fixed = function() { + var key = node.key; + return make_node(typeof key == "string" ? AST_Dot : AST_Sub, node, { + expression: save(), + property: key + }); + }; + node.value.walk(scanner); + }); + fixed = save; + return true; + } + visit(node, fixed); + return true; + }); + lhs.walk(scanner); + } + def(AST_Accessor, function(tw, descend, compressor) { push(tw); reset_variables(tw, compressor, this); @@ -595,52 +645,66 @@ merge(Compressor.prototype, { }); def(AST_Assign, function(tw, descend, compressor) { var node = this; - var eq = node.operator == "="; - var sym = node.left; - if (eq && sym.equivalent_to(node.right) && !sym.has_side_effects(compressor)) { + var left = node.left; + if (node.operator == "=" && left.equivalent_to(node.right) && !left.has_side_effects(compressor)) { node.right.walk(tw); - walk_prop(sym); + walk_prop(left); node.__drop = true; - return true; - } - if (!(sym instanceof AST_SymbolRef)) { - mark_assignment_to_arguments(sym); - return; - } - var d = sym.definition(); - d.assignments++; - var fixed = d.fixed; - var value = eq ? node.right : node; - if (is_modified(compressor, tw, node, value, 0)) { - d.fixed = false; + } else if (!(left instanceof AST_Destructured || left instanceof AST_SymbolRef)) { + mark_assignment_to_arguments(left); return; - } - var safe = eq || safe_to_read(tw, d); - node.right.walk(tw); - if (safe && safe_to_assign(tw, d)) { - push_ref(d, sym); - mark(tw, d); - if (eq) { - tw.loop_ids[d.id] = tw.in_loop; - mark_escaped(tw, d, sym.scope, node, value, 0, 1); - sym.fixed = d.fixed = function() { - return node.right; - }; - } else { + } else if (node.operator == "=") { + node.right.walk(tw); + scan_declaration(tw, left, function() { + return node.right; + }, function(sym, fixed) { + if (!(sym instanceof AST_SymbolRef)) { + mark_assignment_to_arguments(sym); + sym.walk(tw); + return; + } + var d = sym.definition(); + d.assignments++; + if (!is_modified(compressor, tw, node, node.right, 0) + && can_drop_symbol(tw, sym) && safe_to_assign(tw, d)) { + push_ref(d, sym); + mark(tw, d); + tw.loop_ids[d.id] = tw.in_loop; + mark_escaped(tw, d, sym.scope, node, node.right, 0, 1); + sym.fixed = d.fixed = fixed; + sym.fixed.assigns = [ node ]; + } else { + sym.walk(tw); + d.fixed = false; + } + }); + } else { + var d = left.definition(); + d.assignments++; + var fixed = d.fixed; + if (is_modified(compressor, tw, node, node, 0)) { + d.fixed = false; + return; + } + var safe = safe_to_read(tw, d); + node.right.walk(tw); + if (safe && safe_to_assign(tw, d)) { + push_ref(d, left); + mark(tw, d); if (d.single_use) d.single_use = false; - sym.fixed = d.fixed = function() { + left.fixed = d.fixed = function() { return make_node(AST_Binary, node, { operator: node.operator.slice(0, -1), - left: make_ref(sym, fixed), + left: make_ref(left, fixed), right: node.right }); }; + left.fixed.assigns = !fixed || !fixed.assigns ? [] : fixed.assigns.slice(); + left.fixed.assigns.push(node); + } else { + left.walk(tw); + d.fixed = false; } - sym.fixed.assigns = eq || !fixed || !fixed.assigns ? [] : fixed.assigns.slice(); - sym.fixed.assigns.push(node); - } else { - sym.walk(tw); - d.fixed = false; } return true; @@ -766,10 +830,12 @@ merge(Compressor.prototype, { push(tw); var init = this.init; init.walk(tw); - if (init instanceof AST_SymbolRef) { + if (init instanceof AST_Definitions) { + init.definitions[0].name.match_symbol(function(node) { + if (node instanceof AST_SymbolDeclaration) node.definition().fixed = false; + }); + } else if (init instanceof AST_SymbolRef) { init.definition().fixed = false; - } else if (init instanceof AST_Var) { - init.definitions[0].name.definition().fixed = false; } this.body.walk(tw); pop(tw); @@ -793,22 +859,26 @@ merge(Compressor.prototype, { // Virtually turn IIFE parameters into variable definitions: // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})() // So existing transformation rules can work on them. + var safe = !fn.uses_arguments || tw.has_directive("use strict"); fn.argnames.forEach(function(arg, i) { - var d = arg.definition(); - if (d.fixed === undefined && (!fn.uses_arguments || tw.has_directive("use strict"))) { - mark(tw, d); - tw.loop_ids[d.id] = tw.in_loop; - var value = iife.args[i]; - d.fixed = function() { - var j = fn.argnames.indexOf(arg); - return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife); - }; - d.fixed.assigns = [ arg ]; - } else { - d.fixed = false; - } + var value = iife.args[i]; + scan_declaration(tw, arg, function() { + var j = fn.argnames.indexOf(arg); + return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife); + }, function(node, fixed) { + var d = node.definition(); + if (safe && d.fixed === undefined) { + mark(tw, d); + tw.loop_ids[d.id] = tw.in_loop; + var value = iife.args[i]; + d.fixed = fixed; + d.fixed.assigns = [ arg ]; + } else { + d.fixed = false; + } + }); }); - descend(); + walk_body(fn, tw); var safe_ids = tw.safe_ids; pop(tw); walk_defuns(tw, fn); @@ -881,6 +951,7 @@ merge(Compressor.prototype, { } else if (d.fixed === undefined || !safe_to_read(tw, d)) { d.fixed = false; } else if (d.fixed) { + if (this.in_arg && d.orig[0] instanceof AST_SymbolLambda) this.fixed = d.scope; var value = this.fixed_value(); var recursive = recursive_ref(tw, d); if (recursive) { @@ -908,7 +979,7 @@ merge(Compressor.prototype, { } mark_escaped(tw, d, this.scope, this, value, 0, 1); } - this.fixed = d.fixed; + if (!this.fixed) this.fixed = d.fixed; var parent; if (d.fixed instanceof AST_Defun && !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) { @@ -988,22 +1059,24 @@ merge(Compressor.prototype, { } return true; }); - def(AST_VarDef, function(tw, descend) { + def(AST_VarDef, function(tw) { var node = this; if (!node.value) return; - descend(); - var d = node.name.definition(); - if (safe_to_assign(tw, d, true)) { - mark(tw, d); - tw.loop_ids[d.id] = tw.in_loop; - d.fixed = function() { - return node.value; - }; - d.fixed.assigns = [ node ]; - if (node.name instanceof AST_SymbolConst && d.redefined()) d.single_use = false; - } else { - d.fixed = false; - } + node.value.walk(tw); + scan_declaration(tw, node.name, function() { + return node.value; + }, function(name, fixed) { + var d = name.definition(); + if (safe_to_assign(tw, d, true)) { + mark(tw, d); + tw.loop_ids[d.id] = tw.in_loop; + d.fixed = fixed; + d.fixed.assigns = [ node ]; + if (name instanceof AST_SymbolConst && d.redefined()) d.single_use = false; + } else { + d.fixed = false; + } + }); return true; }); def(AST_While, function(tw, descend) { @@ -1059,6 +1132,64 @@ merge(Compressor.prototype, { return sym instanceof AST_SymbolLambda && def.scope.name === sym; }); + 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_Destructured) { + node = node.clone(); + descend(node, this); + return node; + } + if (node instanceof AST_DestructuredKeyVal) { + node = node.clone(); + node.value = node.value.transform(this); + return node; + } + return node.convert_symbol(type, process); + })); + }); + function convert_symbol(type, process) { + var node = make_node(type, this, this); + process(node, this); + return node; + } + AST_SymbolDeclaration.DEFMETHOD("convert_symbol", convert_symbol); + AST_SymbolRef.DEFMETHOD("convert_symbol", convert_symbol); + + AST_Destructured.DEFMETHOD("mark_symbol", function(process, tw) { + var marker = new TreeWalker(function(node) { + if (node instanceof AST_DestructuredKeyVal) { + if (node.key instanceof AST_Node) node.key.walk(tw); + node.value.walk(marker); + return true; + } + return process(node); + }); + this.walk(marker); + }); + function mark_symbol(process) { + return process(this); + } + AST_SymbolDeclaration.DEFMETHOD("mark_symbol", mark_symbol); + AST_SymbolRef.DEFMETHOD("mark_symbol", mark_symbol); + + AST_Node.DEFMETHOD("match_symbol", function(predicate) { + return predicate(this); + }); + AST_Destructured.DEFMETHOD("match_symbol", function(predicate) { + var found = false; + var tw = new TreeWalker(function(node) { + if (found) return true; + if (node instanceof AST_DestructuredKeyVal) { + node.value.walk(tw); + return true; + } + if (predicate(node)) return found = true; + }); + this.walk(tw); + return found; + }); + function find_scope(compressor) { var level = 0, node; while (node = compressor.parent(level++)) { @@ -1557,6 +1688,8 @@ merge(Compressor.prototype, { } if (node instanceof AST_Debugger) return true; if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name; + if (node instanceof AST_Destructured) return parent instanceof AST_Assign; + if (node instanceof AST_DestructuredKeyVal) return node.key instanceof AST_Node; if (node instanceof AST_DWLoop) return true; if (node instanceof AST_LoopControl) return true; if (node instanceof AST_SymbolRef) { @@ -1589,6 +1722,9 @@ merge(Compressor.prototype, { } if (!(fn instanceof AST_Lambda)) return true; if (def && recursive_ref(compressor, def)) return true; + if (!all(fn.argnames, function(argname) { + return !(argname instanceof AST_Destructured); + })) return true; if (fn.collapse_scanning) return false; fn.collapse_scanning = true; var replace = can_replace; @@ -1636,13 +1772,20 @@ merge(Compressor.prototype, { } if (node instanceof AST_This) return symbol_in_lvalues(node, parent); if (node instanceof AST_VarDef) { - if (!node.value) return false; - return lvalues.has(node.name.name) || side_effects && may_modify(node.name); + if ((in_try || !lhs_local) && node.name instanceof AST_Destructured) return true; + return node.value && node.name.match_symbol(function(node) { + return node instanceof AST_SymbolDeclaration + && (lvalues.has(node.name) || side_effects && may_modify(node)); + }); } var sym = is_lhs(node.left, node); + if (!sym) return false; if (sym instanceof AST_PropAccess) return true; - if (!(sym instanceof AST_SymbolRef)) return false; - return lvalues.has(sym.name) || read_toplevel && compressor.exposed(sym.definition()); + if ((in_try || !lhs_local) && sym instanceof AST_Destructured) return true; + return sym.match_symbol(function(node) { + return node instanceof AST_SymbolRef + && (lvalues.has(node.name) || read_toplevel && compressor.exposed(node.definition())); + }); } function extract_args() { @@ -1665,6 +1808,7 @@ merge(Compressor.prototype, { name: sym, value: arg })); + if (sym instanceof AST_Destructured) continue; if (sym.name in names) continue; names[sym.name] = true; if (!arg) { @@ -1700,7 +1844,7 @@ merge(Compressor.prototype, { if (expr instanceof AST_Array) { expr.elements.forEach(extract_candidates); } else if (expr instanceof AST_Assign) { - candidates.push(hit_stack.slice()); + if (!(expr.left instanceof AST_Destructured)) candidates.push(hit_stack.slice()); extract_candidates(expr.left); extract_candidates(expr.right); if (expr.left instanceof AST_SymbolRef) { @@ -2057,7 +2201,7 @@ merge(Compressor.prototype, { } if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0)); if (find_arguments && node instanceof AST_Sub) { - scope.argnames.forEach(function(argname) { + scope.each_argname(function(argname) { if (!compressor.option("reduce_vars") || argname.definition().assignments) { lvalues.add(argname.name, true); } @@ -2642,6 +2786,7 @@ merge(Compressor.prototype, { function merge_conditional_assignments(var_def, exprs, keep) { if (!compressor.option("conditionals")) return; + if (var_def.name instanceof AST_Destructured) return; var trimmed = false; var def = var_def.name.definition(); while (exprs.length > keep) { @@ -2759,11 +2904,15 @@ merge(Compressor.prototype, { } } else if (stat instanceof AST_ForIn) { if (defs && defs.TYPE == stat.init.TYPE) { - defs.definitions = defs.definitions.concat(stat.init.definitions); - var name = stat.init.definitions[0].name; - var ref = make_node(AST_SymbolRef, name, name); - name.definition().references.push(ref); - stat.init = ref; + var defns = defs.definitions.slice(); + stat.init = stat.init.definitions[0].name.convert_symbol(AST_SymbolRef, function(ref, name) { + defns.push(make_node(AST_VarDef, name, { + name: name, + value: null + })); + name.definition().references.push(ref); + }); + defs.definitions = defns; CHANGED = true; } stat.object = join_assigns_expr(stat.object); @@ -2821,10 +2970,14 @@ merge(Compressor.prototype, { var block; stat.walk(new TreeWalker(function(node, descend) { if (node instanceof AST_Definitions) { - if (node.remove_initializers(compressor)) { + var defns = []; + if (node.remove_initializers(compressor, defns)) { AST_Node.warn("Dropping initialization in unreachable code [{file}:{line},{col}]", node.start); } - push(node); + if (defns.length > 0) { + node.definitions = defns; + push(node); + } return true; } if (node instanceof AST_Defun) { @@ -3261,8 +3414,10 @@ merge(Compressor.prototype, { var unary_side_effects = makePredicate("delete ++ --"); function is_lhs(node, parent) { - if (parent instanceof AST_Unary && unary_side_effects[parent.operator]) return parent.expression; - if (parent instanceof AST_Assign && parent.left === node) return node; + if (parent instanceof AST_Assign) return parent.left === node && node; + if (parent instanceof AST_Destructured) return node; + if (parent instanceof AST_DestructuredKeyVal) return node; + if (parent instanceof AST_Unary) return unary_side_effects[parent.operator] && parent.expression; } (function(def) { @@ -3831,6 +3986,9 @@ 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) { + 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(); @@ -4094,6 +4252,16 @@ merge(Compressor.prototype, { def(AST_Definitions, function(compressor) { return any(this.definitions, compressor); }); + def(AST_DestructuredArray, function(compressor) { + return any(this.elements, compressor); + }); + def(AST_DestructuredKeyVal, function(compressor) { + return this.key instanceof AST_Node && this.key.has_side_effects(compressor) + || this.value.has_side_effects(compressor); + }); + def(AST_DestructuredObject, function(compressor) { + return any(this.properties, compressor); + }); def(AST_Dot, function(compressor) { return this.expression.may_throw_on_access(compressor) || this.expression.has_side_effects(compressor); @@ -4132,9 +4300,7 @@ merge(Compressor.prototype, { }); def(AST_SymbolDeclaration, return_false); def(AST_SymbolRef, function(compressor) { - return !(this.is_declared(compressor) && all(this.definition().orig, function(sym) { - return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet); - })); + return !this.is_declared(compressor) || !can_drop_symbol(compressor, this); }); def(AST_This, return_false); def(AST_Try, function(compressor) { @@ -4461,12 +4627,26 @@ merge(Compressor.prototype, { var prev = Object.create(null); var tw = new TreeWalker(function(node, descend) { if (node instanceof AST_Assign) { - var sym = node.left; - if (!(sym instanceof AST_SymbolRef)) return; - if (node.operator != "=") mark(sym, true, false); - node.right.walk(tw); - mark(sym, false, true); - return true; + var lhs = node.left; + if (lhs instanceof AST_Destructured) { + node.right.walk(tw); + lhs.mark_symbol(function(node) { + if (node instanceof AST_SymbolRef) { + mark(node, false, true); + } else { + node.walk(tw); + } + return true; + }, tw); + return true; + } + if (lhs instanceof AST_SymbolRef) { + if (node.operator != "=") mark(lhs, true, false); + node.right.walk(tw); + mark(lhs, false, true); + return true; + } + return; } if (node instanceof AST_Binary) { if (!lazy_op[node.operator]) return; @@ -4491,13 +4671,6 @@ merge(Compressor.prototype, { pop(); return true; } - if (node instanceof AST_Const) { - node.definitions.forEach(function(defn) { - references[defn.name.definition().id] = false; - defn.value.walk(tw); - }); - return true; - } if (node instanceof AST_Continue) { var target = tw.loopcontrol_target(node); if (target instanceof AST_Do) insert(target); @@ -4556,21 +4729,16 @@ merge(Compressor.prototype, { pop(); return true; } - if (node instanceof AST_Let) { - node.definitions.forEach(function(defn) { - references[defn.name.definition().id] = false; - if (defn.value) defn.value.walk(tw); - }); - return true; - } if (node instanceof AST_Scope) { push(); segment.block = node; if (node === self) root = segment; if (node instanceof AST_Lambda) { if (node.name) references[node.name.definition().id] = false; - if (node.uses_arguments && !tw.has_directive("use strict")) node.argnames.forEach(function(node) { + node.each_argname(node.uses_arguments && !tw.has_directive("use strict") ? function(node) { references[node.definition().id] = false; + } : function(node) { + mark(node, false, true); }); } descend(); @@ -4596,10 +4764,6 @@ merge(Compressor.prototype, { }); return true; } - if (node instanceof AST_SymbolFunarg) { - if (!node.__unused) mark(node, false, true); - return true; - } if (node instanceof AST_SymbolRef) { mark(node, true, false); return true; @@ -4631,17 +4795,27 @@ merge(Compressor.prototype, { return true; } if (node instanceof AST_VarDef) { - if (node.value) { - node.value.walk(tw); - mark(node.name, false, true); - } else { - var id = node.name.definition().id; - if (!(id in references)) { - declarations.add(id, node.name); + if (node.value) node.value.walk(tw); + node.name.mark_symbol(node.value ? function(node) { + if (!(node instanceof AST_SymbolDeclaration)) return; + if (node instanceof AST_SymbolVar) { + mark(node, false, true); + } else { + references[node.definition().id] = false; + } + return true; + } : function(node) { + if (!(node instanceof AST_SymbolDeclaration)) return; + var id = node.definition().id; + if (!(node instanceof AST_SymbolVar)) { + references[id] = false; + } else if (!(id in references)) { + declarations.add(id, node); } else if (references[id]) { - references[id].push(node.name); + references[id].push(node); } - } + return true; + }, tw); return true; } if (node instanceof AST_While) { @@ -4711,7 +4885,7 @@ merge(Compressor.prototype, { } function mark(sym, read, write) { - var def = sym.definition(); + var def = sym.definition(), ldef; if (def.id in references) { var refs = references[def.id]; if (!refs) return; @@ -4723,7 +4897,10 @@ merge(Compressor.prototype, { } else if (!read) { return; } - } else if (self.variables.get(def.name) !== def || compressor.exposed(def) || sym.name == "arguments") { + } else if ((ldef = self.variables.get(def.name)) !== def) { + if (ldef && root === segment) references[ldef.id] = false; + return references[def.id] = false; + } else if (compressor.exposed(def) || sym.name == "arguments") { return references[def.id] = false; } else { var refs = declarations.get(def.id) || []; @@ -4781,8 +4958,8 @@ merge(Compressor.prototype, { var self = this; var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs; var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars; - var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) { - var sym; + var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(tw, node, props) { + var sym, nested = false; if (node instanceof AST_Assign) { if (node.write_only || node.operator == "=") sym = node.left; } else if (node instanceof AST_Unary) { @@ -4792,13 +4969,12 @@ merge(Compressor.prototype, { while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) { if (sym instanceof AST_Sub) props.unshift(sym.property); sym = sym.expression; + nested = true; } } if (!(sym instanceof AST_SymbolRef)) return; if (compressor.exposed(sym.definition())) return; - if (!all(sym.definition().orig, function(sym) { - return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLambda || sym instanceof AST_SymbolLet); - })) return; + if (!can_drop_symbol(tw, sym, nested)) return; return sym; }; var assign_in_use = Object.create(null); @@ -4823,7 +4999,7 @@ merge(Compressor.prototype, { var scope = this; var tw = new TreeWalker(function(node, descend) { if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) { - node.argnames.forEach(function(argname) { + node.each_argname(function(argname) { var def = argname.definition(); if (!(def.id in in_use_ids)) { in_use_ids[def.id] = true; @@ -4844,24 +5020,28 @@ merge(Compressor.prototype, { } if (node instanceof AST_Definitions) { node.definitions.forEach(function(defn) { - var def = defn.name.definition(); - var_defs_by_id.add(def.id, defn); - if (node instanceof AST_Var && def.orig[0] instanceof AST_SymbolCatch) { - var redef = def.redefined(); - if (redef) var_defs_by_id.add(redef.id, defn); - } - if ((!drop_vars || (node instanceof AST_Const ? def.redefined() : def.const_redefs)) - && !(def.id in in_use_ids)) { - in_use_ids[def.id] = true; - in_use.push(def); - } - if (!defn.value) return; - if (defn.value.has_side_effects(compressor)) { - defn.value.walk(tw); - } else { - initializations.add(def.id, defn.value); - } - assignments.add(def.id, defn); + var side_effects = defn.value + && (defn.name instanceof AST_Destructured || defn.value.has_side_effects(compressor)); + defn.name.mark_symbol(function(name) { + if (!(name instanceof AST_SymbolDeclaration)) return; + var def = name.definition(); + var_defs_by_id.add(def.id, defn); + if (node instanceof AST_Var && def.orig[0] instanceof AST_SymbolCatch) { + var redef = def.redefined(); + if (redef) var_defs_by_id.add(redef.id, defn); + } + if ((!drop_vars || (node instanceof AST_Const ? def.redefined() : def.const_redefs)) + && !(def.id in in_use_ids)) { + in_use_ids[def.id] = true; + in_use.push(def); + } + if (defn.value) { + if (!side_effects) initializations.add(def.id, defn.value); + assignments.add(def.id, defn); + } + return true; + }, tw); + if (side_effects) defn.value.walk(tw); }); return true; } @@ -4916,10 +5096,14 @@ merge(Compressor.prototype, { var drop_fn_name = compressor.option("keep_fnames") ? return_false : compressor.option("ie8") ? function(def) { return !compressor.exposed(def) && def.references.length == def.replaced; } : function(def) { - // any declarations with same name will overshadow - // name of this anonymous function and can therefore - // never be used anywhere - return !(def.id in in_use_ids) || def.orig.length > 1; + if (!(def.id in in_use_ids)) return true; + if (def.orig.length < 2) return false; + // function argument will always overshadow its name + if (def.orig[1] instanceof AST_SymbolFunarg) return true; + // retain if referenced within destructured object of argument + return all(def.references, function(ref) { + return !ref.in_arg; + }); }; // pass 3: we should drop declarations not in_use var unused_fn_names = []; @@ -4928,7 +5112,7 @@ merge(Compressor.prototype, { var tt = new TreeTransformer(function(node, descend, in_list) { var parent = tt.parent(); if (drop_vars) { - var props = [], sym = assign_as_unused(node, props); + var props = [], sym = assign_as_unused(tt, node, props); if (sym) { var def = sym.definition(); var in_use = def.id in in_use_ids; @@ -4990,6 +5174,38 @@ 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(new TreeTransformer(function(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_SymbolFunarg)) { + node.elements[i] = sym.transform(this); + trim = false; + } else if (sym.definition().id in in_use_ids) { + trim = false; + } else if (trim) { + node.elements.pop(); + } else { + node.elements[i] = make_node(AST_Hole, sym); + } + } + return node; + } + if (node instanceof AST_DestructuredKeyVal) { + if (!(node.value instanceof AST_SymbolFunarg)) { + node.value = node.value.transform(this); + return node; + } + if (typeof node.key != "string") return node; + if (node.value.definition().id in in_use_ids) return node; + return List.skip; + } + })); + trim = false; + continue; + } var def = sym.definition(); if (def.id in in_use_ids) { trim = false; @@ -5015,6 +5231,84 @@ merge(Compressor.prototype, { var duplicated = 0; node.definitions.forEach(function(def) { if (def.value) def.value = def.value.transform(tt); + if (def.name instanceof AST_Destructured) { + var value = def.value; + var trimmer = new TreeTransformer(function(node) { + if (node instanceof AST_DestructuredArray) { + var save = value; + if (value instanceof AST_SymbolRef) value = value.fixed_value(); + var values = value instanceof AST_Array && value.elements; + var elements = []; + node.elements.forEach(function(element, index) { + if (element instanceof AST_Hole) return; + value = values && values[index]; + element = element.transform(trimmer); + if (element) elements[index] = element; + }); + value = save; + if (values && elements.length == 0) return null; + for (var i = elements.length; --i >= 0;) { + if (!elements[i]) elements[i] = make_node(AST_Hole, node.elements[i] || node); + } + node.elements = elements; + return node; + } + if (node instanceof AST_DestructuredObject) { + var save = value; + if (value instanceof AST_SymbolRef) value = value.fixed_value(); + var values; + if (value instanceof AST_Object) { + values = Object.create(null); + for (var i = 0; i < value.properties.length; i++) { + var prop = value.properties[i]; + if (typeof prop.key != "string") { + values = null; + break; + } + values[prop.key] = prop.value; + } + } + var properties = []; + node.properties.forEach(function(prop) { + var retain; + if (prop.key instanceof AST_Node) { + prop.key = prop.key.transform(tt); + value = null; + retain = prop.key.has_side_effects(compressor); + } else { + value = values && values[prop.key]; + retain = false; + } + if (retain && prop.value instanceof AST_SymbolDeclaration) { + properties.push(prop); + } else { + var newValue = prop.value.transform(trimmer); + if (newValue) { + prop.value = newValue; + properties.push(prop); + } + } + }); + value = save; + if (properties.length == 0 && value && !value.may_throw_on_access(compressor)) { + return null; + } + node.properties = properties; + return node; + } + if (node instanceof AST_SymbolDeclaration) { + return !drop_vars || node.definition().id in in_use_ids || is_catch(node) ? node : null; + } + }); + var name = def.name.transform(trimmer); + if (name) { + flush(); + } else { + value = value.drop_side_effect_free(compressor); + if (value) side_effects.push(value); + } + return; + } var sym = def.name.definition(); if (!drop_vars || sym.id in in_use_ids) { if (def.value && indexOf_assign(sym, def) < 0) { @@ -5070,26 +5364,9 @@ merge(Compressor.prototype, { remove(var_defs, def); duplicated++; } - if (side_effects.length > 0) { - if (tail.length == 0) { - body.push(make_node(AST_SimpleStatement, node, { - body: make_sequence(node, side_effects) - })); - } else if (def.value) { - side_effects.push(def.value); - def.value = make_sequence(def.value, side_effects); - } else { - def.value = make_node(AST_UnaryPrefix, def, { - operator: "void", - expression: make_sequence(def, side_effects) - }); - } - side_effects = []; - } - tail.push(def); + flush(); } - } else if (sym.orig[0] instanceof AST_SymbolCatch - && sym.scope.resolve() === def.name.scope.resolve()) { + } else if (is_catch(def.name)) { var value = def.value && def.value.drop_side_effect_free(compressor); if (value) side_effects.push(value); var var_defs = var_defs_by_id.get(sym.id); @@ -5112,6 +5389,11 @@ merge(Compressor.prototype, { sym.eliminated++; } + function is_catch(node) { + var sym = node.definition(); + return sym.orig[0] instanceof AST_SymbolCatch && sym.scope.resolve() === node.scope.resolve(); + } + function can_rename(fn, name) { var def = fn.variables.get(name); return !def || fn.name && def === fn.name.definition(); @@ -5123,6 +5405,26 @@ merge(Compressor.prototype, { || parent instanceof AST_For && parent.init === node || parent instanceof AST_If; } + + function flush() { + if (side_effects.length > 0) { + if (tail.length == 0) { + body.push(make_node(AST_SimpleStatement, node, { + body: make_sequence(node, side_effects) + })); + } else if (def.value) { + side_effects.push(def.value); + def.value = make_sequence(def.value, side_effects); + } else { + def.value = make_node(AST_UnaryPrefix, def, { + operator: "void", + expression: make_sequence(def, side_effects) + }); + } + side_effects = []; + } + tail.push(def); + } }); switch (head.length) { case 0: @@ -5227,6 +5529,7 @@ merge(Compressor.prototype, { } else while (sym instanceof AST_PropAccess) { sym = sym.expression.tail_node(); } + if (sym instanceof AST_Destructured) return; var def = sym.definition(); if (!def) return; if (def.id in in_use_ids) return; @@ -5342,7 +5645,7 @@ merge(Compressor.prototype, { var def = node.expression.definition(); if (def.scope === self) assignments.add(def.id, node); } - var node_def, props = [], sym = assign_as_unused(node, props); + var node_def, props = [], sym = assign_as_unused(tw, node, props); if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) { props.forEach(function(prop) { prop.walk(tw); @@ -5376,6 +5679,7 @@ merge(Compressor.prototype, { } if (!drop_vars || !compressor.option("loops")) return; if (!is_empty(node.body)) return; + if (node.init instanceof AST_Destructured) return; if (node.init.has_side_effects(compressor)) return; node.object.walk(tw); return true; @@ -5742,6 +6046,7 @@ merge(Compressor.prototype, { })); function can_hoist(sym, right, count) { + if (!(sym instanceof AST_Symbol)) return; var def = sym.definition(); if (def.assignments != count) return; if (def.direct_access) return; @@ -5764,7 +6069,7 @@ merge(Compressor.prototype, { } }); - function safe_to_drop(fn, compressor) { + function fn_name_unused(fn, compressor) { if (!fn.name || !compressor.option("ie8")) return true; var def = fn.name.definition(); if (compressor.exposed(def)) return false; @@ -5940,7 +6245,7 @@ merge(Compressor.prototype, { return expr.drop_side_effect_free(compressor, first_in_statement); }); def(AST_Function, function(compressor) { - return safe_to_drop(this, compressor) ? null : this; + return fn_name_unused(this, compressor) ? null : this; }); def(AST_Object, function(compressor, first_in_statement) { var exprs = []; @@ -5977,13 +6282,8 @@ merge(Compressor.prototype, { if (!property) return expression; return make_sequence(this, [ expression, property ]); }); - function drop_symbol(ref) { - return all(ref.definition().orig, function(sym) { - return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet); - }); - } def(AST_SymbolRef, function(compressor) { - return this.is_declared(compressor) && drop_symbol(this) ? null : this; + return this.is_declared(compressor) && can_drop_symbol(compressor, this) ? null : this; }); def(AST_This, return_null); def(AST_Unary, function(compressor, first_in_statement) { @@ -5992,7 +6292,9 @@ merge(Compressor.prototype, { this.write_only = !exp.has_side_effects(compressor); return this; } - if (this.operator == "typeof" && exp instanceof AST_SymbolRef && drop_symbol(exp)) return null; + if (this.operator == "typeof" && exp instanceof AST_SymbolRef && can_drop_symbol(compressor, exp)) { + return null; + } var node = exp.drop_side_effect_free(compressor, first_in_statement); if (first_in_statement && node && is_iife_call(node)) { if (node === exp && this.operator == "!") return this; @@ -6477,6 +6779,7 @@ merge(Compressor.prototype, { exprs.push(line.body); } else if (line instanceof AST_Var) { if (!compressor.option("sequences") && exprs.length > 0) return; + line.remove_initializers(compressor, var_defs); line.definitions.forEach(process_var_def); } else { return; @@ -6492,23 +6795,20 @@ merge(Compressor.prototype, { if (stat instanceof AST_SimpleStatement) return [ stat.body ]; if (stat instanceof AST_Var) { var exprs = []; + stat.remove_initializers(compressor, var_defs); stat.definitions.forEach(process_var_def); return exprs; } function process_var_def(var_def) { - var_defs.push(make_node(AST_VarDef, var_def, { - name: var_def.name, - value: null - })); if (!var_def.value) return; - var ref = make_node(AST_SymbolRef, var_def.name, var_def.name); exprs.push(make_node(AST_Assign, var_def, { operator: "=", - left: ref, + left: var_def.name.convert_symbol(AST_SymbolRef, function(ref) { + refs.push(ref); + }), right: var_def.value })); - refs.push(ref); } } }); @@ -6710,25 +7010,27 @@ merge(Compressor.prototype, { return self; }); - AST_Const.DEFMETHOD("remove_initializers", function(compressor) { - this.definitions.forEach(function(def) { - def.value = make_node(AST_Undefined, def).optimize(compressor); - }); - return true; - }); - - function remove_initializers() { - var CHANGED = false; - this.definitions.forEach(function(def) { - if (!def.value) return; - def.value = null; - CHANGED = true; - }); - return CHANGED; + function remove_initializers(make_value) { + return function(compressor, defns) { + var dropped = false; + this.definitions.forEach(function(defn) { + if (defn.value) dropped = true; + defn.name.match_symbol(function(node) { + if (node instanceof AST_SymbolDeclaration) defns.push(make_node(AST_VarDef, node, { + name: node, + value: make_value(compressor, node) + })); + }); + }); + return dropped; + }; } - AST_Let.DEFMETHOD("remove_initializers", remove_initializers); - AST_Var.DEFMETHOD("remove_initializers", remove_initializers); + AST_Const.DEFMETHOD("remove_initializers", remove_initializers(function(compressor, node) { + return make_node(AST_Undefined, node).optimize(compressor); + })); + AST_Let.DEFMETHOD("remove_initializers", remove_initializers(return_null)); + AST_Var.DEFMETHOD("remove_initializers", remove_initializers(return_null)); AST_Definitions.DEFMETHOD("to_assignments", function() { var assignments = this.definitions.reduce(function(a, defn) { @@ -6751,25 +7053,26 @@ merge(Compressor.prototype, { function varify(self, compressor) { return compressor.option("varify") && all(self.definitions, function(defn) { - var node = defn.name; - if (!node.fixed_value()) return false; - var def = node.definition(); - if (compressor.exposed(def)) return false; - var scope = def.scope.resolve(); - for (var s = def.scope; s !== scope;) { - s = s.parent_scope; - if (s.var_names()[node.name]) return false; - } - return true; + return !defn.name.match_symbol(function(node) { + if (!(node instanceof AST_SymbolDeclaration)) return; + if (!node.fixed_value()) return true; + var def = node.definition(); + if (compressor.exposed(def)) return true; + var scope = def.scope.resolve(); + for (var s = def.scope; s !== scope;) { + s = s.parent_scope; + if (s.var_names()[node.name]) return true; + } + }); }) ? make_node(AST_Var, self, { definitions: self.definitions.map(function(defn) { - var name = make_node(AST_SymbolVar, defn.name, defn.name); - var def = name.definition(); - def.orig[def.orig.indexOf(defn.name)] = name; - var scope = def.scope.resolve(); - if (def.scope !== scope) scope.variables.set(def.name, def); return make_node(AST_VarDef, defn, { - name: name, + name: defn.name.convert_symbol(AST_SymbolVar, function(name, node) { + var def = name.definition(); + def.orig[def.orig.indexOf(node)] = name; + var scope = def.scope.resolve(); + if (def.scope !== scope) scope.variables.set(def.name, def); + }), value: defn.value }); }) @@ -6798,14 +7101,23 @@ merge(Compressor.prototype, { if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return; var args = call.args; var pos = 0, last = 0; - var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call); + var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call) ? function(argname, arg) { + if (!argname) return true; + if (argname instanceof AST_DestructuredArray) { + return argname.elements.length == 0 && arg instanceof AST_Array; + } + if (argname instanceof AST_DestructuredObject) { + return argname.properties.length == 0 && arg && !arg.may_throw_on_access(compressor); + } + return argname.__unused; + } : return_false; var side_effects = []; for (var i = 0; i < args.length; i++) { - var trim = i >= fn.argnames.length; - if (trim || "__unused" in fn.argnames[i]) { + var argname = fn.argnames[i]; + if (!argname || "__unused" in argname) { var node = args[i].drop_side_effect_free(compressor); - if (drop_fargs && (trim || fn.argnames[i].__unused)) { - if (!trim) fn.argnames.splice(i, 1); + if (drop_fargs(argname)) { + if (argname) fn.argnames.splice(i, 1); args.splice(i, 1); if (node) side_effects.push(node); i--; @@ -6814,7 +7126,7 @@ merge(Compressor.prototype, { side_effects.push(node); args[pos++] = make_sequence(call, side_effects); side_effects = []; - } else if (!trim) { + } else if (argname) { if (side_effects.length) { args[pos++] = make_sequence(call, side_effects); side_effects = []; @@ -6825,6 +7137,13 @@ merge(Compressor.prototype, { continue; } } + } else if (argname && drop_fargs(argname, args[i])) { + var node = args[i].drop_side_effect_free(compressor); + fn.argnames.splice(i, 1); + args.splice(i, 1); + if (node) side_effects.push(node); + i--; + continue; } else { side_effects.push(args[i]); args[pos++] = make_sequence(call, side_effects); @@ -6832,8 +7151,8 @@ merge(Compressor.prototype, { } last = pos; } - if (drop_fargs) for (; i < fn.argnames.length; i++) { - if (fn.argnames[i].__unused) fn.argnames.splice(i--, 1); + for (; i < fn.argnames.length; i++) { + if (drop_fargs(fn.argnames[i])) fn.argnames.splice(i--, 1); } args.length = last; if (!side_effects.length) return; @@ -7129,7 +7448,12 @@ merge(Compressor.prototype, { var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp; var is_func = fn instanceof AST_Lambda; var stat = is_func && fn.first_statement(); - var can_inline = compressor.option("inline") && !self.is_expr_pure(compressor); + var can_inline = is_func + && compressor.option("inline") + && !self.is_expr_pure(compressor) + && all(fn.argnames, function(argname) { + return !(argname instanceof AST_Destructured); + }); if (can_inline && stat instanceof AST_Return) { var value = stat.value; if (exp === fn && (!value || value.is_constant_expression())) { @@ -7189,7 +7513,10 @@ merge(Compressor.prototype, { } if (compressor.option("side_effects") && all(fn.body, is_empty) - && (fn !== exp || safe_to_drop(fn, compressor))) { + && all(fn.argnames, function(argname) { + return !(argname instanceof AST_Destructured); + }) + && (fn !== exp || fn_name_unused(fn, compressor))) { var args = self.args.concat(make_node(AST_Undefined, self)); return make_sequence(self, args).optimize(compressor); } @@ -9409,6 +9736,17 @@ merge(Compressor.prototype, { return try_evaluate(compressor, self); }); + OPT(AST_DestructuredKeyVal, function(self, compressor) { + if (compressor.option("objects")) { + var key = self.key; + if (key instanceof AST_Node) { + key = key.evaluate(compressor); + if (key !== self.key) self.key = "" + key; + } + } + return self; + }); + OPT(AST_Object, function(self, compressor) { if (!compressor.option("objects") || compressor.has_directive("use strict")) return self; for (var i = self.properties.length; --i >= 0;) { |