Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,15 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `loops` -- optimizations for `do`, `while` and `for` loops when we can
statically determine the condition

- `unused` -- drop unreferenced functions and variables
- `unused` -- drop unreferenced functions and variables (simple direct variable
assignments do not count as references unless set to `"keep_assign"`)

- `toplevel` -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`)
in the toplevel scope (`false` by default, `true` to drop both unreferenced
functions and variables)

- `top_retain` -- prevent specific toplevel functions and variables from `unused`
removal (can be array, comma-separated, RegExp or function. Implies `toplevel`)

- `hoist_funs` -- hoist function declarations

Expand Down
72 changes: 67 additions & 5 deletions lib/compress.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ function Compressor(options, false_by_default) {
booleans : !false_by_default,
loops : !false_by_default,
unused : !false_by_default,
toplevel : !!options["top_retain"],
top_retain : null,
hoist_funs : !false_by_default,
keep_fargs : true,
keep_fnames : false,
Expand All @@ -80,6 +82,21 @@ function Compressor(options, false_by_default) {
global_defs : {},
passes : 1,
}, true);
var top_retain = this.options["top_retain"];
if (top_retain instanceof RegExp) {
this.top_retain = function(def) {
return top_retain.test(def.name);
};
} else if (typeof top_retain === "function") {
this.top_retain = top_retain;
} else if (top_retain) {
if (typeof top_retain === "string") {
top_retain = top_retain.split(/,/);
}
this.top_retain = function(def) {
return top_retain.indexOf(def.name) >= 0;
};
}
var sequences = this.options["sequences"];
this.sequences_limit = sequences == 1 ? 200 : sequences | 0;
this.warnings_produced = {};
Expand Down Expand Up @@ -1407,25 +1424,53 @@ merge(Compressor.prototype, {
AST_Scope.DEFMETHOD("drop_unused", function(compressor){
var self = this;
if (compressor.has_directive("use asm")) return self;
var toplevel = compressor.option("toplevel");
if (compressor.option("unused")
&& !(self instanceof AST_Toplevel)
&& (!(self instanceof AST_Toplevel) || toplevel)
&& !self.uses_eval
&& !self.uses_with
) {
&& !self.uses_with) {
var assign_as_unused = !/keep_assign/.test(compressor.option("unused"));
var drop_funcs = /funcs/.test(toplevel);
var drop_vars = /vars/.test(toplevel);
if (!(self instanceof AST_Toplevel) || toplevel == true) {
drop_funcs = drop_vars = true;
}
var in_use = [];
var in_use_ids = {}; // avoid expensive linear scans of in_use
if (self instanceof AST_Toplevel && compressor.top_retain) {
self.variables.each(function(def) {
if (compressor.top_retain(def) && !(def.id in in_use_ids)) {
in_use_ids[def.id] = true;
in_use.push(def);
}
});
}
var initializations = new Dictionary();
// pass 1: find out which symbols are directly used in
// this scope (not in nested scopes).
var scope = this;
var tw = new TreeWalker(function(node, descend){
if (node !== self) {
if (node instanceof AST_Defun) {
if (!drop_funcs && scope === self) {
var node_def = node.name.definition();
if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true;
in_use.push(node_def);
}
}
initializations.add(node.name.name, node);
return true; // don't go in nested scopes
}
if (node instanceof AST_Definitions && scope === self) {
node.definitions.forEach(function(def){
if (!drop_vars) {
var node_def = def.name.definition();
if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true;
in_use.push(node_def);
}
}
if (def.value) {
initializations.add(def.name.name, def.value);
if (def.value.has_side_effects(compressor)) {
Expand All @@ -1435,6 +1480,14 @@ merge(Compressor.prototype, {
});
return true;
}
if (assign_as_unused
&& node instanceof AST_Assign
&& node.operator == "="
&& node.left instanceof AST_SymbolRef
&& scope === self) {
node.right.walk(tw);
return true;
}
if (node instanceof AST_SymbolRef) {
var node_def = node.definition();
if (!(node_def.id in in_use_ids)) {
Expand Down Expand Up @@ -1494,7 +1547,7 @@ merge(Compressor.prototype, {
}
}
}
if (node instanceof AST_Defun && node !== self) {
if (drop_funcs && node instanceof AST_Defun && node !== self) {
if (!(node.name.definition().id in in_use_ids)) {
compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
name : node.name.name,
Expand All @@ -1506,7 +1559,7 @@ merge(Compressor.prototype, {
}
return node;
}
if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
var def = node.definitions.filter(function(def){
if (def.name.definition().id in in_use_ids) return true;
var w = {
Expand Down Expand Up @@ -1569,6 +1622,15 @@ merge(Compressor.prototype, {
}
return node;
}
if (drop_vars && assign_as_unused
&& node instanceof AST_Assign
&& node.operator == "="
&& node.left instanceof AST_SymbolRef) {
var def = node.left.definition();
if (!(def.id in in_use_ids) && self.variables.get(def.name) === def) {
return node.right;
}
}
if (node instanceof AST_For) {
descend(node, this);

Expand Down
114 changes: 110 additions & 4 deletions test/compress/collapse_vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,9 @@ collapse_vars_while: {
collapse_vars_do_while: {
options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:false, loops:false, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
comparisons:true, evaluate:true, booleans:false, loops:false, unused:"keep_assign",
hoist_funs:true, keep_fargs:true, if_return:true, join_vars:true, cascade:true,
side_effects:true
}
input: {
function f1(y) {
Expand Down Expand Up @@ -409,6 +410,79 @@ collapse_vars_do_while: {
}
}

collapse_vars_do_while_drop_assign: {
options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:false, loops:false, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
}
input: {
function f1(y) {
// The constant do-while condition `c` will be replaced.
var c = 9;
do { } while (c === 77);
}
function f2(y) {
// The non-constant do-while condition `c` will not be replaced.
var c = 5 - y;
do { } while (c);
}
function f3(y) {
// The constant `x` will be replaced in the do loop body.
function fn(n) { console.log(n); }
var a = 2, x = 7;
do {
fn(a = x);
break;
} while (y);
}
function f4(y) {
// The non-constant `a` will not be replaced in the do loop body.
var a = y / 4;
do {
return a;
} while (y);
}
function f5(y) {
function p(x) { console.log(x); }
do {
// The non-constant `a` will be replaced in p(a)
// because it is declared in same block.
var a = y - 3;
p(a);
} while (--y);
}
}
expect: {
function f1(y) {
do ; while (false);
}
function f2(y) {
var c = 5 - y;
do ; while (c);
}
function f3(y) {
function fn(n) { console.log(n); }
do {
fn(7);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please set unused: 2 in the collapse_vars tests with different results?

break;
} while (y);
}
function f4(y) {
var a = y / 4;
do
return a;
while (y);
}
function f5(y) {
function p(x) { console.log(x); }
do {
p(y - 3);
} while (--y);
}
}
}

collapse_vars_seq: {
options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
Expand Down Expand Up @@ -567,8 +641,9 @@ collapse_vars_assignment: {
collapse_vars_lvalues: {
options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
comparisons:true, evaluate:true, booleans:true, loops:true, unused:"keep_assign",
hoist_funs:true, keep_fargs:true, if_return:true, join_vars:true, cascade:true,
side_effects:true
}
input: {
function f0(x) { var i = ++x; return x += i; }
Expand All @@ -593,7 +668,38 @@ collapse_vars_lvalues: {
function f7(x) { var w = e1(), v = e2(), c = v - x; return (w = x) - c; }
function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); }
function f9(x) { var w = e1(); return e2() - x - (w = x); }
}
}

collapse_vars_lvalues_drop_assign: {
options = {
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
}
input: {
function f0(x) { var i = ++x; return x += i; }
function f1(x) { var a = (x -= 3); return x += a; }
function f2(x) { var z = x, a = ++z; return z += a; }
function f3(x) { var a = (x -= 3), b = x + a; return b; }
function f4(x) { var a = (x -= 3); return x + a; }
function f5(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return b - c; }
function f6(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return c - b; }
function f7(x) { var w = e1(), v = e2(), c = v - x, b = w = x; return b - c; }
function f8(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return b - c; }
function f9(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return c - b; }
}
expect: {
function f0(x) { var i = ++x; return x += i; }
function f1(x) { var a = (x -= 3); return x += a; }
function f2(x) { var z = x, a = ++z; return z += a; }
function f3(x) { var a = (x -= 3); return x + a; }
function f4(x) { var a = (x -= 3); return x + a; }
function f5(x) { var v = (e1(), e2()), c = v = --x; return x - c; }
function f6(x) { e1(), e2(); return --x - x; }
function f7(x) { var v = (e1(), e2()), c = v - x; return x - c; }
function f8(x) { var v = (e1(), e2()); return x - (v - x); }
function f9(x) { e1(); return e2() - x - x; }
}
}

Expand Down
Loading