diff options
-rw-r--r-- | lib/ast.js | 3 | ||||
-rw-r--r-- | lib/compress.js | 33 | ||||
-rw-r--r-- | lib/scope.js | 19 | ||||
-rw-r--r-- | test/compress/drop-unused.js | 112 |
4 files changed, 141 insertions, 26 deletions
@@ -314,6 +314,9 @@ var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent if (this.functions) node.functions = this.functions.clone(); if (this.enclosed) node.enclosed = this.enclosed.slice(); return node; + }, + pinned: function() { + return this.uses_eval || this.uses_with; } }, AST_Block); diff --git a/lib/compress.js b/lib/compress.js index 498729d0..ebb2d408 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -348,14 +348,14 @@ merge(Compressor.prototype, { def.chained = false; def.direct_access = false; def.escaped = false; - if (def.scope.uses_eval || def.scope.uses_with) { + if (def.scope.pinned()) { def.fixed = false; } else if (!compressor.exposed(def)) { def.fixed = def.init; } else { def.fixed = false; } - if (def.init instanceof AST_Defun && !all(def.references, function(ref) { + if (def.fixed instanceof AST_Defun && !all(def.references, function(ref) { var scope = ref.scope; do { if (def.scope === scope) return true; @@ -471,8 +471,7 @@ merge(Compressor.prototype, { function ref_once(tw, compressor, def) { return compressor.option("unused") - && !def.scope.uses_eval - && !def.scope.uses_with + && !def.scope.pinned() && def.references.length - def.recursive_refs == 1 && tw.loop_ids[def.id] === tw.in_loop; } @@ -725,7 +724,7 @@ merge(Compressor.prototype, { if (value instanceof AST_Lambda && recursive_ref(tw, d)) { d.recursive_refs++; } else if (value && ref_once(tw, compressor, d)) { - d.single_use = value instanceof AST_Lambda + d.single_use = value instanceof AST_Lambda && !value.pinned() || d.scope === this.scope && value.is_constant_expression(); } else { d.single_use = false; @@ -1066,7 +1065,7 @@ merge(Compressor.prototype, { // Will not attempt to collapse assignments into or past code blocks // which are not sequentially executed, e.g. loops and conditionals. function collapse(statements, compressor) { - if (scope.uses_eval || scope.uses_with) return statements; + if (scope.pinned()) return statements; var args; var candidates = []; var stat_index = statements.length; @@ -1302,7 +1301,7 @@ merge(Compressor.prototype, { if (fn instanceof AST_Function && !fn.name && !fn.uses_arguments - && !fn.uses_eval + && !fn.pinned() && (iife = compressor.parent()) instanceof AST_Call && iife.expression === fn) { var fn_strict = compressor.has_directive("use strict"); @@ -1319,9 +1318,12 @@ merge(Compressor.prototype, { })); if (sym.name in names) continue; names[sym.name] = true; - if (!arg) arg = make_node(AST_Undefined, sym).transform(compressor); - else { - var tw = new TreeWalker(function(node) { + if (!arg) { + arg = make_node(AST_Undefined, sym).transform(compressor); + } else if (arg instanceof AST_Lambda && arg.pinned()) { + arg = null; + } else { + arg.walk(new TreeWalker(function(node) { if (!arg) return true; if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) { var s = node.definition().scope; @@ -1330,12 +1332,11 @@ merge(Compressor.prototype, { } arg = null; } - if (node instanceof AST_This && (fn_strict || !tw.find_parent(AST_Scope))) { + if (node instanceof AST_This && (fn_strict || !this.find_parent(AST_Scope))) { arg = null; return true; } - }); - arg.walk(tw); + })); } if (arg) candidates.unshift([ make_node(AST_VarDef, sym, { name: sym, @@ -3263,7 +3264,7 @@ merge(Compressor.prototype, { if (!compressor.option("unused")) return; if (compressor.has_directive("use asm")) return; var self = this; - if (self.uses_eval || self.uses_with) return; + if (self.pinned()) return; 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) { @@ -4526,7 +4527,7 @@ merge(Compressor.prototype, { if (compressor.option("unused") && is_func && !fn.uses_arguments - && !fn.uses_eval) { + && !fn.pinned()) { var pos = 0, last = 0; for (var i = 0, len = self.args.length; i < len; i++) { var trim = i >= fn.argnames.length; @@ -4806,7 +4807,7 @@ merge(Compressor.prototype, { var def, value, scope, in_loop, level = -1; if (can_inline && !fn.uses_arguments - && !fn.uses_eval + && !fn.pinned() && !(fn.name && fn instanceof AST_Function) && (value = can_flatten_body(stat)) && (exp === fn diff --git a/lib/scope.js b/lib/scope.js index 17d87643..fb14480f 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -63,12 +63,12 @@ SymbolDef.prototype = { unmangleable: function(options) { if (!options) options = {}; - return (this.global && !options.toplevel) + return this.global && !options.toplevel || this.undeclared - || (!options.eval && (this.scope.uses_eval || this.scope.uses_with)) - || (options.keep_fnames + || !options.eval && this.scope.pinned() + || options.keep_fnames && (this.orig[0] instanceof AST_SymbolLambda - || this.orig[0] instanceof AST_SymbolDefun)); + || this.orig[0] instanceof AST_SymbolDefun); }, mangle: function(options) { var cache = options.cache && options.cache.props; @@ -355,7 +355,7 @@ function next_mangled_name(scope, options, def) { return name; } -AST_Symbol.DEFMETHOD("unmangleable", function(options){ +AST_Symbol.DEFMETHOD("unmangleable", function(options) { var def = this.definition(); return !def || def.unmangleable(options); }); @@ -363,16 +363,15 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){ // labels are always mangleable AST_Label.DEFMETHOD("unmangleable", return_false); -AST_Symbol.DEFMETHOD("unreferenced", function(){ - return this.definition().references.length == 0 - && !(this.scope.uses_eval || this.scope.uses_with); +AST_Symbol.DEFMETHOD("unreferenced", function() { + return !this.definition().references.length && !this.scope.pinned(); }); -AST_Symbol.DEFMETHOD("definition", function(){ +AST_Symbol.DEFMETHOD("definition", function() { return this.thedef; }); -AST_Symbol.DEFMETHOD("global", function(){ +AST_Symbol.DEFMETHOD("global", function() { return this.definition().global; }); diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 301bff1c..59990b58 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1814,3 +1814,115 @@ issue_2995: { } expect_stdout: "PASS" } + +issue_3146_1: { + options = { + collapse_vars: true, + unused: true, + } + input: { + (function(f) { + f("g()"); + })(function(a) { + eval(a); + function g(b) { + if (!b) b = "PASS"; + console.log(b); + } + }); + } + expect: { + (function(f) { + f("g()"); + })(function(a) { + eval(a); + function g(b) { + if (!b) b = "PASS"; + console.log(b); + } + }); + } + expect_stdout: "PASS" +} + +issue_3146_2: { + options = { + reduce_vars: true, + unused: true, + } + input: { + (function(f) { + f("g()"); + })(function(a) { + eval(a); + function g(b) { + if (!b) b = "PASS"; + console.log(b); + } + }); + } + expect: { + (function(f) { + f("g()"); + })(function(a) { + eval(a); + function g(b) { + if (!b) b = "PASS"; + console.log(b); + } + }); + } + expect_stdout: "PASS" +} + +issue_3146_3: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var g = "PASS"; + (function(f) { + var g = "FAIL"; + f("console.log(g)", g[g]); + })(function(a) { + eval(a); + }); + } + expect: { + var g = "PASS"; + (function(f) { + var g = "FAIL"; + f("console.log(g)", g[g]); + })(function(a) { + eval(a); + }); + } + expect_stdout: "PASS" +} + +issue_3146_4: { + options = { + reduce_vars: true, + unused: true, + } + input: { + var g = "PASS"; + (function(f) { + var g = "FAIL"; + f("console.log(g)", g[g]); + })(function(a) { + eval(a); + }); + } + expect: { + var g = "PASS"; + (function(f) { + var g = "FAIL"; + f("console.log(g)", g[g]); + })(function(a) { + eval(a); + }); + } + expect_stdout: "PASS" +} |