aboutsummaryrefslogtreecommitdiff
path: root/lib/compress.js
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2020-11-17 00:01:24 +0000
committerGitHub <noreply@github.com>2020-11-17 08:01:24 +0800
commite5f80afc53e7df3b0b57931a03dc3481ddd9c30e (patch)
tree2c3686f36caccfd0a642a344eb6bcde728d80f6a /lib/compress.js
parent42e34c870ad5979135ab7407ab62abce9507fc28 (diff)
downloadtracifyjs-e5f80afc53e7df3b0b57931a03dc3481ddd9c30e.tar.gz
tracifyjs-e5f80afc53e7df3b0b57931a03dc3481ddd9c30e.zip
support destructured literals (#4278)
Diffstat (limited to 'lib/compress.js')
-rw-r--r--lib/compress.js798
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;) {