aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/ast.js3
-rw-r--r--lib/compress.js33
-rw-r--r--lib/scope.js19
-rw-r--r--test/compress/drop-unused.js112
4 files changed, 141 insertions, 26 deletions
diff --git a/lib/ast.js b/lib/ast.js
index b4b47651..331d340b 100644
--- a/lib/ast.js
+++ b/lib/ast.js
@@ -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"
+}