aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--lib/compress.js72
-rw-r--r--test/compress/collapse_vars.js114
-rw-r--r--test/compress/drop-unused.js379
4 files changed, 565 insertions, 10 deletions
diff --git a/README.md b/README.md
index a8b55843..a2eaeae4 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/lib/compress.js b/lib/compress.js
index 459256f5..0dcfb2ba 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -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,
@@ -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 = {};
@@ -1409,13 +1426,27 @@ 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).
@@ -1423,11 +1454,25 @@ merge(Compressor.prototype, {
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)) {
@@ -1437,6 +1482,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)) {
@@ -1496,7 +1549,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,
@@ -1508,7 +1561,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 = {
@@ -1571,6 +1624,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);
diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js
index ef7af9ed..d7432f3f 100644
--- a/test/compress/collapse_vars.js
+++ b/test/compress/collapse_vars.js
@@ -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) {
@@ -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);
+ 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,
@@ -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; }
@@ -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; }
}
}
diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js
index 035a428e..5620cf40 100644
--- a/test/compress/drop-unused.js
+++ b/test/compress/drop-unused.js
@@ -177,3 +177,382 @@ keep_fnames: {
}
}
}
+
+drop_assign: {
+ options = { unused: true };
+ input: {
+ function f1() {
+ var a;
+ a = 1;
+ }
+ function f2() {
+ var a = 1;
+ a = 2;
+ }
+ function f3(a) {
+ a = 1;
+ }
+ function f4() {
+ var a;
+ return a = 1;
+ }
+ function f5() {
+ var a;
+ return function() {
+ a = 1;
+ }
+ }
+ }
+ expect: {
+ function f1() {
+ 1;
+ }
+ function f2() {
+ 2;
+ }
+ function f3(a) {
+ 1;
+ }
+ function f4() {
+ return 1;
+ }
+ function f5() {
+ var a;
+ return function() {
+ a = 1;
+ }
+ }
+ }
+}
+
+keep_assign: {
+ options = { unused: "keep_assign" };
+ input: {
+ function f1() {
+ var a;
+ a = 1;
+ }
+ function f2() {
+ var a = 1;
+ a = 2;
+ }
+ function f3(a) {
+ a = 1;
+ }
+ function f4() {
+ var a;
+ return a = 1;
+ }
+ function f5() {
+ var a;
+ return function() {
+ a = 1;
+ }
+ }
+ }
+ expect: {
+ function f1() {
+ var a;
+ a = 1;
+ }
+ function f2() {
+ var a = 1;
+ a = 2;
+ }
+ function f3(a) {
+ a = 1;
+ }
+ function f4() {
+ var a;
+ return a = 1;
+ }
+ function f5() {
+ var a;
+ return function() {
+ a = 1;
+ }
+ }
+ }
+}
+
+drop_toplevel_funcs: {
+ options = { toplevel: "funcs", unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var a, b = 1, c = g;
+ a = 2;
+ function g() {}
+ console.log(b = 3);
+ }
+}
+
+drop_toplevel_vars: {
+ options = { toplevel: "vars", unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ 2;
+ function g() {}
+ function h() {}
+ console.log(3);
+ }
+}
+
+drop_toplevel_vars_fargs: {
+ options = { keep_fargs: false, toplevel: "vars", unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var c = g;
+ function f() {
+ return function() {
+ c = 2;
+ }
+ }
+ 2;
+ function g() {}
+ function h() {}
+ console.log(3);
+ }
+}
+
+drop_toplevel_all: {
+ options = { toplevel: true, unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ 2;
+ console.log(3);
+ }
+}
+
+drop_toplevel_retain: {
+ options = { top_retain: "f,a,o", unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var a, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ console.log(3);
+ }
+}
+
+drop_toplevel_retain_array: {
+ options = { top_retain: [ "f", "a", "o" ], unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var a, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ console.log(3);
+ }
+}
+
+drop_toplevel_retain_regex: {
+ options = { top_retain: /^[fao]$/, unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var a, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ console.log(3);
+ }
+}
+
+drop_toplevel_all_retain: {
+ options = { toplevel: true, top_retain: "f,a,o", unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var a, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ console.log(3);
+ }
+}
+
+drop_toplevel_funcs_retain: {
+ options = { toplevel: "funcs", top_retain: "f,a,o", unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ console.log(b = 3);
+ }
+}
+
+drop_toplevel_vars_retain: {
+ options = { toplevel: "vars", top_retain: "f,a,o", unused: true };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var a, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(3);
+ }
+}
+
+drop_toplevel_keep_assign: {
+ options = { toplevel: true, unused: "keep_assign" };
+ input: {
+ var a, b = 1, c = g;
+ function f(d) {
+ return function() {
+ c = 2;
+ }
+ }
+ a = 2;
+ function g() {}
+ function h() {}
+ console.log(b = 3);
+ }
+ expect: {
+ var a, b = 1;
+ a = 2;
+ console.log(b = 3);
+ }
+}