aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/compress.js521
-rw-r--r--test/compress/reduce_vars.js90
2 files changed, 354 insertions, 257 deletions
diff --git a/lib/compress.js b/lib/compress.js
index 0f4dd255..4e86a307 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -109,6 +109,14 @@ function Compressor(options, false_by_default) {
return top_retain.indexOf(def.name) >= 0;
};
}
+ var toplevel = this.options["toplevel"];
+ if (typeof toplevel == "string") {
+ this.toplevel.funcs = /funcs/.test(toplevel);
+ this.toplevel.vars = /vars/.test(toplevel);
+ } else {
+ this.toplevel = toplevel ? return_true : return_false;
+ this.toplevel.funcs = this.toplevel.vars = toplevel;
+ }
var sequences = this.options["sequences"];
this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
this.warnings_produced = {};
@@ -117,6 +125,12 @@ function Compressor(options, false_by_default) {
Compressor.prototype = new TreeTransformer;
merge(Compressor.prototype, {
option: function(key) { return this.options[key] },
+ toplevel: function(def) {
+ for (var i = 0, len = def.orig.length; i < len; i++)
+ if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
+ return false;
+ return true;
+ },
compress: function(node) {
if (this.option("expression")) {
node.process_expression(true);
@@ -249,7 +263,6 @@ merge(Compressor.prototype, {
AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan) {
var reduce_vars = rescan && compressor.option("reduce_vars");
- var toplevel = compressor.option("toplevel");
var safe_ids = Object.create(null);
var suppressor = new TreeWalker(function(node) {
if (node instanceof AST_Symbol) {
@@ -310,7 +323,7 @@ merge(Compressor.prototype, {
}
if (node instanceof AST_Defun) {
var d = node.name.definition();
- if (!toplevel && d.global || safe_to_read(d)) {
+ if (d.global && !compressor.toplevel(d) || safe_to_read(d)) {
d.fixed = false;
} else {
d.fixed = node;
@@ -469,7 +482,7 @@ merge(Compressor.prototype, {
}
function reset_def(def) {
- if (toplevel || !def.global || def.orig[0] instanceof AST_SymbolConst) {
+ if (!def.global || def.orig[0] instanceof AST_SymbolConst || compressor.toplevel(def)) {
def.fixed = undefined;
} else {
def.fixed = false;
@@ -643,7 +656,6 @@ merge(Compressor.prototype, {
// in the statement with the var value and then erase the var definition.
var scope = compressor.find_parent(AST_Scope);
- var toplevel = compressor.option("toplevel");
var stat_index;
var prev_stat_index;
var def_stat_index;
@@ -694,7 +706,8 @@ merge(Compressor.prototype, {
// Only interested in cases with just one reference to the variable.
var def = var_decl.name.definition();
if (def.references.length !== 1
- || var_name == "arguments" || (!toplevel && def.global)) {
+ || var_name == "arguments"
+ || def.global && !compressor.toplevel(def)) {
side_effects_encountered = true;
continue;
}
@@ -1825,289 +1838,283 @@ merge(Compressor.prototype, {
});
AST_Scope.DEFMETHOD("drop_unused", function(compressor){
+ if (!compressor.option("unused")) return;
+ if (compressor.has_directive("use asm")) return;
var self = this;
- if (compressor.has_directive("use asm")) return self;
- var toplevel = compressor.option("toplevel");
- if (compressor.option("unused")
- && (!(self instanceof AST_Toplevel) || toplevel)
- && !self.uses_eval
- && !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 = Object.create(null); // 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);
+ if (self.uses_eval || self.uses_with) return;
+ var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
+ var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
+ if (!drop_funcs && !drop_vars) return;
+ var assign_as_unused = !/keep_assign/.test(compressor.option("unused"));
+ var in_use = [];
+ var in_use_ids = Object.create(null); // 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 var_defs_by_id = new Dictionary();
+ 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);
+ }
}
- });
- }
- var var_defs_by_id = new Dictionary();
- 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();
+ 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){
+ var node_def = def.name.definition();
+ if (def.name instanceof AST_SymbolVar) {
+ var_defs_by_id.add(node_def.id, def);
+ }
+ if (!drop_vars) {
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){
- var node_def = def.name.definition();
- if (def.name instanceof AST_SymbolVar) {
- var_defs_by_id.add(node_def.id, def);
- }
- if (!drop_vars) {
- 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)) {
- def.value.walk(tw);
- }
+ if (def.value) {
+ initializations.add(def.name.name, def.value);
+ if (def.value.has_side_effects(compressor)) {
+ def.value.walk(tw);
}
- });
- 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)) {
- in_use_ids[node_def.id] = true;
- in_use.push(node_def);
}
- return true;
- }
- if (node instanceof AST_Scope) {
- var save_scope = scope;
- scope = node;
- descend();
- scope = save_scope;
- return true;
+ });
+ 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)) {
+ in_use_ids[node_def.id] = true;
+ in_use.push(node_def);
}
+ return true;
}
- });
- self.walk(tw);
- // pass 2: for every used symbol we need to walk its
- // initialization code to figure out if it uses other
- // symbols (that may not be in_use).
- for (var i = 0; i < in_use.length; ++i) {
- in_use[i].orig.forEach(function(decl){
- // undeclared globals will be instanceof AST_SymbolRef
- var init = initializations.get(decl.name);
- if (init) init.forEach(function(init){
- var tw = new TreeWalker(function(node){
- if (node instanceof AST_SymbolRef) {
- var node_def = node.definition();
- if (!(node_def.id in in_use_ids)) {
- in_use_ids[node_def.id] = true;
- in_use.push(node_def);
- }
+ if (node instanceof AST_Scope) {
+ var save_scope = scope;
+ scope = node;
+ descend();
+ scope = save_scope;
+ return true;
+ }
+ }
+ });
+ self.walk(tw);
+ // pass 2: for every used symbol we need to walk its
+ // initialization code to figure out if it uses other
+ // symbols (that may not be in_use).
+ for (var i = 0; i < in_use.length; ++i) {
+ in_use[i].orig.forEach(function(decl){
+ // undeclared globals will be instanceof AST_SymbolRef
+ var init = initializations.get(decl.name);
+ if (init) init.forEach(function(init){
+ var tw = new TreeWalker(function(node){
+ if (node instanceof AST_SymbolRef) {
+ var node_def = node.definition();
+ if (!(node_def.id in in_use_ids)) {
+ in_use_ids[node_def.id] = true;
+ in_use.push(node_def);
}
- });
- init.walk(tw);
+ }
});
+ init.walk(tw);
});
- }
- // pass 3: we should drop declarations not in_use
- var tt = new TreeTransformer(
- function before(node, descend, in_list) {
- if (node instanceof AST_Function
- && node.name
- && !compressor.option("keep_fnames")) {
- var def = node.name.definition();
- // any declarations with same name will overshadow
- // name of this anonymous function and can therefore
- // never be used anywhere
- if (!(def.id in in_use_ids) || def.orig.length > 1)
- node.name = null;
- }
- if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
- var trim = !compressor.option("keep_fargs");
- for (var a = node.argnames, i = a.length; --i >= 0;) {
- var sym = a[i];
- if (!(sym.definition().id in in_use_ids)) {
- sym.__unused = true;
- if (trim) {
- a.pop();
- compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", template(sym));
- }
- }
- else {
- trim = false;
+ });
+ }
+ // pass 3: we should drop declarations not in_use
+ var tt = new TreeTransformer(
+ function before(node, descend, in_list) {
+ if (node instanceof AST_Function
+ && node.name
+ && !compressor.option("keep_fnames")) {
+ var def = node.name.definition();
+ // any declarations with same name will overshadow
+ // name of this anonymous function and can therefore
+ // never be used anywhere
+ if (!(def.id in in_use_ids) || def.orig.length > 1)
+ node.name = null;
+ }
+ if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
+ var trim = !compressor.option("keep_fargs");
+ for (var a = node.argnames, i = a.length; --i >= 0;) {
+ var sym = a[i];
+ if (!(sym.definition().id in in_use_ids)) {
+ sym.__unused = true;
+ if (trim) {
+ a.pop();
+ compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", template(sym));
}
}
- }
- if (drop_funcs && node instanceof AST_Defun && node !== self) {
- if (!(node.name.definition().id in in_use_ids)) {
- compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name));
- return make_node(AST_EmptyStatement, node);
+ else {
+ trim = false;
}
- return node;
}
- if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn && tt.parent().init === node)) {
- // place uninitialized names at the start
- var body = [], head = [], tail = [];
- // for unused names whose initialization has
- // side effects, we can cascade the init. code
- // into the next one, or next statement.
- var side_effects = [];
- node.definitions.forEach(function(def) {
- if (def.value) def.value = def.value.transform(tt);
- var sym = def.name.definition();
- if (sym.id in in_use_ids) {
- if (def.name instanceof AST_SymbolVar) {
- var var_defs = var_defs_by_id.get(sym.id);
- if (var_defs.length > 1 && !def.value) {
- compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
- var_defs.splice(var_defs.indexOf(def), 1);
- return;
- }
+ }
+ if (drop_funcs && node instanceof AST_Defun && node !== self) {
+ if (!(node.name.definition().id in in_use_ids)) {
+ compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name));
+ return make_node(AST_EmptyStatement, node);
+ }
+ return node;
+ }
+ if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn && tt.parent().init === node)) {
+ // place uninitialized names at the start
+ var body = [], head = [], tail = [];
+ // for unused names whose initialization has
+ // side effects, we can cascade the init. code
+ // into the next one, or next statement.
+ var side_effects = [];
+ node.definitions.forEach(function(def) {
+ if (def.value) def.value = def.value.transform(tt);
+ var sym = def.name.definition();
+ if (sym.id in in_use_ids) {
+ if (def.name instanceof AST_SymbolVar) {
+ var var_defs = var_defs_by_id.get(sym.id);
+ if (var_defs.length > 1 && !def.value) {
+ compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
+ var_defs.splice(var_defs.indexOf(def), 1);
+ return;
}
- if (def.value) {
- if (side_effects.length > 0) {
- if (tail.length > 0) {
- merge_sequence(side_effects, def.value);
- def.value = make_sequence(def.value, side_effects);
- } else {
- body.push(make_node(AST_SimpleStatement, node, {
- body: make_sequence(node, side_effects)
- }));
- }
- side_effects = [];
+ }
+ if (def.value) {
+ if (side_effects.length > 0) {
+ if (tail.length > 0) {
+ merge_sequence(side_effects, def.value);
+ def.value = make_sequence(def.value, side_effects);
+ } else {
+ body.push(make_node(AST_SimpleStatement, node, {
+ body: make_sequence(node, side_effects)
+ }));
}
- tail.push(def);
- } else {
- head.push(def);
+ side_effects = [];
}
- } else if (sym.orig[0] instanceof AST_SymbolCatch) {
- var value = def.value && def.value.drop_side_effect_free(compressor);
- if (value) merge_sequence(side_effects, value);
- def.value = null;
- head.push(def);
+ tail.push(def);
} else {
- var value = def.value && def.value.drop_side_effect_free(compressor);
- if (value) {
- compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
- merge_sequence(side_effects, value);
- } else {
- compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
- }
+ head.push(def);
}
- });
- if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) {
- var var_defs = var_defs_by_id.get(tail[0].name.definition().id);
- if (var_defs.length > 1) {
- var def = tail.pop();
- compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name));
- var_defs.splice(var_defs.indexOf(def), 1);
- side_effects.unshift(make_node(AST_Assign, def, {
- operator: "=",
- left: make_node(AST_SymbolRef, def.name, def.name),
- right: def.value
- }));
+ } else if (sym.orig[0] instanceof AST_SymbolCatch) {
+ var value = def.value && def.value.drop_side_effect_free(compressor);
+ if (value) merge_sequence(side_effects, value);
+ def.value = null;
+ head.push(def);
+ } else {
+ var value = def.value && def.value.drop_side_effect_free(compressor);
+ if (value) {
+ compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
+ merge_sequence(side_effects, value);
+ } else {
+ compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
}
}
- if (head.length > 0 || tail.length > 0) {
- node.definitions = head.concat(tail);
- body.push(node);
- }
- if (side_effects.length > 0) {
- body.push(make_node(AST_SimpleStatement, node, {
- body: make_sequence(node, side_effects)
+ });
+ if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) {
+ var var_defs = var_defs_by_id.get(tail[0].name.definition().id);
+ if (var_defs.length > 1) {
+ var def = tail.pop();
+ compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name));
+ var_defs.splice(var_defs.indexOf(def), 1);
+ side_effects.unshift(make_node(AST_Assign, def, {
+ operator: "=",
+ left: make_node(AST_SymbolRef, def.name, def.name),
+ right: def.value
}));
}
- switch (body.length) {
- case 0:
- return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
- case 1:
- return body[0];
- default:
- return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
- body: body
- });
- }
- }
- 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 maintain_this_binding(tt.parent(), node, node.right.transform(tt));
- }
}
- // certain combination of unused name + side effect leads to:
- // https://github.com/mishoo/UglifyJS2/issues/44
- // https://github.com/mishoo/UglifyJS2/issues/1830
- // https://github.com/mishoo/UglifyJS2/issues/1838
- // that's an invalid AST.
- // We fix it at this stage by moving the `var` outside the `for`.
- if (node instanceof AST_For) {
- descend(node, this);
- if (node.init instanceof AST_BlockStatement) {
- var block = node.init;
- node.init = block.body.pop();
- block.body.push(node);
- return in_list ? MAP.splice(block.body) : block;
- } else if (node.init instanceof AST_SimpleStatement) {
- node.init = node.init.body;
- } else if (is_empty(node.init)) {
- node.init = null;
- }
- return node;
+ if (head.length > 0 || tail.length > 0) {
+ node.definitions = head.concat(tail);
+ body.push(node);
+ }
+ if (side_effects.length > 0) {
+ body.push(make_node(AST_SimpleStatement, node, {
+ body: make_sequence(node, side_effects)
+ }));
+ }
+ switch (body.length) {
+ case 0:
+ return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
+ case 1:
+ return body[0];
+ default:
+ return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
+ body: body
+ });
}
- if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
- descend(node, this);
- if (node.body instanceof AST_BlockStatement) {
- var block = node.body;
- node.body = block.body.pop();
- block.body.push(node);
- return in_list ? MAP.splice(block.body) : block;
- }
- 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 maintain_this_binding(tt.parent(), node, node.right.transform(tt));
+ }
+ }
+ // certain combination of unused name + side effect leads to:
+ // https://github.com/mishoo/UglifyJS2/issues/44
+ // https://github.com/mishoo/UglifyJS2/issues/1830
+ // https://github.com/mishoo/UglifyJS2/issues/1838
+ // that's an invalid AST.
+ // We fix it at this stage by moving the `var` outside the `for`.
+ if (node instanceof AST_For) {
+ descend(node, this);
+ if (node.init instanceof AST_BlockStatement) {
+ var block = node.init;
+ node.init = block.body.pop();
+ block.body.push(node);
+ return in_list ? MAP.splice(block.body) : block;
+ } else if (node.init instanceof AST_SimpleStatement) {
+ node.init = node.init.body;
+ } else if (is_empty(node.init)) {
+ node.init = null;
}
- if (node instanceof AST_Scope && node !== self)
- return node;
-
- function template(sym) {
- return {
- name : sym.name,
- file : sym.start.file,
- line : sym.start.line,
- col : sym.start.col
- };
+ return node;
+ }
+ if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
+ descend(node, this);
+ if (node.body instanceof AST_BlockStatement) {
+ var block = node.body;
+ node.body = block.body.pop();
+ block.body.push(node);
+ return in_list ? MAP.splice(block.body) : block;
}
+ return node;
}
- );
- self.transform(tt);
- }
+ if (node instanceof AST_Scope && node !== self)
+ return node;
+
+ function template(sym) {
+ return {
+ name : sym.name,
+ file : sym.start.file,
+ line : sym.start.line,
+ col : sym.start.col
+ };
+ }
+ }
+ );
+ self.transform(tt);
});
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
@@ -3683,7 +3690,7 @@ merge(Compressor.prototype, {
}
var name = d.name.length;
var overhead = 0;
- if (compressor.option("unused") && (!d.global || compressor.option("toplevel"))) {
+ if (compressor.option("unused") && (!d.global || compressor.toplevel(d))) {
overhead = (name + 2 + value) / d.references.length;
}
d.should_replace = value <= name + overhead ? fn : false;
diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js
index 57e23891..f7bdcb36 100644
--- a/test/compress/reduce_vars.js
+++ b/test/compress/reduce_vars.js
@@ -2327,3 +2327,93 @@ iife_assign: {
}
expect_stdout: "1"
}
+
+issue_1850_1: {
+ options = {
+ reduce_vars: true,
+ toplevel: false,
+ unused: true,
+ }
+ input: {
+ function f() {
+ console.log(a, a, a);
+ }
+ var a = 1;
+ f();
+ }
+ expect: {
+ function f() {
+ console.log(a, a, a);
+ }
+ var a = 1;
+ f();
+ }
+ expect_stdout: true
+}
+
+issue_1850_2: {
+ options = {
+ reduce_vars: true,
+ toplevel: "funcs",
+ unused: true,
+ }
+ input: {
+ function f() {
+ console.log(a, a, a);
+ }
+ var a = 1;
+ f();
+ }
+ expect: {
+ var a = 1;
+ (function() {
+ console.log(a, a, a);
+ })();
+ }
+ expect_stdout: true
+}
+
+issue_1850_3: {
+ options = {
+ reduce_vars: true,
+ toplevel: "vars",
+ unused: true,
+ }
+ input: {
+ function f() {
+ console.log(a, a, a);
+ }
+ var a = 1;
+ f();
+ }
+ expect: {
+ function f() {
+ console.log(a, a, a);
+ }
+ var a = 1;
+ f();
+ }
+ expect_stdout: true
+}
+
+issue_1850_4: {
+ options = {
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ function f() {
+ console.log(a, a, a);
+ }
+ var a = 1;
+ f();
+ }
+ expect: {
+ var a = 1;
+ (function() {
+ console.log(a, a, a);
+ })();
+ }
+ expect_stdout: true
+}